From: Antonin Houska Date: Fri, 5 Jul 2019 16:24:01 +0200 Subject: [PATCH 03/17] Teach postmaster to accept encryption key This patch teaches postmaster to accept encryption key (as a special kind of startup packet) before recovery starts. In fact the key is received by a backend that postmaster launches if a client tries to initiate connection while the postmaster is in PM_INIT state. At this stage, only the key is received, whereas regular connection attempt ends up with FATAL: the database system is starting up If postmaster receives no key for some time, it raises this error and exits: FATAL: Encryption key not received. With this patch, user can start postmaster as usual: postgres -D data and use pg_keytool utility in another session to send the key: echo 71916b8b93063c280e4e0ee646395f450bda4f1bac85c57bc3a6afd168f3ab47 | pg_keytool -s --- src/backend/postmaster/postmaster.c | 211 +++++++++++++++++++++++++++++++++- src/backend/storage/file/encryption.c | 34 ++++++ src/backend/storage/ipc/ipci.c | 2 + src/bin/Makefile | 1 + src/bin/pg_keytool/.gitignore | 1 + src/bin/pg_keytool/Makefile | 31 +++++ src/bin/pg_keytool/pg_keytool.c | 199 ++++++++++++++++++++++++++++++++ src/fe_utils/encryption.c | 101 ++++++++++++++++ src/include/fe_utils/encryption.h | 2 + src/include/libpq/pqcomm.h | 24 ++++ src/include/storage/encryption.h | 26 +++++ 11 files changed, 628 insertions(+), 4 deletions(-) create mode 100644 src/bin/pg_keytool/.gitignore create mode 100644 src/bin/pg_keytool/Makefile create mode 100644 src/bin/pg_keytool/pg_keytool.c diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index e8e3f8a4d3..47f7bc93f7 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -117,6 +117,7 @@ #include "postmaster/syslogger.h" #include "replication/logicallauncher.h" #include "replication/walsender.h" +#include "storage/encryption.h" #include "storage/fd.h" #include "storage/ipc.h" #include "storage/pg_shmem.h" @@ -402,12 +403,13 @@ static void PostmasterStateMachine(void); static void BackendInitialize(Port *port); static void BackendRun(Port *port) pg_attribute_noreturn(); static void ExitPostmaster(int status) pg_attribute_noreturn(); -static int ServerLoop(void); +static int ServerLoop(bool receive_encryption_key); static void ServerLoopCheckTimeouts(void); static int BackendStartup(Port *port); static int ProcessStartupPacket(Port *port, bool secure_done); static void SendNegotiateProtocolVersion(List *unrecognized_protocol_options); static void processCancelRequest(Port *port, void *pkt); +static void processEncryptionKey(void *pkt); static int initMasks(fd_set *rmask); static void report_fork_failure_to_client(Port *port, int errnum); static CAC_state canAcceptConnections(void); @@ -1365,6 +1367,41 @@ PostmasterMain(int argc, char *argv[]) AddToDataDirLockFile(LOCK_FILE_LINE_PM_STATUS, PM_STATUS_STARTING); /* + * If encryption key is needed and we don't have it yet, call ServerLoop() + * in a restricted mode which allows a client application to send us the + * key. Regular client connections are not allowed yet. + */ + if (data_encrypted && !encryption_setup_done) + { + char sample[ENCRYPTION_SAMPLE_SIZE]; + + status = ServerLoop(true); + + /* No other return code should be seen here. */ + Assert(status == STATUS_OK); + + /* ServerLoop() shouldn't have exited otherwise. */ + Assert(encryption_key_shmem->initialized); + + /* + * Take a local copy of the key so that we don't have to receive it + * again if restarting after a crash. + */ + memcpy(encryption_key, encryption_key_shmem, ENCRYPTION_KEY_LENGTH); + + /* Finalize the setup. */ + setup_encryption(); + + /* Verify the key. */ + sample_encryption(sample); + if (memcmp(encryption_verification, sample, ENCRYPTION_SAMPLE_SIZE)) + ereport(FATAL, + (errmsg("invalid encryption key"), + errdetail("The passed encryption key does not match" + " database encryption key."))); + } + + /* * We're ready to rock and roll... */ StartupPID = StartupDataBase(); @@ -1375,7 +1412,7 @@ PostmasterMain(int argc, char *argv[]) /* Some workers may be scheduled to start now */ maybe_start_bgworkers(); - status = ServerLoop(); + status = ServerLoop(false); /* * ServerLoop probably shouldn't ever return, but if it does, close down. @@ -1615,18 +1652,37 @@ DetermineSleepTime(struct timeval *timeout) static time_t last_lockfile_recheck_time, last_touch_time; /* + * The maximum amount of time postmaster can wait for encryption key if the + * cluster is encrypted. It should be shorter than the time pg_ctl waits for + * postgres to start (60 seconds) and should not be too long so that failure + * to specify encryption key command is recognized soon. + */ +#define MAX_WAIT_FOR_KEY_SECS 20 + +/* When should waiting for encryption key end. */ +static TimestampTz wait_for_keys_until = 0; + +/* * Main idle loop of postmaster * + * If receive_encryption_key is true, only launch backend(s) to receive + * cluster encryption key and stop as soon as the key is in shared memory. + * * NB: Needs to be called with signals blocked */ static int -ServerLoop(void) +ServerLoop(bool receive_encryption_key) { fd_set readmask; int nSockets; last_lockfile_recheck_time = last_touch_time = time(NULL); + /* Compute wait_for_keys_until if needed and not done yet. */ + if (receive_encryption_key && wait_for_keys_until == 0) + wait_for_keys_until = PgStartTime + + MAX_WAIT_FOR_KEY_SECS * USECS_PER_SEC; + nSockets = initMasks(&readmask); for (;;) @@ -1663,6 +1719,27 @@ ServerLoop(void) /* Needs to run with blocked signals! */ DetermineSleepTime(&timeout); + /* + * If waiting for the encryption key, make sure + * MAX_WAIT_FOR_KEY_SECS is not exceeded. + */ + if (receive_encryption_key) + { + TimestampTz timeout_tz, timeout_end;; + + timeout_tz = timeout.tv_sec * USECS_PER_SEC + + timeout.tv_usec; + timeout_end = GetCurrentTimestamp() + timeout_tz; + + if (timeout_end > wait_for_keys_until) + { + timeout_tz -= (timeout_end - wait_for_keys_until); + + timeout.tv_sec = timeout_tz / USECS_PER_SEC; + timeout.tv_usec = timeout_tz % USECS_PER_SEC; + } + } + PG_SETMASK(&UnBlockSig); selres = select(nSockets, &rmask, NULL, NULL, &timeout); @@ -1714,6 +1791,33 @@ ServerLoop(void) } } + /* + * If only waiting for the encryption key, it's too early to launch + * the other processes. + */ + if (receive_encryption_key) + { + /* The regular checks should take place though. */ + ServerLoopCheckTimeouts(); + + /* Done if the key has been delivered. */ + if (encryption_key_shmem->initialized) + return STATUS_OK; + + /* + * Do not wait for the key forever. One problem we solve here is + * that pg_ctl (or custom script that starts postgres) does not + * have to check whether the cluster is encrypted. If DBA forgets + * to pass the encryption key command, he'll simply see the + * startup to fail and the appropriate message to appear in the + * server log. + */ + if (GetCurrentTimestamp() >= wait_for_keys_until) + ereport(FATAL, (errmsg("Encryption key not received."))); + + continue; + } + /* If we have lost the log collector, try to start a new one */ if (SysLoggerPID == 0 && Logging_collector) SysLoggerPID = SysLogger_Start(); @@ -1990,6 +2094,13 @@ ProcessStartupPacket(Port *port, bool secure_done) return STATUS_ERROR; } + if (proto == ENCRYPTION_KEY_MSG_CODE && data_encrypted) + { + processEncryptionKey(buf); + /* Not really an error, but we don't want to proceed further */ + return STATUS_ERROR; + } + if (proto == NEGOTIATE_SSL_CODE && !secure_done) { char SSLok; @@ -2394,6 +2505,77 @@ processCancelRequest(Port *port, void *pkt) } /* + * Process a message from backend that contains the encryption key. + * + * Currently there's no guarantee that only a single backend tries to deliver + * the key, but it's not worth trying to synchronize the access. If multiple + * callers try to process the same message, nothing should get broken. If some + * caller is delivering a wrong key (e.g. due to misconfiguration of another + * cluster), it does not help if it waits until processing of the correct key + * is finished. + * + * If the key gets messed up, the worst case is that the server fails to start + * up. (No data corruption is expected.) + * + * Client should not expect any response to this request: failure on client + * side indicates either broken connection, wrong key, bug in + * send_key_to_postmaster() or repeated call of the client - nothing to be + * handled easily by client code. Most of these failures need attention of the + * DBA. + */ +void +processEncryptionKey(void *pkt) +{ + EncryptionKeyMsg *msg = (EncryptionKeyMsg *) pkt; + + /* Backend to receive the key should not be launched otherwise. */ + Assert(data_encrypted); + + /* + * The checks below would fire in this case too, but this error message is + * clearer for the case like this. + */ + if (pmState != PM_INIT) + { + ereport(COMMERROR, + (errmsg("encryption key can only be setup during startup"), + errhint("Check if the cluster is encrypted."))); + return; + } + + /* + * Someone else already initialized the key. This is not fatal but seems + * to indicate misconfiguration which DBA should be aware of. Check also + * encryption_setup_done because shared memory could have been reset. As + * postmaster has a local copy of the key, the new key should not be used, + * but it'd be inconsistent if we didn't complain in this special case. + */ + if (encryption_key_shmem->initialized || encryption_setup_done) + { + ereport(COMMERROR, + (errmsg("received encryption key more than once"))); + return; + } + + if (msg->version != 1) + { + ereport(COMMERROR, + (errmsg("unexpected version of encryption key message %d", + msg->version))); + return; + } + + memcpy(encryption_key_shmem->data, msg->data, ENCRYPTION_KEY_LENGTH); + /* + * XXX Is memory barrier needed here to make sure that the copying is + * done? + */ + encryption_key_shmem->initialized = true; + + ereport(DEBUG1, (errmsg_internal("encryption key received"))); +} + +/* * canAcceptConnections --- check to see if database state allows connections. */ static CAC_state @@ -2418,7 +2600,8 @@ canAcceptConnections(void) else if (Shutdown > NoShutdown) return CAC_SHUTDOWN; /* shutdown is pending */ else if (!FatalError && - (pmState == PM_STARTUP || + ((data_encrypted && pmState == PM_INIT) || + pmState == PM_STARTUP || pmState == PM_RECOVERY)) return CAC_STARTUP; /* normal startup */ else if (!FatalError && @@ -2761,6 +2944,16 @@ pmdie(SIGNAL_ARGS) pmState = (pmState == PM_RUN) ? PM_WAIT_BACKUP : PM_WAIT_READONLY; } + else if (pmState == PM_INIT && data_encrypted && + !encryption_setup_done) + { + /* + * Only backends to receive encryption key may be active now, + * and these should not be involved in any transactions. + */ + SignalSomeChildren(SIGTERM, BACKEND_TYPE_NORMAL); + pmState = PM_WAIT_BACKENDS; + } /* * Now wait for online backup mode to end and backends to exit. If @@ -2828,6 +3021,16 @@ pmdie(SIGNAL_ARGS) signal_child(WalWriterPID, SIGTERM); pmState = PM_WAIT_BACKENDS; } + else if (pmState == PM_INIT && data_encrypted && + !encryption_setup_done) + { + /* + * Only backends to receive encryption key may be active now, + * and these should not be involved in any transactions. + */ + SignalSomeChildren(SIGTERM, BACKEND_TYPE_NORMAL); + pmState = PM_WAIT_BACKENDS; + } /* * Now wait for backends to exit. If there are none, diff --git a/src/backend/storage/file/encryption.c b/src/backend/storage/file/encryption.c index 629138fae7..1eda334762 100644 --- a/src/backend/storage/file/encryption.c +++ b/src/backend/storage/file/encryption.c @@ -53,6 +53,10 @@ EVP_CIPHER_CTX *ctx_encrypt, unsigned char encryption_key[ENCRYPTION_KEY_LENGTH]; +#ifndef FRONTEND +ShmemEncryptionKey *encryption_key_shmem = NULL; +#endif /* FRONTEND */ + bool data_encrypted = false; char encryption_verification[ENCRYPTION_SAMPLE_SIZE]; @@ -66,6 +70,36 @@ static void evp_error(void); #ifndef FRONTEND /* + * Report space needed for our shared memory area + */ +Size +EncryptionShmemSize(void) +{ + return sizeof(ShmemEncryptionKey); +} + +/* + * Initialize our shared memory area + */ +void +EncryptionShmemInit(void) +{ + bool found; + + encryption_key_shmem = ShmemInitStruct("Cluster Encryption Key", + EncryptionShmemSize(), + &found); + if (!IsUnderPostmaster) + { + Assert(!found); + + encryption_key_shmem->initialized = false; + } + else + Assert(found); +} + +/* * Read encryption key in hexadecimal form from stdin and store it in * encryption_key variable. */ diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c index d7d733530f..771bf30c63 100644 --- a/src/backend/storage/ipc/ipci.c +++ b/src/backend/storage/ipc/ipci.c @@ -147,6 +147,7 @@ CreateSharedMemoryAndSemaphores(int port) size = add_size(size, BTreeShmemSize()); size = add_size(size, SyncScanShmemSize()); size = add_size(size, AsyncShmemSize()); + size = add_size(size, EncryptionShmemSize()); #ifdef EXEC_BACKEND size = add_size(size, ShmemBackendArraySize()); #endif @@ -263,6 +264,7 @@ CreateSharedMemoryAndSemaphores(int port) BTreeShmemInit(); SyncScanShmemInit(); AsyncShmemInit(); + EncryptionShmemInit(); #ifdef EXEC_BACKEND diff --git a/src/bin/Makefile b/src/bin/Makefile index 903e58121f..b05a8bbb4e 100644 --- a/src/bin/Makefile +++ b/src/bin/Makefile @@ -22,6 +22,7 @@ SUBDIRS = \ pg_controldata \ pg_ctl \ pg_dump \ + pg_keytool \ pg_resetwal \ pg_rewind \ pg_test_fsync \ diff --git a/src/bin/pg_keytool/.gitignore b/src/bin/pg_keytool/.gitignore new file mode 100644 index 0000000000..249876a6ed --- /dev/null +++ b/src/bin/pg_keytool/.gitignore @@ -0,0 +1 @@ +/pg_keytool diff --git a/src/bin/pg_keytool/Makefile b/src/bin/pg_keytool/Makefile new file mode 100644 index 0000000000..6da0631f32 --- /dev/null +++ b/src/bin/pg_keytool/Makefile @@ -0,0 +1,31 @@ +# src/bin/pg_keysetup/Makefile + +PGFILEDESC = "pg_keytool - handle cluster encryption key" +PGAPPICON=win32 + +subdir = src/bin/pg_keytool +top_builddir = ../../.. +include $(top_builddir)/src/Makefile.global + +OBJS = pg_keytool.o $(RMGRDESCOBJS) $(WIN32RES) + +override CPPFLAGS := -I$(libpq_srcdir) $(CPPFLAGS) +override CPPFLAGS := -DFRONTEND $(CPPFLAGS) +LDFLAGS_INTERNAL += -L$(top_builddir)/src/fe_utils -lpgfeutils $(libpq_pgport) + +all: pg_keytool + +pg_keytool: $(OBJS) | submake-libpgport + $(CC) $(CFLAGS) $^ $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X) + +install: all installdirs + $(INSTALL_PROGRAM) pg_keytool$(X) '$(DESTDIR)$(bindir)/pg_keytool$(X)' + +installdirs: + $(MKDIR_P) '$(DESTDIR)$(bindir)' + +uninstall: + rm -f '$(DESTDIR)$(bindir)/pg_keytool$(X)' + +clean distclean maintainer-clean: + rm -f pg_keytool$(X) $(OBJS) encryption.c diff --git a/src/bin/pg_keytool/pg_keytool.c b/src/bin/pg_keytool/pg_keytool.c new file mode 100644 index 0000000000..322625da41 --- /dev/null +++ b/src/bin/pg_keytool/pg_keytool.c @@ -0,0 +1,199 @@ +/*------------------------------------------------------------------------- + * + * pg_keytool.c - Handle cluster encryption key. + * + * Copyright (c) 2013-2019, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/bin/pg_keytool/pg_keytool.c + *------------------------------------------------------------------------- + */ +/* + * TODO Adopt the new frontend logging API, after some things are clarified: + * https://www.postgresql.org/message-id/1939.1560773970%40localhost + */ +#define FRONTEND 1 +#include "postgres.h" + +#include +#include + +#include "common/fe_memutils.h" +#include "common/logging.h" +#include "fe_utils/encryption.h" +#include "libpq-fe.h" +#include "libpq-int.h" +#include "libpq/pqcomm.h" +#include "port/pg_crc32c.h" +#include "storage/encryption.h" +#include "getopt_long.h" + +#ifdef USE_ENCRYPTION +static const char *progname; + +unsigned char encryption_key[ENCRYPTION_KEY_LENGTH]; + +static void +usage(const char *progname) +{ + const char *env; + + printf(_("%s is a tool to handle cluster encryption key.\n\n"), + progname); + printf(_("Usage:\n")); + printf(_(" %s [OPTION]...\n"), progname); + printf(_("\nOptions:\n")); + /* Display default host */ + env = getenv("PGHOST"); + printf(_(" -h, --host=HOSTNAME database server host or socket directory (default: \"%s\")\n"), + env ? env : _("local socket")); + /* Display default port */ + env = getenv("PGPORT"); + printf(_(" -p, --port=PORT database server port (default: \"%s\")\n"), + env ? env : DEF_PGPORT_STR); + printf(_(" -s, send output to database server\n")); + printf(_(" -?, --help show this help, then exit\n\n")); + printf(_("Key is read from stdin and sent either to stdout or to PostgreSQL server being started\n")); +} +#endif /* USE_ENCRYPTION */ + +int +main(int argc, char **argv) +{ +/* + * If no encryption library is linked, let the utility fail immediately. It'd + * be weird if we reported incorrect usage just to say later that no useful + * work can be done anyway. + */ +#ifdef USE_ENCRYPTION + int c; + char *host = NULL; + char *port_str = NULL; + bool to_server = false; + int i, n; + int optindex; + char key_chars[ENCRYPTION_KEY_CHARS]; + + static struct option long_options[] = + { + {"pgdata", required_argument, NULL, 'D'}, + {"host", required_argument, NULL, 'h'}, + {"port", required_argument, NULL, 'p'}, + {NULL, 0, NULL, 0} + }; + + pg_logging_init(argv[0]); + 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_keytool (PostgreSQL) " PG_VERSION); + exit(0); + } + } + + while ((c = getopt_long(argc, argv, "h:p:s", + long_options, &optindex)) != -1) + { + switch (c) + { + case 'h': + host = pg_strdup(optarg); + break; + + case 'p': + port_str = pg_strdup(optarg); + break; + + case 's': + to_server = true; + break; + + case '?': + /* Actual help option given */ + if (strcmp(argv[optind - 1], "-?") == 0) + { + usage(progname); + exit(EXIT_SUCCESS); + } + + default: + pg_log_error("Try \"%s --help\" for more information.", progname); + exit(1); + } + } + + /* Complain if any arguments remain */ + if (optind < argc) + { + fprintf(stderr, _("%s: too many command-line arguments (first is \"%s\")\n"), + progname, argv[optind]); + fprintf(stderr, _("Try \"%s --help\" for more information.\n"), + progname); + exit(1); + } + + if ((host || port_str) && !to_server) + { + pg_log_error("host and port can only be passed along with the -s option"); + exit(1); + } + + /* Read the key. */ + n = 0; + /* Key length in characters (two characters per hexadecimal digit) */ + while ((c = getchar()) != EOF && c != '\n') + { + if (n >= ENCRYPTION_KEY_CHARS) + { + pg_log_error("The key is too long"); + exit(EXIT_FAILURE); + } + + key_chars[n++] = c; + } + + if (n < ENCRYPTION_KEY_CHARS) + { + pg_log_error("The key is too short"); + exit(EXIT_FAILURE); + } + + for (i = 0; i < ENCRYPTION_KEY_LENGTH; i++) + { + if (sscanf(key_chars + 2 * i, "%2hhx", encryption_key + i) == 0) + { + pg_log_error("Invalid character in encryption key at position %d", + 2 * i); + exit(EXIT_FAILURE); + } + } + + /* + * Send the encryption key either to stdout or to server. + */ + if (!to_server) + { + for (i = 0; i < ENCRYPTION_KEY_LENGTH; i++) + printf("%.2x", encryption_key[i]); + printf("\n"); + } + else + { + if (!send_key_to_postmaster(host, port_str, encryption_key)) + pg_log_error("could not send encryption key to server"); + } + +#else + pg_log_fatal(ENCRYPTION_NOT_SUPPORTED_MSG); + exit(EXIT_FAILURE); +#endif /* USE_ENCRYPTION */ + return 0; +} diff --git a/src/fe_utils/encryption.c b/src/fe_utils/encryption.c index dab1435014..ca37c9f373 100644 --- a/src/fe_utils/encryption.c +++ b/src/fe_utils/encryption.c @@ -88,3 +88,104 @@ run_encryption_key_command(unsigned char *encryption_key) pfree(buf); pclose(fp); } + +/* + * Send the contents of encryption_key in the form of special startup packet + * to a server that is being started. + * + * Returns true if we could send the message and false if not, however even + * success does not guarantee that server started up - caller should + * eventually test server connection himself. + * + * TODO Windows stuff? + */ +bool +send_key_to_postmaster(const char *host, const char *port, + const unsigned char *encryption_Key) +{ + const char **keywords = pg_malloc0(3 * sizeof(*keywords)); + const char **values = pg_malloc0(3 * sizeof(*values)); + int i; + PGconn *conn = NULL; + int sock = -1; + EncryptionKeyMsg message; + int msg_size, packet_size; + char *packet = NULL; + bool res = true; + +/* How many seconds we can wait for the postmaster to receive the key. */ +#define SEND_ENCRYPT_KEY_TIMEOUT 60 + + if (host) + { + keywords[0] = "host"; + values[0] = host; + } + keywords[1] = "port"; + values[1] = port; + + /* Compose the message. */ + message.encryptionKeyCode = pg_hton32(ENCRYPTION_KEY_MSG_CODE); + message.version = 1; + memcpy(message.data, encryption_key, ENCRYPTION_KEY_LENGTH); + msg_size = offsetof(EncryptionKeyMsg, data) + ENCRYPTION_KEY_LENGTH; + + packet_size = msg_size + 4; + packet = (char *) palloc(packet_size); + *((int32 *) packet) = pg_hton32(packet_size); + memcpy(packet + 4, &message, msg_size); + + /* + * Although we don't expect the server to accept regular libpq messages, + * we try to get at least a valid socket. + */ + for (i = 0; i < SEND_ENCRYPT_KEY_TIMEOUT + 1; i++) + { + if (i > 0) + /* Sleep for 1 second. */ + pg_usleep(1000000L); + + if (conn) + { + PQfinish(conn); + conn = NULL; + } + + conn = PQconnectStartParams(keywords, values, false); + if (conn == NULL) + continue; + + sock = PQsocket(conn); + /* Cannot send the key if there's no valid socket. */ + if (sock == -1) + continue; + + /* Non-blocking write would only make this simple case tricky. */ + if (!pg_set_block(sock)) + continue; + + retry: + /* + * Send the packet. Here we need to use low level API because the server + * does is not fully up so libpq cannot be used properly. + */ + if (send(sock, (char *) packet, packet_size, 0) != packet_size) + { + if (SOCK_ERRNO == EINTR) + /* Interrupted system call - we'll just try again */ + goto retry; + continue; + } + else + /* Success */ + break; + } + + pg_free(keywords); + pg_free(values); + if (conn) + PQfinish(conn); + pfree(packet); + + return res; +} diff --git a/src/include/fe_utils/encryption.h b/src/include/fe_utils/encryption.h index 69e3b2f756..7307557ed6 100644 --- a/src/include/fe_utils/encryption.h +++ b/src/include/fe_utils/encryption.h @@ -16,3 +16,5 @@ extern char *encryption_key_command; extern void run_encryption_key_command(unsigned char *encryption_key); +extern bool send_key_to_postmaster(const char *host, const char *port, + const unsigned char *encryption_Key); diff --git a/src/include/libpq/pqcomm.h b/src/include/libpq/pqcomm.h index baf6a4b6c0..09822801c7 100644 --- a/src/include/libpq/pqcomm.h +++ b/src/include/libpq/pqcomm.h @@ -23,6 +23,8 @@ #endif #include +#include "storage/encryption.h" + #ifdef HAVE_STRUCT_SOCKADDR_STORAGE #ifndef HAVE_STRUCT_SOCKADDR_STORAGE_SS_FAMILY @@ -205,4 +207,26 @@ typedef struct CancelRequestPacket #define NEGOTIATE_SSL_CODE PG_PROTOCOL(1234,5679) #define NEGOTIATE_GSS_CODE PG_PROTOCOL(1234,5680) +/* Client can also send cluster encryption key to the postmaster. */ +#define ENCRYPTION_KEY_MSG_CODE PG_PROTOCOL(1234,5681) + +/* + * Message containing the encryption key, received by postmaster during + * startup. + * + * TODO Consider adding extension identifier and key_length field so that + * postmaster can also receove extension-specific keys. Extension that needs a + * key would call a function from _PG_init() that tells postmaster where to + * store the key and how long the key is. (Should also postgres in standalone + * mode accept keys for extensions?) + */ +typedef struct EncryptionKeyMsg +{ + /* Note this integer field is stored in network byte order! */ + MsgType encryptionKeyCode; /* code to identify a key message */ + + unsigned char version; /* message format version. */ + char data[ENCRYPTION_KEY_LENGTH]; /* the key itself. */ +} EncryptionKeyMsg; + #endif /* PQCOMM_H */ diff --git a/src/include/storage/encryption.h b/src/include/storage/encryption.h index 8479b76a34..4f7b96d4f3 100644 --- a/src/include/storage/encryption.h +++ b/src/include/storage/encryption.h @@ -60,6 +60,29 @@ typedef enum CipherKind /* Key to encrypt / decrypt data. */ extern unsigned char encryption_key[]; +#ifndef FRONTEND +/* + * Space for the encryption key in shared memory. Backend that receives the + * key during startup stores it here so postmaster can eventually take a local + * copy. + * + * We cannot protect the "initialized" field with a spinlock because the data + * will be accessed by postmaster, but it should not be necessary for bool + * type. Furthermore, synchronization is not really critical here, see + * processEncryptionKey(). + */ +typedef struct ShmemEncryptionKey +{ + char data[ENCRYPTION_KEY_LENGTH]; /* the key */ + bool initialized; /* received the key? */ +} ShmemEncryptionKey; + +/* + * Encryption key in the shared memory. + */ +extern ShmemEncryptionKey *encryption_key_shmem; +#endif /* FRONTEND */ + /* * The encrypted data is a series of blocks of size * ENCRYPTION_BLOCK. Currently we use the EVP_aes_256_xts implementation. Make @@ -86,6 +109,9 @@ extern char encryption_verification[]; extern bool encryption_setup_done; #ifndef FRONTEND +extern Size EncryptionShmemSize(void); +extern void EncryptionShmemInit(void); + typedef int (*read_encryption_key_cb) (void); extern void read_encryption_key(read_encryption_key_cb read_char); #endif /* FRONTEND */ -- 2.13.7 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v04-0004-Minor-refactoring-of-pg_ctl.c.patch