On Fri, Jul 31, 2020 at 04:06:38PM +0900, Masahiko Sawada wrote:
> > Given that the purpose of the key manager is to help TDE, discussing
> > the SQL interface part (i.g., the second patch) deviates from the
> > original purpose. I think we should discuss the design and
> > implementation of the key manager first and then other additional
> > interfaces. So I’ve attached a new version patch and removed the
> > second patch part so that we can focus on only the key manager part.
> >
> 
> Since the previous patch sets conflicts with the current HEAD, I've
> attached the rebased patch set.

I have updated the attached patch and am hoping to move this feature
forward.  The changes I made are:

*  handle merge conflicts
*  changed ssl initialization to match other places in our code
*  changed StrNCpy() to strlcpy
*  update the docs

The first three were needed to get it to compile.  I then ran some tests
using the attached shell script as my password script.  First, I found
that initdb called the script twice.  The first call worked fine, but
the second call would accept a password that didn't match the first
call.   This is because there are no keys defined, so there is nothing
for kmgr_verify_passphrase() to check for passkey verification, so it
just succeeds.   In fact, I can't figure out how to create any keys with
the patch, and pg_encrypt() is documented, but not defined anywhere.

Second, in testing starting/stopping the server, pg_ctl doesn't allow
the cluster_passphrase_command to read from /dev/tty, which I think is a
requirement because the command could likely require a user-supplied
unlock key, even if that is not the actual passphrase, just like ssl
keys.  This is because pg_ctl calls setsid() just before calling execl()
to start the server, and setsid() disassociates itself from the
controlling terminal.  I think the fix is to remove setsid() from pg_ctl
and add a postmaster flag to call setsid() after it has potentially
called cluster_passphrase_command, and pg_ctl would use that flag.

-- 
  Bruce Momjian  <br...@momjian.us>        https://momjian.us
  EnterpriseDB                             https://enterprisedb.com

  The usefulness of a cup is in its emptiness, Bruce Lee

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
new file mode 100644
index 2768c85..44e0c1e
*** a/doc/src/sgml/config.sgml
--- b/doc/src/sgml/config.sgml
*************** COPY postgres_log FROM '/full/path/to/lo
*** 7793,7798 ****
--- 7793,7833 ----
      </variablelist>
     </sect1>
  
+    <sect1 id="runtime-config-encryption">
+     <title>Encryption Key Management</title>
+ 
+     <variablelist>
+      <varlistentry id="guc-cluster-passphrase-command" xreflabel="cluster_passphrase_command">
+       <term><varname>cluster_passphrase_command</varname> (<type>string</type>)
+       <indexterm>
+        <primary><varname>cluster_passphrase_command</varname> configuration parameter</primary>
+       </indexterm>
+       </term>
+       <listitem>
+        <para>
+         This option specifies an external command to be invoked when a
+         passphrase for key management system needs to be obtained.
+        </para>
+        <para>
+         The command must print the passphrase to the standard
+         output and have a zero exit code.  In the parameter value,
+         <literal>%p</literal> is replaced by a prompt string.  (Write
+         <literal>%%</literal> for a literal <literal>%</literal>.)
+         Note that the prompt string will probably contain whitespace,
+         so be sure to quote its use adequately.  A single newline is
+         stripped from the end of the output if present.  The passphrase
+         must be at least 64 bytes.
+        </para>
+        <para>
+         This parameter can only be set in the
+         <filename>postgresql.conf</filename> file or on the server
+         command line.
+        </para>
+       </listitem>
+      </varlistentry>
+     </variablelist>
+    </sect1>
+ 
     <sect1 id="runtime-config-client">
      <title>Client Connection Defaults</title>
  
*************** dynamic_library_path = 'C:\tools\postgre
*** 9636,9641 ****
--- 9671,9692 ----
         </para>
        </listitem>
       </varlistentry>
+ 
+       <varlistentry id="guc-key-management-enabled" xreflabel="key_management_enabled">
+       <term><varname>key_management_enabled</varname> (<type>boolean</type>)
+       <indexterm>
+        <primary>Key management configuration parameter parameter</primary>
+       </indexterm>
+       </term>
+       <listitem>
+        <para>
+         Reports whether encryption key management
+         is enabled for this cluster.  See <xref
+         linkend="app-initdb-cluster-passphrase-command"/> for more
+         information.
+        </para>
+       </listitem>
+      </varlistentry>
  
       <varlistentry id="guc-data-directory-mode" xreflabel="data_directory_mode">
        <term><varname>data_directory_mode</varname> (<type>integer</type>)
diff --git a/doc/src/sgml/database-encryption.sgml b/doc/src/sgml/database-encryption.sgml
new file mode 100644
index ...db84b0d
*** a/doc/src/sgml/database-encryption.sgml
--- b/doc/src/sgml/database-encryption.sgml
***************
*** 0 ****
--- 1,292 ----
+ <!-- doc/src/sgml/database-encryption.sgml -->
+ 
+ <chapter id="database-encryption">
+  <title>Database Encryption</title>
+ 
+  <indexterm zone="database-encryption">
+   <primary>Server Side Encryption</primary>
+  </indexterm>
+ 
+  <para>
+   The purpose of database encryption is to protect the confidential data
+   stored in a database from being revealed.
+  </para>
+ 
+  <sect1 id="encryption-key-management">
+   <title>Encryption Key Management</title>
+ 
+   <para>
+    <productname>PostgreSQL</productname> supports internal
+    <firstterm>Encryption Key Management System</firstterm>, which is designed
+    to manage the life cycles of cryptographic keys within the
+    <productname>PostgreSQL</productname>.  This includes dealing with their
+    generation, storage, usage and rotation.
+   </para>
+ 
+   <para>
+    Encryption key management system is enabled when
+    <productname>PostgreSQL</productname> is built with
+    <literal>--with-openssl</literal> and
+    <xref linkend="app-initdb-cluster-passphrase-command"/> is specified during
+    <command>initdb</command>.  The cluster passphrase provided by the
+    <option>--cluster-passphrase-command</option> option during
+    <command>initdb</command> and the one generated by
+    <xref linkend="guc-cluster-passphrase-command"/> in the
+    <filename>postgresql.conf</filename> must match, otherwise, the database
+    cluster will not start up. Note that the cluster passphrase command
+    passed to <command>initdb</command> must return a passphrase of at least
+    64 bytes and less than 1024 bytes. For example.
+ <programlisting>
+ initdb -D dbname --cluster-passphrase-command="cat /path/to/passphrase-file"
+ </programlisting>
+   </para>
+ 
+   <para>
+    Encryption keys managed by <productname>PostgreSQL</productname>'s
+    key management system are only used for the internal usage such as
+    transparent data encryption in a future release.  These encryption keys
+    can never be taken out of database in plaintext form.  Also, the
+    encryption key management system creates some internal encryption keys.
+    There is no interface to add and remove these keys.
+   </para>
+ 
+   <sect2 id="key-encryption-key">
+    <title>Key Encryption Key(<acronym>KEK</acronym>)</title>
+ 
+    <para>
+     During the <command>initdb</command> process, the cluster passphrase
+     provided by <option>--cluster-passphrase-command</option>
+     is derived into a <firstterm>Key Encryption Key
+     (<acronym>KEK</acronym>)</firstterm>.  <acronym>KEK</acronym>
+     encapsulates cryptographic keys managed inside
+     <productname>PostgreSQL</productname> described in <xref
+     linkend="key-derivations"/> using by a way of authenticated encryption
+     described in <xref linkend="key-wrapping"/> before storing the keys
+     to a persistent storage. <acronym>KEK</acronym> must be stored in
+     a trusted key store, such as key vault software or services, or a
+     hardware security module.
+    </para>
+ 
+    <para>
+     When a <productname>PostgreSQL</productname> server
+     with enabled encryption key management is started, the
+     <varname>cluster_passphrase_command</varname> parameter in
+     <filename>postgresql.conf</filename> will be evaluated and the cluster
+     passphrase will be derived into <acronym>KEK</acronym> in similar
+     ways as initdb.
+    </para>
+ 
+    <para>
+     After that, the cryptographic keys will be retrieved from
+     <filename>pg_cryptokeys</filename> directory to be restored and
+     integrity-checked by the key management system using the <acronym>KEK</acronym>.
+     If this process fails, it is likely that the cluster passphrase supplied
+     to the cluster is not the same as that supplied to the
+     <command>initdb</command> process.  The cluster will refuse to start in this
+     case and user has to manually correct the cluster passphrase.
+    </para>
+ 
+    <para>
+     <acronym>KEK</acronym> is not stored physically within the
+     <productname>PostgreSQL</productname> server as they are designed
+     to be derived from the correctly configured cluster passphrase.
+    </para>
+   </sect2>
+ 
+   <sect2 id="key-derivations">
+    <title>Key Derivations</title>
+ 
+    <para>
+     Encryption key management systems can manage multiple
+     cryptographic keys that have different purposes and usages within
+     <productname>PostgreSQL</productname>.  Currently, the Postgres
+     encryption key management system manages no cryptographic keys.
+    </para>
+   </sect2>
+ 
+   <sect2 id="key-wrapping">
+    <title>Key Protection</title>
+ 
+    <para>
+     Key management system persists cryptographic keys to the disk after wrapping
+     them by <acronym>KEK</acronym>.  This section describes how key maangement
+     system wrap and unwrap key.
+    </para>
+ 
+    <para>
+     The key management system uses Encryption with Associated Data
+     (<acronym>AEAD</acronym>) to wrap cryptographic keys, which is
+     a form of encryption. In addition to providing a way to protect
+     confidential data from being revealed, it provides a way to check
+     the integrity and authenticity of some associated data.  It uses
+     the Encrypt-Then-MAC approach, based on the Advanced Encryption
+     Standard (<acronym>AES</acronym>) in Cipher Block Chaining
+     (<acronym>CBC</acronym>) mode. It uses a random initialization
+     vector (<acronym>IV</acronym>) and a <literal>HMAC-SHA</literal>
+     message authentication code (<acronym>MAC</acronym>).
+    </para>
+ 
+    <para>
+     Key management system uses two kinds of cryptographic keys for key wrapping:
+    </para>
+ 
+    <para>
+     <variablelist>
+      <varlistentry>
+       <term><literal>Encryption Key</literal></term>
+       <listitem>
+        <para>
+         Encryption key is 256 bits long randomly generate key.  It is primarily used
+         as a key for encapsulate or restore data with <acronym>AES256</acronym>
+         algorithm.
+        </para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term><literal>MAC Key</literal></term>
+       <listitem>
+        <para>
+         <acronym>MAC</acronym> key is a 512-bit randomly generated key.
+         <acronym>SHA512</acronym> is the algorithm used along with the
+         <acronym>MAC</acronym> key to compute a cryptographic hash for integrity
+         purposes.
+        </para>
+       </listitem>
+      </varlistentry>
+     </variablelist>
+    </para>
+ 
+    <para>
+     Key management systems's key wrapping algorithm is as follows:
+ 
+     <orderedlist>
+      <listitem>
+       <simpara>Generate random <acronym>IV</acronym>.</simpara>
+      </listitem>
+      <listitem>
+       <simpara>Add padding to the plaintext following PKCS#7 described in
+       <ulink url="https://tools.ietf.org/html/rfc2315";>RFC2315</ulink>.</simpara>
+      </listitem>
+      <listitem>
+       <simpara>Encrypt padded plain text with the <acronym>IV</acronym>
+        using <acronym>AES256</acronym> in <acronym>CBC</acronym>
+        mode.</simpara>
+      </listitem>
+      <listitem>
+       <simpara>Compute <acronym>HMAC</acronym> over the encrypted data.</simpara>
+      </listitem>
+      <listitem>
+       <simpara>Concatenate <acronym>HMAC</acronym>, <acronym>IV</acronym>
+       and encrypted ciphertext as the result of ciphertext.</simpara>
+      </listitem>
+     </orderedlist>
+    </para>
+ 
+    <para>
+     The length of the result ciphertext can be inferred from that of the plaintext
+     by following formula:
+ <programlisting>
+  Ciphertext Length = 64 + 16 + 16 * (floor(input_size / 16) + 1)
+ </programlisting>
+    </para>
+   </sect2>
+ 
+   <sect2 id="key-management-rotation">
+    <title>Key Rotation Process</title>
+ 
+    <para>
+     Encryption keys in general are not interminable &mdash;
+     the longer the same key is in use, the greater the chance
+     of it being breached. Performing key rotation at regular
+     intervals helps meet standardized security practices such as <ulink
+     url="https://www.pcisecuritystandards.org/";>PCI-DSS</ulink> and it is
+     a good practice in security to limit the number of encrypted bytes
+     available for a specific key version. The key lifetimes are based
+     on key length, key strength, algorithm and total number of bytes
+     enciphered. The key management system provides a efficient method to
+     perform key rotation.
+    </para>
+ 
+    <para>
+     Please be aware that the phrase <literal>"key rotation"</literal> here
+     only refers to the rotation of <acronym>KEK</acronym>. The cryptographic
+     keys managed by encryption key management system are not rotated; they
+     will in fact be the same before and after a <literal>"key rotation"</literal>.
+     This can be justified because the actual keys are never stored anywhere
+     physically, presented to user or captured in logging. What is being
+     rotated here is the <acronym>KEK</acronym> who is responsible for
+     encapsulating and restoring cryptographic keys.
+    </para>
+ 
+    <para>
+     Since <acronym>KEK</acronym> is derived from a cluster passphrase, the
+     <literal>"key rotation"</literal> ultimately refers to the rotation of
+     cluster passphrase and deriving a new <acronym>KEK</acronym> from the
+     new cluster passphrase. The new <acronym>KEK</acronym> can then be used
+     to encapsulate all encryptions keys and store the new results in
+     <filename>pg_cryptokeys</filename> directory.
+    </para>
+ 
+    <para>
+     To complete the cluster passphrase rotation, user needs to follow the
+     steps below:
+    </para>
+    <itemizedlist>
+     <listitem>
+      <para>
+       Ensure the <productname>PostgreSQL</productname> server is running
+       correctly with KMS enabled.  Passphrase rotation cannot be completed
+       with the server shut down.
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Update <xref linkend="guc-cluster-passphrase-command"/> parameter and
+       load such that the new command will return a new cluster passphrase.
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       In a session, execute <function>pg_rotate_cluster_passphrase()</function>
+       SQL function to initiate the rotation. The function returns true upon
+       successful key rotation and false if otherwise.
+ <programlisting>
+ =# SELECT pg_rotate_cluster_passphrase();
+ pg_rotate_cluster_passphrase
+ ------------------------------
+  t
+ (1 row)
+ </programlisting>
+      </para>
+     </listitem>
+    </itemizedlist>
+ 
+    <para>
+     Upon successful cluster passphrase rotation, all managed cryptographic
+     keys will be re-encapsulated by the new <acronym>KEK</acronym>
+     derived from the new cluster passphrase. The new encapsulated  keys
+     will be stored in <filename>pg_cryptokeys</filename> directory.
+     Please note that the cryptographic keys are the same as before; the
+     rotation process only changes the <acronym>KEK</acronym> that is used
+     to encapsulate and verify the actual cryptographic keys. This way,
+     there is no need to decrypt all the encrypted data with the old keys
+     and re-encrypt them with the new.
+    </para>
+ 
+    <para>
+     In case of a crash during the cluster passphrase rotation
+     process, the key management system is able to recover to the
+     previous sets of cryptographic keys the next time server starts
+     up. This is possible because the key rotation and encapsulation
+     process are done on a separate temporary key directory called
+     <filename>pg_cryptokeys_tmp</filename> and it will replace
+     <filename>pg_cryptokeys</filename> and be deleted only when everything
+     is successfully finished. If the server starts with pg_cryptokeys_tmp
+     folder present, it would indicate that previous attempt of cluster
+     passphrase rotation was not completed. In this case, the server will
+     discard <filename>pg_cryptokeys_tmp</filename> folder and load the
+     keys in <filename>pg_cryptokeys</filename> as usual.
+    </para>
+   </sect2>
+  </sect1>
+ </chapter>
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
new file mode 100644
index 38e8aa0..b96f4ac
*** a/doc/src/sgml/filelist.sgml
--- b/doc/src/sgml/filelist.sgml
***************
*** 49,54 ****
--- 49,55 ----
  <!ENTITY wal           SYSTEM "wal.sgml">
  <!ENTITY logical-replication    SYSTEM "logical-replication.sgml">
  <!ENTITY jit    SYSTEM "jit.sgml">
