blob: 0b6fce1304e4afcf48d5183b863e81eaa7c195b1 [file]
/*-------------------------------------------------------------------------
*
* kmgr.c
* Cluster file encryption routines
*
* Cluster file encryption is enabled if user requests it during initdb.
* During bootstrap, we generate data encryption keys, wrap them with the
* cluster-level key, and store them into each file located at KMGR_DIR.
* During startup, we decrypt all internal keys and load them to the shared
* memory. Internal keys in the shared memory are read-only. The wrapping
* and unwrapping key routines require the OpenSSL library.
*
* Copyright (c) 2021, PostgreSQL Global Development Group
*
* IDENTIFICATION
* src/backend/crypto/kmgr.c
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include <sys/stat.h>
#include <unistd.h>
#include "funcapi.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "access/xlog.h"
#include "common/file_perm.h"
#include "common/kmgr_utils.h"
#include "common/sha2.h"
#include "access/xlog.h"
#include "common/controldata_utils.h"
#include "crypto/kmgr.h"
#include "postmaster/postmaster.h"
#include "storage/copydir.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/shmem.h"
#include "utils/builtins.h"
#include "utils/memutils.h"
/* Struct stores file encryption keys in plaintext format */
typedef struct KmgrShmemData
{
CryptoKey intlKeys[KMGR_NUM_DATA_KEYS];
} KmgrShmemData;
static KmgrShmemData *KmgrShmem = NULL;
/* GUC variables */
char *cluster_key_command = NULL;
bool tde_force_switch = true;
CryptoKey bootstrap_keys[KMGR_NUM_DATA_KEYS];
extern char *bootstrap_old_key_datadir;
extern int bootstrap_file_encryption_method;
static void bzeroKmgrKeys(int status, Datum arg);
static void KmgrWriteCryptoKeys(const char *dir, unsigned char **keys, int *key_lens);
static CryptoKey *generate_crypto_key(int len);
static void InitializeFileEncryptionStatus()
{
FileEncryptionEnabled = tde_force_switch && (GetFileEncryptionMethod() != DISABLED_ENCRYPTION_METHOD);
}
/*
* This function must be called ONCE during initdb. It creates the DEK
* files wrapped with the KEK supplied by kmgr_run_cluster_key_command().
* There is also an option for the keys to be copied from another cluster.
*/
void
BootStrapKmgr(void)
{
char live_path[MAXPGPATH];
unsigned char *keys_wrap[KMGR_NUM_DATA_KEYS];
int key_lens[KMGR_NUM_DATA_KEYS];
char cluster_key_hex[ALLOC_KMGR_CLUSTER_KEY_LEN];
int cluster_key_hex_len;
unsigned char cluster_key[KMGR_CLUSTER_KEY_LEN];
InitializeFileEncryptionStatus();
if (!FileEncryptionEnabled)
return;
#ifndef USE_OPENSSL
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."))));
#endif
snprintf(live_path, sizeof(live_path), "%s/%s", DataDir, LIVE_KMGR_DIR);
/*
* Copy cluster file encryption keys from an old cluster? This is useful
* for pg_upgrade upgrades where the copied database files are already
* encrypted using the old cluster's DEK keys.
*/
if (bootstrap_old_key_datadir != NULL)
{
char old_key_dir[MAXPGPATH];
snprintf(old_key_dir, sizeof(old_key_dir), "%s/%s",
bootstrap_old_key_datadir, LIVE_KMGR_DIR);
copydir(old_key_dir, LIVE_KMGR_DIR, true);
}
/* create an empty directory */
else
{
if (mkdir(LIVE_KMGR_DIR, pg_dir_create_mode) < 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create cluster file encryption directory \"%s\": %m",
LIVE_KMGR_DIR)));
}
/*
* Get key encryption key (KEK) from the cluster_key command. The cluster
* key command might need to check for the existence of files in the live
* directory, e.g., PIV, so run this _after_ copying the directory in
* place.
*/
cluster_key_hex_len = kmgr_run_cluster_key_command(cluster_key_command,
cluster_key_hex,
ALLOC_KMGR_CLUSTER_KEY_LEN,
live_path, terminal_fd);
/* decode supplied hex */
if (hex_decode(cluster_key_hex, cluster_key_hex_len,
(char *) cluster_key) !=
KMGR_CLUSTER_KEY_LEN)
ereport(ERROR,
(errmsg("cluster key must be %d hexadecimal characters",
KMGR_CLUSTER_KEY_LEN * 2)));
/* We are not in copy mode? Generate new cluster file encryption keys. */
if (bootstrap_old_key_datadir == NULL)
{
unsigned char *bootstrap_keys_wrap[KMGR_NUM_DATA_KEYS];
int key_lens[KMGR_NUM_DATA_KEYS];
PgCipherCtx *cluster_key_ctx;
/* Create KEK encryption context */
cluster_key_ctx = pg_cipher_ctx_create(PG_CIPHER_AES_KWP, cluster_key,
KMGR_CLUSTER_KEY_LEN, true);
if (!cluster_key_ctx)
elog(ERROR, "could not initialize encryption context");
/* Wrap data encryption keys (DEK) using the key encryption key (KEK) */
for (int id = 0; id < KMGR_NUM_DATA_KEYS; id++)
{
CryptoKey *key;
int block_size = 0;
/* generate a DEK */
key = generate_crypto_key(
encryption_methods[bootstrap_file_encryption_method].bit_length / 8);
/* output generated random string as hex, for testing */
{
char str[MAXPGPATH];
int out_len;
out_len = hex_encode((char *) (key->key), key->klen,
str);
str[out_len] = '\0';
}
block_size = pg_cipher_blocksize(cluster_key_ctx);
elog(LOG, "block_size:%d", block_size);
bootstrap_keys_wrap[id] = palloc0(KMGR_MAX_KEY_LEN_BYTES +
block_size);
/* wrap DEK with KEK */
if (!kmgr_wrap_data_key(cluster_key_ctx, key, bootstrap_keys_wrap[id], &(key_lens[id])))
{
pg_cipher_ctx_free(cluster_key_ctx);
elog(ERROR, "failed to wrap data encryption key");
}
/* remove DEK from memory */
explicit_bzero(key, sizeof(CryptoKey));
}
/* Write data encryption keys to the disk */
KmgrWriteCryptoKeys(LIVE_KMGR_DIR, bootstrap_keys_wrap, key_lens);
pg_cipher_ctx_free(cluster_key_ctx);
}
/*
* We are either decrypting keys we copied from an old cluster, or
* decrypting keys we just wrote above --- either way, we decrypt them
* here and store them in a file-scoped variable for use in later
* encrypting during bootstrap mode.
*/
/* Get the crypto keys from the live directory */
kmgr_read_wrapped_data_keys(LIVE_KMGR_DIR, keys_wrap, key_lens);
if (!kmgr_verify_cluster_key(cluster_key, keys_wrap, key_lens, bootstrap_keys))
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("supplied cluster key does not match expected cluster_key")));
/* bzero DEK on exit */
on_proc_exit(bzeroKmgrKeys, 0);
/* bzero KEK */
explicit_bzero(cluster_key_hex, cluster_key_hex_len);
explicit_bzero(cluster_key, KMGR_CLUSTER_KEY_LEN);
}
/* Report shared-memory space needed by KmgrShmem */
Size
KmgrShmemSize(void)
{
if (!tde_force_switch)
return 0;
return MAXALIGN(sizeof(KmgrShmemData));
}
/* Allocate and initialize key manager memory */
void
KmgrShmemInit(void)
{
bool found;
if (!tde_force_switch)
return;
KmgrShmem = (KmgrShmemData *) ShmemInitStruct("File encryption key manager",
KmgrShmemSize(), &found);
/* bzero DEK on exit */
on_shmem_exit(bzeroKmgrKeys, 0);
}
/*
* Get cluster key and verify it, then get the data encryption keys.
* This function is called by postmaster at startup time.
*/
void
InitializeKmgr(void)
{
unsigned char *keys_wrap[KMGR_NUM_DATA_KEYS];
int key_lens[KMGR_NUM_DATA_KEYS];
char cluster_key_hex[ALLOC_KMGR_CLUSTER_KEY_LEN];
int cluster_key_hex_len;
struct stat buffer;
char live_path[MAXPGPATH];
unsigned char cluster_key[KMGR_CLUSTER_KEY_LEN];
InitializeFileEncryptionStatus();
if (!FileEncryptionEnabled)
return;
#ifndef USE_OPENSSL
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."))));
#endif
elog(DEBUG1, "starting up cluster file encryption manager");
if (stat(KMGR_DIR, &buffer) != 0 || !S_ISDIR(buffer.st_mode))
ereport(ERROR,
(errcode(ERRCODE_INTERNAL_ERROR),
(errmsg("cluster file encryption directory %s is missing", KMGR_DIR))));
if (stat(KMGR_DIR_PID, &buffer) == 0)
ereport(ERROR,
(errcode(ERRCODE_INTERNAL_ERROR),
(errmsg("cluster had a pg_alterckey failure that needs repair or pg_alterckey is running"),
errhint("Run pg_alterckey --repair or wait for it to complete."))));
/*
* We want OLD deleted since it allows access to the data encryption keys
* using the old cluster key. If NEW exists, it means either the new
* directory is partly written, or NEW wasn't renamed to LIVE --- in
* either case, it needs to be repaired. See src/bin/pg_alterckey/README
* for more details.
*/
if (stat(OLD_KMGR_DIR, &buffer) == 0 || stat(NEW_KMGR_DIR, &buffer) == 0)
ereport(ERROR,
(errcode(ERRCODE_INTERNAL_ERROR),
(errmsg("cluster had a pg_alterckey failure that needs repair"),
errhint("Run pg_alterckey --repair."))));
/* If OLD, NEW, and LIVE do not exist, there is a serious problem. */
if (stat(LIVE_KMGR_DIR, &buffer) != 0 || !S_ISDIR(buffer.st_mode))
ereport(ERROR,
(errcode(ERRCODE_INTERNAL_ERROR),
(errmsg("cluster has no data encryption keys"))));
/* Get the cluster key (KEK) */
snprintf(live_path, sizeof(live_path), "%s/%s", DataDir, LIVE_KMGR_DIR);
cluster_key_hex_len = kmgr_run_cluster_key_command(cluster_key_command,
cluster_key_hex,
ALLOC_KMGR_CLUSTER_KEY_LEN,
live_path, terminal_fd);
/* decode supplied hex */
if (hex_decode(cluster_key_hex, cluster_key_hex_len,
(char *) cluster_key) !=
KMGR_CLUSTER_KEY_LEN)
ereport(ERROR,
(errmsg("cluster key must be %d hexadecimal characters",
KMGR_CLUSTER_KEY_LEN * 2)));
/* Load wrapped DEKs from their files into an array */
kmgr_read_wrapped_data_keys(LIVE_KMGR_DIR, keys_wrap, key_lens);
/*
* Verify cluster key and store the unwrapped data encryption keys in
* shared memory.
*/
if (!kmgr_verify_cluster_key(cluster_key, keys_wrap, key_lens, KmgrShmem->intlKeys))
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("supplied cluster key does not match expected cluster key")));
/* Check that retrieved key lengths match controldata length. */
for (int id = 0; id < KMGR_NUM_DATA_KEYS; id++)
if (KmgrShmem->intlKeys[id].klen * 8 !=
encryption_methods[GetFileEncryptionMethod()].bit_length)
{
char path[MAXPGPATH];
CryptoKeyFilePath(path, DataDir, id);
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("data encryption key %s of length %d does not match controldata key length %d",
path, KmgrShmem->intlKeys[id].klen * 8,
encryption_methods[GetFileEncryptionMethod()].bit_length)));
}
/* bzero KEK */
explicit_bzero(cluster_key_hex, cluster_key_hex_len);
explicit_bzero(cluster_key, KMGR_CLUSTER_KEY_LEN);
}
static void
bzeroKmgrKeys(int status, Datum arg)
{
if (IsBootstrapProcessingMode())
explicit_bzero(bootstrap_keys, sizeof(bootstrap_keys));
else
explicit_bzero(KmgrShmem->intlKeys, sizeof(KmgrShmem->intlKeys));
}
/* return requested DEK */
const CryptoKey *
KmgrGetKey(int id)
{
Assert(id < KMGR_NUM_DATA_KEYS);
return (const CryptoKey *) (IsBootstrapProcessingMode() ?
&(bootstrap_keys[id]) : &(KmgrShmem->intlKeys[id]));
}
bool CheckIsSM4Method(void)
{
if (strcmp("SM4", encryption_methods[GetFileEncryptionMethod()].name) == 0)
return true;
return false;
}
/* Generate a DEK inside a CryptoKey */
static CryptoKey *
generate_crypto_key(int len)
{
CryptoKey *newkey;
Assert(len <= KMGR_MAX_KEY_LEN);
newkey = (CryptoKey *) palloc0(sizeof(CryptoKey));
newkey->klen = len;
if (!pg_strong_random(newkey->key, len))
elog(ERROR, "failed to generate new file encryption key");
return newkey;
}
/*
* Write the DEKs to the disk.
*/
static void
KmgrWriteCryptoKeys(const char *dir, unsigned char **keys, int *key_lens)
{
elog(DEBUG2, "writing data encryption keys wrapped using the cluster key");
for (int i = 0; i < KMGR_NUM_DATA_KEYS; i++)
{
int fd;
char path[MAXPGPATH];
CryptoKeyFilePath(path, dir, i);
if ((fd = BasicOpenFile(path, O_RDWR | O_CREAT | O_EXCL | PG_BINARY)) < 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not open file \"%s\": %m",
path)));
errno = 0;
pgstat_report_wait_start(WAIT_EVENT_KEY_FILE_WRITE);
if (write(fd, keys[i], key_lens[i]) != key_lens[i])
{
/* if write didn't set errno, assume problem is no disk space */
if (errno == 0)
errno = ENOSPC;
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not write file \"%s\": %m",
path)));
}
pgstat_report_wait_end();
pgstat_report_wait_start(WAIT_EVENT_KEY_FILE_SYNC);
if (pg_fsync(fd) != 0)
ereport(PANIC,
(errcode_for_file_access(),
errmsg("could not fsync file \"%s\": %m",
path)));
pgstat_report_wait_end();
if (close(fd) != 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not close file \"%s\": %m",
path)));
}
}