From 0802065af07a62169802a6adc75edf763d1db385 Mon Sep 17 00:00:00 2001
From: Masahiko Sawada <sawada.mshk@gmail.com>
Date: Wed, 30 Oct 2019 16:16:18 +0900
Subject: [PATCH v2 2/5] Enable transparent data encryption

---
 src/backend/access/transam/xlog.c             |  46 +++
 src/backend/bootstrap/bootstrap.c             |  11 +-
 src/backend/postmaster/pgstat.c               |   9 +
 src/backend/postmaster/postmaster.c           |   6 +
 src/backend/storage/encryption/kmgr.c         | 366 ++++++++++++++++++
 src/backend/tcop/postgres.c                   |   8 +
 src/backend/utils/misc/guc.c                  |  37 +-
 src/backend/utils/misc/postgresql.conf.sample |   5 +
 src/bin/initdb/initdb.c                       |  77 +++-
 src/bin/pg_checksums/pg_checksums.c           |   1 +
 src/bin/pg_controldata/pg_controldata.c       |  19 +
 src/include/access/xlog.h                     |   4 +
 src/include/catalog/pg_control.h              |  10 +
 src/include/pgstat.h                          |   3 +
 src/include/storage/kmgr.h                    |  67 ++++
 src/include/utils/guc_tables.h                |   1 +
 16 files changed, 665 insertions(+), 5 deletions(-)
 create mode 100644 src/backend/storage/encryption/kmgr.c
 create mode 100644 src/include/storage/kmgr.h

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 2e3cc51006..fe7b253df4 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -55,8 +55,10 @@
 #include "replication/walreceiver.h"
 #include "replication/walsender.h"
 #include "storage/bufmgr.h"
+#include "storage/encryption.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
+#include "storage/kmgr.h"
 #include "storage/large_object.h"
 #include "storage/latch.h"
 #include "storage/pmsignal.h"
@@ -77,6 +79,7 @@
 #include "pg_trace.h"
 
 extern uint32 bootstrap_data_checksum_version;
+extern uint32 bootstrap_data_encryption_cipher;
 
 /* Unsupported old recovery command file names (relative to $PGDATA) */
 #define RECOVERY_COMMAND_FILE	"recovery.conf"
@@ -4783,6 +4786,10 @@ ReadControlFile(void)
 	/* Make the initdb settings visible as GUC variables, too */
 	SetConfigOption("data_checksums", DataChecksumsEnabled() ? "yes" : "no",
 					PGC_INTERNAL, PGC_S_OVERRIDE);
+
+	SetConfigOption("data_encryption_cipher",
+					EncryptionCipherString(GetDataEncryptionCipher()),
+					PGC_INTERNAL, PGC_S_OVERRIDE);
 }
 
 /*
@@ -4815,6 +4822,20 @@ GetMockAuthenticationNonce(void)
 	return ControlFile->mock_authentication_nonce;
 }
 
+WrappedEncKeyWithHmac *
+GetTDERelationEncryptionKey(void)
+{
+	Assert(ControlFile != NULL);
+	return &(ControlFile->tde_rdek);
+}
+
+WrappedEncKeyWithHmac *
+GetTDEWALEncryptionKey(void)
+{
+	Assert(ControlFile != NULL);
+	return &(ControlFile->tde_wdek);
+}
+
 /*
  * Are checksums enabled for data pages?
  */
@@ -4825,6 +4846,13 @@ DataChecksumsEnabled(void)
 	return (ControlFile->data_checksum_version > 0);
 }
 
+int
+GetDataEncryptionCipher(void)
+{
+	Assert(ControlFile != NULL);
+	return ControlFile->data_encryption_cipher;
+}
+
 /*
  * Returns a fake LSN for unlogged relations.
  *
@@ -5091,6 +5119,7 @@ BootStrapXLOG(void)
 	XLogPageHeader page;
 	XLogLongPageHeader longpage;
 	XLogRecord *record;
+	KmgrBootstrapInfo *kmgrinfo;
 	char	   *recptr;
 	bool		use_existent;
 	uint64		sysidentifier;
@@ -5168,6 +5197,12 @@ BootStrapXLOG(void)
 	SetMultiXactIdLimit(checkPoint.oldestMulti, checkPoint.oldestMultiDB, true);
 	SetCommitTsLimit(InvalidTransactionId, InvalidTransactionId);
 
+	/*
+	 * Bootstrap key management module beforehand in order to encrypt the first
+	 * xlog record.
+	 */
+	kmgrinfo = BootStrapKmgr(bootstrap_data_encryption_cipher);
+
 	/* Set up the XLOG page header */
 	page->xlp_magic = XLOG_PAGE_MAGIC;
 	page->xlp_info = XLP_LONG_HEADER;