+ <!ENTITY database-encryption SYSTEM "database-encryption.sgml">
  
  <!-- programmer's guide -->
  <!ENTITY bgworker   SYSTEM "bgworker.sgml">
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
new file mode 100644
index e7cff98..e537e98
*** a/doc/src/sgml/func.sgml
--- b/doc/src/sgml/func.sgml
*************** SELECT m.* FROM pg_statistic_ext join pg
*** 26770,26773 ****
--- 26770,26872 ----
  
    </sect1>
  
+   <sect1 id="functions-encryption">
+    <title>Encryption Functions</title>
+ 
+    <sect2 id="functions-data-encryption">
+     <title>Data Encryption Function</title>
+     <para>
+      The functions shown in
+      <xref linkend="functions-encryption-table"/> are for encrypting
+      and decrypting data with the <literal>SQL key</literal> described in
+      <xref linkend="encryption-key-management"/>.
+     </para>
+ 
+     <table id="functions-encryption-table">
+      <title>Encryption <acronym>SQL</acronym> Functions</title>
+      <tgroup cols="3">
+       <thead>
+        <row>
+         <entry>Function</entry>
+         <entry>Return Type</entry>
+         <entry>Description</entry>
+        </row>
+       </thead>
+       <tbody>
+ 
+        <row>
+         <entry>
+          <indexterm>
+           <primary>pg_encrypt</primary>
+          </indexterm>
+          <literal><function>pg_encrypt(<parameter>data</parameter> <type>text</type>)</function></literal>
+         </entry>
+         <entry>
+          <type>bytea</type>
+         </entry>
+         <entry>
+          Encrypt the given data with the internal SQL key
+         </entry>
+        </row>
+ 
+        <row>
+         <entry>
+          <indexterm>
+           <primary>pg_unwrap</primary>
+          </indexterm>
+          <literal><function>pg_decrypt(<parameter>data</parameter> <type>bytea</type>)</function></literal>
+         </entry>
+         <entry>
+          <type>text</type>
+         </entry>
+         <entry>
+          Decrypt the given data with the internal SQL key
+         </entry>
+        </row>
+       </tbody>
+      </tgroup>
+     </table>
+    </sect2>
+ 
+    <sect2 id="functions-key-management">
+     <title>Key Management Functions</title>
+     <para>
+      The function shown in
+      <xref linkend="functions-key-management-table"/> are for encryption
+      key management described in <xref linkend="encryption-key-management"/>.
+     </para>
+ 
+     <table id="functions-key-management-table">
+      <title>Encryption Key Management <acronym>SQL</acronym> Functions</title>
+      <tgroup cols="3">
+       <thead>
+        <row>
+         <entry>Function</entry>
+         <entry>Return Type</entry>
+         <entry>Description</entry>
+        </row>
+       </thead>
+       <tbody>
+        <row>
+         <entry>
+          <indexterm>
+           <primary>pg_rotate_cluster_passphrase</primary>
+          </indexterm>
+          <literal><function>pg_rotate_cluster_passphrase()</function></literal>
+         </entry>
+         <entry>
+          <type>boolean</type>
+         </entry>
+         <entry>
+          Rotate the cluster passphrase. See
+          <xref linkend="key-management-rotation"/> for details.
+         </entry>
+        </row>
+ 
+       </tbody>
+      </tgroup>
+     </table>
+    </sect2>
+   </sect1>
+ 
  </chapter>
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
new file mode 100644
index 3ac588d..a6e55ba
*** a/doc/src/sgml/installation.sgml
--- b/doc/src/sgml/installation.sgml
*************** build-postgresql:
*** 979,986 ****
         <listitem>
          <para>
           Build with support for <acronym>SSL</acronym> (encrypted)
!          connections. This requires the <productname>OpenSSL</productname>
!          package to be installed.  <filename>configure</filename> will check
           for the required header files and libraries to make sure that
           your <productname>OpenSSL</productname> installation is sufficient
           before proceeding.
--- 979,987 ----
         <listitem>
          <para>
           Build with support for <acronym>SSL</acronym> (encrypted)
!          connections and key management. This requires the
!          <productname>OpenSSL</productname> package to be installed.
!          <filename>configure</filename> will check
           for the required header files and libraries to make sure that
           your <productname>OpenSSL</productname> installation is sufficient
           before proceeding.
diff --git a/doc/src/sgml/postgres.sgml b/doc/src/sgml/postgres.sgml
new file mode 100644
index 730d5fd..0ea7da6
*** a/doc/src/sgml/postgres.sgml
--- b/doc/src/sgml/postgres.sgml
*************** break is not needed in a wider output re
*** 171,176 ****
--- 171,177 ----
    &wal;
    &logical-replication;
    &jit;
+   &database-encryption;
    &regress;
  
   </part>
diff --git a/doc/src/sgml/ref/initdb.sgml b/doc/src/sgml/ref/initdb.sgml
new file mode 100644
index 385ac25..1d0678a
*** a/doc/src/sgml/ref/initdb.sgml
--- b/doc/src/sgml/ref/initdb.sgml
*************** PostgreSQL documentation
*** 163,168 ****
--- 163,187 ----
        </listitem>
       </varlistentry>
  
+      <varlistentry id="app-initdb-cluster-passphrase-command" xreflabel="cluster passphrase command">
+       <term><option>--cluster-passphrase-command=<replaceable class="parameter">command</replaceable></option></term>
+       <listitem>
+        <para>
+         This option specifies an external command to be invoked when a passphrase
+         for key management system needs to be obtained.
+        </para>
+        <para>
+         The command must print the passphrase to the standard output and exit
+         with code 0.  In the parameter value, <literal>%p</literal> is
+         replaced by a prompt string.  (Write <literal>%%</literal> for a
+         literal <literal>%</literal>.)  Note that the prompt string will
+         probably contain whitespace, so be sure to quote adequately.  A single
+         newline is stripped from the end of the output if present.  The passphrase
+         must be at least 64 bytes.
+        </para>
+       </listitem>
+      </varlistentry>
+ 
       <varlistentry>
        <term><option>-D <replaceable class="parameter">directory</replaceable></option></term>
        <term><option>--pgdata=<replaceable class="parameter">directory</replaceable></option></term>
diff --git a/doc/src/sgml/ref/pgupgrade.sgml b/doc/src/sgml/ref/pgupgrade.sgml
new file mode 100644
index b59c569..29b0015
*** a/doc/src/sgml/ref/pgupgrade.sgml
--- b/doc/src/sgml/ref/pgupgrade.sgml
*************** psql --username=postgres --file=script.s
*** 823,828 ****
--- 823,835 ----
     is down.
    </para>
  
+   <para>
+    During the upgrade <command>pg_upgrade</command> copies the all internal keys
+    to the new cluster. If you want to upgrade from the old cluster that enables
+    the key management to the new cluster that also enables, you must use the same
+    <varname>cluster_passphrase_command</varname> to both clusters. Otherwise
+    <command>pg_upgrade</command> fails due to mismatching the cluster passphrase.
+   </para>
   </refsect1>
  
   <refsect1>
diff --git a/doc/src/sgml/storage.sgml b/doc/src/sgml/storage.sgml
new file mode 100644
index 3234adb..05e9b26
*** a/doc/src/sgml/storage.sgml
--- b/doc/src/sgml/storage.sgml
*************** Item
*** 78,83 ****
--- 78,88 ----
  </row>
  
  <row>
+  <entry><filename>pg_cryptokeys</filename></entry>
+  <entry>Subdirectory containing cryptographic keys</entry>
+ </row>
+ 
+ <row>
   <entry><filename>pg_dynshmem</filename></entry>
   <entry>Subdirectory containing files used by the dynamic shared memory
    subsystem</entry>
diff --git a/src/backend/Makefile b/src/backend/Makefile
new file mode 100644
index 9706a95..4ace302
*** a/src/backend/Makefile
--- b/src/backend/Makefile
*************** SUBDIRS = access bootstrap catalog parse
*** 21,27 ****
  	main nodes optimizer partitioning port postmaster \
  	regex replication rewrite \
  	statistics storage tcop tsearch utils $(top_builddir)/src/timezone \
! 	jit
  
  include $(srcdir)/common.mk
  
--- 21,27 ----
  	main nodes optimizer partitioning port postmaster \
  	regex replication rewrite \
  	statistics storage tcop tsearch utils $(top_builddir)/src/timezone \
! 	jit crypto
  
  include $(srcdir)/common.mk
  
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
new file mode 100644
index 52a67b1..d392911
*** a/src/backend/access/transam/xlog.c
--- b/src/backend/access/transam/xlog.c
***************
*** 44,49 ****
--- 44,50 ----
  #include "commands/tablespace.h"
  #include "common/controldata_utils.h"
  #include "executor/instrument.h"
+ #include "crypto/kmgr.h"
  #include "miscadmin.h"
  #include "pg_trace.h"
  #include "pgstat.h"
***************
*** 81,86 ****
--- 82,88 ----
  #include "utils/timestamp.h"
  
  extern uint32 bootstrap_data_checksum_version;
+ extern uint32 bootstrap_key_management_version;
  
  /* Unsupported old recovery command file names (relative to $PGDATA) */
  #define RECOVERY_COMMAND_FILE	"recovery.conf"
*************** InitControlFile(uint64 sysidentifier)
*** 4607,4612 ****
--- 4609,4615 ----
  	ControlFile->wal_log_hints = wal_log_hints;
  	ControlFile->track_commit_timestamp = track_commit_timestamp;
  	ControlFile->data_checksum_version = bootstrap_data_checksum_version;
+ 	ControlFile->key_management_version = bootstrap_key_management_version;
  }
  
  static void
*************** ReadControlFile(void)
*** 4894,4899 ****
--- 4897,4905 ----
  	/* Make the initdb settings visible as GUC variables, too */
  	SetConfigOption("data_checksums", DataChecksumsEnabled() ? "yes" : "no",
  					PGC_INTERNAL, PGC_S_OVERRIDE);
+ 
+ 	SetConfigOption("key_management", KeyManagementEnabled() ? "yes" : "no",
+ 					PGC_INTERNAL, PGC_S_OVERRIDE);
  }
  
  /*
*************** DataChecksumsEnabled(void)
*** 4937,4942 ****
--- 4943,4958 ----
  }
  
  /*
+  * Are key management enabled?
+  */
+ bool
+ KeyManagementEnabled(void)
+ {
+ 	Assert(ControlFile != NULL);
+ 	return (ControlFile->key_management_version > 0);
+ }
+ 
+ /*
   * Returns a fake LSN for unlogged relations.
   *
   * Each call generates an LSN that is greater than any previous value
*************** BootStrapXLOG(void)
*** 5343,5348 ****
--- 5359,5368 ----
  	/* some additional ControlFile fields are set in WriteControlFile() */
  	WriteControlFile();
  
+ 	/* Enable key manager if required */
+ 	if (ControlFile->key_management_version > 0)
+ 		BootStrapKmgr();
+ 
  	/* Bootstrap the commit log, too */
  	BootStrapCLOG();
  	BootStrapCommitTs();
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
new file mode 100644
index 76b2f50..8c46b20
*** a/src/backend/bootstrap/bootstrap.c
--- b/src/backend/bootstrap/bootstrap.c
***************
*** 28,33 ****
--- 28,34 ----
  #include "catalog/pg_collation.h"
  #include "catalog/pg_type.h"
  #include "common/link-canary.h"
+ #include "crypto/kmgr.h"
  #include "libpq/pqsignal.h"
  #include "miscadmin.h"
  #include "nodes/makefuncs.h"
***************
*** 51,56 ****
--- 52,58 ----
  #include "utils/relmapper.h"
  
  uint32		bootstrap_data_checksum_version = 0;	/* No checksum */
+ uint32		bootstrap_key_management_version = 0;	/* disabled */
  
  
  static void CheckerModeMain(void);
