From af9da6990b09276e65054d0d341f758f928e0fc6 Mon Sep 17 00:00:00 2001
From: Antonin Houska <ah@cybertec.at>
Date: Fri, 5 Jul 2019 16:24:01 +0200
Subject: [PATCH 03/17] Teach postmaster to accept encryption key

This patch teaches postmaster to accept encryption key (as a special kind of
startup packet) before recovery starts. In fact the key is received by a
backend that postmaster launches if a client tries to initiate connection
while the postmaster is in PM_INIT state. At this stage, only the key is
received, whereas regular connection attempt ends up with

FATAL:  the database system is starting up

If postmaster receives no key for some time, it raises this error and exits:

FATAL:  Encryption key not received.

With this patch, user can start postmaster as usual:

postgres -D data

and use pg_keytool utility in another session to send the key:

echo 71916b8b93063c280e4e0ee646395f450bda4f1bac85c57bc3a6afd168f3ab47 | pg_keytool -s
---
 src/backend/postmaster/postmaster.c   | 211 +++++++++++++++++++++++++++++++++-
 src/backend/storage/file/encryption.c |  34 ++++++
 src/backend/storage/ipc/ipci.c        |   2 +
 src/bin/Makefile                      |   1 +
 src/bin/pg_keytool/.gitignore         |   1 +
 src/bin/pg_keytool/Makefile           |  31 +++++
 src/bin/pg_keytool/pg_keytool.c       | 199 ++++++++++++++++++++++++++++++++
 src/fe_utils/encryption.c             | 101 ++++++++++++++++
 src/include/fe_utils/encryption.h     |   2 +
 src/include/libpq/pqcomm.h            |  24 ++++
 src/include/storage/encryption.h      |  26 +++++
 11 files changed, 628 insertions(+), 4 deletions(-)
 create mode 100644 src/bin/pg_keytool/.gitignore
 create mode 100644 src/bin/pg_keytool/Makefile
 create mode 100644 src/bin/pg_keytool/pg_keytool.c

diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index e8e3f8a4d3..47f7bc93f7 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -117,6 +117,7 @@
 #include "postmaster/syslogger.h"
 #include "replication/logicallauncher.h"
 #include "replication/walsender.h"
+#include "storage/encryption.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/pg_shmem.h"
@@ -402,12 +403,13 @@ static void PostmasterStateMachine(void);
 static void BackendInitialize(Port *port);
 static void BackendRun(Port *port) pg_attribute_noreturn();
 static void ExitPostmaster(int status) pg_attribute_noreturn();
-static int	ServerLoop(void);
+static int	ServerLoop(bool receive_encryption_key);
 static void ServerLoopCheckTimeouts(void);
 static int	BackendStartup(Port *port);
 static int	ProcessStartupPacket(Port *port, bool secure_done);
 static void SendNegotiateProtocolVersion(List *unrecognized_protocol_options);
 static void processCancelRequest(Port *port, void *pkt);
+static void processEncryptionKey(void *pkt);
 static int	initMasks(fd_set *rmask);
 static void report_fork_failure_to_client(Port *port, int errnum);
 static CAC_state canAcceptConnections(void);
@@ -1365,6 +1367,41 @@ PostmasterMain(int argc, char *argv[])
 	AddToDataDirLockFile(LOCK_FILE_LINE_PM_STATUS, PM_STATUS_STARTING);
 
 	/*
+	 * If encryption key is needed and we don't have it yet, call ServerLoop()
+	 * in a restricted mode which allows a client application to send us the
+	 * key. Regular client connections are not allowed yet.
+	 */
+	if (data_encrypted && !encryption_setup_done)
+	{
+		char	sample[ENCRYPTION_SAMPLE_SIZE];
+
+		status = ServerLoop(true);
+
+		/* No other return code should be seen here. */
+		Assert(status == STATUS_OK);
+
+		/* ServerLoop() shouldn't have exited otherwise. */
+		Assert(encryption_key_shmem->initialized);
+
+		/*
+		 * Take a local copy of the key so that we don't have to receive it
+		 * again if restarting after a crash.
+		 */
+		memcpy(encryption_key, encryption_key_shmem, ENCRYPTION_KEY_LENGTH);
+
+		/* Finalize the setup. */
+		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.")));
+	}
+
+	/*
 	 * We're ready to rock and roll...
 	 */
 	StartupPID = StartupDataBase();
