diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
new file mode 100644
index 4b60382..e469515
*** a/doc/src/sgml/config.sgml
--- b/doc/src/sgml/config.sgml
*************** COPY postgres_log FROM '/full/path/to/lo
*** 7816,7821 ****
--- 7816,7867 ----
+
+ Cluster File Encryption
+
+
+
+ cluster_key_command (string)
+
+ cluster_key_command configuration parameter
+
+
+
+
+ This option specifies an external command to obtain the cluster-level
+ key for cluster file encryption during server initialization and
+ server start.
+
+
+ The command can prompt for the passphrase or PIN from the terminal
+ if is used, and must exit with
+ code 0. In the parameter value, %R represents
+ the file descriptor number opened to the terminal that started
+ the server. This is useful for prompting from the terminal.
+ A file descriptor is only available if enabled at server start. If
+ %R is used and no file descriptor is available,
+ the server will not start. Value %P is replaced
+ by a pre-defined prompt string. Value %d is
+ replaced by the directory containing the keys; this is useful
+ if the command must create files with the keys, e.g., to store a
+ cluster-level key encryped by a key stored in a hardware security module.
+ (Write %% for a literal %.)
+ Note that the prompt string will probably contain whitespace, so
+ be sure to quote its use adequately. Newline are stripped
+ from the end of the output if present. The output key must
+ be 64 hexadecimal characters.
+
+
+ This parameter can only be set by
+ initdb, in the
+ postgresql.conf file, or on the server
+ command line.
+
+
+
+
+
+
Client Connection Defaults
*************** dynamic_library_path = 'C:\tools\postgre
*** 9636,9641 ****
--- 9682,9703 ----
+
+
+ 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 ...fadb2bc
*** 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 read
+ 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 used 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 key
+ 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 key command
+ passed to initdb must return a key of
+ 64 hexadecimal characters. For example.
+
+ initdb -D dbname --cluster-key-command="cat /path/to/cluster-key-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 key-enryption key (KEK) supplied by the cluster key
+ command before being stored in the database directory. The key or
+ passphrase that derives the key 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 cluster key, each time the
+ server is started the postgresql.conf
+ cluster_key_command command will be executed
+ and the cluster key retrieved. The data encryption keys in the
+ pg_cryptokeys directory will then be decrypted
+ using the supplied key and integrity-checked to see if it
+ matches the initdb-supplied key. If this check fails, 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-SHA512
+ 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
+ RFC 2315.
+
+
+ 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..12c1073
*** a/doc/src/sgml/ref/initdb.sgml
--- b/doc/src/sgml/ref/initdb.sgml
*************** PostgreSQL documentation
*** 163,168 ****
--- 163,198 ----
+
+
+
+
+ This option specifies an external command to obtain the cluster-level
+ key for cluster file encryption during server initialization and
+ server start.
+
+
+ The command can prompt for the passphrase or PIN from the terminal
+ if is used, and must exit with
+ code 0. In the parameter value, %R represents
+ the file descriptor number opened to the terminal that started
+ the server. This is useful for prompting from the terminal.
+ A file descriptor is only available if enabled at server start. If
+ %R is used and no file descriptor is available,
+ the server will not start. Value %P is replaced
+ by a pre-defined prompt string. Value %d is
+ replaced by the directory containing the keys; this is useful
+ if the command must create files with the keys, e.g., to store a
+ cluster-level key encryped by a key stored in a hardware security module.
+ (Write %% for a literal %.)
+ Note that the prompt string will probably contain whitespace, so
+ be sure to quote its use adequately. Newlines are stripped
+ from the end of the output if present. The output key must
+ be 64 hexadecimal characters.
+
+
+
+
*************** PostgreSQL documentation
*** 223,228 ****
--- 253,270 ----
+
+
+
+
+
+ Specifies the number of bits for the file encryption keys. The
+ default is 128 bits.
+
+
+
+
*************** PostgreSQL documentation
*** 286,291 ****
--- 328,344 ----
+
+
+
+
+ Allows the command
+ to prompt for a passphrase or PIN.
+
+
+
+
+
*************** PostgreSQL documentation
*** 306,311 ****
--- 359,376 ----
+
+
+
+
+
+
+ Copies cluster file encryption keys from another cluster; required
+ when using pg_upgrade on a cluster
+ with cluster file encryption enabled.
+
+
+
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..809e966
*** 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 ----
+
+
+
+
+
+
+ Allows the command
+ to prompt for a passphrase or PIN.
+ reporting.
+
+
+
diff --git a/doc/src/sgml/ref/pgupgrade.sgml b/doc/src/sgml/ref/pgupgrade.sgml
new file mode 100644
index 92e1d09..98be392
*** a/doc/src/sgml/ref/pgupgrade.sgml
--- b/doc/src/sgml/ref/pgupgrade.sgml
*************** PostgreSQL documentation
*** 168,173 ****
--- 168,180 ----
+
+
+ allows prompting for a passphrase or PIN
+
+
+
+
dir
dir
directory to use for postmaster sockets during upgrade;
*************** make prefix=/usr/local/pgsql.new install
*** 309,315 ****
Again, use compatible initdb
flags that match the old cluster. Many
prebuilt installers do this step automatically. There is no need to
! start the new cluster.
--- 316,324 ----
Again, use compatible initdb
flags that match the old cluster. Many
prebuilt installers do this step automatically. There is no need to
! start the new cluster. If upgrading a cluster that uses
! cluster file encryption, the initdb option
! must be specified.
*************** psql --username=postgres --file=script.s
*** 838,843 ****
--- 847,859 ----
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_key_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..805da81
*** a/doc/src/sgml/ref/postgres-ref.sgml
--- b/doc/src/sgml/ref/postgres-ref.sgml
*************** PostgreSQL documentation
*** 298,303 ****
--- 298,316 ----
+
+
+
+ Makes postgres prompt for a passphrase or PIN
+ from the specified open numeric file descriptor. The descriptor
+ is closed after the key is read. The file descriptor number
+ -1 duplicates standard error for the terminal;
+ this is useful for single-user mode.
+
+
+
+
+
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 b1e5d2d..3059536
*** 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)
*** 4618,4623 ****
--- 4621,4627 ----
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)
*** 4717,4722 ****
--- 4721,4727 ----
pg_crc32c crc;
int fd;
static char wal_segsz_str[20];
+ static char file_encryption_keylen_str[20];
int r;
/*
*************** ReadControlFile(void)
*** 4905,4910 ****
--- 4910,4921 ----
/* 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)
*** 5354,5359 ****
--- 5365,5379 ----
/* 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..bf93135
*** 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,60 ----
#include "utils/relmapper.h"
uint32 bootstrap_data_checksum_version = 0; /* No checksum */
+ int bootstrap_file_encryption_keylen = 0; /* disabled */
+ char *bootstrap_old_key_datadir = NULL; /* 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)
{
--- 228,234 ----
/* If no -x argument, we are a CheckerProcess */
MyAuxProcType = CheckerProcess;
! while ((flag = getopt(argc, argv, "B:c:d:D:FkK:r:R:u:x:X:-:")) != -1)
{
switch (flag)
{
*************** AuxiliaryProcessMain(int argc, char *arg
*** 253,261 ****
--- 257,274 ----
case 'k':
bootstrap_data_checksum_version = PG_DATA_CHECKSUM_VERSION;
break;
+ case 'K':
+ bootstrap_file_encryption_keylen = atoi(optarg);
+ break;
+ case 'u':
+ bootstrap_old_key_datadir = pstrdup(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 ****
--- 325,336 ----
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 ...6673689
*** a/src/backend/crypto/kmgr.c
--- b/src/backend/crypto/kmgr.c
***************
*** 0 ****
--- 1,364 ----
+ /*-------------------------------------------------------------------------
+ *
+ * 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
+ * the cluster-level key, 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 depend 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 "common/file_perm.h"
+ #include "common/kmgr_utils.h"
+ #include "common/sha2.h"
+ #include "access/xlog.h"
+ #include "crypto/kmgr.h"
+ #include "storage/copydir.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_key_command = NULL;
+ int file_encryption_keylen = 0;
+
+ CryptoKey bootstrap_keys[KMGR_MAX_INTERNAL_KEYS];
+
+ extern char *bootstrap_old_key_datadir;
+ extern int bootstrap_file_encryption_keylen;
+
+ static void bzeroKmgrKeys(int status, Datum arg);
+ 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)
+ {
+ char live_path[MAXPGPATH];
+ CryptoKey *keys_wrap;
+ int nkeys;
+ char cluster_key[ALLOC_KMGR_CLUSTER_KEY_LEN];
+ int passlen;
+
+ #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
+
+ snprintf(live_path, sizeof(live_path), "%s/%s", DataDir, LIVE_KMGR_DIR);
+
+ /* copy cluster file encryption keys from an old cluster? */
+ if (bootstrap_old_key_datadir != NULL)
+ {
+ char old_key_dir[MAXPGPATH];
+
+ snprintf(old_key_dir, sizeof(old_key_dir), "%s/%s",
+ bootstrap_old_key_datadir, LIVE_KMGR_DIR);
+ copydir(old_key_dir, LIVE_KMGR_DIR, true);
+ }
+ /* create empty directory */
+ else
+ {
+ if (mkdir(LIVE_KMGR_DIR, pg_dir_create_mode) < 0)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not create cluster file encryption directory \"%s\": %m",
+ LIVE_KMGR_DIR)));
+ }
+
+ /*
+ * Get key encryption key from the cluster_key command. The cluster_key
+ * command might want to check for the existance of files in the
+ * live directory, so run this _after_ copying the directory in place.
+ */
+ passlen = kmgr_run_cluster_key_command(cluster_key_command,
+ cluster_key, ALLOC_KMGR_CLUSTER_KEY_LEN,
+ live_path);
+ if (passlen != KMGR_CLUSTER_KEY_LEN)
+ ereport(ERROR,
+ (errmsg("cluster key must be %d hexadecimal characters",
+ KMGR_CLUSTER_KEY_LEN)));
+
+ /* generate new cluster file encryption keys */
+ if (bootstrap_old_key_datadir == NULL)
+ {
+ CryptoKey bootstrap_keys_wrap[KMGR_MAX_INTERNAL_KEYS];
+ PgKeyWrapCtx *ctx;
+ uint8 KEK_enc[KMGR_ENC_KEY_LEN];
+ uint8 KEK_hmac[KMGR_MAC_KEY_LEN];
+
+ /* Get key encryption key and HMAC key from cluster key */
+ kmgr_derive_keys(cluster_key, 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(bootstrap_file_encryption_keylen);
+
+ if (!kmgr_wrap_key(ctx, key, &(bootstrap_keys_wrap[id])))
+ {
+ pg_free_keywrap_ctx(ctx);
+ elog(ERROR, "failed to wrap data encryption key");
+ }
+
+ explicit_bzero(key, sizeof(CryptoKey));
+ }
+
+ /* Save data encryption keys to the disk */
+ KmgrSaveCryptoKeys(LIVE_KMGR_DIR, bootstrap_keys_wrap);
+
+ explicit_bzero(bootstrap_keys_wrap, sizeof(bootstrap_keys_wrap));
+ pg_free_keywrap_ctx(ctx);
+ }
+
+ /*
+ * We are either decrypting keys we copied from an old cluster, or
+ * decrypting keys we just wrote above --- either way, we decrypt
+ * them here and store them in a file-scoped variable for use in
+ * later encrypting during bootstrap mode.
+ */
+ /* Get the crypto keys from the file */
+ keys_wrap = kmgr_get_cryptokeys(LIVE_KMGR_DIR, &nkeys);
+ Assert(nkeys == KMGR_MAX_INTERNAL_KEYS);
+
+ if (!kmgr_verify_cluster_key(cluster_key, passlen, keys_wrap, bootstrap_keys,
+ KMGR_MAX_INTERNAL_KEYS))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cluster key does not match expected cluster_key")));
+
+ /* bzero keys on exit */
+ on_proc_exit(bzeroKmgrKeys, 0);
+
+ explicit_bzero(cluster_key, passlen);
+ }
+
+ /* 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);
+
+ on_shmem_exit(bzeroKmgrKeys, 0);
+ }
+
+ /*
+ * Get cluster key and verify it, then get the data encryption keys.
+ * This function is called by postmaster at startup time.
+ */
+ void
+ InitializeKmgr(void)
+ {
+ CryptoKey *keys_wrap;
+ int nkeys;
+ char cluster_key[ALLOC_KMGR_CLUSTER_KEY_LEN];
+ int passlen;
+ struct stat buffer;
+ char live_path[MAXPGPATH];
+
+ if (!file_encryption_keylen)
+ return;
+
+ elog(DEBUG1, "starting up cluster file encryption manager");
+
+ if (stat(KMGR_DIR, &buffer) != 0 || !S_ISDIR(buffer.st_mode))
+ ereport(ERROR,
+ (errcode(ERRCODE_INTERNAL_ERROR),
+ (errmsg("cluster file encryption directory %s is missing", KMGR_DIR))));
+
+ if (stat(KMGR_DIR_PID, &buffer) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INTERNAL_ERROR),
+ (errmsg("cluster had a pg_alterckey failure that needs repair or pg_alterckey is running"),
+ errhint("Run pg_alterckey --repair or wait for it to complete."))));
+
+ /*
+ * We want OLD deleted since it allows access to the data encryption
+ * keys using the old cluster key. If NEW exists, it means either
+ * NEW is partly written, or NEW wasn't renamed to LIVE --- in either
+ * case, it needs to be repaired.
+ */
+ if (stat(OLD_KMGR_DIR, &buffer) == 0 || stat(NEW_KMGR_DIR, &buffer) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INTERNAL_ERROR),
+ (errmsg("cluster had a pg_alterckey failure that needs repair"),
+ errhint("Run pg_alterckey --repair."))));
+
+ /* If OLD, NEW, and LIVE do not exist, there is a serious problem. */
+ if (stat(LIVE_KMGR_DIR, &buffer) != 0 || !S_ISDIR(buffer.st_mode))
+ ereport(ERROR,
+ (errcode(ERRCODE_INTERNAL_ERROR),
+ (errmsg("cluster has no data encryption keys"))));
+
+ /* Get cluster key */
+ snprintf(live_path, sizeof(live_path), "%s/%s", DataDir, LIVE_KMGR_DIR);
+ passlen = kmgr_run_cluster_key_command(cluster_key_command,
+ cluster_key, ALLOC_KMGR_CLUSTER_KEY_LEN,
+ live_path);
+
+ if (passlen != KMGR_CLUSTER_KEY_LEN)
+ ereport(ERROR,
+ (errmsg("cluster_key must be %d hexadecimal characters",
+ KMGR_CLUSTER_KEY_LEN)));
+
+ /* Get the crypto keys from the file */
+ keys_wrap = kmgr_get_cryptokeys(LIVE_KMGR_DIR, &nkeys);
+ Assert(nkeys == KMGR_MAX_INTERNAL_KEYS);
+
+ /*
+ * Verify cluster key and prepare a data encryption key in plaintext in shared memory.
+ */
+ if (!kmgr_verify_cluster_key(cluster_key, passlen, keys_wrap, KmgrShmem->intlKeys,
+ KMGR_MAX_INTERNAL_KEYS))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cluster key does not match expected cluster key")));
+
+ explicit_bzero(cluster_key, passlen);
+ }
+
+ static void
+ bzeroKmgrKeys(int status, Datum arg)
+ {
+ if (IsBootstrapProcessingMode())
+ explicit_bzero(bootstrap_keys, sizeof(bootstrap_keys));
+ else
+ explicit_bzero(KmgrShmem->intlKeys, sizeof(KmgrShmem->intlKeys));
+ }
+
+ const CryptoKey *
+ KmgrGetKey(int id)
+ {
+ Assert(id < KMGR_MAX_INTERNAL_KEYS);
+
+ return (const CryptoKey *) (IsBootstrapProcessingMode() ?
+ &(bootstrap_keys[id]) : &(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/main/main.c b/src/backend/main/main.c
new file mode 100644
index b6e5128..19aa502
*** 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 key\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,357 ****
--- 352,360 ----
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(_(" -u DATADIR copy encryption keys from datadir\n"));
printf(_(" -x NUM internal use\n"));
printf(_("\nPlease read the documentation for the complete list of run-time\n"
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
new file mode 100644
index d87d9d0..71f2b90
*** a/src/backend/postmaster/pgstat.c
--- b/src/backend/postmaster/pgstat.c
*************** pgstat_get_wait_io(WaitEventIO w)
*** 4152,4157 ****
--- 4152,4166 ----
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 5d09822..297b3fc
*** 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[])
*** 1325,1330 ****
--- 1331,1340 ----
*/
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 1d8d174..c9fc2f4
*** 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,162 ----
*/
static const char *const excludeDirContents[] =
{
+ /* Skip temporary crypto key directories */
+ NEW_KMGR_DIR,
+ OLD_KMGR_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..0c1d038
*** 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,3667 ----
strlcpy(OutputFileName, optarg, MAXPGPATH);
break;
+ case 'R':
+ terminal_fd = atoi(optarg);
+ if (terminal_fd == -1)
+ /*
+ * Allow file descriptor closing to be bypassed via -1.
+ * We just dup sterr. This is useful for single-user mode.
+ */
+ terminal_fd = dup(2);
+ break;
+
case 'S':
SetConfigOption("work_mem", optarg, ctx, gucsource);
break;
*************** PostgresMain(int argc, char *argv[],
*** 3903,3908 ****
--- 3914,3930 ----
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/adt/encode.c b/src/backend/utils/adt/encode.c
new file mode 100644
index a609d49..b2c9f83
*** a/src/backend/utils/adt/encode.c
--- b/src/backend/utils/adt/encode.c
***************
*** 15,20 ****
--- 15,21 ----
#include
+ #include "common/hex_decode.h"
#include "mb/pg_wchar.h"
#include "utils/builtins.h"
#include "utils/memutils.h"
*************** binary_decode(PG_FUNCTION_ARGS)
*** 146,162 ****
static const char hextbl[] = "0123456789abcdef";
- static const int8 hexlookup[128] = {
- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
- -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- };
-
uint64
hex_encode(const char *src, size_t len, char *dst)
{
--- 147,152 ----
*************** hex_encode(const char *src, size_t len,
*** 171,228 ****
return (uint64) len * 2;
}
- static inline char
- get_hex(const char *cp)
- {
- unsigned char c = (unsigned char) *cp;
- int res = -1;
-
- if (c < 127)
- res = hexlookup[c];
-
- if (res < 0)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("invalid hexadecimal digit: \"%.*s\"",
- pg_mblen(cp), cp)));
-
- return (char) res;
- }
-
- uint64
- hex_decode(const char *src, size_t len, char *dst)
- {
- const char *s,
- *srcend;
- char v1,
- v2,
- *p;
-
- srcend = src + len;
- s = src;
- p = dst;
- while (s < srcend)
- {
- if (*s == ' ' || *s == '\n' || *s == '\t' || *s == '\r')
- {
- s++;
- continue;
- }
- v1 = get_hex(s) << 4;
- s++;
- if (s >= srcend)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("invalid hexadecimal data: odd number of digits")));
-
- v2 = get_hex(s);
- s++;
- *p++ = v1 | v2;
- }
-
- return p - dst;
- }
-
static uint64
hex_enc_len(const char *src, size_t srclen)
{
--- 161,166 ----
*************** esc_dec_len(const char *src, size_t srcl
*** 552,557 ****
--- 490,501 ----
return len;
}
+ static uint64
+ common_hex_decode(const char *src, size_t len, char *dst)
+ {
+ return hex_decode(src, len, dst);
+ }
+
/*
* Common
*/
*************** static const struct
*** 566,572 ****
{
"hex",
{
! hex_enc_len, hex_dec_len, hex_encode, hex_decode
}
},
{
--- 510,516 ----
{
"hex",
{
! hex_enc_len, hex_dec_len, hex_encode, common_hex_decode
}
},
{
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
new file mode 100644
index ff9bf23..9300d19
*** a/src/backend/utils/adt/varlena.c
--- b/src/backend/utils/adt/varlena.c
***************
*** 22,27 ****
--- 22,28 ----
#include "catalog/pg_type.h"
#include "common/hashfn.h"
#include "common/int.h"
+ #include "common/hex_decode.h"
#include "common/unicode_norm.h"
#include "lib/hyperloglog.h"
#include "libpq/pqformat.h"
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
new file mode 100644
index dabcbb0..c617369
*** 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
*** 3389,3394 ****
--- 3392,3408 ----
check_huge_page_size, NULL, NULL
},
+ {
+ {"file_encryption_keylen", PGC_INTERNAL, PRESET_OPTIONS,
+ gettext_noop("Shows 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
*** 4382,4387 ****
--- 4396,4411 ----
"",
NULL, NULL, NULL
},
+
+ {
+ {"cluster_key_command", PGC_SIGHUP, ENCRYPTION,
+ gettext_noop("Command to obtain cluster key for cluster file encryption."),
+ NULL
+ },
+ &cluster_key_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 b7fb2ec..f19cfe5
*** a/src/backend/utils/misc/postgresql.conf.sample
--- b/src/backend/utils/misc/postgresql.conf.sample
***************
*** 632,637 ****
--- 632,642 ----
# autovacuum, -1 means use
# vacuum_cost_limit
+ #------------------------------------------------------------------------------
+ # ENCRYPTION
+ #------------------------------------------------------------------------------
+
+ #cluster_key_command = ''
#------------------------------------------------------------------------------
# CLIENT CONNECTION DEFAULTS
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
new file mode 100644
index f994c42..d7a7c0e
*** a/src/bin/initdb/initdb.c
--- b/src/bin/initdb/initdb.c
*************** static bool debug = false;
*** 141,151 ****
--- 141,156 ----
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_key_cmd = NULL;
+ static char *old_key_datadir = NULL;
/* internal vars */
*************** static const char *const subdirs[] = {
*** 203,208 ****
--- 208,214 ----
"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);
--- 960,972 ----
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);
--- 997,1009 ----
}
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 ****
--- 1193,1205 ----
"password_encryption = md5");
}
+ if (cluster_key_cmd)
+ {
+ snprintf(repltok, sizeof(repltok), "cluster_key_command = '%s'",
+ escape_quotes(cluster_key_cmd));
+ conflines = replace_token(conflines, "#cluster_key_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;
--- 1409,1430 ----
/* 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 %s %s",
backend_exec,
wal_segment_size_mb * (1024 * 1024),
data_checksums ? "-k" : "",
+ cluster_key_cmd ? "-K" : "", buf,
+ old_key_datadir ? "-u" : "",
+ old_key_datadir ? old_key_datadir : "",
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"));
--- 2305,2329 ----
" 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-key-command=COMMAND\n"
+ " enable cluster file encryption and set command\n"
+ " to obtain the cluster key\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, --authprompt prompt the user for a passphrase or PIN\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 ****
--- 2890,2912 ----
/* 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;
--- 2930,2938 ----
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 ****
--- 3005,3015 ----
{"waldir", required_argument, NULL, 'X'},
{"wal-segsize", required_argument, NULL, 12},
{"data-checksums", no_argument, NULL, 'k'},
+ {"authprompt", no_argument, NULL, 'R'},
+ {"file-encryption-keylen", no_argument, NULL, 'K'},
{"allow-group-access", no_argument, NULL, 'g'},
+ {"cluster-key-command", required_argument, NULL, 'c'},
+ {"copy-encryption-keys", required_argument, NULL, 'u'},
{NULL, 0, NULL, 0}
};
*************** main(int argc, char *argv[])
*** 2999,3005 ****
/* process command-line options */
! while ((c = getopt_long(argc, argv, "A:dD:E:gkL:nNsST:U:WX:", long_options, &option_index)) != -1)
{
switch (c)
{
--- 3051,3057 ----
/* process command-line options */
! while ((c = getopt_long(argc, argv, "A:c:dD:E:gkK:L:nNRsST:u:U:WX:", long_options, &option_index)) != -1)
{
switch (c)
{
*************** main(int argc, char *argv[])
*** 3045,3050 ****
--- 3097,3108 ----
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 ****
--- 3139,3150 ----
case 9:
pwfilename = pg_strdup(optarg);
break;
+ case 'c':
+ cluster_key_cmd = pg_strdup(optarg);
+ break;
+ case 'u':
+ old_key_datadir = pg_strdup(optarg);
+ break;
case 's':
show_setting = true;
break;
*************** main(int argc, char *argv[])
*** 3151,3156 ****
--- 3215,3251 ----
exit(1);
}
+ #ifndef USE_OPENSSL
+ if (cluster_key_cmd)
+ {
+ pg_log_error("cluster file encryption is not supported because OpenSSL is not supported by this build");
+ exit(1);
+ }
+ #endif
+
+ if (old_key_datadir != NULL && cluster_key_cmd == NULL)
+ {
+ pg_log_error("copying encryption keys requires the cluster key command to be specified");
+ exit(1);
+ }
+
+ if (file_encryption_keylen != 0 && cluster_key_cmd == NULL)
+ {
+ pg_log_error("a non-zero file encryption key length requires the cluster key command to be specified");
+ 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);
+ }
+
+ /* set the default */
+ if (file_encryption_keylen == 0 && cluster_key_cmd != NULL)
+ file_encryption_keylen = 128;
+
check_authmethod_unspecified(&authmethodlocal);
check_authmethod_unspecified(&authmethodhost);
*************** main(int argc, char *argv[])
*** 3218,3223 ****
--- 3313,3323 ----
else
printf(_("Data page checksums are disabled.\n"));
+ if (cluster_key_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..5fa1f72
*** 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, --authprompt prompt for a paasphrase or PIN\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'},
+ {"authprompt", 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..b8775ca
*** a/src/bin/pg_rewind/filemap.c
--- b/src/bin/pg_rewind/filemap.c
***************
*** 28,33 ****
--- 28,34 ----
#include "catalog/pg_tablespace_d.h"
#include "common/hashfn.h"
+ #include "common/kmgr_utils.h"
#include "common/string.h"
#include "datapagemap.h"
#include "filemap.h"
*************** static const char *excludeDirContents[]
*** 108,113 ****
--- 109,121 ----
"pg_notify",
/*
+ * Skip cryptographic keys. It's generally not a good idea to copy the
+ * cryptographic keys from source database because these might use
+ * different cluster key.
+ */
+ KMGR_DIR,
+
+ /*
* Old contents are loaded for possible debugging but are not required for
* normal operation, see SerialInit().
*/
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
new file mode 100644
index 6685d51..ea92874
*** a/src/bin/pg_upgrade/check.c
--- b/src/bin/pg_upgrade/check.c
***************
*** 10,15 ****
--- 10,16 ----
#include "postgres_fe.h"
#include "catalog/pg_authid_d.h"
+ #include "common/kmgr_utils.h"
#include "fe_utils/string_utils.h"
#include "mb/pg_wchar.h"
#include "pg_upgrade.h"
*************** static void check_for_tables_with_oids(C
*** 27,32 ****
--- 28,34 ----
static void check_for_reg_data_type_usage(ClusterInfo *cluster);
static void check_for_jsonb_9_4_usage(ClusterInfo *cluster);
static void check_for_pg_role_prefix(ClusterInfo *cluster);
+ static void check_for_cluster_key_failure(ClusterInfo *cluster);
static void check_for_new_tablespace_dir(ClusterInfo *new_cluster);
static char *get_canonical_locale_name(int category, const char *locale);
*************** check_and_dump_old_cluster(bool live_che
*** 139,144 ****
--- 141,149 ----
if (GET_MAJOR_VERSION(old_cluster.major_version) <= 905)
check_for_pg_role_prefix(&old_cluster);
+ if (GET_MAJOR_VERSION(old_cluster.major_version) >= 1400)
+ check_for_cluster_key_failure(&old_cluster);
+
if (GET_MAJOR_VERSION(old_cluster.major_version) == 904 &&
old_cluster.controldata.cat_ver < JSONB_FORMAT_CHANGE_CAT_VER)
check_for_jsonb_9_4_usage(&old_cluster);
*************** check_new_cluster(void)
*** 173,178 ****
--- 178,186 ----
check_loadable_libraries();
+ if (GET_MAJOR_VERSION(old_cluster.major_version) >= 1400)
+ check_for_cluster_key_failure(&new_cluster);
+
switch (user_opts.transfer_mode)
{
case TRANSFER_MODE_CLONE:
*************** check_for_pg_role_prefix(ClusterInfo *cl
*** 1267,1272 ****
--- 1275,1306 ----
check_ok();
}
+
+
+ /*
+ * check_for_cluster_key_failure()
+ *
+ * Make sure there was no unrepaired pg_alterckey failure
+ */
+ static void
+ check_for_cluster_key_failure(ClusterInfo *cluster)
+ {
+ struct stat buffer;
+
+ if (stat (KMGR_DIR_PID, &buffer) == 0)
+ {
+ if (cluster == &old_cluster)
+ pg_fatal("The source cluster had a pg_alterckey failure that needs repair or\n"
+ "pg_alterckey is running. Run pg_alterckey --repair or wait for it\n"
+ "to complete.\n");
+ else
+ pg_fatal("The target cluster had a pg_alterckey failure that needs repair or\n"
+ "pg_alterckey is running. Run pg_alterckey --repair or wait for it\n"
+ "to complete.\n");
+ }
+
+ check_ok();
+ }
/*
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..c985119
*** 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"
diff --git a/src/bin/pg_upgrade/option.c b/src/bin/pg_upgrade/option.c
new file mode 100644
index 548d648..4702998
*** a/src/bin/pg_upgrade/option.c
--- b/src/bin/pg_upgrade/option.c
*************** parseCommandLine(int argc, char *argv[])
*** 52,57 ****
--- 52,58 ----
{"check", no_argument, NULL, 'c'},
{"link", no_argument, NULL, 'k'},
{"retain", no_argument, NULL, 'r'},
+ {"authprompt", no_argument, NULL, 'R'},
{"jobs", required_argument, NULL, 'j'},
{"socketdir", required_argument, NULL, 's'},
{"verbose", no_argument, NULL, 'v'},
*************** parseCommandLine(int argc, char *argv[])
*** 102,108 ****
if (os_user_effective_id == 0)
pg_fatal("%s: cannot be run as root\n", os_info.progname);
! while ((option = getopt_long(argc, argv, "d:D:b:B:cj:ko:O:p:P:rs:U:v",
long_options, &optindex)) != -1)
{
switch (option)
--- 103,109 ----
if (os_user_effective_id == 0)
pg_fatal("%s: cannot be run as root\n", os_info.progname);
! while ((option = getopt_long(argc, argv, "d:D:b:B:cj:ko:O:p:P:rRs:U:v",
long_options, &optindex)) != -1)
{
switch (option)
*************** parseCommandLine(int argc, char *argv[])
*** 180,185 ****
--- 181,190 ----
log_opts.retain = true;
break;
+ case 'R':
+ user_opts.pass_terminal_fd = true;
+ break;
+
case 's':
user_opts.socketdir = pg_strdup(optarg);
break;
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
new file mode 100644
index ee70243..53ce195
*** 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;
/*
*************** typedef struct
*** 293,298 ****
--- 295,301 ----
int jobs; /* number of processes/threads to use */
char *socketdir; /* directory to use for Unix sockets */
bool ind_coll_unknown; /* mark unknown index collation versions */
+ bool pass_terminal_fd; /* pass -R to pg_ctl? */
} UserOpts;
typedef struct
diff --git a/src/bin/pg_upgrade/server.c b/src/bin/pg_upgrade/server.c
new file mode 100644
index 713509f..9208ad0
*** a/src/bin/pg_upgrade/server.c
--- b/src/bin/pg_upgrade/server.c
*************** start_postmaster(ClusterInfo *cluster, b
*** 244,251 ****
* vacuumdb --freeze actually freezes the tuples.
*/
snprintf(cmd, sizeof(cmd),
! "\"%s/pg_ctl\" -w -l \"%s\" -D \"%s\" -o \"-p %d%s%s %s%s\" start",
! cluster->bindir, SERVER_LOG_FILE, cluster->pgconfig, cluster->port,
(cluster->controldata.cat_ver >=
BINARY_UPGRADE_SERVER_FLAG_CAT_VER) ? " -b" :
" -c autovacuum=off -c autovacuum_freeze_max_age=2000000000",
--- 244,252 ----
* vacuumdb --freeze actually freezes the tuples.
*/
snprintf(cmd, sizeof(cmd),
! "\"%s/pg_ctl\" -w%s -l \"%s\" -D \"%s\" -o \"-p %d%s%s %s%s\" start",
! cluster->bindir, user_opts.pass_terminal_fd ? " -R" : "",
! SERVER_LOG_FILE, cluster->pgconfig, cluster->port,
(cluster->controldata.cat_ver >=
BINARY_UPGRADE_SERVER_FLAG_CAT_VER) ? " -b" :
" -c autovacuum=off -c autovacuum_freeze_max_age=2000000000",
diff --git a/src/common/Makefile b/src/common/Makefile
new file mode 100644
index af891cb..9f5e9ca
*** a/src/common/Makefile
--- b/src/common/Makefile
*************** OBJS_COMMON = \
*** 58,66 ****
--- 58,68 ----
file_perm.o \
file_utils.o \
hashfn.o \
+ hex_decode.o \
ip.o \
jsonapi.o \
keywords.o \
+ kmgr_utils.o \
kwlookup.o \
link-canary.o \
md5_common.o \
*************** OBJS_COMMON = \
*** 81,91 ****
--- 83,97 ----
ifeq ($(with_openssl),yes)
OBJS_COMMON += \
+ cipher_openssl.o \
+ hmac_openssl.o \
protocol_openssl.o \
cryptohash_openssl.o
else
OBJS_COMMON += \
+ cipher.o \
cryptohash.o \
+ hmac.o \
md5.o \
sha2.o
endif
diff --git a/src/common/cipher.c b/src/common/cipher.c
new file mode 100644
index ...10f698c
*** a/src/common/cipher.c
--- b/src/common/cipher.c
***************
*** 0 ****
--- 1,63 ----
+ /*-------------------------------------------------------------------------
+ *
+ * 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"
+
+ static cipher_failure(void);
+
+ PgCipherCtx *
+ pg_cipher_ctx_create(int cipher, uint8 *key, int klen)
+ {
+ cipher_failure();
+ }
+
+ void
+ pg_cipher_ctx_free(PgCipherCtx *ctx)
+ {
+ cipher_failure();
+ }
+
+ bool
+ pg_cipher_encrypt(PgCipherCtx *ctx, const uint8 *in, int inlen,
+ uint8 *out, int *outlen, const uint8 *iv)
+ {
+ cipher_failure();
+ }
+
+ bool
+ pg_cipher_decrypt(PgCipherCtx *ctx, const uint8 *in, int inlen,
+ uint8 *out, int *outlen, const uint8 *iv)
+ {
+ cipher_failure();
+ }
+
+ static
+ cipher_failure(void)
+ {
+ #ifndef FRONTEND
+ 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."))));
+ #else
+ fprintf(stderr, _("cluster encryption is not supported because OpenSSL is not supported by this build"));
+ exit(1);
+ #endif
+ }
+
diff --git a/src/common/cipher_openssl.c b/src/common/cipher_openssl.c
new file mode 100644
index ...12b8ecf
*** a/src/common/cipher_openssl.c
--- b/src/common/cipher_openssl.c
***************
*** 0 ****
--- 1,175 ----
+ /*-------------------------------------------------------------------------
+ * 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/cipher.h"
+ #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 ossl_EVP_cipher_func get_evp_aes_cbc(int klen);
+ static EVP_CIPHER_CTX *ossl_cipher_ctx_create(int cipher, uint8 *key, int klen,
+ bool enc);
+
+ /*
+ * 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;
+
+ 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);
+
+ return ctx;
+ }
+
+ void
+ pg_cipher_ctx_free(PgCipherCtx *ctx)
+ {
+ EVP_CIPHER_CTX_free(ctx->encctx);
+ EVP_CIPHER_CTX_free(ctx->decctx);
+ }
+
+ bool
+ pg_cipher_encrypt(PgCipherCtx *ctx, const uint8 *in, int inlen,
+ uint8 *out, int *outlen, const uint8 *iv)
+ {
+ int len;
+ int enclen;
+
+ if (!EVP_EncryptInit_ex(ctx->encctx, NULL, NULL, NULL, iv))
+ return false;
+
+ if (!EVP_EncryptUpdate(ctx->encctx, out, &len, in, inlen))
+ return false;
+
+ enclen = len;
+
+ if (!EVP_EncryptFinal_ex(ctx->encctx, (uint8 *) ((char *) out + enclen),
+ &len))
+ return false;
+
+ *outlen = enclen + len;
+
+ return true;
+ }
+
+ bool
+ pg_cipher_decrypt(PgCipherCtx *ctx, const uint8 *in, int inlen,
+ uint8 *out, int *outlen, const uint8 *iv)
+ {
+ int declen;
+ int len;
+
+ if (!EVP_DecryptInit_ex(ctx->decctx, NULL, NULL, NULL, iv))
+ return false;
+
+ if (!EVP_DecryptUpdate(ctx->decctx, out, &len, in, inlen))
+ return false;
+
+ declen = len;
+
+ if (!EVP_DecryptFinal_ex(ctx->decctx, (uint8 *) ((char *) out + declen),
+ &len))
+ return false;
+
+ *outlen = declen + len;
+
+ return true;
+ }
+
+ 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..
+ */
+ static 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;
+
+ 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;
+ }
+
diff --git a/src/common/cryptohash_openssl.c b/src/common/cryptohash_openssl.c
new file mode 100644
index 118651c..b0b283e
*** a/src/common/cryptohash_openssl.c
--- b/src/common/cryptohash_openssl.c
***************
*** 25,30 ****
--- 25,31 ----
#include "common/cryptohash.h"
#ifndef FRONTEND
+ #include "miscadmin.h"
#include "utils/memutils.h"
#include "utils/resowner.h"
#include "utils/resowner_private.h"
*************** pg_cryptohash_create(pg_cryptohash_type
*** 85,91 ****
ctx->type = type;
#ifndef FRONTEND
! ResourceOwnerEnlargeCryptoHash(CurrentResourceOwner);
#endif
/*
--- 86,97 ----
ctx->type = type;
#ifndef FRONTEND
! /*
! * Allow cluster file encryption to use this in bootstrap mode and
! * early in server start.
! */
! if (CurrentResourceOwner)
! ResourceOwnerEnlargeCryptoHash(CurrentResourceOwner);
#endif
/*
*************** pg_cryptohash_create(pg_cryptohash_type
*** 109,117 ****
}
#ifndef FRONTEND
! state->resowner = CurrentResourceOwner;
! ResourceOwnerRememberCryptoHash(CurrentResourceOwner,
! PointerGetDatum(ctx));
#endif
return ctx;
--- 115,128 ----
}
#ifndef FRONTEND
! if (CurrentResourceOwner)
! {
! state->resowner = CurrentResourceOwner;
! ResourceOwnerRememberCryptoHash(CurrentResourceOwner,
! PointerGetDatum(ctx));
! }
! else
! state->resowner = NULL;
#endif
return ctx;
*************** pg_cryptohash_free(pg_cryptohash_ctx *ct
*** 221,228 ****
EVP_MD_CTX_destroy(state->evpctx);
#ifndef FRONTEND
! ResourceOwnerForgetCryptoHash(state->resowner,
! PointerGetDatum(ctx));
#endif
explicit_bzero(state, sizeof(pg_cryptohash_state));
--- 232,244 ----
EVP_MD_CTX_destroy(state->evpctx);
#ifndef FRONTEND
! /*
! * Allow cluster file encryption to use this in bootstrap mode and
! * early in server start.
! */
! if (state->resowner)
! ResourceOwnerForgetCryptoHash(state->resowner,
! PointerGetDatum(ctx));
#endif
explicit_bzero(state, sizeof(pg_cryptohash_state));
diff --git a/src/common/hex_decode.c b/src/common/hex_decode.c
new file mode 100644
index ...3ecdc73
*** a/src/common/hex_decode.c
--- b/src/common/hex_decode.c
***************
*** 0 ****
--- 1,106 ----
+ /*-------------------------------------------------------------------------
+ *
+ * hex_decode.c
+ * hex decoding
+ *
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/common/hex_decode.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+
+ #ifndef FRONTEND
+ #include "postgres.h"
+ #else
+ #include "postgres_fe.h"
+ #endif
+
+ #ifdef FRONTEND
+ #include "common/logging.h"
+ #else
+ #include "mb/pg_wchar.h"
+ #endif
+ #include "common/hex_decode.h"
+
+
+ static const int8 hexlookup[128] = {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
+ -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ };
+
+ static inline char
+ get_hex(const char *cp)
+ {
+ unsigned char c = (unsigned char) *cp;
+ int res = -1;
+
+ if (c < 127)
+ res = hexlookup[c];
+
+ if (res < 0)
+ {
+ #ifdef FRONTEND
+ pg_log_fatal("invalid hexadecimal digit");
+ exit(EXIT_FAILURE);
+ #else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid hexadecimal digit: \"%.*s\"",
+ pg_mblen(cp), cp)));
+ #endif
+ }
+
+ return (char) res;
+ }
+
+ uint64
+ hex_decode(const char *src, size_t len, char *dst)
+ {
+ const char *s,
+ *srcend;
+ char v1,
+ v2,
+ *p;
+
+ srcend = src + len;
+ s = src;
+ p = dst;
+ while (s < srcend)
+ {
+ if (*s == ' ' || *s == '\n' || *s == '\t' || *s == '\r')
+ {
+ s++;
+ continue;
+ }
+ v1 = get_hex(s) << 4;
+ s++;
+ if (s >= srcend)
+ {
+ #ifdef FRONTEND
+ pg_log_fatal("invalid hexadecimal data: odd number of digits");
+ exit(EXIT_FAILURE);
+ #else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid hexadecimal data: odd number of digits")));
+ #endif
+ }
+ v2 = get_hex(s);
+ s++;
+ *p++ = v1 | v2;
+ }
+
+ return p - dst;
+ }
diff --git a/src/common/hmac.c b/src/common/hmac.c
new file mode 100644
index ...6d8248c
*** a/src/common/hmac.c
--- b/src/common/hmac.c
***************
*** 0 ****
--- 1,35 ----
+ /*-------------------------------------------------------------------------
+ *
+ * hmac.c
+ * Shared frontend/backend for HMAC functions
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/common/hmac.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+ #ifndef FRONTEND
+ #include "postgres.h"
+ #else
+ #include "postgres_fe.h"
+ #endif
+
+ #include "common/hmac.h"
+
+ bool
+ pg_HMAC_SHA512(const uint8 *key, const uint8 *in, int inlen,
+ uint8 *out)
+ {
+ #ifndef FRONTEND
+ 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."))));
+ #else
+ fprintf(stderr, _("cluster encryption is not supported because OpenSSL is not supported by this build"));
+ exit(1);
+ #endif
+ }
diff --git a/src/common/hmac_openssl.c b/src/common/hmac_openssl.c
new file mode 100644
index ...e7b8978
*** a/src/common/hmac_openssl.c
--- b/src/common/hmac_openssl.c
***************
*** 0 ****
--- 1,33 ----
+ /*-------------------------------------------------------------------------
+ * hmac_openssl.c
+ * HMAC 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/hmac_openssl.c
+ *
+ *-------------------------------------------------------------------------
+ */
+ #ifndef FRONTEND
+ #include "postgres.h"
+ #else
+ #include "postgres_fe.h"
+ #endif
+
+ #include "common/sha2.h"
+
+ #include "common/hmac.h"
+ #include
+ #include
+
+ bool
+ pg_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 ...15c7593
*** a/src/common/kmgr_utils.c
--- b/src/common/kmgr_utils.c
***************
*** 0 ****
--- 1,583 ----
+ /*-------------------------------------------------------------------------
+ *
+ * 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/cryptohash.h"
+ #include "common/file_perm.h"
+ #include "common/kmgr_utils.h"
+ #include "common/hex_decode.h"
+ #include "common/string.h"
+ #include "crypto/kmgr.h"
+ #include "lib/stringinfo.h"
+ #include "postmaster/postmaster.h"
+ #include "storage/fd.h"
+
+ #ifndef FRONTEND
+ #include "pgstat.h"
+ #include "storage/fd.h"
+ #endif
+
+ #define KMGR_PROMPT_MSG "Enter authentication needed to generate the cluster key: "
+
+ #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 cluster key by unwrapping the given keys.
+ * If the given cluster key is correct we set unwrapped keys to out_keys and return
+ * true. Otherwise return false. Please note that this function changes the
+ * contents of out_keys even on failure. Both in_keys and out_keys must be the
+ * same length, nkey.
+ */
+ bool
+ kmgr_verify_cluster_key(char *cluster_key, int passlen,
+ CryptoKey *in_keys, CryptoKey *out_keys, 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 cluster key.
+ */
+ kmgr_derive_keys(cluster_key, 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, &(in_keys[i]), &(out_keys[i])))
+ {
+ /* The cluster key is not correct */
+ pg_free_keywrap_ctx(tmpctx);
+ return false;
+ }
+ explicit_bzero(&(in_keys[i]), sizeof(in_keys[i]));
+ }
+
+ /* The cluster key is correct, free the cipher context */
+ pg_free_keywrap_ctx(tmpctx);
+
+ return true;
+ }
+
+ /* Generate encryption key and mac key from given cluster key */
+ void
+ kmgr_derive_keys(char *cluster_key, Size passlen,
+ uint8 enckey[KMGR_ENC_KEY_LEN],
+ uint8 mackey[KMGR_MAC_KEY_LEN])
+ {
+ pg_cryptohash_ctx *ctx;
+
+ 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");
+
+ /* Convert hex cluster key to binary */
+ if (hex_decode(cluster_key, KMGR_CLUSTER_KEY_LEN, (char *)enckey) !=
+ KMGR_CLUSTER_KEY_LEN / 2)
+ {
+ #ifdef FRONTEND
+ pg_log_fatal("cluster key must be %d hexadecimal characters", KMGR_CLUSTER_KEY_LEN);
+ exit(EXIT_FAILURE);
+ #else
+ ereport(ERROR,
+ (errmsg("cluster_key must be %d hexadecimal characters",
+ KMGR_CLUSTER_KEY_LEN)));
+ #endif
+ }
+
+ /* Generate mac key from cluster key */
+ ctx = pg_cryptohash_create(PG_SHA512);
+ if (pg_cryptohash_init(ctx) < 0 ||
+ pg_cryptohash_update(ctx, (uint8 *) enckey, KMGR_ENC_KEY_LEN) < 0 ||
+ pg_cryptohash_final(ctx, mackey) < 0)
+ {
+ #ifdef FRONTEND
+ pg_log_fatal("could not create cluster file encryption hash");
+ exit(EXIT_FAILURE);
+ #else
+ ereport(ERROR,
+ (errcode(ERRCODE_INTERNAL_ERROR),
+ errmsg("could not create cluster file encryption hash")));
+ #endif
+ }
+ pg_cryptohash_free(ctx);
+ }
+
+ /*
+ * Run cluster key 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_key_command(char *cluster_key_command, char *buf,
+ int size, char *dir)
+ {
+ StringInfoData command;
+ const char *sp;
+ FILE *fh;
+ int pclose_rc;
+ size_t len = 0;
+
+ buf[0] = '\0';
+
+ Assert(size > 0);
+
+ /*
+ * Build the command to be executed.
+ */
+ initStringInfo(&command);
+
+ for (sp = cluster_key_command; *sp; sp++)
+ {
+ if (*sp == '%')
+ {
+ switch (sp[1])
+ {
+ case 'd':
+ {
+ char *nativePath;
+
+ sp++;
+
+ /*
+ * This needs to use a placeholder to not modify the
+ * input with the conversion done via
+ * make_native_path().
+ */
+ nativePath = pstrdup(dir);
+ make_native_path(nativePath);
+ appendStringInfoString(&command, nativePath);
+ pfree(nativePath);
+ break;
+ }
+ case 'P':
+ sp++;
+ appendStringInfoString(&command, KMGR_PROMPT_MSG);
+ break;
+ case 'R':
+ {
+ char fd_str[20];
+
+ if (terminal_fd == -1)
+ {
+ #ifdef FRONTEND
+ pg_log_fatal("cluster key referenced %%R, but -R not specified");
+ exit(EXIT_FAILURE);
+ #else
+ ereport(ERROR,
+ (errcode(ERRCODE_INTERNAL_ERROR),
+ errmsg("cluster key referenced %%R, but -R not specified")));
+ #endif
+ }
+
+ sp++;
+ snprintf(fd_str, sizeof(fd_str), "%d", terminal_fd);
+ appendStringInfoString(&command, fd_str);
+ break;
+ }
+ case '%':
+ /* convert %% to a single % */
+ sp++;
+ appendStringInfoChar(&command, *sp);
+ break;
+ default:
+ /* otherwise treat the % as not special */
+ appendStringInfoChar(&command, *sp);
+ break;
+ }
+ }
+ else
+ {
+ appendStringInfoChar(&command, *sp);
+ }
+ }
+
+ #ifdef FRONTEND
+ fh = open_pipe_stream(command.data);
+ if (fh == NULL)
+ {
+ pg_log_fatal("could not execute command \"%s\": %m",
+ command.data);
+ exit(EXIT_FAILURE);
+ }
+ #else
+ fh = OpenPipeStream(command.data, "r");
+ if (fh == NULL)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not execute command \"%s\": %m",
+ command.data)));
+ #endif
+
+ if ((len = fread(buf, sizeof(char), size - 1, fh)) < size)
+ {
+ if (ferror(fh))
+ {
+ #ifdef FRONTEND
+ pg_log_fatal("could not read from command \"%s\": %m",
+ command.data);
+ exit(EXIT_FAILURE);
+ #else
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not read from command \"%s\": %m",
+ command.data)));
+ #endif
+ }
+ }
+ buf[len] = '\0';
+
+ #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.data);
+ exit(EXIT_FAILURE);
+ #else
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("command \"%s\" failed",
+ command.data),
+ errdetail_internal("%s", wait_result_to_str(pclose_rc))));
+ #endif
+ }
+
+ /* strip trailing newline and carriage return */
+ len = pg_strip_crlf(buf);
+
+ pfree(command.data);
+
+ 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, LIVE_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 keys");
+ #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/common/cipher.h b/src/include/common/cipher.h
new file mode 100644
index ...f3eca7b
*** a/src/include/common/cipher.h
--- b/src/include/common/cipher.h
***************
*** 0 ****
--- 1,71 ----
+ /*-------------------------------------------------------------------------
+ *
+ * cipher.h
+ * Declarations for cryptographic functions
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * src/include/common/cipher.h
+ *
+ *-------------------------------------------------------------------------
+ */
+ #ifndef PG_CIPHER_H
+ #define PG_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)
+
+ #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);
+
+ #endif /* PG_CIPHER_H */
diff --git a/src/include/common/hex_decode.h b/src/include/common/hex_decode.h
new file mode 100644
index ...1f99f06
*** a/src/include/common/hex_decode.h
--- b/src/include/common/hex_decode.h
***************
*** 0 ****
--- 1,16 ----
+ /*
+ * hex_decode.h
+ * hex decoding
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/common/hex_decode.h
+ */
+ #ifndef COMMON_HEX_DECODE_H
+ #define COMMON_HEX_DECODE_H
+
+ extern uint64 hex_decode(const char *src, size_t len, char *dst);
+
+
+ #endif /* COMMON_HEX_DECODE_H */
diff --git a/src/include/common/hmac.h b/src/include/common/hmac.h
new file mode 100644
index ...4e9fdbe
*** a/src/include/common/hmac.h
--- b/src/include/common/hmac.h
***************
*** 0 ****
--- 1,23 ----
+ /*-------------------------------------------------------------------------
+ *
+ * hmac.h
+ * Declarations for HMAC function
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * src/include/common/hmac.h
+ *
+ *-------------------------------------------------------------------------
+ */
+ #ifndef PG_HMAC_H
+ #define PG_HMAC_H
+
+ /* HMAC key and HMAC length. We use HMAC-SHA256 */
+ #define PG_HMAC_SHA512_KEY_LEN 64
+ #define PG_HMAC_SHA512_LEN 64
+
+ extern bool pg_HMAC_SHA512(const uint8 *key,
+ const uint8 *in, int inlen,
+ uint8 *out);
+
+ #endif /* PG_HMAC_H */
diff --git a/src/include/common/kmgr_utils.h b/src/include/common/kmgr_utils.h
new file mode 100644
index ...999a81d
*** a/src/include/common/kmgr_utils.h
--- b/src/include/common/kmgr_utils.h
***************
*** 0 ****
--- 1,110 ----
+ /*-------------------------------------------------------------------------
+ *
+ * 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"
+ #include "common/hmac.h"
+
+ /* Current version number */
+ #define KMGR_VERSION 1
+
+ /*
+ * Directories where cluster file encryption keys reside within PGDATA.
+ */
+ #define KMGR_DIR "pg_cryptokeys"
+ #define KMGR_DIR_PID KMGR_DIR"/pg_alterckey.pid"
+ #define LIVE_KMGR_DIR KMGR_DIR"/live"
+ /* used during cluster key rotation */
+ #define NEW_KMGR_DIR KMGR_DIR"/new"
+ #define OLD_KMGR_DIR KMGR_DIR"/old"
+
+ /*
+ * 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)
+
+ /* Valid hexbyte length of cluster key */
+ #define KMGR_CLUSTER_KEY_LEN 64
+ /* allow for \r,\n, and null byte */
+ #define ALLOC_KMGR_CLUSTER_KEY_LEN (KMGR_CLUSTER_KEY_LEN + 2 + 1)
+
+ /* 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 *cluster_key, Size passlen,
+ uint8 enckey[KMGR_ENC_KEY_LEN],
+ uint8 mackey[KMGR_MAC_KEY_LEN]);
+ extern bool kmgr_verify_cluster_key(char *cluster_key, int passlen,
+ CryptoKey *in_keys, CryptoKey *out_keys,
+ 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_key_command(char *cluster_key_command,
+ char *buf, int size, char *dir);
+ 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 ...fa38a32
*** a/src/include/crypto/kmgr.h
--- b/src/include/crypto/kmgr.h
***************
*** 0 ****
--- 1,30 ----
+ /*-------------------------------------------------------------------------
+ *
+ * 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/hmac.h"
+ #include "common/kmgr_utils.h"
+ #include "storage/relfilenode.h"
+ #include "storage/bufpage.h"
+
+ /* GUC parameters */
+ extern int file_encryption_keylen;
+ extern char *cluster_key_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
new file mode 100644
index 5954068..b8f98f9
*** a/src/include/pgstat.h
--- b/src/include/pgstat.h
*************** typedef enum
*** 1010,1015 ****
--- 1010,1018 ----
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/builtins.h b/src/include/utils/builtins.h
new file mode 100644
index 4db5ad3..19271e0
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
*************** extern int errdomainconstraint(Oid datat
*** 33,39 ****
/* encode.c */
extern uint64 hex_encode(const char *src, size_t len, char *dst);
- extern uint64 hex_decode(const char *src, size_t len, char *dst);
/* int.c */
extern int2vector *buildint2vector(const int16 *int2s, int n);
--- 33,38 ----
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/interfaces/ecpg/ecpglib/data.c b/src/interfaces/ecpg/ecpglib/data.c
new file mode 100644
index 6bc91ef..326de71
*** a/src/interfaces/ecpg/ecpglib/data.c
--- b/src/interfaces/ecpg/ecpglib/data.c
***************
*** 5,10 ****
--- 5,11 ----
#include
+ #include "common/hex_decode.h"
#include "ecpgerrno.h"
#include "ecpglib.h"
#include "ecpglib_extern.h"
*************** ecpg_hex_dec_len(unsigned srclen)
*** 136,192 ****
return srclen >> 1;
}
- static inline char
- get_hex(char c)
- {
- static const int8 hexlookup[128] = {
- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
- -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- };
- int res = -1;
-
- if (c > 0 && c < 127)
- res = hexlookup[(unsigned char) c];
-
- return (char) res;
- }
-
- static unsigned
- hex_decode(const char *src, unsigned len, char *dst)
- {
- const char *s,
- *srcend;
- char v1,
- v2,
- *p;
-
- srcend = src + len;
- s = src;
- p = dst;
- while (s < srcend)
- {
- if (*s == ' ' || *s == '\n' || *s == '\t' || *s == '\r')
- {
- s++;
- continue;
- }
- v1 = get_hex(*s++) << 4;
- if (s >= srcend)
- return -1;
-
- v2 = get_hex(*s++);
- *p++ = v1 | v2;
- }
-
- return p - dst;
- }
-
unsigned
ecpg_hex_encode(const char *src, unsigned len, char *dst)
{
--- 137,142 ----
diff --git a/src/test/Makefile b/src/test/Makefile
new file mode 100644
index ab1ef9a..730efbf
*** 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
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
new file mode 100644
index f92c140..2ec377b
*** a/src/tools/msvc/Mkvcbuild.pm
--- b/src/tools/msvc/Mkvcbuild.pm
*************** sub mkvcbuild
*** 121,140 ****
our @pgcommonallfiles = qw(
archive.c base64.c checksum_helper.c
config_info.c controldata_utils.c d2s.c encnames.c exec.c
! f2s.c file_perm.c file_utils.c hashfn.c ip.c jsonapi.c
! keywords.c kwlookup.c link-canary.c md5_common.c
pg_get_line.c pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c
saslprep.c scram-common.c string.c stringinfo.c unicode_norm.c username.c
wait_error.c wchar.c);
if ($solution->{options}->{openssl})
{
push(@pgcommonallfiles, 'cryptohash_openssl.c');
push(@pgcommonallfiles, 'protocol_openssl.c');
}
else
{
push(@pgcommonallfiles, 'cryptohash.c');
push(@pgcommonallfiles, 'md5.c');
push(@pgcommonallfiles, 'sha2.c');
}
--- 121,144 ----
our @pgcommonallfiles = qw(
archive.c base64.c checksum_helper.c
config_info.c controldata_utils.c d2s.c encnames.c exec.c
! f2s.c file_perm.c file_utils.c hashfn.c hex_decode.c ip.c jsonapi.c
! keywords.c kmgr_utils.c kwlookup.c link-canary.c md5_common.c
pg_get_line.c pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c
saslprep.c scram-common.c string.c stringinfo.c unicode_norm.c username.c
wait_error.c wchar.c);
if ($solution->{options}->{openssl})
{
+ push(@pgcommonallfiles, 'cipher_openssl.c');
+ push(@pgcommonallfiles, 'hmac_openssl.c');
push(@pgcommonallfiles, 'cryptohash_openssl.c');
push(@pgcommonallfiles, 'protocol_openssl.c');
}
else
{
+ push(@pgcommonallfiles, 'cipher.c');
push(@pgcommonallfiles, 'cryptohash.c');
+ push(@pgcommonallfiles, 'hmac.c');
push(@pgcommonallfiles, 'md5.c');
push(@pgcommonallfiles, 'sha2.c');
}