diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
new file mode 100644
index 4b60382..ecc1a65
*** 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_passphrase_command (string)
+
+ cluster_passphrase_command configuration parameter
+
+
+
+
+ This option specifies an external command to obtain the passphrase
+ 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
+ passphrase 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. A single newline is stripped
+ from the end of the output if present. The output passphrase must
+ be at least 64 bytes.
+
+
+ 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 ...f0788ec
*** 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 passphrase
+ provided by the
+ option during initdb and the one generated
+ by in the
+ postgresql.conf must match for the database
+ cluster to start. Note that the cluster passphrase command
+ passed to initdb must return a passphrase of at
+ least 64 bytes and less than 1024 bytes. For example.
+
+ initdb -D dbname --cluster-passphrase-command="cat /path/to/passphrase-file"
+
+
+
+
+
+ Internals
+
+
+ During the initdb process, if the
+ is specified, two
+ data-level encryption keys are created. These two keys are then
+ encrypted with the passphrase (KEK) supplied by the cluster passphrase
+ command before being stored in the database directory. The passphrase
+ must be stored in a trusted key store, such as key vault software,
+ hardware security module, or supplied from the terminal.
+
+
+
+ If the PostgreSQL server has
+ been initialized to require a passphrase, each time the
+ server is started the postgresql.conf
+ cluster_passphrase_command command will be executed
+ and the cluster passphrase retrieved. The data encryption keys in the
+ pg_cryptokeys directory will then be decrypted
+ using the supplied passphrase and integrity-checked to see if it
+ matches the initdb-supplied passphrase. If this check fails, the
+ server will refuse to start.
+
+
+
+
+ Key Protection
+
+
+ Cluster file encryption uses Encryption with Associated Data
+ (AEAD) to wrap cryptographic keys. In addition
+ to providing a way to protect confidential data from being revealed,
+ it provides a way to check the integrity and authenticity of some
+ associated data. It uses the Encrypt-Then-MAC approach, based on the
+ Advanced Encryption Standard (AES256) in Cipher Block
+ Chaining (CBC) mode. It uses a random initialization
+ vector (IV) and a HMAC-SHA
+ message authentication code (MAC).
+
+
+
+ Key management system uses two kinds of cryptographic keys for key wrapping:
+
+
+
+
+
+ Data Encryption Key
+
+
+ Data Encryption keys are 128, 192, or 256-bit length randomly generate keys.
+
+
+
+
+ MAC Key
+
+
+ MAC key is a 512-bit randomly generated key.
+ SHA512 is the algorithm used along with the
+ MAC key to compute a cryptographic hash for
+ integrity purposes.
+
+
+
+
+
+
+
+ The data key wrapping algorithm is as follows:
+
+
+
+ Generate random IV.
+
+
+ Add padding to the plain text following PKCS#7 described in
+ 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..9aa8888
*** 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 passphrase
+ 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
+ passphrase 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. A single newline is stripped
+ from the end of the output if present. The output passphrase must
+ be at least 64 bytes.
+
+
+
+
*************** 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_altercpass.sgml b/doc/src/sgml/ref/pg_altercpass.sgml
new file mode 100644
index ...1683d08
*** a/doc/src/sgml/ref/pg_altercpass.sgml
--- b/doc/src/sgml/ref/pg_altercpass.sgml
***************
*** 0 ****
--- 1,186 ----
+
+
+
+
+ pg_altercpass
+
+
+
+ pg_altercpass
+ 1
+ Application
+
+
+
+ pg_altercpass
+ alter the PostgreSQL cluster passphrase
+
+
+
+
+ pg_altercpass
+
+
+
+
+ old_cluster_passphrase_command
+ new_cluster_passphrase_command
+
+
+
+
+
+ datadir
+
+
+
+
+ pg_altercpass
+
+
+
+
+
+
+
+
+
+
+
+
+
+ datadir
+
+
+
+
+
+ Description
+
+ pg_altercpass alters the cluster passphrase used
+ for cluster file encryption. The cluster passphrase is initially set
+ during . The command can be run while the
+ server is running or stopped. The new password must be used the next
+ time the server is started.
+
+
+
+ Technically, pg_altercpass changes the key
+ encryption key (KEK) which encrypts the data
+ encryption keys; it does not change the data encryption keys. It does
+ this by decrypting each data encryption key using the old_cluster_passphrase_command,
+ re-encrypting it using the new_cluster_passphrase_command, and
+ then writes the result back to the cluster directory.
+
+
+
+ See the documentation for how to define
+ the old and new passprhase commands. You can use different executables
+ for these commands, or you can use the same executable with different
+ arguments to specify retrieval of the old or new key.
+
+
+
+ When started, pg_altercpass repairs any files that
+ remain from previous pg_altercpass failures before
+ altering the cluster passphrase. To perform only the repair task,
+ use the option. The server will not start
+ if repair is needed, though a running server is unaffected by an
+ unrepaired cluster passphrase configuration.
+
+
+
+ You can specify the data directory on the command line, or use
+ the environment variable PGDATA.
+
+
+
+
+ Options
+
+
+
+
+
+
+
+ Allows the and
+ commands
+ to prompt for a passphrase or PIN.
+
+
+
+
+
+
+
+ Other options:
+
+
+
+
+
+
+
+ Print the pg_altercpass version and exit.
+
+
+
+
+
+
+
+
+
+ Show help about pg_altercpass command line
+ arguments, and exit.
+
+
+
+
+
+
+
+
+
+
+ Environment
+
+
+
+ PGDATA
+
+
+
+ Default data directory location
+
+
+
+
+
+ PG_COLOR
+
+
+ Specifies whether to use color in diagnostic messages. Possible values
+ are always, auto and
+ never.
+
+
+
+
+
+
+
+ See Also
+
+
+
+
+
+
+
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..a2f12a4
*** 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..5d7a8b3
*** 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_passphrase_command and the same
+ file encryption key length.
+
diff --git a/doc/src/sgml/ref/postgres-ref.sgml b/doc/src/sgml/ref/postgres-ref.sgml
new file mode 100644
index 4aaa7ab..a996dec
*** 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 the cluster passphrase
+ 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 8dd225c..31fe655
*** 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)
*** 4608,4613 ****
--- 4611,4617 ----
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)
*** 4707,4712 ****
--- 4711,4717 ----
pg_crc32c crc;
int fd;
static char wal_segsz_str[20];
+ static char file_encryption_keylen_str[20];
int r;
/*
*************** ReadControlFile(void)
*** 4895,4900 ****
--- 4900,4911 ----
/* 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)
*** 5343,5348 ****
--- 5354,5368 ----
/* 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 ...24c5d7f
*** a/src/backend/crypto/kmgr.c
--- b/src/backend/crypto/kmgr.c
***************
*** 0 ****
--- 1,359 ----
+ /*-------------------------------------------------------------------------
+ *
+ * kmgr.c
+ * Cluster file encryption routines
+ *
+ * Cluster file encryption is enabled if user requests it during initdb.
+ * During bootstrap, we generate data encryption keys, wrap them with
+ * KEK which is derived from the user-provided passphrase, and store
+ * them into each file located at KMGR_DIR. Once generated, these are
+ * not changed. During startup, we decrypt all internal keys and load
+ * them to the shared memory space. Internal keys on the shared memory
+ * are read-only. All wrapping and unwrapping key routines 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_passphrase_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 passphrase[KMGR_MAX_PASSPHRASE_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 passphrase command. The passphrase
+ * 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_passphrase_command(cluster_passphrase_command,
+ passphrase, KMGR_MAX_PASSPHRASE_LEN,
+ live_path);
+ if (passlen < KMGR_MIN_PASSPHRASE_LEN)
+ ereport(ERROR,
+ (errmsg("passphrase must be at least %d bytes",
+ KMGR_MIN_PASSPHRASE_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 passphrase */
+ kmgr_derive_keys(passphrase, passlen, KEK_enc, KEK_hmac);
+
+ /* Create temporary key wrap context */
+ ctx = pg_create_keywrap_ctx(KEK_enc, KEK_hmac);
+ if (!ctx)
+ elog(ERROR, "could not initialize encryption context");
+
+ /* Wrap all data encryption keys by key encryption key */
+ for (int id = 0; id < KMGR_MAX_INTERNAL_KEYS; id++)
+ {
+ CryptoKey *key;
+
+ /* generate a data encryption key */
+ key = generate_crypto_key(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_passphrase(passphrase, passlen, keys_wrap, bootstrap_keys,
+ KMGR_MAX_INTERNAL_KEYS))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cluster passphrase does not match expected passphrase")));
+
+ /* bzero keys on exit */
+ on_proc_exit(bzeroKmgrKeys, 0);
+
+ explicit_bzero(passphrase, 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 encryption key passphrase and verify it, then get the data encryption keys.
+ * This function is called by postmaster at startup time.
+ */
+ void
+ InitializeKmgr(void)
+ {
+ CryptoKey *keys_wrap;
+ int nkeys;
+ char passphrase[KMGR_MAX_PASSPHRASE_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_altercpass failure that needs repair or pg_altercpass is running"),
+ errhint("Run pg_altercpass --repair or wait for it to complete."))));
+
+ /*
+ * We want OLD deleted since it allows access to the data encryption
+ * keys using the old cluster passphrase. 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_altercpass failure that needs repair"),
+ errhint("Run pg_altercpass --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 passphrase */
+ snprintf(live_path, sizeof(live_path), "%s/%s", DataDir, LIVE_KMGR_DIR);
+ passlen = kmgr_run_cluster_passphrase_command(cluster_passphrase_command,
+ passphrase, KMGR_MAX_PASSPHRASE_LEN,
+ live_path);
+
+ /* Get the crypto keys from the file */
+ keys_wrap = kmgr_get_cryptokeys(LIVE_KMGR_DIR, &nkeys);
+ Assert(nkeys == KMGR_MAX_INTERNAL_KEYS);
+
+ /*
+ * Verify passphrase and prepare a data encryption key in plaintext in shared memory.
+ */
+ if (!kmgr_verify_passphrase(passphrase, passlen, keys_wrap, KmgrShmem->intlKeys,
+ KMGR_MAX_INTERNAL_KEYS))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cluster passphrase does not match expected passphrase")));
+
+ explicit_bzero(passphrase, 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..65d191d
*** a/src/backend/main/main.c
--- b/src/backend/main/main.c
*************** help(const char *progname)
*** 324,329 ****
--- 324,330 ----
#endif
printf(_(" -N MAX-CONNECT maximum number of allowed connections\n"));
printf(_(" -p PORT port number to listen on\n"));
+ printf(_(" -R fd prompt for the cluster passphrase\n"));
printf(_(" -s show statistics after each query\n"));
printf(_(" -S WORK-MEM set amount of memory for sorts (in kB)\n"));
printf(_(" -V, --version output version information, then exit\n"));
*************** help(const char *progname)
*** 351,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 7c75a25..7459287
*** a/src/backend/postmaster/pgstat.c
--- b/src/backend/postmaster/pgstat.c
*************** pgstat_get_wait_io(WaitEventIO w)
*** 4155,4160 ****
--- 4155,4169 ----
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/misc/guc.c b/src/backend/utils/misc/guc.c
new file mode 100644
index dabcbb0..d889ca5
*** 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_passphrase_command", PGC_SIGHUP, ENCRYPTION,
+ gettext_noop("Command to obtain passphrase for cluster file encryption."),
+ NULL
+ },
+ &cluster_passphrase_command,
+ "",
+ NULL, NULL, NULL
+ },
{
{"application_name", PGC_USERSET, LOGGING_WHAT,
diff --git a/src/backend/utils/misc/pg_controldata.c b/src/backend/utils/misc/pg_controldata.c
new file mode 100644
index d50d87a..6fcab31
*** a/src/backend/utils/misc/pg_controldata.c
--- b/src/backend/utils/misc/pg_controldata.c
*************** pg_control_recovery(PG_FUNCTION_ARGS)
*** 263,270 ****
Datum
pg_control_init(PG_FUNCTION_ARGS)
{
! Datum values[11];
! bool nulls[11];
TupleDesc tupdesc;
HeapTuple htup;
ControlFileData *ControlFile;
--- 263,270 ----
Datum
pg_control_init(PG_FUNCTION_ARGS)
{
! Datum values[12];
! bool nulls[12];
TupleDesc tupdesc;
HeapTuple htup;
ControlFileData *ControlFile;
*************** pg_control_init(PG_FUNCTION_ARGS)
*** 274,280 ****
* Construct a tuple descriptor for the result row. This must match this
* function's pg_proc entry!
*/
! tupdesc = CreateTemplateTupleDesc(11);
TupleDescInitEntry(tupdesc, (AttrNumber) 1, "max_data_alignment",
INT4OID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 2, "database_block_size",
--- 274,280 ----
* Construct a tuple descriptor for the result row. This must match this
* function's pg_proc entry!
*/
! tupdesc = CreateTemplateTupleDesc(12);
TupleDescInitEntry(tupdesc, (AttrNumber) 1, "max_data_alignment",
INT4OID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 2, "database_block_size",
*************** pg_control_init(PG_FUNCTION_ARGS)
*** 297,302 ****
--- 297,304 ----
BOOLOID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 11, "data_page_checksum_version",
INT4OID, -1, 0);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 12, "file_encryption_keylen",
+ INT4OID, -1, 0);
tupdesc = BlessTupleDesc(tupdesc);
/* read the control file */
*************** pg_control_init(PG_FUNCTION_ARGS)
*** 338,343 ****
--- 340,348 ----
values[10] = Int32GetDatum(ControlFile->data_checksum_version);
nulls[10] = false;
+ values[11] = Int32GetDatum(ControlFile->file_encryption_keylen);
+ nulls[11] = false;
+
htup = heap_form_tuple(tupdesc, values, nulls);
PG_RETURN_DATUM(HeapTupleGetDatum(htup));
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
new file mode 100644
index b7fb2ec..0c13d66
*** 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_passphrase_command = ''
#------------------------------------------------------------------------------
# CLIENT CONNECTION DEFAULTS
diff --git a/src/bin/Makefile b/src/bin/Makefile
new file mode 100644
index 8b87035..191fa6c
*** a/src/bin/Makefile
--- b/src/bin/Makefile
*************** include $(top_builddir)/src/Makefile.glo
*** 16,21 ****
--- 16,22 ----
SUBDIRS = \
initdb \
pg_archivecleanup \
+ pg_altercpass \
pg_basebackup \
pg_checksums \
pg_config \
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
new file mode 100644
index f994c42..489e8ed
*** 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_passphrase_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_passphrase_cmd)
+ {
+ snprintf(repltok, sizeof(repltok), "cluster_passphrase_command = '%s'",
+ escape_quotes(cluster_passphrase_cmd));
+ conflines = replace_token(conflines, "#cluster_passphrase_command = ''", repltok);
+ }
+
/*
* If group access has been enabled for the cluster then it makes sense to
* ensure that the log files also allow group access. Otherwise a backup
*************** bootstrap_template1(void)
*** 1394,1406 ****
/* Also ensure backend isn't confused by this environment var: */
unsetenv("PGCLIENTENCODING");
snprintf(cmd, sizeof(cmd),
! "\"%s\" --boot -x1 -X %u %s %s %s",
backend_exec,
wal_segment_size_mb * (1024 * 1024),
data_checksums ? "-k" : "",
boot_options,
! debug ? "-d 5" : "");
PG_CMD_OPEN;
--- 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_passphrase_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-passphrase-command=COMMAND\n"
+ " enable cluster file encryption and set command\n"
+ " to obtain the cluster passphrase\n"));
printf(_(" -d, --debug generate lots of debugging output\n"));
printf(_(" -k, --data-checksums use data page checksums\n"));
+ printf(_(" -K, --file-encryption-keylen\n"
+ " bit length of the file encryption key\n"));
printf(_(" -L DIRECTORY where to find the input files\n"));
printf(_(" -n, --no-clean do not clean up after errors\n"));
printf(_(" -N, --no-sync do not wait for changes to be written safely to disk\n"));
+ printf(_(" -R, --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-passphrase-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_passphrase_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_passphrase_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_passphrase_cmd == NULL)
+ {
+ pg_log_error("copying encryption keys requires the cluster passphrase command to be specified");
+ exit(1);
+ }
+
+ if (file_encryption_keylen != 0 && cluster_passphrase_cmd == NULL)
+ {
+ pg_log_error("a non-zero file encryption key length requires the cluster passphrase command to be specified");
+ exit(1);
+ }
+
+ 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_passphrase_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_passphrase_cmd)
+ printf(_("Cluster file encryption is enabled.\n"));
+ else
+ printf(_("Cluster file encryption is disabled.\n"));
+
if (pwprompt || pwfilename)
get_su_pwd();
diff --git a/src/bin/pg_altercpass/.gitignore b/src/bin/pg_altercpass/.gitignore
new file mode 100644
index ...fe7160f
*** a/src/bin/pg_altercpass/.gitignore
--- b/src/bin/pg_altercpass/.gitignore
***************
*** 0 ****
--- 1,2 ----
+ /pg_altercpass
+
diff --git a/src/bin/pg_altercpass/Makefile b/src/bin/pg_altercpass/Makefile
new file mode 100644
index ...0a2b815
*** a/src/bin/pg_altercpass/Makefile
--- b/src/bin/pg_altercpass/Makefile
***************
*** 0 ****
--- 1,44 ----
+ #-------------------------------------------------------------------------
+ #
+ # Makefile for src/bin/pg_altercpass
+ #
+ # Copyright (c) 1998-2020, PostgreSQL Global Development Group
+ #
+ # src/bin/pg_altercpass/Makefile
+ #
+ #-------------------------------------------------------------------------
+
+ PGFILEDESC = "pg_altercpass - alter the cluster passphrase"
+ PGAPPICON=win32
+
+ subdir = src/bin/pg_altercpass
+ top_builddir = ../../..
+ include $(top_builddir)/src/Makefile.global
+
+ OBJS = \
+ $(WIN32RES) \
+ pg_altercpass.o
+
+ all: pg_altercpass
+
+ pg_altercpass: $(OBJS) | submake-libpgport
+ $(CC) $(CFLAGS) $^ $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)
+
+ install: all installdirs
+ $(INSTALL_PROGRAM) pg_altercpass$(X) '$(DESTDIR)$(bindir)/pg_altercpass$(X)'
+
+ installdirs:
+ $(MKDIR_P) '$(DESTDIR)$(bindir)'
+
+ uninstall:
+ rm -f '$(DESTDIR)$(bindir)/pg_altercpass$(X)'
+
+ clean distclean maintainer-clean:
+ rm -f pg_altercpass$(X) $(OBJS)
+ rm -rf tmp_check
+
+ check:
+ $(prove_check)
+
+ installcheck:
+ $(prove_installcheck)
diff --git a/src/bin/pg_altercpass/pg_altercpass.c b/src/bin/pg_altercpass/pg_altercpass.c
new file mode 100644
index ...8032fa0
*** a/src/bin/pg_altercpass/pg_altercpass.c
--- b/src/bin/pg_altercpass/pg_altercpass.c
***************
*** 0 ****
--- 1,669 ----
+ /*-------------------------------------------------------------------------
+ *
+ * pg_altercpass.c
+ * A utility to change the cluster passphrase (key encryption key, KEK)
+ * used for cluster file encryption.
+ *
+ * The theory of operation is fairly simple:
+ * 1. Create lock file
+ * 2. Retrieve current and new cluster passphrase using the supplied
+ * commands.
+ * 3. Revert any failed alter operation.
+ * 4. Create a temporary directory in PGDATA
+ * 5. For each data encryption key in the pg_cryptokeys directory,
+ * descrypt it with the old cluster passphrase and reencrypt it
+ * with the new cluster passphrase.
+ * 6. Make the temporary directory the new pg_cryptokeys directory.
+ * 7. Remove lock file
+ *
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/bin/pg_altercpass/pg_altercpass.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+
+ #define FRONTEND 1
+
+ #include "postgres_fe.h"
+
+ #include
+ #include
+ #include
+
+ #include "common/file_perm.h"
+ #include "common/file_utils.h"
+ #include "common/restricted_token.h"
+ #include "crypto/kmgr.h"
+ #include "common/logging.h"
+ #include "getopt_long.h"
+ #include "pg_getopt.h"
+
+
+ static int lock_fd = -1;
+ static bool pass_terminal_fd = false;
+ int terminal_fd = -1;
+ static bool repair_mode = false;
+ static char *old_cluster_passphrase_cmd = NULL,
+ *new_cluster_passphrase_cmd = NULL;
+ static char old_passphrase[KMGR_MAX_PASSPHRASE_LEN],
+ new_passphrase[KMGR_MAX_PASSPHRASE_LEN];
+ static int old_passlen, new_passlen;
+ static CryptoKey in_key, data_key, out_key;
+ static char top_path[MAXPGPATH], pid_path[MAXPGPATH], live_path[MAXPGPATH],
+ new_path[MAXPGPATH], old_path[MAXPGPATH];
+
+ static char *DataDir = NULL;
+ static const char *progname;
+
+ static void create_lockfile(void);
+ static void recover_failure(void);
+ static void retrieve_passphrases(void);
+ static void bzero_keys_and_exit(int exit_code);
+ static void reencrypt_data_keys(void);
+ static void install_new_keys(void);
+
+ static void
+ usage(const char *progname)
+ {
+ printf(_("%s changes the cluster passphrase of a PostgreSQL database cluster.\n\n"), progname);
+ printf(_("Usage:\n"));
+ printf(_(" %s [OPTION] old_cluster_passphrase_command new_cluster_passphrase_command [DATADIR]\n"), progname);
+ printf(_(" %s [repair_option] [DATADIR]\n"), progname);
+ printf(_("\nOptions:\n"));
+ printf(_(" -R, --authprompt prompt for a passphrases or PIN\n"));
+ printf(_(" [-D, --pgdata=]DATADIR data directory\n"));
+ printf(_(" -V, --version output version information, then exit\n"));
+ printf(_(" -?, --help show this help, then exit\n"));
+ printf(_("\nRepair options:\n"));
+ printf(_(" -r, --repair repair previous failure\n"));
+ printf(_("\nIf no data directory (DATADIR) is specified, "
+ "the environment variable PGDATA\nis used.\n\n"));
+ printf(_("Report bugs to <%s>.\n"), PACKAGE_BUGREPORT);
+ printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
+ }
+
+
+ int
+ main(int argc, char *argv[])
+ {
+ static struct option long_options1[] = {
+ {"authprompt", required_argument, NULL, 'R'},
+ {"repair", required_argument, NULL, 'r'},
+ {NULL, 0, NULL, 0}
+ };
+
+ static struct option long_options2[] = {
+ {"pgdata", required_argument, NULL, 'D'},
+ {NULL, 0, NULL, 0}
+ };
+
+ int c;
+
+ pg_logging_init(argv[0]);
+ set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_altercpass"));
+ progname = get_progname(argv[0]);
+
+ if (argc > 1)
+ {
+ if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
+ {
+ usage(progname);
+ exit(0);
+ }
+ if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
+ {
+ puts("pg_altercpass (PostgreSQL) " PG_VERSION);
+ exit(0);
+ }
+ }
+
+ /* check for -r/-R */
+ while ((c = getopt_long(argc, argv, "rR", long_options1, NULL)) != -1)
+ {
+ switch (c)
+ {
+ case 'r':
+ repair_mode = true;
+ break;
+
+ case 'R':
+ pass_terminal_fd = true;
+ break;
+
+ default:
+ fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
+ exit(1);
+ }
+ }
+
+ if (!repair_mode)
+ {
+ /* get cluser passphrase commands */
+ if (optind < argc)
+ old_cluster_passphrase_cmd = argv[optind++];
+ else
+ {
+ pg_log_error("missing old_cluster_passphrase_command");
+ fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
+ progname);
+ exit(1);
+ }
+
+ if (optind < argc)
+ new_cluster_passphrase_cmd = argv[optind++];
+ else
+ {
+ pg_log_error("missing new_cluster_passphrase_command");
+ fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
+ progname);
+ exit(1);
+ }
+ }
+
+ /* check for datadir */
+ argc -= optind;
+ argv += optind;
+
+ while ((c = getopt_long(argc, argv, "D:", long_options2, NULL)) != -1)
+ {
+ switch (c)
+ {
+ case 'D':
+ DataDir = optarg;
+ break;
+
+ default:
+ fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
+ exit(1);
+ }
+ }
+
+ if (DataDir == NULL)
+ {
+ if (optind < argc)
+ DataDir = argv[optind++];
+ else
+ DataDir = getenv("PGDATA");
+ }
+
+ /*
+ * Disallow running as root because we create directories in PGDATA
+ */
+ #ifndef WIN32
+ if (geteuid() == 0)
+ {
+ pg_log_error("%s: cannot be run as root\n"
+ "Please log in (using, e.g., \"su\") as the "
+ "(unprivileged) user that will\n"
+ "own the server process.\n",
+ progname);
+ exit(1);
+ }
+ #endif
+
+ get_restricted_token();
+
+ /* Set mask based on PGDATA permissions */
+ if (!GetDataDirectoryCreatePerm(DataDir))
+ {
+ pg_log_error("could not read permissions of directory \"%s\": %m",
+ DataDir);
+ exit(1);
+ }
+
+ umask(pg_mode_mask);
+
+ snprintf(top_path, sizeof(top_path), "%s/%s", DataDir, KMGR_DIR);
+ snprintf(pid_path, sizeof(pid_path), "%s/%s", DataDir, KMGR_DIR_PID);
+ snprintf(live_path, sizeof(live_path), "%s/%s", DataDir, LIVE_KMGR_DIR);
+ snprintf(new_path, sizeof(new_path), "%s/%s", DataDir, NEW_KMGR_DIR);
+ snprintf(old_path, sizeof(old_path), "%s/%s", DataDir, OLD_KMGR_DIR);
+
+ /* Complain if any arguments remain */
+ if (optind < argc)
+ {
+ pg_log_error("too many command-line arguments (first is \"%s\")",
+ argv[optind]);
+ fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
+ progname);
+ exit(1);
+ }
+
+ if (DataDir == NULL)
+ {
+ pg_log_error("no data directory specified");
+ fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
+ exit(1);
+ }
+
+ create_lockfile();
+
+ recover_failure();
+
+ if (!repair_mode)
+ {
+ retrieve_passphrases();
+ reencrypt_data_keys();
+ install_new_keys();
+ }
+
+ #ifndef WIN32
+ /* remove file system reference to file */
+ if (unlink(pid_path) < 0)
+ {
+ pg_log_error("could not delete lock file \"%s\": %m", KMGR_DIR_PID);
+ exit(1);
+ }
+ #endif
+
+ close (lock_fd);
+
+ bzero_keys_and_exit(0);
+ }
+
+ /* This prevents almost all cases of concurrent access */
+ void
+ create_lockfile(void)
+ {
+ struct stat buffer;
+ char lock_pid_str[20];
+
+ if (stat(top_path, &buffer) != 0 || !S_ISDIR(buffer.st_mode))
+ {
+ pg_log_error("cluster file encryption directory \"%s\" is missing; is it enabled?", KMGR_DIR_PID);
+ fprintf(stderr, _("Exiting with no changes made.\n"));
+ exit(1);
+ }
+
+ /* Does a lockfile exist? */
+ if ((lock_fd = open(pid_path, O_RDONLY, 0)) != -1)
+ {
+ int lock_pid;
+ int len;
+
+ /* read the PID */
+ if ((len = read(lock_fd, lock_pid_str, sizeof(lock_pid_str) - 1)) == 0)
+ {
+ pg_log_error("cannot read pid from lock file \"%s\": %m", KMGR_DIR_PID);
+ fprintf(stderr, _("Exiting with no changes made.\n"));
+ exit(1);
+ }
+ lock_pid_str[len] = '\0';
+
+ if ((lock_pid = atoi(lock_pid_str)) == 0)
+ {
+ pg_log_error("invalid pid in lock file \"%s\": %m", KMGR_DIR_PID);
+ fprintf(stderr, _("Exiting with no changes made.\n"));
+ exit(1);
+ }
+
+ /* Is the PID running? */
+ if (kill(lock_pid, 0) == 0)
+ {
+ pg_log_error("active process %d currently holds a lock on this operation, recorded in \"%s\"",
+ lock_pid, KMGR_DIR_PID);
+ fprintf(stderr, _("Exiting with no changes made.\n"));
+ exit(1);
+ }
+
+ close(lock_fd);
+
+ if (repair_mode)
+ printf("old lock file removed\n");
+
+ /*
+ * pid is no longer running, so remove the lock file.
+ * This is not 100% safe from concurrent access, e.g.:
+ *
+ * process 1 exits and leaves stale lock file
+ * process 2 checks stale lock file of process 1
+ * process 3 checks stale lock file of process 1
+ * process 2 remove the lock file of process 1
+ * process 4 creates a lock file
+ * process 3 remove the lock file of process 4
+ * process 5 creates a lock file
+ *
+ * The sleep(2) helps with this since it reduces the likelyhood
+ * a process that did an unlock will interfere with another unlock
+ * process. We could ask users to remove the lock, but that seems
+ * even more error-prone, especially since this might happen
+ * on server start. Many PG tools seem to have problems with
+ * concurrent access.
+ */
+ unlink(pid_path);
+
+ /* Sleep to reduce the likelihood of concurrent unlink */
+ sleep(2);
+ }
+
+ /* Create our own lockfile? */
+ lock_fd = open(pid_path, O_RDWR | O_CREAT | O_EXCL
+ #ifdef WIN32
+ /* delete on close */
+ | O_TEMPORARY
+ #endif
+ , pg_file_create_mode);
+
+ if (lock_fd == -1)
+ {
+ if (errno == EEXIST)
+ pg_log_error("an active process currently holds a lock on this operation, recorded in \"%s\"",
+ KMGR_DIR_PID);
+ else
+ pg_log_error("unable to create lock file \"%s\": %m", KMGR_DIR_PID);
+ fprintf(stderr, _("Exiting with no changes made.\n"));
+ exit(1);
+ }
+
+ snprintf(lock_pid_str, sizeof(lock_pid_str), "%d\n", getpid());
+ if (write(lock_fd, lock_pid_str, strlen(lock_pid_str)) != strlen(lock_pid_str))
+ {
+ pg_log_error("could not write pid to lock file \"%s\": %m", KMGR_DIR_PID);
+ fprintf(stderr, _("Exiting with no changes made.\n"));
+ exit(1);
+ }
+ }
+
+ /*
+ * recover_failure
+ *
+ * A previous pg_altercpass might have failed, so it might need recovery.
+ * The normal operation is:
+ * 1. reencrypt LIVE_KMGR_DIR -> NEW_KMGR_DIR
+ * 2. rename KMGR_DIR -> OLD_KMGR_DIR
+ * 3. rename NEW_KMGR_DIR -> LIVE_KMGR_DIR
+ * remove OLD_KMGR_DIR
+ *
+ * There are eight possible directory configurations:
+ *
+ * LIVE_KMGR_DIR NEW_KMGR_DIR OLD_KMGR_DIR
+ *
+ * Normal:
+ * 0. normal X
+ * 1. remove new X X
+ * 2. install new X X
+ * 3. remove old X X
+ *
+ * Abnormal:
+ * fatal
+ * restore old X
+ * install new X
+ * remove old and new X X X
+ *
+ * We don't handle the abnormal cases, just report an error.
+ */
+ static void
+ recover_failure(void)
+ {
+ struct stat buffer;
+ bool is_live, is_new, is_old;
+
+ is_live = !stat(live_path, &buffer);
+ is_new = !stat(new_path, &buffer);
+ is_old = !stat(old_path, &buffer);
+
+ /* normal #0 */
+ if (is_live && !is_new && !is_old)
+ {
+ if (repair_mode)
+ printf("repair unnecessary\n");
+ return;
+ }
+ /* remove new #1 */
+ else if (is_live && is_new && !is_old)
+ {
+ if (!rmtree(new_path, true))
+ {
+ pg_log_error("unable to remove new directory \"%s\": %m", NEW_KMGR_DIR);
+ fprintf(stderr, _("Exiting with no changes made.\n"));
+ exit(1);
+ }
+ printf(_("removed files created during previously aborted alter operation\n"));
+ return;
+ }
+ /* install new #2 */
+ else if (!is_live && is_new && is_old)
+ {
+ if (rename(new_path, live_path) != 0)
+ {
+ pg_log_error("unable to rename directory \"%s\" to \"%s\": %m",
+ NEW_KMGR_DIR, LIVE_KMGR_DIR);
+ fprintf(stderr, _("Exiting with no changes made.\n"));
+ exit(1);
+ }
+ printf(_("Installed new cluster password supplied in previous alter operation\n"));
+ return;
+ }
+ /* remove old #3 */
+ else if (is_live && !is_new && is_old)
+ {
+ if (!rmtree(old_path, true))
+ {
+ pg_log_error("unable to remove old directory \"%s\": %m", OLD_KMGR_DIR);
+ fprintf(stderr, _("Exiting with no changes made.\n"));
+ exit(1);
+ }
+ printf(_("Removed old files invalidated during previous alter operation\n"));
+ return;
+ }
+ else
+ {
+ pg_log_error("cluster file encryption directory \"%s\" is in an abnormal state and cannot be processed",
+ KMGR_DIR);
+ fprintf(stderr, _("Exiting with no changes made.\n"));
+ exit(1);
+ }
+ }
+
+ /* Retrieve old and new passphrases */
+ void
+ retrieve_passphrases()
+ {
+ /*
+ * If we have been asked to pass an open file descriptor to the user
+ * terminal to the commands, set one up.
+ */
+ if (pass_terminal_fd)
+ {
+ #ifndef WIN32
+ terminal_fd = open("/dev/tty", O_RDWR, 0);
+ #else
+ 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);
+ }
+ }
+
+ /* Get old key encryption key from the passphrase command */
+ old_passlen = kmgr_run_cluster_passphrase_command(old_cluster_passphrase_cmd,
+ old_passphrase, KMGR_MAX_PASSPHRASE_LEN,
+ live_path);
+ if (old_passlen < KMGR_MIN_PASSPHRASE_LEN)
+ {
+ pg_log_error("passphrase must be at least %d bytes", KMGR_MIN_PASSPHRASE_LEN);
+ bzero_keys_and_exit(1);
+ }
+
+ /*
+ * Create new key directory here in case the passphrase command needs it
+ * to exist.
+ */
+ if (mkdir(new_path, pg_dir_create_mode) != 0)
+ {
+ pg_log_error("unable to create new cluster passphrase directory \"%s\": %m", NEW_KMGR_DIR);
+ bzero_keys_and_exit(1);
+ }
+
+ /* Get new key */
+ new_passlen = kmgr_run_cluster_passphrase_command(new_cluster_passphrase_cmd,
+ new_passphrase, KMGR_MAX_PASSPHRASE_LEN,
+ new_path);
+ if (new_passlen < KMGR_MIN_PASSPHRASE_LEN)
+ {
+ pg_log_error("passphrase must be at least %d bytes", KMGR_MIN_PASSPHRASE_LEN);
+ bzero_keys_and_exit(1);
+ }
+
+ if (pass_terminal_fd)
+ close(terminal_fd);
+
+ /* output newline */
+ puts("");
+
+ if (strcmp(old_passphrase, new_passphrase) == 0)
+ {
+ pg_log_error("passphrases are identical, exiting\n");
+ bzero_keys_and_exit(1);
+ }
+
+ }
+
+ /* Decrypt old keys encrypted with old pass phrase and reencrypt with new one */
+ void
+ reencrypt_data_keys(void)
+ {
+ DIR *dir;
+ struct dirent *de;
+ PgKeyWrapCtx *old_ctx, *new_ctx;
+ uint8 old_enckey[KMGR_ENC_KEY_LEN], new_enckey[KMGR_ENC_KEY_LEN];
+ uint8 old_hmackey[KMGR_MAC_KEY_LEN], new_hmackey[KMGR_MAC_KEY_LEN];
+
+ if ((dir = opendir(live_path)) == NULL)
+ {
+ pg_log_error("unable to open live cluster passphrase directory \"%s\": %m", LIVE_KMGR_DIR);
+ bzero_keys_and_exit(1);
+ }
+
+ kmgr_derive_keys(old_passphrase, old_passlen, old_enckey, old_hmackey);
+ old_ctx = pg_create_keywrap_ctx(old_enckey, old_hmackey);
+
+ kmgr_derive_keys(new_passphrase, new_passlen, new_enckey, new_hmackey);
+ new_ctx = pg_create_keywrap_ctx(new_enckey, new_hmackey);
+
+ while ((de = readdir(dir)) != NULL)
+ {
+ /*
+ * We copy only the numeric files/keys, since there might be encrypted passphrase
+ * files in the old directory that only match the old key.
+ */
+ if (strspn(de->d_name, "0123456789") == strlen(de->d_name))
+ {
+ char src_path[MAXPGPATH], dst_path[MAXPGPATH];
+ int src_fd, dst_fd;
+ int len;
+ uint32 id = strtoul(de->d_name, NULL, 10);
+
+ CryptoKeyFilePath(src_path, live_path, id);
+ CryptoKeyFilePath(dst_path, new_path, id);
+
+ if ((src_fd = open(src_path, O_RDONLY | PG_BINARY, 0)) < 0)
+ {
+ pg_log_error("could not open file \"%s\": %m", src_path);
+ bzero_keys_and_exit(1);
+ }
+
+ if ((dst_fd = open(dst_path, O_RDWR | O_CREAT | O_TRUNC | PG_BINARY,
+ pg_file_create_mode)) < 0)
+ {
+ pg_log_error("could not open file \"%s\": %m", dst_path);
+ bzero_keys_and_exit(1);
+ }
+
+ /* Read the source key */
+ len = read(src_fd, &in_key, sizeof(CryptoKey));
+ if (len != sizeof(CryptoKey))
+ {
+ if (len < 0)
+ pg_log_error("could read file \"%s\": %m", src_path);
+ else
+ pg_log_error("could read file \"%s\": read %d of %zu",
+ src_path, len, sizeof(CryptoKey));
+ bzero_keys_and_exit(1);
+ }
+
+ /* decrypt with old key */
+ if (!kmgr_unwrap_key(old_ctx, &in_key, &data_key))
+ {
+ pg_log_error("incorrect old key specified");
+ bzero_keys_and_exit(1);
+ }
+
+ /* encrypt with new key */
+ if (!kmgr_wrap_key(new_ctx, &data_key, &out_key))
+ {
+ pg_log_error("could not encrypt new key");
+ bzero_keys_and_exit(1);
+ }
+
+ /* Write to the dest key */
+ len = write(dst_fd, &out_key, sizeof(CryptoKey));
+ if (len != sizeof(CryptoKey))
+ {
+ pg_log_error("could not write fie \"%s\"", dst_path);
+ bzero_keys_and_exit(1);
+ }
+
+ close(src_fd);
+ close(dst_fd);
+ }
+ }
+
+ /* The passphrase is correct, free the cipher context */
+ pg_free_keywrap_ctx(old_ctx);
+ pg_free_keywrap_ctx(new_ctx);
+
+ closedir(dir);
+ }
+
+ void
+ install_new_keys(void)
+ {
+ /* add fsyncs? XXX */
+ if (rename(live_path, old_path) != 0)
+ {
+ pg_log_error("unable to rename directory \"%s\" to \"%s\": %m",
+ LIVE_KMGR_DIR, OLD_KMGR_DIR);
+ bzero_keys_and_exit(1);
+ }
+
+ if (rename(new_path, live_path) != 0)
+ {
+ pg_log_error("unable to rename directory \"%s\" to \"%s\": %m",
+ NEW_KMGR_DIR, LIVE_KMGR_DIR);
+ bzero_keys_and_exit(1);
+ }
+
+ if (!rmtree(old_path, true))
+ {
+ pg_log_error("unable to remove old directory \"%s\": %m", OLD_KMGR_DIR);
+ bzero_keys_and_exit(1);
+ }
+ }
+
+ void
+ bzero_keys_and_exit(int exit_code)
+ {
+ explicit_bzero(old_passphrase, sizeof(old_passphrase));
+ explicit_bzero(new_passphrase, sizeof(new_passphrase));
+
+ explicit_bzero(&in_key, sizeof(in_key));
+ explicit_bzero(&data_key, sizeof(data_key));
+ explicit_bzero(&out_key, sizeof(out_key));
+
+ if (exit_code != 0)
+ {
+ unlink(pid_path);
+ printf("Re-running pg_altercpass to repair might be needed before the next server start\n");
+ }
+
+ exit(exit_code);
+ }
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..87b09db
*** 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 passphrase.
+ */
+ 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..5951f1e
*** 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_passphrase_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_passphrase_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_passphrase_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_passphrase_failure()
+ *
+ * Make sure there was no unrepaired pg_altercpass failure
+ */
+ static void
+ check_for_cluster_passphrase_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_altercpass failure that needs repair or\n"
+ "pg_altercpass is running. Run pg_altercpass --repair or wait for it\n"
+ "to complete.\n");
+ else
+ pg_fatal("The target cluster had a pg_altercpass failure that needs repair or\n"
+ "pg_altercpass is running. Run pg_altercpass --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..d7d0f7a
*** a/src/common/Makefile
--- b/src/common/Makefile
*************** OBJS_COMMON = \
*** 61,66 ****
--- 61,67 ----
ip.o \
jsonapi.o \
keywords.o \
+ kmgr_utils.o \
kwlookup.o \
link-canary.o \
md5_common.o \
*************** OBJS_COMMON = \
*** 81,90 ****
--- 82,93 ----
ifeq ($(with_openssl),yes)
OBJS_COMMON += \
+ cipher_openssl.o \
protocol_openssl.o \
cryptohash_openssl.o
else
OBJS_COMMON += \
+ cipher.o \
cryptohash.o \
md5.o \
sha2.o
diff --git a/src/common/cipher.c b/src/common/cipher.c
new file mode 100644
index ...6a80543
*** a/src/common/cipher.c
--- b/src/common/cipher.c
***************
*** 0 ****
--- 1,70 ----
+ /*-------------------------------------------------------------------------
+ *
+ * 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();
+ }
+
+ bool
+ pg_HMAC_SHA512(const uint8 *key, const uint8 *in, int inlen,
+ uint8 *out)
+ {
+ 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 ...44d1597
*** a/src/common/cipher_openssl.c
--- b/src/common/cipher_openssl.c
***************
*** 0 ****
--- 1,186 ----
+ /*-------------------------------------------------------------------------
+ * cipher_openssl.c
+ * Cryptographic function using OpenSSL
+ *
+ * This contains the common low-level functions needed in both frontend and
+ * backend, for implement the database encryption.
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/common/cipher_openssl.c
+ *
+ *-------------------------------------------------------------------------
+ */
+ #ifndef FRONTEND
+ #include "postgres.h"
+ #else
+ #include "postgres_fe.h"
+ #endif
+
+ #include "common/sha2.h"
+
+ #include "common/cipher.h"
+ #include
+ #include
+ #include
+ #include
+ #include
+
+ /*
+ * prototype for the EVP functions that return an algorithm, e.g.
+ * EVP_aes_128_cbc().
+ */
+ typedef const EVP_CIPHER *(*ossl_EVP_cipher_func) (void);
+
+ static 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;
+ }
+
+ 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);
+ }
+
+ 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/kmgr_utils.c b/src/common/kmgr_utils.c
new file mode 100644
index ...9e77dc5
*** a/src/common/kmgr_utils.c
--- b/src/common/kmgr_utils.c
***************
*** 0 ****
--- 1,580 ----
+ /*-------------------------------------------------------------------------
+ *
+ * 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 "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 cluster passphrase: "
+
+ #ifdef FRONTEND
+ static FILE *open_pipe_stream(const char *command);
+ static int close_pipe_stream(FILE *file);
+ #endif
+
+ static void read_one_keyfile(const char *dataDir, uint32 id, CryptoKey *key_p);
+
+ /* Return a key wrap context initialized with the given keys */
+ PgKeyWrapCtx *
+ pg_create_keywrap_ctx(uint8 key[KMGR_ENC_KEY_LEN], uint8 mackey[KMGR_MAC_KEY_LEN])
+ {
+ PgKeyWrapCtx *ctx;
+
+ ctx = (PgKeyWrapCtx *) palloc0(sizeof(PgKeyWrapCtx));
+
+ /* Create and initialize a cipher context */
+ ctx->cipherctx = pg_cipher_ctx_create(PG_CIPHER_AES_CBC, key, KMGR_ENC_KEY_LEN);
+ if (ctx->cipherctx == NULL)
+ return NULL;
+
+ /* Set encryption key and MAC key */
+ memcpy(ctx->mackey, mackey, KMGR_MAC_KEY_LEN);
+
+ return ctx;
+ }
+
+ /* Free the key wrap context */
+ void
+ pg_free_keywrap_ctx(PgKeyWrapCtx *ctx)
+ {
+ if (!ctx)
+ return;
+
+ Assert(ctx->cipherctx);
+
+ pg_cipher_ctx_free(ctx->cipherctx);
+
+ #ifndef FRONTEND
+ pfree(ctx);
+ #else
+ pg_free(ctx);
+ #endif
+ }
+
+ /*
+ * Encrypt the given data. Return true and set encrypted data to 'out' if
+ * success. Otherwise return false. The caller must allocate sufficient space
+ * for cipher data calculated by using KmgrSizeOfCipherText(). Please note that
+ * this function modifies 'out' data even on failure case.
+ */
+ bool
+ kmgr_wrap_key(PgKeyWrapCtx *ctx, CryptoKey *in, CryptoKey *out)
+ {
+ uint8 *hmac;
+ uint8 *iv;
+ uint8 *enc;
+ int enclen;
+
+ Assert(ctx && in && out);
+
+ hmac = out->key;
+ iv = hmac + KMGR_HMAC_LEN;
+ enc = iv + PG_AES_IV_SIZE;
+
+ /* Generate IV */
+ if (!pg_strong_random(iv, PG_AES_IV_SIZE))
+ return false;
+
+ if (!pg_cipher_encrypt(ctx->cipherctx, in->key, in->klen, enc, &enclen, iv))
+ return false;
+
+ if (!pg_HMAC_SHA512(ctx->mackey, enc, enclen, hmac))
+ return false;
+
+ out->klen = KmgrSizeOfCipherText(in->klen);;
+ Assert(out->klen == KMGR_HMAC_LEN + PG_AES_IV_SIZE + enclen);
+
+ return true;
+ }
+
+ /*
+ * Decrypt the given Data. Return true and set plain text data to `out` if
+ * success. Otherwise return false. The caller must allocate sufficient space
+ * for cipher data calculated by using KmgrSizeOfPlainText(). Please note that
+ * this function modifies 'out' data even on failure case.
+ */
+ bool
+ kmgr_unwrap_key(PgKeyWrapCtx *ctx, CryptoKey *in, CryptoKey *out)
+ {
+ uint8 hmac[KMGR_HMAC_LEN];
+ uint8 *expected_hmac;
+ uint8 *iv;
+ uint8 *enc;
+ int enclen;
+
+ Assert(ctx && in && out);
+
+ expected_hmac = in->key;
+ iv = expected_hmac + KMGR_HMAC_LEN;
+ enc = iv + PG_AES_IV_SIZE;
+ enclen = in->klen - (enc - in->key);
+
+ /* Verify the correctness of HMAC */
+ if (!pg_HMAC_SHA512(ctx->mackey, enc, enclen, hmac))
+ return false;
+
+ if (memcmp(hmac, expected_hmac, KMGR_HMAC_LEN) != 0)
+ return false;
+
+ /* Decrypt encrypted data */
+ if (!pg_cipher_decrypt(ctx->cipherctx, enc, enclen, out->key, &(out->klen), iv))
+ return false;
+
+ return true;
+ }
+
+ /*
+ * Verify the correctness of the given passphrase by unwrapping the given keys.
+ * If the given passphrase is correct we set unwrapped keys to 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_passphrase(char *passphrase, 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 passphrase.
+ */
+ kmgr_derive_keys(passphrase, passlen, user_enckey, user_hmackey);
+ tmpctx = pg_create_keywrap_ctx(user_enckey, user_hmackey);
+
+ for (int i = 0; i < nkeys; i++)
+ {
+ if (!kmgr_unwrap_key(tmpctx, &(in_keys[i]), &(out_keys[i])))
+ {
+ /* The passphrase is not correct */
+ pg_free_keywrap_ctx(tmpctx);
+ return false;
+ }
+ explicit_bzero(&(in_keys[i]), sizeof(in_keys[i]));
+ }
+
+ /* The passphrase is correct, free the cipher context */
+ pg_free_keywrap_ctx(tmpctx);
+
+ return true;
+ }
+
+ /* Generate encryption key and mac key from given passphrase */
+ void
+ kmgr_derive_keys(char *passphrase, Size passlen,
+ uint8 enckey[KMGR_ENC_KEY_LEN],
+ uint8 mackey[KMGR_MAC_KEY_LEN])
+ {
+ pg_cryptohash_ctx *ctx1, *ctx2;
+
+ StaticAssertStmt(KMGR_ENC_KEY_LEN == PG_AES256_KEY_LEN,
+ "derived encryption key size does not match AES256 key size");
+ StaticAssertStmt(KMGR_MAC_KEY_LEN == PG_HMAC_SHA512_KEY_LEN,
+ "derived mac key size does not match HMAC-SHA512 key size");
+
+ /* Generate encryption key from passphrase */
+ ctx1 = pg_cryptohash_create(PG_SHA256);
+ if (pg_cryptohash_init(ctx1) < 0 ||
+ pg_cryptohash_update(ctx1, (uint8 *) passphrase, passlen) < 0 ||
+ pg_cryptohash_final(ctx1, enckey) < 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(ctx1);
+
+ /* Generate mac key from passphrase */
+ ctx2 = pg_cryptohash_create(PG_SHA512);
+ if (pg_cryptohash_init(ctx2) < 0 ||
+ pg_cryptohash_update(ctx2, (uint8 *) passphrase, passlen) < 0 ||
+ pg_cryptohash_final(ctx2, 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(ctx2);
+ }
+
+ /*
+ * Run cluster passphrase command.
+ *
+ * prompt will be substituted for %P, file descriptor for %R
+ *
+ * The result will be put in buffer buf, which is of size size.
+ * The return value is the length of the actual result.
+ */
+ int
+ kmgr_run_cluster_passphrase_command(char *passphrase_command, char *buf,
+ int size, char *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 = passphrase_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 passphrase referenced %%R, but -R not specified");
+ exit(EXIT_FAILURE);
+ #else
+ ereport(ERROR,
+ (errcode(ERRCODE_INTERNAL_ERROR),
+ errmsg("cluster passphrase 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, 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
+ }
+ }
+
+ #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
+ }
+
+ 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 ...f8bd4db
*** a/src/include/common/cipher.h
--- b/src/include/common/cipher.h
***************
*** 0 ****
--- 1,78 ----
+ /*-------------------------------------------------------------------------
+ *
+ * cipher.h
+ * Declarations for cryptographic functions
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * src/include/common/cipher.h
+ *
+ *-------------------------------------------------------------------------
+ */
+ #ifndef 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)
+
+ /* HMAC key and HMAC length. We use HMAC-SHA256 */
+ #define PG_HMAC_SHA512_KEY_LEN 64
+ #define PG_HMAC_SHA512_LEN 64
+
+ #ifdef USE_OPENSSL
+ typedef EVP_CIPHER_CTX cipher_private_ctx;
+ #else
+ typedef void cipher_private_ctx;
+ #endif
+
+ /*
+ * This struct has two implementation-private context for
+ * encryption and decryption. The caller must create the encryption
+ * context using by pg_cipher_ctx_create() and pass the context to
+ * pg_cipher_encrypt() or pg_cipher_decrypt().
+ */
+ typedef struct PgCipherCtx
+ {
+ cipher_private_ctx *encctx;
+ cipher_private_ctx *decctx;
+ } PgCipherCtx;
+
+ extern PgCipherCtx *pg_cipher_ctx_create(int cipher, uint8 *key, int klen);
+ extern void pg_cipher_ctx_free(PgCipherCtx *ctx);
+ extern bool pg_cipher_encrypt(PgCipherCtx *ctx,
+ const uint8 *in, int inlen,
+ uint8 *out, int *outlen,
+ const uint8 *iv);
+ extern bool pg_cipher_decrypt(PgCipherCtx *ctx,
+ const uint8 *in, int inlen,
+ uint8 *out, int *outlen,
+ const uint8 *iv);
+ extern bool pg_HMAC_SHA512(const uint8 *key,
+ const uint8 *in, int inlen,
+ uint8 *out);
+
+ #endif /* PG_CIPHER_H */
diff --git a/src/include/common/kmgr_utils.h b/src/include/common/kmgr_utils.h
new file mode 100644
index ...a25bb7d
*** a/src/include/common/kmgr_utils.h
--- b/src/include/common/kmgr_utils.h
***************
*** 0 ****
--- 1,108 ----
+ /*-------------------------------------------------------------------------
+ *
+ * kmgr_utils.h
+ * Declarations for utility function for file encryption key
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * src/include/common/kmgr_utils.h
+ *
+ *-------------------------------------------------------------------------
+ */
+ #ifndef KMGR_UTILS_H
+ #define KMGR_UTILS_H
+
+ #include "common/cipher.h"
+
+ /* Current version number */
+ #define KMGR_VERSION 1
+
+ /*
+ * Directories where cluster file encryption keys reside within PGDATA.
+ */
+ #define KMGR_DIR "pg_cryptokeys"
+ #define KMGR_DIR_PID KMGR_DIR"/pg_altercpass.pid"
+ #define LIVE_KMGR_DIR KMGR_DIR"/live"
+ /* used during cluster passphrase 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)
+
+ /* Allowed length of cluster passphrase */
+ #define KMGR_MIN_PASSPHRASE_LEN 64
+ #define KMGR_MAX_PASSPHRASE_LEN 1024
+
+ /* Maximum length of key the key manager can store */
+ #define KMGR_MAX_KEY_LEN 256
+ #define KMGR_MAX_WRAPPED_KEY_LEN KmgrSizeOfCipherText(KMGR_MAX_KEY_LEN)
+
+ /*
+ * Size of encrypted key size with padding. We use PKCS#7 padding,
+ * described in RFC 5652.
+ */
+ #define SizeOfDataWithPadding(klen) \
+ ((int)(klen) + (PG_AES_BLOCK_SIZE - ((int)(klen) % PG_AES_BLOCK_SIZE)))
+
+ /* Macros to compute the size of cipher text and plain text */
+ #define KmgrSizeOfCipherText(len) \
+ (KMGR_MAC_KEY_LEN + PG_AES_IV_SIZE + SizeOfDataWithPadding((int)(len)))
+ #define KmgrSizeOfPlainText(klen) \
+ ((int)(klen) - (KMGR_MAC_KEY_LEN + PG_AES_IV_SIZE))
+
+ /* CryptoKey file name is keys id */
+ #define CryptoKeyFilePath(path, dir, id) \
+ snprintf((path), MAXPGPATH, "%s/%d", (dir), (id))
+
+ /*
+ * Cryptographic key data structure. This structure is used for
+ * both on-disk (raw key) and on-memory (wrapped key).
+ */
+ typedef struct CryptoKey
+ {
+ int klen;
+ uint8 key[KMGR_MAX_WRAPPED_KEY_LEN];
+ } CryptoKey;
+
+ /* Key wrapping cipher context */
+ typedef struct PgKeyWrapCtx
+ {
+ uint8 mackey[KMGR_MAC_KEY_LEN];
+ PgCipherCtx *cipherctx;
+ } PgKeyWrapCtx;
+
+ extern PgKeyWrapCtx *pg_create_keywrap_ctx(uint8 key[KMGR_ENC_KEY_LEN],
+ uint8 mackey[KMGR_MAC_KEY_LEN]);
+ extern void pg_free_keywrap_ctx(PgKeyWrapCtx *ctx);
+ extern bool kmgr_wrap_key(PgKeyWrapCtx *ctx, CryptoKey *in, CryptoKey *out);
+ extern bool kmgr_unwrap_key(PgKeyWrapCtx *ctx, CryptoKey *in, CryptoKey *out);
+
+
+
+ extern void kmgr_derive_keys(char *passphrase, Size passlen,
+ uint8 enckey[KMGR_ENC_KEY_LEN],
+ uint8 mackey[KMGR_MAC_KEY_LEN]);
+ extern bool kmgr_verify_passphrase(char *passphrase, int passlen,
+ CryptoKey *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_passphrase_command(char *passphrase_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 ...5464af1
*** a/src/include/crypto/kmgr.h
--- b/src/include/crypto/kmgr.h
***************
*** 0 ****
--- 1,29 ----
+ /*-------------------------------------------------------------------------
+ *
+ * kmgr.h
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * src/include/crypto/kmgr.h
+ *
+ *-------------------------------------------------------------------------
+ */
+ #ifndef KMGR_H
+ #define KMGR_H
+
+ #include "common/cipher.h"
+ #include "common/kmgr_utils.h"
+ #include "storage/relfilenode.h"
+ #include "storage/bufpage.h"
+
+ /* GUC parameters */
+ extern int file_encryption_keylen;
+ extern char *cluster_passphrase_command;
+
+ extern Size KmgrShmemSize(void);
+ extern void KmgrShmemInit(void);
+ extern void BootStrapKmgr(void);
+ extern void InitializeKmgr(void);
+ extern const CryptoKey *KmgrGetKey(int id);
+
+ #endif /* KMGR_H */
diff --git a/src/include/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/guc_tables.h b/src/include/utils/guc_tables.h
new file mode 100644
index 7f36e11..c0dbf69
*** a/src/include/utils/guc_tables.h
--- b/src/include/utils/guc_tables.h
*************** enum config_group
*** 89,94 ****
--- 89,95 ----
STATS,
STATS_MONITORING,
STATS_COLLECTOR,
+ ENCRYPTION,
AUTOVACUUM,
CLIENT_CONN,
CLIENT_CONN_STATEMENT,
diff --git a/src/test/Makefile b/src/test/Makefile
new file mode 100644
index 14cde4f..7db4c0b
*** a/src/test/Makefile
--- b/src/test/Makefile
*************** endif
*** 30,36 ****
endif
ifeq ($(with_openssl),yes)
ifneq (,$(filter ssl,$(PG_TEST_EXTRA)))
! SUBDIRS += ssl
endif
endif
--- 30,36 ----
endif
ifeq ($(with_openssl),yes)
ifneq (,$(filter ssl,$(PG_TEST_EXTRA)))
! SUBDIRS += ssl crypto
endif
endif
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
new file mode 100644
index f92c140..c7a08ad
*** a/src/tools/msvc/Mkvcbuild.pm
--- b/src/tools/msvc/Mkvcbuild.pm
*************** sub mkvcbuild
*** 122,139 ****
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');
--- 122,141 ----
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 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, 'cryptohash_openssl.c');
push(@pgcommonallfiles, 'protocol_openssl.c');
}
else
{
+ push(@pgcommonallfiles, 'cipher.c');
push(@pgcommonallfiles, 'cryptohash.c');
push(@pgcommonallfiles, 'md5.c');
push(@pgcommonallfiles, 'sha2.c');