@@ -1375,7 +1412,7 @@ PostmasterMain(int argc, char *argv[])
 	/* Some workers may be scheduled to start now */
 	maybe_start_bgworkers();
 
-	status = ServerLoop();
+	status = ServerLoop(false);
 
 	/*
 	 * ServerLoop probably shouldn't ever return, but if it does, close down.
@@ -1615,18 +1652,37 @@ DetermineSleepTime(struct timeval *timeout)
 static time_t		last_lockfile_recheck_time, last_touch_time;
 
 /*
+ * The maximum amount of time postmaster can wait for encryption key if the
+ * cluster is encrypted. It should be shorter than the time pg_ctl waits for
+ * postgres to start (60 seconds) and should not be too long so that failure
+ * to specify encryption key command is recognized soon.
+ */
+#define	MAX_WAIT_FOR_KEY_SECS	20
+
+/* When should waiting for encryption key end. */
+static TimestampTz	wait_for_keys_until = 0;
+
+/*
  * Main idle loop of postmaster
  *
+ * If receive_encryption_key is true, only launch backend(s) to receive
+ * cluster encryption key and stop as soon as the key is in shared memory.
+ *
  * NB: Needs to be called with signals blocked
  */
 static int
-ServerLoop(void)
+ServerLoop(bool receive_encryption_key)
 {
 	fd_set		readmask;
 	int			nSockets;
 
 	last_lockfile_recheck_time = last_touch_time = time(NULL);
 
+	/* Compute wait_for_keys_until if needed and not done yet. */
+	if (receive_encryption_key && wait_for_keys_until == 0)
+		wait_for_keys_until = PgStartTime +
+			MAX_WAIT_FOR_KEY_SECS * USECS_PER_SEC;
+
 	nSockets = initMasks(&readmask);
 
 	for (;;)
@@ -1663,6 +1719,27 @@ ServerLoop(void)
 			/* Needs to run with blocked signals! */
 			DetermineSleepTime(&timeout);
 
+			/*
+			 * If waiting for the encryption key, make sure
+			 * MAX_WAIT_FOR_KEY_SECS is not exceeded.
+			 */
+			if (receive_encryption_key)
+			{
+				TimestampTz	timeout_tz, timeout_end;;
+
+				timeout_tz = timeout.tv_sec * USECS_PER_SEC +
+					timeout.tv_usec;
+				timeout_end = GetCurrentTimestamp() + timeout_tz;
+
+				if (timeout_end > wait_for_keys_until)
+				{
+					timeout_tz -= (timeout_end - wait_for_keys_until);
+
+					timeout.tv_sec = timeout_tz / USECS_PER_SEC;
+					timeout.tv_usec = timeout_tz % USECS_PER_SEC;
+				}
+			}
+
 			PG_SETMASK(&UnBlockSig);
 
 			selres = select(nSockets, &rmask, NULL, NULL, &timeout);
@@ -1714,6 +1791,33 @@ ServerLoop(void)
 			}
 		}
 
+		/*
+		 * If only waiting for the encryption key, it's too early to launch
+		 * the other processes.
+		 */
+		if (receive_encryption_key)
+		{
+			/* The regular checks should take place though. */
+			ServerLoopCheckTimeouts();
+
+			/* Done if the key has been delivered. */
+			if (encryption_key_shmem->initialized)
+				return STATUS_OK;
+
+			/*
+			 * Do not wait for the key forever. One problem we solve here is
+			 * that pg_ctl (or custom script that starts postgres) does not
+			 * have to check whether the cluster is encrypted. If DBA forgets
+			 * to pass the encryption key command, he'll simply see the
+			 * startup to fail and the appropriate message to appear in the
+			 * server log.
+			 */
+			if (GetCurrentTimestamp() >= wait_for_keys_until)
+				ereport(FATAL, (errmsg("Encryption key not received.")));
+
+			continue;
+		}
+
 		/* If we have lost the log collector, try to start a new one */
 		if (SysLoggerPID == 0 && Logging_collector)
 			SysLoggerPID = SysLogger_Start();
