From a6cdae26675a74d102dfe38a3a8f77430ea5480d Mon Sep 17 00:00:00 2001
From: Antonin Houska <ah@cybertec.at>
Date: Fri, 5 Jul 2019 16:24:01 +0200
Subject: [PATCH 01/17] Make postgres aware of the encryption

This patch makes postgres aware of the encryption (i.e. sets up encryption key
and initializes the encryption library) but does not actually encrypt any
data.

If initdb is passed the "encryption key command" (the -K option), it executes
it and reads the encryption key from the command's stdout:

initdb -D data -K "echo 71916b8b93063c280e4e0ee646395f450bda4f1bac85c57bc3a6afd168f3ab47"

-K option (without any argument) is then passed to bootstrap process just so
it knows that the new cluster should be encrypted. The first thing the
bootstrap process does in such a case is that it reads the encryption key from
stdin. The point is that a flag is eventually sets a control file that
indidicates that encryption is enabled for the cluster. A sample string is
also encrypted and stored in the control file. It'll be used on each startup
to verify that user passed the correct key.

pg_controldata utility shows two new fields now: "Data encryption" and "Data
encryption fingerprint".

Once a standalone backend it started, it reads the control file and recognizes
the cluster is encrypted. Therefore it expects the encryption key to be the
first user's input:

postgres --single  -D data postgres
key>71916b8b93063c280e4e0ee646395f450bda4f1bac85c57bc3a6afd168f3ab47

Once the correct key is passed, the session can continue as if there was no
encryption. The only difference user can see is that "data_encryption"
variable is set:

PostgreSQL stand-alone backend 13devel
backend> SHOW data_encryption;
         1: data_encryption     (typeid = 25, len = -1, typmod = -1, byval = f)
        ----
         1: data_encryption = "on"      (typeid = 25, len = -1, typmod = -1, byval = f)
        ----
---
 src/backend/access/transam/xlog.c       |  72 ++++++++
 src/backend/bootstrap/bootstrap.c       |  48 ++++-
 src/backend/storage/file/Makefile       |   2 +-
 src/backend/storage/file/encryption.c   | 313 ++++++++++++++++++++++++++++++++
 src/backend/tcop/postgres.c             |  30 +++
 src/backend/utils/misc/guc.c            |  12 ++
 src/bin/initdb/.gitignore               |   1 +
 src/bin/initdb/Makefile                 |   7 +-
 src/bin/initdb/initdb.c                 |  74 +++++++-
 src/bin/pg_controldata/pg_controldata.c |   9 +
 src/common/string.c                     |  39 ++++
 src/fe_utils/Makefile                   |   3 +-
 src/fe_utils/encryption.c               |  90 +++++++++
 src/include/catalog/pg_control.h        |  11 ++
 src/include/common/string.h             |   1 +
 src/include/fe_utils/encryption.h       |  18 ++
 src/include/pg_config_manual.h          |   7 +
 src/include/storage/encryption.h        |  99 ++++++++++
 18 files changed, 829 insertions(+), 7 deletions(-)
 create mode 100644 src/backend/storage/file/encryption.c
 create mode 100644 src/fe_utils/encryption.c
 create mode 100644 src/include/fe_utils/encryption.h
 create mode 100644 src/include/storage/encryption.h

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 13e0d2366f..6b58e0ac76 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -55,6 +55,7 @@
 #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/large_object.h"
@@ -77,6 +78,7 @@
 #include "pg_trace.h"
 
 extern uint32 bootstrap_data_checksum_version;
+extern char *bootstrap_encryption_sample;
 
 /* Unsupported old recovery command file names (relative to $PGDATA) */
 #define RECOVERY_COMMAND_FILE	"recovery.conf"
@@ -4774,6 +4776,58 @@ ReadControlFile(void)
 		ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 						errmsg("\"max_wal_size\" must be at least twice \"wal_segment_size\"")));
 
+	/*
+	 * Initialize encryption, but not if the current backend has already done
+	 * that.
+	 */
+	if (ControlFile->data_cipher > PG_CIPHER_NONE && !data_encrypted)
+	{
+		/*
+		 * Set data_encryption for caller to know that he needs to retrieve
+		 * the key and initialize the encryption library.
+		 */
+		SetConfigOption("data_encryption", "true", PGC_INTERNAL,
+						PGC_S_OVERRIDE);
+
+		/*
+		 * Save the verification string. We'll perform the actual verification
+		 * as soon as the encryption setup is done.
+		 */
+		memcpy(encryption_verification,
+			   ControlFile->encryption_verification,
+			   ENCRYPTION_SAMPLE_SIZE);
+
+		/*
+		 * full_page_writes must be set because torn page write of an
+		 * encrypted page implies that decryption of the page will produce
+		 * garbage. This damage can affect even those parts of the page that
+		 * haven't been modified by any access method. And since no access
+		 * method modified those parts, there might be no XLOG records to
+		 * repair them during crash recovery. So full page image is the only
+		 * way to fix such a page.
+		 *
+		 * Do not enforce this setting in binary upgrade mode, since
+		 * pg_upgrade sets it to off for performance reasons. (If the upgrade
+		 * crashes, the new cluster must be created from scratch anyway.)
+		 *
+		 * XXX It would be nice to have guc.c check so that we don't have to
+		 * copy and paste the error message. However it's unclear how to
+		 * ensure that either ERROR is raised or nothing happens at all. It
+		 * seems that set_config_option() can change
+		 * config_generic.reset_source if the check succeeded, but that's too
+		 * invasive.
+		 */
+		if (!fullPageWrites && !IsBinaryUpgrade)
+			ereport(FATAL,
+					(errmsg("invalid value for parameter \"full_page_writes\": %d",
+							fullPageWrites),
+					 errdetail("Cannot disable parameter when the cluster is encrypted.")));
+	}
+
+	/*
+	 * This calculation relies on data_encryption (in particular the header
+	 * sizes do), so we could not do it earlier.
+	 */
 	UsableBytesInSegment =
 		(wal_segment_size / XLOG_BLCKSZ * UsableBytesInPage) -
 		(SizeOfXLogLongPHD - SizeOfXLogShortPHD);
