diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
new file mode 100644
index f810789..017f75b
*** a/doc/src/sgml/config.sgml
--- b/doc/src/sgml/config.sgml
*************** COPY postgres_log FROM '/full/path/to/lo
*** 7815,7820 ****
--- 7815,7858 ----
+
+ Cluster File Encryption
+
+
+
+ cluster_passphrase_command (string)
+
+ cluster_passphrase_command configuration parameter
+
+
+
+
+ This option specifies an external command to obtain the
+ passphrase for cluster file encryption.
+
+
+ The command must print the passphrase to the standard
+ output and exit with code 0. In the parameter value,
+ %p is replaced by a prompt string, and
+ %R represents the file descriptor number opened
+ to the terminal that started the server, if enabled at server start.
+ Since it can be blank, it is wise for it to be the
+ last command-line argument specified. (Write %%
+ for a literal %.) Note that the prompt string
+ will probably contain whitespace, so be sure to quote its use
+ adequately. A single newline is stripped from the end of the
+ output if present. The passphrase must be at least 64 bytes.
+
+
+ This parameter can only be set in the
+ postgresql.conf file or on the server
+ command line.
+
+
+
+
+
+
Client Connection Defaults
*************** dynamic_library_path = 'C:\tools\postgre
*** 9658,9663 ****
--- 9696,9717 ----
+
+
+ file_encryption_keylen (boolean)
+
+ Cluster file encryption key length
+
+
+
+
+ Reports the bit length of the cluster file
+ encryption key, or zero if disabled. See for more
+ information.
+
+
+
data_directory_mode (integer)
diff --git a/doc/src/sgml/database-encryption.sgml b/doc/src/sgml/database-encryption.sgml
new file mode 100644
index ...3f6659f
*** a/doc/src/sgml/database-encryption.sgml
--- b/doc/src/sgml/database-encryption.sgml
***************
*** 0 ****
--- 1,159 ----
+
+
+
+ Cluster File Encryption
+
+
+ Cluster File Encryption
+
+
+
+ The purpose of cluster file encryption is to prevent users with access
+ to the directories used to store database files from being able to
+ access the data stored in those files. For example, when using cluster
+ file encryption, users who have read access to the cluster directories
+ for backup purposes will not be able to read the data stored in the
+ these files.
+
+
+
+ Cluster file encryption uses two levels of encryption. The first level
+ is data encryption keys, specifically keys zero and one. Key zero is
+ the key uses to encrypt database heap and index files which are stored in
+ the file system, plus temporary files created during database operation.
+ XXX others? Key one is used to encrypt write-ahead log (WAL) files.
+ Two different keys are used so that primary and standby servers can
+ use different zero (heap/index/temp) keys, but the same one (WAL) key,
+ so that these keys can eventually be rotated by switching the primary
+ to the standby as and then changing the WAL key.
+
+
+
+ The second level of encryption is a key used to encrypt first-level
+ keys. This type of key is often referred to as a Key Encryption Key
+ (KEK). This key is not stored
+ in the file system, but provided at initdb time and
+ each time the server is started. This key prevents anyone with access
+ to the database directories from reading the data because they do not
+ know the second-level key which encrypted the first-level keys which
+ encrypted the database cluster files.
+
+
+
+ Initialization
+
+
+ Cluster file encryption is enabled when
+ PostgreSQL is built
+ with --with-openssl and is specified
+ during initdb. The cluster passphrase
+ provided by the
+ option during initdb and the one generated
+ by in the
+ postgresql.conf must match for the database
+ cluster to start. Note that the cluster passphrase command
+ passed to initdb must return a passphrase of at
+ least 64 bytes and less than 1024 bytes. For example.
+
+ initdb -D dbname --cluster-passphrase-command="cat /path/to/passphrase-file"
+
+
+
+
+
+ Internals
+
+
+ During the initdb process, if the
+ is specified, two
+ data-level encryption keys are created. These two keys are then
+ encrypted with the passphrase (KEK) supplied by the cluster passphrase
+ command before being stored in the database directory. The passphrase
+ must be stored in a trusted key store, such as key vault software,
+ hardware security module, or supplied from the terminal.
+
+
+
+ If the PostgreSQL server has
+ been initialized to require a passphrase, each time the
+ server is started the postgresql.conf
+ cluster_passphrase_command command will be executed
+ and the cluster passphrase retrieved. The data encryption keys in the
+ pg_cryptokeys directory will then be decrypted
+ using the supplied passphrase and integrity-checked to see if it
+ matches the initdb-supplied passphrase. If this check fails, and the
+ the server will refuse to start.
+
+
+
+
+ Key Protection
+
+
+ Cluster file encryption uses Encryption with Associated Data
+ (AEAD) to wrap cryptographic keys. In addition
+ to providing a way to protect confidential data from being revealed,
+ it provides a way to check the integrity and authenticity of some
+ associated data. It uses the Encrypt-Then-MAC approach, based on the
+ Advanced Encryption Standard (AES256) in Cipher Block
+ Chaining (CBC) mode. It uses a random initialization
+ vector (IV) and a HMAC-SHA
+ message authentication code (MAC).
+
+
+
+ Key management system uses two kinds of cryptographic keys for key wrapping:
+
+
+
+
+
+ Data Encryption Key
+
+
+ Data Encryption keys are 128, 192, or 256-bit length randomly generate keys.
+
+
+
+
+ MAC Key
+
+
+ MAC key is a 512-bit randomly generated key.
+ SHA512 is the algorithm used along with the
+ MAC key to compute a cryptographic hash for
+ integrity purposes.
+
+
+
+
+
+
+
+ The data key wrapping algorithm is as follows:
+
+
+
+ Generate random IV.
+
+
+ Add padding to the plain text following PKCS#7 described in
+ RFC2315.
+
+
+ Encrypt padded plain text with the IV
+ using AES256 in CBC
+ mode.
+
+
+ Compute HMAC over the encrypted data.
+
+
+ Concatenate HMAC, IV
+ and encrypted cipher text.
+
+
+
+
+
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
new file mode 100644
index 38e8aa0..b96f4ac
*** a/doc/src/sgml/filelist.sgml
--- b/doc/src/sgml/filelist.sgml
***************
*** 49,54 ****
--- 49,55 ----
+
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
new file mode 100644
index 0ac1cb9..bcc8024
*** a/doc/src/sgml/installation.sgml
--- b/doc/src/sgml/installation.sgml
*************** build-postgresql:
*** 976,983 ****
Build with support for SSL (encrypted)
! connections. This requires the OpenSSL
! package to be installed. configure will check
for the required header files and libraries to make sure that
your OpenSSL installation is sufficient
before proceeding.
--- 976,984 ----
Build with support for SSL (encrypted)
! connections and cluster file encryption. This requires the
! OpenSSL package to be installed.
! configure will check
for the required header files and libraries to make sure that
your OpenSSL installation is sufficient
before proceeding.
diff --git a/doc/src/sgml/postgres.sgml b/doc/src/sgml/postgres.sgml
new file mode 100644
index 730d5fd..0ea7da6
*** a/doc/src/sgml/postgres.sgml
--- b/doc/src/sgml/postgres.sgml
*************** break is not needed in a wider output re
*** 171,176 ****
--- 171,177 ----
&wal;
&logical-replication;
&jit;
+ &database-encryption;
®ress;
diff --git a/doc/src/sgml/ref/initdb.sgml b/doc/src/sgml/ref/initdb.sgml
new file mode 100644
index 385ac25..f3ac1d5
*** a/doc/src/sgml/ref/initdb.sgml
--- b/doc/src/sgml/ref/initdb.sgml
*************** PostgreSQL documentation
*** 163,168 ****
--- 163,191 ----
+
+
+
+
+ This option enables cluster file encryption and specifies the
+ external command to obtain the cluster passphrase.
+
+
+ The command must print the passphrase to the standard output and
+ exit with code 0. In the parameter value, %p
+ is replaced by a prompt string, and %R
+ represents the file descriptor number opened to the terminal, if
+ enabled by . Since it can be blank,
+ it is wise for it to be the last command-line argument specified.
+ (Write %% for a literal %.)
+ Note that the prompt string will probably contain whitespace,
+ so be sure to quote adequately. A single newline is stripped
+ from the end of the output if present. The passphrase must be
+ at least 64 bytes.
+
+
+
+
*************** PostgreSQL documentation
*** 223,228 ****
--- 246,263 ----
+
+
+
+
+
+ Specifies the number of bits for the file encryption keys. The
+ default is 128 bits.
+
+
+
+
*************** PostgreSQL documentation
*** 284,289 ****
--- 319,335 ----
+
+
+
+
+
+
+ Makes initdb prompt for a passphrase
+ to assign to the cluster.
+
+
+
diff --git a/doc/src/sgml/ref/pg_ctl-ref.sgml b/doc/src/sgml/ref/pg_ctl-ref.sgml
new file mode 100644
index 3946fa5..bb32402
*** a/doc/src/sgml/ref/pg_ctl-ref.sgml
--- b/doc/src/sgml/ref/pg_ctl-ref.sgml
*************** PostgreSQL documentation
*** 38,43 ****
--- 38,44 ----
options
path
+
*************** PostgreSQL documentation
*** 72,77 ****
--- 73,79 ----
seconds
options
+
*************** PostgreSQL documentation
*** 372,377 ****
--- 374,391 ----
+
+
+
+
+
+
+ Makes pg_ctl prompt for the cluster
+ passphrase. This also disables dot-status progress
+ reporting.
+
+
+
diff --git a/doc/src/sgml/ref/pgupgrade.sgml b/doc/src/sgml/ref/pgupgrade.sgml
new file mode 100644
index 92e1d09..7b8d01b
*** a/doc/src/sgml/ref/pgupgrade.sgml
--- b/doc/src/sgml/ref/pgupgrade.sgml
*************** psql --username=postgres --file=script.s
*** 838,843 ****
--- 838,850 ----
is down.
+
+ If the old cluster uses file encryption, the new cluster must use
+ the same keys, so pg_upgrade copies them to the
+ new cluster. It is necessary to initialize the new cluster with
+ the same cluster_passphrase_command and the same
+ file encryption key length.
+
diff --git a/doc/src/sgml/ref/postgres-ref.sgml b/doc/src/sgml/ref/postgres-ref.sgml
new file mode 100644
index 4aaa7ab..1806c2a
*** a/doc/src/sgml/ref/postgres-ref.sgml
--- b/doc/src/sgml/ref/postgres-ref.sgml
*************** PostgreSQL documentation
*** 298,303 ****
--- 298,314 ----
+
+
+
+ Makes postgres prompt for the cluster
+ passphrase from the specified open numeric file descriptor.
+ The descriptor is closed after the key is read.
+
+
+
+
+
diff --git a/doc/src/sgml/storage.sgml b/doc/src/sgml/storage.sgml
new file mode 100644
index 3234adb..cdbc214
*** a/doc/src/sgml/storage.sgml
--- b/doc/src/sgml/storage.sgml
*************** Item
*** 78,83 ****
--- 78,88 ----
+ pg_cryptokeys
+ Subdirectory containing file encryption keys
+
+
+
pg_dynshmem
Subdirectory containing files used by the dynamic shared memory
subsystem
diff --git a/src/backend/Makefile b/src/backend/Makefile
new file mode 100644
index 9706a95..4ace302
*** a/src/backend/Makefile
--- b/src/backend/Makefile
*************** SUBDIRS = access bootstrap catalog parse
*** 21,27 ****
main nodes optimizer partitioning port postmaster \
regex replication rewrite \
statistics storage tcop tsearch utils $(top_builddir)/src/timezone \
! jit
include $(srcdir)/common.mk
--- 21,27 ----
main nodes optimizer partitioning port postmaster \
regex replication rewrite \
statistics storage tcop tsearch utils $(top_builddir)/src/timezone \
! jit crypto
include $(srcdir)/common.mk
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
new file mode 100644
index 13f1d8c..ffdc450
*** a/src/backend/access/transam/xlog.c
--- b/src/backend/access/transam/xlog.c
***************
*** 44,54 ****
--- 44,56 ----
#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"
#include "port/atomics.h"
#include "postmaster/bgwriter.h"
+ #include "postmaster/postmaster.h"
#include "postmaster/startup.h"
#include "postmaster/walwriter.h"
#include "replication/basebackup.h"
***************
*** 81,86 ****
--- 83,89 ----
#include "utils/timestamp.h"
extern uint32 bootstrap_data_checksum_version;
+ extern int bootstrap_file_encryption_keylen;
/* Unsupported old recovery command file names (relative to $PGDATA) */
#define RECOVERY_COMMAND_FILE "recovery.conf"
*************** InitControlFile(uint64 sysidentifier)
*** 4607,4612 ****
--- 4610,4616 ----
ControlFile->wal_log_hints = wal_log_hints;
ControlFile->track_commit_timestamp = track_commit_timestamp;
ControlFile->data_checksum_version = bootstrap_data_checksum_version;
+ ControlFile->file_encryption_keylen = bootstrap_file_encryption_keylen;
}
static void
*************** ReadControlFile(void)
*** 4706,4711 ****
--- 4710,4716 ----
pg_crc32c crc;
int fd;
static char wal_segsz_str[20];
+ static char file_encryption_keylen_str[20];
int r;
/*
*************** ReadControlFile(void)
*** 4894,4899 ****
--- 4899,4910 ----
/* Make the initdb settings visible as GUC variables, too */
SetConfigOption("data_checksums", DataChecksumsEnabled() ? "yes" : "no",
PGC_INTERNAL, PGC_S_OVERRIDE);
+
+ Assert(ControlFile != NULL);
+ snprintf(file_encryption_keylen_str, sizeof(file_encryption_keylen_str), "%d",
+ ControlFile->file_encryption_keylen);
+ SetConfigOption("file_encryption_keylen", file_encryption_keylen_str, PGC_INTERNAL,
+ PGC_S_OVERRIDE);
}
/*
*************** BootStrapXLOG(void)
*** 5342,5347 ****
--- 5353,5367 ----
/* some additional ControlFile fields are set in WriteControlFile() */
WriteControlFile();
+ /* Enable file encryption if required */
+ if (ControlFile->file_encryption_keylen > 0)
+ BootStrapKmgr();
+ if (terminal_fd != -1)
+ {
+ close(terminal_fd);
+ terminal_fd = -1;
+ }
+
/* Bootstrap the commit log, too */
BootStrapCLOG();
BootStrapCommitTs();
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
new file mode 100644
index a7ed93f..4e908be
*** a/src/backend/bootstrap/bootstrap.c
--- b/src/backend/bootstrap/bootstrap.c
***************
*** 28,39 ****
--- 28,41 ----
#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"
#include "pg_getopt.h"
#include "pgstat.h"
#include "postmaster/bgwriter.h"
+ #include "postmaster/postmaster.h"
#include "postmaster/startup.h"
#include "postmaster/walwriter.h"
#include "replication/walreceiver.h"
***************
*** 51,56 ****
--- 53,59 ----
#include "utils/relmapper.h"
uint32 bootstrap_data_checksum_version = 0; /* No checksum */
+ int bootstrap_file_encryption_keylen = 0; /* disabled */
static void CheckerModeMain(void);
*************** AuxiliaryProcessMain(int argc, char *arg
*** 224,230 ****
/* If no -x argument, we are a CheckerProcess */
MyAuxProcType = CheckerProcess;
! while ((flag = getopt(argc, argv, "B:c:d:D:Fkr:x:X:-:")) != -1)
{
switch (flag)
{
--- 227,233 ----
/* If no -x argument, we are a CheckerProcess */
MyAuxProcType = CheckerProcess;
! while ((flag = getopt(argc, argv, "B:c:d:D:FkK:r:R:x:X:-:")) != -1)
{
switch (flag)
{
*************** AuxiliaryProcessMain(int argc, char *arg
*** 253,261 ****
--- 256,270 ----
case 'k':
bootstrap_data_checksum_version = PG_DATA_CHECKSUM_VERSION;
break;
+ case 'K':
+ bootstrap_file_encryption_keylen = atoi(optarg);
+ break;
case 'r':
strlcpy(OutputFileName, optarg, MAXPGPATH);
break;
+ case 'R':
+ terminal_fd = atoi(optarg);
+ break;
case 'x':
MyAuxProcType = atoi(optarg);
break;
*************** AuxiliaryProcessMain(int argc, char *arg
*** 312,317 ****
--- 321,332 ----
proc_exit(1);
}
+ if (bootstrap_file_encryption_keylen != 0 &&
+ bootstrap_file_encryption_keylen != 128 &&
+ bootstrap_file_encryption_keylen != 192 &&
+ bootstrap_file_encryption_keylen != 256)
+ elog(PANIC, "unrecognized file encryption length: %d", bootstrap_file_encryption_keylen);
+
switch (MyAuxProcType)
{
case StartupProcess:
diff --git a/src/backend/crypto/Makefile b/src/backend/crypto/Makefile
new file mode 100644
index ...c273620
*** a/src/backend/crypto/Makefile
--- b/src/backend/crypto/Makefile
***************
*** 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 ...ea01ed7
*** a/src/backend/crypto/kmgr.c
--- b/src/backend/crypto/kmgr.c
***************
*** 0 ****
--- 1,256 ----
+ /*-------------------------------------------------------------------------
+ *
+ * kmgr.c
+ * Cluster file encryption routines
+ *
+ * Cluster file encryption is enabled if user requests it during initdb.
+ * During bootstrap, we generate data encryption 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
+ * the openssl library for now.
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/crypto/kmgr.c
+ *-------------------------------------------------------------------------
+ */
+
+ #include "postgres.h"
+
+ #include
+ #include
+
+ #include "funcapi.h"
+ #include "miscadmin.h"
+ #include "pgstat.h"
+
+ #include "access/xlog.h"
+ #include "common/sha2.h"
+ #include "common/kmgr_utils.h"
+ #include "crypto/kmgr.h"
+ #include "storage/fd.h"
+ #include "storage/ipc.h"
+ #include "storage/shmem.h"
+ #include "utils/builtins.h"
+ #include "utils/memutils.h"
+ /* Struct stores file encryption keys in plaintext format */
+ typedef struct KmgrShmemData
+ {
+ CryptoKey intlKeys[KMGR_MAX_INTERNAL_KEYS];
+ } KmgrShmemData;
+ static KmgrShmemData *KmgrShmem;
+
+ /* GUC variables */
+ char *cluster_passphrase_command = NULL;
+ int file_encryption_keylen = 0;
+
+ static void KmgrSaveCryptoKeys(const char *dir, CryptoKey *keys);
+ static CryptoKey *generate_crypto_key(int len);
+
+ /*
+ * This function must be called ONCE during initdb.
+ */
+ void
+ BootStrapKmgr(void)
+ {
+ PgKeyWrapCtx *ctx;
+ CryptoKey keys_wrap[KMGR_MAX_INTERNAL_KEYS];
+ char passphrase[KMGR_MAX_PASSPHRASE_LEN];
+ uint8 KEK_enc[KMGR_ENC_KEY_LEN];
+ uint8 KEK_hmac[KMGR_MAC_KEY_LEN];
+ int passlen;
+
+ /*
+ * Requirement check. We need openssl library to enable file encryption
+ * 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 at least %d bytes",
+ KMGR_MIN_PASSPHRASE_LEN)));
+
+ /* Get key encryption key and HMAC key from passphrase */
+ kmgr_derive_keys(passphrase, passlen, KEK_enc, KEK_hmac);
+
+ /* Create temporary key wrap context */
+ ctx = pg_create_keywrap_ctx(KEK_enc, KEK_hmac);
+ if (!ctx)
+ elog(ERROR, "could not initialize encryption context");
+
+ /* Wrap all data encryption keys by key encryption key */
+ for (int id = 0; id < KMGR_MAX_INTERNAL_KEYS; id++)
+ {
+ CryptoKey *key;
+
+ /* generate a data encryption key */
+ key = generate_crypto_key(file_encryption_keylen);
+
+ if (!kmgr_wrap_key(ctx, key, &(keys_wrap[id])))
+ {
+ pg_free_keywrap_ctx(ctx);
+ elog(ERROR, "failed to wrap data encryption key");
+ }
+ }
+
+ /* Save data encryption 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 (!file_encryption_keylen)
+ return 0;
+
+ return MAXALIGN(sizeof(KmgrShmemData));
+ }
+
+ /* Allocate and initialize key manager memory */
+ void
+ KmgrShmemInit(void)
+ {
+ bool found;
+
+ if (!file_encryption_keylen)
+ return;
+
+ KmgrShmem = (KmgrShmemData *) ShmemInitStruct("File encryption key manager",
+ KmgrShmemSize(), &found);
+
+ if (!found)
+ memset(KmgrShmem, 0, KmgrShmemSize());
+ }
+
+ /*
+ * Get encryption key passphrase and verify it, then get the data encryption 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 (!file_encryption_keylen)
+ return;
+
+ elog(DEBUG1, "starting up cluster file encryption manager");
+
+ /* 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 a data encryption key in plaintext in shared memory.
+ */
+ 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 file encryption key");
+
+ newkey->klen = len;
+
+ return newkey;
+ }
+
+ /*
+ * Save the given file encryption 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)));
+ }
+ }
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
new file mode 100644
index e102600..c7e8619
*** a/src/backend/libpq/be-secure-openssl.c
--- b/src/backend/libpq/be-secure-openssl.c
***************
*** 30,35 ****
--- 30,36 ----
#endif
#include
+ #include
#include
#include
#ifndef OPENSSL_NO_ECDH
diff --git a/src/backend/main/main.c b/src/backend/main/main.c
new file mode 100644
index b6e5128..71833a6
*** a/src/backend/main/main.c
--- b/src/backend/main/main.c
*************** help(const char *progname)
*** 324,329 ****
--- 324,330 ----
#endif
printf(_(" -N MAX-CONNECT maximum number of allowed connections\n"));
printf(_(" -p PORT port number to listen on\n"));
+ printf(_(" -R fd prompt for the cluster passphrase\n"));
printf(_(" -s show statistics after each query\n"));
printf(_(" -S WORK-MEM set amount of memory for sorts (in kB)\n"));
printf(_(" -V, --version output version information, then exit\n"));
*************** help(const char *progname)
*** 351,356 ****
--- 352,358 ----
printf(_("\nOptions for bootstrapping mode:\n"));
printf(_(" --boot selects bootstrapping mode (must be first argument)\n"));
printf(_(" DBNAME database name (mandatory argument in bootstrapping mode)\n"));
+ printf(_(" -K len enable cluster file encryption with specified key length\n"));
printf(_(" -r FILENAME send stdout and stderr to given file\n"));
printf(_(" -x NUM internal use\n"));
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
new file mode 100644
index e76e627..127c988
*** a/src/backend/postmaster/pgstat.c
--- b/src/backend/postmaster/pgstat.c
*************** pgstat_get_wait_io(WaitEventIO w)
*** 4137,4142 ****
--- 4137,4151 ----
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
new file mode 100644
index b7799ed..4f9a97f
*** a/src/backend/postmaster/postmaster.c
--- b/src/backend/postmaster/postmaster.c
***************
*** 100,105 ****
--- 100,106 ----
#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"
*************** static int SendStop = false;
*** 230,235 ****
--- 231,237 ----
/* still more option variables */
bool EnableSSL = false;
+ int terminal_fd = -1;
int PreAuthDelay = 0;
int AuthenticationTimeout = 60;
*************** PostmasterMain(int argc, char *argv[])
*** 686,692 ****
* tcop/postgres.c (the option sets should not conflict) and with the
* common help() function in main/main.c.
*/
! while ((opt = getopt(argc, argv, "B:bc:C:D:d:EeFf:h:ijk:lN:nOPp:r:S:sTt:W:-:")) != -1)
{
switch (opt)
{
--- 688,694 ----
* tcop/postgres.c (the option sets should not conflict) and with the
* common help() function in main/main.c.
*/
! while ((opt = getopt(argc, argv, "B:bc:C:D:d:EeFf:h:ijk:lN:nOPp:r:R:S:sTt:W:-:")) != -1)
{
switch (opt)
{
*************** PostmasterMain(int argc, char *argv[])
*** 777,782 ****
--- 779,788 ----
/* only used by single-user backend */
break;
+ case 'R':
+ terminal_fd = atoi(optarg);
+ break;
+
case 'S':
SetConfigOption("work_mem", optarg, PGC_POSTMASTER, PGC_S_ARGV);
break;
*************** PostmasterMain(int argc, char *argv[])
*** 1324,1329 ****
--- 1330,1339 ----
*/
RemovePgTempFiles();
+ InitializeKmgr();
+ if (terminal_fd != -1)
+ close(terminal_fd);
+
/*
* Initialize stats collection subsystem (this does NOT start the
* collector process!)
diff --git a/src/backend/replication/basebackup.c b/src/backend/replication/basebackup.c
new file mode 100644
index b89df01..6ad051d
*** a/src/backend/replication/basebackup.c
--- b/src/backend/replication/basebackup.c
***************
*** 18,23 ****
--- 18,24 ----
#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"
*************** struct exclude_list_item
*** 152,157 ****
--- 153,161 ----
*/
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
new file mode 100644
index 96c2aaa..64fe10c
*** a/src/backend/storage/ipc/ipci.c
--- b/src/backend/storage/ipc/ipci.c
***************
*** 23,28 ****
--- 23,29 ----
#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"
*************** CreateSharedMemoryAndSemaphores(void)
*** 149,154 ****
--- 150,156 ----
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
*************** CreateSharedMemoryAndSemaphores(void)
*** 267,272 ****
--- 269,275 ----
BTreeShmemInit();
SyncScanShmemInit();
AsyncShmemInit();
+ KmgrShmemInit();
#ifdef EXEC_BACKEND
diff --git a/src/backend/storage/lmgr/lwlocknames.txt b/src/backend/storage/lmgr/lwlocknames.txt
new file mode 100644
index 774292f..a44805d
*** a/src/backend/storage/lmgr/lwlocknames.txt
--- b/src/backend/storage/lmgr/lwlocknames.txt
*************** XactTruncationLock 44
*** 53,55 ****
--- 53,56 ----
# 45 was XactTruncationLock until removal of BackendRandomLock
WrapLimitsVacuumLock 46
NotifyQueueTailLock 47
+ KmgrFileLock 48
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
new file mode 100644
index 3679799..ade27d5
*** a/src/backend/tcop/postgres.c
--- b/src/backend/tcop/postgres.c
***************
*** 42,47 ****
--- 42,48 ----
#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"
*************** process_postgres_switches(int argc, char
*** 3559,3565 ****
* postmaster/postmaster.c (the option sets should not conflict) and with
* the common help() function in main/main.c.
*/
! while ((flag = getopt(argc, argv, "B:bc:C:D:d:EeFf:h:ijk:lN:nOPp:r:S:sTt:v:W:-:")) != -1)
{
switch (flag)
{
--- 3560,3566 ----
* postmaster/postmaster.c (the option sets should not conflict) and with
* the common help() function in main/main.c.
*/
! while ((flag = getopt(argc, argv, "B:bc:C:D:d:EeFf:h:ijk:lN:nOPp:r:R:S:sTt:v:W:-:")) != -1)
{
switch (flag)
{
*************** process_postgres_switches(int argc, char
*** 3651,3656 ****
--- 3652,3661 ----
strlcpy(OutputFileName, optarg, MAXPGPATH);
break;
+ case 'R':
+ terminal_fd = atoi(optarg);
+ break;
+
case 'S':
SetConfigOption("work_mem", optarg, ctx, gucsource);
break;
*************** PostgresMain(int argc, char *argv[],
*** 3903,3908 ****
--- 3908,3924 ----
BaseInit();
/*
+ * Initialize kmgr for cluster encryption. Since kmgr needs to attach to
+ * shared memory the initialization must be called after BaseInit().
+ */
+ if (!IsUnderPostmaster)
+ {
+ InitializeKmgr();
+ if (terminal_fd != -1)
+ close(terminal_fd);
+ }
+
+ /*
* Create a per-backend PGPROC struct in shared memory, except in the
* EXEC_BACKEND case where this was done in SubPostmasterMain. We must do
* this before we can use LWLocks (and in the EXEC_BACKEND case we already
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
new file mode 100644
index 245a347..f568c7c
*** a/src/backend/utils/misc/guc.c
--- b/src/backend/utils/misc/guc.c
***************
*** 47,52 ****
--- 47,53 ----
#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"
*************** const char *const config_group_names[] =
*** 745,750 ****
--- 746,753 ----
gettext_noop("Statistics / Monitoring"),
/* STATS_COLLECTOR */
gettext_noop("Statistics / Query and Index Statistics Collector"),
+ /* ENCRYPTION */
+ gettext_noop("Encryption"),
/* AUTOVACUUM */
gettext_noop("Autovacuum"),
/* CLIENT_CONN */
*************** static struct config_int ConfigureNamesI
*** 3399,3404 ****
--- 3402,3418 ----
check_huge_page_size, NULL, NULL
},
+ {
+ {"file_encryption_keylen", PGC_INTERNAL, PRESET_OPTIONS,
+ gettext_noop("Show the bit length of the file encryption key."),
+ NULL,
+ GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE
+ },
+ &file_encryption_keylen,
+ 0, 0, 256,
+ NULL, NULL, NULL
+ },
+
/* End-of-list marker */
{
{NULL, 0, 0, NULL, NULL}, NULL, 0, 0, 0, NULL, NULL, NULL
*************** static struct config_string ConfigureNam
*** 4392,4397 ****
--- 4406,4421 ----
"",
NULL, NULL, NULL
},
+
+ {
+ {"cluster_passphrase_command", PGC_SIGHUP, ENCRYPTION,
+ gettext_noop("Command to obtain passphrase for cluster file encryption."),
+ NULL
+ },
+ &cluster_passphrase_command,
+ "",
+ NULL, NULL, NULL
+ },
{
{"application_name", PGC_USERSET, LOGGING_WHAT,
diff --git a/src/backend/utils/misc/pg_controldata.c b/src/backend/utils/misc/pg_controldata.c
new file mode 100644
index d50d87a..6fcab31
*** a/src/backend/utils/misc/pg_controldata.c
--- b/src/backend/utils/misc/pg_controldata.c
*************** pg_control_recovery(PG_FUNCTION_ARGS)
*** 263,270 ****
Datum
pg_control_init(PG_FUNCTION_ARGS)
{
! Datum values[11];
! bool nulls[11];
TupleDesc tupdesc;
HeapTuple htup;
ControlFileData *ControlFile;
--- 263,270 ----
Datum
pg_control_init(PG_FUNCTION_ARGS)
{
! Datum values[12];
! bool nulls[12];
TupleDesc tupdesc;
HeapTuple htup;
ControlFileData *ControlFile;
*************** pg_control_init(PG_FUNCTION_ARGS)
*** 274,280 ****
* Construct a tuple descriptor for the result row. This must match this
* function's pg_proc entry!
*/
! tupdesc = CreateTemplateTupleDesc(11);
TupleDescInitEntry(tupdesc, (AttrNumber) 1, "max_data_alignment",
INT4OID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 2, "database_block_size",
--- 274,280 ----
* Construct a tuple descriptor for the result row. This must match this
* function's pg_proc entry!
*/
! tupdesc = CreateTemplateTupleDesc(12);
TupleDescInitEntry(tupdesc, (AttrNumber) 1, "max_data_alignment",
INT4OID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 2, "database_block_size",
*************** pg_control_init(PG_FUNCTION_ARGS)
*** 297,302 ****
--- 297,304 ----
BOOLOID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 11, "data_page_checksum_version",
INT4OID, -1, 0);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 12, "file_encryption_keylen",
+ INT4OID, -1, 0);
tupdesc = BlessTupleDesc(tupdesc);
/* read the control file */
*************** pg_control_init(PG_FUNCTION_ARGS)
*** 338,343 ****
--- 340,348 ----
values[10] = Int32GetDatum(ControlFile->data_checksum_version);
nulls[10] = false;
+ values[11] = Int32GetDatum(ControlFile->file_encryption_keylen);
+ nulls[11] = false;
+
htup = heap_form_tuple(tupdesc, values, nulls);
PG_RETURN_DATUM(HeapTupleGetDatum(htup));
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
new file mode 100644
index 9cb571f..fc30641
*** a/src/backend/utils/misc/postgresql.conf.sample
--- b/src/backend/utils/misc/postgresql.conf.sample
***************
*** 633,638 ****
--- 633,643 ----
# 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
new file mode 100644
index ee3bfa8..cc58f51
*** a/src/bin/initdb/initdb.c
--- b/src/bin/initdb/initdb.c
*************** static bool debug = false;
*** 141,151 ****
--- 141,155 ----
static bool noclean = false;
static bool do_sync = true;
static bool sync_only = false;
+ static bool pass_terminal_fd = false;
+ static char *term_fd_opt = NULL;
+ static int file_encryption_keylen = 0;
static bool show_setting = false;
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_cmd = NULL;
/* internal vars */
*************** static const char *const subdirs[] = {
*** 203,208 ****
--- 207,213 ----
"global",
"pg_wal/archive_status",
"pg_commit_ts",
+ "pg_cryptokeys",
"pg_dynshmem",
"pg_notify",
"pg_serial",
*************** test_config_settings(void)
*** 954,965 ****
test_buffs = MIN_BUFS_FOR_CONNS(test_conns);
snprintf(cmd, sizeof(cmd),
! "\"%s\" --boot -x0 %s "
"-c max_connections=%d "
"-c shared_buffers=%d "
"-c dynamic_shared_memory_type=%s "
"< \"%s\" > \"%s\" 2>&1",
backend_exec, boot_options,
test_conns, test_buffs,
dynamic_shared_memory_type,
DEVNULL, DEVNULL);
--- 959,971 ----
test_buffs = MIN_BUFS_FOR_CONNS(test_conns);
snprintf(cmd, sizeof(cmd),
! "\"%s\" --boot -x0 %s %s "
"-c max_connections=%d "
"-c shared_buffers=%d "
"-c dynamic_shared_memory_type=%s "
"< \"%s\" > \"%s\" 2>&1",
backend_exec, boot_options,
+ term_fd_opt ? term_fd_opt : "",
test_conns, test_buffs,
dynamic_shared_memory_type,
DEVNULL, DEVNULL);
*************** test_config_settings(void)
*** 990,1001 ****
}
snprintf(cmd, sizeof(cmd),
! "\"%s\" --boot -x0 %s "
"-c max_connections=%d "
"-c shared_buffers=%d "
"-c dynamic_shared_memory_type=%s "
"< \"%s\" > \"%s\" 2>&1",
backend_exec, boot_options,
n_connections, test_buffs,
dynamic_shared_memory_type,
DEVNULL, DEVNULL);
--- 996,1008 ----
}
snprintf(cmd, sizeof(cmd),
! "\"%s\" --boot -x0 %s %s "
"-c max_connections=%d "
"-c shared_buffers=%d "
"-c dynamic_shared_memory_type=%s "
"< \"%s\" > \"%s\" 2>&1",
backend_exec, boot_options,
+ term_fd_opt ? term_fd_opt : "",
n_connections, test_buffs,
dynamic_shared_memory_type,
DEVNULL, DEVNULL);
*************** setup_config(void)
*** 1185,1190 ****
--- 1192,1204 ----
"password_encryption = md5");
}
+ if (cluster_passphrase_cmd)
+ {
+ snprintf(repltok, sizeof(repltok), "cluster_passphrase_command = '%s'",
+ escape_quotes(cluster_passphrase_cmd));
+ 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
*************** bootstrap_template1(void)
*** 1394,1406 ****
/* Also ensure backend isn't confused by this environment var: */
unsetenv("PGCLIENTENCODING");
snprintf(cmd, sizeof(cmd),
! "\"%s\" --boot -x1 -X %u %s %s %s",
backend_exec,
wal_segment_size_mb * (1024 * 1024),
data_checksums ? "-k" : "",
boot_options,
! debug ? "-d 5" : "");
PG_CMD_OPEN;
--- 1408,1427 ----
/* Also ensure backend isn't confused by this environment var: */
unsetenv("PGCLIENTENCODING");
+ if (file_encryption_keylen != 0)
+ sprintf(buf, "%d", file_encryption_keylen);
+ else
+ buf[0] = '\0';
+
snprintf(cmd, sizeof(cmd),
! "\"%s\" --boot -x1 -X %u %s %s %s %s %s %s",
backend_exec,
wal_segment_size_mb * (1024 * 1024),
data_checksums ? "-k" : "",
+ cluster_passphrase_cmd ? "-K" : "", buf,
boot_options,
! debug ? "-d 5" : "",
! term_fd_opt ? term_fd_opt : "");
PG_CMD_OPEN;
*************** usage(const char *progname)
*** 2281,2299 ****
" set default locale in the respective category for\n"
" new databases (default taken from environment)\n"));
printf(_(" --no-locale equivalent to --locale=C\n"));
! printf(_(" --pwfile=FILE read password for the new superuser from file\n"));
printf(_(" -T, --text-search-config=CFG\n"
" default text search configuration\n"));
printf(_(" -U, --username=NAME database superuser name\n"));
! printf(_(" -W, --pwprompt prompt for a password for the new superuser\n"));
printf(_(" -X, --waldir=WALDIR location for the write-ahead log directory\n"));
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(_(" -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"));
printf(_(" -N, --no-sync do not wait for changes to be written safely to disk\n"));
printf(_(" -s, --show show internal settings\n"));
printf(_(" -S, --sync-only only sync data directory\n"));
printf(_("\nOther options:\n"));
--- 2302,2326 ----
" set default locale in the respective category for\n"
" new databases (default taken from environment)\n"));
printf(_(" --no-locale equivalent to --locale=C\n"));
! printf(_(" --pwfile=FILE read the new superuser password from file\n"));
printf(_(" -T, --text-search-config=CFG\n"
" default text search configuration\n"));
printf(_(" -U, --username=NAME database superuser name\n"));
! printf(_(" -W, --pwprompt prompt for the new superuser password\n"));
printf(_(" -X, --waldir=WALDIR location for the write-ahead log directory\n"));
printf(_(" --wal-segsize=SIZE size of WAL segments, in megabytes\n"));
printf(_("\nLess commonly used options:\n"));
+ printf(_(" -c --cluster-passphrase-command=COMMAND\n"
+ " enable cluster file encryption and set command\n"
+ " to obtain the cluster passphrase\n"));
printf(_(" -d, --debug generate lots of debugging output\n"));
printf(_(" -k, --data-checksums use data page checksums\n"));
+ printf(_(" -K, --file-encryption-keylen\n"
+ " bit length of the file encryption key\n"));
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"));
+ printf(_(" -R, --cpprompt prompt for the new cluster passphrase\n"));
printf(_(" -s, --show show internal settings\n"));
printf(_(" -S, --sync-only only sync data directory\n"));
printf(_("\nOther options:\n"));
*************** initialize_data_directory(void)
*** 2860,2865 ****
--- 2887,2909 ----
/* Top level PG_VERSION is checked by bootstrapper, so make it first */
write_version_file(NULL);
+ if (pass_terminal_fd)
+ {
+ #ifndef WIN32
+ int terminal_fd = open("/dev/tty", O_RDWR, 0);
+ #else
+ int terminal_fd = open("CONOUT$", O_RDWR, 0);
+ #endif
+
+ if (terminal_fd < 0)
+ {
+ pg_log_error(_("%s: could not open terminal: %s\n"),
+ progname, strerror(errno));
+ exit(1);
+ }
+ term_fd_opt = psprintf("-R %d", terminal_fd);
+ }
+
/* Select suitable configuration settings */
set_null_conf();
test_config_settings();
*************** initialize_data_directory(void)
*** 2883,2890 ****
fflush(stdout);
snprintf(cmd, sizeof(cmd),
! "\"%s\" %s template1 >%s",
backend_exec, backend_options,
DEVNULL);
PG_CMD_OPEN;
--- 2927,2935 ----
fflush(stdout);
snprintf(cmd, sizeof(cmd),
! "\"%s\" %s %s template1 >%s",
backend_exec, backend_options,
+ term_fd_opt ? term_fd_opt : "",
DEVNULL);
PG_CMD_OPEN;
*************** main(int argc, char *argv[])
*** 2957,2963 ****
--- 3002,3011 ----
{"waldir", required_argument, NULL, 'X'},
{"wal-segsize", required_argument, NULL, 12},
{"data-checksums", no_argument, NULL, 'k'},
+ {"cpprompt", no_argument, NULL, 'R'},
+ {"file-encryption-keylen", no_argument, NULL, 'K'},
{"allow-group-access", no_argument, NULL, 'g'},
+ {"cluster-passphrase-command", required_argument, NULL, 'c'},
{NULL, 0, NULL, 0}
};
*************** main(int argc, char *argv[])
*** 2999,3005 ****
/* process command-line options */
! while ((c = getopt_long(argc, argv, "dD:E:kL:nNU:WA:sST:X:g", long_options, &option_index)) != -1)
{
switch (c)
{
--- 3047,3053 ----
/* process command-line options */
! while ((c = getopt_long(argc, argv, "A:c:dD:E:kL:nNRsST:U:WX:g", long_options, &option_index)) != -1)
{
switch (c)
{
*************** main(int argc, char *argv[])
*** 3045,3050 ****
--- 3093,3104 ----
case 'N':
do_sync = false;
break;
+ case 'R':
+ pass_terminal_fd = true;
+ break;
+ case 'K':
+ file_encryption_keylen = atoi(optarg);
+ break;
case 'S':
sync_only = true;
break;
*************** main(int argc, char *argv[])
*** 3081,3086 ****
--- 3135,3143 ----
case 9:
pwfilename = pg_strdup(optarg);
break;
+ case 'c':
+ cluster_passphrase_cmd = pg_strdup(optarg);
+ break;
case 's':
show_setting = true;
break;
*************** main(int argc, char *argv[])
*** 3151,3156 ****
--- 3208,3238 ----
exit(1);
}
+ if (file_encryption_keylen != 0 && file_encryption_keylen != 128 &&
+ file_encryption_keylen != 192 && file_encryption_keylen != 256)
+ {
+ pg_log_error("invalid file encrypt key length; supported values are 0 (disabled), 128, 192, and 256");
+ exit(1);
+ }
+
+ if (file_encryption_keylen != 0 && cluster_passphrase_cmd == NULL)
+ {
+ pg_log_error("a non-zero file encryption key length requires the cluster passphrase command to be specified");
+ exit(1);
+ }
+
+ /* set the default */
+ if (file_encryption_keylen == 0 && cluster_passphrase_cmd != NULL)
+ file_encryption_keylen = 128;
+
+ #ifndef USE_OPENSSL
+ if (cluster_passphrase_cmd)
+ {
+ pg_log_error("cluster file encryption is not supported because OpenSSL is not supported by this build");
+ exit(1);
+ }
+ #endif
+
check_authmethod_unspecified(&authmethodlocal);
check_authmethod_unspecified(&authmethodhost);
*************** main(int argc, char *argv[])
*** 3218,3223 ****
--- 3300,3310 ----
else
printf(_("Data page checksums are disabled.\n"));
+ if (cluster_passphrase_cmd)
+ printf(_("Cluster file encryption is enabled.\n"));
+ else
+ printf(_("Cluster file encryption 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
new file mode 100644
index 3e00ac0..c3b38b7
*** a/src/bin/pg_controldata/pg_controldata.c
--- b/src/bin/pg_controldata/pg_controldata.c
***************
*** 25,30 ****
--- 25,31 ----
#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"
*************** main(int argc, char *argv[])
*** 334,338 ****
--- 335,341 ----
ControlFile->data_checksum_version);
printf(_("Mock authentication nonce: %s\n"),
mock_auth_nonce_str);
+ printf(_("File encryption key length: %d\n"),
+ ControlFile->file_encryption_keylen);
return 0;
}
diff --git a/src/bin/pg_ctl/pg_ctl.c b/src/bin/pg_ctl/pg_ctl.c
new file mode 100644
index fc07f1a..b493206
*** a/src/bin/pg_ctl/pg_ctl.c
--- b/src/bin/pg_ctl/pg_ctl.c
*************** typedef enum
*** 79,84 ****
--- 79,85 ----
static bool do_wait = true;
static int wait_seconds = DEFAULT_WAIT;
static bool wait_seconds_arg = false;
+ static bool pass_terminal_fd = false;
static bool silent_mode = false;
static ShutdownMode shutdown_mode = FAST_MODE;
static int sig = SIGINT; /* default */
*************** free_readfile(char **optlines)
*** 442,448 ****
static pgpid_t
start_postmaster(void)
{
! char cmd[MAXPGPATH];
#ifndef WIN32
pgpid_t pm_pid;
--- 443,449 ----
static pgpid_t
start_postmaster(void)
{
! char cmd[MAXPGPATH], *term_fd_opt = NULL;
#ifndef WIN32
pgpid_t pm_pid;
*************** start_postmaster(void)
*** 467,472 ****
--- 468,486 ----
/* fork succeeded, in child */
+ if (pass_terminal_fd)
+ {
+ int terminal_fd = open("/dev/tty", O_RDWR, 0);
+
+ if (terminal_fd < 0)
+ {
+ write_stderr(_("%s: could not open terminal: %s\n"),
+ progname, strerror(errno));
+ exit(1);
+ }
+ term_fd_opt = psprintf(" -R %d", terminal_fd);
+ }
+
/*
* If possible, detach the postmaster process from the launching process
* group and make it a group leader, so that it doesn't get signaled along
*************** start_postmaster(void)
*** 487,498 ****
* has the same PID as the current child process.
*/
if (log_file != NULL)
! snprintf(cmd, MAXPGPATH, "exec \"%s\" %s%s < \"%s\" >> \"%s\" 2>&1",
exec_path, pgdata_opt, post_opts,
DEVNULL, log_file);
else
! snprintf(cmd, MAXPGPATH, "exec \"%s\" %s%s < \"%s\" 2>&1",
! exec_path, pgdata_opt, post_opts, DEVNULL);
(void) execl("/bin/sh", "/bin/sh", "-c", cmd, (char *) NULL);
--- 501,514 ----
* has the same PID as the current child process.
*/
if (log_file != NULL)
! snprintf(cmd, MAXPGPATH, "exec \"%s\" %s%s%s < \"%s\" >> \"%s\" 2>&1",
exec_path, pgdata_opt, post_opts,
+ term_fd_opt ? term_fd_opt : "",
DEVNULL, log_file);
else
! snprintf(cmd, MAXPGPATH, "exec \"%s\" %s%s%s < \"%s\" 2>&1",
! exec_path, pgdata_opt, post_opts,
! term_fd_opt ? term_fd_opt : "", DEVNULL);
(void) execl("/bin/sh", "/bin/sh", "-c", cmd, (char *) NULL);
*************** start_postmaster(void)
*** 513,518 ****
--- 529,549 ----
PROCESS_INFORMATION pi;
const char *comspec;
+ if (pass_terminal_fd)
+ {
+ /* Hopefully we can read and write CONOUT, see simple_prompt() XXX */
+ /* Do CreateRestrictedProcess() children even inherit open file descriptors? XXX */
+ int terminal_fd = open("CONOUT$", O_RDWR, 0);
+
+ if (terminal_fd < 0)
+ {
+ write_stderr(_("%s: could not open terminal: %s\n"),
+ progname, strerror(errno));
+ exit(1);
+ }
+ term_fd_opt = psprintf(" -R %d", terminal_fd);
+ }
+
/* Find CMD.EXE location using COMSPEC, if it's set */
comspec = getenv("COMSPEC");
if (comspec == NULL)
*************** start_postmaster(void)
*** 553,564 ****
else
close(fd);
! snprintf(cmd, MAXPGPATH, "\"%s\" /C \"\"%s\" %s%s < \"%s\" >> \"%s\" 2>&1\"",
! comspec, exec_path, pgdata_opt, post_opts, DEVNULL, log_file);
}
else
! snprintf(cmd, MAXPGPATH, "\"%s\" /C \"\"%s\" %s%s < \"%s\" 2>&1\"",
! comspec, exec_path, pgdata_opt, post_opts, DEVNULL);
if (!CreateRestrictedProcess(cmd, &pi, false))
{
--- 584,597 ----
else
close(fd);
! snprintf(cmd, MAXPGPATH, "\"%s\" /C \"\"%s\" %s%s%s < \"%s\" >> \"%s\" 2>&1\"",
! comspec, exec_path, pgdata_opt, post_opts,
! term_fd_opt ? term_fd_opt : "", DEVNULL, log_file);
}
else
! snprintf(cmd, MAXPGPATH, "\"%s\" /C \"\"%s\" %s%s%s < \"%s\" 2>&1\"",
! comspec, exec_path, pgdata_opt, post_opts,
! term_fd_opt ? term_fd_opt : "", DEVNULL);
if (!CreateRestrictedProcess(cmd, &pi, false))
{
*************** wait_for_postmaster(pgpid_t pm_pid, bool
*** 689,695 ****
}
else
#endif
! print_msg(".");
}
pg_usleep(USEC_PER_SEC / WAITS_PER_SEC);
--- 722,729 ----
}
else
#endif
! if (!pass_terminal_fd)
! print_msg(".");
}
pg_usleep(USEC_PER_SEC / WAITS_PER_SEC);
*************** do_help(void)
*** 2066,2071 ****
--- 2100,2106 ----
printf(_(" -o, --options=OPTIONS command line options to pass to postgres\n"
" (PostgreSQL server executable) or initdb\n"));
printf(_(" -p PATH-TO-POSTGRES normally not necessary\n"));
+ printf(_(" -R, --cpprompt prompt for the cluster passphrase\n"));
printf(_("\nOptions for stop or restart:\n"));
printf(_(" -m, --mode=MODE MODE can be \"smart\", \"fast\", or \"immediate\"\n"));
*************** main(int argc, char **argv)
*** 2260,2265 ****
--- 2295,2301 ----
{"mode", required_argument, NULL, 'm'},
{"pgdata", required_argument, NULL, 'D'},
{"options", required_argument, NULL, 'o'},
+ {"cpprompt", no_argument, NULL, 'R'},
{"silent", no_argument, NULL, 's'},
{"timeout", required_argument, NULL, 't'},
{"core-files", no_argument, NULL, 'c'},
*************** main(int argc, char **argv)
*** 2332,2338 ****
/* process command-line options */
while (optind < argc)
{
! while ((c = getopt_long(argc, argv, "cD:e:l:m:N:o:p:P:sS:t:U:wW",
long_options, &option_index)) != -1)
{
switch (c)
--- 2368,2374 ----
/* process command-line options */
while (optind < argc)
{
! while ((c = getopt_long(argc, argv, "cD:e:l:m:N:o:p:P:RsS:t:U:wW",
long_options, &option_index)) != -1)
{
switch (c)
*************** main(int argc, char **argv)
*** 2385,2390 ****
--- 2421,2429 ----
case 'P':
register_password = pg_strdup(optarg);
break;
+ case 'R':
+ pass_terminal_fd = true;
+ break;
case 's':
silent_mode = true;
break;
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
new file mode 100644
index cb6ef19..8f928b3
*** a/src/bin/pg_resetwal/pg_resetwal.c
--- b/src/bin/pg_resetwal/pg_resetwal.c
*************** PrintControlValues(bool guessed)
*** 804,809 ****
--- 804,811 ----
(ControlFile.float8ByVal ? _("by value") : _("by reference")));
printf(_("Data page checksum version: %u\n"),
ControlFile.data_checksum_version);
+ printf(_("File encryption key length: %d\n"),
+ ControlFile.file_encryption_keylen);
}
diff --git a/src/bin/pg_rewind/filemap.c b/src/bin/pg_rewind/filemap.c
new file mode 100644
index ba34dba..9ad49b6
*** a/src/bin/pg_rewind/filemap.c
--- b/src/bin/pg_rewind/filemap.c
*************** static const char *excludeDirContents[]
*** 108,113 ****
--- 108,121 ----
"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
new file mode 100644
index 39bcaa8..a0aa995
*** a/src/bin/pg_upgrade/controldata.c
--- b/src/bin/pg_upgrade/controldata.c
***************
*** 9,18 ****
--- 9,24 ----
#include "postgres_fe.h"
+ #include
#include
#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()
*
*************** get_control_data(ClusterInfo *cluster, b
*** 59,64 ****
--- 65,71 ----
bool got_date_is_int = false;
bool got_data_checksum_version = false;
bool got_cluster_state = false;
+ int got_file_encryption_keylen = 0;
char *lc_collate = NULL;
char *lc_ctype = NULL;
char *lc_monetary = NULL;
*************** get_control_data(ClusterInfo *cluster, b
*** 202,207 ****
--- 209,221 ----
got_data_checksum_version = true;
}
+ /* Only in <= 14 */
+ if (GET_MAJOR_VERSION(cluster->major_version) <= 1400)
+ {
+ cluster->controldata.file_encryption_keylen = 0;
+ got_file_encryption_keylen = true;
+ }
+
/* we have the result of cmd in "output". so parse it line by line now */
while (fgets(bufin, sizeof(bufin), output))
{
*************** get_control_data(ClusterInfo *cluster, b
*** 485,490 ****
--- 499,516 ----
cluster->controldata.data_checksum_version = str2uint(p);
got_data_checksum_version = true;
}
+ else if ((p = strstr(bufin, "File encryption key length:")) != 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.file_encryption_keylen = atoi(p);
+ got_file_encryption_keylen = true;
+ }
}
pclose(output);
*************** get_control_data(ClusterInfo *cluster, b
*** 539,545 ****
!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)
{
if (cluster == &old_cluster)
pg_log(PG_REPORT,
--- 565,572 ----
!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_file_encryption_keylen)
{
if (cluster == &old_cluster)
pg_log(PG_REPORT,
*************** get_control_data(ClusterInfo *cluster, b
*** 605,610 ****
--- 632,641 ----
if (!got_data_checksum_version)
pg_log(PG_REPORT, " data checksum version\n");
+ /* value added in Postgres 14 */
+ if (!got_file_encryption_keylen)
+ pg_log(PG_REPORT, " file encryption key length\n");
+
pg_fatal("Cannot continue without required control information, terminating\n");
}
}
*************** check_control_data(ControlData *oldctrl,
*** 669,674 ****
--- 700,714 ----
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 file encryption key length
+ * doesn't match the new one.
+
+ */
+ if (oldctrl->file_encryption_keylen != newctrl->file_encryption_keylen)
+ pg_fatal("old and new clusters use different file encryption key lengths or\n"
+ "one cluster uses encryption and the other does not");
}
diff --git a/src/bin/pg_upgrade/file.c b/src/bin/pg_upgrade/file.c
new file mode 100644
index cc8a675..177771a
*** a/src/bin/pg_upgrade/file.c
--- b/src/bin/pg_upgrade/file.c
***************
*** 11,16 ****
--- 11,17 ----
#include
#include
+ #include
#ifdef HAVE_COPYFILE_H
#include
#endif
***************
*** 21,26 ****
--- 22,28 ----
#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"
*************** check_hard_link(void)
*** 372,374 ****
--- 374,443 ----
unlink(new_link_file);
}
+
+ /*
+ * Copy cryptographic keys from the old cluster to the new cluster.
+ */
+ void
+ copy_data_encryption_keys(ClusterInfo *old_cluster, ClusterInfo *new_cluster)
+ {
+ DIR *dir;
+ struct dirent *de;
+ char path[MAXPGPATH];
+
+ 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 (strspn(de->d_name, "0123456789") == strlen(de->d_name))
+ {
+ CryptoKey key;
+ char src_path[MAXPGPATH];
+ char dst_path[MAXPGPATH];
+ int src_fd;
+ int dst_fd;
+ int len;
+ uint32 id = strtoul(de->d_name, NULL, 10);
+
+ snprintf(src_path, MAXPGPATH, "%s/%s/%u",
+ old_cluster->pgdata, KMGR_DIR, id);
+ snprintf(dst_path, MAXPGPATH, "%s/%s/%u",
+ 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
new file mode 100644
index e2253ec..6b72b19
*** a/src/bin/pg_upgrade/pg_upgrade.c
--- b/src/bin/pg_upgrade/pg_upgrade.c
*************** main(int argc, char **argv)
*** 157,162 ****
--- 157,169 ----
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_data_encryption_keys(&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
* the old system, but we do it anyway just in case. We do it late here
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
new file mode 100644
index ee70243..d1cc30c
*** a/src/bin/pg_upgrade/pg_upgrade.h
--- b/src/bin/pg_upgrade/pg_upgrade.h
***************
*** 11,16 ****
--- 11,17 ----
#include
#include "libpq-fe.h"
+ #include "common/kmgr_utils.h"
/* Use port in the private/dynamic port number range */
#define DEF_PGUPORT 50432
*************** typedef struct
*** 219,224 ****
--- 220,226 ----
bool date_is_int;
bool float8_pass_by_value;
bool data_checksum_version;
+ int file_encryption_keylen;
} ControlData;
/*
*************** void rewriteVisibilityMap(const char *f
*** 374,379 ****
--- 376,383 ----
const char *schemaName, const char *relName);
void check_file_clone(void);
void check_hard_link(void);
+ void copy_data_encryption_keys(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
new file mode 100644
index 25c55bd..cfb34fd
*** a/src/common/Makefile
--- b/src/common/Makefile
*************** OBJS_COMMON = \
*** 49,54 ****
--- 49,55 ----
archive.o \
base64.o \
checksum_helper.o \
+ cipher.o \
config_info.o \
controldata_utils.o \
d2s.o \
*************** OBJS_COMMON = \
*** 61,66 ****
--- 62,68 ----
ip.o \
jsonapi.o \
keywords.o \
+ kmgr_utils.o \
kwlookup.o \
link-canary.o \
md5.o \
*************** OBJS_COMMON = \
*** 81,86 ****
--- 83,89 ----
ifeq ($(with_openssl),yes)
OBJS_COMMON += \
+ cipher_openssl.o \
protocol_openssl.o \
sha2_openssl.o
else
diff --git a/src/common/cipher.c b/src/common/cipher.c
new file mode 100644
index ...908afe8
*** a/src/common/cipher.c
--- b/src/common/cipher.c
***************
*** 0 ****
--- 1,87 ----
+ /*-------------------------------------------------------------------------
+ *
+ * cipher.c
+ * Shared frontend/backend for cryptographic functions
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/common/cipher.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+ #ifndef FRONTEND
+ #include "postgres.h"
+ #else
+ #include "postgres_fe.h"
+ #endif
+
+ #include "common/cipher.h"
+ #ifdef USE_OPENSSL
+ #include "common/cipher_openssl.h"
+ #endif
+
+ /*
+ * Return a newly created cipher context. 'cipher' specifies cipher algorithm
+ * by identifer like PG_CIPHER_XXX.
+ */
+ PgCipherCtx *
+ pg_cipher_ctx_create(int cipher, uint8 *key, int klen)
+ {
+ PgCipherCtx *ctx = NULL;
+
+ if (cipher >= PG_MAX_CIPHER_ID)
+ return NULL;
+
+ #ifdef USE_OPENSSL
+ ctx = (PgCipherCtx *) palloc0(sizeof(PgCipherCtx));
+
+ ctx->encctx = ossl_cipher_ctx_create(cipher, key, klen, true);
+ ctx->decctx = ossl_cipher_ctx_create(cipher, key, klen, false);
+ #endif
+
+ return ctx;
+ }
+
+ void
+ pg_cipher_ctx_free(PgCipherCtx *ctx)
+ {
+ #ifdef USE_OPENSSL
+ ossl_cipher_ctx_free(ctx->encctx);
+ ossl_cipher_ctx_free(ctx->decctx);
+ #endif
+ }
+
+ bool
+ pg_cipher_encrypt(PgCipherCtx *ctx, const uint8 *in, int inlen,
+ uint8 *out, int *outlen, const uint8 *iv)
+ {
+ bool r = false;
+ #ifdef USE_OPENSSL
+ r = ossl_cipher_encrypt(ctx->encctx, in, inlen, out, outlen, iv);
+ #endif
+ return r;
+ }
+
+ bool
+ pg_cipher_decrypt(PgCipherCtx *ctx, const uint8 *in, int inlen,
+ uint8 *out, int *outlen, const uint8 *iv)
+ {
+ bool r = false;
+ #ifdef USE_OPENSSL
+ r = ossl_cipher_decrypt(ctx->decctx, in, inlen, out, outlen, iv);
+ #endif
+ return r;
+ }
+
+ bool
+ pg_HMAC_SHA512(const uint8 *key, const uint8 *in, int inlen,
+ uint8 *out)
+ {
+ bool r = false;
+ #ifdef USE_OPENSSL
+ r = ossl_HMAC_SHA512(key, in, inlen, out);
+ #endif
+ return r;
+ }
diff --git a/src/common/cipher_openssl.c b/src/common/cipher_openssl.c
new file mode 100644
index ...e8bc241
*** a/src/common/cipher_openssl.c
--- b/src/common/cipher_openssl.c
***************
*** 0 ****
--- 1,181 ----
+ /*-------------------------------------------------------------------------
+ * cipher_openssl.c
+ * Cryptographic function using OpenSSL
+ *
+ * This contains the common low-level functions needed in both frontend and
+ * backend, for implement the database encryption.
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/common/cipher_openssl.c
+ *
+ *-------------------------------------------------------------------------
+ */
+ #ifndef FRONTEND
+ #include "postgres.h"
+ #else
+ #include "postgres_fe.h"
+ #endif
+
+ #include "common/sha2.h"
+ #include "common/cipher_openssl.h"
+
+ #include
+ #include
+ #include
+ #include
+ #include
+
+ /*
+ * prototype for the EVP functions that return an algorithm, e.g.
+ * EVP_aes_128_cbc().
+ */
+ typedef const EVP_CIPHER *(*ossl_EVP_cipher_func) (void);
+
+ static bool ossl_initialized = false;
+
+ static ossl_EVP_cipher_func get_evp_aes_cbc(int klen);
+
+ static ossl_EVP_cipher_func
+ get_evp_aes_cbc(int klen)
+ {
+ switch (klen)
+ {
+ case PG_AES128_KEY_LEN:
+ return EVP_aes_128_cbc;
+ case PG_AES192_KEY_LEN:
+ return EVP_aes_192_cbc;
+ case PG_AES256_KEY_LEN:
+ return EVP_aes_256_cbc;
+ default:
+ return NULL;
+ }
+ }
+
+ /*
+ * Initialize and return an EVP_CIPHER_CTX. Return NULL if the given
+ * cipher algorithm is not supported or on failure..
+ */
+ EVP_CIPHER_CTX *
+ ossl_cipher_ctx_create(int cipher, uint8 *key, int klen, bool enc)
+ {
+ EVP_CIPHER_CTX *ctx;
+ ossl_EVP_cipher_func func;
+ int ret;
+
+ if (!ossl_initialized)
+ {
+ #ifdef HAVE_OPENSSL_INIT_SSL
+ OPENSSL_init_ssl(OPENSSL_INIT_LOAD_CONFIG, NULL);
+ #else
+ OPENSSL_config(NULL);
+ SSL_library_init();
+ SSL_load_error_strings();
+ #endif
+ ossl_initialized = true;
+ }
+
+ ctx = EVP_CIPHER_CTX_new();
+
+ switch (cipher)
+ {
+ case PG_CIPHER_AES_CBC:
+ func = get_evp_aes_cbc(klen);
+ if (!func)
+ goto failed;
+ break;
+ default:
+ goto failed;
+ }
+
+
+ if (enc)
+ ret = EVP_EncryptInit_ex(ctx, (const EVP_CIPHER *) func(), NULL, key, NULL);
+ else
+ ret = EVP_DecryptInit_ex(ctx, (const EVP_CIPHER *) func(), NULL, key, NULL);
+
+ if (!ret)
+ goto failed;
+
+ if (!EVP_CIPHER_CTX_set_key_length(ctx, PG_AES256_KEY_LEN))
+ goto failed;
+
+ /*
+ * Always enable padding. We don't need to check the return value as
+ * EVP_CIPHER_CTX_set_padding always returns 1.
+ */
+ EVP_CIPHER_CTX_set_padding(ctx, 1);
+
+ return ctx;
+
+ failed:
+ EVP_CIPHER_CTX_free(ctx);
+ return NULL;
+ }
+
+ void
+ ossl_cipher_ctx_free(EVP_CIPHER_CTX *ctx)
+ {
+ return EVP_CIPHER_CTX_free(ctx);
+ }
+
+ bool
+ ossl_cipher_encrypt(EVP_CIPHER_CTX *ctx,
+ const uint8 *in, int inlen,
+ uint8 *out, int *outlen,
+ const uint8 *iv)
+ {
+ int len;
+ int enclen;
+
+ if (!EVP_EncryptInit_ex(ctx, NULL, NULL, NULL, iv))
+ return false;
+
+ if (!EVP_EncryptUpdate(ctx, out, &len, in, inlen))
+ return false;
+
+ enclen = len;
+
+ if (!EVP_EncryptFinal_ex(ctx, (uint8 *) ((char *) out + enclen),
+ &len))
+ return false;
+
+ *outlen = enclen + len;
+
+ return true;
+ }
+
+ bool
+ ossl_cipher_decrypt(EVP_CIPHER_CTX *ctx,
+ const uint8 *in, int inlen,
+ uint8 *out, int *outlen,
+ const uint8 *iv)
+ {
+ int declen;
+ int len;
+
+ if (!EVP_DecryptInit_ex(ctx, NULL, NULL, NULL, iv))
+ return false;
+
+ if (!EVP_DecryptUpdate(ctx, out, &len, in, inlen))
+ return false;
+
+ declen = len;
+
+ if (!EVP_DecryptFinal_ex(ctx, (uint8 *) ((char *) out + declen),
+ &len))
+ return false;
+
+ *outlen = declen + len;
+
+ return true;
+ }
+
+ bool
+ ossl_HMAC_SHA512(const uint8 *key, const uint8 *in, int inlen,
+ uint8 *out)
+ {
+ return HMAC(EVP_sha512(), key, PG_SHA512_DIGEST_LENGTH,
+ in, (uint32) inlen, out, NULL);
+ }
diff --git a/src/common/kmgr_utils.c b/src/common/kmgr_utils.c
new file mode 100644
index ...e4fcab3
*** a/src/common/kmgr_utils.c
--- b/src/common/kmgr_utils.c
***************
*** 0 ****
--- 1,524 ----
+ /*-------------------------------------------------------------------------
+ *
+ * kmgr_utils.c
+ * Shared frontend/backend for cluster file encryption
+ *
+ * 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
+ #include
+
+ #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 "postmaster/postmaster.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 passphrase: "
+
+ #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->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, file descriptor for %R
+ *
+ * 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':
+ strlcpy(dp, KMGR_PROMPT_MSG, strlen(KMGR_PROMPT_MSG)+1);
+ dp += strlen(KMGR_PROMPT_MSG);
+ p++;
+ break;
+ case 'R':
+ /* Safe from overflow? XXX */
+ dp += sprintf(dp, "%d", terminal_fd);
+ 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 (strspn(de->d_name, "0123456789") == strlen(de->d_name))
+ {
+ uint32 id = strtoul(de->d_name, NULL, 10);
+
+ 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/catalog/pg_control.h b/src/include/catalog/pg_control.h
new file mode 100644
index 06bed90..a4c1259
*** a/src/include/catalog/pg_control.h
--- b/src/include/catalog/pg_control.h
***************
*** 22,28 ****
/* Version identifier for this pg_control format */
! #define PG_CONTROL_VERSION 1300
/* Nonce key length, see below */
#define MOCK_AUTH_NONCE_LEN 32
--- 22,28 ----
/* Version identifier for this pg_control format */
! #define PG_CONTROL_VERSION 1400
/* Nonce key length, see below */
#define MOCK_AUTH_NONCE_LEN 32
*************** typedef struct ControlFileData
*** 226,231 ****
--- 226,234 ----
*/
char mock_authentication_nonce[MOCK_AUTH_NONCE_LEN];
+ /* File encryption key length. Zero if disabled. */
+ int file_encryption_keylen;
+
/* 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
new file mode 100644
index e7fbda9..67bfb84
*** a/src/include/catalog/pg_proc.dat
--- b/src/include/catalog/pg_proc.dat
***************
*** 11008,11012 ****
{ oid => '4351', descr => 'check Unicode normalization',
proname => 'is_normalized', prorettype => 'bool', proargtypes => 'text text',
prosrc => 'unicode_is_normalized' },
-
- ]
--- 11008,11010 ----
diff --git a/src/include/common/cipher.h b/src/include/common/cipher.h
new file mode 100644
index ...f782791
*** a/src/include/common/cipher.h
--- b/src/include/common/cipher.h
***************
*** 0 ****
--- 1,78 ----
+ /*-------------------------------------------------------------------------
+ *
+ * cipher.h
+ * Declarations for cryptographic functions
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * src/include/common/cipher.h
+ *
+ *-------------------------------------------------------------------------
+ */
+ #ifndef CIPHER_H
+ #define CIPHER_H
+
+ #ifdef USE_OPENSSL
+ #include
+ #include
+ #include
+ #endif
+
+ /*
+ * Supported symmetric encryption algorithm. These identifiers are passed
+ * to pg_cipher_ctx_create() function, and then actual encryption
+ * implementations need to initialize their context of the given encryption
+ * algorithm.
+ */
+ #define PG_CIPHER_AES_CBC 0
+ #define PG_MAX_CIPHER_ID 1
+
+ /* AES128/192/256 various length definitions */
+ #define PG_AES128_KEY_LEN (128 / 8)
+ #define PG_AES192_KEY_LEN (192 / 8)
+ #define PG_AES256_KEY_LEN (256 / 8)
+
+ /*
+ * The encrypted data is a series of blocks of size. Initialization
+ * vector(IV) is the same size of cipher block.
+ */
+ #define PG_AES_BLOCK_SIZE 16
+ #define PG_AES_IV_SIZE (PG_AES_BLOCK_SIZE)
+
+ /* HMAC key and HMAC length. We use HMAC-SHA256 */
+ #define PG_HMAC_SHA512_KEY_LEN 64
+ #define PG_HMAC_SHA512_LEN 64
+
+ #ifdef USE_OPENSSL
+ typedef EVP_CIPHER_CTX cipher_private_ctx;
+ #else
+ typedef void cipher_private_ctx;
+ #endif
+
+ /*
+ * This struct has two implementation-private context for
+ * encryption and decryption. The caller must create the encryption
+ * context using by pg_cipher_ctx_create() and pass the context to
+ * pg_cipher_encrypt() or pg_cipher_decrypt().
+ */
+ typedef struct PgCipherCtx
+ {
+ cipher_private_ctx *encctx;
+ cipher_private_ctx *decctx;
+ } PgCipherCtx;
+
+ extern PgCipherCtx *pg_cipher_ctx_create(int cipher, uint8 *key, int klen);
+ extern void pg_cipher_ctx_free(PgCipherCtx *ctx);
+ extern bool pg_cipher_encrypt(PgCipherCtx *ctx,
+ const uint8 *in, int inlen,
+ uint8 *out, int *outlen,
+ const uint8 *iv);
+ extern bool pg_cipher_decrypt(PgCipherCtx *ctx,
+ const uint8 *in, int inlen,
+ uint8 *out, int *outlen,
+ const uint8 *iv);
+ extern bool pg_HMAC_SHA512(const uint8 *key,
+ const uint8 *in, int inlen,
+ uint8 *out);
+
+ #endif /* CIPHER_H */
diff --git a/src/include/common/cipher_openssl.h b/src/include/common/cipher_openssl.h
new file mode 100644
index ...0fd1308
*** a/src/include/common/cipher_openssl.h
--- b/src/include/common/cipher_openssl.h
***************
*** 0 ****
--- 1,37 ----
+ /*-------------------------------------------------------------------------
+ *
+ * cipher_openssl.h
+ * Declarations for helper functions using OpenSSL
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * src/include/common/cipher_openssl.h
+ *
+ *-------------------------------------------------------------------------
+ */
+ #ifndef CIPHER_OPENSSL_H
+ #define CIPHER_OPENSSL_H
+
+ #ifndef FRONTEND
+ #include "postgres.h"
+ #else
+ #include "postgres_fe.h"
+ #endif
+
+ #include "common/cipher.h"
+
+ extern EVP_CIPHER_CTX *ossl_cipher_ctx_create(int cipher, uint8 *key, int klen,
+ bool enc);
+ extern void ossl_cipher_ctx_free(EVP_CIPHER_CTX *ctx);
+ extern bool ossl_cipher_encrypt(EVP_CIPHER_CTX *ctx,
+ const uint8 *in, int inlen,
+ uint8 *out, int *outlen,
+ const uint8 *iv);
+ extern bool ossl_cipher_decrypt(EVP_CIPHER_CTX *ctx,
+ const uint8 *in, int inlen,
+ uint8 *out, int *outlen,
+ const uint8 *iv);
+ extern bool ossl_HMAC_SHA512(const uint8 *key,
+ const uint8 *in, int inlen,
+ uint8 *out);
+ #endif
diff --git a/src/include/common/kmgr_utils.h b/src/include/common/kmgr_utils.h
new file mode 100644
index ...62bc24b
*** a/src/include/common/kmgr_utils.h
--- b/src/include/common/kmgr_utils.h
***************
*** 0 ****
--- 1,105 ----
+ /*-------------------------------------------------------------------------
+ *
+ * kmgr_utils.h
+ * Declarations for utility function for file encryption key
+ *
+ * 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.
+ */
+ #define KMGR_REL_KEY_ID 0
+ #define KMGR_WAL_KEY_ID 1
+ #define KMGR_MAX_INTERNAL_KEYS 2
+
+ /* Encryption key and MAC key used for key wrapping */
+ /* Though the data key length can be shorter, we always encrypt with AES256 */
+ #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 256
+ #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/%d", (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 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 ...5464af1
*** a/src/include/crypto/kmgr.h
--- b/src/include/crypto/kmgr.h
***************
*** 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 int file_encryption_keylen;
+ 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/pg_config.h.in b/src/include/pg_config.h.in
new file mode 100644
index de8f838..338dbe1
*** a/src/include/pg_config.h.in
--- b/src/include/pg_config.h.in
***************
*** 385,390 ****
--- 385,393 ----
/* Define to 1 if you have the `OPENSSL_init_ssl' function. */
#undef HAVE_OPENSSL_INIT_SSL
+ /* Define to 1 if you have the `OPENSSL_init_crypto' function. */
+ #undef HAVE_OPENSSL_INIT_CRYPTO
+
/* Define to 1 if you have the header file. */
#undef HAVE_OSSP_UUID_H
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
new file mode 100644
index 257e515..cf98577
*** a/src/include/pgstat.h
--- b/src/include/pgstat.h
*************** typedef enum
*** 1004,1009 ****
--- 1004,1012 ----
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/postmaster/postmaster.h b/src/include/postmaster/postmaster.h
new file mode 100644
index babc87d..b1f0721
*** a/src/include/postmaster/postmaster.h
--- b/src/include/postmaster/postmaster.h
*************** extern bool enable_bonjour;
*** 30,35 ****
--- 30,37 ----
extern char *bonjour_name;
extern bool restart_after_crash;
+ extern int terminal_fd;
+
#ifdef WIN32
extern HANDLE PostmasterHandle;
#else
diff --git a/src/include/utils/guc_tables.h b/src/include/utils/guc_tables.h
new file mode 100644
index 7f36e11..c0dbf69
*** a/src/include/utils/guc_tables.h
--- b/src/include/utils/guc_tables.h
*************** enum config_group
*** 89,94 ****
--- 89,95 ----
STATS,
STATS_MONITORING,
STATS_COLLECTOR,
+ ENCRYPTION,
AUTOVACUUM,
CLIENT_CONN,
CLIENT_CONN_STATEMENT,
diff --git a/src/test/Makefile b/src/test/Makefile
new file mode 100644
index 14cde4f..7db4c0b
*** a/src/test/Makefile
--- b/src/test/Makefile
*************** endif
*** 30,36 ****
endif
ifeq ($(with_openssl),yes)
ifneq (,$(filter ssl,$(PG_TEST_EXTRA)))
! SUBDIRS += ssl
endif
endif
--- 30,36 ----
endif
ifeq ($(with_openssl),yes)
ifneq (,$(filter ssl,$(PG_TEST_EXTRA)))
! SUBDIRS += ssl crypto
endif
endif