@@ -1990,6 +2094,13 @@ ProcessStartupPacket(Port *port, bool secure_done)
 		return STATUS_ERROR;
 	}
 
+	if (proto == ENCRYPTION_KEY_MSG_CODE && data_encrypted)
+	{
+		processEncryptionKey(buf);
+		/* Not really an error, but we don't want to proceed further */
+		return STATUS_ERROR;
+	}
+
 	if (proto == NEGOTIATE_SSL_CODE && !secure_done)
 	{
 		char		SSLok;
@@ -2394,6 +2505,77 @@ processCancelRequest(Port *port, void *pkt)
 }
 
 /*
+ * Process a message from backend that contains the encryption key.
+ *
+ * Currently there's no guarantee that only a single backend tries to deliver
+ * the key, but it's not worth trying to synchronize the access. If multiple
+ * callers try to process the same message, nothing should get broken. If some
+ * caller is delivering a wrong key (e.g. due to misconfiguration of another
+ * cluster), it does not help if it waits until processing of the correct key
+ * is finished.
+ *
+ * If the key gets messed up, the worst case is that the server fails to start
+ * up. (No data corruption is expected.)
+ *
+ * Client should not expect any response to this request: failure on client
+ * side indicates either broken connection, wrong key, bug in
+ * send_key_to_postmaster() or repeated call of the client - nothing to be
+ * handled easily by client code. Most of these failures need attention of the
+ * DBA.
+ */
+void
+processEncryptionKey(void *pkt)
+{
+	EncryptionKeyMsg	*msg = (EncryptionKeyMsg *) pkt;
+
+	/* Backend to receive the key should not be launched otherwise. */
+	Assert(data_encrypted);
+
+	/*
+	 * The checks below would fire in this case too, but this error message is
+	 * clearer for the case like this.
+	 */
+	if (pmState != PM_INIT)
+	{
+		ereport(COMMERROR,
+				(errmsg("encryption key can only be setup during startup"),
+				 errhint("Check if the cluster is encrypted.")));
+		return;
+	}
+
+	/*
+	 * Someone else already initialized the key. This is not fatal but seems
+	 * to indicate misconfiguration which DBA should be aware of. Check also
+	 * encryption_setup_done because shared memory could have been reset. As
+	 * postmaster has a local copy of the key, the new key should not be used,
+	 * but it'd be inconsistent if we didn't complain in this special case.
+	 */
+	if (encryption_key_shmem->initialized || encryption_setup_done)
+	{
+		ereport(COMMERROR,
+				(errmsg("received encryption key more than once")));
+		return;
+	}
+
+	if (msg->version != 1)
+	{
+		ereport(COMMERROR,
+				(errmsg("unexpected version of encryption key message %d",
+					msg->version)));
+		return;
+	}
+
+	memcpy(encryption_key_shmem->data, msg->data, ENCRYPTION_KEY_LENGTH);
+	/*
+	 * XXX Is memory barrier needed here to make sure that the copying is
+	 * done?
+	 */
+	encryption_key_shmem->initialized = true;
+
+	ereport(DEBUG1, (errmsg_internal("encryption key received")));
+}
+
+/*
  * canAcceptConnections --- check to see if database state allows connections.
  */
 static CAC_state
