aboutsummaryrefslogtreecommitdiff
path: root/src/common
diff options
context:
space:
mode:
Diffstat (limited to 'src/common')
-rw-r--r--src/common/Makefile3
-rw-r--r--src/common/cipher.c67
-rw-r--r--src/common/cipher_openssl.c268
-rw-r--r--src/common/kmgr_utils.c507
4 files changed, 845 insertions, 0 deletions
diff --git a/src/common/Makefile b/src/common/Makefile
index f6249779395..85d1388a646 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -62,6 +62,7 @@ OBJS_COMMON = \
ip.o \
jsonapi.o \
keywords.o \
+ kmgr_utils.o \
kwlookup.o \
link-canary.o \
md5_common.o \
@@ -82,10 +83,12 @@ OBJS_COMMON = \
ifeq ($(with_openssl),yes)
OBJS_COMMON += \
+ cipher_openssl.o \
protocol_openssl.o \
cryptohash_openssl.o
else
OBJS_COMMON += \
+ cipher.o \
cryptohash.o \
md5.o \
sha2.o
diff --git a/src/common/cipher.c b/src/common/cipher.c
new file mode 100644
index 00000000000..e42d9844492
--- /dev/null
+++ b/src/common/cipher.c
@@ -0,0 +1,67 @@
+/*-------------------------------------------------------------------------
+ *
+ * cipher.c
+ * Shared frontend/backend for cryptographic functions
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/common/cipher.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/cipher.h"
+
+static cipher_failure(void);
+
+PgCipherCtx *
+pg_cipher_ctx_create(int cipher, uint8 *key, int klen, bool enc)
+{
+ cipher_failure();
+}
+
+void
+pg_cipher_ctx_free(PgCipherCtx *ctx)
+{
+ cipher_failure();
+}
+
+bool
+pg_cipher_encrypt(PgCipherCtx *ctx, const unsigned char *plaintext,
+ const int inlen, unsigned char *ciphertext, int *outlen,
+ const unsigned char *iv, const int ivlen,
+ unsigned char *outtag, const int taglen)
+{
+ cipher_failure();
+}
+
+bool
+pg_cipher_decrypt(PgCipherCtx *ctx, const unsigned char *ciphertext,
+ const int inlen, unsigned char *plaintext, int *outlen,
+ const unsigned char *iv, const int ivlen,
+ const unsigned char *intag, const int taglen)
+{
+ cipher_failure();
+}
+
+static
+cipher_failure(void)
+{
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ (errmsg("cluster file encryption is not supported because OpenSSL is not supported by this build"),
+ errhint("Compile with --with-openssl to use this feature."))));
+#else
+ fprintf(stderr, _("cluster file encryption is not supported because OpenSSL is not supported by this build"));
+ exit(1);
+#endif
+}
+
diff --git a/src/common/cipher_openssl.c b/src/common/cipher_openssl.c
new file mode 100644
index 00000000000..e7a1dc7ad9e
--- /dev/null
+++ b/src/common/cipher_openssl.c
@@ -0,0 +1,268 @@
+/*-------------------------------------------------------------------------
+ * cipher_openssl.c
+ * Cryptographic function using OpenSSL
+ *
+ * This contains the common low-level functions needed in both frontend and
+ * backend, for implement the database encryption.
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/common/cipher_openssl.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/cipher.h"
+#include <openssl/conf.h>
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/ssl.h>
+
+/*
+ * prototype for the EVP functions that return an algorithm, e.g.
+ * EVP_aes_128_gcm().
+ */
+typedef const EVP_CIPHER *(*ossl_EVP_cipher_func) (void);
+
+static ossl_EVP_cipher_func get_evp_aes_gcm(int klen);
+static EVP_CIPHER_CTX *ossl_cipher_ctx_create(int cipher, uint8 *key, int klen,
+ bool enc);
+
+/*
+ * Return a newly created cipher context. 'cipher' specifies cipher algorithm
+ * by identifer like PG_CIPHER_XXX.
+ */
+PgCipherCtx *
+pg_cipher_ctx_create(int cipher, uint8 *key, int klen, bool enc)
+{
+ PgCipherCtx *ctx = NULL;
+
+ if (cipher >= PG_MAX_CIPHER_ID)
+ return NULL;
+
+ ctx = ossl_cipher_ctx_create(cipher, key, klen, enc);
+
+ return ctx;
+}
+
+void
+pg_cipher_ctx_free(PgCipherCtx *ctx)
+{
+ EVP_CIPHER_CTX_free(ctx);
+}
+
+/*
+ * Encryption routine to encrypt data provided.
+ *
+ * ctx is the encryption context which must have been created previously.
+ *
+ * plaintext is the data we are going to encrypt
+ * inlen is the length of the data to encrypt
+ *
+ * ciphertext is the encrypted result
+ * outlen is the encrypted length
+ *
+ * iv is the IV to use.
+ * ivlen is the IV length to use.
+ *
+ * outtag is the resulting tag.
+ * taglen is the length of the tag.
+ */
+bool
+pg_cipher_encrypt(PgCipherCtx *ctx,
+ const unsigned char *plaintext, const int inlen,
+ unsigned char *ciphertext, int *outlen,
+ const unsigned char *iv, const int ivlen,
+ unsigned char *outtag, const int taglen)
+{
+ int len;
+ int enclen;
+
+ Assert(ctx != NULL);
+
+ /*
+ * Here we are setting the IV for the context which was passed
+ * in. Note that we signal to OpenSSL that we are configuring
+ * a new value for the context by passing in 'NULL' for the
+ * 2nd ('type') parameter.
+ */
+
+ /* Set the IV length first */
+ if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, ivlen, NULL))
+ return false;
+
+ /* Set the IV for this encryption. */
+ if (!EVP_EncryptInit_ex(ctx, NULL, NULL, NULL, iv))
+ return false;
+
+ /*
+ * This is the function which is actually performing the
+ * encryption for us.
+ */
+ if (!EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, inlen))
+ return false;
+
+ enclen = len;
+
+ /* Finalize the encryption, which could add more to output. */
+ if (!EVP_EncryptFinal_ex(ctx, ciphertext + enclen, &len))
+ return false;
+
+ *outlen = enclen + len;
+
+ /*
+ * Once all of the encryption has been completed we grab
+ * the tag.
+ */
+ if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, taglen, outtag))
+ return false;
+
+ return true;
+}
+/*
+ * Decryption routine
+ *
+ * ctx is the encryption context which must have been created previously.
+ *
+ * ciphertext is the data we are going to decrypt
+ * inlen is the length of the data to decrypt
+ *
+ * plaintext is the decrypted result
+ * outlen is the decrypted length
+ *
+ * iv is the IV to use.
+ * ivlen is the length of the IV.
+ *
+ * intag is the tag to use to verify.
+ * taglen is the length of the tag.
+ */
+bool
+pg_cipher_decrypt(PgCipherCtx *ctx,
+ const unsigned char *ciphertext, const int inlen,
+ unsigned char *plaintext, int *outlen,
+ const unsigned char *iv, const int ivlen,
+ unsigned char *intag, const int taglen)
+{
+ int declen;
+ int len;
+
+ /*
+ * Here we are setting the IV for the context which was passed
+ * in. Note that we signal to OpenSSL that we are configuring
+ * a new value for the context by passing in 'NULL' for the
+ * 2nd ('type') parameter.
+ */
+
+ /* Set the IV length first */
+ if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, ivlen, NULL))
+ return false;
+
+ /* Set the IV for this decryption. */
+ if (!EVP_DecryptInit_ex(ctx, NULL, NULL, NULL, iv))
+ return false;
+
+ /*
+ * This is the function which is actually performing the
+ * decryption for us.
+ */
+ if (!EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, inlen))
+ return false;
+
+ declen = len;
+
+ /* Set the expected tag value. */
+ if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, taglen, intag))
+ return false;
+
+ /*
+ * Finalize the decryption, which could add more to output,
+ * this is also the step which checks the tag and we MUST
+ * fail if this indicates an invalid tag!
+ */
+ if (!EVP_DecryptFinal_ex(ctx, plaintext + declen, &len))
+ return false;
+
+ *outlen = declen + len;
+
+ return true;
+}
+
+/*
+ * Returns the correct cipher functions for OpenSSL based
+ * on the key length requested.
+ */
+static ossl_EVP_cipher_func
+get_evp_aes_gcm(int klen)
+{
+ switch (klen)
+ {
+ case PG_AES128_KEY_LEN:
+ return EVP_aes_128_gcm;
+ case PG_AES192_KEY_LEN:
+ return EVP_aes_192_gcm;
+ case PG_AES256_KEY_LEN:
+ return EVP_aes_256_gcm;
+ default:
+ return NULL;
+ }
+}
+
+/*
+ * Initialize and return an EVP_CIPHER_CTX. Returns NULL if the given
+ * cipher algorithm is not supported or on failure.
+ */
+static EVP_CIPHER_CTX *
+ossl_cipher_ctx_create(int cipher, uint8 *key, int klen, bool enc)
+{
+ EVP_CIPHER_CTX *ctx;
+ ossl_EVP_cipher_func func;
+ int ret;
+
+ ctx = EVP_CIPHER_CTX_new();
+
+ /*
+ * We currently only support AES GCM but others could be
+ * added in the future.
+ */
+ switch (cipher)
+ {
+ case PG_CIPHER_AES_GCM:
+ func = get_evp_aes_gcm(klen);
+ if (!func)
+ goto failed;
+ break;
+ default:
+ goto failed;
+ }
+
+ /*
+ * We create the context here based on the cipher requested and the provided
+ * key. Note that the IV will be provided in the actual encryption call
+ * through another EVP_EncryptInit_ex call- this is fine as long as 'type'
+ * is passed in as NULL!
+ */
+ if (enc)
+ ret = EVP_EncryptInit_ex(ctx, (const EVP_CIPHER *) func(), NULL, key, NULL);
+ else
+ ret = EVP_DecryptInit_ex(ctx, (const EVP_CIPHER *) func(), NULL, key, NULL);
+
+ if (!ret)
+ goto failed;
+
+ /* Set the key length based on the key length requested. */
+ if (!EVP_CIPHER_CTX_set_key_length(ctx, klen))
+ goto failed;
+
+ return ctx;
+
+failed:
+ EVP_CIPHER_CTX_free(ctx);
+ return NULL;
+}
+
diff --git a/src/common/kmgr_utils.c b/src/common/kmgr_utils.c
new file mode 100644
index 00000000000..d031976f500
--- /dev/null
+++ b/src/common/kmgr_utils.c
@@ -0,0 +1,507 @@
+/*-------------------------------------------------------------------------
+ *
+ * kmgr_utils.c
+ * Shared frontend/backend for cluster file encryption
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/common/kmgr_utils.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include <unistd.h>
+#include <sys/stat.h>
+
+#ifdef FRONTEND
+#include "common/logging.h"
+#endif
+#include "common/cryptohash.h"
+#include "common/file_perm.h"
+#include "common/kmgr_utils.h"
+#include "common/hex_decode.h"
+#include "common/string.h"
+#include "crypto/kmgr.h"
+#include "lib/stringinfo.h"
+#include "postmaster/postmaster.h"
+#include "storage/fd.h"
+
+#ifndef FRONTEND
+#include "pgstat.h"
+#include "storage/fd.h"
+#endif
+
+#define KMGR_PROMPT_MSG "Enter authentication needed to generate the cluster key: "
+
+#ifdef FRONTEND
+static FILE *open_pipe_stream(const char *command);
+static int close_pipe_stream(FILE *file);
+#endif
+
+static void read_one_keyfile(const char *dataDir, uint32 id, CryptoKey *key_p);
+
+/*
+ * Encrypt the given data. Return true and set encrypted data to 'out' if
+ * success. Otherwise return false. The caller must allocate sufficient space
+ * for cipher data calculated by using KmgrSizeOfCipherText(). Please note that
+ * this function modifies 'out' data even on failure case.
+ */
+bool
+kmgr_wrap_key(PgCipherCtx *ctx, CryptoKey *in, CryptoKey *out)
+{
+ int len, enclen;
+ unsigned char iv[sizeof(in->pgkey_id) + sizeof(in->counter)];
+
+ Assert(ctx && in && out);
+
+ /* Get the actual length of the key we are wrapping */
+ memcpy(&len, in->encrypted_key, sizeof(len));
+
+ /* Key ID remains the same */
+ out->pgkey_id = in->pgkey_id;
+
+ /* Increment the counter */
+ out->counter = in->counter + 1;
+
+ /* Construct the IV we are going to use, see kmgr_utils.h */
+ memcpy(iv, &out->pgkey_id, sizeof(out->pgkey_id));
+ memcpy(iv + sizeof(out->pgkey_id), &out->counter, sizeof(out->counter));
+
+ if (!pg_cipher_encrypt(ctx,
+ in->encrypted_key, /* Plaintext source, key length + key */
+ sizeof(in->encrypted_key), /* Full data length */
+ out->encrypted_key, /* Ciphertext result */
+ &enclen, /* Resulting length, must match input for us */
+ iv, /* Generated IV from above */
+ sizeof(iv), /* Length of the IV */
+ (unsigned char *) &out->tag, /* Resulting tag */
+ sizeof(out->tag))) /* Length of our tag */
+ return false;
+
+ Assert(enclen == sizeof(in->encrypted_key));
+
+ return true;
+}
+
+/*
+ * Decrypt the given Data. Return true and set plain text data to `out` if
+ * success. Otherwise return false. The caller must allocate sufficient space
+ * for cipher data calculated by using KmgrSizeOfPlainText(). Please note that
+ * this function modifies 'out' data even on failure case.
+ */
+bool
+kmgr_unwrap_key(PgCipherCtx *ctx, CryptoKey *in, CryptoKey *out)
+{
+ int declen;
+ unsigned char iv[sizeof(in->pgkey_id) + sizeof(in->counter)];
+
+ Assert(ctx && in && out);
+
+ out->pgkey_id = in->pgkey_id;
+ out->counter = in->counter;
+ out->tag = in->tag;
+
+ /* Construct the IV we are going to use, see kmgr_utils.h */
+ memcpy(iv, &out->pgkey_id, sizeof(out->pgkey_id));
+ memcpy(iv + sizeof(out->pgkey_id), &out->counter, sizeof(out->counter));
+
+ /* Decrypt encrypted data */
+ if (!pg_cipher_decrypt(ctx,
+ in->encrypted_key, /* Encrypted source */
+ sizeof(in->encrypted_key), /* Length of encrypted data */
+ out->encrypted_key, /* Plaintext result */
+ &declen, /* Length of plaintext */
+ iv, /* IV we constructed above */
+ sizeof(iv), /* Size of our IV */
+ (unsigned char *) &in->tag, /* Tag which will be verified */
+ sizeof(in->tag))) /* Size of our tag */
+ return false;
+
+ Assert(declen == sizeof(in->encrypted_key));
+
+ return true;
+}
+
+/*
+ * Verify the correctness of the given cluster key by unwrapping the given keys.
+ * If the given cluster key is correct we set unwrapped keys to out_keys and return
+ * true. Otherwise return false. Please note that this function changes the
+ * contents of out_keys even on failure. Both in_keys and out_keys must be the
+ * same length, nkey.
+ */
+bool
+kmgr_verify_cluster_key(unsigned char *cluster_key,
+ CryptoKey *in_keys, CryptoKey *out_keys, int nkeys)
+{
+ PgCipherCtx *ctx;
+
+ /*
+ * Create decryption context with cluster KEK.
+ */
+ ctx = pg_cipher_ctx_create(PG_CIPHER_AES_GCM, cluster_key,
+ KMGR_CLUSTER_KEY_LEN, false);
+
+ for (int i = 0; i < nkeys; i++)
+ {
+ if (!kmgr_unwrap_key(ctx, &(in_keys[i]), &(out_keys[i])))
+ {
+ /* The cluster key is not correct */
+ pg_cipher_ctx_free(ctx);
+ return false;
+ }
+ explicit_bzero(&(in_keys[i]), sizeof(in_keys[i]));
+ }
+
+ /* The cluster key is correct, free the cipher context */
+ pg_cipher_ctx_free(ctx);
+
+ return true;
+}
+
+/*
+ * Run cluster key command.
+ *
+ * prompt will be substituted for %p, file descriptor for %R
+ *
+ * The result will be put in buffer buf, which is of size size.
+ * The return value is the length of the actual result.
+ */
+int
+kmgr_run_cluster_key_command(char *cluster_key_command, char *buf,
+ int size, char *dir)
+{
+ StringInfoData command;
+ const char *sp;
+ FILE *fh;
+ int pclose_rc;
+ size_t len = 0;
+
+ buf[0] = '\0';
+
+ Assert(size > 0);
+
+ /*
+ * Build the command to be executed.
+ */
+ initStringInfo(&command);
+
+ for (sp = cluster_key_command; *sp; sp++)
+ {
+ if (*sp == '%')
+ {
+ switch (sp[1])
+ {
+ case 'd':
+ {
+ char *nativePath;
+
+ sp++;
+
+ /*
+ * This needs to use a placeholder to not modify the
+ * input with the conversion done via
+ * make_native_path().
+ */
+ nativePath = pstrdup(dir);
+ make_native_path(nativePath);
+ appendStringInfoString(&command, nativePath);
+ pfree(nativePath);
+ break;
+ }
+ case 'p':
+ sp++;
+ appendStringInfoString(&command, KMGR_PROMPT_MSG);
+ break;
+ case 'R':
+ {
+ char fd_str[20];
+
+ if (terminal_fd == -1)
+ {
+#ifdef FRONTEND
+ pg_log_fatal("cluster key command referenced %%R, but --authprompt not specified");
+ exit(EXIT_FAILURE);
+#else
+ ereport(ERROR,
+ (errcode(ERRCODE_INTERNAL_ERROR),
+ errmsg("cluster key command referenced %%R, but --authprompt not specified")));
+#endif
+ }
+
+ sp++;
+ snprintf(fd_str, sizeof(fd_str), "%d", terminal_fd);
+ appendStringInfoString(&command, fd_str);
+ break;
+ }
+ case '%':
+ /* convert %% to a single % */
+ sp++;
+ appendStringInfoChar(&command, *sp);
+ break;
+ default:
+ /* otherwise treat the % as not special */
+ appendStringInfoChar(&command, *sp);
+ break;
+ }
+ }
+ else
+ {
+ appendStringInfoChar(&command, *sp);
+ }
+ }
+
+#ifdef FRONTEND
+ fh = open_pipe_stream(command.data);
+ if (fh == NULL)
+ {
+ pg_log_fatal("could not execute command \"%s\": %m",
+ command.data);
+ exit(EXIT_FAILURE);
+ }
+#else
+ fh = OpenPipeStream(command.data, "r");
+ if (fh == NULL)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not execute command \"%s\": %m",
+ command.data)));
+#endif
+
+ if (!fgets(buf, size, fh))
+ {
+ if (ferror(fh))
+ {
+#ifdef FRONTEND
+ pg_log_fatal("could not read from command \"%s\": %m",
+ command.data);
+ exit(EXIT_FAILURE);
+#else
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not read from command \"%s\": %m",
+ command.data)));
+#endif
+ }
+ }
+
+#ifdef FRONTEND
+ pclose_rc = close_pipe_stream(fh);
+#else
+ pclose_rc = ClosePipeStream(fh);
+#endif
+
+ if (pclose_rc == -1)
+ {
+#ifdef FRONTEND
+ pg_log_fatal("could not close pipe to external command: %m");
+ exit(EXIT_FAILURE);
+#else
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not close pipe to external command: %m")));
+#endif
+ }
+ else if (pclose_rc != 0)
+ {
+#ifdef FRONTEND
+ pg_log_fatal("command \"%s\" failed", command.data);
+ exit(EXIT_FAILURE);
+#else
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("command \"%s\" failed",
+ command.data),
+ errdetail_internal("%s", wait_result_to_str(pclose_rc))));
+#endif
+ }
+
+ /* strip trailing newline and carriage return */
+ len = pg_strip_crlf(buf);
+
+ pfree(command.data);
+
+ return len;
+}
+
+#ifdef FRONTEND
+static FILE *
+open_pipe_stream(const char *command)
+{
+ FILE *res;
+
+#ifdef WIN32
+ size_t cmdlen = strlen(command);
+ char *buf;
+ int save_errno;
+
+ buf = malloc(cmdlen + 2 + 1);
+ if (buf == NULL)
+ {
+ errno = ENOMEM;
+ return NULL;
+ }
+ buf[0] = '"';
+ mempcy(&buf[1], command, cmdlen);
+ buf[cmdlen + 1] = '"';
+ buf[cmdlen + 2] = '\0';
+
+ res = _popen(buf, "r");
+
+ save_errno = errno;
+ free(buf);
+ errno = save_errno;
+#else
+ res = popen(command, "r");
+#endif /* WIN32 */
+ return res;
+}
+
+static int
+close_pipe_stream(FILE *file)
+{
+#ifdef WIN32
+ return _pclose(file);
+#else
+ return pclose(file);
+#endif /* WIN32 */
+}
+#endif /* FRONTEND */
+
+CryptoKey *
+kmgr_get_cryptokeys(const char *path, int *nkeys)
+{
+ struct dirent *de;
+ DIR *dir;
+ CryptoKey *keys;
+
+#ifndef FRONTEND
+ if ((dir = AllocateDir(path)) == NULL)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not open directory \"%s\": %m",
+ path)));
+#else
+ if ((dir = opendir(path)) == NULL)
+ pg_log_fatal("could not open directory \"%s\": %m", path);
+#endif
+
+ keys = (CryptoKey *) palloc0(sizeof(CryptoKey) * KMGR_MAX_INTERNAL_KEYS);
+ *nkeys = 0;
+
+#ifndef FRONTEND
+ while ((de = ReadDir(dir, LIVE_KMGR_DIR)) != NULL)
+#else
+ while ((de = readdir(dir)) != NULL)
+#endif
+ {
+ if (strspn(de->d_name, "0123456789") == strlen(de->d_name))
+ {
+ uint32 id = strtoul(de->d_name, NULL, 10);
+
+ if (id < 0 || id >= KMGR_MAX_INTERNAL_KEYS)
+ {
+#ifndef FRONTEND
+ elog(ERROR, "invalid cryptographic key identifier %u", id);
+#else
+ pg_log_fatal("invalid cryptographic key identifier %u", id);
+#endif
+ }
+
+ if (*nkeys >= KMGR_MAX_INTERNAL_KEYS)
+ {
+#ifndef FRONTEND
+ elog(ERROR, "too many cryptographic keys");
+#else
+ pg_log_fatal("too many cryptographic keys");
+#endif
+ }
+
+ read_one_keyfile(path, id, &(keys[id]));
+ (*nkeys)++;
+ }
+ }
+
+#ifndef FRONTEND
+ FreeDir(dir);
+#else
+ closedir(dir);
+#endif
+
+ return keys;
+}
+
+static void
+read_one_keyfile(const char *cryptoKeyDir, uint32 id, CryptoKey *key_p)
+{
+ char path[MAXPGPATH];
+ int fd;
+ int r;
+
+ CryptoKeyFilePath(path, cryptoKeyDir, id);
+
+#ifndef FRONTEND
+ if ((fd = OpenTransientFile(path, O_RDONLY | PG_BINARY)) == -1)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not open file \"%s\" for reading: %m",
+ path)));
+#else
+ if ((fd = open(path, O_RDONLY | PG_BINARY, 0)) == -1)
+ pg_log_fatal("could not open file \"%s\" for reading: %m",
+ path);
+#endif
+
+#ifndef FRONTEND
+ pgstat_report_wait_start(WAIT_EVENT_KEY_FILE_READ);
+#endif
+
+ /* Get key bytes */
+ r = read(fd, key_p, sizeof(CryptoKey));
+ if (r != sizeof(CryptoKey))
+ {
+ if (r < 0)
+ {
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not read file \"%s\": %m", path)));
+#else
+ pg_log_fatal("could not read file \"%s\": %m", path);
+#endif
+ }
+ else
+ {
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_DATA_CORRUPTED),
+ errmsg("could not read file \"%s\": read %d of %zu",
+ path, r, sizeof(CryptoKey))));
+#else
+ pg_log_fatal("could not read file \"%s\": read %d of %zu",
+ path, r, sizeof(CryptoKey));
+#endif
+ }
+ }
+
+#ifndef FRONTEND
+ pgstat_report_wait_end();
+#endif
+
+#ifndef FRONTEND
+ if (CloseTransientFile(fd) != 0)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not close file \"%s\": %m",
+ path)));
+#else
+ if (close(fd) != 0)
+ pg_log_fatal("could not close file \"%s\": %m", path);
+#endif
+}