@@ -5204,6 +5239,11 @@ BootStrapXLOG(void)
 	use_existent = false;
 	openLogFile = XLogFileInit(1, &use_existent, false);
 
+	/* Encrypt the first xlog record if necessary */
+	if (bootstrap_data_encryption_cipher > TDE_ENCRYPTION_OFF)
+		page = (XLogPageHeader) EncryptXLog((char *) page,
+											recptr - (char *) page, 1, 0);
+
 	/* Write the first page with the initial record */
 	errno = 0;
 	pgstat_report_wait_start(WAIT_EVENT_WAL_BOOTSTRAP_WRITE);
@@ -5243,6 +5283,11 @@ BootStrapXLOG(void)
 	ControlFile->checkPoint = checkPoint.redo;
 	ControlFile->checkPointCopy = checkPoint;
 	ControlFile->unloggedLSN = FirstNormalUnloggedLSN;
+	if (kmgrinfo)
+	{
+		memcpy(&(ControlFile->tde_rdek), &(kmgrinfo->relEncKey), sizeof(WrappedEncKeyWithHmac));
+		memcpy(&(ControlFile->tde_wdek), &(kmgrinfo->walEncKey), sizeof(WrappedEncKeyWithHmac));
+	}
 
 	/* Set important parameter values for use when replaying WAL */
 	ControlFile->MaxConnections = MaxConnections;
@@ -5254,6 +5299,7 @@ BootStrapXLOG(void)
 	ControlFile->wal_log_hints = wal_log_hints;
 	ControlFile->track_commit_timestamp = track_commit_timestamp;
 	ControlFile->data_checksum_version = bootstrap_data_checksum_version;
+	ControlFile->data_encryption_cipher = bootstrap_data_encryption_cipher;
 
 	/* some additional ControlFile fields are set in WriteControlFile() */
 
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index 9238fbe98d..155a4a9752 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -40,6 +40,8 @@
 #include "storage/bufmgr.h"
 #include "storage/bufpage.h"
 #include "storage/condition_variable.h"
+#include "storage/encryption.h"
+#include "storage/kmgr.h"
 #include "storage/ipc.h"
 #include "storage/proc.h"
 #include "tcop/tcopprot.h"
@@ -52,6 +54,9 @@
 
 uint32		bootstrap_data_checksum_version = 0;	/* No checksum */
 
+/* No encryption */
+uint32		bootstrap_data_encryption_cipher = TDE_ENCRYPTION_OFF;
+
 
 #define ALLOC(t, c) \
 	((t *) MemoryContextAllocZero(TopMemoryContext, (unsigned)(c) * sizeof(t)))