*************** AuxiliaryProcessMain(int argc, char *arg
*** 224,230 ****
  	/* If no -x argument, we are a CheckerProcess */
  	MyAuxProcType = CheckerProcess;
  
! 	while ((flag = getopt(argc, argv, "B:c:d:D:Fkr:x:X:-:")) != -1)
  	{
  		switch (flag)
  		{
--- 226,232 ----
  	/* If no -x argument, we are a CheckerProcess */
  	MyAuxProcType = CheckerProcess;
  
! 	while ((flag = getopt(argc, argv, "B:c:d:D:eFkr:x:X:-:")) != -1)
  	{
  		switch (flag)
  		{
*************** AuxiliaryProcessMain(int argc, char *arg
*** 247,252 ****
--- 249,257 ----
  					pfree(debugstr);
  				}
  				break;
+ 			case 'e':
+ 				bootstrap_key_management_version = KMGR_VERSION;
+ 				break;
  			case 'F':
  				SetConfigOption("fsync", "false", PGC_POSTMASTER, PGC_S_ARGV);
  				break;
diff --git a/src/backend/crypto/Makefile b/src/backend/crypto/Makefile
new file mode 100644
index ...c273620
*** a/src/backend/crypto/Makefile
--- b/src/backend/crypto/Makefile
***************
*** 0 ****
--- 1,18 ----
+ #-------------------------------------------------------------------------
+ #
+ # Makefile
+ #    Makefile for src/backend/crypto
+ #
+ # IDENTIFICATION
+ #    src/backend/crypto/Makefile
+ #
+ #-------------------------------------------------------------------------
+ 
+ subdir = src/backend/crypto
+ top_builddir = ../../..
+ include $(top_builddir)/src/Makefile.global
+ 
+ OBJS = \
+ 	kmgr.o
+ 
+ include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/crypto/kmgr.c b/src/backend/crypto/kmgr.c
new file mode 100644
index ...2a37c38
*** a/src/backend/crypto/kmgr.c
--- b/src/backend/crypto/kmgr.c
***************
*** 0 ****
--- 1,457 ----
+ /*-------------------------------------------------------------------------
+  *
+  * kmgr.c
+  *	 Key manager routines
+  *
+  * Key manager is enabled if user requests during initdb.  During bootstrap,
+  * we generate internal keys, wrap them with KEK which is derived from the
+  * user-provided passphrase, and store them into each file located at KMGR_DIR.
+  * Once generated, these are not changed.  During startup, we decrypt all
+  * internal keys and load them to the shared memory space.  Internal keys on
+  * the shared memory are read-only.  All wrapping and unwrapping key routines
+  * depends on openssl library for now.
+  *
+  * Copyright (c) 2020, PostgreSQL Global Development Group
+  *
+  * IDENTIFICATION
+  *	  src/backend/crypto/kmgr.c
+  *-------------------------------------------------------------------------
+  */
+ 
+ #include "postgres.h"
+ 
+ #include <sys/stat.h>
+ #include <unistd.h>
+ 
+ #include "funcapi.h"
+ #include "miscadmin.h"
+ #include "pgstat.h"
+ 
+ #include "common/sha2.h"
+ #include "common/kmgr_utils.h"
+ #include "crypto/kmgr.h"
+ #include "storage/fd.h"
+ #include "storage/ipc.h"
+ #include "storage/shmem.h"
+ #include "utils/builtins.h"
+ #include "utils/guc.h"
+ #include "utils/memutils.h"
+ 
+ /* Struct stores internal keys in plaintext format */
+ typedef struct KmgrShmemData
+ {
+ 	/*
+ 	 * Internal cryptographic keys. Keys are stored at its ID'th.
+ 	 */
+ 	CryptoKey	intlKeys[KMGR_MAX_INTERNAL_KEYS];
+ } KmgrShmemData;
+ static KmgrShmemData *KmgrShmem;
+ 
+ /*
+  * Key lengths in bytes of internal keys.
+  *
+  * No key supported for now.
+  */
+ static int internalKeyLengths[KMGR_MAX_INTERNAL_KEYS];
+ 
+ /* GUC variables */
+ bool		key_management_enabled = false;;
+ char	   *cluster_passphrase_command = NULL;
+ 
+ static void KmgrSaveCryptoKeys(const char *dir, CryptoKey *keys);
+ static CryptoKey *generate_crypto_key(int len);
+ static void recoverIncompleteRotation(void);
+ 
+ /*
+  * This function must be called ONCE on system install.
+  */
+ void
+ BootStrapKmgr(void)
+ {
+ 	PgKeyWrapCtx	*ctx;
+ 	CryptoKey	keys_wrap[KMGR_MAX_INTERNAL_KEYS];
+ 	char		passphrase[KMGR_MAX_PASSPHRASE_LEN];
+ 	uint8		kekenc[KMGR_ENC_KEY_LEN];
+ 	uint8		kekhmac[KMGR_MAC_KEY_LEN];
+ 	int			passlen;
+ 
+ 	/*
+ 	 * Requirement check. We need openssl library to enable key management
+ 	 * because all encryption and decryption calls happen via openssl function
+ 	 * calls.
+ 	 */
+ #ifndef USE_OPENSSL
+ 	ereport(ERROR,
+ 			(errcode(ERRCODE_CONFIG_FILE_ERROR),
+ 			 (errmsg("cluster encryption is not supported because OpenSSL is not supported by this build"),
+ 			  errhint("Compile with --with-openssl to use cluster encryption."))));
+ #endif
+ 
+ 	memset(keys_wrap, 0, sizeof(keys_wrap));
+ 
+ 	/* Get key encryption key from the passphrase command */
+ 	passlen = kmgr_run_cluster_passphrase_command(cluster_passphrase_command,
+ 												  passphrase, KMGR_MAX_PASSPHRASE_LEN);
+ 	if (passlen < KMGR_MIN_PASSPHRASE_LEN)
+ 		ereport(ERROR,
+ 				(errmsg("passphrase must be more than %d bytes",
+ 						KMGR_MIN_PASSPHRASE_LEN)));
+ 
+ 	/* Get key encryption key and HMAC key from passphrase */
+ 	kmgr_derive_keys(passphrase, passlen, kekenc, kekhmac);
+ 
+ 	/* Create temporarily key wrap context */
+ 	ctx = pg_create_keywrap_ctx(kekenc, kekhmac);
+ 	if (!ctx)
+ 		elog(ERROR, "could not initialize encryption contect");
+ 
+ 	/* Wrap all internal keys by key encryption key */
+ 	for (int id = 0; id < KMGR_MAX_INTERNAL_KEYS; id++)
+ 	{
+ 		CryptoKey *key;
+ 
+ 		/* generate an internal key */
+ 		key = generate_crypto_key(internalKeyLengths[id]);
+ 
+ 		if (!kmgr_wrap_key(ctx, key, &(keys_wrap[id])))
+ 		{
+ 			pg_free_keywrap_ctx(ctx);
+ 			elog(ERROR, "failed to wrap cluster encryption key");
+ 		}
+ 	}
+ 
+ 	/* Save internal keys to the disk */
+ 	KmgrSaveCryptoKeys(KMGR_DIR, keys_wrap);
+ 
+ 	pg_free_keywrap_ctx(ctx);
+ }
+ 
+ /* Report shared-memory space needed by KmgrShmem */
+ Size
+ KmgrShmemSize(void)
+ {
+ 	if (!key_management_enabled)
+ 		return 0;
+ 
+ 	return MAXALIGN(sizeof(KmgrShmemData));
+ }
+ 
+ /* Allocate and initialize key manager memory */
+ void
+ KmgrShmemInit(void)
+ {
+ 	bool	found;
+ 
+ 	if (!key_management_enabled)
+ 		return;
+ 
+ 	KmgrShmem = (KmgrShmemData *) ShmemInitStruct("Key manager",
+ 												  KmgrShmemSize(), &found);
+ 
+ 	if (!found)
+ 		memset(KmgrShmem, 0, KmgrShmemSize());
+ }
+ 
+ /*
+  * Get encryption key passphrase and verify it, then get the internal keys.
+  * This function is called by postmaster at startup time.
+  */
+ void
+ InitializeKmgr(void)
+ {
+ 	CryptoKey	*keys_wrap;
+ 	char		passphrase[KMGR_MAX_PASSPHRASE_LEN];
+ 	int			passlen;
+ 	int			nkeys;
+ 
+ 	if (!key_management_enabled)
+ 		return;
+ 
+ 	elog(DEBUG1, "starting up key management system");
+ 
+ 	/* Recover the failure of the last passphrase rotation if necessary */
+ 	recoverIncompleteRotation();
+ 
+ 	/* Get the crypto keys from the file */
+ 	keys_wrap = kmgr_get_cryptokeys(KMGR_DIR, &nkeys);
+ 	Assert(nkeys == KMGR_MAX_INTERNAL_KEYS);
+ 
+ 	/* Get cluster passphrase */
+ 	passlen = kmgr_run_cluster_passphrase_command(cluster_passphrase_command,
+ 												  passphrase, KMGR_MAX_PASSPHRASE_LEN);
+ 
+ 	/*
+ 	 * Verify passphrase and prepare an internal key in plaintext on shared memory.
+ 	 *
+ 	 * XXX: do we need to prevent internal keys from being swapped out using
+ 	 * mlock?
+ 	 */
+ 	if (!kmgr_verify_passphrase(passphrase, passlen, keys_wrap, KmgrShmem->intlKeys,
+ 								KMGR_MAX_INTERNAL_KEYS))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("cluster passphrase does not match expected passphrase")));
+ }
+ 
+ const CryptoKey *
+ KmgrGetKey(int id)
+ {
+ 	Assert(id < KMGR_MAX_INTERNAL_KEYS);
+ 
+ 	return (const CryptoKey *) &(KmgrShmem->intlKeys[id]);
+ }
+ 
+ /* Generate an empty CryptoKey */
+ static CryptoKey *
+ generate_crypto_key(int len)
+ {
+ 	CryptoKey *newkey;
+ 
+ 	Assert(len < KMGR_MAX_KEY_LEN);
+ 	newkey = (CryptoKey *) palloc0(sizeof(CryptoKey));
+ 
+ 	if (!pg_strong_random(newkey->key, len))
+ 		elog(ERROR, "failed to generate new crypto key");
+ 
+ 	newkey->klen = len;
+ 
+ 	return newkey;
+ }
+ 
+ /*
+  * Save the given crypto keys to the disk. We don't need CRC check for crypto
+  * keys because these keys have HMAC which is used for integrity check
+  * during unwrapping.
+  */
+ static void
+ KmgrSaveCryptoKeys(const char *dir, CryptoKey *keys)
+ {
+ 	elog(DEBUG2, "saving all cryptographic keys");
+ 
+ 	for (int i = 0; i < KMGR_MAX_INTERNAL_KEYS; i++)
+ 	{
+ 		int			fd;
+ 		char		path[MAXPGPATH];
+ 
+ 		CryptoKeyFilePath(path, dir, i);
+ 
+ 		if ((fd = BasicOpenFile(path, O_RDWR | O_CREAT | O_EXCL | PG_BINARY)) < 0)
+ 			ereport(ERROR,
+ 					(errcode_for_file_access(),
+ 					 errmsg("could not open file \"%s\": %m",
+ 							path)));
+ 
+ 		errno = 0;
+ 		pgstat_report_wait_start(WAIT_EVENT_KEY_FILE_WRITE);
+ 		if (write(fd, &(keys[i]), sizeof(CryptoKey)) != sizeof(CryptoKey))
+ 		{
+ 			/* if write didn't set errno, assume problem is no disk space */
+ 			if (errno == 0)
+ 				errno = ENOSPC;
+ 
+ 			ereport(ERROR,
+ 					(errcode_for_file_access(),
+ 					 errmsg("could not write file \"%s\": %m",
+ 							path)));
+ 		}
+ 		pgstat_report_wait_end();
+ 
+ 		pgstat_report_wait_start(WAIT_EVENT_KEY_FILE_SYNC);
+ 		if (pg_fsync(fd) != 0)
+ 			ereport(PANIC,
+ 					(errcode_for_file_access(),
+ 					 errmsg("could not fsync file \"%s\": %m",
+ 							path)));
+ 		pgstat_report_wait_end();
+ 
+ 		if (close(fd) != 0)
+ 			ereport(ERROR,
+ 					(errcode_for_file_access(),
+ 					 errmsg("could not close file \"%s\": %m",
+ 							path)));
+ 	}
+ }
+ 
+ 
+ /*
+  * Check the last passphrase rotation was completed. If not, we decide which wrapped
+  * keys will be used according to the status of temporary directory and its wrapped
+  * keys.
+  */
+ static void
+ recoverIncompleteRotation(void)
+ {
+ 	struct stat st;
+ 	struct stat st_tmp;
+ 	CryptoKey *keys;
+ 	int			nkeys_tmp;
+ 
+ 	/* The cluster passphrase rotation was completed, nothing to do */
+ 	if (stat(KMGR_TMP_DIR, &st_tmp) != 0)
+ 		return;
+ 
+ 	/*
+ 	 * If there is only temporary directory, it means that the previous
+ 	 * rotation failed after wrapping the all internal keys by the new
+ 	 * passphrase.  Therefore we use the new cluster passphrase.
+ 	 */
+ 	if (stat(KMGR_DIR, &st) != 0)
+ 	{
+ 		ereport(DEBUG1,
+ 				(errmsg("there is only temporary directory, use the newly wrapped keys")));
+ 
+ 		if (rename(KMGR_TMP_DIR, KMGR_DIR) != 0)
+ 			ereport(ERROR,
+ 					errmsg("could not rename directory \"%s\" to \"%s\": %m",
+ 						   KMGR_TMP_DIR, KMGR_DIR));
+ 		ereport(LOG,
+ 				errmsg("cryptographic keys wrapped by new passphrase command are chosen"),
+ 				errdetail("last cluster passphrase rotation failed in the middle"));
+ 		return;
+ 	}
+ 
+ 	/*
+ 	 * In case where both the original directory and temporary directory
+ 	 * exist, there are two possibilities: (a) the all internal keys are
+ 	 * wrapped by the new passphrase but rotation failed before removing the
+ 	 * original directory, or (b) the rotation failed during wrapping internal
+ 	 * keys by the new passphrase.  In case of (a) we need to use the wrapped
+ 	 * keys in the temporary directory as rotation is essentially completed,
+ 	 * but in case of (b) we use the wrapped keys in the original directory.
+ 	 *
+ 	 * To check the possibility of (b) we validate the wrapped keys in the
+ 	 * temporary directory by checking the number of wrapped keys.  Since the
+ 	 * wrapped key length is smaller than one disk sector, which is 512 bytes
+ 	 * on common hardware, saving wrapped key is atomic write. So we can
+ 	 * ensure that the all wrapped keys are valid if the number of wrapped
+ 	 * keys in the temporary directory is KMGR_MAX_INTERNAL_KEYS.
+ 	 */
+ 	keys = kmgr_get_cryptokeys(KMGR_TMP_DIR, &nkeys_tmp);
+ 
+ 	if (nkeys_tmp == KMGR_MAX_INTERNAL_KEYS)
+ 	{
+ 		/*
+ 		 * This is case (a), the all wrapped keys in temporary directory are
+ 		 * valid. Remove the original directory and rename.
+ 		 */
+ 		ereport(DEBUG1,
+ 				(errmsg("last passphrase rotation failed before renaming direcotry name, use the newly wrapped keys")));
+ 
+ 		if (!rmtree(KMGR_DIR, true))
+ 			ereport(ERROR,
+ 					(errmsg("could not remove directory \"%s\"",
+ 							KMGR_DIR)));
+ 		if (rename(KMGR_TMP_DIR, KMGR_DIR) != 0)
+ 			ereport(ERROR,
+ 					errmsg("could not rename directory \"%s\" to \"%s\": %m",
+ 						   KMGR_TMP_DIR, KMGR_DIR));
+ 
+ 		ereport(LOG,
+ 				errmsg("cryptographic keys wrapped by new passphrase command are chosen"),
+ 				errdetail("last cluster passphrase rotation failed in the middle"));
+ 	}
+ 	else
+ 	{
+ 		/*
+ 		 * This is case (b), the last passphrase rotation failed during
+ 		 * wrapping keys. Remove the keys in the temporary directory and use
+ 		 * keys in the original keys.
+ 		 */
+ 		ereport(DEBUG1,
+ 				(errmsg("last passphrase rotation failed during wrapping keys, use the old wrapped keys")));
+ 
+ 		if (!rmtree(KMGR_TMP_DIR, true))
+ 			ereport(ERROR,
+ 					(errmsg("could not remove directory \"%s\"",
+ 							KMGR_DIR)));
+ 		ereport(LOG,
+ 				errmsg("cryptographic keys wrapped by old passphrase command are chosen"),
+ 				errdetail("last cluster passphrase rotation failed in the middle"));
+ 	}
+ 
+ 	pfree(keys);
+ }
+ 
+ /*
+  * SQL function to rotate the cluster passphrase. This function assumes that
+  * the cluster_passphrase_command is already reloaded to the new value.
+  * All internal keys are wrapped by the new passphrase and saved to the disk.
+  * To update all crypto keys atomically we save the newly wrapped keys to the
+  * temporary directory, pg_cryptokeys_tmp, and remove the original directory,
+  * pg_cryptokeys, and rename it. These operation is performed without the help
+  * of WAL.  In the case of failure during rotationpg_cryptokeys directory and
+  * pg_cryptokeys_tmp directory can be left in incomplete status.  We recover
+  * the incomplete situation by checkIncompleteRotation.
+  */
+ Datum
+ pg_rotate_cluster_passphrase(PG_FUNCTION_ARGS)
+ {
+ 	PgKeyWrapCtx	*ctx;
+ 	CryptoKey	newkeys[KMGR_MAX_INTERNAL_KEYS];
+ 	char		passphrase[KMGR_MAX_PASSPHRASE_LEN];
+ 	uint8		new_kekenc[KMGR_ENC_KEY_LEN];
+ 	uint8		new_kekhmac[KMGR_MAC_KEY_LEN];
+ 	int			passlen;
+ 
+ 	if (!key_management_enabled)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 				 errmsg("could not rotate cluster passphrase because key management is not supported")));
+ 
+ 	memset(newkeys, 0, sizeof(newkeys));
+ 
+ 	/* Recover the failure of the last passphrase rotation if necessary */
+ 	recoverIncompleteRotation();
+ 
+ 	passlen = kmgr_run_cluster_passphrase_command(cluster_passphrase_command,
+ 												  passphrase,
+ 												  KMGR_MAX_PASSPHRASE_LEN);
+ 	if (passlen < KMGR_MIN_PASSPHRASE_LEN)
+ 		ereport(ERROR,
+ 				(errmsg("passphrase must be more than %d bytes",
+ 						KMGR_MIN_PASSPHRASE_LEN)));
+ 
+ 	/* Get new key encryption key and encryption context */
+ 	kmgr_derive_keys(passphrase, passlen, new_kekenc, new_kekhmac);
+ 	ctx = pg_create_keywrap_ctx(new_kekenc, new_kekhmac);
+ 	if (!ctx)
+ 		elog(ERROR, "could not initialize encryption contect");
+ 
+ 	for (int id = 0; id < KMGR_MAX_INTERNAL_KEYS; id++)
+ 	{
+ 		if (!kmgr_wrap_key(ctx, &(KmgrShmem->intlKeys[id]), &(newkeys[id])))
+ 			elog(ERROR, "failed to wrap key");
+ 	}
+ 
+ 	/* Create temporary directory */
+ 	if (MakePGDirectory(KMGR_TMP_DIR) < 0)
+ 		ereport(ERROR,
+ 				(errcode_for_file_access(),
+ 				 errmsg("could not create temporary directory \"%s\": %m",
+ 						KMGR_TMP_DIR)));
+ 	fsync_fname(KMGR_TMP_DIR, true);
+ 
+ 	/* Prevent concurrent key rotation */
+ 	LWLockAcquire(KmgrFileLock, LW_EXCLUSIVE);
+ 
+ 	/* Save the key wrapped by the new passphrase to the temporary directory */
+ 	KmgrSaveCryptoKeys(KMGR_TMP_DIR, newkeys);
+ 
+ 	/* Remove the original directory */
+ 	if (!rmtree(KMGR_DIR, true))
+ 		ereport(ERROR,
+ 				(errmsg("could not remove directory \"%s\"",
+ 						KMGR_DIR)));
+ 
+ 	/* Rename to the original directory */
+ 	if (rename(KMGR_TMP_DIR, KMGR_DIR) != 0)
+ 		ereport(ERROR,
+ 				(errmsg("could not rename directory \"%s\" to \"%s\": %m",
+ 						KMGR_TMP_DIR, KMGR_DIR)));
+ 	fsync_fname(KMGR_DIR, true);
+ 
+ 	LWLockRelease(KmgrFileLock);
+ 
+ 	pg_free_keywrap_ctx(ctx);
+ 	PG_RETURN_BOOL(true);
+ }
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
new file mode 100644
index 8b21ff4..744fa96
*** a/src/backend/libpq/be-secure-openssl.c
--- b/src/backend/libpq/be-secure-openssl.c
***************
*** 30,35 ****
--- 30,36 ----
  #endif
  
  #include <openssl/ssl.h>
+ #include <openssl/conf.h>
  #include <openssl/dh.h>
  #include <openssl/conf.h>
  #ifndef OPENSSL_NO_ECDH
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
new file mode 100644
index 822f0eb..a1faea7
*** a/src/backend/postmaster/pgstat.c
--- b/src/backend/postmaster/pgstat.c
*************** pgstat_get_wait_io(WaitEventIO w)
*** 4134,4139 ****
--- 4134,4148 ----
  		case WAIT_EVENT_DSM_FILL_ZERO_WRITE:
  			event_name = "DSMFillZeroWrite";
  			break;
+ 		case WAIT_EVENT_KEY_FILE_READ:
+ 			event_name = "KeyFileRead";
+ 			break;
+ 		case WAIT_EVENT_KEY_FILE_WRITE:
+ 			event_name = "KeyFileWrite";
+ 			break;
+ 		case WAIT_EVENT_KEY_FILE_SYNC:
+ 			event_name = "KeyFileSync";
+ 			break;
  		case WAIT_EVENT_LOCK_FILE_ADDTODATADIR_READ:
  			event_name = "LockFileAddToDataDirRead";
  			break;
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
new file mode 100644
index 959e3b8..6ebab83
*** a/src/backend/postmaster/postmaster.c
--- b/src/backend/postmaster/postmaster.c
***************
*** 100,105 ****
--- 100,106 ----
  #include "common/file_perm.h"
  #include "common/ip.h"
  #include "common/string.h"
+ #include "crypto/kmgr.h"
  #include "lib/ilist.h"
  #include "libpq/auth.h"
  #include "libpq/libpq.h"
*************** PostmasterMain(int argc, char *argv[])
*** 1351,1356 ****
--- 1352,1362 ----
  	autovac_init();
  
  	/*
+ 	 * Initialize key manager.
+ 	 */
+ 	InitializeKmgr();
+ 
+ 	/*
  	 * Load configuration files for client authentication.
  	 */
  	if (!load_hba())
diff --git a/src/backend/replication/basebackup.c b/src/backend/replication/basebackup.c
new file mode 100644
index b89df01..6ad051d
*** a/src/backend/replication/basebackup.c
--- b/src/backend/replication/basebackup.c
***************
*** 18,23 ****
--- 18,24 ----
  
  #include "access/xlog_internal.h"	/* for pg_start/stop_backup */
  #include "catalog/pg_type.h"
+ #include "common/kmgr_utils.h"
  #include "common/file_perm.h"
  #include "commands/progress.h"
  #include "lib/stringinfo.h"
*************** struct exclude_list_item
*** 152,157 ****
--- 153,161 ----
   */
  static const char *const excludeDirContents[] =
  {
+ 	/* Skip temporary crypto key files */
+ 	KMGR_TMP_DIR,
+ 
  	/*
  	 * Skip temporary statistics files. PG_STAT_TMP_DIR must be skipped even
  	 * when stats_temp_directory is set because PGSS_TEXT_FILE is always
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
new file mode 100644
index 96c2aaa..64fe10c
*** a/src/backend/storage/ipc/ipci.c
--- b/src/backend/storage/ipc/ipci.c
***************
*** 23,28 ****
--- 23,29 ----
  #include "access/syncscan.h"
  #include "access/twophase.h"
  #include "commands/async.h"
+ #include "crypto/kmgr.h"
  #include "miscadmin.h"
  #include "pgstat.h"
  #include "postmaster/autovacuum.h"
*************** CreateSharedMemoryAndSemaphores(void)
*** 149,154 ****
--- 150,156 ----
  		size = add_size(size, BTreeShmemSize());
  		size = add_size(size, SyncScanShmemSize());
  		size = add_size(size, AsyncShmemSize());
+ 		size = add_size(size, KmgrShmemSize());
  #ifdef EXEC_BACKEND
  		size = add_size(size, ShmemBackendArraySize());
  #endif
*************** CreateSharedMemoryAndSemaphores(void)
*** 267,272 ****
--- 269,275 ----
  	BTreeShmemInit();
  	SyncScanShmemInit();
  	AsyncShmemInit();
+ 	KmgrShmemInit();
  
  #ifdef EXEC_BACKEND
  
diff --git a/src/backend/storage/lmgr/lwlocknames.txt b/src/backend/storage/lmgr/lwlocknames.txt
new file mode 100644
index 774292f..a44805d
*** a/src/backend/storage/lmgr/lwlocknames.txt
--- b/src/backend/storage/lmgr/lwlocknames.txt
*************** XactTruncationLock					44
*** 53,55 ****
--- 53,56 ----
  # 45 was XactTruncationLock until removal of BackendRandomLock
  WrapLimitsVacuumLock				46
  NotifyQueueTailLock					47
+ KmgrFileLock					48
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
new file mode 100644
index 411cfad..7572f18
*** a/src/backend/tcop/postgres.c
--- b/src/backend/tcop/postgres.c
***************
*** 42,47 ****
--- 42,48 ----
  #include "catalog/pg_type.h"
  #include "commands/async.h"
  #include "commands/prepare.h"
+ #include "crypto/kmgr.h"
  #include "executor/spi.h"
  #include "jit/jit.h"
  #include "libpq/libpq.h"
*************** PostgresMain(int argc, char *argv[],
*** 3902,3907 ****
--- 3903,3915 ----
  	BaseInit();
  
  	/*
+ 	 * Initialize kmgr for cluster encryption. Since kmgr needs to attach to
+ 	 * shared memory the initialization must be called after BaseInit().
+ 	 */
+ 	if (!IsUnderPostmaster)
+ 		InitializeKmgr();
+ 
+ 	/*
  	 * Create a per-backend PGPROC struct in shared memory, except in the
  	 * EXEC_BACKEND case where this was done in SubPostmasterMain. We must do
  	 * this before we can use LWLocks (and in the EXEC_BACKEND case we already
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
new file mode 100644
index a62d64e..411f068
*** a/src/backend/utils/misc/guc.c
--- b/src/backend/utils/misc/guc.c
***************
*** 47,52 ****
--- 47,53 ----
  #include "commands/vacuum.h"
  #include "commands/variable.h"
  #include "common/string.h"
+ #include "crypto/kmgr.h"
  #include "funcapi.h"
  #include "jit/jit.h"
  #include "libpq/auth.h"
*************** const char *const config_group_names[] =
*** 745,750 ****
--- 746,753 ----
  	gettext_noop("Statistics / Monitoring"),
  	/* STATS_COLLECTOR */
  	gettext_noop("Statistics / Query and Index Statistics Collector"),
+ 	/* ENCRYPTION */
+ 	gettext_noop("Encryption"),
  	/* AUTOVACUUM */
  	gettext_noop("Autovacuum"),
  	/* CLIENT_CONN */
*************** static struct config_bool ConfigureNames
*** 2036,2041 ****
--- 2039,2055 ----
  		NULL, NULL, NULL
  	},
  
+ 	{
+ 		{"key_management", PGC_INTERNAL, PRESET_OPTIONS,
+ 		 gettext_noop("Show whether key management is enabled for this cluster."),
+ 		 NULL,
+ 		 GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE
+ 		},
+ 		&key_management_enabled,
+ 		false,
+ 		NULL, NULL, NULL
+ 	},
+ 
  	/* End-of-list marker */
  	{
  		{NULL, 0, 0, NULL, NULL}, NULL, false, NULL, NULL, NULL
*************** static struct config_string ConfigureNam
*** 4392,4397 ****
--- 4406,4421 ----
  		"",
  		NULL, NULL, NULL
  	},
+ 
+ 	{
+ 		{"cluster_passphrase_command", PGC_SIGHUP, ENCRYPTION,
+ 			gettext_noop("Command to obtain passphrase for database encryption."),
+ 			NULL
+ 		},
+ 		&cluster_passphrase_command,
+ 		"",
+ 		NULL, NULL, NULL
+ 	},
  
  	{
  		{"application_name", PGC_USERSET, LOGGING_WHAT,
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
new file mode 100644
index 9cb571f..fc30641
*** a/src/backend/utils/misc/postgresql.conf.sample
--- b/src/backend/utils/misc/postgresql.conf.sample
***************
*** 633,638 ****
--- 633,643 ----
  					# autovacuum, -1 means use
  					# vacuum_cost_limit
  
+ #------------------------------------------------------------------------------
+ # ENCRYPTION
+ #------------------------------------------------------------------------------
+ 
+ #cluster_passphrase_command = ''
  
  #------------------------------------------------------------------------------
  # CLIENT CONNECTION DEFAULTS
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
new file mode 100644
index ee3bfa8..f088151
*** a/src/bin/initdb/initdb.c
--- b/src/bin/initdb/initdb.c
*************** static bool data_checksums = false;
*** 146,151 ****
--- 146,152 ----
  static char *xlog_dir = NULL;
  static char *str_wal_segment_size_mb = NULL;
  static int	wal_segment_size_mb;
+ static char *cluster_passphrase = NULL;
  
  
  /* internal vars */
*************** static const char *const subdirs[] = {
*** 203,208 ****
--- 204,210 ----
  	"global",
  	"pg_wal/archive_status",
  	"pg_commit_ts",
+ 	"pg_cryptokeys",
  	"pg_dynshmem",
  	"pg_notify",
  	"pg_serial",
*************** setup_config(void)
*** 1185,1190 ****
--- 1187,1199 ----
  								  "password_encryption = md5");
  	}
  
+ 	if (cluster_passphrase)
+ 	{
+ 		snprintf(repltok, sizeof(repltok), "cluster_passphrase_command = '%s'",
+ 				 escape_quotes(cluster_passphrase));
+ 		conflines = replace_token(conflines, "#cluster_passphrase_command = ''", repltok);
+ 	}
+ 
  	/*
  	 * If group access has been enabled for the cluster then it makes sense to
  	 * ensure that the log files also allow group access.  Otherwise a backup
*************** bootstrap_template1(void)
*** 1395,1408 ****
  	unsetenv("PGCLIENTENCODING");
  
  	snprintf(cmd, sizeof(cmd),
! 			 "\"%s\" --boot -x1 -X %u %s %s %s",
  			 backend_exec,
  			 wal_segment_size_mb * (1024 * 1024),
  			 data_checksums ? "-k" : "",
  			 boot_options,
  			 debug ? "-d 5" : "");
  
- 
  	PG_CMD_OPEN;
  
  	for (line = bki_lines; *line != NULL; line++)
--- 1404,1417 ----
  	unsetenv("PGCLIENTENCODING");
  
  	snprintf(cmd, sizeof(cmd),
! 			 "\"%s\" --boot -x1 -X %u %s %s %s %s",
  			 backend_exec,
  			 wal_segment_size_mb * (1024 * 1024),
  			 data_checksums ? "-k" : "",
+ 			 cluster_passphrase ? "-e" : "",
  			 boot_options,
  			 debug ? "-d 5" : "");
  
  	PG_CMD_OPEN;
  
  	for (line = bki_lines; *line != NULL; line++)
*************** usage(const char *progname)
*** 2290,2295 ****
--- 2299,2306 ----
  	printf(_("      --wal-segsize=SIZE    size of WAL segments, in megabytes\n"));
  	printf(_("\nLess commonly used options:\n"));
  	printf(_("  -d, --debug               generate lots of debugging output\n"));
+ 	printf(_("  -c  --cluster-passphrase-command=COMMAND\n"
+ 			 "                            set command to obtain passphrase for key management\n"));
  	printf(_("  -k, --data-checksums      use data page checksums\n"));
  	printf(_("  -L DIRECTORY              where to find the input files\n"));
  	printf(_("  -n, --no-clean            do not clean up after errors\n"));
*************** main(int argc, char *argv[])
*** 2958,2963 ****
--- 2969,2975 ----
  		{"wal-segsize", required_argument, NULL, 12},
  		{"data-checksums", no_argument, NULL, 'k'},
  		{"allow-group-access", no_argument, NULL, 'g'},
+ 		{"cluster-passphrase-command", required_argument, NULL, 'c'},
  		{NULL, 0, NULL, 0}
  	};
  
*************** main(int argc, char *argv[])
*** 2999,3005 ****
  
  	/* process command-line options */
  
! 	while ((c = getopt_long(argc, argv, "dD:E:kL:nNU:WA:sST:X:g", long_options, &option_index)) != -1)
  	{
  		switch (c)
  		{
--- 3011,3017 ----
  
  	/* process command-line options */
  
! 	while ((c = getopt_long(argc, argv, "c:dD:E:kL:nNU:WA:sST:X:g", long_options, &option_index)) != -1)
  	{
  		switch (c)
  		{
*************** main(int argc, char *argv[])
*** 3081,3086 ****
--- 3093,3101 ----
  			case 9:
  				pwfilename = pg_strdup(optarg);
  				break;
+ 			case 'c':
+ 				cluster_passphrase = pg_strdup(optarg);
+ 				break;
  			case 's':
  				show_setting = true;
  				break;
*************** main(int argc, char *argv[])
*** 3151,3156 ****
--- 3166,3179 ----
  		exit(1);
  	}
  
+ #ifndef USE_OPENSSL
+ 	if (cluster_passphrase)
+ 	{
+ 		pg_log_error("cluster encryption is not supported because OpenSSL is not supported by this build");
+ 		exit(1);
+ 	}
+ #endif
+ 
  	check_authmethod_unspecified(&authmethodlocal);
  	check_authmethod_unspecified(&authmethodhost);
  
*************** main(int argc, char *argv[])
*** 3218,3223 ****
--- 3241,3251 ----
  	else
  		printf(_("Data page checksums are disabled.\n"));
  
+ 	if (cluster_passphrase)
+ 		printf(_("Key management system is enabled.\n"));
+ 	else
+ 		printf(_("Key management system is disabled.\n"));
+ 
  	if (pwprompt || pwfilename)
  		get_su_pwd();
  
diff --git a/src/bin/pg_controldata/pg_controldata.c b/src/bin/pg_controldata/pg_controldata.c
new file mode 100644
index 3e00ac0..39ba7f0
*** a/src/bin/pg_controldata/pg_controldata.c
--- b/src/bin/pg_controldata/pg_controldata.c
***************
*** 25,30 ****
--- 25,31 ----
  #include "access/xlog_internal.h"
  #include "catalog/pg_control.h"
  #include "common/controldata_utils.h"
+ #include "common/kmgr_utils.h"
  #include "common/logging.h"
  #include "getopt_long.h"
  #include "pg_getopt.h"
*************** main(int argc, char *argv[])
*** 334,338 ****
--- 335,341 ----
  		   ControlFile->data_checksum_version);
  	printf(_("Mock authentication nonce:            %s\n"),
  		   mock_auth_nonce_str);
+ 	printf(_("Key management version:               %u\n"),
+ 		   ControlFile->key_management_version);
  	return 0;
  }
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
new file mode 100644
index cb6ef19..a9d200e
*** a/src/bin/pg_resetwal/pg_resetwal.c
--- b/src/bin/pg_resetwal/pg_resetwal.c
*************** PrintControlValues(bool guessed)
*** 804,809 ****
--- 804,811 ----
  		   (ControlFile.float8ByVal ? _("by value") : _("by reference")));
  	printf(_("Data page checksum version:           %u\n"),
  		   ControlFile.data_checksum_version);
+ 	printf(_("Key management version:               %u\n"),
+ 		   ControlFile.key_management_version);
  }
  
  
diff --git a/src/bin/pg_rewind/filemap.c b/src/bin/pg_rewind/filemap.c
new file mode 100644
index 1abc257..3084ae7
*** a/src/bin/pg_rewind/filemap.c
--- b/src/bin/pg_rewind/filemap.c
*************** static const char *excludeDirContents[]
*** 74,79 ****
--- 74,87 ----
  	"pg_notify",
  
  	/*
+ 	 * Skip cryptographic keys. It's generally not good idea to copy the
+ 	 * cryptographic keys from source database because these might use
+ 	 * different cluster passphrase.
+ 	 */
+ 	"pg_cryptokeys",			/* defined as KMGR_DIR */
+ 	"pg_cryptokeys_tmp",		/* defined as KMGR_TMP_DIR */
+ 
+ 	/*
  	 * Old contents are loaded for possible debugging but are not required for
  	 * normal operation, see SerialInit().
  	 */
diff --git a/src/bin/pg_upgrade/controldata.c b/src/bin/pg_upgrade/controldata.c
new file mode 100644
index 39bcaa8..e5794ff
*** a/src/bin/pg_upgrade/controldata.c
--- b/src/bin/pg_upgrade/controldata.c
***************
*** 9,18 ****
--- 9,24 ----
  
  #include "postgres_fe.h"
  
+ #include <dirent.h>
  #include <ctype.h>
  
  #include "pg_upgrade.h"
  
+ #include "access/xlog_internal.h"
+ #include "common/controldata_utils.h"
+ #include "common/file_utils.h"
+ #include "common/kmgr_utils.h"
+ 
  /*
   * get_control_data()
   *
*************** get_control_data(ClusterInfo *cluster, b
*** 59,64 ****
--- 65,71 ----
  	bool		got_date_is_int = false;
  	bool		got_data_checksum_version = false;
  	bool		got_cluster_state = false;
+ 	bool		got_key_management_enabled = false;
  	char	   *lc_collate = NULL;
  	char	   *lc_ctype = NULL;
  	char	   *lc_monetary = NULL;
*************** get_control_data(ClusterInfo *cluster, b
*** 202,207 ****
--- 209,221 ----
  		got_data_checksum_version = true;
  	}
  
+ 	/* Only in <= 14 */
+ 	if (GET_MAJOR_VERSION(cluster->major_version) <= 1400)
+ 	{
+ 		cluster->controldata.key_management_enabled = false;
+ 		got_key_management_enabled = true;
+ 	}
+ 
  	/* we have the result of cmd in "output". so parse it line by line now */
  	while (fgets(bufin, sizeof(bufin), output))
  	{
*************** get_control_data(ClusterInfo *cluster, b
*** 485,490 ****
--- 499,516 ----
  			cluster->controldata.data_checksum_version = str2uint(p);
  			got_data_checksum_version = true;
  		}
+ 		else if ((p = strstr(bufin, "Key management:")) != NULL)
+ 		{
+ 			p = strchr(p, ':');
+ 
+ 			if (p == NULL || strlen(p) <= 1)
+ 				pg_fatal("%d: controldata retrieval problem\n", __LINE__);
+ 
+ 			p++;				/* remove ':' char */
+ 			/* used later for contrib check */
+ 			cluster->controldata.key_management_enabled = strstr(p, "on") != NULL;
+ 			got_key_management_enabled = true;
+ 		}
  	}
  
  	pclose(output);
*************** get_control_data(ClusterInfo *cluster, b
*** 539,545 ****
  		!got_index || !got_toast ||
  		(!got_large_object &&
  		 cluster->controldata.ctrl_ver >= LARGE_OBJECT_SIZE_PG_CONTROL_VER) ||
! 		!got_date_is_int || !got_data_checksum_version)
  	{
  		if (cluster == &old_cluster)
  			pg_log(PG_REPORT,
--- 565,572 ----
  		!got_index || !got_toast ||
  		(!got_large_object &&
  		 cluster->controldata.ctrl_ver >= LARGE_OBJECT_SIZE_PG_CONTROL_VER) ||
! 		!got_date_is_int || !got_data_checksum_version ||
! 		!got_key_management_enabled)
  	{
  		if (cluster == &old_cluster)
  			pg_log(PG_REPORT,
*************** get_control_data(ClusterInfo *cluster, b
*** 605,610 ****
--- 632,641 ----
  		if (!got_data_checksum_version)
  			pg_log(PG_REPORT, "  data checksum version\n");
  
+ 		/* value added in Postgres 12 */
+ 		if (!got_key_management_enabled)
+ 			pg_log(PG_REPORT, "  key management enabled\n");
+ 
  		pg_fatal("Cannot continue without required control information, terminating\n");
  	}
  }
*************** check_control_data(ControlData *oldctrl,
*** 669,674 ****
--- 700,713 ----
  		pg_fatal("old cluster uses data checksums but the new one does not\n");
  	else if (oldctrl->data_checksum_version != newctrl->data_checksum_version)
  		pg_fatal("old and new cluster pg_controldata checksum versions do not match\n");
+ 
+ 	/*
+ 	 * We cannot upgrade if the old cluster enables the key management but
+ 	 * the new one doesn't support because the old one might already have
+ 	 * data encrypted by the master encryption key.
+ 	 */
+ 	if (oldctrl->key_management_enabled && !newctrl->key_management_enabled)
+ 		pg_fatal("old cluster uses key management but the new one does not\n");
  }
  
  
diff --git a/src/bin/pg_upgrade/file.c b/src/bin/pg_upgrade/file.c
new file mode 100644
index cc8a675..282359f
*** a/src/bin/pg_upgrade/file.c
--- b/src/bin/pg_upgrade/file.c
***************
*** 11,16 ****
--- 11,17 ----
  
  #include <sys/stat.h>
  #include <fcntl.h>
+ #include <dirent.h>
  #ifdef HAVE_COPYFILE_H
  #include <copyfile.h>
  #endif
***************
*** 21,26 ****
--- 22,28 ----
  
  #include "access/visibilitymap.h"
  #include "common/file_perm.h"
+ #include "common/file_utils.h"
  #include "pg_upgrade.h"
  #include "storage/bufpage.h"
  #include "storage/checksum.h"
*************** check_hard_link(void)
*** 372,374 ****
--- 374,451 ----
  
  	unlink(new_link_file);
  }
+ 
+ /*
+  * Copy cryptographic keys from the old cluster to the new cluster.
+  */
+ void
+ copy_master_encryption_key(ClusterInfo *old_cluster, ClusterInfo * new_cluster)
+ {
+ 	DIR				*dir;
+ 	struct dirent *de;
+ 	char path[MAXPGPATH];
+ 
+ 	/* We copy the crypto keys only if both clusters enable the key management */
+ 	if (!old_cluster->controldata.key_management_enabled ||
+ 		!new_cluster->controldata.key_management_enabled)
+ 		return;
+ 
+ 	prep_status("Copying master encryption key");
+ 
+ 	snprintf(path, MAXPGPATH, "%s/%s", old_cluster->pgdata, KMGR_DIR);
+ 
+ 	if ((dir = opendir(path)) == NULL)
+ 		pg_fatal("could not open directory \"%s\": %m", path);
+ 
+ 	while ((de = readdir(dir)) != NULL)
+ 	{
+ 		if (strlen(de->d_name) == 4 &&
+ 			strspn(de->d_name, "0123456789ABCDEF") == 4)
+ 		{
+ 			CryptoKey key;
+ 			char src_path[MAXPGPATH];
+ 			char dst_path[MAXPGPATH];
+ 			uint32	id;
+ 			int src_fd;
+ 			int dst_fd;
+ 			int len;
+ 
+ 			id = strtoul(de->d_name, NULL, 16);
+ 
+ 			snprintf(src_path, MAXPGPATH, "%s/%s/%04X",
+ 					 old_cluster->pgdata, KMGR_DIR, id);
+ 			snprintf(dst_path, MAXPGPATH, "%s/%s/%04X",
+ 					 new_cluster->pgdata, KMGR_DIR, id);
+ 
+ 			if ((src_fd = open(src_path, O_RDONLY | PG_BINARY, 0)) < 0)
+ 				pg_fatal("could not open file \"%s\": %m", src_path);
+ 
+ 			if ((dst_fd = open(dst_path, O_RDWR | O_CREAT | O_TRUNC | PG_BINARY,
+ 								pg_file_create_mode)) < 0)
+ 				pg_fatal("could not open file \"%s\": %m", dst_path);
+ 
+ 			/* Read the source key */
+ 			len = read(src_fd, &key, sizeof(CryptoKey));
+ 			if (len != sizeof(CryptoKey))
+ 			{
+ 				if (len < 0)
+ 					pg_fatal("could not read file \"%s\": %m", src_path);
+ 				else
+ 					pg_fatal("could not read file \"%s\": read %d of %zu",
+ 							 src_path, len, sizeof(CryptoKey));
+ 			}
+ 
+ 			/* Write to the dest key */
+ 			len = write(dst_fd, &key, sizeof(CryptoKey));
+ 			if (len != sizeof(CryptoKey))
+ 				pg_fatal("could not write fie \"%s\"", dst_path);
+ 
+ 			close(src_fd);
+ 			close(dst_fd);
+ 		}
+ 	}
+ 
+ 	closedir(dir);
+ 
+ 	check_ok();
+ }
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
new file mode 100644
index 1bc86e4..d1bacdf
*** a/src/bin/pg_upgrade/pg_upgrade.c
--- b/src/bin/pg_upgrade/pg_upgrade.c
*************** main(int argc, char **argv)
*** 158,163 ****
--- 158,170 ----
  								 old_cluster.pgdata, new_cluster.pgdata);
  
  	/*
+ 	 * Copy the internal encryption keys from the old cluster to the new one.
+ 	 * This is necessary because the data in the old cluster might be
+ 	 * encrypted with the old master encryption key.
+ 	 */
+ 	copy_master_encryption_key(&old_cluster, &new_cluster);
+ 
+ 	/*
  	 * Assuming OIDs are only used in system tables, there is no need to
  	 * restore the OID counter because we have not transferred any OIDs from
  	 * the old system, but we do it anyway just in case.  We do it late here
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
new file mode 100644
index 8b90cef..32ab236
*** a/src/bin/pg_upgrade/pg_upgrade.h
--- b/src/bin/pg_upgrade/pg_upgrade.h
***************
*** 11,16 ****
--- 11,17 ----
  #include <sys/time.h>
  
  #include "libpq-fe.h"
+ #include "common/kmgr_utils.h"
  
  /* Use port in the private/dynamic port number range */
  #define DEF_PGUPORT			50432
*************** typedef struct
*** 219,224 ****
--- 220,226 ----
  	bool		date_is_int;
  	bool		float8_pass_by_value;
  	bool		data_checksum_version;
+ 	bool		key_management_enabled;
  } ControlData;
  
  /*
*************** void		rewriteVisibilityMap(const char *f
*** 375,380 ****
--- 377,384 ----
  								 const char *schemaName, const char *relName);
  void		check_file_clone(void);
  void		check_hard_link(void);
+ void		copy_master_encryption_key(ClusterInfo *old_cluster,
+ 									   ClusterInfo * new_cluster);
  
  /* fopen_priv() is no longer different from fopen() */
  #define fopen_priv(path, mode)	fopen(path, mode)
