diff --git a/configure b/configure index 702adba839..2daa5bf0c2 100755 --- a/configure +++ b/configure @@ -12113,7 +12113,7 @@ done # defines OPENSSL_VERSION_NUMBER to claim version 2.0.0, even though it # doesn't have these OpenSSL 1.1.0 functions. So check for individual # functions. - for ac_func in OPENSSL_init_ssl BIO_get_data BIO_meth_new ASN1_STRING_get0_data + for ac_func in OPENSSL_init_ssl OPENSSL_init_crypto BIO_get_data BIO_meth_new ASN1_STRING_get0_data do : as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var" diff --git a/configure.in b/configure.in index 8165f70039..79329d9f15 100644 --- a/configure.in +++ b/configure.in @@ -1194,7 +1194,7 @@ if test "$with_openssl" = yes ; then # defines OPENSSL_VERSION_NUMBER to claim version 2.0.0, even though it # doesn't have these OpenSSL 1.1.0 functions. So check for individual # functions. - AC_CHECK_FUNCS([OPENSSL_init_ssl BIO_get_data BIO_meth_new ASN1_STRING_get0_data]) + AC_CHECK_FUNCS([OPENSSL_init_ssl OPENSSL_init_crypto BIO_get_data BIO_meth_new ASN1_STRING_get0_data]) # OpenSSL versions before 1.1.0 required setting callback functions, for # thread-safety. In 1.1.0, it's no longer required, and CRYPTO_lock() # function was removed. diff --git a/src/backend/Makefile b/src/backend/Makefile index 9706a95848..4ace302038 100644 --- a/src/backend/Makefile +++ b/src/backend/Makefile @@ -21,7 +21,7 @@ SUBDIRS = access bootstrap catalog parser commands executor foreign lib libpq \ main nodes optimizer partitioning port postmaster \ regex replication rewrite \ statistics storage tcop tsearch utils $(top_builddir)/src/timezone \ - jit + jit crypto include $(srcdir)/common.mk diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index 7f4f784c0e..5293137225 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -41,6 +41,7 @@ #include "catalog/pg_database.h" #include "commands/tablespace.h" #include "common/controldata_utils.h" +#include "crypto/kmgr.h" #include "miscadmin.h" #include "pg_trace.h" #include "pgstat.h" @@ -77,6 +78,7 @@ #include "utils/timestamp.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" @@ -4779,6 +4781,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", + kmgr_cipher_string(GetDataEncryptionCipher()), + PGC_INTERNAL, PGC_S_OVERRIDE); } /* @@ -4811,6 +4817,13 @@ GetMockAuthenticationNonce(void) return ControlFile->mock_authentication_nonce; } +WrappedEncKeyWithHmac * +GetMasterEncryptionKey(void) +{ + Assert(ControlFile != NULL); + return &(ControlFile->master_dek); +} + /* * Are checksums enabled for data pages? */ @@ -4821,6 +4834,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. * @@ -5087,6 +5107,7 @@ BootStrapXLOG(void) XLogPageHeader page; XLogLongPageHeader longpage; XLogRecord *record; + WrappedEncKeyWithHmac *masterkey; char *recptr; bool use_existent; uint64 sysidentifier; @@ -5164,6 +5185,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. + */ + masterkey = BootStrapKmgr(bootstrap_data_encryption_cipher); + /* Set up the XLOG page header */ page->xlp_magic = XLOG_PAGE_MAGIC; page->xlp_info = XLP_LONG_HEADER; @@ -5239,6 +5266,9 @@ BootStrapXLOG(void) ControlFile->checkPoint = checkPoint.redo; ControlFile->checkPointCopy = checkPoint; ControlFile->unloggedLSN = FirstNormalUnloggedLSN; + if (masterkey) + memcpy(&(ControlFile->master_dek), masterkey, + sizeof(WrappedEncKeyWithHmac)); /* Set important parameter values for use when replaying WAL */ ControlFile->MaxConnections = MaxConnections; @@ -5250,6 +5280,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 bfc629c753..95751a0c4e 100644 --- a/src/backend/bootstrap/bootstrap.c +++ b/src/backend/bootstrap/bootstrap.c @@ -28,6 +28,7 @@ #include "catalog/pg_collation.h" #include "catalog/pg_type.h" #include "common/link-canary.h" +#include "crypto/kmgr.h" #include "libpq/pqsignal.h" #include "miscadmin.h" #include "nodes/makefuncs.h" @@ -52,6 +53,9 @@ uint32 bootstrap_data_checksum_version = 0; /* No checksum */ +/* No encryption */ +uint32 bootstrap_data_encryption_cipher = KMGR_ENCRYPTION_OFF; + #define ALLOC(t, c) \ ((t *) MemoryContextAllocZero(TopMemoryContext, (unsigned)(c) * sizeof(t))) @@ -226,7 +230,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 +253,10 @@ AuxiliaryProcessMain(int argc, char *argv[]) pfree(debugstr); } break; + + case 'e': + bootstrap_data_encryption_cipher = kmgr_cipher_value(optarg); + break; case 'F': SetConfigOption("fsync", "false", PGC_POSTMASTER, PGC_S_ARGV); break; diff --git a/src/backend/crypto/Makefile b/src/backend/crypto/Makefile new file mode 100644 index 0000000000..d7bfa17b39 --- /dev/null +++ b/src/backend/crypto/Makefile @@ -0,0 +1,17 @@ +#------------------------------------------------------------------------- +# +# Makefile-- +# Makefile for cryptographic +# +# IDENTIFICATION +# src/backend/crypto/Makefile +# +#------------------------------------------------------------------------- + +subdir = src/backend/crypto +top_builddir = ../../.. +include $(top_builddir)/src/Makefile.global + +OBJS = kmgr.o + +include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/crypto/kmgr.c b/src/backend/crypto/kmgr.c new file mode 100644 index 0000000000..697c387864 --- /dev/null +++ b/src/backend/crypto/kmgr.c @@ -0,0 +1,272 @@ +/*------------------------------------------------------------------------- + * + * 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 "common/kmgr_utils.h" +#include "crypto/kmgr.h" +#include "storage/fd.h" +#include "storage/shmem.h" +#include "utils/builtins.h" +#include "utils/guc.h" +#include "utils/memutils.h" + +/* + * Key encryption key. This key is derived from the passphrase provided + * by user when startup. This variable is set during verification + * of user given passphrase. After verification, the plain key data + * is set to this variable. + */ +static uint8 keyEncKey[KMGR_KEK_LEN]; + +/* + * Mater encryption key. Similar to key encryption key, this + * store the plain key data. + */ +static uint8 masterEncKey[KMGR_MAX_DEK_LEN]; + +/* GUC variable */ +char *cluster_passphrase_command = NULL; + +int data_encryption_cipher; +int EncryptionKeyLen; + +/* + * This function must be called ONCE on system install. We retrieve the KEK, + * generate the master key. + */ +WrappedEncKeyWithHmac * +BootStrapKmgr(int bootstrap_data_encryption_cipher) +{ + WrappedEncKeyWithHmac *ret_mk; + char passphrase[KMGR_MAX_PASSPHRASE_LEN]; + uint8 hmackey[KMGR_HMAC_KEY_LEN]; + int passlen; + + if (bootstrap_data_encryption_cipher == KMGR_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 + + ret_mk = palloc0(sizeof(WrappedEncKeyWithHmac)); + + /* + * Set data encryption cipher so that subsequent bootstrapping process + * can proceed. + */ + SetConfigOption("data_encryption_cipher", + kmgr_cipher_string(bootstrap_data_encryption_cipher), + PGC_INTERNAL, PGC_S_OVERRIDE); + + /* Get key encryption key from command */ + passlen = kmgr_run_cluster_passphrase_command(cluster_passphrase_command, + passphrase, KMGR_MAX_PASSPHRASE_LEN); + if (passlen < KMGR_MIN_PASSPHRASE_LEN) + ereport(ERROR, + (errmsg("passphrase must be more than %d bytes", + KMGR_MIN_PASSPHRASE_LEN))); + + /* Get key encryption key and HMAC key from passphrase */ + kmgr_derive_keys(passphrase, passlen, keyEncKey, hmackey); + + /* Generate the master encryption key */ + if (!pg_strong_random(masterEncKey, EncryptionKeyLen)) + ereport(ERROR, + (errmsg("failed to generate cluster encryption key"))); + + /* Wrap the master key by KEK */ + kmgr_wrap_key(keyEncKey, masterEncKey, EncryptionKeyLen, ret_mk->key); + + /* Compute HMAC of the master key */ + kmgr_compute_HMAC(hmackey, ret_mk->key, SizeOfWrappedDEK(), + ret_mk->hmac); + + /* return keys and HMACs generated during bootstrap */ + return ret_mk; +} + +/* + * Get encryption key passphrase and verify it, then get the un-wrapped + * master encryption key. This function is called by postmaster at startup time. + */ +void +InitializeKmgr(void) +{ + WrappedEncKeyWithHmac *wrapped_mk; + char passphrase[KMGR_MAX_PASSPHRASE_LEN]; + int passlen; + + if (!DataEncryptionEnabled()) + return; + + /* Get cluster passphrase */ + passlen = kmgr_run_cluster_passphrase_command(cluster_passphrase_command, + passphrase, KMGR_MAX_PASSPHRASE_LEN); + + /* Get two wrapped keys stored in control file */ + wrapped_mk = GetMasterEncryptionKey(); + + /* Verify the correctness of given passphrase */ + if (!kmgr_verify_passphrase(passphrase, passlen, wrapped_mk, + SizeOfWrappedDEK())) + ereport(ERROR, + (errmsg("cluster passphrase does not match expected passphrase"))); + + kmgr_derive_keys(passphrase, passlen, keyEncKey, NULL); + + /* The passphrase is correct, unwrap the master key */ + kmgr_unwrap_key(keyEncKey, wrapped_mk->key, SizeOfWrappedDEK(), + masterEncKey); +} + +/* Return plain cluster encryption key */ +const char * +KmgrGetMasterEncryptionKey(void) +{ + return DataEncryptionEnabled() ? + pstrdup((const char *) masterEncKey) : NULL; +} + +extern +void assign_data_encryption_cipher(int new_encryption_cipher, + void *extra) +{ + switch (new_encryption_cipher) + { + case KMGR_ENCRYPTION_OFF : + EncryptionKeyLen = 0; + break; + case KMGR_ENCRYPTION_AES128: + EncryptionKeyLen = 16; + break; + case KMGR_ENCRYPTION_AES256: + EncryptionKeyLen = 32; + break; + } +} + +/* + * SQL function to rotate the cluster encryption key. This function + * assumes that the cluster_passphrase_command is already reloaded + * to the new value. + */ +Datum +pg_rotate_encryption_key(PG_FUNCTION_ARGS) +{ + WrappedEncKeyWithHmac new_masterkey; + WrappedEncKeyWithHmac *cur_masterkey; + char passphrase[KMGR_MAX_PASSPHRASE_LEN]; + uint8 new_kek[KMGR_KEK_LEN]; + uint8 new_hmackey[KMGR_HMAC_KEY_LEN]; + int passlen; + + passlen = kmgr_run_cluster_passphrase_command(cluster_passphrase_command, + passphrase, + KMGR_MAX_PASSPHRASE_LEN); + if (passlen < KMGR_MIN_PASSPHRASE_LEN) + ereport(ERROR, + (errmsg("passphrase must be more than %d bytes", + KMGR_MIN_PASSPHRASE_LEN))); + + kmgr_derive_keys(passphrase, passlen, new_kek, new_hmackey); + + /* Copy the current master encryption key */ + memcpy(&(new_masterkey.key), masterEncKey, EncryptionKeyLen); + + /* + * Wrap and compute HMAC of the master key by the new key + * encryption key. + */ + kmgr_wrap_key(new_kek, new_masterkey.key, EncryptionKeyLen, + new_masterkey.key); + kmgr_compute_HMAC(new_hmackey, new_masterkey.key, + SizeOfWrappedDEK(), new_masterkey.hmac); + + /* Update control file */ + LWLockAcquire(ControlFileLock, LW_EXCLUSIVE); + cur_masterkey = GetMasterEncryptionKey(); + memcpy(cur_masterkey, &new_masterkey, sizeof(WrappedEncKeyWithHmac)); + UpdateControlFile(); + LWLockRelease(ControlFileLock); + + PG_RETURN_BOOL(true); +} + +Datum +pg_kmgr_wrap(PG_FUNCTION_ARGS) +{ + bytea *data = PG_GETARG_BYTEA_PP(0); + bytea *res; + unsigned datalen; + unsigned reslen; + bool ret; + + datalen = VARSIZE_ANY_EXHDR(data); + + if (datalen % 8 != 0) + ereport(ERROR, + (errmsg("input data must be multiple of 8 bytes"))); + + reslen = VARHDRSZ + datalen + AES256_KEY_WRAP_VALUE_LEN; + res = palloc(reslen); + + ret = kmgr_wrap_key(keyEncKey, (uint8 *) VARDATA_ANY(data), datalen, + (uint8 *) VARDATA(res)); + if (!ret) + ereport(ERROR, + (errmsg("could not wrap the given secret"))); + + SET_VARSIZE(res, reslen); + PG_RETURN_BYTEA_P(res); +} + +Datum +pg_kmgr_unwrap(PG_FUNCTION_ARGS) +{ + bytea *data = PG_GETARG_BYTEA_PP(0); + bytea *res; + unsigned datalen; + unsigned reslen; + bool ret; + + datalen = VARSIZE_ANY_EXHDR(data); + + if (datalen % 8 != 0) + ereport(ERROR, + (errmsg("input data must be multiple of 8 bytes"))); + + reslen = VARHDRSZ + datalen - AES256_KEY_WRAP_VALUE_LEN; + res = palloc(reslen); + + ret = kmgr_unwrap_key(keyEncKey, (uint8 *) VARDATA_ANY(data), datalen, + (uint8 *) VARDATA(res)); + if (!ret) + ereport(ERROR, + (errmsg("could not unwrap the given secret"))); + + SET_VARSIZE(res, reslen); + PG_RETURN_BYTEA_P(res); +} diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c index 51c486bebd..03d832aad0 100644 --- a/src/backend/postmaster/pgstat.c +++ b/src/backend/postmaster/pgstat.c @@ -3949,6 +3949,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 7a92dac525..c67f18132e 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -100,6 +100,7 @@ #include "common/file_perm.h" #include "common/ip.h" #include "common/string.h" +#include "crypto/kmgr.h" #include "lib/ilist.h" #include "libpq/auth.h" #include "libpq/libpq.h" @@ -1335,6 +1336,11 @@ PostmasterMain(int argc, char *argv[]) */ autovac_init(); + /* + * Initialize cluster encryption key manager. + */ + InitializeKmgr(); + /* * Load configuration files for client authentication. */ diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index 0a6f80963b..4ff81743fc 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -42,6 +42,7 @@ #include "catalog/pg_type.h" #include "commands/async.h" #include "commands/prepare.h" +#include "crypto/kmgr.h" #include "executor/spi.h" #include "jit/jit.h" #include "libpq/libpq.h" @@ -3883,6 +3884,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 9f179a9129..7048ac87db 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -43,6 +43,7 @@ #include "commands/vacuum.h" #include "commands/variable.h" #include "common/string.h" +#include "crypto/kmgr.h" #include "funcapi.h" #include "jit/jit.h" #include "libpq/auth.h" @@ -73,6 +74,7 @@ #include "replication/walsender.h" #include "storage/bufmgr.h" #include "storage/dsm_impl.h" +#include "storage/standby.h" #include "storage/fd.h" #include "storage/large_object.h" #include "storage/pg_shmem.h" @@ -465,6 +467,13 @@ const struct config_enum_entry ssl_protocol_versions_info[] = { {NULL, 0, false} }; +const struct config_enum_entry data_encryption_cipher_options[] = { + {"off", KMGR_ENCRYPTION_OFF, false}, + {"aes-128", KMGR_ENCRYPTION_AES128, false}, + {"aes-256", KMGR_ENCRYPTION_AES256, false}, + {NULL, 0, false} +}; + static struct config_enum_entry shared_memory_options[] = { #ifndef WIN32 {"sysv", SHMEM_TYPE_SYSV, false}, @@ -717,6 +726,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 */ @@ -4193,7 +4204,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 @@ -4240,6 +4251,16 @@ static struct config_string ConfigureNamesString[] = NULL, NULL, NULL }, + { + {"cluster_passphrase_command", PGC_SIGHUP, ENCRYPTION, + gettext_noop("Command to obtain passphrase for database encryption."), + NULL + }, + &cluster_passphrase_command, + "", + NULL, NULL, NULL + }, + { {"application_name", PGC_USERSET, LOGGING_WHAT, gettext_noop("Sets the application name to be reported in statistics and logs."), @@ -4633,6 +4654,19 @@ static struct config_enum ConfigureNamesEnum[] = check_ssl_max_protocol_version, 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, + KMGR_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 e1048c0047..0dd5ee946d 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -614,6 +614,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 7f1534aebb..5bed2c76ab 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 */ @@ -1206,6 +1215,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 @@ -1416,14 +1432,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++) @@ -2312,6 +2329,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=TYPE set encryption cipher (AES-128/AES-256) 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")); @@ -2377,6 +2397,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 (strcasecmp(cipher, *p) == 0) + return; + } + + pg_log_error("invalid encryption cipher \"%s\"\nencryption cipher options are AES-128 and AES-256", cipher); + exit(1); +} void setup_pgdata(void) @@ -2984,6 +3040,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} }; @@ -3025,7 +3083,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) { @@ -3107,6 +3165,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; @@ -3185,6 +3249,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); @@ -3244,6 +3310,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 46ee1f1dc3..f84325fc17 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 19e21ab491..14d3c6757b 100644 --- a/src/bin/pg_controldata/pg_controldata.c +++ b/src/bin/pg_controldata/pg_controldata.c @@ -25,6 +25,7 @@ #include "access/xlog_internal.h" #include "catalog/pg_control.h" #include "common/controldata_utils.h" +#include "common/kmgr_utils.h" #include "common/logging.h" #include "getopt_long.h" #include "pg_getopt.h" @@ -83,7 +84,6 @@ wal_level_str(WalLevel wal_level) return _("unrecognized wal_level"); } - int main(int argc, char *argv[]) { @@ -333,5 +333,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"), + kmgr_cipher_string(ControlFile->data_encryption_cipher)); return 0; } diff --git a/src/common/Makefile b/src/common/Makefile index 44ca68fa6c..dc157b51aa 100644 --- a/src/common/Makefile +++ b/src/common/Makefile @@ -48,6 +48,7 @@ LIBS += $(PTHREAD_LIBS) OBJS_COMMON = \ base64.o \ + cipher.o \ config_info.o \ controldata_utils.o \ d2s.o \ @@ -57,6 +58,7 @@ OBJS_COMMON = \ file_perm.o \ ip.o \ keywords.o \ + kmgr_utils.o \ kwlookup.o \ link-canary.o \ md5.o \ @@ -76,6 +78,7 @@ OBJS_COMMON = \ ifeq ($(with_openssl),yes) OBJS_COMMON += \ + cipher_openssl.o \ protocol_openssl.o \ sha2_openssl.o else diff --git a/src/common/cipher.c b/src/common/cipher.c new file mode 100644 index 0000000000..8b8a76af2e --- /dev/null +++ b/src/common/cipher.c @@ -0,0 +1,95 @@ +/*------------------------------------------------------------------------- + * + * cipher.c + * Shared frontend/backend for cryptographic functions + * + * Copyright (c) 2019, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/common/cipher.c + * + *------------------------------------------------------------------------- + */ + +#ifndef FRONTEND +#include "postgres.h" +#else +#include "postgres_fe.h" +#endif + +#include "common/cipher.h" +#include "common/cipher_openssl.h" + +pg_cipher_ctx * +pg_cipher_ctx_create(void) +{ +#ifdef USE_OPENSSL + return ossl_cipher_ctx_create(); +#endif + return NULL; +} + +void +pg_cipher_setup(void) +{ +#ifdef USE_OPENSSL + ossl_cipher_setup(); +#endif +} + +bool +pg_aes256_ctr_wrap_init(pg_cipher_ctx *ctx) +{ +#ifdef USE_OPENSSL + return ossl_aes256_ctr_wrap_init(ctx); +#endif + return false; +} + +bool +pg_aes256_ctr_unwrap_init(pg_cipher_ctx *ctx) +{ +#ifdef USE_OPENSSL + return ossl_aes256_ctr_unwrap_init(ctx); +#endif + return false; +} + +bool +pg_cipher_encrypt(pg_cipher_ctx *ctx, const uint8 *key, + const uint8 *input, int input_size, + const uint8 *iv, uint8 *dest, int *dest_size) + +{ + bool r = true; +#ifdef USE_OPENSSL + r = ossl_cipher_encrypt(ctx, key, input, input_size, iv, + dest, dest_size); +#endif + return r; +} + +bool +pg_cipher_decrypt(pg_cipher_ctx *ctx, const uint8 *key, + const uint8 *input, int input_size, + const uint8 *iv, uint8 *dest, int *dest_size) +{ + bool r = true; +#ifdef USE_OPENSSL + r = ossl_cipher_decrypt(ctx, key, input, input_size, iv, + dest, dest_size); +#endif + return r; +} + +bool +pg_compute_HMAC(const uint8 *key, const uint8 *data, + int data_size, uint8 *result, int *result_size) +{ + bool r = true; +#ifdef USE_OPENSSL + r = ossl_compute_HMAC(key, data, data_size, result, + result_size); +#endif + return r; +} diff --git a/src/common/cipher_openssl.c b/src/common/cipher_openssl.c new file mode 100644 index 0000000000..519653c7d5 --- /dev/null +++ b/src/common/cipher_openssl.c @@ -0,0 +1,99 @@ +/*------------------------------------------------------------------------- + * 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) 2017-2019, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/common/cipher_openssl.c + * + *------------------------------------------------------------------------- + */ +#ifndef FRONTEND +#include "postgres.h" +#else +#include "postgres_fe.h" +#endif + +#include "common/cipher_openssl.h" + +#include +#include +#include +#include + +pg_cipher_ctx * +ossl_cipher_ctx_create(void) +{ + return EVP_CIPHER_CTX_new(); +} + +bool +ossl_cipher_setup(void) +{ +#ifdef HAVE_OPENSSL_INIT_CRYPTO + /* Setup OpenSSL */ + if (!OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CONFIG, NULL)) + return false; + return true; +#endif + return false; +} + +bool +ossl_aes256_ctr_wrap_init(pg_cipher_ctx *ctx) +{ + EVP_CIPHER_CTX_set_flags(ctx, EVP_CIPHER_CTX_FLAG_WRAP_ALLOW); + if (!EVP_EncryptInit_ex(ctx, EVP_aes_256_wrap(), NULL, NULL, NULL)) + return false; + + EVP_CIPHER_CTX_set_key_length(ctx, AES256_KEY_LEN); + return true; +} + +bool +ossl_aes256_ctr_unwrap_init(pg_cipher_ctx *ctx) +{ + EVP_CIPHER_CTX_set_flags(ctx, EVP_CIPHER_CTX_FLAG_WRAP_ALLOW); + if (!EVP_DecryptInit_ex(ctx, EVP_aes_256_wrap(), NULL, NULL, NULL)) + return false; + + EVP_CIPHER_CTX_set_key_length(ctx, AES256_KEY_LEN); + return true; +} + +bool +ossl_cipher_encrypt(pg_cipher_ctx *ctx, const uint8 *key, + const uint8 *input, int input_size, + const uint8 *iv, uint8 *dest, + int *dest_size) +{ + if (!EVP_EncryptInit_ex(ctx, NULL, NULL, key, iv)) + return false; + + return EVP_EncryptUpdate(ctx, dest, dest_size, input, input_size); +} + +bool +ossl_cipher_decrypt(pg_cipher_ctx *ctx, const uint8 *key, + const uint8 *input, int input_size, + const uint8 *iv, uint8 *dest, + int *dest_size) +{ + if (!EVP_DecryptInit_ex(ctx, NULL, NULL, key, iv)) + return false; + + return EVP_DecryptUpdate(ctx, dest, dest_size, input, input_size); +} + +bool +ossl_compute_HMAC(const uint8 *key, const uint8 *data, + int data_size, uint8 *result, + int *result_size) +{ + return HMAC(EVP_sha256(), key, AES256_KEY_LEN, data, + (uint32) data_size, result, (uint32 *) result_size); +} diff --git a/src/common/kmgr_utils.c b/src/common/kmgr_utils.c new file mode 100644 index 0000000000..a4ce4235ed --- /dev/null +++ b/src/common/kmgr_utils.c @@ -0,0 +1,354 @@ +/*------------------------------------------------------------------------- + * + * kmgr_utils.c + * Shared frontend/backend for cryptographic key management + * + * Copyright (c) 2019, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/common/kmgr_utils.c + * + *------------------------------------------------------------------------- + */ + +#ifndef FRONTEND +#include "postgres.h" +#else +#include "postgres_fe.h" +#endif + +#ifdef FRONTEND +#include "common/logging.h" +#endif +#include "common/kmgr_utils.h" +#include "common/sha2.h" +#include "utils/elog.h" +#include "storage/fd.h" + +#define KMGR_PROMPT_MSG "Enter database encryption pass phrase:" + +static pg_cipher_ctx *wrapctx = NULL; +static pg_cipher_ctx *unwrapctx = NULL; +static bool keywrap_initialized = false; + +#ifdef FRONTEND +static FILE *open_pipe_stream(const char *command); +static int close_pipe_stream(FILE *file); +#endif + +static void +initialize_keywrap_ctx(void) +{ + wrapctx = pg_cipher_ctx_create(); + if (wrapctx == NULL) + goto err; + + unwrapctx = pg_cipher_ctx_create(); + if (unwrapctx == NULL) + goto err; + + if (!pg_aes256_ctr_wrap_init(wrapctx)) + goto err; + + if (!pg_aes256_ctr_wrap_init(unwrapctx)) + goto err; + + keywrap_initialized = true; + return; + +err: +#ifdef FRONTEND + pg_log_fatal("could not initialize cipher context"); + exit(EXIT_FAILURE); +#else + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("could not initialize cipher context"))); +#endif +} + +/* + * Hash the given passphrase and extract it into KEK and HMAC + * key. + */ +void +kmgr_derive_keys(char *passphrase, Size passlen, + uint8 kek[KMGR_KEK_LEN], + uint8 hmackey[KMGR_HMAC_KEY_LEN]) +{ + uint8 keys[PG_SHA512_DIGEST_LENGTH]; + pg_sha512_ctx ctx; + + pg_sha512_init(&ctx); + pg_sha512_update(&ctx, (const uint8 *) passphrase, passlen); + pg_sha512_final(&ctx, keys); + + /* + * SHA-512 results 64 bytes. We extract it into two keys for + * each 32 bytes. + */ + if (kek) + memcpy(kek, keys, KMGR_KEK_LEN); + if (hmackey) + memcpy(hmackey, keys + KMGR_KEK_LEN, KMGR_HMAC_KEY_LEN); +} + +/* + * Verify the correctness of the given passphrase. We compute HMACs of the + * wrapped key using the HMAC key retrieved 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. + */ +bool +kmgr_verify_passphrase(char *passphrase, int passlen, + WrappedEncKeyWithHmac *kh, int keylen) +{ + uint8 user_kek[KMGR_KEK_LEN]; + uint8 user_hmackey[KMGR_HMAC_KEY_LEN]; + uint8 result_hmac[KMGR_HMAC_LEN]; + + kmgr_derive_keys(passphrase, passlen, user_kek, user_hmackey); + + /* Verify both HMAC */ + kmgr_compute_HMAC(user_hmackey, kh->key, keylen, result_hmac); + + if (memcmp(result_hmac, kh->hmac, KMGR_HMAC_LEN) != 0) + return false; + + return true; +} + +/* + * Run cluster passphrase command. + * + * prompt will be substituted for %p. + * + * The result will be put in buffer buf, which is of size size. + * The return value is the length of the actual result. + */ +int +kmgr_run_cluster_passphrase_command(char *passphrase_command, char *buf, + int size) +{ + char command[MAXPGPATH]; + char *p; + char *dp; + char *endp; + FILE *fh; + int pclose_rc; + size_t len = 0; + + Assert(size > 0); + buf[0] = '\0'; + + dp = command; + endp = command + MAXPGPATH - 1; + *endp = '\0'; + + for (p = passphrase_command; *p; p++) + { + if (p[0] == '%') + { + switch (p[1]) + { + case 'p': + StrNCpy(dp, KMGR_PROMPT_MSG, strlen(KMGR_PROMPT_MSG)); + dp += strlen(KMGR_PROMPT_MSG); + p++; + break; + case '%': + p++; + if (dp < endp) + *dp++ = *p; + break; + default: + if (dp < endp) + *dp++ = *p; + break; + } + } + else + { + if (dp < endp) + *dp++ = *p; + } + } + *dp = '\0'; + +#ifdef FRONTEND + fh = open_pipe_stream(command); + if (fh == NULL) + { + pg_log_fatal("could not execute command \"%s\": %m", + command); + exit(EXIT_FAILURE); + } +#else + fh = OpenPipeStream(command, "r"); + if (fh == NULL) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not execute command \"%s\": %m", + command))); +#endif + + if ((len = fread(buf, sizeof(char), size, fh)) < size) + { + if (ferror(fh)) + { +#ifdef FRONTEND + pg_log_fatal("could not read from command \"%s\": %m", + command); + exit(EXIT_FAILURE); +#else + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not read from command \"%s\": %m", + command))); +#endif + } + } + +#ifdef FRONTEND + pclose_rc = close_pipe_stream(fh); +#else + pclose_rc = ClosePipeStream(fh); +#endif + + if (pclose_rc == -1) + { +#ifdef FRONTEND + pg_log_fatal("could not close pipe to external command: %m"); + exit(EXIT_FAILURE); +#else + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not close pipe to external command: %m"))); +#endif + } + else if (pclose_rc != 0) + { +#ifdef FRONTEND + pg_log_fatal("command \"%s\" failed", command); + exit(EXIT_FAILURE); +#else + ereport(ERROR, + (errcode_for_file_access(), + errmsg("command \"%s\" failed", + command), + errdetail_internal("%s", wait_result_to_str(pclose_rc)))); +#endif + } + + return len; +} + +bool +kmgr_wrap_key(uint8 *key, const uint8 *in, int insize, uint8 *out) +{ + int outsize; + + if (!keywrap_initialized) + initialize_keywrap_ctx(); + + return pg_cipher_encrypt(wrapctx, key, in , insize, + NULL, out, &outsize); +} + +bool +kmgr_unwrap_key(uint8 *key, const uint8 *in, int insize, uint8 *out) +{ + int outsize; + + if (!keywrap_initialized) + initialize_keywrap_ctx(); + + return pg_cipher_decrypt(unwrapctx, key, in, insize, + NULL, out, &outsize); +} + +bool +kmgr_compute_HMAC(uint8 *key, const uint8 *data, int size, + uint8 *result) +{ + int resultsize; + + return pg_compute_HMAC(key, data, size, result, &resultsize); +} + +/* Convert cipher name string to integer value */ +int +kmgr_cipher_value(const char *name) +{ + if (strcasecmp(name, "aes-128") == 0) + return KMGR_ENCRYPTION_AES128; + + if (strcasecmp(name, "aes-256") == 0) + return KMGR_ENCRYPTION_AES256; + + return KMGR_ENCRYPTION_OFF; +} + +/* Convert integer value to cipher name string */ +char * +kmgr_cipher_string(int value) +{ + switch (value) + { + case KMGR_ENCRYPTION_OFF : + return "off"; + case KMGR_ENCRYPTION_AES128: + return "aes-128"; + case KMGR_ENCRYPTION_AES256: + return "aes-256"; + default: + break; + } + + return "unknown"; +} + +#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 */ diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h index 98b033fc20..c035db2ccd 100644 --- a/src/include/access/xlog.h +++ b/src/include/access/xlog.h @@ -15,6 +15,7 @@ #include "access/xlogdefs.h" #include "access/xloginsert.h" #include "access/xlogreader.h" +#include "crypto/kmgr.h" #include "datatype/timestamp.h" #include "lib/stringinfo.h" #include "nodes/pg_list.h" @@ -291,8 +292,10 @@ extern TimestampTz GetCurrentChunkReplayStartTime(void); extern void UpdateControlFile(void); extern uint64 GetSystemIdentifier(void); +extern WrappedEncKeyWithHmac *GetMasterEncryptionKey(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 de5670e538..a8a1a46a17 100644 --- a/src/include/catalog/pg_control.h +++ b/src/include/catalog/pg_control.h @@ -17,6 +17,7 @@ #include "access/transam.h" #include "access/xlogdefs.h" +#include "crypto/kmgr.h" #include "pgtime.h" /* for pg_time_t */ #include "port/pg_crc32c.h" @@ -219,6 +220,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 @@ -226,6 +230,11 @@ typedef struct ControlFileData */ char mock_authentication_nonce[MOCK_AUTH_NONCE_LEN]; + /* + * Key information for data encryption. + */ + WrappedEncKeyWithHmac master_dek; + /* CRC of all above ... MUST BE LAST! */ pg_crc32c crc; } ControlFileData; diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fcf2a1214c..d4a744af6c 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -10748,4 +10748,17 @@ proname => 'pg_partition_root', prorettype => 'regclass', proargtypes => 'regclass', prosrc => 'pg_partition_root' }, +# function for key managements +{ oid => '8200', descr => 'rotate cluter encryption key', + proname => 'pg_rotate_encryption_key', + provolatile => 'v', prorettype => 'bool', + proargtypes => '', prosrc => 'pg_rotate_encryption_key' }, +{ oid => '8201', descr => 'wrap the given secret', + proname => 'pg_kmgr_wrap', + provolatile => 'v', prorettype => 'bytea', + proargtypes => 'bytea', prosrc => 'pg_kmgr_wrap' }, +{ oid => '8202', descr => 'unwrap the given secret', + proname => 'pg_kmgr_unwrap', + provolatile => 'v', prorettype => 'bytea', + proargtypes => 'bytea', prosrc => 'pg_kmgr_unwrap' }, ] diff --git a/src/include/common/cipher.h b/src/include/common/cipher.h new file mode 100644 index 0000000000..c61cb34b1c --- /dev/null +++ b/src/include/common/cipher.h @@ -0,0 +1,70 @@ +/*------------------------------------------------------------------------- + * + * cipher.h + * Declarations for cryptographic functions + * + * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/common/cipher.h + * + *------------------------------------------------------------------------- + */ +#ifndef CIPHER_H +#define CIPHER_H + +#ifdef USE_OPENSSL +#include +#include +#include +#endif + +/* Key lengths for AES */ +#define AES128_KEY_LEN 16 +#define AES256_KEY_LEN 32 + +/* + * The encrypted data is a series of blocks of size ENCRYPTION_BLOCK. + * Initialization vector(IV) is the same size of cipher block. + */ +#define AES_BLOCK_SIZE 16 +#define AES_IV_SIZE (AES_BLOCK_SIZE) + +/* + * Key wrapping appends the initial 8 bytes value. Therefore + * wrapped key size gets larger than original one. + */ +#define AES256_KEY_WRAP_VALUE_LEN 8 +#define AES256_MAX_WRAPPED_KEY_LEN (AES256_KEY_LEN + AES256_KEY_WRAP_VALUE_LEN) + +/* Size of HMAC key is the same as the length of hash, we use SHA-256 */ +#define SHA256_HMAC_KEY_LEN 32 + +/* SHA-256 results 256 bits HMAC */ +#define SHA256_HMAC_LEN 32 + + +#ifdef USE_OPENSSL +typedef EVP_CIPHER_CTX pg_cipher_ctx; +#else +typedef void pg_cipher_ctx; +#endif + +extern pg_cipher_ctx *pg_cipher_ctx_create(void); +extern void pg_cipher_setup(void); +extern bool pg_aes256_ctr_wrap_init(pg_cipher_ctx *ctx); +extern bool pg_aes256_ctr_unwrap_init(pg_cipher_ctx *ctx); + +extern bool pg_cipher_encrypt(pg_cipher_ctx *ctx, const uint8 *key, + const uint8 *input, int input_size, + const uint8 *iv, uint8 *dest, + int *dest_size); +extern bool pg_cipher_decrypt(pg_cipher_ctx *ctx, const uint8 *key, + const uint8 *input, int input_size, + const uint8 *iv, uint8 *dest, + int *dest_size); +extern bool pg_compute_HMAC(const uint8 *key, const uint8 *data, + int data_size, uint8 *result, + int *result_size); + +#endif /* CIPHER_H */ diff --git a/src/include/common/cipher_openssl.h b/src/include/common/cipher_openssl.h new file mode 100644 index 0000000000..97d8ae766b --- /dev/null +++ b/src/include/common/cipher_openssl.h @@ -0,0 +1,39 @@ +/*------------------------------------------------------------------------- + * + * cipher_openssl.h + * Declarations for helper functions using OpenSSL + * + * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/common/cipher_openssl.h + * + *------------------------------------------------------------------------- + */ +#ifndef CIPHER_OPENSSL_H +#define CIPHER_OPENSSL_H + +#ifndef FRONTEND +#include "postgres.h" +#else +#include "postgres_fe.h" +#endif + +#include "common/cipher.h" + +extern pg_cipher_ctx *ossl_cipher_ctx_create(void); +extern bool ossl_cipher_setup(void); +extern bool ossl_aes256_ctr_wrap_init(pg_cipher_ctx *ctx); +extern bool ossl_aes256_ctr_unwrap_init(pg_cipher_ctx *ctx); +extern bool ossl_cipher_encrypt(pg_cipher_ctx *ctx, const uint8 *key, + const uint8 *input, int input_size, + const uint8 *iv, uint8 *dest, + int *dest_size); +extern bool ossl_cipher_decrypt(pg_cipher_ctx *ctx, const uint8 *key, + const uint8 *input, int input_size, + const uint8 *iv, uint8 *dest, + int *dest_size); +extern bool ossl_compute_HMAC(const uint8 *key, const uint8 *data, + int data_size, uint8 *result, + int *result_size); +#endif diff --git a/src/include/common/kmgr_utils.h b/src/include/common/kmgr_utils.h new file mode 100644 index 0000000000..600f3b1b1b --- /dev/null +++ b/src/include/common/kmgr_utils.h @@ -0,0 +1,64 @@ +/*------------------------------------------------------------------------- + * + * kmgr_utils.h + * Declarations for utility function for cryptographic key management + * + * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/common/kmgr_utils.h + * + *------------------------------------------------------------------------- + */ +#ifndef KMGR_UTILS_H +#define KMGR_UTILS_H + +#include "common/cipher.h" + +/* Key encryption key is always AES-256 key */ +#define KMGR_KEK_LEN AES256_KEY_LEN + +/* Master data encryption key supports AES-128 and AES-256 */ +#define KMGR_MAX_DEK_LEN AES256_KEY_LEN + +/* HMAC and HMAC key */ +#define KMGR_HMAC_KEY_LEN SHA256_HMAC_KEY_LEN +#define KMGR_HMAC_LEN SHA256_HMAC_LEN + +#define KMGR_MAX_PASSPHRASE_LEN 1024 +#define KMGR_MIN_PASSPHRASE_LEN 8 + +/* Value of data_encryption_cipher */ +enum +{ + KMGR_ENCRYPTION_OFF = 0, + KMGR_ENCRYPTION_AES128, + KMGR_ENCRYPTION_AES256 +}; + +/* + * Struct for keys that needs to be verified using its HMAC. + */ +typedef struct WrappedEncKeyWithHmac +{ + uint8 key[AES256_MAX_WRAPPED_KEY_LEN]; + uint8 hmac[KMGR_HMAC_LEN]; +} WrappedEncKeyWithHmac; + +extern void kmgr_derive_keys(char *passphrase, Size passlen, + uint8 kek[KMGR_KEK_LEN], + uint8 hmackey[KMGR_HMAC_KEY_LEN]); +extern bool kmgr_verify_passphrase(char *passphrase, int passlen, + WrappedEncKeyWithHmac *kh, int keylen); +extern bool kmgr_wrap_key(uint8 *key, const uint8 *in, int insize, + uint8 *out); +extern bool kmgr_unwrap_key(uint8 *key, const uint8 *in, int insize, + uint8 *out); +extern bool kmgr_compute_HMAC(uint8 *key, const uint8 *data, int size, + uint8 *result); +extern int kmgr_run_cluster_passphrase_command(char *passphrase_command, + char *buf, int size); +extern int kmgr_cipher_value(const char *name); +extern char * kmgr_cipher_string(int value); + +#endif /* KMGR_UTILS_H */ diff --git a/src/include/crypto/kmgr.h b/src/include/crypto/kmgr.h new file mode 100644 index 0000000000..fc41131f69 --- /dev/null +++ b/src/include/crypto/kmgr.h @@ -0,0 +1,44 @@ +/*------------------------------------------------------------------------- + * + * kmgr.h + * Key management module for transparent data encryption + * + * Portions Copyright (c) 2019, 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" + +#define KMGR_MAX_PASSPHRASE_LEN 1024 +#define KMGR_PROMPT_MSG "Enter database encryption pass phrase:" + +#define DataEncryptionEnabled() \ + (data_encryption_cipher > KMGR_ENCRYPTION_OFF) + +#define SizeOfWrappedDEK() \ + (EncryptionKeyLen + AES256_KEY_WRAP_VALUE_LEN) + +/* GUC parameter */ +extern PGDLLIMPORT int data_encryption_cipher; + +/* Encryption keys (TDEK and WDEK) length */ +extern int EncryptionKeyLen; + +/* GUC variable */ +extern char *cluster_passphrase_command; + +extern WrappedEncKeyWithHmac *BootStrapKmgr(int bootstrap_data_encryption_cipher); +extern void InitializeKmgr(void); +extern const char *KmgrGetMasterEncryptionKey(void); +extern void assign_data_encryption_cipher(int new_encryption_cipher, + void *extra); + +#endif /* KMGR_H */ diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in index 6f485f73cd..bb35bf782c 100644 --- a/src/include/pg_config.h.in +++ b/src/include/pg_config.h.in @@ -415,6 +415,9 @@ /* Define to 1 if you have the `OPENSSL_init_ssl' function. */ #undef HAVE_OPENSSL_INIT_SSL +/* Define to 1 if you have the `OPENSSL_init_crypto' function. */ +#undef HAVE_OPENSSL_INIT_CRYPTO + /* Define to 1 if you have the header file. */ #undef HAVE_OSSP_UUID_H diff --git a/src/include/pgstat.h b/src/include/pgstat.h index aecb6013f0..3d1c5a3bd1 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/utils/guc_tables.h b/src/include/utils/guc_tables.h index 454c2df487..c0c53b1e13 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,