@@ -226,7 +231,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:e:Fkr:x:X:-:")) != -1)
 	{
 		switch (flag)
 		{
@@ -249,6 +254,10 @@ AuxiliaryProcessMain(int argc, char *argv[])
 					pfree(debugstr);
 				}
 				break;
+
+			case 'e':
+				bootstrap_data_encryption_cipher = EncryptionCipherValue(optarg);
+				break;
 			case 'F':
 				SetConfigOption("fsync", "false", PGC_POSTMASTER, PGC_S_ARGV);
 				break;
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index 011076c3e3..1d1ee1edbb 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -3956,6 +3956,15 @@ pgstat_get_wait_io(WaitEventIO w)
 		case WAIT_EVENT_DSM_FILL_ZERO_WRITE:
 			event_name = "DSMFillZeroWrite";
 			break;
+		case WAIT_EVENT_KMGR_FILE_READ:
+			event_name = "KmgrFileRead";
+			break;
+		case WAIT_EVENT_KMGR_FILE_SYNC:
+			event_name = "KmgrFileSync";
+			break;
+		case WAIT_EVENT_KMGR_FILE_WRITE:
+			event_name = "KmgrFileWrite";
+			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
index 5f30359165..46a1878c1f 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -119,6 +119,7 @@
 #include "replication/walsender.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
+#include "storage/kmgr.h"
 #include "storage/pg_shmem.h"
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
@@ -1335,6 +1336,11 @@ PostmasterMain(int argc, char *argv[])
 	 */
 	autovac_init();
 
+	/*
+	 * Initialize cluster encryption key manager.
+	 */
+	InitializeKmgr();
+
 	/*
 	 * Load configuration files for client authentication.
 	 */
diff --git a/src/backend/storage/encryption/kmgr.c b/src/backend/storage/encryption/kmgr.c
new file mode 100644
index 0000000000..a9638c63c1
--- /dev/null
+++ b/src/backend/storage/encryption/kmgr.c
@@ -0,0 +1,366 @@
+/*-------------------------------------------------------------------------
+ *
+ * kmgr.c
+ *	 Encryption key management module.
+ *
+ * Copyright (c) 2019, 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 "storage/encryption.h"
+#include "storage/fd.h"
+#include "storage/kmgr.h"
+#include "storage/shmem.h"
+#include "utils/builtins.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+#define KMGR_PROMPT_MSG "Enter database encryption pass phrase:"
+
+/*
+ * Key encryption key. This variable is set during verification
+ * of user given passphrase. After verified, the plain key data
+ * is set to this variable.
+ */
+static keydata_t keyEncKey[TDE_KEK_SIZE];
+
+/*
+ * Relation encryption key and WAL encryption key.  Similar to
+ * key encryption key, these variables store the plain key data.
+ */
+static keydata_t relEncKey[TDE_MAX_DEK_SIZE];
+static keydata_t walEncKey[TDE_MAX_DEK_SIZE];
+
+/* GUC variable */
+char *cluster_passphrase_command = NULL;
+
+static int run_cluster_passphrase_command(char *buf, int size);
+static void get_kek_and_hmackey_from_passphrase(char *passphrase,
+												Size passlen,
+												keydata_t kek[TDE_KEK_SIZE],
+												keydata_t hmackey[TDE_HMAC_KEY_SIZE]);
+static bool verify_passphrase(char *passphrase, int passlen,
+							  WrappedEncKeyWithHmac *rdek,
+							  WrappedEncKeyWithHmac *wdek);
+
+/*
+ * This func must be called ONCE on system install. we retrive KEK,
+ * generate RDEK and WDEK etc.
+ */
+KmgrBootstrapInfo *
+BootStrapKmgr(int bootstrap_data_encryption_cipher)
+{
+	KmgrBootstrapInfo *kmgrinfo;
+	char passphrase[TDE_MAX_PASSPHRASE_LEN];
+	keydata_t hmackey[TDE_HMAC_KEY_SIZE];
+	keydata_t *rdek_enc;
+	keydata_t *wdek_enc;
+	keydata_t *rdek_hmac;
+	keydata_t *wdek_hmac;
+	int	wrapped_keysize;
+	int	len;
+	int size;
+
+	if (bootstrap_data_encryption_cipher == TDE_ENCRYPTION_OFF)
+		return NULL;
+
+#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
+
+	kmgrinfo = palloc0(sizeof(KmgrBootstrapInfo));
+	rdek_enc = kmgrinfo->relEncKey.key;
+	rdek_hmac = kmgrinfo->relEncKey.hmac;
+	wdek_enc = kmgrinfo->walEncKey.key;
+	wdek_hmac = kmgrinfo->walEncKey.hmac;
+
+	/*
+	 * Set data encryption cipher so that subsequent bootstrapping process
+	 * can proceed.
+	 */
+	SetConfigOption("data_encryption_cipher",
+					EncryptionCipherString(bootstrap_data_encryption_cipher),
+					PGC_INTERNAL, PGC_S_OVERRIDE);
+
+	/* Get key encryption key fro command */
+	len = run_cluster_passphrase_command(passphrase, TDE_MAX_PASSPHRASE_LEN);
+
+	/* Get key encryption key and HMAC key from passphrase */
+	get_kek_and_hmackey_from_passphrase(passphrase, len, keyEncKey,
+										hmackey);
+
+	/*
+	 * Generate relation encryption key and WAL encryption key.
+	 * The generated two keys must be stored in relEncKey and
+	 * walEncKey that can be used by other modules since even
+	 * during bootstrapping we need to encrypt both systemcatalogs
+	 * and WAL.
+	 */
+	if (!pg_strong_random(relEncKey, EncryptionKeySize))
+		ereport(ERROR,
+				(errmsg("failed to generate relation encryption key")));
+	if (!pg_strong_random(walEncKey, EncryptionKeySize))
+		ereport(ERROR,
+				(errmsg("failed to generate WAL encryption key")));
+
+	/* Wrap both keys by KEK */
+	wrapped_keysize = EncryptionKeySize + TDE_DEK_WRAP_VALUE_SIZE;
+	pg_wrap_key(keyEncKey, TDE_KEK_SIZE,
+				relEncKey, EncryptionKeySize,
+				rdek_enc, &size);
+	if (size != wrapped_keysize)
+		elog(ERROR, "wrapped relation encryption key size is invalid, got %d expected %d",
+			 size, wrapped_keysize);
+
+	pg_wrap_key(keyEncKey, TDE_KEK_SIZE,
+				walEncKey, EncryptionKeySize,
+				wdek_enc, &size);
+	if (size != wrapped_keysize)
+		elog(ERROR, "wrapped WAL encryption key size is invalid, got %d expected %d",
+			 size, wrapped_keysize);
+
+	/* Compute both HMAC */
+	pg_compute_hmac(hmackey, TDE_HMAC_KEY_SIZE,
+					rdek_enc, wrapped_keysize,
+					rdek_hmac);
+	pg_compute_hmac(hmackey, TDE_HMAC_KEY_SIZE,
+					wdek_enc, wrapped_keysize,
+					wdek_hmac);
+
+	/* return keys and HMACs generated during bootstrap */
+	return kmgrinfo;
+}
+
+/*
+ * 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.
+ */
+static int
+run_cluster_passphrase_command(char *buf, int size)
+{
+	StringInfoData command;
+	char	   *p;
+	FILE	   *fh;
+	int			pclose_rc;
+	size_t		len = 0;
+
+	Assert(size > 0);
+	buf[0] = '\0';
+
+	initStringInfo(&command);
+
+	for (p = cluster_passphrase_command; *p; p++)
+	{
+		if (p[0] == '%')
+		{
+			switch (p[1])
+			{
+				case 'p':
+					appendStringInfoString(&command, KMGR_PROMPT_MSG);
+					p++;
+					break;
+				case '%':
+					appendStringInfoChar(&command, '%');
+					p++;
+					break;
+				default:
+					appendStringInfoChar(&command, p[0]);
+			}
+		}
+		else
+			appendStringInfoChar(&command, p[0]);
+	}
+
+	fh = OpenPipeStream(command.data, "r");
+	if (fh == NULL)
+		ereport(ERROR,
+				(errcode_for_file_access(),
+				 errmsg("could not execute command \"%s\": %m",
+						command.data)));
+
+	if (!fgets(buf, size, fh))
+	{
+		if (ferror(fh))
+		{
+			pfree(command.data);
+			ereport(ERROR,
+					(errcode_for_file_access(),
+					 errmsg("could not read from command \"%s\": %m",
+							command.data)));
+		}
+	}
+
+	pclose_rc = ClosePipeStream(fh);
+	if (pclose_rc == -1)
+	{
+		pfree(command.data);
+		ereport(ERROR,
+				(errcode_for_file_access(),
+				 errmsg("could not close pipe to external command: %m")));
+	}
+	else if (pclose_rc != 0)
+	{
+		pfree(command.data);
+		ereport(ERROR,
+				(errcode_for_file_access(),
+				 errmsg("command \"%s\" failed",
+						command.data),
+				 errdetail_internal("%s", wait_result_to_str(pclose_rc))));
+	}
+
+	/* strip trailing newline */
+	len = strlen(buf);
+	if (len > 0 && buf[len - 1] == '\n')
+		buf[--len] = '\0';
+
+	pfree(command.data);
+
+	return len;
+}
+
+/*
+ * Get encryption key passphrase and verify it, then get the un-encrypted
+ * RDEK and WDEK. This function is called by postmaster at startup time.
+ */
+void
+InitializeKmgr(void)
+{
+	WrappedEncKeyWithHmac *wrapped_rdek;
+	WrappedEncKeyWithHmac *wrapped_wdek;
+	char passphrase[TDE_MAX_PASSPHRASE_LEN];
+	int		len;
+	int		wrapped_keysize;
+	int		unwrapped_size;
+
+	if (!DataEncryptionEnabled())
+		return;
+
+	/* Get cluster passphrase */
+	len = run_cluster_passphrase_command(passphrase, TDE_MAX_PASSPHRASE_LEN);
+
+	/* Get two wrapped keys stored in control file */
+	wrapped_rdek = GetTDERelationEncryptionKey();
+	wrapped_wdek = GetTDEWALEncryptionKey();
+
+	wrapped_keysize = EncryptionKeySize + TDE_DEK_WRAP_VALUE_SIZE;
+
+	/* Verify the correctness of given passphrase */
+	if (!verify_passphrase(passphrase, len, wrapped_rdek, wrapped_wdek))
+		ereport(ERROR,
+				(errmsg("cluster passphrase does not match expected passphrase")));
+
+	/* The passphrase is correct, unwrap both RDEK and WDEK */
+	pg_unwrap_key(keyEncKey, TDE_KEK_SIZE,
+				  wrapped_rdek->key, wrapped_keysize,
+				  relEncKey, &unwrapped_size);
+	if (unwrapped_size != EncryptionKeySize)
+		elog(ERROR, "unwrapped relation encryption key size is invalid, got %d expected %d",
+			 unwrapped_size, EncryptionKeySize);
+
+	pg_unwrap_key(keyEncKey, TDE_KEK_SIZE,
+				  wrapped_wdek->key, wrapped_keysize,
+				  walEncKey, &unwrapped_size);
+	if (unwrapped_size != EncryptionKeySize)
+		elog(ERROR, "unwrapped WAL encryptoin key size is invalid, got %d expected %d",
+			 unwrapped_size, EncryptionKeySize);
+}
+
+ /*
+  * Hash the given passphrase and extract it into KEK and HMAC
+  * key.
+  */
+static void
+get_kek_and_hmackey_from_passphrase(char *passphrase, Size passlen,
+									keydata_t kek_out[TDE_KEK_SIZE],
+									keydata_t hmackey_out[TDE_HMAC_KEY_SIZE])
+{
+	keydata_t enckey_and_hmackey[PG_SHA512_DIGEST_LENGTH];
+	pg_sha512_ctx ctx;
+
+	pg_sha512_init(&ctx);
+	pg_sha512_update(&ctx, (const uint8 *) passphrase, passlen);
+	pg_sha512_final(&ctx, enckey_and_hmackey);
+
+	/*
+	 * SHA-512 results 64 bytes. We extract it into two keys for
+	 * each 32 bytes: one for key encryption and another one for
+	 * HMAC.
+	 */
+	memcpy(kek_out, enckey_and_hmackey, TDE_KEK_SIZE);
+	memcpy(hmackey_out, enckey_and_hmackey + TDE_KEK_SIZE, TDE_HMAC_KEY_SIZE);
+}
+
+/*
+ * Verify the correctness of the given passphrase. We compute HMACs of the
+ * wrapped keys (RDEK and WDEK) using the HMAC key retrived from the user
+ * provided passphrase. And then we compare it with the HMAC stored alongside
+ * the controlfile. Return true if both HMACs are matched, meaning the given
+ * passphrase is correct. Otherwise return false.
+ */
+static bool
+verify_passphrase(char *passphrase, int passlen,
+				  WrappedEncKeyWithHmac *rdek, WrappedEncKeyWithHmac *wdek)
+{
+	keydata_t user_kek[TDE_KEK_SIZE];
+	keydata_t user_hmackey[TDE_HMAC_KEY_SIZE];
+	keydata_t result_hmac[TDE_HMAC_SIZE];
+	int	wrapped_keysize = EncryptionKeySize + TDE_DEK_WRAP_VALUE_SIZE;
+
+	get_kek_and_hmackey_from_passphrase(passphrase, passlen,
+										user_kek, user_hmackey);
+
+	/* Verify both HMACs of RDEK and WDEK */
+	pg_compute_hmac(user_hmackey, TDE_HMAC_KEY_SIZE,
+					rdek->key, wrapped_keysize,
+					result_hmac);
+	if (memcmp(result_hmac, rdek->hmac, TDE_HMAC_SIZE) != 0)
+		return false;
+
+	pg_compute_hmac(user_hmackey, TDE_HMAC_KEY_SIZE,
+					wdek->key, wrapped_keysize,
+					result_hmac);
+	if (memcmp(result_hmac, wdek->hmac, TDE_HMAC_SIZE) != 0)
+		return false;
+
+	/* The passphrase is verified. Save the key encryption key */
+	memcpy(keyEncKey, user_kek, TDE_KEK_SIZE);
+
+	return true;
+}
+
+/* Return plain relation encryption key */
+const char *
+KmgrGetRelationEncryptionKey(void)
+{
+	Assert(DataEncryptionEnabled());
+	return (const char *) relEncKey;
+}
+
+/* Return plain WAL encryption key */
+const char *
+KmgrGetWALEncryptionKey(void)
+{
+	Assert(DataEncryptionEnabled());
+	return (const char *) walEncKey;
+}
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 4bec40aa28..b493641b58 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -63,6 +63,7 @@
 #include "replication/walsender.h"
 #include "rewrite/rewriteHandler.h"
 #include "storage/bufmgr.h"
+#include "storage/kmgr.h"
 #include "storage/ipc.h"
 #include "storage/proc.h"
 #include "storage/procsignal.h"
@@ -3863,6 +3864,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 31a5ef0474..a2756c2c3c 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -71,7 +71,10 @@
 #include "replication/walreceiver.h"
 #include "replication/walsender.h"
 #include "storage/bufmgr.h"
+#include "storage/encryption.h"
+#include "storage/enc_common.h"
 #include "storage/dsm_impl.h"
+#include "storage/kmgr.h"
 #include "storage/standby.h"
 #include "storage/fd.h"
 #include "storage/large_object.h"
@@ -457,6 +460,13 @@ const struct config_enum_entry ssl_protocol_versions_info[] = {
 	{NULL, 0, false}
 };
 
+const struct config_enum_entry data_encryption_cipher_options[] = {
+	{"off",		TDE_ENCRYPTION_OFF, false},
+	{"aes-128", TDE_ENCRYPTION_AES_128, false},
+	{"aes-256", TDE_ENCRYPTION_AES_256, false},
+	{NULL, 0, false}
+};
+
 static struct config_enum_entry shared_memory_options[] = {
 #ifndef WIN32
 	{"sysv", SHMEM_TYPE_SYSV, false},
@@ -704,6 +714,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 */
@@ -4108,7 +4120,7 @@ static struct config_string ConfigureNamesString[] =
 		{"ssl_ciphers", PGC_SIGHUP, CONN_AUTH_SSL,
 			gettext_noop("Sets the list of allowed SSL ciphers."),
 			NULL,
-			GUC_SUPERUSER_ONLY
+			GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE
 		},
 		&SSLCipherSuites,
 #ifdef USE_OPENSSL
@@ -4155,6 +4167,16 @@ static struct config_string ConfigureNamesString[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"cluster_passphrase_command", PGC_POSTMASTER, 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."),
@@ -4537,6 +4559,19 @@ static struct config_enum ConfigureNamesEnum[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"data_encryption_cipher", PGC_INTERNAL, PRESET_OPTIONS,
+		 gettext_noop("Specify encryption algorithms to use."),
+		 NULL,
+		 GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE,
+		 GUC_SUPERUSER_ONLY
+		},
+		&data_encryption_cipher,
+		TDE_ENCRYPTION_OFF,
+		data_encryption_cipher_options,
+		NULL, assign_data_encryption_cipher, NULL
+	},
+
 	/* End-of-list marker */
 	{
 		{NULL, 0, 0, NULL, NULL}, NULL, 0, NULL, NULL, NULL, NULL
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 0fc23e3a61..0c50b4f8cf 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -600,6 +600,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 88a261d9bd..75462b8010 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -114,6 +114,13 @@ static const char *const auth_methods_local[] = {
 	NULL
 };
 
+static const char *const encryption_ciphers[] = {
+	"none",
+	"aes-128",
+	"aes-256",
+	NULL
+};
+
 /*
  * these values are passed in by makefile defines
  */
@@ -145,6 +152,8 @@ 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 *enc_cipher = NULL;
+static char *cluster_passphrase = NULL;
 
 
 /* internal vars */
@@ -1208,6 +1217,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
@@ -1421,14 +1437,15 @@ 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 %s",
 			 backend_exec,
 			 wal_segment_size_mb * (1024 * 1024),
 			 data_checksums ? "-k" : "",
+			 enc_cipher ? "-e" : "",
+			 enc_cipher ? enc_cipher : "",
 			 boot_options,
 			 debug ? "-d 5" : "");
 
-
 	PG_CMD_OPEN;
 
 	for (line = bki_lines; *line != NULL; line++)
@@ -2351,6 +2368,9 @@ 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(_("  -e  --enc-cipher=MODE     set encryption cipher for data encryption\n"));
+	printf(_("  -c  --cluster-passphrase-command=COMMAND\n"
+			 "                            set command to obtain passphrase for data encryption key\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"));
@@ -2416,6 +2436,42 @@ check_need_password(const char *authmethodlocal, const char *authmethodhost)
 	}
 }
 
