From 0802065af07a62169802a6adc75edf763d1db385 Mon Sep 17 00:00:00 2001 From: Masahiko Sawada Date: Wed, 30 Oct 2019 16:16:18 +0900 Subject: [PATCH v2 2/5] Enable transparent data encryption --- src/backend/access/transam/xlog.c | 46 +++ src/backend/bootstrap/bootstrap.c | 11 +- src/backend/postmaster/pgstat.c | 9 + src/backend/postmaster/postmaster.c | 6 + src/backend/storage/encryption/kmgr.c | 366 ++++++++++++++++++ src/backend/tcop/postgres.c | 8 + src/backend/utils/misc/guc.c | 37 +- src/backend/utils/misc/postgresql.conf.sample | 5 + src/bin/initdb/initdb.c | 77 +++- src/bin/pg_checksums/pg_checksums.c | 1 + src/bin/pg_controldata/pg_controldata.c | 19 + src/include/access/xlog.h | 4 + src/include/catalog/pg_control.h | 10 + src/include/pgstat.h | 3 + src/include/storage/kmgr.h | 67 ++++ src/include/utils/guc_tables.h | 1 + 16 files changed, 665 insertions(+), 5 deletions(-) create mode 100644 src/backend/storage/encryption/kmgr.c create mode 100644 src/include/storage/kmgr.h diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index 2e3cc51006..fe7b253df4 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -55,8 +55,10 @@ #include "replication/walreceiver.h" #include "replication/walsender.h" #include "storage/bufmgr.h" +#include "storage/encryption.h" #include "storage/fd.h" #include "storage/ipc.h" +#include "storage/kmgr.h" #include "storage/large_object.h" #include "storage/latch.h" #include "storage/pmsignal.h" @@ -77,6 +79,7 @@ #include "pg_trace.h" extern uint32 bootstrap_data_checksum_version; +extern uint32 bootstrap_data_encryption_cipher; /* Unsupported old recovery command file names (relative to $PGDATA) */ #define RECOVERY_COMMAND_FILE "recovery.conf" @@ -4783,6 +4786,10 @@ ReadControlFile(void) /* Make the initdb settings visible as GUC variables, too */ SetConfigOption("data_checksums", DataChecksumsEnabled() ? "yes" : "no", PGC_INTERNAL, PGC_S_OVERRIDE); + + SetConfigOption("data_encryption_cipher", + EncryptionCipherString(GetDataEncryptionCipher()), + PGC_INTERNAL, PGC_S_OVERRIDE); } /* @@ -4815,6 +4822,20 @@ GetMockAuthenticationNonce(void) return ControlFile->mock_authentication_nonce; } +WrappedEncKeyWithHmac * +GetTDERelationEncryptionKey(void) +{ + Assert(ControlFile != NULL); + return &(ControlFile->tde_rdek); +} + +WrappedEncKeyWithHmac * +GetTDEWALEncryptionKey(void) +{ + Assert(ControlFile != NULL); + return &(ControlFile->tde_wdek); +} + /* * Are checksums enabled for data pages? */ @@ -4825,6 +4846,13 @@ DataChecksumsEnabled(void) return (ControlFile->data_checksum_version > 0); } +int +GetDataEncryptionCipher(void) +{ + Assert(ControlFile != NULL); + return ControlFile->data_encryption_cipher; +} + /* * Returns a fake LSN for unlogged relations. * @@ -5091,6 +5119,7 @@ BootStrapXLOG(void) XLogPageHeader page; XLogLongPageHeader longpage; XLogRecord *record; + KmgrBootstrapInfo *kmgrinfo; char *recptr; bool use_existent; uint64 sysidentifier; @@ -5168,6 +5197,12 @@ BootStrapXLOG(void) SetMultiXactIdLimit(checkPoint.oldestMulti, checkPoint.oldestMultiDB, true); SetCommitTsLimit(InvalidTransactionId, InvalidTransactionId); + /* + * Bootstrap key management module beforehand in order to encrypt the first + * xlog record. + */ + kmgrinfo = BootStrapKmgr(bootstrap_data_encryption_cipher); + /* Set up the XLOG page header */ page->xlp_magic = XLOG_PAGE_MAGIC; page->xlp_info = XLP_LONG_HEADER; @@ -5204,6 +5239,11 @@ BootStrapXLOG(void) use_existent = false; openLogFile = XLogFileInit(1, &use_existent, false); + /* Encrypt the first xlog record if necessary */ + if (bootstrap_data_encryption_cipher > TDE_ENCRYPTION_OFF) + page = (XLogPageHeader) EncryptXLog((char *) page, + recptr - (char *) page, 1, 0); + /* Write the first page with the initial record */ errno = 0; pgstat_report_wait_start(WAIT_EVENT_WAL_BOOTSTRAP_WRITE); @@ -5243,6 +5283,11 @@ BootStrapXLOG(void) ControlFile->checkPoint = checkPoint.redo; ControlFile->checkPointCopy = checkPoint; ControlFile->unloggedLSN = FirstNormalUnloggedLSN; + if (kmgrinfo) + { + memcpy(&(ControlFile->tde_rdek), &(kmgrinfo->relEncKey), sizeof(WrappedEncKeyWithHmac)); + memcpy(&(ControlFile->tde_wdek), &(kmgrinfo->walEncKey), sizeof(WrappedEncKeyWithHmac)); + } /* Set important parameter values for use when replaying WAL */ ControlFile->MaxConnections = MaxConnections; @@ -5254,6 +5299,7 @@ BootStrapXLOG(void) ControlFile->wal_log_hints = wal_log_hints; ControlFile->track_commit_timestamp = track_commit_timestamp; ControlFile->data_checksum_version = bootstrap_data_checksum_version; + ControlFile->data_encryption_cipher = bootstrap_data_encryption_cipher; /* some additional ControlFile fields are set in WriteControlFile() */ diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c index 9238fbe98d..155a4a9752 100644 --- a/src/backend/bootstrap/bootstrap.c +++ b/src/backend/bootstrap/bootstrap.c @@ -40,6 +40,8 @@ #include "storage/bufmgr.h" #include "storage/bufpage.h" #include "storage/condition_variable.h" +#include "storage/encryption.h" +#include "storage/kmgr.h" #include "storage/ipc.h" #include "storage/proc.h" #include "tcop/tcopprot.h" @@ -52,6 +54,9 @@ uint32 bootstrap_data_checksum_version = 0; /* No checksum */ +/* No encryption */ +uint32 bootstrap_data_encryption_cipher = TDE_ENCRYPTION_OFF; + #define ALLOC(t, c) \ ((t *) MemoryContextAllocZero(TopMemoryContext, (unsigned)(c) * sizeof(t))) @@ -226,7 +231,7 @@ AuxiliaryProcessMain(int argc, char *argv[]) /* If no -x argument, we are a CheckerProcess */ MyAuxProcType = CheckerProcess; - while ((flag = getopt(argc, argv, "B:c:d:D:Fkr:x:X:-:")) != -1) + while ((flag = getopt(argc, argv, "B:c:d:D:e:Fkr:x:X:-:")) != -1) { switch (flag) { @@ -249,6 +254,10 @@ AuxiliaryProcessMain(int argc, char *argv[]) pfree(debugstr); } break; + + case 'e': + bootstrap_data_encryption_cipher = EncryptionCipherValue(optarg); + break; case 'F': SetConfigOption("fsync", "false", PGC_POSTMASTER, PGC_S_ARGV); break; diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c index 011076c3e3..1d1ee1edbb 100644 --- a/src/backend/postmaster/pgstat.c +++ b/src/backend/postmaster/pgstat.c @@ -3956,6 +3956,15 @@ pgstat_get_wait_io(WaitEventIO w) case WAIT_EVENT_DSM_FILL_ZERO_WRITE: event_name = "DSMFillZeroWrite"; break; + case WAIT_EVENT_KMGR_FILE_READ: + event_name = "KmgrFileRead"; + break; + case WAIT_EVENT_KMGR_FILE_SYNC: + event_name = "KmgrFileSync"; + break; + case WAIT_EVENT_KMGR_FILE_WRITE: + event_name = "KmgrFileWrite"; + break; case WAIT_EVENT_LOCK_FILE_ADDTODATADIR_READ: event_name = "LockFileAddToDataDirRead"; break; diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index 5f30359165..46a1878c1f 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -119,6 +119,7 @@ #include "replication/walsender.h" #include "storage/fd.h" #include "storage/ipc.h" +#include "storage/kmgr.h" #include "storage/pg_shmem.h" #include "storage/pmsignal.h" #include "storage/proc.h" @@ -1335,6 +1336,11 @@ PostmasterMain(int argc, char *argv[]) */ autovac_init(); + /* + * Initialize cluster encryption key manager. + */ + InitializeKmgr(); + /* * Load configuration files for client authentication. */ diff --git a/src/backend/storage/encryption/kmgr.c b/src/backend/storage/encryption/kmgr.c new file mode 100644 index 0000000000..a9638c63c1 --- /dev/null +++ b/src/backend/storage/encryption/kmgr.c @@ -0,0 +1,366 @@ +/*------------------------------------------------------------------------- + * + * kmgr.c + * Encryption key management module. + * + * Copyright (c) 2019, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/storage/encryption/kmgr.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include + +#include "funcapi.h" +#include "miscadmin.h" +#include "pgstat.h" + +#include "access/xlog.h" +#include "common/sha2.h" +#include "storage/encryption.h" +#include "storage/fd.h" +#include "storage/kmgr.h" +#include "storage/shmem.h" +#include "utils/builtins.h" +#include "utils/guc.h" +#include "utils/memutils.h" + +#define KMGR_PROMPT_MSG "Enter database encryption pass phrase:" + +/* + * Key encryption key. This variable is set during verification + * of user given passphrase. After verified, the plain key data + * is set to this variable. + */ +static keydata_t keyEncKey[TDE_KEK_SIZE]; + +/* + * Relation encryption key and WAL encryption key. Similar to + * key encryption key, these variables store the plain key data. + */ +static keydata_t relEncKey[TDE_MAX_DEK_SIZE]; +static keydata_t walEncKey[TDE_MAX_DEK_SIZE]; + +/* GUC variable */ +char *cluster_passphrase_command = NULL; + +static int run_cluster_passphrase_command(char *buf, int size); +static void get_kek_and_hmackey_from_passphrase(char *passphrase, + Size passlen, + keydata_t kek[TDE_KEK_SIZE], + keydata_t hmackey[TDE_HMAC_KEY_SIZE]); +static bool verify_passphrase(char *passphrase, int passlen, + WrappedEncKeyWithHmac *rdek, + WrappedEncKeyWithHmac *wdek); + +/* + * This func must be called ONCE on system install. we retrive KEK, + * generate RDEK and WDEK etc. + */ +KmgrBootstrapInfo * +BootStrapKmgr(int bootstrap_data_encryption_cipher) +{ + KmgrBootstrapInfo *kmgrinfo; + char passphrase[TDE_MAX_PASSPHRASE_LEN]; + keydata_t hmackey[TDE_HMAC_KEY_SIZE]; + keydata_t *rdek_enc; + keydata_t *wdek_enc; + keydata_t *rdek_hmac; + keydata_t *wdek_hmac; + int wrapped_keysize; + int len; + int size; + + if (bootstrap_data_encryption_cipher == TDE_ENCRYPTION_OFF) + return NULL; + +#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 + + kmgrinfo = palloc0(sizeof(KmgrBootstrapInfo)); + rdek_enc = kmgrinfo->relEncKey.key; + rdek_hmac = kmgrinfo->relEncKey.hmac; + wdek_enc = kmgrinfo->walEncKey.key; + wdek_hmac = kmgrinfo->walEncKey.hmac; + + /* + * Set data encryption cipher so that subsequent bootstrapping process + * can proceed. + */ + SetConfigOption("data_encryption_cipher", + EncryptionCipherString(bootstrap_data_encryption_cipher), + PGC_INTERNAL, PGC_S_OVERRIDE); + + /* Get key encryption key fro command */ + len = run_cluster_passphrase_command(passphrase, TDE_MAX_PASSPHRASE_LEN); + + /* Get key encryption key and HMAC key from passphrase */ + get_kek_and_hmackey_from_passphrase(passphrase, len, keyEncKey, + hmackey); + + /* + * Generate relation encryption key and WAL encryption key. + * The generated two keys must be stored in relEncKey and + * walEncKey that can be used by other modules since even + * during bootstrapping we need to encrypt both systemcatalogs + * and WAL. + */ + if (!pg_strong_random(relEncKey, EncryptionKeySize)) + ereport(ERROR, + (errmsg("failed to generate relation encryption key"))); + if (!pg_strong_random(walEncKey, EncryptionKeySize)) + ereport(ERROR, + (errmsg("failed to generate WAL encryption key"))); + + /* Wrap both keys by KEK */ + wrapped_keysize = EncryptionKeySize + TDE_DEK_WRAP_VALUE_SIZE; + pg_wrap_key(keyEncKey, TDE_KEK_SIZE, + relEncKey, EncryptionKeySize, + rdek_enc, &size); + if (size != wrapped_keysize) + elog(ERROR, "wrapped relation encryption key size is invalid, got %d expected %d", + size, wrapped_keysize); + + pg_wrap_key(keyEncKey, TDE_KEK_SIZE, + walEncKey, EncryptionKeySize, + wdek_enc, &size); + if (size != wrapped_keysize) + elog(ERROR, "wrapped WAL encryption key size is invalid, got %d expected %d", + size, wrapped_keysize); + + /* Compute both HMAC */ + pg_compute_hmac(hmackey, TDE_HMAC_KEY_SIZE, + rdek_enc, wrapped_keysize, + rdek_hmac); + pg_compute_hmac(hmackey, TDE_HMAC_KEY_SIZE, + wdek_enc, wrapped_keysize, + wdek_hmac); + + /* return keys and HMACs generated during bootstrap */ + return kmgrinfo; +} + +/* + * Run cluster_passphrase_command + * + * prompt will be substituted for %p. + * + * The result will be put in buffer buf, which is of size size. + * The return value is the length of the actual result. + */ +static int +run_cluster_passphrase_command(char *buf, int size) +{ + StringInfoData command; + char *p; + FILE *fh; + int pclose_rc; + size_t len = 0; + + Assert(size > 0); + buf[0] = '\0'; + + initStringInfo(&command); + + for (p = cluster_passphrase_command; *p; p++) + { + if (p[0] == '%') + { + switch (p[1]) + { + case 'p': + appendStringInfoString(&command, KMGR_PROMPT_MSG); + p++; + break; + case '%': + appendStringInfoChar(&command, '%'); + p++; + break; + default: + appendStringInfoChar(&command, p[0]); + } + } + else + appendStringInfoChar(&command, p[0]); + } + + fh = OpenPipeStream(command.data, "r"); + if (fh == NULL) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not execute command \"%s\": %m", + command.data))); + + if (!fgets(buf, size, fh)) + { + if (ferror(fh)) + { + pfree(command.data); + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not read from command \"%s\": %m", + command.data))); + } + } + + pclose_rc = ClosePipeStream(fh); + if (pclose_rc == -1) + { + pfree(command.data); + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not close pipe to external command: %m"))); + } + else if (pclose_rc != 0) + { + pfree(command.data); + ereport(ERROR, + (errcode_for_file_access(), + errmsg("command \"%s\" failed", + command.data), + errdetail_internal("%s", wait_result_to_str(pclose_rc)))); + } + + /* strip trailing newline */ + len = strlen(buf); + if (len > 0 && buf[len - 1] == '\n') + buf[--len] = '\0'; + + pfree(command.data); + + return len; +} + +/* + * Get encryption key passphrase and verify it, then get the un-encrypted + * RDEK and WDEK. This function is called by postmaster at startup time. + */ +void +InitializeKmgr(void) +{ + WrappedEncKeyWithHmac *wrapped_rdek; + WrappedEncKeyWithHmac *wrapped_wdek; + char passphrase[TDE_MAX_PASSPHRASE_LEN]; + int len; + int wrapped_keysize; + int unwrapped_size; + + if (!DataEncryptionEnabled()) + return; + + /* Get cluster passphrase */ + len = run_cluster_passphrase_command(passphrase, TDE_MAX_PASSPHRASE_LEN); + + /* Get two wrapped keys stored in control file */ + wrapped_rdek = GetTDERelationEncryptionKey(); + wrapped_wdek = GetTDEWALEncryptionKey(); + + wrapped_keysize = EncryptionKeySize + TDE_DEK_WRAP_VALUE_SIZE; + + /* Verify the correctness of given passphrase */ + if (!verify_passphrase(passphrase, len, wrapped_rdek, wrapped_wdek)) + ereport(ERROR, + (errmsg("cluster passphrase does not match expected passphrase"))); + + /* The passphrase is correct, unwrap both RDEK and WDEK */ + pg_unwrap_key(keyEncKey, TDE_KEK_SIZE, + wrapped_rdek->key, wrapped_keysize, + relEncKey, &unwrapped_size); + if (unwrapped_size != EncryptionKeySize) + elog(ERROR, "unwrapped relation encryption key size is invalid, got %d expected %d", + unwrapped_size, EncryptionKeySize); + + pg_unwrap_key(keyEncKey, TDE_KEK_SIZE, + wrapped_wdek->key, wrapped_keysize, + walEncKey, &unwrapped_size); + if (unwrapped_size != EncryptionKeySize) + elog(ERROR, "unwrapped WAL encryptoin key size is invalid, got %d expected %d", + unwrapped_size, EncryptionKeySize); +} + + /* + * Hash the given passphrase and extract it into KEK and HMAC + * key. + */ +static void +get_kek_and_hmackey_from_passphrase(char *passphrase, Size passlen, + keydata_t kek_out[TDE_KEK_SIZE], + keydata_t hmackey_out[TDE_HMAC_KEY_SIZE]) +{ + keydata_t enckey_and_hmackey[PG_SHA512_DIGEST_LENGTH]; + pg_sha512_ctx ctx; + + pg_sha512_init(&ctx); + pg_sha512_update(&ctx, (const uint8 *) passphrase, passlen); + pg_sha512_final(&ctx, enckey_and_hmackey); + + /* + * SHA-512 results 64 bytes. We extract it into two keys for + * each 32 bytes: one for key encryption and another one for + * HMAC. + */ + memcpy(kek_out, enckey_and_hmackey, TDE_KEK_SIZE); + memcpy(hmackey_out, enckey_and_hmackey + TDE_KEK_SIZE, TDE_HMAC_KEY_SIZE); +} + +/* + * Verify the correctness of the given passphrase. We compute HMACs of the + * wrapped keys (RDEK and WDEK) using the HMAC key retrived from the user + * provided passphrase. And then we compare it with the HMAC stored alongside + * the controlfile. Return true if both HMACs are matched, meaning the given + * passphrase is correct. Otherwise return false. + */ +static bool +verify_passphrase(char *passphrase, int passlen, + WrappedEncKeyWithHmac *rdek, WrappedEncKeyWithHmac *wdek) +{ + keydata_t user_kek[TDE_KEK_SIZE]; + keydata_t user_hmackey[TDE_HMAC_KEY_SIZE]; + keydata_t result_hmac[TDE_HMAC_SIZE]; + int wrapped_keysize = EncryptionKeySize + TDE_DEK_WRAP_VALUE_SIZE; + + get_kek_and_hmackey_from_passphrase(passphrase, passlen, + user_kek, user_hmackey); + + /* Verify both HMACs of RDEK and WDEK */ + pg_compute_hmac(user_hmackey, TDE_HMAC_KEY_SIZE, + rdek->key, wrapped_keysize, + result_hmac); + if (memcmp(result_hmac, rdek->hmac, TDE_HMAC_SIZE) != 0) + return false; + + pg_compute_hmac(user_hmackey, TDE_HMAC_KEY_SIZE, + wdek->key, wrapped_keysize, + result_hmac); + if (memcmp(result_hmac, wdek->hmac, TDE_HMAC_SIZE) != 0) + return false; + + /* The passphrase is verified. Save the key encryption key */ + memcpy(keyEncKey, user_kek, TDE_KEK_SIZE); + + return true; +} + +/* Return plain relation encryption key */ +const char * +KmgrGetRelationEncryptionKey(void) +{ + Assert(DataEncryptionEnabled()); + return (const char *) relEncKey; +} + +/* Return plain WAL encryption key */ +const char * +KmgrGetWALEncryptionKey(void) +{ + Assert(DataEncryptionEnabled()); + return (const char *) walEncKey; +} diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index 4bec40aa28..b493641b58 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -63,6 +63,7 @@ #include "replication/walsender.h" #include "rewrite/rewriteHandler.h" #include "storage/bufmgr.h" +#include "storage/kmgr.h" #include "storage/ipc.h" #include "storage/proc.h" #include "storage/procsignal.h" @@ -3863,6 +3864,13 @@ PostgresMain(int argc, char *argv[], /* Early initialization */ BaseInit(); + /* + * Initialize kmgr for cluster encryption. Since kmgr needs to attach to + * shared memory the initialization must be called after BaseInit(). + */ + if (!IsUnderPostmaster) + InitializeKmgr(); + /* * Create a per-backend PGPROC struct in shared memory, except in the * EXEC_BACKEND case where this was done in SubPostmasterMain. We must do diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 31a5ef0474..a2756c2c3c 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -71,7 +71,10 @@ #include "replication/walreceiver.h" #include "replication/walsender.h" #include "storage/bufmgr.h" +#include "storage/encryption.h" +#include "storage/enc_common.h" #include "storage/dsm_impl.h" +#include "storage/kmgr.h" #include "storage/standby.h" #include "storage/fd.h" #include "storage/large_object.h" @@ -457,6 +460,13 @@ const struct config_enum_entry ssl_protocol_versions_info[] = { {NULL, 0, false} }; +const struct config_enum_entry data_encryption_cipher_options[] = { + {"off", TDE_ENCRYPTION_OFF, false}, + {"aes-128", TDE_ENCRYPTION_AES_128, false}, + {"aes-256", TDE_ENCRYPTION_AES_256, false}, + {NULL, 0, false} +}; + static struct config_enum_entry shared_memory_options[] = { #ifndef WIN32 {"sysv", SHMEM_TYPE_SYSV, false}, @@ -704,6 +714,8 @@ const char *const config_group_names[] = gettext_noop("Statistics / Monitoring"), /* STATS_COLLECTOR */ gettext_noop("Statistics / Query and Index Statistics Collector"), + /* ENCRYPTION */ + gettext_noop("Encryption"), /* AUTOVACUUM */ gettext_noop("Autovacuum"), /* CLIENT_CONN */ @@ -4108,7 +4120,7 @@ static struct config_string ConfigureNamesString[] = {"ssl_ciphers", PGC_SIGHUP, CONN_AUTH_SSL, gettext_noop("Sets the list of allowed SSL ciphers."), NULL, - GUC_SUPERUSER_ONLY + GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE }, &SSLCipherSuites, #ifdef USE_OPENSSL @@ -4155,6 +4167,16 @@ static struct config_string ConfigureNamesString[] = NULL, NULL, NULL }, + { + {"cluster_passphrase_command", PGC_POSTMASTER, ENCRYPTION, + gettext_noop("Command to obtain passphrase for database encryption."), + NULL + }, + &cluster_passphrase_command, + "", + NULL, NULL, NULL + }, + { {"application_name", PGC_USERSET, LOGGING_WHAT, gettext_noop("Sets the application name to be reported in statistics and logs."), @@ -4537,6 +4559,19 @@ static struct config_enum ConfigureNamesEnum[] = NULL, NULL, NULL }, + { + {"data_encryption_cipher", PGC_INTERNAL, PRESET_OPTIONS, + gettext_noop("Specify encryption algorithms to use."), + NULL, + GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE, + GUC_SUPERUSER_ONLY + }, + &data_encryption_cipher, + TDE_ENCRYPTION_OFF, + data_encryption_cipher_options, + NULL, assign_data_encryption_cipher, NULL + }, + /* End-of-list marker */ { {NULL, 0, 0, NULL, NULL}, NULL, 0, NULL, NULL, NULL, NULL diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index 0fc23e3a61..0c50b4f8cf 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -600,6 +600,11 @@ # autovacuum, -1 means use # vacuum_cost_limit +#------------------------------------------------------------------------------ +# ENCRYPTION +#------------------------------------------------------------------------------ + +#cluster_passphrase_command = '' #------------------------------------------------------------------------------ # CLIENT CONNECTION DEFAULTS diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c index 88a261d9bd..75462b8010 100644 --- a/src/bin/initdb/initdb.c +++ b/src/bin/initdb/initdb.c @@ -114,6 +114,13 @@ static const char *const auth_methods_local[] = { NULL }; +static const char *const encryption_ciphers[] = { + "none", + "aes-128", + "aes-256", + NULL +}; + /* * these values are passed in by makefile defines */ @@ -145,6 +152,8 @@ 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 *enc_cipher = NULL; +static char *cluster_passphrase = NULL; /* internal vars */ @@ -1208,6 +1217,13 @@ setup_config(void) "password_encryption = scram-sha-256"); } + if (cluster_passphrase) + { + snprintf(repltok, sizeof(repltok), "cluster_passphrase_command = '%s'", + escape_quotes(cluster_passphrase)); + conflines = replace_token(conflines, "#cluster_passphrase_command = ''", repltok); + } + /* * If group access has been enabled for the cluster then it makes sense to * ensure that the log files also allow group access. Otherwise a backup @@ -1421,14 +1437,15 @@ bootstrap_template1(void) unsetenv("PGCLIENTENCODING"); snprintf(cmd, sizeof(cmd), - "\"%s\" --boot -x1 -X %u %s %s %s", + "\"%s\" --boot -x1 -X %u %s %s %s %s %s", backend_exec, wal_segment_size_mb * (1024 * 1024), data_checksums ? "-k" : "", + enc_cipher ? "-e" : "", + enc_cipher ? enc_cipher : "", boot_options, debug ? "-d 5" : ""); - PG_CMD_OPEN; for (line = bki_lines; *line != NULL; line++) @@ -2351,6 +2368,9 @@ usage(const char *progname) printf(_(" --wal-segsize=SIZE size of WAL segments, in megabytes\n")); printf(_("\nLess commonly used options:\n")); printf(_(" -d, --debug generate lots of debugging output\n")); + printf(_(" -e --enc-cipher=MODE set encryption cipher for data encryption\n")); + printf(_(" -c --cluster-passphrase-command=COMMAND\n" + " set command to obtain passphrase for data encryption key\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")); @@ -2416,6 +2436,42 @@ check_need_password(const char *authmethodlocal, const char *authmethodhost) } } +static void +check_encryption_cipher(const char *cipher, const char *passphrase, + const char *const *valid_ciphers) +{ + const char *const *p; + + if (!cipher && !passphrase) + return; + +#ifndef USE_OPENSSL + pg_log_error("cluster encryption is not supported because OpenSSL is not supported by this build"); + exit(1); +#endif + + /* Check both options must be specified at the same time */ + if (cipher && !passphrase) + { + pg_log_error("encryption passphrase command must be specified when encryption cipher is specified"); + exit(1); + } + + if (!cipher && passphrase) + { + pg_log_error("encryption cipher must be specified when encryption passphrase command is specified"); + exit(1); + } + + for (p = valid_ciphers; *p; p++) + { + if (strcmp(cipher, *p) == 0) + return; + } + + pg_log_error("invalid encryption cipher \"%s\"", cipher); + exit(1); +} void setup_pgdata(void) @@ -3029,6 +3085,8 @@ main(int argc, char *argv[]) {"wal-segsize", required_argument, NULL, 12}, {"data-checksums", no_argument, NULL, 'k'}, {"allow-group-access", no_argument, NULL, 'g'}, + {"enc-cipher", required_argument, NULL, 'e'}, + {"cluster-passphrase-command", required_argument, NULL, 'c'}, {NULL, 0, NULL, 0} }; @@ -3070,7 +3128,7 @@ main(int argc, char *argv[]) /* process command-line options */ - while ((c = getopt_long(argc, argv, "dD:E:kL:nNU:WA:sST:X:g", long_options, &option_index)) != -1) + while ((c = getopt_long(argc, argv, "c:dD:E:e:kL:nNU:WA:sST:X:g", long_options, &option_index)) != -1) { switch (c) { @@ -3152,6 +3210,12 @@ main(int argc, char *argv[]) case 9: pwfilename = pg_strdup(optarg); break; + case 'e': + enc_cipher = pg_strdup(optarg); + break; + case 'c': + cluster_passphrase = pg_strdup(optarg); + break; case 's': show_setting = true; break; @@ -3230,6 +3294,8 @@ main(int argc, char *argv[]) check_need_password(authmethodlocal, authmethodhost); + check_encryption_cipher(enc_cipher, cluster_passphrase, encryption_ciphers); + /* set wal segment size */ if (str_wal_segment_size_mb == NULL) wal_segment_size_mb = (DEFAULT_XLOG_SEG_SIZE) / (1024 * 1024); @@ -3289,6 +3355,11 @@ main(int argc, char *argv[]) else printf(_("Data page checksums are disabled.\n")); + if (enc_cipher) + printf(_("Data encryption using %s is enabled.\n"), enc_cipher); + else + printf(_("Data encryption is disabled.\n")); + if (pwprompt || pwfilename) get_su_pwd(); diff --git a/src/bin/pg_checksums/pg_checksums.c b/src/bin/pg_checksums/pg_checksums.c index 971ae73f54..792a9e7565 100644 --- a/src/bin/pg_checksums/pg_checksums.c +++ b/src/bin/pg_checksums/pg_checksums.c @@ -100,6 +100,7 @@ static const char *const skip[] = { "pg_control", "pg_filenode.map", "pg_internal.init", + "pg_kmgr", "PG_VERSION", #ifdef EXEC_BACKEND "config_exec_params", diff --git a/src/bin/pg_controldata/pg_controldata.c b/src/bin/pg_controldata/pg_controldata.c index b14767f8b6..a2f595d097 100644 --- a/src/bin/pg_controldata/pg_controldata.c +++ b/src/bin/pg_controldata/pg_controldata.c @@ -26,6 +26,8 @@ #include "catalog/pg_control.h" #include "common/controldata_utils.h" #include "common/logging.h" +#include "storage/encryption.h" +#include "pg_getopt.h" #include "getopt_long.h" #include "pg_getopt.h" @@ -83,6 +85,21 @@ wal_level_str(WalLevel wal_level) return _("unrecognized wal_level"); } +static const char * +encryption_cipher_str(int val) +{ + switch (val) + { + case TDE_ENCRYPTION_OFF: + return "off"; + case TDE_ENCRYPTION_AES_128: + return "aes-128"; + case TDE_ENCRYPTION_AES_256: + return "aes-256"; + } + + return _("unrecognized encryption cipher"); +} int main(int argc, char *argv[]) @@ -335,5 +352,7 @@ main(int argc, char *argv[]) ControlFile->data_checksum_version); printf(_("Mock authentication nonce: %s\n"), mock_auth_nonce_str); + printf(_("Data encryption cipher: %s\n"), + encryption_cipher_str(ControlFile->data_encryption_cipher)); return 0; } diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h index d519252aad..b3ad28cd3f 100644 --- a/src/include/access/xlog.h +++ b/src/include/access/xlog.h @@ -19,6 +19,7 @@ #include "lib/stringinfo.h" #include "nodes/pg_list.h" #include "storage/fd.h" +#include "storage/kmgr.h" /* Sync methods */ @@ -292,8 +293,11 @@ extern char *XLogFileNameP(TimeLineID tli, XLogSegNo segno); extern void UpdateControlFile(void); extern uint64 GetSystemIdentifier(void); +extern WrappedEncKeyWithHmac *GetTDERelationEncryptionKey(void); +extern WrappedEncKeyWithHmac *GetTDEWALEncryptionKey(void); extern char *GetMockAuthenticationNonce(void); extern bool DataChecksumsEnabled(void); +extern int GetDataEncryptionCipher(void); extern XLogRecPtr GetFakeLSNForUnloggedRel(void); extern Size XLOGShmemSize(void); extern void XLOGShmemInit(void); diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h index ff98d9e91a..5412b148cb 100644 --- a/src/include/catalog/pg_control.h +++ b/src/include/catalog/pg_control.h @@ -19,6 +19,7 @@ #include "access/xlogdefs.h" #include "pgtime.h" /* for pg_time_t */ #include "port/pg_crc32c.h" +#include "storage/kmgr.h" /* Version identifier for this pg_control format */ @@ -221,6 +222,9 @@ typedef struct ControlFileData /* Are data pages protected by checksums? Zero if no checksum version */ uint32 data_checksum_version; + /* Are data pages and WAL encrypted? Zero if encryption is disabled */ + uint32 data_encryption_cipher; + /* * Random nonce, used in authentication requests that need to proceed * based on values that are cluster-unique, like a SASL exchange that @@ -228,6 +232,12 @@ typedef struct ControlFileData */ char mock_authentication_nonce[MOCK_AUTH_NONCE_LEN]; + /* + * Key information for data encryption. + */ + WrappedEncKeyWithHmac tde_rdek; + WrappedEncKeyWithHmac tde_wdek; + /* CRC of all above ... MUST BE LAST! */ pg_crc32c crc; } ControlFileData; diff --git a/src/include/pgstat.h b/src/include/pgstat.h index fe076d823d..fff3c27995 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -895,6 +895,9 @@ typedef enum WAIT_EVENT_DATA_FILE_TRUNCATE, WAIT_EVENT_DATA_FILE_WRITE, WAIT_EVENT_DSM_FILL_ZERO_WRITE, + WAIT_EVENT_KMGR_FILE_READ, + WAIT_EVENT_KMGR_FILE_SYNC, + WAIT_EVENT_KMGR_FILE_WRITE, WAIT_EVENT_LOCK_FILE_ADDTODATADIR_READ, WAIT_EVENT_LOCK_FILE_ADDTODATADIR_SYNC, WAIT_EVENT_LOCK_FILE_ADDTODATADIR_WRITE, diff --git a/src/include/storage/kmgr.h b/src/include/storage/kmgr.h new file mode 100644 index 0000000000..5a484f8546 --- /dev/null +++ b/src/include/storage/kmgr.h @@ -0,0 +1,67 @@ +/*------------------------------------------------------------------------- + * + * kmgr.h + * Key management module for transparent data encryption + * + * Portions Copyright (c) 2019, PostgreSQL Global Development Group + * + * src/include/storage/kmgr.h + * + *------------------------------------------------------------------------- + */ +#ifndef KMGR_H +#define KMGR_H + +#include "storage/relfilenode.h" +#include "storage/bufpage.h" + +/* Size of HMAC key is the same as the length of hash, we use SHA-256 */ +#define TDE_HMAC_KEY_SIZE 32 + +/* SHA-256 results 256 bits HMAC */ +#define TDE_HMAC_SIZE 32 + +/* Size of key encryption key (KEK), which is always AES-256 key */ +#define TDE_KEK_SIZE 32 + +/* + * Max size of data encryption key. We support AES-128 and AES-256, the + * maximum key size is 32. + */ +#define TDE_MAX_DEK_SIZE 32 + +/* Key wrapping appends the initial 8 bytes value */ +#define TDE_DEK_WRAP_VALUE_SIZE 8 + +/* Wrapped key size is n+1 value */ +#define TDE_MAX_WRAPPED_DEK_SIZE (TDE_MAX_DEK_SIZE + TDE_DEK_WRAP_VALUE_SIZE) + +#define TDE_MAX_PASSPHRASE_LEN 1024 + +typedef unsigned char keydata_t; + +/* + * Struct for keys that needs to be verified using its HMAC. + */ +typedef struct WrappedEncKeyWithHmac +{ + keydata_t key[TDE_MAX_WRAPPED_DEK_SIZE]; + keydata_t hmac[TDE_HMAC_SIZE]; +} WrappedEncKeyWithHmac; + +/* Struct for bootstrap information passing to the bootstrap routine */ +typedef struct KmgrBootstrapInfo +{ + WrappedEncKeyWithHmac relEncKey; + WrappedEncKeyWithHmac walEncKey; +} KmgrBootstrapInfo; + +/* GUC variable */ +extern char *cluster_passphrase_command; + +extern KmgrBootstrapInfo *BootStrapKmgr(int bootstrap_data_encryption_cipher); +extern void InitializeKmgr(void); +extern const char *KmgrGetRelationEncryptionKey(void); +extern const char *KmgrGetWALEncryptionKey(void); + +#endif /* KMGR_H */ diff --git a/src/include/utils/guc_tables.h b/src/include/utils/guc_tables.h index d68976fafa..c1ae129f9d 100644 --- a/src/include/utils/guc_tables.h +++ b/src/include/utils/guc_tables.h @@ -89,6 +89,7 @@ enum config_group STATS, STATS_MONITORING, STATS_COLLECTOR, + ENCRYPTION, AUTOVACUUM, CLIENT_CONN, CLIENT_CONN_STATEMENT, -- 2.23.0