diff --git a/src/common/Makefile b/src/common/Makefile
new file mode 100644
index 25c55bd..cfb34fd
*** a/src/common/Makefile
--- b/src/common/Makefile
*************** OBJS_COMMON = \
*** 49,54 ****
--- 49,55 ----
  	archive.o \
  	base64.o \
  	checksum_helper.o \
+ 	cipher.o \
  	config_info.o \
  	controldata_utils.o \
  	d2s.o \
*************** OBJS_COMMON = \
*** 61,66 ****
--- 62,68 ----
  	ip.o \
  	jsonapi.o \
  	keywords.o \
+ 	kmgr_utils.o \
  	kwlookup.o \
  	link-canary.o \
  	md5.o \
*************** OBJS_COMMON = \
*** 81,86 ****
--- 83,89 ----
  
  ifeq ($(with_openssl),yes)
  OBJS_COMMON += \
+ 	cipher_openssl.o \
  	protocol_openssl.o \
  	sha2_openssl.o
  else
diff --git a/src/common/cipher.c b/src/common/cipher.c
new file mode 100644
index ...908afe8
*** a/src/common/cipher.c
--- b/src/common/cipher.c
***************
*** 0 ****
--- 1,87 ----
+ /*-------------------------------------------------------------------------
+  *
+  * cipher.c
+  *	  Shared frontend/backend for cryptographic functions
+  *
+  * Copyright (c) 2020, PostgreSQL Global Development Group
+  *
+  * IDENTIFICATION
+  *	  src/common/cipher.c
+  *
+  *-------------------------------------------------------------------------
+  */
+ 
+ #ifndef FRONTEND
+ #include "postgres.h"
+ #else
+ #include "postgres_fe.h"
+ #endif
+ 
+ #include "common/cipher.h"
+ #ifdef USE_OPENSSL
+ #include "common/cipher_openssl.h"
+ #endif
+ 
+ /*
+  * Return a newly created cipher context.  'cipher' specifies cipher algorithm
+  * by identifer like PG_CIPHER_XXX.
+  */
+ PgCipherCtx *
+ pg_cipher_ctx_create(int cipher, uint8 *key, int klen)
+ {
+ 	PgCipherCtx *ctx = NULL;
+ 
+ 	if (cipher >= PG_MAX_CIPHER_ID)
+ 		return NULL;
+ 
+ #ifdef USE_OPENSSL
+ 	ctx = (PgCipherCtx *) palloc0(sizeof(PgCipherCtx));
+ 
+ 	ctx->encctx = ossl_cipher_ctx_create(cipher, key, klen, true);
+ 	ctx->decctx = ossl_cipher_ctx_create(cipher, key, klen, false);
+ #endif
+ 
+ 	return ctx;
+ }
+ 
+ void
+ pg_cipher_ctx_free(PgCipherCtx *ctx)
+ {
+ #ifdef USE_OPENSSL
+ 	ossl_cipher_ctx_free(ctx->encctx);
+ 	ossl_cipher_ctx_free(ctx->decctx);
+ #endif
+ }
+ 
+ bool
+ pg_cipher_encrypt(PgCipherCtx *ctx, const uint8 *in, int inlen,
+ 				  uint8 *out, int *outlen, const uint8 *iv)
+ {
+ 	bool		r = false;
+ #ifdef USE_OPENSSL
+ 	r = ossl_cipher_encrypt(ctx->encctx, in, inlen, out, outlen, iv);
+ #endif
+ 	return r;
+ }
+ 
+ bool
+ pg_cipher_decrypt(PgCipherCtx *ctx, const uint8 *in, int inlen,
+ 				  uint8 *out, int *outlen, const uint8 *iv)
+ {
+ 	bool		r = false;
+ #ifdef USE_OPENSSL
+ 	r = ossl_cipher_decrypt(ctx->decctx, in, inlen, out, outlen, iv);
+ #endif
+ 	return r;
+ }
+ 
+ bool
+ pg_HMAC_SHA512(const uint8 *key, const uint8 *in, int inlen,
+ 			   uint8 *out)
+ {
+ 	bool		r = false;
+ #ifdef USE_OPENSSL
+ 	r = ossl_HMAC_SHA512(key, in, inlen, out);
+ #endif
+ 	return r;
+ }
diff --git a/src/common/cipher_openssl.c b/src/common/cipher_openssl.c
new file mode 100644
index ...e8bc241
*** a/src/common/cipher_openssl.c
--- b/src/common/cipher_openssl.c
***************
*** 0 ****
--- 1,181 ----
+ /*-------------------------------------------------------------------------
+  * cipher_openssl.c
+  *		Cryptographic function using OpenSSL
+  *
+  * This contains the common low-level functions needed in both frontend and
+  * backend, for implement the database encryption.
+  *
+  * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+  *
+  * IDENTIFICATION
+  *	  src/common/cipher_openssl.c
+  *
+  *-------------------------------------------------------------------------
+  */
+ #ifndef FRONTEND
+ #include "postgres.h"
+ #else
+ #include "postgres_fe.h"
+ #endif
+ 
+ #include "common/sha2.h"
+ #include "common/cipher_openssl.h"
+ 
+ #include <openssl/ssl.h>
+ #include <openssl/conf.h>
+ #include <openssl/evp.h>
+ #include <openssl/err.h>
+ #include <openssl/hmac.h>
+ 
+ /*
+  * prototype for the EVP functions that return an algorithm, e.g.
+  * EVP_aes_128_cbc().
+  */
+ typedef const EVP_CIPHER *(*ossl_EVP_cipher_func) (void);
+ 
+ static bool ossl_initialized = false;
+ 
+ static ossl_EVP_cipher_func get_evp_aes_cbc(int klen);
+ 
+ static ossl_EVP_cipher_func
+ get_evp_aes_cbc(int klen)
+ {
+ 	switch (klen)
+ 	{
+ 		case PG_AES128_KEY_LEN:
+ 			return EVP_aes_128_cbc;
+ 		case PG_AES192_KEY_LEN:
+ 			return EVP_aes_192_cbc;
+ 		case PG_AES256_KEY_LEN:
+ 			return EVP_aes_256_cbc;
+ 		default:
+ 			return NULL;
+ 	}
+ }
+ 
+ /*
+  * Initialize and return an EVP_CIPHER_CTX. Return NULL if the given
+  * cipher algorithm is not supported or on failure..
+  */
+ EVP_CIPHER_CTX *
+ ossl_cipher_ctx_create(int cipher, uint8 *key, int klen, bool enc)
+ {
+ 	EVP_CIPHER_CTX			*ctx;
+ 	ossl_EVP_cipher_func	func;
+ 	int	ret;
+ 
+ 	if (!ossl_initialized)
+ 	{
+ #ifdef HAVE_OPENSSL_INIT_SSL
+ 		OPENSSL_init_ssl(OPENSSL_INIT_LOAD_CONFIG, NULL);
+ #else
+ 		OPENSSL_config(NULL);
+ 		SSL_library_init();
+ 		SSL_load_error_strings();
+ #endif
+ 		ossl_initialized = true;
+ 	}
+ 
+ 	ctx = EVP_CIPHER_CTX_new();
+ 
+ 	switch (cipher)
+ 	{
+ 		case PG_CIPHER_AES_CBC:
+ 			func = get_evp_aes_cbc(klen);
+ 			if (!func)
+ 				goto failed;
+ 			break;
+ 		default:
+ 			goto failed;
+ 	}
+ 
+ 
+ 	if (enc)
+ 		ret = EVP_EncryptInit_ex(ctx, (const EVP_CIPHER *) func(), NULL, key, NULL);
+ 	else
+ 		ret = EVP_DecryptInit_ex(ctx, (const EVP_CIPHER *) func(), NULL, key, NULL);
+ 
+ 	if (!ret)
+ 		goto failed;
+ 
+ 	if (!EVP_CIPHER_CTX_set_key_length(ctx, PG_AES256_KEY_LEN))
+ 		goto failed;
+ 
+ 	/*
+ 	 * Always enable padding. We don't need to check the return value as
+ 	 * EVP_CIPHER_CTX_set_padding always returns 1.
+ 	 */
+ 	EVP_CIPHER_CTX_set_padding(ctx, 1);
+ 
+ 	return ctx;
+ 
+ failed:
+ 	EVP_CIPHER_CTX_free(ctx);
+ 	return NULL;
+ }
+ 
+ void
+ ossl_cipher_ctx_free(EVP_CIPHER_CTX *ctx)
+ {
+ 	return EVP_CIPHER_CTX_free(ctx);
+ }
+ 
+ bool
+ ossl_cipher_encrypt(EVP_CIPHER_CTX *ctx,
+ 					const uint8 *in, int inlen,
+ 					uint8 *out, int *outlen,
+ 					const uint8 *iv)
+ {
+ 	int			len;
+ 	int			enclen;
+ 
+ 	if (!EVP_EncryptInit_ex(ctx, NULL, NULL, NULL, iv))
+ 		return false;
+ 
+ 	if (!EVP_EncryptUpdate(ctx, out, &len, in, inlen))
+ 		return false;
+ 
+ 	enclen = len;
+ 
+ 	if (!EVP_EncryptFinal_ex(ctx, (uint8 *) ((char *) out + enclen),
+ 							 &len))
+ 		return false;
+ 
+ 	*outlen = enclen + len;
+ 
+ 	return true;
+ }
+ 
+ bool
+ ossl_cipher_decrypt(EVP_CIPHER_CTX *ctx,
+ 					const uint8 *in, int inlen,
+ 					uint8 *out, int *outlen,
+ 					const uint8 *iv)
+ {
+ 	int			declen;
+ 	int			len;
+ 
+ 	if (!EVP_DecryptInit_ex(ctx, NULL, NULL, NULL, iv))
+ 		return false;
+ 
+ 	if (!EVP_DecryptUpdate(ctx, out, &len, in, inlen))
+ 		return false;
+ 
+ 	declen = len;
+ 
+ 	if (!EVP_DecryptFinal_ex(ctx, (uint8 *) ((char *) out + declen),
+ 							 &len))
+ 		return false;
+ 
+ 	*outlen = declen + len;
+ 
+ 	return true;
+ }
+ 
+ bool
+ ossl_HMAC_SHA512(const uint8 *key, const uint8 *in, int inlen,
+ 				 uint8 *out)
+ {
+ 	return HMAC(EVP_sha512(), key, PG_SHA512_DIGEST_LENGTH,
+ 				in, (uint32) inlen, out, NULL);
+ }
diff --git a/src/common/kmgr_utils.c b/src/common/kmgr_utils.c
new file mode 100644
index ...42ab462
*** a/src/common/kmgr_utils.c
--- b/src/common/kmgr_utils.c
***************
*** 0 ****
--- 1,523 ----
+ /*-------------------------------------------------------------------------
+  *
+  * kmgr_utils.c
+  *	  Shared frontend/backend for cryptographic key management
+  *
+  * Copyright (c) 2020, PostgreSQL Global Development Group
+  *
+  * IDENTIFICATION
+  *	  src/common/kmgr_utils.c
+  *
+  *-------------------------------------------------------------------------
+  */
+ 
+ #ifndef FRONTEND
+ #include "postgres.h"
+ #else
+ #include "postgres_fe.h"
+ #endif
+ 
+ #include <unistd.h>
+ #include <sys/stat.h>
+ 
+ #ifdef FRONTEND
+ #include "common/logging.h"
+ #endif
+ #include "common/file_perm.h"
+ #include "common/kmgr_utils.h"
+ #include "common/sha2.h"
+ #include "crypto/kmgr.h"
+ #include "utils/elog.h"
+ #include "storage/fd.h"
+ 
+ #ifndef FRONTEND
+ #include "pgstat.h"
+ #include "storage/fd.h"
+ #endif
+ 
+ #define KMGR_PROMPT_MSG "Enter database encryption pass phrase: "
+ 
+ #ifdef FRONTEND
+ static FILE *open_pipe_stream(const char *command);
+ static int	close_pipe_stream(FILE *file);
+ #endif
+ 
+ static void read_one_keyfile(const char *dataDir, uint32 id, CryptoKey *key_p);
+ 
+ /* Return a key wrap context initialized with the given keys */
+ PgKeyWrapCtx *
+ pg_create_keywrap_ctx(uint8 key[KMGR_ENC_KEY_LEN], uint8 mackey[KMGR_MAC_KEY_LEN])
+ {
+ 	PgKeyWrapCtx *ctx;
+ 
+ 	ctx = (PgKeyWrapCtx *) palloc0(sizeof(PgKeyWrapCtx));
+ 
+ 	/* Create and initialize a cipher context */
+ 	ctx->cipherctx = pg_cipher_ctx_create(PG_CIPHER_AES_CBC, key, KMGR_ENC_KEY_LEN);
+ 	if (ctx->cipherctx == NULL)
+ 		return NULL;
+ 
+ 	/* Set encryption key and MAC key */
+ 	memcpy(ctx->key, key, KMGR_ENC_KEY_LEN);
+ 	memcpy(ctx->mackey, mackey, KMGR_MAC_KEY_LEN);
+ 
+ 	return ctx;
+ }
+ 
+ /* Free the key wrap context */
+ void
+ pg_free_keywrap_ctx(PgKeyWrapCtx *ctx)
+ {
+ 	if (!ctx)
+ 		return;
+ 
+ 	Assert(ctx->cipherctx);
+ 
+ 	pg_cipher_ctx_free(ctx->cipherctx);
+ 
+ #ifndef FRONTEND
+ 	pfree(ctx);
+ #else
+ 	pg_free(ctx);
+ #endif
+ }
+ 
+ /*
+  * Encrypt the given data. Return true and set encrypted data to 'out' if
+  * success.  Otherwise return false. The caller must allocate sufficient space
+  * for cipher data calculated by using KmgrSizeOfCipherText(). Please note that
+  * this function modifies 'out' data even on failure case.
+  */
+ bool
+ kmgr_wrap_key(PgKeyWrapCtx *ctx, CryptoKey *in, CryptoKey *out)
+ {
+ 	uint8	*hmac;
+ 	uint8	*iv;
+ 	uint8	*enc;
+ 	int		enclen;
+ 
+ 	Assert(ctx && in && out);
+ 
+ 	hmac = out->key;
+ 	iv = hmac + KMGR_HMAC_LEN;
+ 	enc = iv + PG_AES_IV_SIZE;
+ 
+ 	/* Generate IV */
+ 	if (!pg_strong_random(iv, PG_AES_IV_SIZE))
+ 		return false;
+ 
+ 	if (!pg_cipher_encrypt(ctx->cipherctx, in->key, in->klen, enc, &enclen, iv))
+ 		return false;
+ 
+ 	if (!pg_HMAC_SHA512(ctx->mackey, enc, enclen, hmac))
+ 		return false;
+ 
+ 	out->klen = KmgrSizeOfCipherText(in->klen);;
+ 	Assert(out->klen == KMGR_HMAC_LEN + PG_AES_IV_SIZE + enclen);
+ 
+ 	return true;
+ }
+ 
+ /*
+  * Decrypt the given Data. Return true and set plain text data to `out` if
+  * success.  Otherwise return false. The caller must allocate sufficient space
+  * for cipher data calculated by using KmgrSizeOfPlainText(). Please note that
+  * this function modifies 'out' data even on failure case.
+  */
+ bool
+ kmgr_unwrap_key(PgKeyWrapCtx *ctx, CryptoKey *in, CryptoKey *out)
+ {
+ 	uint8	hmac[KMGR_HMAC_LEN];
+ 	uint8   *expected_hmac;
+ 	uint8   *iv;
+ 	uint8	*enc;
+ 	int		enclen;
+ 
+ 	Assert(ctx && in && out);
+ 
+ 	expected_hmac = in->key;
+ 	iv = expected_hmac + KMGR_HMAC_LEN;
+ 	enc = iv + PG_AES_IV_SIZE;
+ 	enclen = in->klen - (enc - in->key);
+ 
+ 	/* Verify the correctness of HMAC */
+ 	if (!pg_HMAC_SHA512(ctx->mackey, enc, enclen, hmac))
+ 		return false;
+ 
+ 	if (memcmp(hmac, expected_hmac, KMGR_HMAC_LEN) != 0)
+ 		return false;
+ 
+ 	/* Decrypt encrypted data */
+ 	if (!pg_cipher_decrypt(ctx->cipherctx, enc, enclen, out->key, &(out->klen), iv))
+ 		return false;
+ 
+ 	return true;
+ }
+ 
+ /*
+  * Verify the correctness of the given passphrase by unwrapping the given keys.
+  * If the given passphrase is correct we set unwrapped keys to keys_out and return
+  * true.  Otherwise return false.  Please note that this function changes the
+  * contents of keys_out even on failure.  Both keys_in and keys_out must be the
+  * same length, nkey.
+  */
+ bool
+ kmgr_verify_passphrase(char *passphrase, int passlen,
+ 					   CryptoKey *keys_in, CryptoKey *keys_out, int nkeys)
+ {
+ 	PgKeyWrapCtx *tmpctx;
+ 	uint8		user_enckey[KMGR_ENC_KEY_LEN];
+ 	uint8		user_hmackey[KMGR_MAC_KEY_LEN];
+ 
+ 	/*
+ 	 * Create temporary wrap context with encryption key and HMAC key extracted
+ 	 * from the passphrase.
+ 	 */
+ 	kmgr_derive_keys(passphrase, passlen, user_enckey, user_hmackey);
+ 	tmpctx = pg_create_keywrap_ctx(user_enckey, user_hmackey);
+ 
+ 	for (int i = 0; i < nkeys; i++)
+ 	{
+ 
+ 		if (!kmgr_unwrap_key(tmpctx, &(keys_in[i]), &(keys_out[i])))
+ 		{
+ 			/* The passphrase is not correct */
+ 			pg_free_keywrap_ctx(tmpctx);
+ 			return false;
+ 		}
+ 	}
+ 
+ 	/* The passphrase is correct, free the cipher context */
+ 	pg_free_keywrap_ctx(tmpctx);
+ 
+ 	return true;
+ }
+ 
+ /* Generate encryption key and mac key from given passphrase */
+ void
+ kmgr_derive_keys(char *passphrase, Size passlen,
+ 				 uint8 enckey[KMGR_ENC_KEY_LEN],
+ 				 uint8 mackey[KMGR_MAC_KEY_LEN])
+ {
+ 	pg_sha256_ctx ctx1;
+ 	pg_sha512_ctx ctx2;
+ 
+ 	StaticAssertStmt(KMGR_ENC_KEY_LEN == PG_AES256_KEY_LEN,
+ 		"derived encryption key size does not match AES256 key size");
+ 	StaticAssertStmt(KMGR_MAC_KEY_LEN == PG_HMAC_SHA512_KEY_LEN,
+ 		"derived mac key size does not match HMAC-SHA512 key size");
+ 
+ 	/* Generate encryption key from passphrase */
+ 	pg_sha256_init(&ctx1);
+ 	pg_sha256_update(&ctx1, (const uint8 *) passphrase, passlen);
+ 	pg_sha256_final(&ctx1, enckey);
+ 
+ 	/* Generate mac key from passphrase */
+ 	pg_sha512_init(&ctx2);
+ 	pg_sha512_update(&ctx2, (const uint8 *) passphrase, passlen);
+ 	pg_sha512_final(&ctx2, mackey);
+ }
+ 
+ /*
+  * Run cluster passphrase command.
+  *
+  * prompt will be substituted for %p.
+  *
+  * The result will be put in buffer buf, which is of size size.
+  * The return value is the length of the actual result.
+  */
+ int
+ kmgr_run_cluster_passphrase_command(char *passphrase_command, char *buf,
+ 									int size)
+ {
+ 	char		command[MAXPGPATH];
+ 	char	   *p;
+ 	char	   *dp;
+ 	char	   *endp;
+ 	FILE	   *fh;
+ 	int			pclose_rc;
+ 	size_t		len = 0;
+ 
+ 	Assert(size > 0);
+ 	buf[0] = '\0';
+ 
+ 	dp = command;
+ 	endp = command + MAXPGPATH - 1;
+ 	*endp = '\0';
+ 
+ 	for (p = passphrase_command; *p; p++)
+ 	{
+ 		if (p[0] == '%')
+ 		{
+ 			switch (p[1])
+ 			{
+ 				case 'p':
+ 					strlcpy(dp, KMGR_PROMPT_MSG, strlen(KMGR_PROMPT_MSG)+1);
+ 					dp += strlen(KMGR_PROMPT_MSG);
+ 					p++;
+ 					break;
+ 				case '%':
+ 					p++;
+ 					if (dp < endp)
+ 						*dp++ = *p;
+ 					break;
+ 				default:
+ 					if (dp < endp)
+ 						*dp++ = *p;
+ 					break;
+ 			}
+ 		}
+ 		else
+ 		{
+ 			if (dp < endp)
+ 				*dp++ = *p;
+ 		}
+ 	}
+ 	*dp = '\0';
+ 
+ #ifdef FRONTEND
+ 	fh = open_pipe_stream(command);
+ 	if (fh == NULL)
+ 	{
+ 		pg_log_fatal("could not execute command \"%s\": %m",
+ 					 command);
+ 		exit(EXIT_FAILURE);
+ 	}
+ #else
+ 	fh = OpenPipeStream(command, "r");
+ 	if (fh == NULL)
+ 		ereport(ERROR,
+ 				(errcode_for_file_access(),
+ 				 errmsg("could not execute command \"%s\": %m",
+ 						command)));
+ #endif
+ 
+ 	if ((len = fread(buf, sizeof(char), size, fh)) < size)
+ 	{
+ 		if (ferror(fh))
+ 		{
+ #ifdef FRONTEND
+ 			pg_log_fatal("could not read from command \"%s\": %m",
+ 						 command);
+ 			exit(EXIT_FAILURE);
+ #else
+ 			ereport(ERROR,
+ 					(errcode_for_file_access(),
+ 					 errmsg("could not read from command \"%s\": %m",
+ 							command)));
+ #endif
+ 		}
+ 	}
+ 
+ #ifdef FRONTEND
+ 	pclose_rc = close_pipe_stream(fh);
+ #else
+ 	pclose_rc = ClosePipeStream(fh);
+ #endif
+ 
+ 	if (pclose_rc == -1)
+ 	{
+ #ifdef FRONTEND
+ 		pg_log_fatal("could not close pipe to external command: %m");
+ 		exit(EXIT_FAILURE);
+ #else
+ 		ereport(ERROR,
+ 				(errcode_for_file_access(),
+ 				 errmsg("could not close pipe to external command: %m")));
+ #endif
+ 	}
+ 	else if (pclose_rc != 0)
+ 	{
+ #ifdef FRONTEND
+ 		pg_log_fatal("command \"%s\" failed", command);
+ 		exit(EXIT_FAILURE);
+ #else
+ 		ereport(ERROR,
+ 				(errcode_for_file_access(),
+ 				 errmsg("command \"%s\" failed",
+ 						command),
+ 				 errdetail_internal("%s", wait_result_to_str(pclose_rc))));
+ #endif
+ 	}
+ 
+ 	return len;
+ }
+ 
+ #ifdef FRONTEND
+ static FILE *
+ open_pipe_stream(const char *command)
+ {
+ 	FILE	   *res;
+ 
+ #ifdef WIN32
+ 	size_t		cmdlen = strlen(command);
+ 	char	   *buf;
+ 	int			save_errno;
+ 
+ 	buf = malloc(cmdlen + 2 + 1);
+ 	if (buf == NULL)
+ 	{
+ 		errno = ENOMEM;
+ 		return NULL;
+ 	}
+ 	buf[0] = '"';
+ 	mempcy(&buf[1], command, cmdlen);
+ 	buf[cmdlen + 1] = '"';
+ 	buf[cmdlen + 2] = '\0';
+ 
+ 	res = _popen(buf, "r");
+ 
+ 	save_errno = errno;
+ 	free(buf);
+ 	errno = save_errno;
+ #else
+ 	res = popen(command, "r");
+ #endif							/* WIN32 */
+ 	return res;
+ }
+ 
+ static int
+ close_pipe_stream(FILE *file)
+ {
+ #ifdef WIN32
+ 	return _pclose(file);
+ #else
+ 	return pclose(file);
+ #endif							/* WIN32 */
+ }
+ #endif							/* FRONTEND */
+ 
+ CryptoKey *
+ kmgr_get_cryptokeys(const char *path, int *nkeys)
+ {
+ 	struct dirent *de;
+ 	DIR			*dir;
+ 	CryptoKey	*keys;
+ 
+ #ifndef FRONTEND
+ 	if ((dir = AllocateDir(path)) == NULL)
+ 		ereport(ERROR,
+ 				(errcode_for_file_access(),
+ 				 errmsg("could not open directory \"%s\": %m",
+ 						path)));
+ #else
+ 	if ((dir = opendir(path)) == NULL)
+ 		pg_log_fatal("could not open directory \"%s\": %m", path);
+ #endif
+ 
+ 	keys = (CryptoKey *) palloc0(sizeof(CryptoKey) * KMGR_MAX_INTERNAL_KEYS);
+ 	*nkeys = 0;
+ 
+ #ifndef FRONTEND
+ 	while ((de = ReadDir(dir, KMGR_DIR)) != NULL)
+ #else
+ 	while ((de = readdir(dir)) != NULL)
+ #endif
+ 	{
+ 		if (strlen(de->d_name) == 4 &&
+ 			strspn(de->d_name, "0123456789ABCDEF") == 4)
+ 		{
+ 			uint32		id;
+ 
+ 			id = strtoul(de->d_name, NULL, 16);
+ 
+ 			if (id < 0 || id >= KMGR_MAX_INTERNAL_KEYS)
+ 			{
+ #ifndef FRONTEND
+ 				elog(ERROR, "invalid cryptographic key identifier %u", id);
+ #else
+ 				pg_log_fatal("invalid cryptographic key identifier %u", id);
+ #endif
+ 			}
+ 
+ 			if (*nkeys >= KMGR_MAX_INTERNAL_KEYS)
+ 			{
+ #ifndef FRONTEND
+ 				elog(ERROR, "too many cryptographic kes");
+ #else
+ 				pg_log_fatal("too many cryptographic keys");
+ #endif
+ 			}
+ 
+ 			read_one_keyfile(path, id, &(keys[id]));
+ 			(*nkeys)++;
+ 		}
+ 	}
+ 
+ #ifndef FRONTEND
+ 	FreeDir(dir);
+ #else
+ 	closedir(dir);
+ #endif
+ 
+ 	return keys;
+ }
+ 
+ static void
+ read_one_keyfile(const char *cryptoKeyDir, uint32 id, CryptoKey *key_p)
+ {
+ 	char		path[MAXPGPATH];
+ 	int			fd;
+ 	int			r;
+ 
+ 	CryptoKeyFilePath(path, cryptoKeyDir, id);
+ 
+ #ifndef FRONTEND
+ 	if ((fd = OpenTransientFile(path, O_RDONLY | PG_BINARY)) == -1)
+ 		ereport(ERROR,
+ 				(errcode_for_file_access(),
+ 				 errmsg("could not open file \"%s\" for reading: %m",
+ 						path)));
+ #else
+ 	if ((fd = open(path, O_RDONLY | PG_BINARY, 0)) == -1)
+ 		pg_log_fatal("could not open file \"%s\" for reading: %m",
+ 					 path);
+ #endif
+ 
+ #ifndef FRONTEND
+ 	pgstat_report_wait_start(WAIT_EVENT_KEY_FILE_READ);
+ #endif
+ 
+ 	/* Get key bytes */
+ 	r = read(fd, key_p, sizeof(CryptoKey));
+ 	if (r != sizeof(CryptoKey))
+ 	{
+ 		if (r < 0)
+ 		{
+ #ifndef FRONTEND
+ 			ereport(ERROR,
+ 					(errcode_for_file_access(),
+ 					 errmsg("could not read file \"%s\": %m", path)));
+ #else
+ 			pg_log_fatal("could not read file \"%s\": %m", path);
+ #endif
+ 		}
+ 		else
+ 		{
+ #ifndef FRONTEND
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_DATA_CORRUPTED),
+ 					 errmsg("could not read file \"%s\": read %d of %zu",
+ 							path, r, sizeof(CryptoKey))));
+ #else
+ 			pg_log_fatal("could not read file \"%s\": read %d of %zu",
+ 						 path, r, sizeof(CryptoKey));
+ #endif
+ 		}
+ 	}
+ 
+ #ifndef FRONTEND
+ 	pgstat_report_wait_end();
+ #endif
+ 
+ #ifndef FRONTEND
+ 	if (CloseTransientFile(fd) != 0)
+ 		ereport(ERROR,
+ 				(errcode_for_file_access(),
+ 				 errmsg("could not close file \"%s\": %m",
+ 						path)));
+ #else
+ 	if (close(fd) != 0)
+ 		pg_log_fatal("could not close file \"%s\": %m", path);
+ #endif
+ }
diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h
new file mode 100644
index 221af87..33f28c7
*** a/src/include/access/xlog.h
--- b/src/include/access/xlog.h
*************** extern void UpdateControlFile(void);
*** 319,324 ****
--- 319,325 ----
  extern uint64 GetSystemIdentifier(void);
  extern char *GetMockAuthenticationNonce(void);
  extern bool DataChecksumsEnabled(void);