@@ -2418,7 +2600,8 @@ canAcceptConnections(void)
 		else if (Shutdown > NoShutdown)
 			return CAC_SHUTDOWN;	/* shutdown is pending */
 		else if (!FatalError &&
-				 (pmState == PM_STARTUP ||
+				 ((data_encrypted && pmState == PM_INIT) ||
+				  pmState == PM_STARTUP ||
 				  pmState == PM_RECOVERY))
 			return CAC_STARTUP; /* normal startup */
 		else if (!FatalError &&
@@ -2761,6 +2944,16 @@ pmdie(SIGNAL_ARGS)
 				pmState = (pmState == PM_RUN) ?
 					PM_WAIT_BACKUP : PM_WAIT_READONLY;
 			}
+			else if (pmState == PM_INIT && data_encrypted &&
+					 !encryption_setup_done)
+			{
+				/*
+				 * Only backends to receive encryption key may be active now,
+				 * and these should not be involved in any transactions.
+				 */
+				SignalSomeChildren(SIGTERM, BACKEND_TYPE_NORMAL);
+				pmState = PM_WAIT_BACKENDS;
+			}
 
 			/*
 			 * Now wait for online backup mode to end and backends to exit. If
@@ -2828,6 +3021,16 @@ pmdie(SIGNAL_ARGS)
 					signal_child(WalWriterPID, SIGTERM);
 				pmState = PM_WAIT_BACKENDS;
 			}
+			else if (pmState == PM_INIT && data_encrypted &&
+					 !encryption_setup_done)
+			{
+				/*
+				 * Only backends to receive encryption key may be active now,
+				 * and these should not be involved in any transactions.
+				 */
+				SignalSomeChildren(SIGTERM, BACKEND_TYPE_NORMAL);
+				pmState = PM_WAIT_BACKENDS;
+			}
 
 			/*
 			 * Now wait for backends to exit.  If there are none,
diff --git a/src/backend/storage/file/encryption.c b/src/backend/storage/file/encryption.c
index 629138fae7..1eda334762 100644
--- a/src/backend/storage/file/encryption.c
+++ b/src/backend/storage/file/encryption.c
@@ -53,6 +53,10 @@ EVP_CIPHER_CTX *ctx_encrypt,
 
 unsigned char encryption_key[ENCRYPTION_KEY_LENGTH];
 
+#ifndef FRONTEND
+ShmemEncryptionKey *encryption_key_shmem = NULL;
+#endif							/* FRONTEND */
+
 bool		data_encrypted = false;
 
 char encryption_verification[ENCRYPTION_SAMPLE_SIZE];
@@ -66,6 +70,36 @@ static void evp_error(void);
 
 #ifndef FRONTEND
 /*
+ * Report space needed for our shared memory area
+ */
+Size
+EncryptionShmemSize(void)
+{
+	return sizeof(ShmemEncryptionKey);
+}
+
+/*
+ * Initialize our shared memory area
+ */
+void
+EncryptionShmemInit(void)
+{
+	bool	found;
+
+	encryption_key_shmem = ShmemInitStruct("Cluster Encryption Key",
+										   EncryptionShmemSize(),
+										   &found);
+	if (!IsUnderPostmaster)
+	{
+		Assert(!found);
+
+		encryption_key_shmem->initialized = false;
+	}
+	else
+		Assert(found);
+}
+
+/*
  * Read encryption key in hexadecimal form from stdin and store it in
  * encryption_key variable.
  */
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index d7d733530f..771bf30c63 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -147,6 +147,7 @@ CreateSharedMemoryAndSemaphores(int port)
 		size = add_size(size, BTreeShmemSize());
 		size = add_size(size, SyncScanShmemSize());
 		size = add_size(size, AsyncShmemSize());
+		size = add_size(size, EncryptionShmemSize());
 #ifdef EXEC_BACKEND
 		size = add_size(size, ShmemBackendArraySize());
 #endif
@@ -263,6 +264,7 @@ CreateSharedMemoryAndSemaphores(int port)
 	BTreeShmemInit();
 	SyncScanShmemInit();
 	AsyncShmemInit();
+	EncryptionShmemInit();
 
 #ifdef EXEC_BACKEND
 