@@ -5255,6 +5309,24 @@ BootStrapXLOG(void)
 	ControlFile->track_commit_timestamp = track_commit_timestamp;
 	ControlFile->data_checksum_version = bootstrap_data_checksum_version;
 
+	if (data_encrypted)
+	{
+		char	sample[ENCRYPTION_SAMPLE_SIZE];
+
+		ControlFile->data_cipher = PG_CIPHER_AES_BLOCK_CBC_256_STREAM_CTR_256;
+
+		sample_encryption(sample);
+
+		memcpy(ControlFile->encryption_verification, sample,
+			   ENCRYPTION_SAMPLE_SIZE);
+	}
+	else
+	{
+		ControlFile->data_cipher = PG_CIPHER_NONE;
+		memset(ControlFile->encryption_verification, 0,
+			   ENCRYPTION_SAMPLE_SIZE);
+	}
+
 	/* some additional ControlFile fields are set in WriteControlFile() */
 
 	WriteControlFile();
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index 43627ab8f4..406fff51a9 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -40,6 +40,7 @@
 #include "storage/bufmgr.h"
 #include "storage/bufpage.h"
 #include "storage/condition_variable.h"
+#include "storage/encryption.h"
 #include "storage/ipc.h"
 #include "storage/proc.h"
 #include "tcop/tcopprot.h"
@@ -58,6 +59,7 @@ uint32		bootstrap_data_checksum_version = 0;	/* No checksum */
 
 static void CheckerModeMain(void);
 static void BootstrapModeMain(void);
+static int bootstrap_getc(void);
 static void bootstrap_signals(void);
 static void ShutdownAuxiliaryProcess(int code, Datum arg);
 static Form_pg_attribute AllocateAttribute(void);