+ extern bool	KeyManagementEnabled(void);
  extern XLogRecPtr GetFakeLSNForUnloggedRel(void);
  extern Size XLOGShmemSize(void);
  extern void XLOGShmemInit(void);
diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h
new file mode 100644
index 06bed90..c7df6d6
*** a/src/include/catalog/pg_control.h
--- b/src/include/catalog/pg_control.h
*************** typedef struct ControlFileData
*** 226,231 ****
--- 226,234 ----
  	 */
  	char		mock_authentication_nonce[MOCK_AUTH_NONCE_LEN];
  
+ 	/* Key management cipher. Zero if no version */
+ 	uint32		key_management_version;
+ 
  	/* CRC of all above ... MUST BE LAST! */
  	pg_crc32c	crc;
  } ControlFileData;
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
new file mode 100644
index 22340ba..b1523e1
*** a/src/include/catalog/pg_proc.dat
--- b/src/include/catalog/pg_proc.dat
***************
*** 10992,10995 ****
--- 10992,11001 ----
    proname => 'is_normalized', prorettype => 'bool', proargtypes => 'text text',
    prosrc => 'unicode_is_normalized' },
  
+ # function for key managements
+ { oid => '8200', descr => 'rotate cluter passphrase',
+   proname => 'pg_rotate_cluster_passphrase',
+   provolatile => 'v', prorettype => 'bool',
+   proargtypes => '', prosrc => 'pg_rotate_cluster_passphrase' },
+ 
  ]