diff --git a/src/bin/Makefile b/src/bin/Makefile
index 903e58121f..b05a8bbb4e 100644
--- a/src/bin/Makefile
+++ b/src/bin/Makefile
@@ -22,6 +22,7 @@ SUBDIRS = \
 	pg_controldata \
 	pg_ctl \
 	pg_dump \
+	pg_keytool \
 	pg_resetwal \
 	pg_rewind \
 	pg_test_fsync \
diff --git a/src/bin/pg_keytool/.gitignore b/src/bin/pg_keytool/.gitignore
new file mode 100644
index 0000000000..249876a6ed
--- /dev/null
+++ b/src/bin/pg_keytool/.gitignore
@@ -0,0 +1 @@
+/pg_keytool
diff --git a/src/bin/pg_keytool/Makefile b/src/bin/pg_keytool/Makefile
new file mode 100644
index 0000000000..6da0631f32
--- /dev/null
+++ b/src/bin/pg_keytool/Makefile
@@ -0,0 +1,31 @@
+# src/bin/pg_keysetup/Makefile
+
+PGFILEDESC = "pg_keytool - handle cluster encryption key"
+PGAPPICON=win32
+
+subdir = src/bin/pg_keytool
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+OBJS = pg_keytool.o $(RMGRDESCOBJS) $(WIN32RES)
+
+override CPPFLAGS := -I$(libpq_srcdir) $(CPPFLAGS)
+override CPPFLAGS := -DFRONTEND $(CPPFLAGS)
+LDFLAGS_INTERNAL += -L$(top_builddir)/src/fe_utils -lpgfeutils $(libpq_pgport)
+
+all: pg_keytool
+
+pg_keytool: $(OBJS) | submake-libpgport
+	$(CC) $(CFLAGS) $^ $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)
+
+install: all installdirs
+	$(INSTALL_PROGRAM) pg_keytool$(X) '$(DESTDIR)$(bindir)/pg_keytool$(X)'
+
+installdirs:
+	$(MKDIR_P) '$(DESTDIR)$(bindir)'
+
+uninstall:
+	rm -f '$(DESTDIR)$(bindir)/pg_keytool$(X)'
+
+clean distclean maintainer-clean:
+	rm -f pg_keytool$(X) $(OBJS) encryption.c
diff --git a/src/bin/pg_keytool/pg_keytool.c b/src/bin/pg_keytool/pg_keytool.c
new file mode 100644
index 0000000000..322625da41
--- /dev/null
+++ b/src/bin/pg_keytool/pg_keytool.c
@@ -0,0 +1,199 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_keytool.c - Handle cluster encryption key.
+ *
+ * Copyright (c) 2013-2019, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *		  src/bin/pg_keytool/pg_keytool.c
+ *-------------------------------------------------------------------------
+ */
+/*
+ * TODO Adopt the new frontend logging API, after some things are clarified:
+ * https://www.postgresql.org/message-id/1939.1560773970%40localhost
+ */
+#define FRONTEND 1
+#include "postgres.h"
+
+#include <dirent.h>
+#include <unistd.h>
+
+#include "common/fe_memutils.h"
+#include "common/logging.h"
+#include "fe_utils/encryption.h"
+#include "libpq-fe.h"
+#include "libpq-int.h"
+#include "libpq/pqcomm.h"
+#include "port/pg_crc32c.h"
+#include "storage/encryption.h"
+#include "getopt_long.h"
+
+#ifdef USE_ENCRYPTION
+static const char *progname;
+
+unsigned char encryption_key[ENCRYPTION_KEY_LENGTH];
+
+static void
+usage(const char *progname)
+{
+	const char *env;
+
+	printf(_("%s is a tool to handle cluster encryption key.\n\n"),
+		   progname);
+	printf(_("Usage:\n"));
+	printf(_("  %s [OPTION]...\n"), progname);
+	printf(_("\nOptions:\n"));
+	/* Display default host */
+	env = getenv("PGHOST");
+	printf(_("  -h, --host=HOSTNAME    database server host or socket directory (default: \"%s\")\n"),
+			env ? env : _("local socket"));
+	/* Display default port */
+	env = getenv("PGPORT");
+	printf(_("  -p, --port=PORT        database server port (default: \"%s\")\n"),
+			env ? env : DEF_PGPORT_STR);
+	printf(_("  -s,                    send output to database server\n"));
+	printf(_("  -?, --help             show this help, then exit\n\n"));
+	printf(_("Key is read from stdin and sent either to stdout or to PostgreSQL server being started\n"));
+}
+#endif							/* USE_ENCRYPTION */
+
+int
+main(int argc, char **argv)
+{
+/*
+ * If no encryption library is linked, let the utility fail immediately. It'd
+ * be weird if we reported incorrect usage just to say later that no useful
+ * work can be done anyway.
+ */
+#ifdef USE_ENCRYPTION
+	int			c;
+	char		*host = NULL;
+	char		*port_str = NULL;
+	bool		to_server = false;
+	int			i, n;
+	int			optindex;
+	char		key_chars[ENCRYPTION_KEY_CHARS];
+
+	static struct option long_options[] =
+	{
+		{"pgdata", required_argument, NULL, 'D'},
+		{"host", required_argument, NULL, 'h'},
+		{"port", required_argument, NULL, 'p'},
+		{NULL, 0, NULL, 0}
+	};
+
+	pg_logging_init(argv[0]);
+	progname = get_progname(argv[0]);
+
+	if (argc > 1)
+	{
+		if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
+		{
+			usage(progname);
+			exit(0);
+		}
+		if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
+		{
+			puts("pg_keytool (PostgreSQL) " PG_VERSION);
+			exit(0);
+		}
+	}
+
+	while ((c = getopt_long(argc, argv, "h:p:s",
+							long_options, &optindex)) != -1)
+	{
+		switch (c)
+		{
+			case 'h':
+				host = pg_strdup(optarg);
+				break;
+
+			case 'p':
+				port_str = pg_strdup(optarg);
+				break;
+
+			case 's':
+				to_server = true;
+				break;
+
+			case '?':
+				/* Actual help option given */
+				if (strcmp(argv[optind - 1], "-?") == 0)
+				{
+					usage(progname);
+					exit(EXIT_SUCCESS);
+				}
+
+			default:
+				pg_log_error("Try \"%s --help\" for more information.", progname);
+				exit(1);
+		}
+	}
+
+	/* Complain if any arguments remain */
+	if (optind < argc)
+	{
+		fprintf(stderr, _("%s: too many command-line arguments (first is \"%s\")\n"),
+				progname, argv[optind]);
+		fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
+				progname);
+		exit(1);
+	}
+
+	if ((host || port_str) && !to_server)
+	{
+		pg_log_error("host and port can only be passed along with the -s option");
+		exit(1);
+	}
+
+	/* Read the key. */
+	n = 0;
+	/* Key length in characters (two characters per hexadecimal digit) */
+	while ((c = getchar()) != EOF && c != '\n')
+	{
+		if (n >= ENCRYPTION_KEY_CHARS)
+		{
+			pg_log_error("The key is too long");
+			exit(EXIT_FAILURE);
+		}
+
+		key_chars[n++] = c;
+	}
+
+	if (n < ENCRYPTION_KEY_CHARS)
+	{
+		pg_log_error("The key is too short");
+		exit(EXIT_FAILURE);
+	}
+
+	for (i = 0; i < ENCRYPTION_KEY_LENGTH; i++)
+	{
+		if (sscanf(key_chars + 2 * i, "%2hhx", encryption_key + i) == 0)
+		{
+			pg_log_error("Invalid character in encryption key at position %d",
+						 2 * i);
+			exit(EXIT_FAILURE);
+		}
+	}
+
+	/*
+	 * Send the encryption key either to stdout or to server.
+	 */
+	if (!to_server)
+	{
+		for (i = 0; i < ENCRYPTION_KEY_LENGTH; i++)
+			printf("%.2x", encryption_key[i]);
+		printf("\n");
+	}
+	else
+	{
+		if (!send_key_to_postmaster(host, port_str, encryption_key))
+			pg_log_error("could not send encryption key to server");
+	}
+
+#else
+	pg_log_fatal(ENCRYPTION_NOT_SUPPORTED_MSG);
+	exit(EXIT_FAILURE);
+#endif							/* USE_ENCRYPTION */
+	return 0;
+}
diff --git a/src/fe_utils/encryption.c b/src/fe_utils/encryption.c
index dab1435014..ca37c9f373 100644
--- a/src/fe_utils/encryption.c
+++ b/src/fe_utils/encryption.c
@@ -88,3 +88,104 @@ run_encryption_key_command(unsigned char *encryption_key)
 	pfree(buf);
 	pclose(fp);
 }