@@ -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:FkKr:x:X:-:")) != -1)
 	{
 		switch (flag)
 		{
@@ -252,6 +254,22 @@ AuxiliaryProcessMain(int argc, char *argv[])
 			case 'F':
 				SetConfigOption("fsync", "false", PGC_POSTMASTER, PGC_S_ARGV);
 				break;
+#ifdef	USE_ENCRYPTION
+			case 'K':
+				/*
+				 * When auxiliary process (bootstrap) starts, the control file
+				 * does not exist yet, so command line option needs to be used
+				 * to indicate that the encryption is enabled.
+				 *
+				 * Postmaster should not pass this option. Instead, it just
+				 * sets data_encrypted according to the control file and child
+				 * processes inherit that.
+				 */
+				Assert(!IsUnderPostmaster);
+				data_encrypted = true;
+
+				break;
+#endif							/* USE_ENCRYPTION */
 			case 'k':
 				bootstrap_data_checksum_version = PG_DATA_CHECKSUM_VERSION;
 				break;
@@ -373,6 +391,24 @@ AuxiliaryProcessMain(int argc, char *argv[])
 	if (!IsUnderPostmaster)
 		InitializeMaxBackends();
 
+	/*
+	 * If data_encryption is set because of command line option, do the setup
+	 * now. (If set by postmaster, postmaster should have performed the
+	 * setup.)
+	 *
+	 * This should only be useful for the bootstrap process. Anyone else
+	 * detects the encryption via ReadControlFile().
+	 */
+	if (data_encrypted && MyAuxProcType == BootstrapProcess)
+	{
+		Assert(!IsUnderPostmaster);
+
+		/* Read the key from stdin. */
+		read_encryption_key(bootstrap_getc);
+
+		setup_encryption();
+	}
+
 	BaseInit();
 
 	/*
@@ -551,6 +587,16 @@ BootstrapModeMain(void)
  */
 
 /*
+ * Read a single character from stdin. This is a callback for
+ * read_encryption_key().
+ */
+static int
+bootstrap_getc(void)
+{
+	return getc(stdin);
+}
+
+/*
  * Set up signal handling for a bootstrap process
  */
 static void
diff --git a/src/backend/storage/file/Makefile b/src/backend/storage/file/Makefile
index ca6a0e4f7d..9f277755fc 100644
--- a/src/backend/storage/file/Makefile
+++ b/src/backend/storage/file/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/storage/file
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = fd.o buffile.o copydir.o reinit.o sharedfileset.o
+OBJS = fd.o buffile.o copydir.o reinit.o sharedfileset.o encryption.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/storage/file/encryption.c b/src/backend/storage/file/encryption.c
new file mode 100644
index 0000000000..629138fae7
--- /dev/null
+++ b/src/backend/storage/file/encryption.c
@@ -0,0 +1,313 @@
+/*-------------------------------------------------------------------------
+ *
+ * encryption.c
+ *	  This code handles encryption and decryption of data.
+ *
+ * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * See src/backend/storage/file/README.encryption for explanation of the
+ * design.
+ *
+ * IDENTIFICATION
+ *	  src/backend/storage/file/encryption.c
+ *
+ * NOTES
+ *		This file is compiled as both front-end and backend code, so the
+ *		FRONTEND macro must be used to distinguish the case if we need to
+ *		report error or if server-defined variable / function seems useful.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+#include "access/xlog.h"
+#include "access/xlogdefs.h"
+#include "common/fe_memutils.h"
+#include "common/sha2.h"
+#include "common/string.h"
+#include "catalog/pg_control.h"
+#include "storage/bufpage.h"
+#include "storage/encryption.h"
+
+#ifndef FRONTEND
+#include "port.h"
+#include "storage/shmem.h"
+#include "storage/fd.h"
+#include "utils/memutils.h"
+#endif							/* FRONTEND */
+
+#ifdef USE_ENCRYPTION
+#include <openssl/conf.h>
+#include <openssl/evp.h>
+#include <openssl/err.h>
+
+EVP_CIPHER_CTX *ctx_encrypt,
+		   *ctx_decrypt,
+		   *ctx_encrypt_stream,
+		   *ctx_decrypt_stream;
+#endif							/* USE_ENCRYPTION */
+
+unsigned char encryption_key[ENCRYPTION_KEY_LENGTH];
+
+bool		data_encrypted = false;
+
+char encryption_verification[ENCRYPTION_SAMPLE_SIZE];
+
+bool	encryption_setup_done = false;
+
+#ifdef USE_ENCRYPTION
+static void init_encryption_context(EVP_CIPHER_CTX **ctx_p, bool stream);
+static void evp_error(void);
+#endif							/* USE_ENCRYPTION */
+
+#ifndef FRONTEND
+/*
+ * Read encryption key in hexadecimal form from stdin and store it in
+ * encryption_key variable.
+ */
+void
+read_encryption_key(read_encryption_key_cb read_char)
+{
+	char	*buf;
+	int		read_len, i, c;
+
+	buf = (char *) palloc(ENCRYPTION_KEY_CHARS);
+
+	read_len = 0;
+	while ((c = (*read_char)()) != EOF && c != '\n')
+	{
+		if (read_len >= ENCRYPTION_KEY_CHARS)
+			ereport(FATAL, (errmsg("Encryption key is too long")));
+
+		buf[read_len++] = c;
+	}
+
+	if (read_len < ENCRYPTION_KEY_CHARS)
+		ereport(FATAL, (errmsg("Encryption key is too short")));
+
+	/* Turn the hexadecimal representation into an array of bytes. */
+	for (i = 0; i < ENCRYPTION_KEY_LENGTH; i++)
+	{
+		if (sscanf(buf + 2 * i, "%2hhx", encryption_key + i) == 0)
+		{
+			ereport(FATAL,
+					(errmsg("Invalid character in encryption key at position %d",
+							2 * i)));
+		}
+	}
+
+	pfree(buf);
+}
+#endif							/* FRONTEND */
+
+/*
+ * Initialize encryption subsystem for use. Must be called before any
+ * encryptable data is read from or written to data directory.
+ */
+void
+setup_encryption(void)
+{
+#ifdef USE_ENCRYPTION
+	/*
+	 * Setup OpenSSL.
+	 *
+	 * None of these functions should return a value or raise error.
+	 */
+	ERR_load_crypto_strings();
+	OpenSSL_add_all_algorithms();
+
+	/*
+	 * TODO Find out if this needs to be called for OpenSSL < 1.1.0.
+	 */
+	/* OPENSSL_config(NULL); */
+
+	init_encryption_context(&ctx_encrypt, false);
+	init_encryption_context(&ctx_decrypt, false);
+
+	init_encryption_context(&ctx_encrypt_stream, true);
+	init_encryption_context(&ctx_decrypt_stream, true);
+
+	encryption_setup_done = true;
+#else  /* !USE_ENCRYPTION */
+#ifndef FRONTEND
+	/*
+	 * If no encryption implementation is linked and caller requests
+	 * encryption, we should error out here and thus cause the calling process
+	 * to fail (preferably postmaster, so the child processes don't make the
+	 * same mistake).
+	 */
+	ereport(FATAL, (errmsg(ENCRYPTION_NOT_SUPPORTED_MSG)));
+#else
+	/* Front-end shouldn't actually get here, but be careful. */
+	fprintf(stderr, "%s\n", ENCRYPTION_NOT_SUPPORTED_MSG);
+	exit(EXIT_FAILURE);
+#endif	/* FRONTEND */
+#endif							/* USE_ENCRYPTION */
+}
+
+/*
+ * Encrypts a fixed value into *buf to verify that encryption key is correct.
+ * Caller provided buf needs to be able to hold at least ENCRYPTION_SAMPLE_SIZE
+ * bytes.
+ */
+void
+sample_encryption(char *buf)
+{
+	char		tweak[TWEAK_SIZE];
+	int			i;
+
+	for (i = 0; i < TWEAK_SIZE; i++)
+		tweak[i] = i;
+
+	encrypt_block("postgresqlcrypt", buf, ENCRYPTION_SAMPLE_SIZE, tweak,
+				  false);
+}
+
+/*
+ * Encrypts one block of data with a specified tweak value. May only be called
+ * when encryption_enabled is true.
+ *
+ * Input and output buffer may point to the same location.
+ *
+ * "size" must be a (non-zero) multiple of ENCRYPTION_BLOCK.
+ *
+ * "tweak" value must be TWEAK_SIZE bytes long.
+ *
+ * If "stream" is set, stream cipher is used instead of block one.
+ *
+ * All-zero blocks are not encrypted to correctly handle relation extension,
+ * and also to simplify handling of holes created by seek past EOF and
+ * consequent write (see buffile.c).
+ */
+void
+encrypt_block(const char *input, char *output, Size size, char *tweak,
+			  bool stream)
+{
+#ifdef USE_ENCRYPTION
+	int			out_size;
+	EVP_CIPHER_CTX *ctx;
+
+	Assert(data_encrypted);
+
+	/*
+	 * Block cipher should only be used if the size is whole multiple of
+	 * encryption block size.
+	 */
+	Assert((size >= ENCRYPTION_BLOCK && size % ENCRYPTION_BLOCK == 0) ||
+		   stream);
+
+	/*
+	 * Empty page is not worth encryption. Do not waste cycles checking for
+	 * stream cipher as this is currently used only for XLOG pages, and empty
+	 * XLOG page should not be written to disk.
+	 */
+	if (!stream && IsAllZero(input, size))
+	{
+		memset(output, 0, size);
+		return;
+	}
+
+	ctx = !stream ? ctx_encrypt : ctx_encrypt_stream;
+
+	/* The remaining initialization. */
+	if (EVP_EncryptInit_ex(ctx, NULL, NULL, encryption_key,
+						   (unsigned char *) tweak) != 1)
+		evp_error();
+
+	/* Do the actual encryption. */
+	if (EVP_EncryptUpdate(ctx, (unsigned char *) output,
+						  &out_size, (unsigned char *) input, size) != 1)
+		evp_error();
+
+	Assert(out_size == size);
+#else
+	/* data_encrypted should not be set */
+	Assert(false);
+#endif							/* USE_ENCRYPTION */
+}
+
+
+#ifdef USE_ENCRYPTION
+/*
+ * Initialize the OpenSSL context for passed cipher.
+ *
+ * On server side this happens during postmaster startup, so other processes
+ * inherit the initialized context via fork(). There's no reason to this again
+ * and again in encrypt_block() / decrypt_block(), also because we cannot
+ * handle out-of-memory conditions encountered by OpenSSL in another way than
+ * ereport(FATAL). The OOM is much less likely to happen during postmaster
+ * startup, and even if it happens, troubleshooting should be easier than if
+ * it happened during normal operation.
+ *
+ * XXX Do we need to call EVP_CIPHER_CTX_cleanup() (via on_proc_exit callback
+ * for server processes and other way for front-ends)? Not sure it's
+ * necessary, as the initialization does not involve any shared resources
+ * (e.g. files).
+ */
+static void
+init_encryption_context(EVP_CIPHER_CTX **ctx_p, bool stream)
+{
+	EVP_CIPHER_CTX *ctx;
+	const EVP_CIPHER *cipher;
+#ifdef USE_ASSERT_CHECKING
+	int			block_size;
+#endif							/* USE_ASSERT_CHECKING */
+
+	cipher = !stream ? EVP_aes_256_cbc() : EVP_aes_256_ctr();
+
+	if ((*ctx_p = EVP_CIPHER_CTX_new()) == NULL)
+		evp_error();
+	ctx = *ctx_p;
+	if (EVP_EncryptInit_ex(ctx, cipher, NULL, NULL, NULL) != 1)
+		evp_error();
+
+	/*
+	 * No padding is needed. For a block cipher, the input block size should
+	 * already be a multiple of ENCRYPTION_BLOCK. For stream cipher, we don't
+	 * need padding anyway. This might save some cycles at the OpenSSL end.
+	 * XXX Is it setting worth when we don't call EVP_DecryptFinal_ex()
+	 * anyway?
+	 */
+	EVP_CIPHER_CTX_set_padding(ctx, 0);
+
+	Assert(EVP_CIPHER_CTX_iv_length(ctx) == TWEAK_SIZE);
+	Assert(EVP_CIPHER_CTX_key_length(ctx) == ENCRYPTION_KEY_LENGTH);
+	block_size = EVP_CIPHER_CTX_block_size(ctx);
+#ifdef USE_ASSERT_CHECKING
+	if (!stream)
+		Assert(block_size == ENCRYPTION_BLOCK);
+	else
+		Assert(block_size == 1);
+#endif							/* USE_ASSERT_CHECKING */
+}
+
+#endif							/* USE_ENCRYPTION */
+
+#ifdef USE_ENCRYPTION
+/*
+ * Error callback for openssl.
+ */
+static void
+evp_error(void)
+{
+	ERR_print_errors_fp(stderr);
+#ifndef FRONTEND
+
+	/*
+	 * FATAL is the appropriate level because backend can hardly fix anything
+	 * if encryption / decryption has failed.
+	 *
+	 * XXX Do we yet need EVP_CIPHER_CTX_cleanup() here?
+	 */
+	elog(FATAL, "OpenSSL encountered error during encryption or decryption.");
+#else
+	fprintf(stderr,
+			"OpenSSL encountered error during encryption or decryption.");
+	exit(EXIT_FAILURE);
+#endif							/* FRONTEND */
+}
+#endif							/* USE_ENCRYPTION */
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 44a59e1d4f..38dad9311f 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/encryption.h"
 #include "storage/ipc.h"
 #include "storage/proc.h"
 #include "storage/procsignal.h"
@@ -3868,6 +3869,35 @@ PostgresMain(int argc, char *argv[],
 	PG_SETMASK(&UnBlockSig);
 
 	/*
+	 * Standalone backend operating on an encrypted cluster needs encryption
+	 * key.
+	 */
+	if (!IsUnderPostmaster && data_encrypted)
+	{
+		char	sample[ENCRYPTION_SAMPLE_SIZE];
+
+		/* Display a prompt for user to enter the encryption key. */
+		printf("key> ");
+		fflush(stdout);
+
+		/*
+		 * Read the key from stdin. Pass interactive_getc() as the callback so
+		 * that the reading is interruptible.
+		 */
+		read_encryption_key(interactive_getc);
+
+		setup_encryption();
+
+		/* Verify the key. */
+		sample_encryption(sample);
+		if (memcmp(encryption_verification, sample, ENCRYPTION_SAMPLE_SIZE))
+			ereport(FATAL,
+					(errmsg("invalid encryption key"),
+					 errdetail("The passed encryption key does not match"
+							   " database encryption key.")));
+	}
+
+	/*
 	 * General initialization.
 	 *
 	 * NOTE: if you are tempted to add code in this vicinity, consider putting
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 631f16f5fe..2b7fc2fb0f 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -71,6 +71,7 @@
 #include "replication/walreceiver.h"
 #include "replication/walsender.h"
 #include "storage/bufmgr.h"
+#include "storage/encryption.h"
 #include "storage/dsm_impl.h"
 #include "storage/standby.h"
 #include "storage/fd.h"
@@ -1836,6 +1837,17 @@ static struct config_bool ConfigureNamesBool[] =
 	},
 
 	{
+		{"data_encryption", PGC_INTERNAL, PRESET_OPTIONS,
+			gettext_noop("Shows whether data encryption is turned on for this cluster."),
+			NULL,
+			GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE
+		},
+		&data_encrypted,
+		false,
+		NULL, NULL, NULL
+	},
+
+	{
 		{"syslog_sequence_numbers", PGC_SIGHUP, LOGGING_WHERE,
 			gettext_noop("Add sequence number to syslog messages to avoid duplicate suppression."),
 			NULL
diff --git a/src/bin/initdb/.gitignore b/src/bin/initdb/.gitignore
index 71a899ffb8..0f33b5c67d 100644
--- a/src/bin/initdb/.gitignore
+++ b/src/bin/initdb/.gitignore
@@ -1,5 +1,6 @@
 /encnames.c
 /localtime.c
+/encryption.c
 
 /initdb
 
diff --git a/src/bin/initdb/Makefile b/src/bin/initdb/Makefile
index 7c404430a9..b72de55d62 100644
--- a/src/bin/initdb/Makefile
+++ b/src/bin/initdb/Makefile
@@ -26,7 +26,7 @@ ifneq (,$(with_system_tzdata))
 override CPPFLAGS += '-DSYSTEMTZDIR="$(with_system_tzdata)"'
 endif
 
-OBJS=	initdb.o findtimezone.o localtime.o encnames.o $(WIN32RES)
+OBJS=	initdb.o findtimezone.o localtime.o encnames.o encryption.o $(WIN32RES)
 
 all: initdb
 
@@ -45,6 +45,9 @@ encnames.c: % : $(top_srcdir)/src/backend/utils/mb/%
 localtime.c: % : $(top_srcdir)/src/timezone/%
 	rm -f $@ && $(LN_S) $< .
 
+encryption.c: % : $(top_srcdir)/src/backend/storage/file/%
+	rm -f $@ && $(LN_S) $< .
+
 install: all installdirs
 	$(INSTALL_PROGRAM) initdb$(X) '$(DESTDIR)$(bindir)/initdb$(X)'
 
@@ -55,7 +58,7 @@ uninstall:
 	rm -f '$(DESTDIR)$(bindir)/initdb$(X)'
 
 clean distclean maintainer-clean:
-	rm -f initdb$(X) $(OBJS) encnames.c localtime.c
+	rm -f initdb$(X) $(OBJS) encnames.c localtime.c encryption.c
 	rm -rf tmp_check
 
 # ensure that changes in datadir propagate into object file
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index f1acbdfcf2..f2a582e975 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -68,11 +68,13 @@
 #include "common/logging.h"
 #include "common/restricted_token.h"
 #include "common/username.h"
+#include "fe_utils/encryption.h"
 #include "fe_utils/string_utils.h"
 #include "getaddrinfo.h"
 #include "getopt_long.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
+#include "storage/encryption.h"
 
 
 /* Ideally this would be in a .h file, but it hardly seems worth the trouble */
@@ -171,6 +173,8 @@ static bool output_failed = false;
 static int	output_errno = 0;
 static char *pgdata_native;
 
+unsigned char encryption_key[ENCRYPTION_KEY_LENGTH];
+
 /* defaults */
 static int	n_connections = 10;
 static int	n_buffers = 50;
@@ -239,6 +243,7 @@ static char **filter_lines_with_token(char **lines, const char *token);
 static char **readfile(const char *path);
 static void writefile(char *path, char **lines);
 static FILE *popen_check(const char *command, const char *mode);
+static void send_encryption_key(FILE *f);
 static char *get_id(void);
 static int	get_encoding_id(const char *encoding_name);
 static void set_input(char **dest, const char *filename);
@@ -584,6 +589,22 @@ popen_check(const char *command, const char *mode)
 }
 
 /*
+ * Send encryption key in hexadecimal format to the file stream passed.
+ *
+ * The backend processes could actually receive binary data but that would
+ * make startup of postgres in single-user mode less convenient.
+ */
+static void
+send_encryption_key(FILE *f)
+{
+	int	i;
+
+	for (i = 0; i < ENCRYPTION_KEY_LENGTH; i++)
+		fprintf(f, "%.2x", encryption_key[i]);
+	fputc('\n', f);
+}
+
+/*
  * clean up any files we created on failure
  * if we created the data directory remove it too
  */
@@ -1379,6 +1400,7 @@ bootstrap_template1(void)
 	char	  **bki_lines;
 	char		headerline[MAXPGPATH];
 	char		buf[64];
+	static char *encr_opt_str = NULL;
 
 	printf(_("running bootstrap script ... "));
 	fflush(stdout);
@@ -1446,17 +1468,37 @@ bootstrap_template1(void)
 	/* Also ensure backend isn't confused by this environment var: */
 	unsetenv("PGCLIENTENCODING");
 
+	/* Prepare the -K option for the backend. */
+	if (encryption_key_command)
+	{
+		size_t		len;
+
+		len = 3;
+		encr_opt_str = (char *) pg_malloc(len);
+		snprintf(encr_opt_str, len, "-K");
+	}
+	else
+	{
+		encr_opt_str = (char *) pg_malloc(1);
+		encr_opt_str[0] = '\0';
+	}
+
 	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" : "",
+			 encr_opt_str,
 			 boot_options,
 			 debug ? "-d 5" : "");
 
 
 	PG_CMD_OPEN;
 
+	/* If the cluster is encrypted, first send the encryption key. */
+	if (encryption_key_command)
+		send_encryption_key(cmdfd);
+
 	for (line = bki_lines; *line != NULL; line++)
 	{
 		PG_CMD_PUTS(*line);
@@ -2378,6 +2420,10 @@ usage(const char *progname)
 	printf(_("\nLess commonly used options:\n"));
 	printf(_("  -d, --debug               generate lots of debugging output\n"));
 	printf(_("  -k, --data-checksums      use data page checksums\n"));
+#ifdef	USE_ENCRYPTION
+	printf(_("  -K, --encryption-key-command\n"
+			 "                            command that returns encryption key\n"));
+#endif							/* USE_ENCRYPTION */
 	printf(_("  -L DIRECTORY              where to find the input files\n"));
 	printf(_("  -n, --no-clean            do not clean up after errors\n"));
 	printf(_("  -N, --no-sync             do not wait for changes to be written safely to disk\n"));
@@ -2957,6 +3003,13 @@ initialize_data_directory(void)
 	/* Top level PG_VERSION is checked by bootstrapper, so make it first */
 	write_version_file(NULL);
 
+	/*
+	 * If the cluster will be encrypted, run the command to generate the
+	 * encryption key.
+	 */
+	if (encryption_key_command)
+		run_encryption_key_command(encryption_key);
+
 	/* Select suitable configuration settings */
 	set_null_conf();
 	test_config_settings();
@@ -2986,6 +3039,10 @@ initialize_data_directory(void)
 
 	PG_CMD_OPEN;
 
+	/* If the cluster is encrypted, first send the encryption key. */
+	if (encryption_key_command)
+		send_encryption_key(cmdfd);
+
 	setup_auth(cmdfd);
 
 	setup_depend(cmdfd);
@@ -3054,6 +3111,9 @@ main(int argc, char *argv[])
 		{"waldir", required_argument, NULL, 'X'},
 		{"wal-segsize", required_argument, NULL, 12},
 		{"data-checksums", no_argument, NULL, 'k'},
+#ifdef	USE_ENCRYPTION
+		{"encryption-key-command", required_argument, NULL, 'K'},
+#endif							/* USE_ENCRYPTION */
 		{"allow-group-access", no_argument, NULL, 'g'},
 		{NULL, 0, NULL, 0}
 	};
@@ -3096,7 +3156,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, "dD:E:kK:L:nNU:WA:sST:X:g", long_options, &option_index)) != -1)
 	{
 		switch (c)
 		{
@@ -3148,6 +3208,11 @@ main(int argc, char *argv[])
 			case 'k':
 				data_checksums = true;
 				break;
+#ifdef	USE_ENCRYPTION
+			case 'K':
+				encryption_key_command = pg_strdup(optarg);
+				break;
+#endif							/* USE_ENCRYPTION */
 			case 'L':
 				share_path = pg_strdup(optarg);
 				break;
@@ -3318,6 +3383,11 @@ main(int argc, char *argv[])
 	if (pwprompt || pwfilename)
 		get_su_pwd();
 
+	if (encryption_key_command)
+		printf(_("Data encryption is enabled.\n"));
+	else
+		printf(_("Data encryption is disabled.\n"));
+
 	printf("\n");
 
 	initialize_data_directory();
diff --git a/src/bin/pg_controldata/pg_controldata.c b/src/bin/pg_controldata/pg_controldata.c
index 390ea0a939..3eef925f97 100644
--- a/src/bin/pg_controldata/pg_controldata.c
+++ b/src/bin/pg_controldata/pg_controldata.c
@@ -336,5 +336,14 @@ main(int argc, char *argv[])
 		   ControlFile->data_checksum_version);
 	printf(_("Mock authentication nonce:            %s\n"),
 		   mock_auth_nonce_str);
+	printf(_("Data encryption:                      %s\n"),
+		   ControlFile->data_cipher > PG_CIPHER_NONE ? _("on") : _("off"));
+	if (ControlFile->data_cipher > PG_CIPHER_NONE)
+		printf(_("Data encryption fingerprint:          %08X%08X%08X%08X\n"),
+			   htonl(((uint32 *) ControlFile->encryption_verification)[0]),
+			   htonl(((uint32 *) ControlFile->encryption_verification)[1]),
+			   htonl(((uint32 *) ControlFile->encryption_verification)[2]),
+			   htonl(((uint32 *) ControlFile->encryption_verification)[3])
+			);
 	return 0;
 }