diff --git a/src/include/common/cipher.h b/src/include/common/cipher.h
new file mode 100644
index ...f782791
*** a/src/include/common/cipher.h
--- b/src/include/common/cipher.h
***************
*** 0 ****
--- 1,78 ----
+ /*-------------------------------------------------------------------------
+  *
+  * cipher.h
+  *		Declarations for cryptographic functions
+  *
+  * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+  *
+  * src/include/common/cipher.h
+  *
+  *-------------------------------------------------------------------------
+  */
+ #ifndef CIPHER_H
+ #define CIPHER_H
+ 
+ #ifdef USE_OPENSSL
+ #include <openssl/evp.h>
+ #include <openssl/conf.h>
+ #include <openssl/err.h>
+ #endif
+ 
+ /*
+  * Supported symmetric encryption algorithm. These identifiers are passed
+  * to pg_cipher_ctx_create() function, and then actual encryption
+  * implementations need to initialize their context of the given encryption
+  * algorithm.
+  */
+ #define PG_CIPHER_AES_CBC			0
+ #define PG_MAX_CIPHER_ID			1
+ 
+ /* AES128/192/256 various length definitions */
+ #define PG_AES128_KEY_LEN			(128 / 8)
+ #define PG_AES192_KEY_LEN			(192 / 8)
+ #define PG_AES256_KEY_LEN			(256 / 8)
+ 
+ /*
+  * The encrypted data is a series of blocks of size. Initialization
+  * vector(IV) is the same size of cipher block.
+  */
+ #define PG_AES_BLOCK_SIZE			16
+ #define PG_AES_IV_SIZE				(PG_AES_BLOCK_SIZE)
+ 
+ /* HMAC key and HMAC length. We use HMAC-SHA256 */
+ #define PG_HMAC_SHA512_KEY_LEN		64
+ #define PG_HMAC_SHA512_LEN			64
+ 
+ #ifdef USE_OPENSSL
+ typedef EVP_CIPHER_CTX cipher_private_ctx;
+ #else
+ typedef void cipher_private_ctx;
+ #endif
+ 
+ /*
+  * This struct has two implementation-private context for
+  * encryption and decryption. The caller must create the encryption
+  * context using by pg_cipher_ctx_create() and pass the context  to
+  * pg_cipher_encrypt() or pg_cipher_decrypt().
+  */
+ typedef struct PgCipherCtx
+ {
+ 	cipher_private_ctx *encctx;
+ 	cipher_private_ctx *decctx;
+ } PgCipherCtx;
+ 
+ extern PgCipherCtx *pg_cipher_ctx_create(int cipher, uint8 *key, int klen);
+ extern void pg_cipher_ctx_free(PgCipherCtx *ctx);
+ extern bool pg_cipher_encrypt(PgCipherCtx *ctx,
+ 							  const uint8 *in, int inlen,
+ 							  uint8 *out, int *outlen,
+ 							  const uint8 *iv);
+ extern bool pg_cipher_decrypt(PgCipherCtx *ctx,
+ 							  const uint8 *in, int inlen,
+ 							  uint8 *out, int *outlen,
+ 							  const uint8 *iv);
+ extern bool pg_HMAC_SHA512(const uint8 *key,
+ 						   const uint8 *in, int inlen,
+ 						   uint8 *out);
+ 
+ #endif							/* CIPHER_H */
diff --git a/src/include/common/cipher_openssl.h b/src/include/common/cipher_openssl.h
new file mode 100644
index ...0fd1308
*** a/src/include/common/cipher_openssl.h
--- b/src/include/common/cipher_openssl.h
***************
*** 0 ****
--- 1,37 ----
+ /*-------------------------------------------------------------------------
+  *
+  * cipher_openssl.h
+  *		Declarations for helper functions using OpenSSL
+  *
+  * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+  *
+  * src/include/common/cipher_openssl.h
+  *
+  *-------------------------------------------------------------------------
+  */
+ #ifndef CIPHER_OPENSSL_H
+ #define CIPHER_OPENSSL_H
+ 
+ #ifndef FRONTEND
+ #include "postgres.h"
+ #else
+ #include "postgres_fe.h"
+ #endif
+ 
+ #include "common/cipher.h"
+ 
+ extern EVP_CIPHER_CTX *ossl_cipher_ctx_create(int cipher, uint8 *key, int klen,
+ 											  bool enc);
+ extern void ossl_cipher_ctx_free(EVP_CIPHER_CTX *ctx);
+ extern bool ossl_cipher_encrypt(EVP_CIPHER_CTX *ctx,
+ 								const uint8 *in, int inlen,
+ 								uint8 *out, int *outlen,
+ 								const uint8 *iv);
+ extern bool ossl_cipher_decrypt(EVP_CIPHER_CTX *ctx,
+ 								const uint8 *in, int inlen,
+ 								uint8 *out,	int *outlen,
+ 								const uint8 *iv);
+ extern bool ossl_HMAC_SHA512(const uint8 *key,
+ 							 const uint8 *in, int inlen,
+ 							 uint8 *out);
+ #endif
diff --git a/src/include/common/kmgr_utils.h b/src/include/common/kmgr_utils.h
new file mode 100644
index ...1dc8f43
*** a/src/include/common/kmgr_utils.h
--- b/src/include/common/kmgr_utils.h
***************
*** 0 ****
--- 1,105 ----
+ /*-------------------------------------------------------------------------
+  *
+  * kmgr_utils.h
+  *		Declarations for utility function for key management
+  *
+  * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+  *
+  * src/include/common/kmgr_utils.h
+  *
+  *-------------------------------------------------------------------------
+  */
+ #ifndef KMGR_UTILS_H
+ #define KMGR_UTILS_H
+ 
+ #include "common/cipher.h"
+ 
+ /* Current version number */
+ #define KMGR_VERSION 1
+ 
+ /*
+  * Directory where cryptographic keys reside within PGDATA. KMGR_DIR_TMP
+  * is used during cluster passphrase rotation.
+  */
+ #define KMGR_DIR			"pg_cryptokeys"
+ #define KMGR_TMP_DIR		"pg_cryptokeys_tmp"
+ 
+ /*
+  * Identifiers of internal keys.  When adding a new internal key, we
+  * also need to add its key length to internalKeyLengths.
+  */
+ /* #define KMGR_XXX_KEY_ID 0 */
+ #define KMGR_MAX_INTERNAL_KEYS	0
+ 
+ /* Encryption key and MAC key used for key wrapping */
+ #define KMGR_ENC_KEY_LEN			PG_AES256_KEY_LEN
+ #define KMGR_MAC_KEY_LEN			PG_HMAC_SHA512_KEY_LEN
+ #define KMGR_HMAC_LEN				PG_HMAC_SHA512_LEN
+ 
+ /* Key wrapping key consists of encryption key and mac key */
+ #define KMGR_KEY_LEN				(PG_AEAD_ENC_KEY_LEN + PG_AEAD_MAC_KEY_LEN)
+ 
+ /* Allowed length of cluster passphrase */
+ #define KMGR_MIN_PASSPHRASE_LEN 	64
+ #define KMGR_MAX_PASSPHRASE_LEN		1024
+ 
+ /* Maximum length of key the key manager can store */
+ #define KMGR_MAX_KEY_LEN			128
+ #define KMGR_MAX_WRAPPED_KEY_LEN	KmgrSizeOfCipherText(KMGR_MAX_KEY_LEN)
+ 
+ /*
+  * Size of encrypted key size with padding. We use PKCS#7 padding,
+  * described in RFC 5652.
+  */
+ #define SizeOfDataWithPadding(klen) \
+ 	((int)(klen) + (PG_AES_BLOCK_SIZE - ((int)(klen) % PG_AES_BLOCK_SIZE)))
+ 
+ /* Macros to compute the size of cipher text and plain text */
+ #define KmgrSizeOfCipherText(len) \
+ 	(KMGR_MAC_KEY_LEN + PG_AES_IV_SIZE + SizeOfDataWithPadding((int)(len)))
+ #define KmgrSizeOfPlainText(klen) \
+ 	((int)(klen) - (KMGR_MAC_KEY_LEN + PG_AES_IV_SIZE))
+ 
+ /* CryptoKey file name is keys id */
+ #define CryptoKeyFilePath(path, dir, id) \
+ 	snprintf((path), MAXPGPATH, "%s/%04X", (dir), (id))
+ 
+ /*
+  * Cryptographic key data structure. This structure is used for
+  * both on-disk (raw key) and on-memory (wrapped key).
+  */
+ typedef struct CryptoKey
+ {
+ 	int		klen;
+ 	uint8	key[KMGR_MAX_WRAPPED_KEY_LEN];
+ } CryptoKey;
+ 
+ /* Key wrapping cipher context */
+ typedef struct PgKeyWrapCtx
+ {
+ 	uint8			key[KMGR_ENC_KEY_LEN];
+ 	uint8			mackey[KMGR_MAC_KEY_LEN];
+ 	PgCipherCtx		*cipherctx;
+ } PgKeyWrapCtx;
+ 
+ extern PgKeyWrapCtx *pg_create_keywrap_ctx(uint8 key[KMGR_ENC_KEY_LEN],
+ 										   uint8 mackey[KMGR_MAC_KEY_LEN]);
+ extern void pg_free_keywrap_ctx(PgKeyWrapCtx *ctx);
+ extern bool kmgr_wrap_key(PgKeyWrapCtx *ctx, CryptoKey *in, CryptoKey *out);
+ extern bool kmgr_unwrap_key(PgKeyWrapCtx *ctx, CryptoKey *in, CryptoKey *out);
+ 
+ 
+ 
+ extern void kmgr_derive_keys(char *passphrase, Size passlen,
+ 							 uint8 enckey[KMGR_ENC_KEY_LEN],
+ 							 uint8 mackey[KMGR_MAC_KEY_LEN]);
+ extern bool kmgr_verify_passphrase(char *passphrase, int passlen,
+ 								   CryptoKey *keys_in, CryptoKey *keys_out,
+ 								   int nkey);
+ extern bool kmgr_wrap_key(PgKeyWrapCtx *ctx, CryptoKey *in, CryptoKey *out);
+ extern bool kmgr_unwrap_key(PgKeyWrapCtx *ctx, CryptoKey *in, CryptoKey *out);
+ extern int	kmgr_run_cluster_passphrase_command(char *passphrase_command,
+ 												char *buf, int size);
+ extern CryptoKey *kmgr_get_cryptokeys(const char *path, int *nkeys);
+ 
+ #endif							/* KMGR_UTILS_H */
diff --git a/src/include/crypto/kmgr.h b/src/include/crypto/kmgr.h
new file mode 100644
index ...783f06d
*** a/src/include/crypto/kmgr.h
--- b/src/include/crypto/kmgr.h
***************
*** 0 ****
--- 1,29 ----
+ /*-------------------------------------------------------------------------
+  *
+  * kmgr.h
+  *
+  * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+  *
+  * src/include/crypto/kmgr.h
+  *
+  *-------------------------------------------------------------------------
+  */
+ #ifndef KMGR_H
+ #define KMGR_H
+ 
+ #include "common/cipher.h"
+ #include "common/kmgr_utils.h"
+ #include "storage/relfilenode.h"
+ #include "storage/bufpage.h"
+ 
+ /* GUC parameters */
+ extern bool key_management_enabled;
+ extern char *cluster_passphrase_command;
+ 
+ extern Size KmgrShmemSize(void);
+ extern void KmgrShmemInit(void);
+ extern void BootStrapKmgr(void);
+ extern void InitializeKmgr(void);
+ extern const CryptoKey *KmgrGetKey(int id);
+ 
+ #endif							/* KMGR_H */
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
new file mode 100644
index fb270df..d50a7c9
*** a/src/include/pg_config.h.in
--- b/src/include/pg_config.h.in
***************
*** 385,390 ****
--- 385,393 ----
  /* Define to 1 if you have the `OPENSSL_init_ssl' function. */
  #undef HAVE_OPENSSL_INIT_SSL
  