+
+/*
+ * Send the contents of encryption_key in the form of special startup packet
+ * to a server that is being started.
+ *
+ * Returns true if we could send the message and false if not, however even
+ * success does not guarantee that server started up - caller should
+ * eventually test server connection himself.
+ *
+ * TODO Windows stuff?
+ */
+bool
+send_key_to_postmaster(const char *host, const char *port,
+					   const unsigned char *encryption_Key)
+{
+	const char **keywords = pg_malloc0(3 * sizeof(*keywords));
+	const char **values = pg_malloc0(3 * sizeof(*values));
+	int	i;
+	PGconn *conn = NULL;
+	int	sock = -1;
+	EncryptionKeyMsg	message;
+	int	msg_size, packet_size;
+	char	*packet = NULL;
+	bool	res = true;
+
+/* How many seconds we can wait for the postmaster to receive the key. */
+#define SEND_ENCRYPT_KEY_TIMEOUT	60
+
+	if (host)
+	{
+		keywords[0] = "host";
+		values[0] = host;
+	}
+	keywords[1] = "port";
+	values[1] = port;
+
+	/* Compose the message. */
+	message.encryptionKeyCode = pg_hton32(ENCRYPTION_KEY_MSG_CODE);
+	message.version = 1;
+	memcpy(message.data, encryption_key, ENCRYPTION_KEY_LENGTH);
+	msg_size = offsetof(EncryptionKeyMsg, data) + ENCRYPTION_KEY_LENGTH;
+
+	packet_size = msg_size + 4;
+	packet = (char *) palloc(packet_size);
+	*((int32 *) packet) = pg_hton32(packet_size);
+	memcpy(packet + 4, &message, msg_size);
+
+	/*
+	 * Although we don't expect the server to accept regular libpq messages,
+	 * we try to get at least a valid socket.
+	 */
+	for (i = 0; i < SEND_ENCRYPT_KEY_TIMEOUT + 1; i++)
+	{
+		if (i > 0)
+			/* Sleep for 1 second. */
+			pg_usleep(1000000L);
+
+		if (conn)
+		{
+			PQfinish(conn);
+			conn = NULL;
+		}
+
+		conn = PQconnectStartParams(keywords, values, false);
+		if (conn == NULL)
+			continue;
+
+		sock = PQsocket(conn);
+		/* Cannot send the key if there's no valid socket. */
+		if (sock == -1)
+			continue;
+
+		/* Non-blocking write would only make this simple case tricky. */
+		if (!pg_set_block(sock))
+			continue;
+
+	retry:
+		/*
+		 * Send the packet. Here we need to use low level API because the server
+		 * does is not fully up so libpq cannot be used properly.
+		 */
+		if (send(sock, (char *) packet, packet_size, 0) != packet_size)
+		{
+			if (SOCK_ERRNO == EINTR)
+				/* Interrupted system call - we'll just try again */
+				goto retry;
+			continue;
+		}
+		else
+			/* Success */
+			break;
+	}
+
+	pg_free(keywords);
+	pg_free(values);
+	if (conn)
+		PQfinish(conn);
+	pfree(packet);
+
+	return res;
+}
diff --git a/src/include/fe_utils/encryption.h b/src/include/fe_utils/encryption.h
index 69e3b2f756..7307557ed6 100644
--- a/src/include/fe_utils/encryption.h
+++ b/src/include/fe_utils/encryption.h
@@ -16,3 +16,5 @@
 extern char *encryption_key_command;
 
 extern void run_encryption_key_command(unsigned char *encryption_key);