diff --git a/src/common/string.c b/src/common/string.c
index b01a56ceaa..5ba890d37d 100644
--- a/src/common/string.c
+++ b/src/common/string.c
@@ -42,6 +42,45 @@ pg_str_endswith(const char *str, const char *end)
 	return strcmp(str, end) == 0;
 }
 
+/*
+ * Helper function to check if a page is completely empty.
+ *
+ * TODO Invent name that is more consistent with that of the other function(s)
+ * in this module.
+ */
+bool
+IsAllZero(const char *input, Size size)
+{
+	const char *pos = input;
+	const char *aligned_start = (char *) MAXALIGN64(input);
+	const char *end = input + size;
+
+	/* Check 1 byte at a time until pos is 8 byte aligned */
+	while (pos < aligned_start)
+		if (*pos++ != 0)
+			return false;
+
+	/*
+	 * Run 8 parallel 8 byte checks in one iteration. On 2016 hardware
+	 * slightly faster than 4 parallel checks.
+	 */
+	while (pos + 8 * sizeof(uint64) <= end)
+	{
+		uint64	   *p = (uint64 *) pos;
+
+		if ((p[0] | p[1] | p[2] | p[3] | p[4] | p[5] | p[6] | p[7]) != 0)
+			return false;
+		pos += 8 * sizeof(uint64);
+	}
+
+	/* Handle unaligned tail. */
+	while (pos < end)
+		if (*pos++ != 0)
+			return false;
+
+	return true;
+}
+
 
 /*
  * strtoint --- just like strtol, but returns int not long
diff --git a/src/fe_utils/Makefile b/src/fe_utils/Makefile
index 7d73800323..a508ec150e 100644
--- a/src/fe_utils/Makefile
+++ b/src/fe_utils/Makefile
@@ -19,7 +19,8 @@ include $(top_builddir)/src/Makefile.global
 
 override CPPFLAGS := -DFRONTEND -I$(libpq_srcdir) $(CPPFLAGS)
 
-OBJS = mbprint.o print.o psqlscan.o simple_list.o string_utils.o conditional.o
+OBJS = mbprint.o print.o psqlscan.o simple_list.o string_utils.o conditional.o \
+	encryption.o
 
 all: libpgfeutils.a
 
diff --git a/src/fe_utils/encryption.c b/src/fe_utils/encryption.c
new file mode 100644
index 0000000000..dab1435014
--- /dev/null
+++ b/src/fe_utils/encryption.c
@@ -0,0 +1,90 @@
+/*-------------------------------------------------------------------------
+ *
+ * encryption.c
+ *	  Front-end code to handle keys for full cluster encryption. The actual
+ *	  encryption is not performed here.
+ *
+ * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/fe_utils/encryption.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include <unistd.h>
+
+#include "postgres_fe.h"
+
+#include "common/fe_memutils.h"
+#include "common/file_perm.h"
+#include "common/logging.h"
+#include "fe_utils/encryption.h"
+#include "storage/encryption.h"
+#include "libpq-fe.h"
+#include "libpq-int.h"
+#include "libpq/pqcomm.h"
+
+char	   *encryption_key_command = NULL;
+
+/*
+ * Run the command that is supposed to generate encryption key and store it
+ * where encryption_key points to.
+ */
+void
+run_encryption_key_command(unsigned char *encryption_key)
+{
+	FILE	   *fp;
+	char	   *buf;
+	int		read_len, i, c;
+
+	Assert(encryption_key_command != NULL &&
+		   strlen(encryption_key_command) > 0);
+
+	fp = popen(encryption_key_command, "r");
+	if (fp == NULL)
+	{
+		pg_log_fatal("Failed to execute \"%s\"", encryption_key_command);
+		exit(EXIT_FAILURE);
+	}
+
+	buf = (char *) palloc(ENCRYPTION_KEY_CHARS);
+
+	/*
+	 * Read the key. This is very similar to backend's read_encryption_key()
+	 * but there seems to be no straightforward way to call the function from
+	 * here.
+	 */
+	read_len = 0;
+	while ((c = fgetc(fp)) != EOF && c != '\n')
+	{
+		if (read_len >= ENCRYPTION_KEY_CHARS)
+		{
+			pg_log_fatal("Encryption key is too long");
+			exit(EXIT_FAILURE);
+		}
+
+		buf[read_len++] = c;
+	}
+
+	if (read_len < ENCRYPTION_KEY_CHARS)
+	{
+		pg_log_fatal("Encryption key is too short");
+		exit(EXIT_FAILURE);
+	}
+
+	/* Turn the hexadecimal representation into an array of bytes. */
+	for (i = 0; i < ENCRYPTION_KEY_LENGTH; i++)
+	{
+		if (sscanf(buf + 2 * i, "%2hhx", encryption_key + i) == 0)
+		{
+			pg_log_fatal("Invalid character in encryption key at position %d",
+						 2 * i);
+			exit(EXIT_FAILURE);
+		}
+	}
+
+	pfree(buf);
+	pclose(fp);
+}
diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h
index ff98d9e91a..c1b53e1ebf 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/encryption.h"
 
 
 /* Version identifier for this pg_control format */