+ /* Define to 1 if you have the `OPENSSL_init_crypto' function. */
+ #undef HAVE_OPENSSL_INIT_CRYPTO
+ 
  /* Define to 1 if you have the <ossp/uuid.h> header file. */
  #undef HAVE_OSSP_UUID_H
  
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
new file mode 100644
index a821ff4..6549260
*** a/src/include/pgstat.h
--- b/src/include/pgstat.h
*************** typedef enum
*** 998,1003 ****
--- 998,1006 ----
  	WAIT_EVENT_DATA_FILE_TRUNCATE,
  	WAIT_EVENT_DATA_FILE_WRITE,
  	WAIT_EVENT_DSM_FILL_ZERO_WRITE,
+ 	WAIT_EVENT_KEY_FILE_READ,
+ 	WAIT_EVENT_KEY_FILE_WRITE,
+ 	WAIT_EVENT_KEY_FILE_SYNC,
  	WAIT_EVENT_LOCK_FILE_ADDTODATADIR_READ,
  	WAIT_EVENT_LOCK_FILE_ADDTODATADIR_SYNC,
  	WAIT_EVENT_LOCK_FILE_ADDTODATADIR_WRITE,
diff --git a/src/include/utils/guc_tables.h b/src/include/utils/guc_tables.h
new file mode 100644
index 04431d0..a0e582a
*** a/src/include/utils/guc_tables.h
--- b/src/include/utils/guc_tables.h
*************** enum config_group
*** 89,94 ****
--- 89,95 ----
  	STATS,
  	STATS_MONITORING,
  	STATS_COLLECTOR,
+ 	ENCRYPTION,
  	AUTOVACUUM,
  	CLIENT_CONN,
  	CLIENT_CONN_STATEMENT,
diff --git a/src/test/Makefile b/src/test/Makefile
new file mode 100644
index efb206a..5276c41
*** a/src/test/Makefile
--- b/src/test/Makefile
*************** endif
*** 29,35 ****
  endif
  ifeq ($(with_openssl),yes)
  ifneq (,$(filter ssl,$(PG_TEST_EXTRA)))
! SUBDIRS += ssl
  endif
  endif
  
--- 29,35 ----
  endif
  ifeq ($(with_openssl),yes)
  ifneq (,$(filter ssl,$(PG_TEST_EXTRA)))
! SUBDIRS += ssl crypto
  endif
  endif
  

Attachment: pass.sh
Description: Bourne shell script

Reply via email to