+extern bool send_key_to_postmaster(const char *host, const char *port,
+								   const unsigned char *encryption_Key);
diff --git a/src/include/libpq/pqcomm.h b/src/include/libpq/pqcomm.h
index baf6a4b6c0..09822801c7 100644
--- a/src/include/libpq/pqcomm.h
+++ b/src/include/libpq/pqcomm.h
@@ -23,6 +23,8 @@
 #endif
 #include <netinet/in.h>
 
+#include "storage/encryption.h"
+
 #ifdef HAVE_STRUCT_SOCKADDR_STORAGE
 
 #ifndef HAVE_STRUCT_SOCKADDR_STORAGE_SS_FAMILY
@@ -205,4 +207,26 @@ typedef struct CancelRequestPacket
 #define NEGOTIATE_SSL_CODE PG_PROTOCOL(1234,5679)
 #define NEGOTIATE_GSS_CODE PG_PROTOCOL(1234,5680)
 
+/* Client can also send cluster encryption key to the postmaster. */
+#define ENCRYPTION_KEY_MSG_CODE PG_PROTOCOL(1234,5681)
+
+/*
+ * Message containing the encryption key, received by postmaster during
+ * startup.
+ *
+ * TODO Consider adding extension identifier and key_length field so that
+ * postmaster can also receove extension-specific keys. Extension that needs a
+ * key would call a function from _PG_init() that tells postmaster where to
+ * store the key and how long the key is. (Should also postgres in standalone
+ * mode accept keys for extensions?)
+ */
+typedef struct EncryptionKeyMsg
+{
+	/* Note this integer field is stored in network byte order! */
+	MsgType		encryptionKeyCode;	/* code to identify a key message */
+
+	unsigned char	version;	/* message format version. */
+	char	data[ENCRYPTION_KEY_LENGTH];	/* the key itself. */
+} EncryptionKeyMsg;
+
 #endif							/* PQCOMM_H */
