v13-0002-Add-key-management-module.patch
application/octet-stream
Filename: v13-0002-Add-key-management-module.patch
Type: application/octet-stream
Part: 2
Message:
Re: Internal key management system
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch v13-0002
Subject: Add key management module.
| File | + | − |
|---|---|---|
| src/backend/access/transam/xlog.c | 20 | 0 |
| src/backend/bootstrap/bootstrap.c | 6 | 1 |
| src/backend/crypto/kmgr.c | 457 | 0 |
| src/backend/crypto/Makefile | 18 | 0 |
| src/backend/Makefile | 1 | 1 |
| src/backend/postmaster/pgstat.c | 9 | 0 |
| src/backend/postmaster/postmaster.c | 6 | 0 |
| src/backend/replication/basebackup.c | 4 | 0 |
| src/backend/storage/ipc/ipci.c | 3 | 0 |
| src/backend/storage/lmgr/lwlocknames.txt | 1 | 0 |
| src/backend/tcop/postgres.c | 8 | 0 |
| src/backend/utils/misc/guc.c | 24 | 0 |
| src/backend/utils/misc/postgresql.conf.sample | 5 | 0 |
| src/bin/initdb/initdb.c | 31 | 3 |
| src/bin/pg_controldata/pg_controldata.c | 3 | 0 |
| src/bin/pg_resetwal/pg_resetwal.c | 2 | 0 |
| src/bin/pg_rewind/filemap.c | 8 | 0 |
| src/bin/pg_upgrade/controldata.c | 40 | 1 |
| src/bin/pg_upgrade/file.c | 77 | 0 |
| src/bin/pg_upgrade/pg_upgrade.c | 7 | 0 |
| src/bin/pg_upgrade/pg_upgrade.h | 4 | 0 |
| src/common/kmgr_utils.c | 523 | 0 |
| src/common/Makefile | 1 | 0 |
| src/include/access/xlog.h | 1 | 0 |
| src/include/catalog/pg_control.h | 3 | 0 |
| src/include/catalog/pg_proc.dat | 6 | 0 |
| src/include/common/kmgr_utils.h | 105 | 0 |
| src/include/crypto/kmgr.h | 29 | 0 |
| src/include/pgstat.h | 3 | 0 |
| src/include/utils/guc_tables.h | 1 | 0 |
| src/test/Makefile | 1 | 1 |
From 19921ed37a9e65c8ca462bd3436a70ab6b854600 Mon Sep 17 00:00:00 2001
From: Masahiko Sawada <sawada.mshk@gmail.com>
Date: Tue, 26 May 2020 13:15:41 +0900
Subject: [PATCH v13 2/3] Add key management module.
---
src/backend/Makefile | 2 +-
src/backend/access/transam/xlog.c | 20 +
src/backend/bootstrap/bootstrap.c | 7 +-
src/backend/crypto/Makefile | 18 +
src/backend/crypto/kmgr.c | 457 +++++++++++++++
src/backend/postmaster/pgstat.c | 9 +
src/backend/postmaster/postmaster.c | 6 +
src/backend/replication/basebackup.c | 4 +
src/backend/storage/ipc/ipci.c | 3 +
src/backend/storage/lmgr/lwlocknames.txt | 1 +
src/backend/tcop/postgres.c | 8 +
src/backend/utils/misc/guc.c | 24 +
src/backend/utils/misc/postgresql.conf.sample | 5 +
src/bin/initdb/initdb.c | 34 +-
src/bin/pg_controldata/pg_controldata.c | 3 +
src/bin/pg_resetwal/pg_resetwal.c | 2 +
src/bin/pg_rewind/filemap.c | 8 +
src/bin/pg_upgrade/controldata.c | 41 +-
src/bin/pg_upgrade/file.c | 77 +++
src/bin/pg_upgrade/pg_upgrade.c | 7 +
src/bin/pg_upgrade/pg_upgrade.h | 4 +
src/common/Makefile | 1 +
src/common/kmgr_utils.c | 523 ++++++++++++++++++
src/include/access/xlog.h | 1 +
src/include/catalog/pg_control.h | 3 +
src/include/catalog/pg_proc.dat | 6 +
src/include/common/kmgr_utils.h | 105 ++++
src/include/crypto/kmgr.h | 29 +
src/include/pgstat.h | 3 +
src/include/utils/guc_tables.h | 1 +
src/test/Makefile | 2 +-
31 files changed, 1407 insertions(+), 7 deletions(-)
create mode 100644 src/backend/crypto/Makefile
create mode 100644 src/backend/crypto/kmgr.c
create mode 100644 src/common/kmgr_utils.c
create mode 100644 src/include/common/kmgr_utils.h
create mode 100644 src/include/crypto/kmgr.h
diff --git a/src/backend/Makefile b/src/backend/Makefile
index 9706a95848..4ace302038 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -21,7 +21,7 @@ SUBDIRS = access bootstrap catalog parser commands executor foreign lib libpq \
main nodes optimizer partitioning port postmaster \
regex replication rewrite \
statistics storage tcop tsearch utils $(top_builddir)/src/timezone \
- jit
+ jit crypto
include $(srcdir)/common.mk
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 756b838e6a..287104ad76 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -44,6 +44,7 @@
#include "commands/tablespace.h"
#include "common/controldata_utils.h"
#include "executor/instrument.h"
+#include "crypto/kmgr.h"
#include "miscadmin.h"
#include "pg_trace.h"
#include "pgstat.h"
@@ -80,6 +81,7 @@
#include "utils/timestamp.h"
extern uint32 bootstrap_data_checksum_version;
+extern uint32 bootstrap_key_management_version;
/* Unsupported old recovery command file names (relative to $PGDATA) */
#define RECOVERY_COMMAND_FILE "recovery.conf"
@@ -4604,6 +4606,7 @@ InitControlFile(uint64 sysidentifier)
ControlFile->wal_log_hints = wal_log_hints;
ControlFile->track_commit_timestamp = track_commit_timestamp;
ControlFile->data_checksum_version = bootstrap_data_checksum_version;
+ ControlFile->key_management_version = bootstrap_key_management_version;
}
static void
@@ -4891,6 +4894,9 @@ ReadControlFile(void)
/* Make the initdb settings visible as GUC variables, too */
SetConfigOption("data_checksums", DataChecksumsEnabled() ? "yes" : "no",
PGC_INTERNAL, PGC_S_OVERRIDE);
+
+ SetConfigOption("key_management", KeyManagementEnabled() ? "yes" : "no",
+ PGC_INTERNAL, PGC_S_OVERRIDE);
}
/*
@@ -4933,6 +4939,16 @@ DataChecksumsEnabled(void)
return (ControlFile->data_checksum_version > 0);
}
+/*
+ * Are key management enabled?
+ */
+bool
+KeyManagementEnabled(void)
+{
+ Assert(ControlFile != NULL);
+ return (ControlFile->key_management_version > 0);
+}
+
/*
* Returns a fake LSN for unlogged relations.
*
@@ -5340,6 +5356,10 @@ BootStrapXLOG(void)
/* some additional ControlFile fields are set in WriteControlFile() */
WriteControlFile();
+ /* Enable key manager if required */
+ if (ControlFile->key_management_version > 0)
+ BootStrapKmgr();
+
/* Bootstrap the commit log, too */
BootStrapCLOG();
BootStrapCommitTs();
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index 45b7efbe46..883dc42908 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -28,6 +28,7 @@
#include "catalog/pg_collation.h"
#include "catalog/pg_type.h"
#include "common/link-canary.h"
+#include "crypto/kmgr.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
@@ -51,6 +52,7 @@
#include "utils/relmapper.h"
uint32 bootstrap_data_checksum_version = 0; /* No checksum */
+uint32 bootstrap_key_management_version = 0; /* disabled */
#define ALLOC(t, c) \
@@ -226,7 +228,7 @@ AuxiliaryProcessMain(int argc, char *argv[])
/* If no -x argument, we are a CheckerProcess */
MyAuxProcType = CheckerProcess;
- while ((flag = getopt(argc, argv, "B:c:d:D:Fkr:x:X:-:")) != -1)
+ while ((flag = getopt(argc, argv, "B:c:d:D:eFkr:x:X:-:")) != -1)
{
switch (flag)
{
@@ -249,6 +251,9 @@ AuxiliaryProcessMain(int argc, char *argv[])
pfree(debugstr);
}
break;
+ case 'e':
+ bootstrap_key_management_version = KMGR_VERSION;
+ break;
case 'F':
SetConfigOption("fsync", "false", PGC_POSTMASTER, PGC_S_ARGV);
break;
diff --git a/src/backend/crypto/Makefile b/src/backend/crypto/Makefile
new file mode 100644
index 0000000000..c27362029d
--- /dev/null
+++ b/src/backend/crypto/Makefile
@@ -0,0 +1,18 @@
+#-------------------------------------------------------------------------
+#
+# Makefile
+# Makefile for src/backend/crypto
+#
+# IDENTIFICATION
+# src/backend/crypto/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/backend/crypto
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+OBJS = \
+ kmgr.o
+
+include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/crypto/kmgr.c b/src/backend/crypto/kmgr.c
new file mode 100644
index 0000000000..2a37c38f1f
--- /dev/null
+++ b/src/backend/crypto/kmgr.c
@@ -0,0 +1,457 @@
+/*-------------------------------------------------------------------------
+ *
+ * kmgr.c
+ * Key manager routines
+ *
+ * Key manager is enabled if user requests during initdb. During bootstrap,
+ * we generate internal keys, wrap them with KEK which is derived from the
+ * user-provided passphrase, and store them into each file located at KMGR_DIR.
+ * Once generated, these are not changed. During startup, we decrypt all
+ * internal keys and load them to the shared memory space. Internal keys on
+ * the shared memory are read-only. All wrapping and unwrapping key routines
+ * depends on openssl library for now.
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/crypto/kmgr.c
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "funcapi.h"
+#include "miscadmin.h"
+#include "pgstat.h"
+
+#include "common/sha2.h"
+#include "common/kmgr_utils.h"
+#include "crypto/kmgr.h"
+#include "storage/fd.h"
+#include "storage/ipc.h"
+#include "storage/shmem.h"
+#include "utils/builtins.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+/* Struct stores internal keys in plaintext format */
+typedef struct KmgrShmemData
+{
+ /*
+ * Internal cryptographic keys. Keys are stored at its ID'th.
+ */
+ CryptoKey intlKeys[KMGR_MAX_INTERNAL_KEYS];
+} KmgrShmemData;
+static KmgrShmemData *KmgrShmem;
+
+/*
+ * Key lengths in bytes of internal keys.
+ *
+ * No key supported for now.
+ */
+static int internalKeyLengths[KMGR_MAX_INTERNAL_KEYS];
+
+/* GUC variables */
+bool key_management_enabled = false;;
+char *cluster_passphrase_command = NULL;
+
+static void KmgrSaveCryptoKeys(const char *dir, CryptoKey *keys);
+static CryptoKey *generate_crypto_key(int len);
+static void recoverIncompleteRotation(void);
+
+/*
+ * This function must be called ONCE on system install.
+ */
+void
+BootStrapKmgr(void)
+{
+ PgKeyWrapCtx *ctx;
+ CryptoKey keys_wrap[KMGR_MAX_INTERNAL_KEYS];
+ char passphrase[KMGR_MAX_PASSPHRASE_LEN];
+ uint8 kekenc[KMGR_ENC_KEY_LEN];
+ uint8 kekhmac[KMGR_MAC_KEY_LEN];
+ int passlen;
+
+ /*
+ * Requirement check. We need openssl library to enable key management
+ * because all encryption and decryption calls happen via openssl function
+ * calls.
+ */
+#ifndef USE_OPENSSL
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ (errmsg("cluster encryption is not supported because OpenSSL is not supported by this build"),
+ errhint("Compile with --with-openssl to use cluster encryption."))));
+#endif
+
+ memset(keys_wrap, 0, sizeof(keys_wrap));
+
+ /* Get key encryption key from the passphrase command */
+ passlen = kmgr_run_cluster_passphrase_command(cluster_passphrase_command,
+ passphrase, KMGR_MAX_PASSPHRASE_LEN);
+ if (passlen < KMGR_MIN_PASSPHRASE_LEN)
+ ereport(ERROR,
+ (errmsg("passphrase must be more than %d bytes",
+ KMGR_MIN_PASSPHRASE_LEN)));
+
+ /* Get key encryption key and HMAC key from passphrase */
+ kmgr_derive_keys(passphrase, passlen, kekenc, kekhmac);
+
+ /* Create temporarily key wrap context */
+ ctx = pg_create_keywrap_ctx(kekenc, kekhmac);
+ if (!ctx)
+ elog(ERROR, "could not initialize encryption contect");
+
+ /* Wrap all internal keys by key encryption key */
+ for (int id = 0; id < KMGR_MAX_INTERNAL_KEYS; id++)
+ {
+ CryptoKey *key;
+
+ /* generate an internal key */
+ key = generate_crypto_key(internalKeyLengths[id]);
+
+ if (!kmgr_wrap_key(ctx, key, &(keys_wrap[id])))
+ {
+ pg_free_keywrap_ctx(ctx);
+ elog(ERROR, "failed to wrap cluster encryption key");
+ }
+ }
+
+ /* Save internal keys to the disk */
+ KmgrSaveCryptoKeys(KMGR_DIR, keys_wrap);
+
+ pg_free_keywrap_ctx(ctx);
+}
+
+/* Report shared-memory space needed by KmgrShmem */
+Size
+KmgrShmemSize(void)
+{
+ if (!key_management_enabled)
+ return 0;
+
+ return MAXALIGN(sizeof(KmgrShmemData));
+}
+
+/* Allocate and initialize key manager memory */
+void
+KmgrShmemInit(void)
+{
+ bool found;
+
+ if (!key_management_enabled)
+ return;
+
+ KmgrShmem = (KmgrShmemData *) ShmemInitStruct("Key manager",
+ KmgrShmemSize(), &found);
+
+ if (!found)
+ memset(KmgrShmem, 0, KmgrShmemSize());
+}
+
+/*
+ * Get encryption key passphrase and verify it, then get the internal keys.
+ * This function is called by postmaster at startup time.
+ */
+void
+InitializeKmgr(void)
+{
+ CryptoKey *keys_wrap;
+ char passphrase[KMGR_MAX_PASSPHRASE_LEN];
+ int passlen;
+ int nkeys;
+
+ if (!key_management_enabled)
+ return;
+
+ elog(DEBUG1, "starting up key management system");
+
+ /* Recover the failure of the last passphrase rotation if necessary */
+ recoverIncompleteRotation();
+
+ /* Get the crypto keys from the file */
+ keys_wrap = kmgr_get_cryptokeys(KMGR_DIR, &nkeys);
+ Assert(nkeys == KMGR_MAX_INTERNAL_KEYS);
+
+ /* Get cluster passphrase */
+ passlen = kmgr_run_cluster_passphrase_command(cluster_passphrase_command,
+ passphrase, KMGR_MAX_PASSPHRASE_LEN);
+
+ /*
+ * Verify passphrase and prepare an internal key in plaintext on shared memory.
+ *
+ * XXX: do we need to prevent internal keys from being swapped out using
+ * mlock?
+ */
+ if (!kmgr_verify_passphrase(passphrase, passlen, keys_wrap, KmgrShmem->intlKeys,
+ KMGR_MAX_INTERNAL_KEYS))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cluster passphrase does not match expected passphrase")));
+}
+
+const CryptoKey *
+KmgrGetKey(int id)
+{
+ Assert(id < KMGR_MAX_INTERNAL_KEYS);
+
+ return (const CryptoKey *) &(KmgrShmem->intlKeys[id]);
+}
+
+/* Generate an empty CryptoKey */
+static CryptoKey *
+generate_crypto_key(int len)
+{
+ CryptoKey *newkey;
+
+ Assert(len < KMGR_MAX_KEY_LEN);
+ newkey = (CryptoKey *) palloc0(sizeof(CryptoKey));
+
+ if (!pg_strong_random(newkey->key, len))
+ elog(ERROR, "failed to generate new crypto key");
+
+ newkey->klen = len;
+
+ return newkey;
+}
+
+/*
+ * Save the given crypto keys to the disk. We don't need CRC check for crypto
+ * keys because these keys have HMAC which is used for integrity check
+ * during unwrapping.
+ */
+static void
+KmgrSaveCryptoKeys(const char *dir, CryptoKey *keys)
+{
+ elog(DEBUG2, "saving all cryptographic keys");
+
+ for (int i = 0; i < KMGR_MAX_INTERNAL_KEYS; i++)
+ {
+ int fd;
+ char path[MAXPGPATH];
+
+ CryptoKeyFilePath(path, dir, i);
+
+ if ((fd = BasicOpenFile(path, O_RDWR | O_CREAT | O_EXCL | PG_BINARY)) < 0)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not open file \"%s\": %m",
+ path)));
+
+ errno = 0;
+ pgstat_report_wait_start(WAIT_EVENT_KEY_FILE_WRITE);
+ if (write(fd, &(keys[i]), sizeof(CryptoKey)) != sizeof(CryptoKey))
+ {
+ /* if write didn't set errno, assume problem is no disk space */
+ if (errno == 0)
+ errno = ENOSPC;
+
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not write file \"%s\": %m",
+ path)));
+ }
+ pgstat_report_wait_end();
+
+ pgstat_report_wait_start(WAIT_EVENT_KEY_FILE_SYNC);
+ if (pg_fsync(fd) != 0)
+ ereport(PANIC,
+ (errcode_for_file_access(),
+ errmsg("could not fsync file \"%s\": %m",
+ path)));
+ pgstat_report_wait_end();
+
+ if (close(fd) != 0)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not close file \"%s\": %m",
+ path)));
+ }
+}
+
+
+/*
+ * Check the last passphrase rotation was completed. If not, we decide which wrapped
+ * keys will be used according to the status of temporary directory and its wrapped
+ * keys.
+ */
+static void
+recoverIncompleteRotation(void)
+{
+ struct stat st;
+ struct stat st_tmp;
+ CryptoKey *keys;
+ int nkeys_tmp;
+
+ /* The cluster passphrase rotation was completed, nothing to do */
+ if (stat(KMGR_TMP_DIR, &st_tmp) != 0)
+ return;
+
+ /*
+ * If there is only temporary directory, it means that the previous
+ * rotation failed after wrapping the all internal keys by the new
+ * passphrase. Therefore we use the new cluster passphrase.
+ */
+ if (stat(KMGR_DIR, &st) != 0)
+ {
+ ereport(DEBUG1,
+ (errmsg("there is only temporary directory, use the newly wrapped keys")));
+
+ if (rename(KMGR_TMP_DIR, KMGR_DIR) != 0)
+ ereport(ERROR,
+ errmsg("could not rename directory \"%s\" to \"%s\": %m",
+ KMGR_TMP_DIR, KMGR_DIR));
+ ereport(LOG,
+ errmsg("cryptographic keys wrapped by new passphrase command are chosen"),
+ errdetail("last cluster passphrase rotation failed in the middle"));
+ return;
+ }
+
+ /*
+ * In case where both the original directory and temporary directory
+ * exist, there are two possibilities: (a) the all internal keys are
+ * wrapped by the new passphrase but rotation failed before removing the
+ * original directory, or (b) the rotation failed during wrapping internal
+ * keys by the new passphrase. In case of (a) we need to use the wrapped
+ * keys in the temporary directory as rotation is essentially completed,
+ * but in case of (b) we use the wrapped keys in the original directory.
+ *
+ * To check the possibility of (b) we validate the wrapped keys in the
+ * temporary directory by checking the number of wrapped keys. Since the
+ * wrapped key length is smaller than one disk sector, which is 512 bytes
+ * on common hardware, saving wrapped key is atomic write. So we can
+ * ensure that the all wrapped keys are valid if the number of wrapped
+ * keys in the temporary directory is KMGR_MAX_INTERNAL_KEYS.
+ */
+ keys = kmgr_get_cryptokeys(KMGR_TMP_DIR, &nkeys_tmp);
+
+ if (nkeys_tmp == KMGR_MAX_INTERNAL_KEYS)
+ {
+ /*
+ * This is case (a), the all wrapped keys in temporary directory are
+ * valid. Remove the original directory and rename.
+ */
+ ereport(DEBUG1,
+ (errmsg("last passphrase rotation failed before renaming direcotry name, use the newly wrapped keys")));
+
+ if (!rmtree(KMGR_DIR, true))
+ ereport(ERROR,
+ (errmsg("could not remove directory \"%s\"",
+ KMGR_DIR)));
+ if (rename(KMGR_TMP_DIR, KMGR_DIR) != 0)
+ ereport(ERROR,
+ errmsg("could not rename directory \"%s\" to \"%s\": %m",
+ KMGR_TMP_DIR, KMGR_DIR));
+
+ ereport(LOG,
+ errmsg("cryptographic keys wrapped by new passphrase command are chosen"),
+ errdetail("last cluster passphrase rotation failed in the middle"));
+ }
+ else
+ {
+ /*
+ * This is case (b), the last passphrase rotation failed during
+ * wrapping keys. Remove the keys in the temporary directory and use
+ * keys in the original keys.
+ */
+ ereport(DEBUG1,
+ (errmsg("last passphrase rotation failed during wrapping keys, use the old wrapped keys")));
+
+ if (!rmtree(KMGR_TMP_DIR, true))
+ ereport(ERROR,
+ (errmsg("could not remove directory \"%s\"",
+ KMGR_DIR)));
+ ereport(LOG,
+ errmsg("cryptographic keys wrapped by old passphrase command are chosen"),
+ errdetail("last cluster passphrase rotation failed in the middle"));
+ }
+
+ pfree(keys);
+}
+
+/*
+ * SQL function to rotate the cluster passphrase. This function assumes that
+ * the cluster_passphrase_command is already reloaded to the new value.
+ * All internal keys are wrapped by the new passphrase and saved to the disk.
+ * To update all crypto keys atomically we save the newly wrapped keys to the
+ * temporary directory, pg_cryptokeys_tmp, and remove the original directory,
+ * pg_cryptokeys, and rename it. These operation is performed without the help
+ * of WAL. In the case of failure during rotationpg_cryptokeys directory and
+ * pg_cryptokeys_tmp directory can be left in incomplete status. We recover
+ * the incomplete situation by checkIncompleteRotation.
+ */
+Datum
+pg_rotate_cluster_passphrase(PG_FUNCTION_ARGS)
+{
+ PgKeyWrapCtx *ctx;
+ CryptoKey newkeys[KMGR_MAX_INTERNAL_KEYS];
+ char passphrase[KMGR_MAX_PASSPHRASE_LEN];
+ uint8 new_kekenc[KMGR_ENC_KEY_LEN];
+ uint8 new_kekhmac[KMGR_MAC_KEY_LEN];
+ int passlen;
+
+ if (!key_management_enabled)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("could not rotate cluster passphrase because key management is not supported")));
+
+ memset(newkeys, 0, sizeof(newkeys));
+
+ /* Recover the failure of the last passphrase rotation if necessary */
+ recoverIncompleteRotation();
+
+ passlen = kmgr_run_cluster_passphrase_command(cluster_passphrase_command,
+ passphrase,
+ KMGR_MAX_PASSPHRASE_LEN);
+ if (passlen < KMGR_MIN_PASSPHRASE_LEN)
+ ereport(ERROR,
+ (errmsg("passphrase must be more than %d bytes",
+ KMGR_MIN_PASSPHRASE_LEN)));
+
+ /* Get new key encryption key and encryption context */
+ kmgr_derive_keys(passphrase, passlen, new_kekenc, new_kekhmac);
+ ctx = pg_create_keywrap_ctx(new_kekenc, new_kekhmac);
+ if (!ctx)
+ elog(ERROR, "could not initialize encryption contect");
+
+ for (int id = 0; id < KMGR_MAX_INTERNAL_KEYS; id++)
+ {
+ if (!kmgr_wrap_key(ctx, &(KmgrShmem->intlKeys[id]), &(newkeys[id])))
+ elog(ERROR, "failed to wrap key");
+ }
+
+ /* Create temporary directory */
+ if (MakePGDirectory(KMGR_TMP_DIR) < 0)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not create temporary directory \"%s\": %m",
+ KMGR_TMP_DIR)));
+ fsync_fname(KMGR_TMP_DIR, true);
+
+ /* Prevent concurrent key rotation */
+ LWLockAcquire(KmgrFileLock, LW_EXCLUSIVE);
+
+ /* Save the key wrapped by the new passphrase to the temporary directory */
+ KmgrSaveCryptoKeys(KMGR_TMP_DIR, newkeys);
+
+ /* Remove the original directory */
+ if (!rmtree(KMGR_DIR, true))
+ ereport(ERROR,
+ (errmsg("could not remove directory \"%s\"",
+ KMGR_DIR)));
+
+ /* Rename to the original directory */
+ if (rename(KMGR_TMP_DIR, KMGR_DIR) != 0)
+ ereport(ERROR,
+ (errmsg("could not rename directory \"%s\" to \"%s\": %m",
+ KMGR_TMP_DIR, KMGR_DIR)));
+ fsync_fname(KMGR_DIR, true);
+
+ LWLockRelease(KmgrFileLock);
+
+ pg_free_keywrap_ctx(ctx);
+ PG_RETURN_BOOL(true);
+}
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index 15f92b66c6..9bf1abd5a5 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -3988,6 +3988,15 @@ pgstat_get_wait_io(WaitEventIO w)
case WAIT_EVENT_DSM_FILL_ZERO_WRITE:
event_name = "DSMFillZeroWrite";
break;
+ case WAIT_EVENT_KEY_FILE_READ:
+ event_name = "KeyFileRead";
+ break;
+ case WAIT_EVENT_KEY_FILE_WRITE:
+ event_name = "KeyFileWrite";
+ break;
+ case WAIT_EVENT_KEY_FILE_SYNC:
+ event_name = "KeyFileSync";
+ break;
case WAIT_EVENT_LOCK_FILE_ADDTODATADIR_READ:
event_name = "LockFileAddToDataDirRead";
break;
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 5b5fc97c72..81f319f7c6 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -100,6 +100,7 @@
#include "common/file_perm.h"
#include "common/ip.h"
#include "common/string.h"
+#include "crypto/kmgr.h"
#include "lib/ilist.h"
#include "libpq/auth.h"
#include "libpq/libpq.h"
@@ -1335,6 +1336,11 @@ PostmasterMain(int argc, char *argv[])
*/
autovac_init();
+ /*
+ * Initialize key manager.
+ */
+ InitializeKmgr();
+
/*
* Load configuration files for client authentication.
*/
diff --git a/src/backend/replication/basebackup.c b/src/backend/replication/basebackup.c
index 6064384e32..155e780aff 100644
--- a/src/backend/replication/basebackup.c
+++ b/src/backend/replication/basebackup.c
@@ -18,6 +18,7 @@
#include "access/xlog_internal.h" /* for pg_start/stop_backup */
#include "catalog/pg_type.h"
+#include "common/kmgr_utils.h"
#include "common/file_perm.h"
#include "commands/progress.h"
#include "lib/stringinfo.h"
@@ -152,6 +153,9 @@ struct exclude_list_item
*/
static const char *const excludeDirContents[] =
{
+ /* Skip temporary crypto key files */
+ KMGR_TMP_DIR,
+
/*
* Skip temporary statistics files. PG_STAT_TMP_DIR must be skipped even
* when stats_temp_directory is set because PGSS_TEXT_FILE is always
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index e850ebd131..6129ae6340 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -23,6 +23,7 @@
#include "access/syncscan.h"
#include "access/twophase.h"
#include "commands/async.h"
+#include "crypto/kmgr.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
@@ -148,6 +149,7 @@ CreateSharedMemoryAndSemaphores(void)
size = add_size(size, BTreeShmemSize());
size = add_size(size, SyncScanShmemSize());
size = add_size(size, AsyncShmemSize());
+ size = add_size(size, KmgrShmemSize());
#ifdef EXEC_BACKEND
size = add_size(size, ShmemBackendArraySize());
#endif
@@ -264,6 +266,7 @@ CreateSharedMemoryAndSemaphores(void)
BTreeShmemInit();
SyncScanShmemInit();
AsyncShmemInit();
+ KmgrShmemInit();
#ifdef EXEC_BACKEND
diff --git a/src/backend/storage/lmgr/lwlocknames.txt b/src/backend/storage/lmgr/lwlocknames.txt
index e6985e8eed..48bf2d4742 100644
--- a/src/backend/storage/lmgr/lwlocknames.txt
+++ b/src/backend/storage/lmgr/lwlocknames.txt
@@ -50,3 +50,4 @@ MultiXactTruncationLock 41
OldSnapshotTimeMapLock 42
LogicalRepWorkerLock 43
XactTruncationLock 44
+KmgrFileLock 45
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index c9424f167c..c23db77083 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -42,6 +42,7 @@
#include "catalog/pg_type.h"
#include "commands/async.h"
#include "commands/prepare.h"
+#include "crypto/kmgr.h"
#include "executor/spi.h"
#include "jit/jit.h"
#include "libpq/libpq.h"
@@ -3907,6 +3908,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 c20885e97b..801800d4ef 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -47,6 +47,7 @@
#include "commands/vacuum.h"
#include "commands/variable.h"
#include "common/string.h"
+#include "crypto/kmgr.h"
#include "funcapi.h"
#include "jit/jit.h"
#include "libpq/auth.h"
@@ -745,6 +746,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 */
@@ -2036,6 +2039,17 @@ static struct config_bool ConfigureNamesBool[] =
NULL, NULL, NULL
},
+ {
+ {"key_management", PGC_INTERNAL, PRESET_OPTIONS,
+ gettext_noop("Show whether key management is enabled for this cluster."),
+ NULL,
+ GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE
+ },
+ &key_management_enabled,
+ false,
+ NULL, NULL, NULL
+ },
+
/* End-of-list marker */
{
{NULL, 0, 0, NULL, NULL}, NULL, false, NULL, NULL, NULL
@@ -4382,6 +4396,16 @@ static struct config_string ConfigureNamesString[] =
NULL, NULL, NULL
},
+ {
+ {"cluster_passphrase_command", PGC_SIGHUP, ENCRYPTION,
+ gettext_noop("Command to obtain passphrase for database encryption."),
+ NULL
+ },
+ &cluster_passphrase_command,
+ "",
+ NULL, NULL, NULL
+ },
+
{
{"application_name", PGC_USERSET, LOGGING_WHAT,
gettext_noop("Sets the application name to be reported in statistics and logs."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index aa30291ea3..e7f2f29293 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -631,6 +631,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 786672b1b6..47df7c692e 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -145,6 +145,7 @@ static bool data_checksums = false;
static char *xlog_dir = NULL;
static char *str_wal_segment_size_mb = NULL;
static int wal_segment_size_mb;
+static char *cluster_passphrase = NULL;
/* internal vars */
@@ -202,6 +203,7 @@ static const char *const subdirs[] = {
"global",
"pg_wal/archive_status",
"pg_commit_ts",
+ "pg_cryptokeys",
"pg_dynshmem",
"pg_notify",
"pg_serial",
@@ -1218,6 +1220,13 @@ setup_config(void)
"password_encryption = md5");
}
+ if (cluster_passphrase)
+ {
+ snprintf(repltok, sizeof(repltok), "cluster_passphrase_command = '%s'",
+ escape_quotes(cluster_passphrase));
+ conflines = replace_token(conflines, "#cluster_passphrase_command = ''", repltok);
+ }
+
/*
* If group access has been enabled for the cluster then it makes sense to
* ensure that the log files also allow group access. Otherwise a backup
@@ -1428,14 +1437,14 @@ bootstrap_template1(void)
unsetenv("PGCLIENTENCODING");
snprintf(cmd, sizeof(cmd),
- "\"%s\" --boot -x1 -X %u %s %s %s",
+ "\"%s\" --boot -x1 -X %u %s %s %s %s",
backend_exec,
wal_segment_size_mb * (1024 * 1024),
data_checksums ? "-k" : "",
+ cluster_passphrase ? "-e" : "",
boot_options,
debug ? "-d 5" : "");
-
PG_CMD_OPEN;
for (line = bki_lines; *line != NULL; line++)
@@ -2323,6 +2332,8 @@ usage(const char *progname)
printf(_(" --wal-segsize=SIZE size of WAL segments, in megabytes\n"));
printf(_("\nLess commonly used options:\n"));
printf(_(" -d, --debug generate lots of debugging output\n"));
+ printf(_(" -c --cluster-passphrase-command=COMMAND\n"
+ " set command to obtain passphrase for key management\n"));
printf(_(" -k, --data-checksums use data page checksums\n"));
printf(_(" -L DIRECTORY where to find the input files\n"));
printf(_(" -n, --no-clean do not clean up after errors\n"));
@@ -2991,6 +3002,7 @@ main(int argc, char *argv[])
{"wal-segsize", required_argument, NULL, 12},
{"data-checksums", no_argument, NULL, 'k'},
{"allow-group-access", no_argument, NULL, 'g'},
+ {"cluster-passphrase-command", required_argument, NULL, 'c'},
{NULL, 0, NULL, 0}
};
@@ -3032,7 +3044,7 @@ main(int argc, char *argv[])
/* process command-line options */
- while ((c = getopt_long(argc, argv, "dD:E:kL:nNU:WA:sST:X:g", long_options, &option_index)) != -1)
+ while ((c = getopt_long(argc, argv, "c:dD:E:kL:nNU:WA:sST:X:g", long_options, &option_index)) != -1)
{
switch (c)
{
@@ -3114,6 +3126,9 @@ main(int argc, char *argv[])
case 9:
pwfilename = pg_strdup(optarg);
break;
+ case 'c':
+ cluster_passphrase = pg_strdup(optarg);
+ break;
case 's':
show_setting = true;
break;
@@ -3184,6 +3199,14 @@ main(int argc, char *argv[])
exit(1);
}
+#ifndef USE_OPENSSL
+ if (cluster_passphrase)
+ {
+ pg_log_error("cluster encryption is not supported because OpenSSL is not supported by this build");
+ exit(1);
+ }
+#endif
+
check_authmethod_unspecified(&authmethodlocal);
check_authmethod_unspecified(&authmethodhost);
@@ -3251,6 +3274,11 @@ main(int argc, char *argv[])
else
printf(_("Data page checksums are disabled.\n"));
+ if (cluster_passphrase)
+ printf(_("Key management system is enabled.\n"));
+ else
+ printf(_("Key management system is disabled.\n"));
+
if (pwprompt || pwfilename)
get_su_pwd();
diff --git a/src/bin/pg_controldata/pg_controldata.c b/src/bin/pg_controldata/pg_controldata.c
index e73639df74..0318127a92 100644
--- a/src/bin/pg_controldata/pg_controldata.c
+++ b/src/bin/pg_controldata/pg_controldata.c
@@ -25,6 +25,7 @@
#include "access/xlog_internal.h"
#include "catalog/pg_control.h"
#include "common/controldata_utils.h"
+#include "common/kmgr_utils.h"
#include "common/logging.h"
#include "getopt_long.h"
#include "pg_getopt.h"
@@ -334,5 +335,7 @@ main(int argc, char *argv[])
ControlFile->data_checksum_version);
printf(_("Mock authentication nonce: %s\n"),
mock_auth_nonce_str);
+ printf(_("Key management version: %u\n"),
+ ControlFile->key_management_version);
return 0;
}
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 233441837f..fb6e038e1c 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -804,6 +804,8 @@ PrintControlValues(bool guessed)
(ControlFile.float8ByVal ? _("by value") : _("by reference")));
printf(_("Data page checksum version: %u\n"),
ControlFile.data_checksum_version);
+ printf(_("Key management version: %u\n"),
+ ControlFile.key_management_version);
}
diff --git a/src/bin/pg_rewind/filemap.c b/src/bin/pg_rewind/filemap.c
index 1879257b66..63415fb141 100644
--- a/src/bin/pg_rewind/filemap.c
+++ b/src/bin/pg_rewind/filemap.c
@@ -73,6 +73,14 @@ static const char *excludeDirContents[] =
/* Contents removed on startup, see AsyncShmemInit(). */
"pg_notify",
+ /*
+ * Skip cryptographic keys. It's generally not good idea to copy the
+ * cryptographic keys from source database because these might use
+ * different cluster passphrase.
+ */
+ "pg_cryptokeys", /* defined as KMGR_DIR */
+ "pg_cryptokeys_tmp", /* defined as KMGR_TMP_DIR */
+
/*
* Old contents are loaded for possible debugging but are not required for
* normal operation, see SerialInit().
diff --git a/src/bin/pg_upgrade/controldata.c b/src/bin/pg_upgrade/controldata.c
index 00d71e3a8a..e126c7b32a 100644
--- a/src/bin/pg_upgrade/controldata.c
+++ b/src/bin/pg_upgrade/controldata.c
@@ -9,10 +9,16 @@
#include "postgres_fe.h"
+#include <dirent.h>
#include <ctype.h>
#include "pg_upgrade.h"
+#include "access/xlog_internal.h"
+#include "common/controldata_utils.h"
+#include "common/file_utils.h"
+#include "common/kmgr_utils.h"
+
/*
* get_control_data()
*
@@ -59,6 +65,7 @@ get_control_data(ClusterInfo *cluster, bool live_check)
bool got_date_is_int = false;
bool got_data_checksum_version = false;
bool got_cluster_state = false;
+ bool got_key_management_enabled = false;
char *lc_collate = NULL;
char *lc_ctype = NULL;
char *lc_monetary = NULL;
@@ -202,6 +209,13 @@ get_control_data(ClusterInfo *cluster, bool live_check)
got_data_checksum_version = true;
}
+ /* Only in <= 14 */
+ if (GET_MAJOR_VERSION(cluster->major_version) <= 1400)
+ {
+ cluster->controldata.key_management_enabled = false;
+ got_key_management_enabled = true;
+ }
+
/* we have the result of cmd in "output". so parse it line by line now */
while (fgets(bufin, sizeof(bufin), output))
{
@@ -485,6 +499,18 @@ get_control_data(ClusterInfo *cluster, bool live_check)
cluster->controldata.data_checksum_version = str2uint(p);
got_data_checksum_version = true;
}
+ else if ((p = strstr(bufin, "Key management:")) != NULL)
+ {
+ p = strchr(p, ':');
+
+ if (p == NULL || strlen(p) <= 1)
+ pg_fatal("%d: controldata retrieval problem\n", __LINE__);
+
+ p++; /* remove ':' char */
+ /* used later for contrib check */
+ cluster->controldata.key_management_enabled = strstr(p, "on") != NULL;
+ got_key_management_enabled = true;
+ }
}
pclose(output);
@@ -539,7 +565,8 @@ get_control_data(ClusterInfo *cluster, bool live_check)
!got_index || !got_toast ||
(!got_large_object &&
cluster->controldata.ctrl_ver >= LARGE_OBJECT_SIZE_PG_CONTROL_VER) ||
- !got_date_is_int || !got_data_checksum_version)
+ !got_date_is_int || !got_data_checksum_version ||
+ !got_key_management_enabled)
{
if (cluster == &old_cluster)
pg_log(PG_REPORT,
@@ -605,6 +632,10 @@ get_control_data(ClusterInfo *cluster, bool live_check)
if (!got_data_checksum_version)
pg_log(PG_REPORT, " data checksum version\n");
+ /* value added in Postgres 12 */
+ if (!got_key_management_enabled)
+ pg_log(PG_REPORT, " key management enabled\n");
+
pg_fatal("Cannot continue without required control information, terminating\n");
}
}
@@ -669,6 +700,14 @@ check_control_data(ControlData *oldctrl,
pg_fatal("old cluster uses data checksums but the new one does not\n");
else if (oldctrl->data_checksum_version != newctrl->data_checksum_version)
pg_fatal("old and new cluster pg_controldata checksum versions do not match\n");
+
+ /*
+ * We cannot upgrade if the old cluster enables the key management but
+ * the new one doesn't support because the old one might already have
+ * data encrypted by the master encryption key.
+ */
+ if (oldctrl->key_management_enabled && !newctrl->key_management_enabled)
+ pg_fatal("old cluster uses key management but the new one does not\n");
}
diff --git a/src/bin/pg_upgrade/file.c b/src/bin/pg_upgrade/file.c
index cc8a675d00..282359feed 100644
--- a/src/bin/pg_upgrade/file.c
+++ b/src/bin/pg_upgrade/file.c
@@ -11,6 +11,7 @@
#include <sys/stat.h>
#include <fcntl.h>
+#include <dirent.h>
#ifdef HAVE_COPYFILE_H
#include <copyfile.h>
#endif
@@ -21,6 +22,7 @@
#include "access/visibilitymap.h"
#include "common/file_perm.h"
+#include "common/file_utils.h"
#include "pg_upgrade.h"
#include "storage/bufpage.h"
#include "storage/checksum.h"
@@ -372,3 +374,78 @@ check_hard_link(void)
unlink(new_link_file);
}
+
+/*
+ * Copy cryptographic keys from the old cluster to the new cluster.
+ */
+void
+copy_master_encryption_key(ClusterInfo *old_cluster, ClusterInfo * new_cluster)
+{
+ DIR *dir;
+ struct dirent *de;
+ char path[MAXPGPATH];
+
+ /* We copy the crypto keys only if both clusters enable the key management */
+ if (!old_cluster->controldata.key_management_enabled ||
+ !new_cluster->controldata.key_management_enabled)
+ return;
+
+ prep_status("Copying master encryption key");
+
+ snprintf(path, MAXPGPATH, "%s/%s", old_cluster->pgdata, KMGR_DIR);
+
+ if ((dir = opendir(path)) == NULL)
+ pg_fatal("could not open directory \"%s\": %m", path);
+
+ while ((de = readdir(dir)) != NULL)
+ {
+ if (strlen(de->d_name) == 4 &&
+ strspn(de->d_name, "0123456789ABCDEF") == 4)
+ {
+ CryptoKey key;
+ char src_path[MAXPGPATH];
+ char dst_path[MAXPGPATH];
+ uint32 id;
+ int src_fd;
+ int dst_fd;
+ int len;
+
+ id = strtoul(de->d_name, NULL, 16);
+
+ snprintf(src_path, MAXPGPATH, "%s/%s/%04X",
+ old_cluster->pgdata, KMGR_DIR, id);
+ snprintf(dst_path, MAXPGPATH, "%s/%s/%04X",
+ new_cluster->pgdata, KMGR_DIR, id);
+
+ if ((src_fd = open(src_path, O_RDONLY | PG_BINARY, 0)) < 0)
+ pg_fatal("could not open file \"%s\": %m", src_path);
+
+ if ((dst_fd = open(dst_path, O_RDWR | O_CREAT | O_TRUNC | PG_BINARY,
+ pg_file_create_mode)) < 0)
+ pg_fatal("could not open file \"%s\": %m", dst_path);
+
+ /* Read the source key */
+ len = read(src_fd, &key, sizeof(CryptoKey));
+ if (len != sizeof(CryptoKey))
+ {
+ if (len < 0)
+ pg_fatal("could not read file \"%s\": %m", src_path);
+ else
+ pg_fatal("could not read file \"%s\": read %d of %zu",
+ src_path, len, sizeof(CryptoKey));
+ }
+
+ /* Write to the dest key */
+ len = write(dst_fd, &key, sizeof(CryptoKey));
+ if (len != sizeof(CryptoKey))
+ pg_fatal("could not write fie \"%s\"", dst_path);
+
+ close(src_fd);
+ close(dst_fd);
+ }
+ }
+
+ closedir(dir);
+
+ check_ok();
+}
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 70194eb096..5dd540e94f 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -157,6 +157,13 @@ main(int argc, char **argv)
transfer_all_new_tablespaces(&old_cluster.dbarr, &new_cluster.dbarr,
old_cluster.pgdata, new_cluster.pgdata);
+ /*
+ * Copy the internal encryption keys from the old cluster to the new one.
+ * This is necessary because the data in the old cluster might be
+ * encrypted with the old master encryption key.
+ */
+ copy_master_encryption_key(&old_cluster, &new_cluster);
+
/*
* Assuming OIDs are only used in system tables, there is no need to
* restore the OID counter because we have not transferred any OIDs from
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 8b90cefbe0..32ab236265 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -11,6 +11,7 @@
#include <sys/time.h>
#include "libpq-fe.h"
+#include "common/kmgr_utils.h"
/* Use port in the private/dynamic port number range */
#define DEF_PGUPORT 50432
@@ -219,6 +220,7 @@ typedef struct
bool date_is_int;
bool float8_pass_by_value;
bool data_checksum_version;
+ bool key_management_enabled;
} ControlData;
/*
@@ -375,6 +377,8 @@ void rewriteVisibilityMap(const char *fromfile, const char *tofile,
const char *schemaName, const char *relName);
void check_file_clone(void);
void check_hard_link(void);
+void copy_master_encryption_key(ClusterInfo *old_cluster,
+ ClusterInfo * new_cluster);
/* fopen_priv() is no longer different from fopen() */
#define fopen_priv(path, mode) fopen(path, mode)
diff --git a/src/common/Makefile b/src/common/Makefile
index 1a6355c0c1..7b35e6d80b 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -61,6 +61,7 @@ OBJS_COMMON = \
ip.o \
jsonapi.o \
keywords.o \
+ kmgr_utils.o \
kwlookup.o \
link-canary.o \
md5.o \
diff --git a/src/common/kmgr_utils.c b/src/common/kmgr_utils.c
new file mode 100644
index 0000000000..2359806cf2
--- /dev/null
+++ b/src/common/kmgr_utils.c
@@ -0,0 +1,523 @@
+/*-------------------------------------------------------------------------
+ *
+ * kmgr_utils.c
+ * Shared frontend/backend for cryptographic key management
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/common/kmgr_utils.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include <unistd.h>
+#include <sys/stat.h>
+
+#ifdef FRONTEND
+#include "common/logging.h"
+#endif
+#include "common/file_perm.h"
+#include "common/kmgr_utils.h"
+#include "common/sha2.h"
+#include "crypto/kmgr.h"
+#include "utils/elog.h"
+#include "storage/fd.h"
+
+#ifndef FRONTEND
+#include "pgstat.h"
+#include "storage/fd.h"
+#endif
+
+#define KMGR_PROMPT_MSG "Enter database encryption pass phrase:"
+
+#ifdef FRONTEND
+static FILE *open_pipe_stream(const char *command);
+static int close_pipe_stream(FILE *file);
+#endif
+
+static void read_one_keyfile(const char *dataDir, uint32 id, CryptoKey *key_p);
+
+/* Return a key wrap context initialized with the given keys */
+PgKeyWrapCtx *
+pg_create_keywrap_ctx(uint8 key[KMGR_ENC_KEY_LEN], uint8 mackey[KMGR_MAC_KEY_LEN])
+{
+ PgKeyWrapCtx *ctx;
+
+ ctx = (PgKeyWrapCtx *) palloc0(sizeof(PgKeyWrapCtx));
+
+ /* Create and initialize a cipher context */
+ ctx->cipherctx = pg_cipher_ctx_create(PG_CIPHER_AES_CBC, key, KMGR_ENC_KEY_LEN);
+ if (ctx->cipherctx == NULL)
+ return NULL;
+
+ /* Set encryption key and MAC key */
+ memcpy(ctx->key, key, KMGR_ENC_KEY_LEN);
+ memcpy(ctx->mackey, mackey, KMGR_MAC_KEY_LEN);
+
+ return ctx;
+}
+
+/* Free the key wrap context */
+void
+pg_free_keywrap_ctx(PgKeyWrapCtx *ctx)
+{
+ if (!ctx)
+ return;
+
+ Assert(ctx->cipherctx);
+
+ pg_cipher_ctx_free(ctx->cipherctx);
+
+#ifndef FRONTEND
+ pfree(ctx);
+#else
+ pg_free(ctx);
+#endif
+}
+
+/*
+ * Encrypt the given data. Return true and set encrypted data to 'out' if
+ * success. Otherwise return false. The caller must allocate sufficient space
+ * for cipher data calculated by using KmgrSizeOfCipherText(). Please note that
+ * this function modifies 'out' data even on failure case.
+ */
+bool
+kmgr_wrap_key(PgKeyWrapCtx *ctx, CryptoKey *in, CryptoKey *out)
+{
+ uint8 *hmac;
+ uint8 *iv;
+ uint8 *enc;
+ int enclen;
+
+ Assert(ctx && in && out);
+
+ hmac = out->key;
+ iv = hmac + KMGR_HMAC_LEN;
+ enc = iv + PG_AES_IV_SIZE;
+
+ /* Generate IV */
+ if (!pg_strong_random(iv, PG_AES_IV_SIZE))
+ return false;
+
+ if (!pg_cipher_encrypt(ctx->cipherctx, in->key, in->klen, enc, &enclen, iv))
+ return false;
+
+ if (!pg_HMAC_SHA512(ctx->mackey, enc, enclen, hmac))
+ return false;
+
+ out->klen = KmgrSizeOfCipherText(in->klen);;
+ Assert(out->klen == KMGR_HMAC_LEN + PG_AES_IV_SIZE + enclen);
+
+ return true;
+}
+
+/*
+ * Decrypt the given Data. Return true and set plain text data to `out` if
+ * success. Otherwise return false. The caller must allocate sufficient space
+ * for cipher data calculated by using KmgrSizeOfPlainText(). Please note that
+ * this function modifies 'out' data even on failure case.
+ */
+bool
+kmgr_unwrap_key(PgKeyWrapCtx *ctx, CryptoKey *in, CryptoKey *out)
+{
+ uint8 hmac[KMGR_HMAC_LEN];
+ uint8 *expected_hmac;
+ uint8 *iv;
+ uint8 *enc;
+ int enclen;
+
+ Assert(ctx && in && out);
+
+ expected_hmac = in->key;
+ iv = expected_hmac + KMGR_HMAC_LEN;
+ enc = iv + PG_AES_IV_SIZE;
+ enclen = in->klen - (enc - in->key);
+
+ /* Verify the correctness of HMAC */
+ if (!pg_HMAC_SHA512(ctx->mackey, enc, enclen, hmac))
+ return false;
+
+ if (memcmp(hmac, expected_hmac, KMGR_HMAC_LEN) != 0)
+ return false;
+
+ /* Decrypt encrypted data */
+ if (!pg_cipher_decrypt(ctx->cipherctx, enc, enclen, out->key, &(out->klen), iv))
+ return false;
+
+ return true;
+}
+
+/*
+ * Verify the correctness of the given passphrase by unwrapping the given keys.
+ * If the given passphrase is correct we set unwrapped keys to keys_out and return
+ * true. Otherwise return false. Please note that this function changes the
+ * contents of keys_out even on failure. Both keys_in and keys_out must be the
+ * same length, nkey.
+ */
+bool
+kmgr_verify_passphrase(char *passphrase, int passlen,
+ CryptoKey *keys_in, CryptoKey *keys_out, int nkeys)
+{
+ PgKeyWrapCtx *tmpctx;
+ uint8 user_enckey[KMGR_ENC_KEY_LEN];
+ uint8 user_hmackey[KMGR_MAC_KEY_LEN];
+
+ /*
+ * Create temporary wrap context with encryption key and HMAC key extracted
+ * from the passphrase.
+ */
+ kmgr_derive_keys(passphrase, passlen, user_enckey, user_hmackey);
+ tmpctx = pg_create_keywrap_ctx(user_enckey, user_hmackey);
+
+ for (int i = 0; i < nkeys; i++)
+ {
+
+ if (!kmgr_unwrap_key(tmpctx, &(keys_in[i]), &(keys_out[i])))
+ {
+ /* The passphrase is not correct */
+ pg_free_keywrap_ctx(tmpctx);
+ return false;
+ }
+ }
+
+ /* The passphrase is correct, free the cipher context */
+ pg_free_keywrap_ctx(tmpctx);
+
+ return true;
+}
+
+/* Generate encryption key and mac key from given passphrase */
+void
+kmgr_derive_keys(char *passphrase, Size passlen,
+ uint8 enckey[KMGR_ENC_KEY_LEN],
+ uint8 mackey[KMGR_MAC_KEY_LEN])
+{
+ pg_sha256_ctx ctx1;
+ pg_sha512_ctx ctx2;
+
+ StaticAssertStmt(KMGR_ENC_KEY_LEN == PG_AES256_KEY_LEN,
+ "derived encryption key size does not match AES256 key size");
+ StaticAssertStmt(KMGR_MAC_KEY_LEN == PG_HMAC_SHA512_KEY_LEN,
+ "derived mac key size does not match HMAC-SHA512 key size");
+
+ /* Generate encryption key from passphrase */
+ pg_sha256_init(&ctx1);
+ pg_sha256_update(&ctx1, (const uint8 *) passphrase, passlen);
+ pg_sha256_final(&ctx1, enckey);
+
+ /* Generate mac key from passphrase */
+ pg_sha512_init(&ctx2);
+ pg_sha512_update(&ctx2, (const uint8 *) passphrase, passlen);
+ pg_sha512_final(&ctx2, mackey);
+}
+
+/*
+ * Run cluster passphrase command.
+ *
+ * prompt will be substituted for %p.
+ *
+ * The result will be put in buffer buf, which is of size size.
+ * The return value is the length of the actual result.
+ */
+int
+kmgr_run_cluster_passphrase_command(char *passphrase_command, char *buf,
+ int size)
+{
+ char command[MAXPGPATH];
+ char *p;
+ char *dp;
+ char *endp;
+ FILE *fh;
+ int pclose_rc;
+ size_t len = 0;
+
+ Assert(size > 0);
+ buf[0] = '\0';
+
+ dp = command;
+ endp = command + MAXPGPATH - 1;
+ *endp = '\0';
+
+ for (p = passphrase_command; *p; p++)
+ {
+ if (p[0] == '%')
+ {
+ switch (p[1])
+ {
+ case 'p':
+ StrNCpy(dp, KMGR_PROMPT_MSG, strlen(KMGR_PROMPT_MSG));
+ dp += strlen(KMGR_PROMPT_MSG);
+ p++;
+ break;
+ case '%':
+ p++;
+ if (dp < endp)
+ *dp++ = *p;
+ break;
+ default:
+ if (dp < endp)
+ *dp++ = *p;
+ break;
+ }
+ }
+ else
+ {
+ if (dp < endp)
+ *dp++ = *p;
+ }
+ }
+ *dp = '\0';
+
+#ifdef FRONTEND
+ fh = open_pipe_stream(command);
+ if (fh == NULL)
+ {
+ pg_log_fatal("could not execute command \"%s\": %m",
+ command);
+ exit(EXIT_FAILURE);
+ }
+#else
+ fh = OpenPipeStream(command, "r");
+ if (fh == NULL)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not execute command \"%s\": %m",
+ command)));
+#endif
+
+ if ((len = fread(buf, sizeof(char), size, fh)) < size)
+ {
+ if (ferror(fh))
+ {
+#ifdef FRONTEND
+ pg_log_fatal("could not read from command \"%s\": %m",
+ command);
+ exit(EXIT_FAILURE);
+#else
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not read from command \"%s\": %m",
+ command)));
+#endif
+ }
+ }
+
+#ifdef FRONTEND
+ pclose_rc = close_pipe_stream(fh);
+#else
+ pclose_rc = ClosePipeStream(fh);
+#endif
+
+ if (pclose_rc == -1)
+ {
+#ifdef FRONTEND
+ pg_log_fatal("could not close pipe to external command: %m");
+ exit(EXIT_FAILURE);
+#else
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not close pipe to external command: %m")));
+#endif
+ }
+ else if (pclose_rc != 0)
+ {
+#ifdef FRONTEND
+ pg_log_fatal("command \"%s\" failed", command);
+ exit(EXIT_FAILURE);
+#else
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("command \"%s\" failed",
+ command),
+ errdetail_internal("%s", wait_result_to_str(pclose_rc))));
+#endif
+ }
+
+ return len;
+}
+
+#ifdef FRONTEND
+static FILE *
+open_pipe_stream(const char *command)
+{
+ FILE *res;
+
+#ifdef WIN32
+ size_t cmdlen = strlen(command);
+ char *buf;
+ int save_errno;
+
+ buf = malloc(cmdlen + 2 + 1);
+ if (buf == NULL)
+ {
+ errno = ENOMEM;
+ return NULL;
+ }
+ buf[0] = '"';
+ mempcy(&buf[1], command, cmdlen);
+ buf[cmdlen + 1] = '"';
+ buf[cmdlen + 2] = '\0';
+
+ res = _popen(buf, "r");
+
+ save_errno = errno;
+ free(buf);
+ errno = save_errno;
+#else
+ res = popen(command, "r");
+#endif /* WIN32 */
+ return res;
+}
+
+static int
+close_pipe_stream(FILE *file)
+{
+#ifdef WIN32
+ return _pclose(file);
+#else
+ return pclose(file);
+#endif /* WIN32 */
+}
+#endif /* FRONTEND */
+
+CryptoKey *
+kmgr_get_cryptokeys(const char *path, int *nkeys)
+{
+ struct dirent *de;
+ DIR *dir;
+ CryptoKey *keys;
+
+#ifndef FRONTEND
+ if ((dir = AllocateDir(path)) == NULL)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not open directory \"%s\": %m",
+ path)));
+#else
+ if ((dir = opendir(path)) == NULL)
+ pg_log_fatal("could not open directory \"%s\": %m", path);
+#endif
+
+ keys = (CryptoKey *) palloc0(sizeof(CryptoKey) * KMGR_MAX_INTERNAL_KEYS);
+ *nkeys = 0;
+
+#ifndef FRONTEND
+ while ((de = ReadDir(dir, KMGR_DIR)) != NULL)
+#else
+ while ((de = readdir(dir)) != NULL)
+#endif
+ {
+ if (strlen(de->d_name) == 4 &&
+ strspn(de->d_name, "0123456789ABCDEF") == 4)
+ {
+ uint32 id;
+
+ id = strtoul(de->d_name, NULL, 16);
+
+ if (id < 0 || id >= KMGR_MAX_INTERNAL_KEYS)
+ {
+#ifndef FRONTEND
+ elog(ERROR, "invalid cryptographic key identifier %u", id);
+#else
+ pg_log_fatal("invalid cryptographic key identifier %u", id);
+#endif
+ }
+
+ if (*nkeys >= KMGR_MAX_INTERNAL_KEYS)
+ {
+#ifndef FRONTEND
+ elog(ERROR, "too many cryptographic kes");
+#else
+ pg_log_fatal("too many cryptographic keys");
+#endif
+ }
+
+ read_one_keyfile(path, id, &(keys[id]));
+ (*nkeys)++;
+ }
+ }
+
+#ifndef FRONTEND
+ FreeDir(dir);
+#else
+ closedir(dir);
+#endif
+
+ return keys;
+}
+
+static void
+read_one_keyfile(const char *cryptoKeyDir, uint32 id, CryptoKey *key_p)
+{
+ char path[MAXPGPATH];
+ int fd;
+ int r;
+
+ CryptoKeyFilePath(path, cryptoKeyDir, id);
+
+#ifndef FRONTEND
+ if ((fd = OpenTransientFile(path, O_RDONLY | PG_BINARY)) == -1)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not open file \"%s\" for reading: %m",
+ path)));
+#else
+ if ((fd = open(path, O_RDONLY | PG_BINARY, 0)) == -1)
+ pg_log_fatal("could not open file \"%s\" for reading: %m",
+ path);
+#endif
+
+#ifndef FRONTEND
+ pgstat_report_wait_start(WAIT_EVENT_KEY_FILE_READ);
+#endif
+
+ /* Get key bytes */
+ r = read(fd, key_p, sizeof(CryptoKey));
+ if (r != sizeof(CryptoKey))
+ {
+ if (r < 0)
+ {
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not read file \"%s\": %m", path)));
+#else
+ pg_log_fatal("could not read file \"%s\": %m", path);
+#endif
+ }
+ else
+ {
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_DATA_CORRUPTED),
+ errmsg("could not read file \"%s\": read %d of %zu",
+ path, r, sizeof(CryptoKey))));
+#else
+ pg_log_fatal("could not read file \"%s\": read %d of %zu",
+ path, r, sizeof(CryptoKey));
+#endif
+ }
+ }
+
+#ifndef FRONTEND
+ pgstat_report_wait_end();
+#endif
+
+#ifndef FRONTEND
+ if (CloseTransientFile(fd) != 0)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not close file \"%s\": %m",
+ path)));
+#else
+ if (close(fd) != 0)
+ pg_log_fatal("could not close file \"%s\": %m", path);
+#endif
+}
diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h
index 221af87e71..33f28c7b1c 100644
--- a/src/include/access/xlog.h
+++ b/src/include/access/xlog.h
@@ -319,6 +319,7 @@ extern void UpdateControlFile(void);
extern uint64 GetSystemIdentifier(void);
extern char *GetMockAuthenticationNonce(void);
extern bool DataChecksumsEnabled(void);
+extern bool KeyManagementEnabled(void);
extern XLogRecPtr GetFakeLSNForUnloggedRel(void);
extern Size XLOGShmemSize(void);
extern void XLOGShmemInit(void);
diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h
index de5670e538..91aafff91c 100644
--- a/src/include/catalog/pg_control.h
+++ b/src/include/catalog/pg_control.h
@@ -226,6 +226,9 @@ typedef struct ControlFileData
*/
char mock_authentication_nonce[MOCK_AUTH_NONCE_LEN];
+ /* Key management cipher. Zero if no version */
+ uint32 key_management_version;
+
/* CRC of all above ... MUST BE LAST! */
pg_crc32c crc;
} ControlFileData;
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 082a11f270..0e0d995dcc 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10954,4 +10954,10 @@
proname => 'is_normalized', prorettype => 'bool', proargtypes => 'text text',
prosrc => 'unicode_is_normalized' },
+# function for key managements
+{ oid => '8200', descr => 'rotate cluter passphrase',
+ proname => 'pg_rotate_cluster_passphrase',
+ provolatile => 'v', prorettype => 'bool',
+ proargtypes => '', prosrc => 'pg_rotate_cluster_passphrase' },
+
]
diff --git a/src/include/common/kmgr_utils.h b/src/include/common/kmgr_utils.h
new file mode 100644
index 0000000000..1dc8f43d02
--- /dev/null
+++ b/src/include/common/kmgr_utils.h
@@ -0,0 +1,105 @@
+/*-------------------------------------------------------------------------
+ *
+ * kmgr_utils.h
+ * Declarations for utility function for key management
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * src/include/common/kmgr_utils.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef KMGR_UTILS_H
+#define KMGR_UTILS_H
+
+#include "common/cipher.h"
+
+/* Current version number */
+#define KMGR_VERSION 1
+
+/*
+ * Directory where cryptographic keys reside within PGDATA. KMGR_DIR_TMP
+ * is used during cluster passphrase rotation.
+ */
+#define KMGR_DIR "pg_cryptokeys"
+#define KMGR_TMP_DIR "pg_cryptokeys_tmp"
+
+/*
+ * Identifiers of internal keys. When adding a new internal key, we
+ * also need to add its key length to internalKeyLengths.
+ */
+/* #define KMGR_XXX_KEY_ID 0 */
+#define KMGR_MAX_INTERNAL_KEYS 0
+
+/* Encryption key and MAC key used for key wrapping */
+#define KMGR_ENC_KEY_LEN PG_AES256_KEY_LEN
+#define KMGR_MAC_KEY_LEN PG_HMAC_SHA512_KEY_LEN
+#define KMGR_HMAC_LEN PG_HMAC_SHA512_LEN
+
+/* Key wrapping key consists of encryption key and mac key */
+#define KMGR_KEY_LEN (PG_AEAD_ENC_KEY_LEN + PG_AEAD_MAC_KEY_LEN)
+
+/* Allowed length of cluster passphrase */
+#define KMGR_MIN_PASSPHRASE_LEN 64
+#define KMGR_MAX_PASSPHRASE_LEN 1024
+
+/* Maximum length of key the key manager can store */
+#define KMGR_MAX_KEY_LEN 128
+#define KMGR_MAX_WRAPPED_KEY_LEN KmgrSizeOfCipherText(KMGR_MAX_KEY_LEN)
+
+/*
+ * Size of encrypted key size with padding. We use PKCS#7 padding,
+ * described in RFC 5652.
+ */
+#define SizeOfDataWithPadding(klen) \
+ ((int)(klen) + (PG_AES_BLOCK_SIZE - ((int)(klen) % PG_AES_BLOCK_SIZE)))
+
+/* Macros to compute the size of cipher text and plain text */
+#define KmgrSizeOfCipherText(len) \
+ (KMGR_MAC_KEY_LEN + PG_AES_IV_SIZE + SizeOfDataWithPadding((int)(len)))
+#define KmgrSizeOfPlainText(klen) \
+ ((int)(klen) - (KMGR_MAC_KEY_LEN + PG_AES_IV_SIZE))
+
+/* CryptoKey file name is keys id */
+#define CryptoKeyFilePath(path, dir, id) \
+ snprintf((path), MAXPGPATH, "%s/%04X", (dir), (id))
+
+/*
+ * Cryptographic key data structure. This structure is used for
+ * both on-disk (raw key) and on-memory (wrapped key).
+ */
+typedef struct CryptoKey
+{
+ int klen;
+ uint8 key[KMGR_MAX_WRAPPED_KEY_LEN];
+} CryptoKey;
+
+/* Key wrapping cipher context */
+typedef struct PgKeyWrapCtx
+{
+ uint8 key[KMGR_ENC_KEY_LEN];
+ uint8 mackey[KMGR_MAC_KEY_LEN];
+ PgCipherCtx *cipherctx;
+} PgKeyWrapCtx;
+
+extern PgKeyWrapCtx *pg_create_keywrap_ctx(uint8 key[KMGR_ENC_KEY_LEN],
+ uint8 mackey[KMGR_MAC_KEY_LEN]);
+extern void pg_free_keywrap_ctx(PgKeyWrapCtx *ctx);
+extern bool kmgr_wrap_key(PgKeyWrapCtx *ctx, CryptoKey *in, CryptoKey *out);
+extern bool kmgr_unwrap_key(PgKeyWrapCtx *ctx, CryptoKey *in, CryptoKey *out);
+
+
+
+extern void kmgr_derive_keys(char *passphrase, Size passlen,
+ uint8 enckey[KMGR_ENC_KEY_LEN],
+ uint8 mackey[KMGR_MAC_KEY_LEN]);
+extern bool kmgr_verify_passphrase(char *passphrase, int passlen,
+ CryptoKey *keys_in, CryptoKey *keys_out,
+ int nkey);
+extern bool kmgr_wrap_key(PgKeyWrapCtx *ctx, CryptoKey *in, CryptoKey *out);
+extern bool kmgr_unwrap_key(PgKeyWrapCtx *ctx, CryptoKey *in, CryptoKey *out);
+extern int kmgr_run_cluster_passphrase_command(char *passphrase_command,
+ char *buf, int size);
+extern CryptoKey *kmgr_get_cryptokeys(const char *path, int *nkeys);
+
+#endif /* KMGR_UTILS_H */
diff --git a/src/include/crypto/kmgr.h b/src/include/crypto/kmgr.h
new file mode 100644
index 0000000000..783f06d4c2
--- /dev/null
+++ b/src/include/crypto/kmgr.h
@@ -0,0 +1,29 @@
+/*-------------------------------------------------------------------------
+ *
+ * kmgr.h
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * src/include/crypto/kmgr.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef KMGR_H
+#define KMGR_H
+
+#include "common/cipher.h"
+#include "common/kmgr_utils.h"
+#include "storage/relfilenode.h"
+#include "storage/bufpage.h"
+
+/* GUC parameters */
+extern bool key_management_enabled;
+extern char *cluster_passphrase_command;
+
+extern Size KmgrShmemSize(void);
+extern void KmgrShmemInit(void);
+extern void BootStrapKmgr(void);
+extern void InitializeKmgr(void);
+extern const CryptoKey *KmgrGetKey(int id);
+
+#endif /* KMGR_H */
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 1387201382..2ac3a3e96c 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -932,6 +932,9 @@ typedef enum
WAIT_EVENT_DATA_FILE_TRUNCATE,
WAIT_EVENT_DATA_FILE_WRITE,
WAIT_EVENT_DSM_FILL_ZERO_WRITE,
+ WAIT_EVENT_KEY_FILE_READ,
+ WAIT_EVENT_KEY_FILE_WRITE,
+ WAIT_EVENT_KEY_FILE_SYNC,
WAIT_EVENT_LOCK_FILE_ADDTODATADIR_READ,
WAIT_EVENT_LOCK_FILE_ADDTODATADIR_SYNC,
WAIT_EVENT_LOCK_FILE_ADDTODATADIR_WRITE,
diff --git a/src/include/utils/guc_tables.h b/src/include/utils/guc_tables.h
index 04431d0eb2..a0e582a483 100644
--- a/src/include/utils/guc_tables.h
+++ b/src/include/utils/guc_tables.h
@@ -89,6 +89,7 @@ enum config_group
STATS,
STATS_MONITORING,
STATS_COLLECTOR,
+ ENCRYPTION,
AUTOVACUUM,
CLIENT_CONN,
CLIENT_CONN_STATEMENT,
diff --git a/src/test/Makefile b/src/test/Makefile
index efb206aa75..5276c4184f 100644
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -29,7 +29,7 @@ endif
endif
ifeq ($(with_openssl),yes)
ifneq (,$(filter ssl,$(PG_TEST_EXTRA)))
-SUBDIRS += ssl
+SUBDIRS += ssl crypto
endif
endif
--
2.23.0