+static void
+check_encryption_cipher(const char *cipher, const char *passphrase,
+						const char *const *valid_ciphers)
+{
+	const char *const *p;
+
+	if (!cipher && !passphrase)
+		return;
+
+#ifndef USE_OPENSSL
+	pg_log_error("cluster encryption is not supported because OpenSSL is not supported by this build");
+	exit(1);
+#endif
+
+	/* Check both options must be specified at the same time */
+	if (cipher && !passphrase)
+	{
+		pg_log_error("encryption passphrase command must be specified when encryption cipher is specified");
+		exit(1);
+	}
+
+	if (!cipher && passphrase)
+	{
+		pg_log_error("encryption cipher must be specified when encryption passphrase command is specified");
+		exit(1);
+	}
+
+	for (p = valid_ciphers; *p; p++)
+	{
+		if (strcmp(cipher, *p) == 0)
+			return;
+	}
+
+	pg_log_error("invalid encryption cipher \"%s\"", cipher);
+	exit(1);
+}
 
 void
 setup_pgdata(void)
@@ -3029,6 +3085,8 @@ main(int argc, char *argv[])
 		{"wal-segsize", required_argument, NULL, 12},
 		{"data-checksums", no_argument, NULL, 'k'},
 		{"allow-group-access", no_argument, NULL, 'g'},
