kms_v7.patch
application/octet-stream
Filename: kms_v7.patch
Type: application/octet-stream
Part: 0
Message:
Re: Internal key management system
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: unified
Series: patch v7
| File | + | − |
|---|---|---|
| configure | 7 | 1 |
| doc/src/sgml/config.sgml | 47 | 0 |
| doc/src/sgml/filelist.sgml | 1 | 0 |
| doc/src/sgml/func.sgml | 76 | 0 |
| doc/src/sgml/installation.sgml | 8 | 6 |
| doc/src/sgml/key-management.sgml | 134 | 0 |
| doc/src/sgml/postgres.sgml | 1 | 0 |
| doc/src/sgml/ref/initdb.sgml | 19 | 0 |
| src/backend/access/transam/xlog.c | 35 | 0 |
| src/backend/bootstrap/bootstrap.c | 6 | 1 |
| src/backend/crypto/kmgr.c | 320 | 0 |
| src/backend/crypto/Makefile | 17 | 0 |
| src/backend/Makefile | 1 | 1 |
| src/backend/postmaster/postmaster.c | 6 | 0 |
| src/backend/tcop/postgres.c | 8 | 0 |
| src/backend/utils/misc/guc.c | 25 | 0 |
| src/backend/utils/misc/postgresql.conf.sample | 5 | 0 |
| src/bin/initdb/initdb.c | 30 | 4 |
| src/bin/pg_controldata/pg_controldata.c | 3 | 0 |
| src/bin/pg_resetwal/pg_resetwal.c | 2 | 0 |
| src/bin/pg_upgrade/controldata.c | 106 | 1 |
| src/bin/pg_upgrade/pg_upgrade.c | 8 | 0 |
| src/bin/pg_upgrade/pg_upgrade.h | 6 | 0 |
| src/common/cipher.c | 134 | 0 |
| src/common/cipher_openssl.c | 212 | 0 |
| src/common/kmgr_utils.c | 445 | 0 |
| src/common/Makefile | 3 | 0 |
| src/include/access/xlog.h | 3 | 0 |
| src/include/catalog/pg_control.h | 7 | 0 |
| src/include/catalog/pg_proc.dat | 13 | 0 |
| src/include/common/cipher.h | 66 | 0 |
| src/include/common/cipher_openssl.h | 48 | 0 |
| src/include/common/kmgr_utils.h | 99 | 0 |
| src/include/crypto/kmgr.h | 27 | 0 |
| src/include/pg_config.h.in | 6 | 0 |
| src/include/utils/guc_tables.h | 1 | 0 |
| src/test/crypto/.gitignore | 2 | 0 |
| src/test/crypto/Makefile | 24 | 0 |
| src/test/crypto/t/001_basic.pl | 32 | 0 |
| src/test/Makefile | 1 | 1 |
| src/test/perl/PostgresNode.pm | 13 | 2 |
diff --git a/configure b/configure
index a7cf71b3f1..1a85ce3454 100755
--- a/configure
+++ b/configure
@@ -12222,7 +12222,7 @@ done
# defines OPENSSL_VERSION_NUMBER to claim version 2.0.0, even though it
# doesn't have these OpenSSL 1.1.0 functions. So check for individual
# functions.
- for ac_func in OPENSSL_init_ssl BIO_get_data BIO_meth_new ASN1_STRING_get0_data
+ for ac_func in OPENSSL_init_ssl OPENSSL_init_crypto BIO_get_data BIO_meth_new ASN1_STRING_get0_data
do :
as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh`
ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var"
@@ -13151,6 +13151,12 @@ else
as_fn_error $? "header file <openssl/err.h> is required for OpenSSL" "$LINENO" 5
fi
+ ac_fn_c_check_header_mongrel "$LINENO" "openssl/kdf.h" "ac_cv_header_openssl_kdf_h" "$ac_includes_default"
+if test "x$ac_cv_header_openssl_kdf_h" = xyes; then :
+ cat >> confdefs.h <<_ACEOF
+#define HAVE_OPENSSL_KDF 1
+_ACEOF
+fi
fi
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 70854ae298..6e7eea77ba 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7470,6 +7470,39 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv;
</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 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>
+ <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>
@@ -9314,6 +9347,20 @@ dynamic_library_path = 'C:\tools\postgresql;H:\my_project\lib;$libdir'
</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>)
<indexterm>
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index 3da2365ea9..26596cedae 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -48,6 +48,7 @@
<!ENTITY wal SYSTEM "wal.sgml">
<!ENTITY logical-replication SYSTEM "logical-replication.sgml">
<!ENTITY jit SYSTEM "jit.sgml">
+<!ENTITY key-management SYSTEM "key-management.sgml">
<!-- programmer's guide -->
<!ENTITY bgworker SYSTEM "bgworker.sgml">
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 464a48ed6a..ef2f2ae436 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -22272,4 +22272,80 @@ SELECT m.* FROM pg_statistic_ext join pg_statistic_ext_data on (oid = stxoid),
</sect1>
+ <sect1 id="functions-key-management">
+ <title>Key Management Functions</title>
+
+ <sect2 id="functions-key-management-wrap">
+ <title>Wrapping and Unwrapping Encryption Key</title>
+ <para>
+ The functions shown in
+ <xref linkend="functions-key-management-table"/> are for wrapping and
+ unwrapping the secret data with the master encryption key described in
+ <xref linkend="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_wrap</primary>
+ </indexterm>
+ <literal><function>pg_wrap(<parameter>data</parameter> <type>text</type>)</function></literal>
+ </entry>
+ <entry>
+ <type>bytea</type>
+ </entry>
+ <entry>
+ Wrap the given wrapped data with the master encryption key
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <indexterm>
+ <primary>pg_unwrap</primary>
+ </indexterm>
+ <literal><function>pg_unwrap(<parameter>data</parameter> <type>bytea</type>)</function></literal>
+ </entry>
+ <entry>
+ <type>text</type>
+ </entry>
+ <entry>
+ Unwrap the given data with the master encryption key
+ </entry>
+ </row>
+
+ <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
index 4f89b4e930..350a7381dc 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -251,10 +251,11 @@ su - postgres
<listitem>
<para>
You need <productname>OpenSSL</productname>, if you want to support
- encrypted client connections. <productname>OpenSSL</productname> is
- also required for random number generation on platforms that do not
- have <filename>/dev/urandom</filename> (except Windows). The minimum
- version required is 1.0.1.
+ encrypted client connections or enable the key management.
+ <productname>OpenSSL</productname> is also required for random
+ number generation on platforms that do not have
+ <filename>/dev/urandom</filename> (except Windows). The minimum
+ version required is 1.0.1 but for key management system it's 1.1.0.
</para>
</listitem>
@@ -979,8 +980,9 @@ build-postgresql:
<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
+ 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/key-management.sgml b/doc/src/sgml/key-management.sgml
new file mode 100644
index 0000000000..2bc0473867
--- /dev/null
+++ b/doc/src/sgml/key-management.sgml
@@ -0,0 +1,134 @@
+<!-- doc/src/sgml/key-management.sgml -->
+
+<chapter id="key-management">
+ <title>Encryption Key Management</title>
+
+ <indexterm zone="key-management">
+ <primary>key management</primary>
+ </indexterm>
+
+ <para>
+ <productname>PostgreSQL</productname> supports
+ <firstterm>Encryption Key Management System</firstterm>, which is enabled when
+ <productname>PostgreSQL</productname> is build with <literal>--with-openssl</literal>
+ and <xref linkend="app-initdb-cluster-passphrase-command"/> is specified during
+ <command>initdb</command>. The cluster passphrase provided by
+ <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 startup, unless the cluster passphrase is changed described in
+ <xref linkend="key-management-rotation"/>.
+ </para>
+
+ <para>
+ The user-provided cluster passphrase is drived into a
+ <firstterm>Key Encryption Key</firstterm>, which is used to encapsulate the
+ <firstterm>Master Encryption Key</firstterm> during the <command>initdb</command>
+ process. The encapsulated master encryption key is stored inside the database
+ cluster,
+ </para>
+
+ <para>
+ Encryption key management system provides several functions to allow user to
+ use the master encryption key to wrap and unwrap their own encryption secrets such
+ as encryption key and password during encryption and decryption operations. This
+ feature allows users to encrypt and decrypt data without knowing the actual key.
+ </para>
+
+ <sect1 id="key-management-wrap-unwrap">
+ <title>Wrap and Unwrap User Secret</title>
+
+ <para>
+ Encryption key management system provides several functions described in
+ in <xref linkend="functions-key-management-table"/>, to wrap and unwrap user
+ secrets with the master encryption key, which is uniquely and securely
+ stored inside the database cluster. Wrap and unwrap functions provides
+ integrity checking, to see if the wrapped data was modified.
+ </para>
+
+ <para>
+ These functions allow user to encrypt and decrypt user data without having
+ to provide user encryption secret in plain text. One possible use case is
+ to use encryption key management system together with <xref linkend="pgcrypto"/>.
+ User wraps the user encryption secret with <function>pg_wrap</function> function
+ and passes the wrapped encryption secret to <function>pg_unwrap</function>
+ function for the <structname>pgcrypto</structname> encryption functions.
+ The wrapped secret can be stored in the application server or somewhere
+ secured and should obtained promptly for cryptographic operation with
+ <structname>pgcrypto</structname>.
+ </para>
+
+ <para>
+ Here is an example that shows how to encrypt and decrypt data together with
+ wrap and unwrap functions:
+ </para>
+
+<programlisting>
+=# SELECT pg_wrap('user sercret key');
+ pg_wrap
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ \xb2c89f76f04f95d029f179e0fc3df4ed7254127b5562a9e27d42d1cd037c942dea65ce7c0750c520fa4f4e90481c9eb7e1e42a068248c262c1a6f25c6eab64303b1154ccc9a14361223641aab4a7aabe
+(1 row)
+</programlisting>
+
+ <para>
+ Once wrapping the user key, user can encrypt and decrypt user data using the
+ wrapped user key togehter with the key unwrap functions:
+ </para>
+
+<programlisting>
+ =# INSERT INTO tbl
+ VALUES (pgp_sym_encrypt('secret data',
+ pg_unwrap('\xb2c89f76f04f95d029f179e0fc3df4ed7254127b5562a9e27d42d1cd037c942dea65ce7c0750c520fa4f4e90481c9eb7e1e42a068248c262c1a6f25c6eab64303b1154ccc9a14361223641aab4a7aabe')));
+ INSERT 1
+
+ =# SELECT * FROM tbl;
+ col
+--------------------------------------------------------------------------------------------------------------------------------------------------------------
+ \xc30d04070302a199ee38bea0320b75d23c01577bb3ffb315d67eecbeca3e40e869cea65efbf0b470f805549af905f94d94c447fbfb8113f585fc86b30c0bd784b10c9857322dc00d556aa8de14
+(1 row)
+
+ =# SELECT pgp_sym_decrypt(col,
+ pg_unwrap('\xb2c89f76f04f95d029f179e0fc3df4ed7254127b5562a9e27d42d1cd037c942dea65ce7c0750c520fa4f4e90481c9eb7e1e42a068248c262c1a6f25c6eab64303b1154ccc9a14361223641aab4a7aabe')) as col
+ FROM tbl;
+ col
+------------------
+ user secret data
+(1 row)
+ </programlisting>
+ </sect1>
+
+ <sect1 id="key-management-rotation">
+ <title>Cluster Passphrase Rotation</title>
+
+ <para>
+ Encryption keys are not interminable, and possibility of key being breached
+ increases longer that a key is in use. Key rotation replaces old key with new
+ key and allows them to minimize their exposure to such an attacker. Rotating
+ keys on a regular basis help meet standardized security practices such as
+ <ulink url="https://www.pcisecuritystandards.org/">PCI-DSS</ulink> and is a
+ security best practice to limit the number of encrypted bytes available for a
+ specific key version. The key lifetime are based on key length, key strength,
+ algorithm and total number of bytes enciphered.
+ </para>
+
+ <para>
+ In <productname>PostgreSQL</productname> encryption key management, a key
+ is the cluster passphrase. Cluster passphrase rotation is represented by
+ generating a new version of the cluster passphrase, and making that version
+ as the primary version. It rotates the cluster passphrase to the new one
+ and re-encrypt the master encryption key with it again. The data that have
+ been encrypted with the master encryption key doesn't need to be re-encrypted
+ again because the raw master encryption key doesn't change.
+ </para>
+
+ <para>
+ To rotate the cluster passphrase user firstly needs to update
+ <xref linkend="guc-cluster-passphrase-command"/> in the
+ <filename>postgresql.conf</filename> so that
+ <productname>PostgreSQL</productname> can obtain a new encryption passphrase.
+ Then execute <function>pg_rotate_cluster_passphrase</function> function to
+ rotates the passphrase.
+ </para>
+ </sect1>
+</chapter>
diff --git a/doc/src/sgml/postgres.sgml b/doc/src/sgml/postgres.sgml
index e59cba7997..24eb37c054 100644
--- a/doc/src/sgml/postgres.sgml
+++ b/doc/src/sgml/postgres.sgml
@@ -163,6 +163,7 @@
&wal;
&logical-replication;
&jit;
+ &key-management;
®ress;
</part>
diff --git a/doc/src/sgml/ref/initdb.sgml b/doc/src/sgml/ref/initdb.sgml
index a04a180165..d5e8862516 100644
--- a/doc/src/sgml/ref/initdb.sgml
+++ b/doc/src/sgml/ref/initdb.sgml
@@ -165,6 +165,25 @@ PostgreSQL documentation
</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/src/backend/Makefile b/src/backend/Makefile
index 9706a95848..4ace302038 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -21,7 +21,7 @@ SUBDIRS = access bootstrap catalog parser commands executor foreign lib libpq \
main nodes optimizer partitioning port postmaster \
regex replication rewrite \
statistics storage tcop tsearch utils $(top_builddir)/src/timezone \
- jit
+ jit crypto
include $(srcdir)/common.mk
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 793c076da6..636cfc9792 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -42,6 +42,7 @@
#include "commands/progress.h"
#include "commands/tablespace.h"
#include "common/controldata_utils.h"
+#include "crypto/kmgr.h"
#include "miscadmin.h"
#include "pg_trace.h"
#include "pgstat.h"
@@ -78,6 +79,7 @@
#include "utils/timestamp.h"
extern uint32 bootstrap_data_checksum_version;
+extern bool bootstrap_key_management_enabled;
/* Unsupported old recovery command file names (relative to $PGDATA) */
#define RECOVERY_COMMAND_FILE "recovery.conf"
@@ -4824,6 +4826,9 @@ ReadControlFile(void)
/* 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);
}
/*
@@ -4856,6 +4861,16 @@ GetMockAuthenticationNonce(void)
return ControlFile->mock_authentication_nonce;
}
+/*
+ * Returns the wrapped master keys from control file..
+ */
+uint8 *
+GetMasterEncryptionKey(void)
+{
+ Assert(ControlFile != NULL);
+ return ControlFile->masterkey;
+}
+
/*
* Are checksums enabled for data pages?
*/
@@ -4866,6 +4881,16 @@ DataChecksumsEnabled(void)
return (ControlFile->data_checksum_version > 0);
}
+/*
+ * Are key management enabled?
+ */
+bool
+KeyManagementEnabled(void)
+{
+ Assert(ControlFile != NULL);
+ return ControlFile->key_management_enabled;
+}
+
/*
* Returns a fake LSN for unlogged relations.
*
@@ -5132,6 +5157,7 @@ BootStrapXLOG(void)
XLogPageHeader page;
XLogLongPageHeader longpage;
XLogRecord *record;
+ uint8 *masterkey;
char *recptr;
bool use_existent;
uint64 sysidentifier;
@@ -5272,6 +5298,15 @@ BootStrapXLOG(void)
ControlFile->checkPoint = checkPoint.redo;
ControlFile->checkPointCopy = checkPoint;
+ ControlFile->unloggedLSN = FirstNormalUnloggedLSN;
+
+ /* Bootstrap the key manager and store master keys into the control file */
+ if ((masterkey = BootStrapKmgr(bootstrap_key_management_enabled)) != NULL)
+ {
+ ControlFile->key_management_enabled = true;
+ memcpy(&(ControlFile->masterkey), masterkey, KMGR_WRAPPED_KEY_LEN);
+ }
+
/* some additional ControlFile fields are set in WriteControlFile() */
WriteControlFile();
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index 5480a024e0..569489cf65 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -28,6 +28,7 @@
#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,6 +52,7 @@
#include "utils/relmapper.h"
uint32 bootstrap_data_checksum_version = 0; /* No checksum */
+bool bootstrap_key_management_enabled = false;
#define ALLOC(t, c) \
@@ -226,7 +228,7 @@ AuxiliaryProcessMain(int argc, char *argv[])
/* If no -x argument, we are a CheckerProcess */
MyAuxProcType = CheckerProcess;
- while ((flag = getopt(argc, argv, "B:c:d:D:Fkr:x:X:-:")) != -1)
+ while ((flag = getopt(argc, argv, "B:c:d:D:eFkr:x:X:-:")) != -1)
{
switch (flag)
{
@@ -249,6 +251,9 @@ AuxiliaryProcessMain(int argc, char *argv[])
pfree(debugstr);
}
break;
+ case 'e':
+ bootstrap_key_management_enabled = true;
+ 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 0000000000..a641860a0f
--- /dev/null
+++ b/src/backend/crypto/Makefile
@@ -0,0 +1,17 @@
+#-------------------------------------------------------------------------
+#
+# 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 0000000000..6eadf88dba
--- /dev/null
+++ b/src/backend/crypto/kmgr.c
@@ -0,0 +1,320 @@
+/*-------------------------------------------------------------------------
+ *
+ * kmgr.c
+ * Key manager interface routines
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/storage/encryption/kmgr.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <unistd.h>
+
+#include "funcapi.h"
+#include "miscadmin.h"
+#include "pgstat.h"
+
+#include "access/xlog.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"
+
+/* GUC variable */
+bool key_management_enabled = false;;
+char *cluster_passphrase_command = NULL;
+
+static MemoryContext KmgrCtx = NULL;
+
+/*
+ * Raw master encryption key and HMAC key. This is used for
+ * key rotation so that we can wrap the same input easier.
+ */
+static uint8 *masterKeys = NULL;
+
+/* Encryption key used for pg_wrap and pg_unwrap */
+static uint8 *userWrapKey = NULL;
+
+/* Key wrap and unwrap contexts initialized with the master keys */
+static KeyWrapCtx *WrapCtx = NULL;
+static KeyWrapCtx *UnwrapCtx = NULL;
+
+static void ShutdownKmgr(int code, Datum arg);
+
+/*
+ * This function must be called ONCE on system install.
+ */
+uint8 *
+BootStrapKmgr(bool bootstrap_key_management_enabled)
+{
+ KeyWrapCtx *ctx;
+ char passphrase[KMGR_MAX_PASSPHRASE_LEN];
+ uint8 kek[KMGR_KEY_LEN];
+ uint8 kekhmac[KMGR_HMACKEY_LEN];
+ uint8 masterkeys[KMGR_KEY_AND_HMACKEY_LEN];
+ uint8 *wrapped_key;
+ int wrapped_keylen;
+ int passlen;
+
+ if (!bootstrap_key_management_enabled)
+ return NULL;
+
+#ifndef USE_OPENSSL
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ (errmsg("key management is not supported because OpenSSL is not supported by this build"),
+ errhint("Compile with --with-openssl to enable key management."))));
+#endif
+
+#ifndef HAVE_OPENSSL_KDF
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ (errmsg("key management is not supported because OpenSSL doesn't support key derivation functions"),
+ errhint("Compile with OpenSSL version 1.1.0 or higher to enable key management."))));
+#endif
+
+ /* Get key encryption key from 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_from_passphrase(passphrase, passlen, kek, kekhmac);
+ ctx = create_keywrap_ctx(kek, kekhmac, true);
+ if (!ctx)
+ ereport(ERROR,
+ (errmsg("could not initialize cipher contect")));
+
+ /* Generate the master encryption key and HMAC key */
+ if (!pg_strong_random(masterkeys, KMGR_KEY_LEN))
+ ereport(ERROR,
+ (errmsg("failed to generate cluster encryption key")));
+ if (!pg_strong_random(masterkeys + KMGR_KEY_LEN, KMGR_HMACKEY_LEN))
+ ereport(ERROR,
+ (errmsg("failed to generate cluster hmac key")));
+
+ /* Wrap the combined master keys by the key encryption key */
+ wrapped_key = palloc0(KMGR_WRAPPED_KEY_LEN);
+ if (!kmgr_wrap_key(ctx, masterkeys, KMGR_KEY_AND_HMACKEY_LEN,
+ wrapped_key, &wrapped_keylen))
+ {
+ free_keywrap_ctx(ctx);
+ ereport(ERROR,
+ (errmsg("failed to wrap cluster encryption key")));
+ }
+ Assert(wrapped_keylen == KMGR_WRAPPED_KEY_LEN);
+
+ free_keywrap_ctx(ctx);
+ return wrapped_key;
+}
+
+/*
+ * Get encryption key passphrase and verify it, then get the un-wrapped
+ * master encryption key and HMAC key. This function is called by postmaster
+ * at startup time.
+ */
+void
+InitializeKmgr(void)
+{
+ MemoryContext oldctx;
+ char passphrase[KMGR_MAX_PASSPHRASE_LEN];
+ uint8 kek_hmackey[KMGR_KEY_AND_HMACKEY_LEN];
+ uint8 userkey_info[KMGR_USER_KEY_INFO_LEN];
+ uint8 *wrapped_key;
+ uint8 *key;
+ uint8 *hmackey;
+ int passlen;
+
+ if (!key_management_enabled)
+ return;
+
+ /* Get cluster passphrase */
+ passlen = kmgr_run_cluster_passphrase_command(cluster_passphrase_command,
+ passphrase, KMGR_MAX_PASSPHRASE_LEN);
+
+ /* Get wrapped master keys: encryption key and HMAC key */
+ wrapped_key = GetMasterEncryptionKey();
+
+ /* Verify the correctness of given passphrase */
+ if (!kmgr_verify_passphrase(passphrase, passlen, wrapped_key, kek_hmackey))
+ ereport(ERROR,
+ (errmsg("cluster passphrase does not match expected passphrase")));
+
+ /* Get raw master key and hmac key */
+ key = kek_hmackey;
+ hmackey = (uint8 *) ((char *) kek_hmackey + KMGR_KEY_LEN);
+
+ KmgrCtx = AllocSetContextCreate(TopMemoryContext,
+ "Key manager context",
+ ALLOCSET_DEFAULT_SIZES);
+ oldctx = MemoryContextSwitchTo(KmgrCtx);
+
+ /*
+ * Create user wrap key used for pg_wrap and pg_unwrap. We don't directly
+ * use the master encryption key for that purpose so that we can separate
+ * multiple key spaces such as user wrap/unwrap and block encryption.
+ */
+ snprintf((char *) userkey_info, KMGR_USER_KEY_INFO_LEN, "%s"UINT64_FORMAT,
+ KMGE_USER_KEY_INFO_PREFIX, GetSystemIdentifier());
+ userWrapKey = palloc(KMGR_KEY_LEN);
+ if (!kmgr_derive_key(key, userkey_info, KMGR_USER_KEY_INFO_LEN,
+ userWrapKey, KMGR_KEY_LEN))
+ ereport(ERROR,
+ (errmsg("could not derive the user key")));
+
+ /* Set wrap and unwrap context with the master keys */
+ WrapCtx = create_keywrap_ctx(userWrapKey, hmackey, true);
+ UnwrapCtx = create_keywrap_ctx(userWrapKey, hmackey, false);
+
+ /* Cache the raw master keys */
+ masterKeys = palloc(KMGR_KEY_AND_HMACKEY_LEN);
+ memcpy(masterKeys, kek_hmackey, KMGR_KEY_AND_HMACKEY_LEN);
+
+ MemoryContextSwitchTo(oldctx);
+ on_shmem_exit(ShutdownKmgr, 0);
+}
+
+/*
+ * This must be called once during postmaster shutdown.
+ */
+static void
+ShutdownKmgr(int code, Datum arg)
+{
+ if (WrapCtx)
+ free_keywrap_ctx(WrapCtx);
+ if (UnwrapCtx)
+ free_keywrap_ctx(UnwrapCtx);
+}
+
+/*
+ * SQL function to wrap the given data by the master keys
+ */
+Datum
+pg_wrap(PG_FUNCTION_ARGS)
+{
+ text *data = PG_GETARG_TEXT_PP(0);
+ bytea *res;
+ int datalen;
+ int reslen;
+ int len;
+
+ if (!key_management_enabled)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("could not wrap key because key management is not supported")));
+
+ datalen = VARSIZE_ANY_EXHDR(data);
+ reslen = VARHDRSZ + SizeOfWrappedKey(datalen);
+ res = palloc(reslen);
+
+ if (!kmgr_wrap_key(WrapCtx, (uint8 *) VARDATA_ANY(data), datalen,
+ (uint8 *) VARDATA(res), &len))
+ ereport(ERROR,
+ (errmsg("could not wrap the given secret")));
+
+ SET_VARSIZE(res, reslen);
+
+ PG_RETURN_TEXT_P(res);
+}
+
+/*
+ * SQL function to unwrap the given data by the master keys
+ */
+Datum
+pg_unwrap(PG_FUNCTION_ARGS)
+{
+ bytea *data = PG_GETARG_BYTEA_PP(0);
+ text *res;
+ int datalen;
+ int buflen;
+ int len;
+
+ if (!key_management_enabled)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("could not wrap key because key management is not supported")));
+
+ datalen = VARSIZE_ANY_EXHDR(data);
+
+ if (datalen < MIN_WRAPPED_KEY_LEN)
+ ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid wrapped key input")));
+
+ buflen = VARHDRSZ + SizeOfUnwrappedKey(datalen);
+ res = palloc(buflen);
+
+ if (!kmgr_unwrap_key(UnwrapCtx, (uint8 *) VARDATA_ANY(data), datalen,
+ (uint8 *) VARDATA(res), &len))
+ ereport(ERROR,
+ (errmsg("could not unwrap the given secret")));
+
+ /*
+ * The size of unwrapped key can be smaller than the size estimated before
+ * unwrapping since the padding is removed during unwrapping.
+ */
+ SET_VARSIZE(res, VARHDRSZ + len);
+
+ PG_RETURN_TEXT_P(res);
+}
+
+/*
+ * SQL function to rotate the cluster passphrase. This function
+ * assumes that the cluster_passphrase_command is already reloaded
+ * to the new value.
+ */
+Datum
+pg_rotate_cluster_passphrase(PG_FUNCTION_ARGS)
+{
+ KeyWrapCtx *ctx;
+ char passphrase[KMGR_MAX_PASSPHRASE_LEN];
+ uint8 new_kek[KMGR_KEY_LEN];
+ uint8 new_hmackey[KMGR_HMACKEY_LEN];
+ uint8 wrapped_keys[KMGR_KEY_AND_HMACKEY_LEN];
+ uint8 *cur_masterkey;
+ int passlen;
+ int outlen;
+
+ if (!key_management_enabled)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("could not rotate cluster passphrase because key management is not supported")));
+
+ 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)));
+
+ kmgr_derive_from_passphrase(passphrase, passlen, new_kek, new_hmackey);
+
+ ctx = create_keywrap_ctx(new_kek, new_hmackey, true);
+
+ if (!kmgr_wrap_key(ctx, masterKeys, KMGR_KEY_AND_HMACKEY_LEN,
+ wrapped_keys, &outlen))
+ ereport(ERROR,
+ (errmsg("failed to wrap key")));
+
+ /* Update control file */
+ LWLockAcquire(ControlFileLock, LW_EXCLUSIVE);
+ cur_masterkey = GetMasterEncryptionKey();
+ memcpy(cur_masterkey, wrapped_keys, KMGR_WRAPPED_KEY_LEN);
+ UpdateControlFile();
+ LWLockRelease(ControlFileLock);
+
+ PG_RETURN_BOOL(true);
+}
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 2b9ab32293..5064a7392f 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -100,6 +100,7 @@
#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"
@@ -1335,6 +1336,11 @@ PostmasterMain(int argc, char *argv[])
*/
autovac_init();
+ /*
+ * Initialize key manager.
+ */
+ InitializeKmgr();
+
/*
* Load configuration files for client authentication.
*/
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 00c77b66c7..e574c161ee 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -42,6 +42,7 @@
#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"
@@ -3887,6 +3888,13 @@ PostgresMain(int argc, char *argv[],
/* Early initialization */
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
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index af876d1f01..bf5b7571d4 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -43,6 +43,7 @@
#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"
@@ -73,6 +74,7 @@
#include "replication/walsender.h"
#include "storage/bufmgr.h"
#include "storage/dsm_impl.h"
+#include "storage/standby.h"
#include "storage/fd.h"
#include "storage/large_object.h"
#include "storage/pg_shmem.h"
@@ -747,6 +749,8 @@ const char *const config_group_names[] =
gettext_noop("Statistics / Monitoring"),
/* STATS_COLLECTOR */
gettext_noop("Statistics / Query and Index Statistics Collector"),
+ /* ENCRYPTION */
+ gettext_noop("Encryption"),
/* AUTOVACUUM */
gettext_noop("Autovacuum"),
/* CLIENT_CONN */
@@ -2058,6 +2062,17 @@ static struct config_bool ConfigureNamesBool[] =
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
@@ -4315,6 +4330,16 @@ static struct config_string ConfigureNamesString[] =
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,
gettext_noop("Sets the application name to be reported in statistics and logs."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index aa44f0c9bf..146e52bbb5 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -615,6 +615,11 @@
# 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
index a6577486ce..ae8c356bb1 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -145,6 +145,7 @@ static bool data_checksums = false;
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 */
@@ -1206,6 +1207,13 @@ setup_config(void)
"password_encryption = scram-sha-256");
}
+ 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
@@ -1416,14 +1424,14 @@ bootstrap_template1(void)
unsetenv("PGCLIENTENCODING");
snprintf(cmd, sizeof(cmd),
- "\"%s\" --boot -x1 -X %u %s %s %s",
+ "\"%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++)
@@ -2311,6 +2319,8 @@ usage(const char *progname)
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"));
@@ -2377,7 +2387,6 @@ check_need_password(const char *authmethodlocal, const char *authmethodhost)
}
}
-
void
setup_pgdata(void)
{
@@ -2984,6 +2993,7 @@ main(int argc, char *argv[])
{"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}
};
@@ -3025,7 +3035,7 @@ main(int argc, char *argv[])
/* process command-line options */
- while ((c = getopt_long(argc, argv, "dD:E:kL:nNU:WA:sST:X:g", long_options, &option_index)) != -1)
+ while ((c = getopt_long(argc, argv, "c:dD:E:kL:nNU:WA:sST:X:g", long_options, &option_index)) != -1)
{
switch (c)
{
@@ -3107,6 +3117,9 @@ main(int argc, char *argv[])
case 9:
pwfilename = pg_strdup(optarg);
break;
+ case 'c':
+ cluster_passphrase = pg_strdup(optarg);
+ break;
case 's':
show_setting = true;
break;
@@ -3177,6 +3190,14 @@ main(int argc, char *argv[])
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);
@@ -3244,6 +3265,11 @@ main(int argc, char *argv[])
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
index e73639df74..7ba1eb0220 100644
--- a/src/bin/pg_controldata/pg_controldata.c
+++ b/src/bin/pg_controldata/pg_controldata.c
@@ -25,6 +25,7 @@
#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"
@@ -334,5 +335,7 @@ main(int argc, char *argv[])
ControlFile->data_checksum_version);
printf(_("Mock authentication nonce: %s\n"),
mock_auth_nonce_str);
+ printf(_("Key management: %s\n"),
+ ControlFile->key_management_enabled ? _("on") : _("off"));
return 0;
}
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 233441837f..b5b47eacb2 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -804,6 +804,8 @@ PrintControlValues(bool guessed)
(ControlFile.float8ByVal ? _("by value") : _("by reference")));
printf(_("Data page checksum version: %u\n"),
ControlFile.data_checksum_version);
+ printf(_("Key management: %s\n"),
+ ControlFile.key_management_enabled ? _("on") : _("off"));
}
diff --git a/src/bin/pg_upgrade/controldata.c b/src/bin/pg_upgrade/controldata.c
index 00d71e3a8a..ede73cae69 100644
--- a/src/bin/pg_upgrade/controldata.c
+++ b/src/bin/pg_upgrade/controldata.c
@@ -13,6 +13,12 @@
#include "pg_upgrade.h"
+#include "access/xlog_internal.h"
+#include "common/controldata_utils.h"
+#include "common/kmgr_utils.h"
+
+static ControlFileData *read_controlfile(ClusterInfo *cluster);
+
/*
* get_control_data()
*
@@ -59,6 +65,7 @@ get_control_data(ClusterInfo *cluster, bool live_check)
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;
@@ -202,6 +209,13 @@ get_control_data(ClusterInfo *cluster, bool live_check)
got_data_checksum_version = true;
}
+ /* Only in <= 12 */
+ if (GET_MAJOR_VERSION(cluster->major_version) <= 1200)
+ {
+ 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))
{
@@ -485,6 +499,18 @@ get_control_data(ClusterInfo *cluster, bool live_check)
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);
@@ -539,7 +565,8 @@ get_control_data(ClusterInfo *cluster, bool live_check)
!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_date_is_int || !got_data_checksum_version ||
+ !got_key_management_enabled)
{
if (cluster == &old_cluster)
pg_log(PG_REPORT,
@@ -605,6 +632,10 @@ get_control_data(ClusterInfo *cluster, bool live_check)
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");
}
}
@@ -669,6 +700,14 @@ check_control_data(ControlData *oldctrl,
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 maangement 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");
}
@@ -693,3 +732,69 @@ disable_old_cluster(void)
"Because \"link\" mode was used, the old cluster cannot be safely\n"
"started once the new cluster has been started.\n\n", old_cluster.pgdata);
}
+
+/*
+ * Copy the master encryption key from the old cluster to the new cluster.
+ */
+void
+copy_master_encryption_key(ClusterInfo *old_cluster, ClusterInfo * new_cluster)
+{
+ ControlFileData *old_controlfile;
+ ControlFileData *new_controlfile;
+
+ if (!old_cluster->controldata.key_management_enabled &&
+ !new_cluster->controldata.key_management_enabled)
+ return;
+
+ prep_status("Copying master encryption key");
+
+ /* Read both control file */
+ old_controlfile = read_controlfile(old_cluster);
+ new_controlfile = read_controlfile(new_cluster);
+
+ /* Copy the master encryption key */
+ memcpy(&(new_controlfile->masterkey), &(old_controlfile->masterkey),
+ KMGR_WRAPPED_KEY_LEN);
+
+ update_controlfile(new_cluster->pgdata, new_controlfile, true);
+
+ pg_free(old_controlfile);
+ pg_free(new_controlfile);
+
+ check_ok();
+}
+
+static ControlFileData *
+read_controlfile(ClusterInfo *cluster)
+{
+ int fd;
+ int len;
+ char path[MAXPGPATH];
+ char *buffer;
+ pg_crc32c crc;
+
+ snprintf(path, MAXPGPATH, "%s/%s", cluster->pgdata, XLOG_CONTROL_FILE);
+
+ if ((fd = open(path, O_RDONLY | PG_BINARY, 0)) < 0)
+ pg_fatal("could not open file \"%s\" for reading: %m", path);
+
+
+ buffer = (char *) pg_malloc(PG_CONTROL_FILE_SIZE);
+
+ len = read(fd, buffer, PG_CONTROL_FILE_SIZE);
+ if (len < 0)
+ pg_fatal("could not read file \"%s\": %m", path);
+ close(fd);
+
+ /* Check the CRC. */
+ INIT_CRC32C(crc);
+ COMP_CRC32C(crc,
+ buffer,
+ offsetof(ControlFileData, crc));
+ FIN_CRC32C(crc);
+
+ if (!EQ_CRC32C(crc, ((ControlFileData *) buffer)->crc))
+ pg_fatal("pg_control exists but has invalid CRC; proceed with caution");
+
+ return (ControlFileData *) buffer;
+}
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 70194eb096..a62e577655 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -157,6 +157,14 @@ main(int argc, char **argv)
transfer_all_new_tablespaces(&old_cluster.dbarr, &new_cluster.dbarr,
old_cluster.pgdata, new_cluster.pgdata);
+ /*
+ * Copy the master encryption key 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
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 8b90cefbe0..d60aab501b 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -11,6 +11,7 @@
#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
@@ -219,6 +220,8 @@ typedef struct
bool date_is_int;
bool float8_pass_by_value;
bool data_checksum_version;
+ bool key_management_enabled;
+ uint8 master_encryption_key[KMGR_WRAPPED_KEY_LEN];
} ControlData;
/*
@@ -346,6 +349,9 @@ void create_script_for_cluster_analyze(char **analyze_script_file_name);
void get_control_data(ClusterInfo *cluster, bool live_check);
void check_control_data(ControlData *oldctrl, ControlData *newctrl);
void disable_old_cluster(void);
+void copy_master_encryption_key(ClusterInfo *old_cluster,
+ ClusterInfo * new_cluster);
+
/* dump.c */
diff --git a/src/common/Makefile b/src/common/Makefile
index ce01df68b9..fd5ccc4c05 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -47,6 +47,7 @@ LIBS += $(PTHREAD_LIBS)
OBJS_COMMON = \
base64.o \
+ cipher.o \
config_info.o \
controldata_utils.o \
d2s.o \
@@ -58,6 +59,7 @@ OBJS_COMMON = \
ip.o \
jsonapi.o \
keywords.o \
+ kmgr_utils.o \
kwlookup.o \
link-canary.o \
md5.o \
@@ -77,6 +79,7 @@ OBJS_COMMON = \
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 0000000000..0fe0ac450c
--- /dev/null
+++ b/src/common/cipher.c
@@ -0,0 +1,134 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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"
+#include "common/cipher_openssl.h"
+
+void
+pg_cipher_setup(void)
+{
+#ifdef USE_OPENSSL
+ ossl_cipher_setup();
+#endif
+}
+
+pg_cipher_ctx *
+pg_cipher_ctx_create(void)
+{
+#ifdef USE_OPENSSL
+ return ossl_cipher_ctx_create();
+#endif
+ return NULL;
+}
+
+void
+pg_cipher_ctx_free(pg_cipher_ctx *ctx)
+{
+#ifdef USE_OPENSSL
+ ossl_cipher_ctx_free(ctx);
+#endif
+}
+
+bool
+pg_aes256_encrypt_init(pg_cipher_ctx *ctx, uint8 *key)
+{
+#ifdef USE_OPENSSL
+ return ossl_aes256_encrypt_init(ctx, key);
+#endif
+ return false;
+}
+
+bool
+pg_aes256_decrypt_init(pg_cipher_ctx *ctx, uint8 *key)
+{
+#ifdef USE_OPENSSL
+ return ossl_aes256_decrypt_init(ctx, key);
+#endif
+ return false;
+}
+
+bool
+pg_cipher_encrypt(pg_cipher_ctx *ctx, const uint8 *input, int input_size,
+ const uint8 *iv, uint8 *dest, int *dest_size)
+{
+ bool r = false;
+#ifdef USE_OPENSSL
+ r = ossl_cipher_encrypt(ctx, input, input_size, iv, dest, dest_size);
+#endif
+ return r;
+}
+
+bool
+pg_cipher_decrypt(pg_cipher_ctx *ctx, const uint8 *input, int input_size,
+ const uint8 *iv, uint8 *dest, int *dest_size)
+{
+ bool r = false;
+#ifdef USE_OPENSSL
+ r = ossl_cipher_decrypt(ctx, input, input_size, iv, dest, dest_size);
+#endif
+ return r;
+}
+
+bool
+pg_compute_HMAC(const uint8 *key, const uint8 *data,
+ int data_size, uint8 *result, int *result_size)
+{
+ bool r = true;
+#ifdef USE_OPENSSL
+ r = ossl_compute_HMAC(key, data, data_size, result,
+ result_size);
+#endif
+ return r;
+}
+
+pg_kdf_ctx *
+pg_kdf_ctx_create(void)
+{
+#ifdef USE_OPENSSL
+ return ossl_kdf_ctx_create();
+#endif
+ return NULL;
+}
+
+void
+pg_kdf_ctx_free(pg_kdf_ctx *ctx)
+{
+#ifdef USE_OPENSSL
+ ossl_kdf_ctx_free(ctx);
+#endif
+}
+
+bool
+pg_kdf_init(pg_kdf_ctx *ctx, const uint8 *base)
+{
+#ifdef USE_OPENSSL
+ return ossl_kdf_init(ctx, base);
+#endif
+ return false;
+}
+
+bool
+pg_derive_key(pg_kdf_ctx *ctx, const uint8 *info, int infolen,
+ uint8 *out, size_t outlen)
+{
+#ifdef USE_OPENSSL
+ return ossl_derive_key(ctx, info, infolen, out, outlen);
+#endif
+ return false;
+}
diff --git a/src/common/cipher_openssl.c b/src/common/cipher_openssl.c
new file mode 100644
index 0000000000..493d5515f6
--- /dev/null
+++ b/src/common/cipher_openssl.c
@@ -0,0 +1,212 @@
+/*-------------------------------------------------------------------------
+ * 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/cipher_openssl.h"
+
+#include <openssl/conf.h>
+#include <openssl/evp.h>
+#include <openssl/err.h>
+#include <openssl/hmac.h>
+
+#ifdef HAVE_OPENSSL_KDF
+#include <openssl/kdf.h>
+#endif
+
+bool
+ossl_cipher_setup(void)
+{
+#ifdef HAVE_OPENSSL_INIT_CRYPTO
+ /* Setup OpenSSL */
+ if (!OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CONFIG, NULL))
+ return false;
+ return true;
+#else
+ OPENSSL_config(NULL);
+#endif
+ return true;
+}
+
+pg_cipher_ctx *
+ossl_cipher_ctx_create(void)
+{
+ return EVP_CIPHER_CTX_new();
+}
+
+void
+ossl_cipher_ctx_free(pg_cipher_ctx *ctx)
+{
+ EVP_CIPHER_CTX_free(ctx);
+}
+
+bool
+ossl_aes256_encrypt_init(pg_cipher_ctx *ctx, uint8 *key)
+{
+ if (!EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, NULL, NULL))
+ return false;
+ if (!EVP_CIPHER_CTX_set_key_length(ctx, PG_AES256_KEY_LEN))
+ return false;
+ if (!EVP_EncryptInit_ex(ctx, NULL, NULL, key, NULL))
+ return false;
+
+ /*
+ * 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 true;
+}
+
+bool
+ossl_aes256_decrypt_init(pg_cipher_ctx *ctx, uint8 *key)
+{
+ if (!EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, NULL, NULL))
+ return false;
+ if (!EVP_CIPHER_CTX_set_key_length(ctx, PG_AES256_KEY_LEN))
+ return false;
+ if (!EVP_DecryptInit_ex(ctx, NULL, NULL, key, NULL))
+ return false;
+
+ /*
+ * 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 true;
+}
+
+bool
+ossl_cipher_encrypt(pg_cipher_ctx *ctx,
+ const uint8 *in, int inlen,
+ const uint8 *iv, uint8 *out,
+ int *outlen)
+{
+ 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(pg_cipher_ctx *ctx,
+ const uint8 *in, int inlen,
+ const uint8 *iv, uint8 *out,
+ int *outlen)
+{
+ 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_compute_HMAC(const uint8 *key, const uint8 *data,
+ int data_size, uint8 *result,
+ int *result_size)
+{
+ return HMAC(EVP_sha256(), key, PG_AES256_KEY_LEN, data,
+ (uint32) data_size, result, (uint32 *) result_size);
+}
+
+/* Key derivation functions (KDF) */
+
+pg_kdf_ctx *
+ossl_kdf_ctx_create(void)
+{
+#ifdef HAVE_OPENSSL_KDF
+ return EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
+#endif
+ return NULL;
+}
+
+void
+ossl_kdf_ctx_free(pg_kdf_ctx *ctx)
+{
+#ifdef HAVE_OPENSSL_KDF
+ EVP_PKEY_CTX_free(ctx);
+#endif
+}
+
+bool
+ossl_kdf_init(pg_kdf_ctx *ctx, const uint8 *base)
+{
+#ifdef HAVE_OPENSSL_KDF
+ if (!EVP_PKEY_derive_init(ctx))
+ return false;
+
+ if (!EVP_PKEY_CTX_set_hkdf_md(ctx, EVP_sha256()))
+ return false;
+
+ if (!EVP_PKEY_CTX_set1_hkdf_key(ctx, base, PG_AES256_KEY_LEN))
+ return false;
+
+ return true;
+#else
+ return false;
+#endif
+}
+
+bool
+ossl_derive_key(pg_kdf_ctx *ctx, const uint8 *info, int infolen,
+ uint8 *out, size_t outlen)
+{
+#ifdef HAVE_OPENSSL_KDF
+ size_t ol = outlen;
+
+ if (!EVP_PKEY_CTX_add1_hkdf_info(ctx, info, infolen))
+ return false;
+
+ if (!EVP_PKEY_derive(ctx, out, &ol))
+ return false;
+
+ return true;
+#else
+ return false;
+#endif
+}
diff --git a/src/common/kmgr_utils.c b/src/common/kmgr_utils.c
new file mode 100644
index 0000000000..b447e5affd
--- /dev/null
+++ b/src/common/kmgr_utils.c
@@ -0,0 +1,445 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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
+
+#ifdef FRONTEND
+#include "common/logging.h"
+#endif
+#include "common/kmgr_utils.h"
+#include "common/sha2.h"
+#include "crypto/kmgr.h"
+#include "utils/elog.h"
+#include "storage/fd.h"
+
+#define KMGR_PROMPT_MSG "Enter database encryption pass phrase:"
+
+static bool cipher_setup = false;
+
+#ifdef FRONTEND
+static FILE *open_pipe_stream(const char *command);
+static int close_pipe_stream(FILE *file);
+#endif
+
+/*
+ * Return the key wrap context initialized with the given keys. Initialize the
+ * context for key wrapping if `for_wrap` is true, otherwise for unwrapping.
+ */
+KeyWrapCtx *
+create_keywrap_ctx(uint8 key[KMGR_KEY_LEN], uint8 hmackey[KMGR_HMACKEY_LEN],
+ bool for_wrap)
+{
+ KeyWrapCtx *ctx;
+ int ret;
+
+ if (!cipher_setup)
+ {
+ pg_cipher_setup();
+ cipher_setup = true;
+ }
+
+#ifndef FRONTEND
+ ctx = (KeyWrapCtx *) palloc0(sizeof(KeyWrapCtx));
+#else
+ ctx = (KeyWrapCtx *) pg_malloc0(sizeof(KeyWrapCtx));
+#endif
+
+ /* Create a cipher context */
+ ctx->cipher = pg_cipher_ctx_create();
+ if (ctx->cipher == NULL)
+ return NULL;
+
+ /* Initialize the cipher context */
+ if (for_wrap)
+ ret = pg_aes256_encrypt_init(ctx->cipher, key);
+ else
+ ret = pg_aes256_decrypt_init(ctx->cipher, key);
+
+ if (!ret)
+ return NULL;
+
+ /* Set encryption key and HMAC key */
+ memcpy(ctx->key, key, KMGR_KEY_LEN);
+ memcpy(ctx->hmackey, hmackey, KMGR_HMACKEY_LEN);
+
+ return ctx;
+}
+
+/* Free the given cipher context */
+void
+free_keywrap_ctx(KeyWrapCtx *ctx)
+{
+ if (!ctx)
+ return;
+
+ Assert(ctx->cipher);
+
+ pg_cipher_ctx_free(ctx->cipher);
+
+#ifndef FRONTEND
+ pfree(ctx);
+#else
+ pg_free(ctx);
+#endif
+}
+
+/*
+ * Verify the correctness of the given passphrase by unwrapping the `wrapped_key`
+ * by the keys extracted from the passphrase. If the given passphrase is correct
+ * we set unwrapped keys to `raw_key` and return true. Otherwise return false.
+ */
+bool
+kmgr_verify_passphrase(char *passphrase, int passlen,
+ uint8 wrapped_key[KMGR_WRAPPED_KEY_LEN],
+ uint8 raw_key[KMGR_KEY_AND_HMACKEY_LEN])
+{
+ uint8 user_key[KMGR_KEY_LEN];
+ uint8 user_hmackey[KMGR_HMACKEY_LEN];
+ KeyWrapCtx *ctx;
+ int keylen;
+
+ /* Extract encryption key and HMAC key from the passphrase */
+ kmgr_derive_from_passphrase(passphrase, passlen, user_key, user_hmackey);
+
+ ctx = create_keywrap_ctx(user_key, user_hmackey, false);
+ if (!kmgr_unwrap_key(ctx, wrapped_key, KMGR_WRAPPED_KEY_LEN,
+ raw_key, &keylen))
+ {
+ /* The passphrase is not correct */
+ free_keywrap_ctx(ctx);
+ return false;
+ }
+
+ /* The passphrase is correct, free the cipher context */
+ free_keywrap_ctx(ctx);
+
+ return true;
+}
+
+/* Hash the given passphrase and extract it into encryption key and HMAC key */
+void
+kmgr_derive_from_passphrase(char *passphrase, Size passlen,
+ uint8 key[KMGR_KEY_LEN],
+ uint8 hmackey[KMGR_HMACKEY_LEN])
+{
+ uint8 keys[PG_SHA512_DIGEST_LENGTH];
+ pg_sha512_ctx ctx;
+
+ pg_sha512_init(&ctx);
+ pg_sha512_update(&ctx, (const uint8 *) passphrase, passlen);
+ pg_sha512_final(&ctx, keys);
+
+ /*
+ * SHA-512 results 64 bytes. We extract it into two keys for each 32
+ * bytes.
+ */
+ if (key)
+ memcpy(key, keys, KMGR_KEY_LEN);
+ if (hmackey)
+ memcpy(hmackey, keys + KMGR_KEY_LEN, KMGR_HMACKEY_LEN);
+}
+
+/*
+ * Wrap the given key. Return true and set wrapped key to `out` if success.
+ * Otherwise return false. The caller must allocate sufficient space for
+ * wrapped key calculated by using SizeOfWrappedKey.
+ */
+bool
+kmgr_wrap_key(KeyWrapCtx *ctx, const uint8 *in, int inlen, uint8 *out, int *outlen)
+{
+ uint8 iv[AES_IV_SIZE];
+ uint8 hmac[KMGR_HMAC_LEN];
+ uint8 *keyenc;
+ int keylen;
+
+ Assert(ctx && in && out);
+
+ /* Generate IV */
+ if (!pg_strong_random(iv, AES_IV_SIZE))
+ return false;
+
+ /*
+ * To avoid allocating the memory for encrypted data, we store encrypted
+ * data directly into *out. Encrypted data places at the end.
+ */
+ keyenc = (uint8 *) ((char *) out + KMGR_HMAC_LEN + AES_IV_SIZE);
+
+ if (!pg_cipher_encrypt(ctx->cipher, in, inlen, iv, keyenc, &keylen))
+ return false;
+
+ if (!kmgr_compute_HMAC(ctx, keyenc, keylen, hmac))
+ return false;
+
+ /*
+ * Assemble the wrapped key. The order of the wrapped key is hmac, iv and
+ * encrypted data.
+ */
+ memcpy(out, hmac, KMGR_HMAC_LEN);
+ memcpy(out + KMGR_HMAC_LEN, iv, AES_IV_SIZE);
+
+ *outlen = SizeOfWrappedKey(inlen);
+
+ return true;
+}
+
+/*
+ * Unwrap the given key. Return true and set unwrapped key to `out` if success.
+ * Otherwise return false. The caller must allocate sufficient space for
+ * unwrapped key calculated by using SizeOfUnwrappedKey.
+ */
+bool
+kmgr_unwrap_key(KeyWrapCtx *ctx, const uint8 *in, int inlen, uint8 *out, int *outlen)
+{
+ uint8 hmac[KMGR_HMAC_LEN];
+ uint8 *iv;
+ uint8 *expected_hmac;
+ uint8 *keyenc;
+ int keylen;
+ char *p = (char *) in;;
+
+ Assert(ctx && in && out);
+
+ /* Disassemble the wrapped keys */
+ expected_hmac = (uint8 *) p;
+ p += KMGR_HMAC_LEN;
+ iv = (uint8 *) p;
+ p += AES_IV_SIZE;
+ keylen = inlen - (p - ((char *) in));
+ keyenc = (uint8 *) p;
+
+ /* Verify the correctness of HMAC */
+ if (!kmgr_compute_HMAC(ctx, keyenc, keylen, hmac))
+ return false;
+
+ if (memcmp(hmac, expected_hmac, KMGR_HMAC_LEN) != 0)
+ return false;
+
+ /* Decrypt encrypted data */
+ if (!pg_cipher_decrypt(ctx->cipher, keyenc, keylen, iv, out, outlen))
+ return false;
+
+ return true;
+}
+
+/*
+ * Compute HMAC of the given input. The HMAC is the fixed length,
+ * KMGR_HMAC_LEN bytes. The caller must allocate enough memory.
+ */
+bool
+kmgr_compute_HMAC(KeyWrapCtx *ctx, const uint8 *in, int inlen, uint8 *out)
+{
+ int resultsize = 0;
+
+ Assert(ctx && in && out);
+ return pg_compute_HMAC(ctx->hmackey, in, inlen, out, &resultsize);
+}
+
+/* Derive a new key based on the base key */
+bool
+kmgr_derive_key(const uint8 *basekey, const uint8 *info, int infolen,
+ uint8 *out, int outlen)
+{
+ pg_kdf_ctx *ctx;
+
+ if ((ctx = pg_kdf_ctx_create()) == NULL)
+ return false;
+
+ if (!pg_kdf_init(ctx, basekey))
+ {
+ pg_kdf_ctx_free(ctx);
+ return false;
+ }
+
+ if (!pg_derive_key(ctx, info, infolen, out, outlen))
+ {
+ pg_kdf_ctx_free(ctx);
+ return false;
+ }
+
+ pg_kdf_ctx_free(ctx);
+ return true;
+}
+
+
+/*
+ * 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':
+ StrNCpy(dp, KMGR_PROMPT_MSG, strlen(KMGR_PROMPT_MSG));
+ 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 */
diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h
index 98b033fc20..0b67d1b474 100644
--- a/src/include/access/xlog.h
+++ b/src/include/access/xlog.h
@@ -15,6 +15,7 @@
#include "access/xlogdefs.h"
#include "access/xloginsert.h"
#include "access/xlogreader.h"
+#include "crypto/kmgr.h"
#include "datatype/timestamp.h"
#include "lib/stringinfo.h"
#include "nodes/pg_list.h"
@@ -291,8 +292,10 @@ extern TimestampTz GetCurrentChunkReplayStartTime(void);
extern void UpdateControlFile(void);
extern uint64 GetSystemIdentifier(void);
+extern uint8 *GetMasterEncryptionKey(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
index de5670e538..92bef35705 100644
--- a/src/include/catalog/pg_control.h
+++ b/src/include/catalog/pg_control.h
@@ -17,6 +17,7 @@
#include "access/transam.h"
#include "access/xlogdefs.h"
+#include "crypto/kmgr.h"
#include "pgtime.h" /* for pg_time_t */
#include "port/pg_crc32c.h"
@@ -219,6 +220,9 @@ typedef struct ControlFileData
/* Are data pages protected by checksums? Zero if no checksum version */
uint32 data_checksum_version;
+ /* Key management cipher. Off by default */
+ bool key_management_enabled;
+
/*
* Random nonce, used in authentication requests that need to proceed
* based on values that are cluster-unique, like a SASL exchange that
@@ -226,6 +230,9 @@ typedef struct ControlFileData
*/
char mock_authentication_nonce[MOCK_AUTH_NONCE_LEN];
+ /* Database cluster master key */
+ uint8 masterkey[KMGR_WRAPPED_KEY_LEN];
+
/* 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
index a64378b002..7aac158671 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10782,4 +10782,17 @@
proname => 'pg_partition_root', prorettype => 'regclass',
proargtypes => 'regclass', prosrc => 'pg_partition_root' },
+# 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' },
+{ oid => '8201', descr => 'wrap the given data',
+ proname => 'pg_wrap',
+ provolatile => 'v', prorettype => 'bytea',
+ proargtypes => 'text', prosrc => 'pg_wrap' },
+{ oid => '8202', descr => 'unwrap the given data',
+ proname => 'pg_unwrap',
+ provolatile => 'v', prorettype => 'text',
+ proargtypes => 'bytea', prosrc => 'pg_unwrap' },
]
diff --git a/src/include/common/cipher.h b/src/include/common/cipher.h
new file mode 100644
index 0000000000..74d543b690
--- /dev/null
+++ b/src/include/common/cipher.h
@@ -0,0 +1,66 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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
+
+/* Key length of AES256 */
+#define PG_AES256_KEY_LEN 32
+
+/*
+ * The encrypted data is a series of blocks of size ENCRYPTION_BLOCK.
+ * Initialization vector(IV) is the same size of cipher block.
+ */
+#define AES_BLOCK_SIZE 16
+#define AES_IV_SIZE (AES_BLOCK_SIZE)
+
+/* HMAC key and HMAC length. We use HMAC-SHA256 */
+#define PG_HMAC_SHA256_KEY_LEN 32
+#define PG_HMAC_SHA256_LEN 32
+
+#ifdef USE_OPENSSL
+typedef EVP_CIPHER_CTX pg_cipher_ctx;
+typedef EVP_PKEY_CTX pg_kdf_ctx;
+#else
+typedef void pg_cipher_ctx;
+typedef void pg_kdf_ctx;
+#endif
+
+extern pg_cipher_ctx *pg_cipher_ctx_create(void);
+extern void pg_cipher_ctx_free(pg_cipher_ctx *ctx);
+extern void pg_cipher_setup(void);
+extern bool pg_aes256_encrypt_init(pg_cipher_ctx *ctx, uint8 *key);
+extern bool pg_aes256_decrypt_init(pg_cipher_ctx *ctx, uint8 *key);
+extern bool pg_cipher_encrypt(pg_cipher_ctx *ctx,
+ const uint8 *input, int input_size,
+ const uint8 *iv, uint8 *dest,
+ int *dest_size);
+extern bool pg_cipher_decrypt(pg_cipher_ctx *ctx,
+ const uint8 *input, int input_size,
+ const uint8 *iv, uint8 *dest,
+ int *dest_size);
+extern bool pg_compute_HMAC(const uint8 *key, const uint8 *data,
+ int data_size, uint8 *result,
+ int *result_size);
+
+extern pg_kdf_ctx *pg_kdf_ctx_create(void);
+extern void pg_kdf_ctx_free(pg_kdf_ctx *ctx);
+extern bool pg_kdf_init(pg_kdf_ctx *ctx, const uint8 *base);
+extern bool pg_derive_key(pg_kdf_ctx *ctx, const uint8 *info, int infolen,
+ uint8 *out, size_t outlen);
+
+#endif /* CIPHER_H */
diff --git a/src/include/common/cipher_openssl.h b/src/include/common/cipher_openssl.h
new file mode 100644
index 0000000000..772f0cc657
--- /dev/null
+++ b/src/include/common/cipher_openssl.h
@@ -0,0 +1,48 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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"
+
+/* Cipher functions */
+extern pg_cipher_ctx *ossl_cipher_ctx_create(void);
+extern void ossl_cipher_ctx_free(pg_cipher_ctx *ctx);
+extern bool ossl_cipher_setup(void);
+extern bool ossl_aes256_encrypt_init(pg_cipher_ctx *ctx, uint8 *key);
+extern bool ossl_aes256_decrypt_init(pg_cipher_ctx *ctx, uint8 *key);
+extern bool ossl_cipher_encrypt(pg_cipher_ctx *ctx,
+ const uint8 *in, int inlen,
+ const uint8 *iv, uint8 *out,
+ int *outlen);
+extern bool ossl_cipher_decrypt(pg_cipher_ctx *ctx,
+ const uint8 *in, int inlen,
+ const uint8 *iv, uint8 *out,
+ int *outlen);
+extern bool ossl_compute_HMAC(const uint8 *key, const uint8 *data,
+ int data_size, uint8 *result,
+ int *result_size);
+
+/* Key derivation functions */
+extern pg_kdf_ctx *ossl_kdf_ctx_create(void);
+extern void ossl_kdf_ctx_free(pg_kdf_ctx *ctx);
+extern bool ossl_kdf_init(pg_kdf_ctx *ctx, const uint8 *base);
+extern bool ossl_derive_key(pg_kdf_ctx *ctx, const uint8 *info, int infolen,
+ uint8 *out, size_t outlen);
+
+#endif
diff --git a/src/include/common/kmgr_utils.h b/src/include/common/kmgr_utils.h
new file mode 100644
index 0000000000..90478902aa
--- /dev/null
+++ b/src/include/common/kmgr_utils.h
@@ -0,0 +1,99 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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"
+
+/* As of now key length supports only AES-256 key */
+#define KMGR_KEY_LEN PG_AES256_KEY_LEN
+
+/* Key management uses HMAC-256 */
+#define KMGR_HMACKEY_LEN PG_HMAC_SHA256_KEY_LEN
+#define KMGR_HMAC_LEN PG_HMAC_SHA256_LEN
+
+/* Allowed length of cluster passphrase */
+#define KMGR_MIN_PASSPHRASE_LEN 64
+#define KMGR_MAX_PASSPHRASE_LEN 1024
+
+/*
+ * Wrapped key consists of HMAC of encrypted key, IV and encrypted key.
+ */
+#define KMGR_KEY_AND_HMACKEY_LEN (KMGR_KEY_LEN + KMGR_HMACKEY_LEN)
+#define KMGR_WRAPPED_KEY_LEN \
+ (KMGR_HMAC_LEN + AES_IV_SIZE + SizeOfKeyWithPadding(KMGR_KEY_AND_HMACKEY_LEN))
+
+/*
+ * Information used for key derivation of user wrap key which is used for
+ * pg_wrap and pg_unwrap. The information consists of prefix and system
+ * identifier which is 32 bytes long.
+ */
+#define KMGE_USER_KEY_INFO_PREFIX "USER_KEY:"
+#define KMGR_USER_KEY_INFO_LEN (9 + 32)
+
+/*
+ * Size of encrypted key size with padding. We use PKCS#7 padding
+ * described in RFC 5652.
+ */
+#define SizeOfKeyWithPadding(klen) \
+ ((int)(klen) + (AES_BLOCK_SIZE - ((int)(klen) % AES_BLOCK_SIZE)))
+
+/*
+ * Macro to compute the size of wrapped and unwrapped key. The wrapped
+ * key consists of HMAC of the encrypted data, IV and the encrypted data
+ * that is the same length as the input.
+ */
+#define SizeOfWrappedKey(klen) \
+ (KMGR_HMACKEY_LEN + AES_IV_SIZE + SizeOfKeyWithPadding((int)(klen)))
+#define SizeOfUnwrappedKey(klen) \
+ ((int)(klen) - (KMGR_HMACKEY_LEN + AES_IV_SIZE))
+
+/* Minimum length of wrapped key */
+#define MIN_WRAPPED_KEY_LEN SizeOfWrappedKey(0)
+
+/* Key wrapping cipher context */
+typedef struct KeyWrapCtx
+{
+ uint8 key[KMGR_KEY_LEN];
+ uint8 hmackey[KMGR_HMACKEY_LEN];
+ pg_cipher_ctx *cipher;
+} KeyWrapCtx;
+
+/* Key derivation function context */
+typedef struct KdfCtx
+{
+ uint8 key[KMGR_KEY_LEN];
+ pg_kdf_ctx *kdf;
+} KdfCtx;
+
+extern KeyWrapCtx *create_keywrap_ctx(uint8 key[KMGR_KEY_LEN],
+ uint8 hmackey[KMGR_HMACKEY_LEN],
+ bool for_wrap);
+extern void free_keywrap_ctx(KeyWrapCtx *ctx);
+extern void kmgr_derive_from_passphrase(char *passphrase, Size passlen,
+ uint8 key[KMGR_KEY_LEN],
+ uint8 hmackey[KMGR_HMACKEY_LEN]);
+extern bool kmgr_verify_passphrase(char *passphrase, int passlen,
+ uint8 wrapped_key[KMGR_WRAPPED_KEY_LEN],
+ uint8 raw_key[KMGR_KEY_AND_HMACKEY_LEN]);
+extern bool kmgr_wrap_key(KeyWrapCtx *ctx, const uint8 *in, int inlen,
+ uint8 *out, int *outlen);
+extern bool kmgr_unwrap_key(KeyWrapCtx *ctx, const uint8 *in, int inlen,
+ uint8 *out, int *outlen);
+extern bool kmgr_derive_key(const uint8 *basekey, const uint8 *info, int infolen,
+ uint8 *out, int outlen);
+extern bool kmgr_compute_HMAC(KeyWrapCtx *ctx, const uint8 *in, int inlen,
+ uint8 *out);
+extern int kmgr_run_cluster_passphrase_command(char *passphrase_command,
+ char *buf, int size);
+
+#endif /* KMGR_UTILS_H */
diff --git a/src/include/crypto/kmgr.h b/src/include/crypto/kmgr.h
new file mode 100644
index 0000000000..ec80d2133f
--- /dev/null
+++ b/src/include/crypto/kmgr.h
@@ -0,0 +1,27 @@
+/*-------------------------------------------------------------------------
+ *
+ * kmgr.h
+ * Key management module for transparent data encryption
+ *
+ * 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 parameter */
+extern bool key_management_enabled;
+extern char *cluster_passphrase_command;
+
+extern uint8 *BootStrapKmgr(bool bootstrap_key_management_enabled);
+extern void InitializeKmgr(void);
+
+#endif /* KMGR_H */
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 41ad209380..41de0f2bc9 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -385,6 +385,12 @@
/* 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 <openssl/kdf.h> header file. */
+#undef HAVE_OPENSSL_KDF
+
/* Define to 1 if you have the <ossp/uuid.h> header file. */
#undef HAVE_OSSP_UUID_H
diff --git a/src/include/utils/guc_tables.h b/src/include/utils/guc_tables.h
index 454c2df487..c0c53b1e13 100644
--- a/src/include/utils/guc_tables.h
+++ b/src/include/utils/guc_tables.h
@@ -89,6 +89,7 @@ enum config_group
STATS,
STATS_MONITORING,
STATS_COLLECTOR,
+ ENCRYPTION,
AUTOVACUUM,
CLIENT_CONN,
CLIENT_CONN_STATEMENT,
diff --git a/src/test/Makefile b/src/test/Makefile
index efb206aa75..5276c4184f 100644
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -29,7 +29,7 @@ endif
endif
ifeq ($(with_openssl),yes)
ifneq (,$(filter ssl,$(PG_TEST_EXTRA)))
-SUBDIRS += ssl
+SUBDIRS += ssl crypto
endif
endif
diff --git a/src/test/crypto/.gitignore b/src/test/crypto/.gitignore
new file mode 100644
index 0000000000..e07b677a7d
--- /dev/null
+++ b/src/test/crypto/.gitignore
@@ -0,0 +1,2 @@
+# Generated by regression tests
+/tmp_check/
diff --git a/src/test/crypto/Makefile b/src/test/crypto/Makefile
new file mode 100644
index 0000000000..b82e0cb554
--- /dev/null
+++ b/src/test/crypto/Makefile
@@ -0,0 +1,24 @@
+#-------------------------------------------------------------------------
+#
+# Makefile for src/test/crypto
+#
+# Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+#
+# src/test/crypto/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/test/crypto
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+export with_openssl
+
+check:
+ $(prove_check)
+
+installcheck:
+ $(prove_installcheck)
+
+clean distclean maintainer-clean:
+ rm -rf tmp_check
diff --git a/src/test/crypto/t/001_basic.pl b/src/test/crypto/t/001_basic.pl
new file mode 100644
index 0000000000..82f3111a00
--- /dev/null
+++ b/src/test/crypto/t/001_basic.pl
@@ -0,0 +1,32 @@
+use strict;
+use warnings;
+use TestLib;
+use PostgresNode;
+use Test::More tests => 6;
+
+my $node = get_new_node('node');
+$node->init(enable_kms => 1);
+$node->start;
+
+sub test_wrap
+{
+ my ($node, $data, $test_name) = @_;
+
+ my $res = $node->safe_psql(
+ 'postgres',
+ qq(
+ SELECT pg_unwrap(pg_wrap('$data'));
+ )
+ );
+ is($res, $data, $test_name);
+}
+
+# Control file should know that checksums are disabled.
+command_like(
+ [ 'pg_controldata', $node->data_dir ],
+ qr/Key management:.*on/,
+ 'key manager is enabled in control file');
+
+test_wrap($node, '123456', 'less block size');
+test_wrap($node, '1234567890123456', 'one block size');
+test_wrap($node, '12345678901234567890', 'more than one block size');
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index 9575268bd7..51cbd83e14 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -434,8 +434,19 @@ sub init
mkdir $self->backup_dir;
mkdir $self->archive_dir;
- TestLib::system_or_bail('initdb', '-D', $pgdata, '-A', 'trust', '-N',
- @{ $params{extra} });
+ if ($params{enable_kms})
+ {
+ TestLib::system_or_bail('initdb', '-D', $pgdata, '-A', 'trust', '-N',
+ '--cluster-passphrase-command',
+ 'echo 1234567890123456789012345678901234567890123456789012345678901234',
+ @{ $params{extra} });
+ }
+ else
+ {
+ TestLib::system_or_bail('initdb', '-D', $pgdata, '-A', 'trust', '-N',
+ @{ $params{extra} });
+ }
+
TestLib::system_or_bail($ENV{PG_REGRESS}, '--config-auth', $pgdata,
@{ $params{auth_extra} });