@@ -228,6 +229,16 @@ typedef struct ControlFileData
 	 */
 	char		mock_authentication_nonce[MOCK_AUTH_NONCE_LEN];
 
+	/*
+	 * Cipher used to encrypt data. Zero if unencrypted.
+	 *
+	 * The data type is actually CipherKind, but we don't want to include
+	 * encryption.h just because of this field.
+	 */
+	uint8		data_cipher;
+	/* Sample value for encryption key verification */
+	uint8		encryption_verification[ENCRYPTION_SAMPLE_SIZE];
+
 	/* CRC of all above ... MUST BE LAST! */
 	pg_crc32c	crc;
 } ControlFileData;
diff --git a/src/include/common/string.h b/src/include/common/string.h
index 77f31337ca..6128d81b8c 100644
--- a/src/include/common/string.h
+++ b/src/include/common/string.h
@@ -14,5 +14,6 @@ extern bool pg_str_endswith(const char *str, const char *end);
 extern int	strtoint(const char *pg_restrict str, char **pg_restrict endptr,
 					 int base);
 extern void pg_clean_ascii(char *str);
+extern bool IsAllZero(const char *input, Size size);
 
 #endif							/* COMMON_STRING_H */
diff --git a/src/include/fe_utils/encryption.h b/src/include/fe_utils/encryption.h
new file mode 100644
index 0000000000..69e3b2f756
--- /dev/null
+++ b/src/include/fe_utils/encryption.h
@@ -0,0 +1,18 @@
+/*-------------------------------------------------------------------------
+ *
+ * encryption.h
+ *	  Client code to support full cluster encryption.
+ *
+ * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/include/fe_utils/encryption.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+/* Executable to retrieve the encryption key. */
+extern char *encryption_key_command;
+
+extern void run_encryption_key_command(unsigned char *encryption_key);
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 743401cb96..b716fb4d13 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -172,6 +172,13 @@
 #endif
 
 /*
+ * OpenSSL is currently the only implementation of encryption we use.
+ */
+#ifdef USE_OPENSSL
+#define USE_ENCRYPTION
+#endif
+
+/*
  * This is the default directory in which AF_UNIX socket files are
  * placed.  Caution: changing this risks breaking your existing client
  * applications, which are likely to continue to look in the old
diff --git a/src/include/storage/encryption.h b/src/include/storage/encryption.h
new file mode 100644
index 0000000000..8479b76a34
--- /dev/null
+++ b/src/include/storage/encryption.h
@@ -0,0 +1,99 @@
+/*-------------------------------------------------------------------------
+ *
+ * encryption.h
+ *	  Full database encryption support
+ *
+ *
+ * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/storage/encryption.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef ENCRYPTION_H
+#define ENCRYPTION_H
+
+#include "access/xlogdefs.h"
+#include "miscadmin.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "port/pg_crc32c.h"
+
+/*
+ * Common error message issued when particular code path cannot be executed
+ * due to absence of the OpenSSL library.
+ */
+#define ENCRYPTION_NOT_SUPPORTED_MSG \
+	"compile postgres with --with-openssl to use encryption."
+
+/*
+ * Full database encryption key.
+ *
+ * The key of EVP_aes_256_cbc() cipher is 256 bits long.
+ */
+#define	ENCRYPTION_KEY_LENGTH	32
+/* Key length in characters (two characters per hexadecimal digit) */
+#define ENCRYPTION_KEY_CHARS	(ENCRYPTION_KEY_LENGTH * 2)
+
+/*
+ * Cipher used to encrypt data.
+ *
+ * Due to very specific requirements, the ciphers are not likely to change,
+ * but we should be somewhat flexible.
+ *
+ * XXX If we have more than one cipher someday, have pg_controldata report the
+ * cipher kind (in textual form) instead of merely saying "on".
+ */
+typedef enum CipherKind
+{
+	/* The cluster is not encrypted. */
+	PG_CIPHER_NONE = 0,
+
+	/*
+	 * AES (Rijndael) in CBC mode of operation as block cipher, and in CTR
+	 * mode as stream cipher. Key length is always 256 bits.
+	 */
+	PG_CIPHER_AES_BLOCK_CBC_256_STREAM_CTR_256
+}			CipherKind;
+
+/* Key to encrypt / decrypt data. */
+extern unsigned char encryption_key[];
+
+/*
+ * The encrypted data is a series of blocks of size
+ * ENCRYPTION_BLOCK. Currently we use the EVP_aes_256_xts implementation. Make
+ * sure the following constants match if adopting another algorithm.
+ */
+#define ENCRYPTION_BLOCK 16
+
+#define TWEAK_SIZE 16
+
+/* Is the cluster encrypted? */
+extern PGDLLIMPORT bool data_encrypted;
+
+/*
+ * Number of bytes reserved to store encryption sample in ControlFileData.
+ */
+#define ENCRYPTION_SAMPLE_SIZE 16
+
+#ifndef FRONTEND
+/* Copy of the same field of ControlFileData. */
+extern char encryption_verification[];
+#endif							/* FRONTEND */
+
+/* Do we have encryption_key and the encryption library initialized? */
+extern bool	encryption_setup_done;
+
+#ifndef FRONTEND
+typedef int (*read_encryption_key_cb) (void);
+extern void read_encryption_key(read_encryption_key_cb read_char);
+#endif							/* FRONTEND */
+
+extern void setup_encryption(void);
+extern void sample_encryption(char *buf);
+extern void encrypt_block(const char *input, char *output, Size size,
+							char *tweak, bool stream);
+extern void encryption_error(bool fatal, char *message);
+
+#endif							/* ENCRYPTION_H */
-- 
2.13.7