+		{"enc-cipher", required_argument, NULL, 'e'},
+		{"cluster-passphrase-command", required_argument, NULL, 'c'},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -3070,7 +3128,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:e:kL:nNU:WA:sST:X:g", long_options, &option_index)) != -1)
 	{
 		switch (c)
 		{
@@ -3152,6 +3210,12 @@ main(int argc, char *argv[])
 			case 9:
 				pwfilename = pg_strdup(optarg);
 				break;
+			case 'e':
+				enc_cipher = pg_strdup(optarg);
+				break;
+			case 'c':
+				cluster_passphrase = pg_strdup(optarg);
+				break;
 			case 's':
 				show_setting = true;
 				break;
@@ -3230,6 +3294,8 @@ main(int argc, char *argv[])
 
 	check_need_password(authmethodlocal, authmethodhost);
 
+	check_encryption_cipher(enc_cipher, cluster_passphrase, encryption_ciphers);
+
 	/* set wal segment size */
 	if (str_wal_segment_size_mb == NULL)
 		wal_segment_size_mb = (DEFAULT_XLOG_SEG_SIZE) / (1024 * 1024);
@@ -3289,6 +3355,11 @@ main(int argc, char *argv[])
 	else
 		printf(_("Data page checksums are disabled.\n"));
 
+	if (enc_cipher)
+		printf(_("Data encryption using %s is enabled.\n"), enc_cipher);
+	else
+		printf(_("Data encryption is disabled.\n"));
+
 	if (pwprompt || pwfilename)
 		get_su_pwd();
 
diff --git a/src/bin/pg_checksums/pg_checksums.c b/src/bin/pg_checksums/pg_checksums.c
index 971ae73f54..792a9e7565 100644
--- a/src/bin/pg_checksums/pg_checksums.c
+++ b/src/bin/pg_checksums/pg_checksums.c
@@ -100,6 +100,7 @@ static const char *const skip[] = {
 	"pg_control",
 	"pg_filenode.map",
 	"pg_internal.init",
+	"pg_kmgr",
 	"PG_VERSION",
 #ifdef EXEC_BACKEND
 	"config_exec_params",
diff --git a/src/bin/pg_controldata/pg_controldata.c b/src/bin/pg_controldata/pg_controldata.c
index b14767f8b6..a2f595d097 100644
--- a/src/bin/pg_controldata/pg_controldata.c
+++ b/src/bin/pg_controldata/pg_controldata.c
@@ -26,6 +26,8 @@
 #include "catalog/pg_control.h"
 #include "common/controldata_utils.h"
 #include "common/logging.h"
+#include "storage/encryption.h"
+#include "pg_getopt.h"
 #include "getopt_long.h"
 #include "pg_getopt.h"
 
@@ -83,6 +85,21 @@ wal_level_str(WalLevel wal_level)
 	return _("unrecognized wal_level");
 }
 
+static const char *
+encryption_cipher_str(int val)
+{
+	switch (val)
+	{
+		case TDE_ENCRYPTION_OFF:
+			return "off";
+		case TDE_ENCRYPTION_AES_128:
+			return "aes-128";
+		case TDE_ENCRYPTION_AES_256:
+			return "aes-256";
+	}
+
+	return _("unrecognized encryption cipher");
+}
 
 int
 main(int argc, char *argv[])
@@ -335,5 +352,7 @@ main(int argc, char *argv[])
 		   ControlFile->data_checksum_version);
 	printf(_("Mock authentication nonce:            %s\n"),
 		   mock_auth_nonce_str);
+	printf(_("Data encryption cipher:               %s\n"),
+		   encryption_cipher_str(ControlFile->data_encryption_cipher));
 	return 0;
 }
diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h
index d519252aad..b3ad28cd3f 100644
--- a/src/include/access/xlog.h
+++ b/src/include/access/xlog.h
@@ -19,6 +19,7 @@
 #include "lib/stringinfo.h"
 #include "nodes/pg_list.h"
 #include "storage/fd.h"
+#include "storage/kmgr.h"
 
 
 /* Sync methods */
@@ -292,8 +293,11 @@ extern char *XLogFileNameP(TimeLineID tli, XLogSegNo segno);
 
 extern void UpdateControlFile(void);
 extern uint64 GetSystemIdentifier(void);
+extern WrappedEncKeyWithHmac *GetTDERelationEncryptionKey(void);
+extern WrappedEncKeyWithHmac *GetTDEWALEncryptionKey(void);
 extern char *GetMockAuthenticationNonce(void);
 extern bool DataChecksumsEnabled(void);
+extern int	GetDataEncryptionCipher(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 ff98d9e91a..5412b148cb 100644
--- a/src/include/catalog/pg_control.h
+++ b/src/include/catalog/pg_control.h
@@ -19,6 +19,7 @@
 #include "access/xlogdefs.h"
 #include "pgtime.h"				/* for pg_time_t */
 #include "port/pg_crc32c.h"
+#include "storage/kmgr.h"
 
 
 /* Version identifier for this pg_control format */
@@ -221,6 +222,9 @@ typedef struct ControlFileData
 	/* Are data pages protected by checksums? Zero if no checksum version */
 	uint32		data_checksum_version;
 
+	/* Are data pages and WAL encrypted? Zero if encryption is disabled */
+	uint32		data_encryption_cipher;
+
 	/*
 	 * Random nonce, used in authentication requests that need to proceed
 	 * based on values that are cluster-unique, like a SASL exchange that
@@ -228,6 +232,12 @@ typedef struct ControlFileData
 	 */
 	char		mock_authentication_nonce[MOCK_AUTH_NONCE_LEN];
 
+	/*
+	 * Key information for data encryption.
+	 */
+	WrappedEncKeyWithHmac tde_rdek;
+	WrappedEncKeyWithHmac tde_wdek;
+
 	/* CRC of all above ... MUST BE LAST! */
 	pg_crc32c	crc;
 } ControlFileData;
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index fe076d823d..fff3c27995 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -895,6 +895,9 @@ typedef enum
 	WAIT_EVENT_DATA_FILE_TRUNCATE,
 	WAIT_EVENT_DATA_FILE_WRITE,
 	WAIT_EVENT_DSM_FILL_ZERO_WRITE,