diff --git a/src/include/storage/encryption.h b/src/include/storage/encryption.h
index 8479b76a34..4f7b96d4f3 100644
--- a/src/include/storage/encryption.h
+++ b/src/include/storage/encryption.h
@@ -60,6 +60,29 @@ typedef enum CipherKind
 /* Key to encrypt / decrypt data. */
 extern unsigned char encryption_key[];
 
+#ifndef FRONTEND
+/*
+ * Space for the encryption key in shared memory. Backend that receives the
+ * key during startup stores it here so postmaster can eventually take a local
+ * copy.
+ *
+ * We cannot protect the "initialized" field with a spinlock because the data
+ * will be accessed by postmaster, but it should not be necessary for bool
+ * type. Furthermore, synchronization is not really critical here, see
+ * processEncryptionKey().
+ */
+typedef struct ShmemEncryptionKey
+{
+	char	data[ENCRYPTION_KEY_LENGTH]; /* the key */
+	bool	initialized;				/* received the key? */
+} ShmemEncryptionKey;
+
+/*
+ * Encryption key in the shared memory.
+ */
+extern ShmemEncryptionKey *encryption_key_shmem;
+#endif							/* FRONTEND */
+
 /*
  * The encrypted data is a series of blocks of size
  * ENCRYPTION_BLOCK. Currently we use the EVP_aes_256_xts implementation. Make
@@ -86,6 +109,9 @@ extern char encryption_verification[];
 extern bool	encryption_setup_done;
 
 #ifndef FRONTEND
+extern Size EncryptionShmemSize(void);
+extern void EncryptionShmemInit(void);
+
 typedef int (*read_encryption_key_cb) (void);
 extern void read_encryption_key(read_encryption_key_cb read_char);
 #endif							/* FRONTEND */
-- 
2.13.7