+	WAIT_EVENT_KMGR_FILE_READ,
+	WAIT_EVENT_KMGR_FILE_SYNC,
+	WAIT_EVENT_KMGR_FILE_WRITE,
 	WAIT_EVENT_LOCK_FILE_ADDTODATADIR_READ,
 	WAIT_EVENT_LOCK_FILE_ADDTODATADIR_SYNC,
 	WAIT_EVENT_LOCK_FILE_ADDTODATADIR_WRITE,
diff --git a/src/include/storage/kmgr.h b/src/include/storage/kmgr.h
new file mode 100644
index 0000000000..5a484f8546
--- /dev/null
+++ b/src/include/storage/kmgr.h
@@ -0,0 +1,67 @@
+/*-------------------------------------------------------------------------
+ *
+ * kmgr.h
+ *	  Key management module for transparent data encryption
+ *
+ * Portions Copyright (c) 2019, PostgreSQL Global Development Group
+ *
+ * src/include/storage/kmgr.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef KMGR_H
+#define KMGR_H
+
+#include "storage/relfilenode.h"
+#include "storage/bufpage.h"
+
+/* Size of HMAC key is the same as the length of hash, we use SHA-256 */
+#define TDE_HMAC_KEY_SIZE		32
+
+/* SHA-256 results 256 bits HMAC */
+#define TDE_HMAC_SIZE			32
+
+/* Size of key encryption key (KEK), which is always AES-256 key */
+#define TDE_KEK_SIZE			32
+
+/*
+ * Max size of data encryption key. We support AES-128 and AES-256, the
+ * maximum key size is 32.
+ */
+#define TDE_MAX_DEK_SIZE			32
+
+/* Key wrapping appends the initial 8 bytes value */
+#define TDE_DEK_WRAP_VALUE_SIZE		8
+
+/* Wrapped key size is n+1 value */
+#define TDE_MAX_WRAPPED_DEK_SIZE	(TDE_MAX_DEK_SIZE + TDE_DEK_WRAP_VALUE_SIZE)
+
+#define TDE_MAX_PASSPHRASE_LEN		1024
+
+typedef unsigned char keydata_t;
+
+/*
+ * Struct for keys that needs to be verified using its HMAC.
+ */
+typedef struct WrappedEncKeyWithHmac
+{
+	keydata_t key[TDE_MAX_WRAPPED_DEK_SIZE];
+	keydata_t hmac[TDE_HMAC_SIZE];
+} WrappedEncKeyWithHmac;
+
+/* Struct for bootstrap information passing to the bootstrap routine */
+typedef struct KmgrBootstrapInfo
+{
+	WrappedEncKeyWithHmac relEncKey;
+	WrappedEncKeyWithHmac walEncKey;
+} KmgrBootstrapInfo;
+
+/* GUC variable */
+extern char *cluster_passphrase_command;
+
+extern KmgrBootstrapInfo *BootStrapKmgr(int bootstrap_data_encryption_cipher);
+extern void InitializeKmgr(void);
+extern const char *KmgrGetRelationEncryptionKey(void);
+extern const char *KmgrGetWALEncryptionKey(void);
+
+#endif /* KMGR_H */
diff --git a/src/include/utils/guc_tables.h b/src/include/utils/guc_tables.h
index d68976fafa..c1ae129f9d 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,
-- 
2.23.0

