mirror of https://github.com/postgres/postgres
This adds a key management system that stores (currently) two data encryption keys of length 128, 192, or 256 bits. The data keys are AES256 encrypted using a key encryption key, and validated via GCM cipher mode. A command to obtain the key encryption key must be specified at initdb time, and will be run at every database server start. New parameters allow a file descriptor open to the terminal to be passed. pg_upgrade support has also been added. Discussion: https://postgr.es/m/CA+fd4k7q5o6Nc_AaX6BcYM9yqTbC6_pnH-6nSD=54Zp6NBQTCQ@mail.gmail.com Discussion: https://postgr.es/m/20201202213814.GG20285@momjian.us Author: Masahiko Sawada, me, Stephen Frostpull/59/head
parent
5c31afc49d
commit
978f869b99
@ -0,0 +1,97 @@ |
|||||||
|
<!-- doc/src/sgml/database-encryption.sgml --> |
||||||
|
|
||||||
|
<chapter id="database-file-encryption"> |
||||||
|
<title>Cluster File Encryption</title> |
||||||
|
|
||||||
|
<indexterm zone="database-file-encryption"> |
||||||
|
<primary>Cluster File Encryption</primary> |
||||||
|
</indexterm> |
||||||
|
|
||||||
|
<para> |
||||||
|
The purpose of cluster file encryption is to prevent users with read |
||||||
|
access to the directories used to store database files and write-ahead |
||||||
|
log from being able to access the data stored in those files. |
||||||
|
For example, when using cluster file encryption, users who have read |
||||||
|
access to the cluster directories for backup purposes will not be able |
||||||
|
to decrypt the data stored in the these files. |
||||||
|
</para> |
||||||
|
|
||||||
|
<para> |
||||||
|
Cluster file encryption uses two levels of encryption. The first level |
||||||
|
is data encryption keys, specifically keys zero and one. Key zero is |
||||||
|
the key used to encrypt database heap and index files which are stored in |
||||||
|
the file system, plus temporary files created during database operation. |
||||||
|
Key one is used to encrypt write-ahead log (WAL) files. Two different |
||||||
|
keys are used so that primary and standby servers can use different zero |
||||||
|
(heap/index/temp) keys, but the same one (WAL) key, so that these keys |
||||||
|
can eventually be rotated by switching the primary to the standby as |
||||||
|
and then changing the WAL key. |
||||||
|
</para> |
||||||
|
|
||||||
|
<para> |
||||||
|
The second level of encryption is a key used to encrypt first-level |
||||||
|
keys. This type of key is often referred to as a Key Encryption Key |
||||||
|
(<acronym>KEK</acronym>). This key is <emphasis>not</emphasis> stored |
||||||
|
in the file system, but provided at <command>initdb</command> time and |
||||||
|
each time the server is started. This key prevents anyone with access |
||||||
|
to the database directories from decrypting the data because they do |
||||||
|
not know the second-level key which encrypted the first-level keys |
||||||
|
which encrypted the database cluster files. |
||||||
|
</para> |
||||||
|
|
||||||
|
<sect1 id="encryption-file-encryption"> |
||||||
|
<title>Initialization</title> |
||||||
|
|
||||||
|
<para> |
||||||
|
Cluster file encryption is enabled when |
||||||
|
<productname>PostgreSQL</productname> is built |
||||||
|
with <literal>--with-openssl</literal> and <xref |
||||||
|
linkend="app-initdb-cluster-key-command"/> is specified |
||||||
|
during <command>initdb</command>. The cluster key |
||||||
|
provided by the <option>--cluster-key-command</option> |
||||||
|
option during <command>initdb</command> and the one generated |
||||||
|
by <xref linkend="guc-cluster-key-command"/> in the |
||||||
|
<filename>postgresql.conf</filename> must match for the database |
||||||
|
cluster to start. Note that the cluster key command |
||||||
|
passed to <command>initdb</command> must return a key of |
||||||
|
64 hexadecimal characters. For example. |
||||||
|
<programlisting> |
||||||
|
initdb -D dbname --cluster-key-command='ckey_passphrase.sh' |
||||||
|
</programlisting> |
||||||
|
</para> |
||||||
|
</sect1> |
||||||
|
|
||||||
|
<sect1 id="key-encryption-key"> |
||||||
|
<title>Internals</title> |
||||||
|
|
||||||
|
<para> |
||||||
|
During the <command>initdb</command> process, if |
||||||
|
<option>--cluster-key-command</option> is specified, two data-level |
||||||
|
encryption keys are created. These two keys are then encrypted with |
||||||
|
the key enryption key (KEK) supplied by the cluster key command before |
||||||
|
being stored in the database directory. The key or passphrase that |
||||||
|
derives the key must be supplied from the terminal or stored in a |
||||||
|
trusted key store, such as key vault software, hardware security module. |
||||||
|
</para> |
||||||
|
|
||||||
|
<para> |
||||||
|
If the <productname>PostgreSQL</productname> server has |
||||||
|
been initialized to require a cluster key, each time the |
||||||
|
server starts the <filename>postgresql.conf</filename> |
||||||
|
<varname>cluster_key_command</varname> command will be executed |
||||||
|
and the cluster key retrieved. The data encryption keys in the |
||||||
|
<filename>pg_cryptokeys</filename> directory will then be decrypted |
||||||
|
using the supplied key and integrity-checked to ensure it |
||||||
|
matches the initdb-supplied key. If this check fails, the |
||||||
|
server will refuse to start. |
||||||
|
</para> |
||||||
|
|
||||||
|
<para> |
||||||
|
The data encryption keys are randomly generated and are of 128, 192, |
||||||
|
or 256-bits in length. They are encrypted by the key encryption key |
||||||
|
(KEK) using Advanced Encryption Standard (<acronym>AES256</acronym>) |
||||||
|
encryption in Galois/Counter Mode (<acronym>GCM</acronym>), which also |
||||||
|
provides KEK authentication. |
||||||
|
</para> |
||||||
|
</sect1> |
||||||
|
</chapter> |
@ -0,0 +1,18 @@ |
|||||||
|
#-------------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# Makefile
|
||||||
|
# Makefile for src/backend/crypto
|
||||||
|
#
|
||||||
|
# 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 |
@ -0,0 +1,372 @@ |
|||||||
|
/*-------------------------------------------------------------------------
|
||||||
|
* |
||||||
|
* 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. |
||||||
|
* Once generated, these are not changed. During startup, we decrypt all |
||||||
|
* internal keys and load them to the shared memory space. Internal keys |
||||||
|
* on the shared memory are read-only. All wrapping and unwrapping key |
||||||
|
* routines require the OpenSSL library. |
||||||
|
* |
||||||
|
* Copyright (c) 2020, 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 "common/file_perm.h" |
||||||
|
#include "common/hex_decode.h" |
||||||
|
#include "common/kmgr_utils.h" |
||||||
|
#include "common/sha2.h" |
||||||
|
#include "access/xlog.h" |
||||||
|
#include "crypto/kmgr.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_MAX_INTERNAL_KEYS]; |
||||||
|
} KmgrShmemData; |
||||||
|
static KmgrShmemData *KmgrShmem; |
||||||
|
|
||||||
|
/* GUC variables */ |
||||||
|
char *cluster_key_command = NULL; |
||||||
|
int file_encryption_keylen = 0; |
||||||
|
|
||||||
|
CryptoKey bootstrap_keys[KMGR_MAX_INTERNAL_KEYS]; |
||||||
|
|
||||||
|
extern char *bootstrap_old_key_datadir; |
||||||
|
extern int bootstrap_file_encryption_keylen; |
||||||
|
|
||||||
|
static void bzeroKmgrKeys(int status, Datum arg); |
||||||
|
static void KmgrSaveCryptoKeys(const char *dir, CryptoKey *keys); |
||||||
|
static CryptoKey *generate_crypto_key(int len); |
||||||
|
|
||||||
|
/*
|
||||||
|
* This function must be called ONCE during initdb. |
||||||
|
*/ |
||||||
|
void |
||||||
|
BootStrapKmgr(void) |
||||||
|
{ |
||||||
|
char live_path[MAXPGPATH]; |
||||||
|
CryptoKey *keys_wrap; |
||||||
|
int nkeys; |
||||||
|
char cluster_key_hex[ALLOC_KMGR_CLUSTER_KEY_LEN]; |
||||||
|
int cluster_key_hex_len; |
||||||
|
unsigned char cluster_key[KMGR_CLUSTER_KEY_LEN]; |
||||||
|
|
||||||
|
#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? */ |
||||||
|
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 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 from the cluster_key command. The cluster_key |
||||||
|
* command might want to check for the existance of files in the |
||||||
|
* live directory, 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); |
||||||
|
|
||||||
|
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))); |
||||||
|
|
||||||
|
/* generate new cluster file encryption keys */ |
||||||
|
if (bootstrap_old_key_datadir == NULL) |
||||||
|
{ |
||||||
|
CryptoKey bootstrap_keys_wrap[KMGR_MAX_INTERNAL_KEYS]; |
||||||
|
PgCipherCtx *cluster_key_ctx; |
||||||
|
|
||||||
|
/* Create KEK encryption context */ |
||||||
|
cluster_key_ctx = pg_cipher_ctx_create(PG_CIPHER_AES_GCM, cluster_key, |
||||||
|
KMGR_CLUSTER_KEY_LEN, true); |
||||||
|
if (!cluster_key_ctx) |
||||||
|
elog(ERROR, "could not initialize encryption context"); |
||||||
|
|
||||||
|
/* Wrap all data encryption keys by key encryption key */ |
||||||
|
for (int id = 0; id < KMGR_MAX_INTERNAL_KEYS; id++) |
||||||
|
{ |
||||||
|
CryptoKey *key; |
||||||
|
|
||||||
|
/* generate a data encryption key */ |
||||||
|
key = generate_crypto_key(bootstrap_file_encryption_keylen); |
||||||
|
|
||||||
|
/* Set this key's ID */ |
||||||
|
key->pgkey_id = id; |
||||||
|
|
||||||
|
if (!kmgr_wrap_key(cluster_key_ctx, key, &(bootstrap_keys_wrap[id]))) |
||||||
|
{ |
||||||
|
pg_cipher_ctx_free(cluster_key_ctx); |
||||||
|
elog(ERROR, "failed to wrap data encryption key"); |
||||||
|
} |
||||||
|
|
||||||
|
explicit_bzero(key, sizeof(CryptoKey)); |
||||||
|
} |
||||||
|
|
||||||
|
/* Save data encryption keys to the disk */ |
||||||
|
KmgrSaveCryptoKeys(LIVE_KMGR_DIR, bootstrap_keys_wrap); |
||||||
|
|
||||||
|
explicit_bzero(bootstrap_keys_wrap, sizeof(bootstrap_keys_wrap)); |
||||||
|
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 file */ |
||||||
|
keys_wrap = kmgr_get_cryptokeys(LIVE_KMGR_DIR, &nkeys); |
||||||
|
Assert(nkeys == KMGR_MAX_INTERNAL_KEYS); |
||||||
|
|
||||||
|
if (!kmgr_verify_cluster_key(cluster_key, keys_wrap, bootstrap_keys, |
||||||
|
KMGR_MAX_INTERNAL_KEYS)) |
||||||
|
ereport(ERROR, |
||||||
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
||||||
|
errmsg("supplied cluster key does not match expected cluster_key"))); |
||||||
|
|
||||||
|
/* bzero keys on exit */ |
||||||
|
on_proc_exit(bzeroKmgrKeys, 0); |
||||||
|
|
||||||
|
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 (!file_encryption_keylen) |
||||||
|
return 0; |
||||||
|
|
||||||
|
return MAXALIGN(sizeof(KmgrShmemData)); |
||||||
|
} |
||||||
|
|
||||||
|
/* Allocate and initialize key manager memory */ |
||||||
|
void |
||||||
|
KmgrShmemInit(void) |
||||||
|
{ |
||||||
|
bool found; |
||||||
|
|
||||||
|
if (!file_encryption_keylen) |
||||||
|
return; |
||||||
|
|
||||||
|
KmgrShmem = (KmgrShmemData *) ShmemInitStruct("File encryption key manager", |
||||||
|
KmgrShmemSize(), &found); |
||||||
|
|
||||||
|
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) |
||||||
|
{ |
||||||
|
CryptoKey *keys_wrap; |
||||||
|
int nkeys; |
||||||
|
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]; |
||||||
|
|
||||||
|
if (!file_encryption_keylen) |
||||||
|
return; |
||||||
|
|
||||||
|
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 |
||||||
|
* NEW is partly written, or NEW wasn't renamed to LIVE --- in either |
||||||
|
* case, it needs to be repaired. |
||||||
|
*/ |
||||||
|
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 cluster key */ |
||||||
|
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); |
||||||
|
|
||||||
|
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))); |
||||||
|
|
||||||
|
/* Get the crypto keys from the file */ |
||||||
|
keys_wrap = kmgr_get_cryptokeys(LIVE_KMGR_DIR, &nkeys); |
||||||
|
Assert(nkeys == KMGR_MAX_INTERNAL_KEYS); |
||||||
|
|
||||||
|
/*
|
||||||
|
* Verify cluster key and prepare a data encryption key in plaintext in shared memory. |
||||||
|
*/ |
||||||
|
if (!kmgr_verify_cluster_key(cluster_key, keys_wrap, KmgrShmem->intlKeys, |
||||||
|
KMGR_MAX_INTERNAL_KEYS)) |
||||||
|
ereport(ERROR, |
||||||
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
||||||
|
errmsg("supplied cluster key does not match expected cluster key"))); |
||||||
|
|
||||||
|
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)); |
||||||
|
} |
||||||
|
|
||||||
|
const CryptoKey * |
||||||
|
KmgrGetKey(int id) |
||||||
|
{ |
||||||
|
Assert(id < KMGR_MAX_INTERNAL_KEYS); |
||||||
|
|
||||||
|
return (const CryptoKey *) (IsBootstrapProcessingMode() ? |
||||||
|
&(bootstrap_keys[id]) : &(KmgrShmem->intlKeys[id])); |
||||||
|
} |
||||||
|
|
||||||
|
/* Generate an empty CryptoKey */ |
||||||
|
static CryptoKey * |
||||||
|
generate_crypto_key(int len) |
||||||
|
{ |
||||||
|
CryptoKey *newkey; |
||||||
|
|
||||||
|
Assert(len <= KMGR_MAX_KEY_LEN); |
||||||
|
newkey = (CryptoKey *) palloc0(sizeof(CryptoKey)); |
||||||
|
|
||||||
|
/* We store the key as length + key into 'encrypted_key' */ |
||||||
|
memcpy(newkey->encrypted_key, &len, sizeof(len)); |
||||||
|
|
||||||
|
if (!pg_strong_random(newkey->encrypted_key + sizeof(len), len)) |
||||||
|
elog(ERROR, "failed to generate new file encryption key"); |
||||||
|
|
||||||
|
return newkey; |
||||||
|
} |
||||||
|
|
||||||
|
/*
|
||||||
|
* Save the given file encryption keys to the disk. |
||||||
|
*/ |
||||||
|
static void |
||||||
|
KmgrSaveCryptoKeys(const char *dir, CryptoKey *keys) |
||||||
|
{ |
||||||
|
elog(DEBUG2, "saving all cryptographic keys"); |
||||||
|
|
||||||
|
for (int i = 0; i < KMGR_MAX_INTERNAL_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]), sizeof(CryptoKey)) != sizeof(CryptoKey)) |
||||||
|
{ |
||||||
|
/* 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))); |
||||||
|
} |
||||||
|
} |
@ -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 |
||||||
|
}
|
||||||
|
|
@ -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; |
||||||
|
} |
||||||
|
|
@ -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 |
||||||
|
} |
@ -0,0 +1,62 @@ |
|||||||
|
/*-------------------------------------------------------------------------
|
||||||
|
* |
||||||
|
* cipher.h |
||||||
|
* Declarations for cryptographic functions |
||||||
|
* |
||||||
|
* Portions Copyright (c) 2020, PostgreSQL Global Development Group |
||||||
|
* |
||||||
|
* src/include/common/cipher.h |
||||||
|
* |
||||||
|
*------------------------------------------------------------------------- |
||||||
|
*/ |
||||||
|
#ifndef PG_CIPHER_H |
||||||
|
#define PG_CIPHER_H |
||||||
|
|
||||||
|
#ifdef USE_OPENSSL |
||||||
|
#include <openssl/evp.h> |
||||||
|
#include <openssl/conf.h> |
||||||
|
#include <openssl/err.h> |
||||||
|
#endif |
||||||
|
|
||||||
|
/*
|
||||||
|
* Supported symmetric encryption algorithm. These identifiers are passed |
||||||
|
* to pg_cipher_ctx_create() function, and then actual encryption |
||||||
|
* implementations need to initialize their context of the given encryption |
||||||
|
* algorithm. |
||||||
|
*/ |
||||||
|
#define PG_CIPHER_AES_GCM 0 |
||||||
|
#define PG_MAX_CIPHER_ID 1 |
||||||
|
|
||||||
|
/* AES128/192/256 various length definitions */ |
||||||
|
#define PG_AES128_KEY_LEN (128 / 8) |
||||||
|
#define PG_AES192_KEY_LEN (192 / 8) |
||||||
|
#define PG_AES256_KEY_LEN (256 / 8) |
||||||
|
|
||||||
|
/*
|
||||||
|
* The encrypted data is a series of blocks of size. Initialization |
||||||
|
* vector(IV) is the same size of cipher block. |
||||||
|
*/ |
||||||
|
#define PG_AES_BLOCK_SIZE 16 |
||||||
|
#define PG_AES_IV_SIZE (PG_AES_BLOCK_SIZE) |
||||||
|
|
||||||
|
#ifdef USE_OPENSSL |
||||||
|
typedef EVP_CIPHER_CTX PgCipherCtx; |
||||||
|
#else |
||||||
|
typedef void PgCipherCtx; |
||||||
|
#endif |
||||||
|
|
||||||
|
extern PgCipherCtx *pg_cipher_ctx_create(int cipher, uint8 *key, int klen, |
||||||
|
bool enc); |
||||||
|
extern void pg_cipher_ctx_free(PgCipherCtx *ctx); |
||||||
|
extern 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 *tag, const int taglen); |
||||||
|
extern 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); |
||||||
|
|
||||||
|
#endif /* PG_CIPHER_H */ |
@ -0,0 +1,98 @@ |
|||||||
|
/*-------------------------------------------------------------------------
|
||||||
|
* |
||||||
|
* kmgr_utils.h |
||||||
|
* Declarations for utility function for file encryption key |
||||||
|
* |
||||||
|
* Portions Copyright (c) 2020, PostgreSQL Global Development Group |
||||||
|
* |
||||||
|
* src/include/common/kmgr_utils.h |
||||||
|
* |
||||||
|
*------------------------------------------------------------------------- |
||||||
|
*/ |
||||||
|
#ifndef KMGR_UTILS_H |
||||||
|
#define KMGR_UTILS_H |
||||||
|
|
||||||
|
#include "common/cipher.h" |
||||||
|
|
||||||
|
/* Current version number */ |
||||||
|
#define KMGR_VERSION 1 |
||||||
|
|
||||||
|
/*
|
||||||
|
* Directories where cluster file encryption keys reside within PGDATA. |
||||||
|
*/ |
||||||
|
#define KMGR_DIR "pg_cryptokeys" |
||||||
|
#define KMGR_DIR_PID KMGR_DIR"/pg_alterckey.pid" |
||||||
|
#define LIVE_KMGR_DIR KMGR_DIR"/live" |
||||||
|
/* used during cluster key rotation */ |
||||||
|
#define NEW_KMGR_DIR KMGR_DIR"/new" |
||||||
|
#define OLD_KMGR_DIR KMGR_DIR"/old" |
||||||
|
|
||||||
|
/* CryptoKey file name is keys id */ |
||||||
|
#define CryptoKeyFilePath(path, dir, id) \ |
||||||
|
snprintf((path), MAXPGPATH, "%s/%d", (dir), (id)) |
||||||
|
|
||||||
|
/*
|
||||||
|
* Identifiers of internal keys. |
||||||
|
*/ |
||||||
|
#define KMGR_KEY_ID_REL 0 |
||||||
|
#define KMGR_KEY_ID_WAL 1 |
||||||
|
#define KMGR_MAX_INTERNAL_KEYS 2 |
||||||
|
|
||||||
|
/* We always, today, use a 256-bit AES key. */ |
||||||
|
#define KMGR_CLUSTER_KEY_LEN PG_AES256_KEY_LEN |
||||||
|
|
||||||
|
/* double for hex format, plus some for spaces, \r,\n, and null byte */ |
||||||
|
#define ALLOC_KMGR_CLUSTER_KEY_LEN (KMGR_CLUSTER_KEY_LEN * 2 + 10 + 2 + 1) |
||||||
|
|
||||||
|
/* Maximum length of key the key manager can store */ |
||||||
|
#define KMGR_MAX_KEY_LEN 256 |
||||||
|
#define KMGR_MAX_KEY_LEN_BYTES KMGR_MAX_KEY_LEN / 8 |
||||||
|
#define KMGR_MAX_WRAPPED_KEY_LEN KmgrSizeOfCipherText(KMGR_MAX_KEY_LEN) |
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Cryptographic key data structure. |
||||||
|
* |
||||||
|
* This is the structure we use to write out the encrypted keys. |
||||||
|
* |
||||||
|
* pgkey_id is the identifier for this key (should be same as the |
||||||
|
* file name and be one of KMGR_KEY_ID_* from above). This is what |
||||||
|
* we consider our 'context' or 'fixed' portion of the deterministic |
||||||
|
* IV we create. |
||||||
|
* |
||||||
|
* counter is updated each time we use the cluster KEK to encrypt a |
||||||
|
* new key. This is our the 'invocation' field of the deterministic |
||||||
|
* IV we create. |
||||||
|
* |
||||||
|
* Absolutely essential when using GCM (or CTR) is that the IV is unique, |
||||||
|
* for a given key, but a deterministic IV such as this is perfectly |
||||||
|
* acceptable and encouraged. If (and only if!) the KEK is changed to a |
||||||
|
* new key, then we can re-initialize the counter. |
||||||
|
* |
||||||
|
* Detailed discussion of deterministic IV creation can be found here: |
||||||
|
* |
||||||
|
* https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf
|
||||||
|
* |
||||||
|
* tag is the GCM tag which is produced and must be validated in order |
||||||
|
* to be able to trust the results of our decryption. |
||||||
|
* |
||||||
|
* encrypted_key is the encrypted key length (as an int) + encrypted key. |
||||||
|
*/ |
||||||
|
typedef struct CryptoKey |
||||||
|
{ |
||||||
|
uint64 pgkey_id; /* Upper half of IV */ |
||||||
|
uint64 counter; /* Lower half of IV */ |
||||||
|
uint128 tag; /* GCM tag */ |
||||||
|
unsigned char encrypted_key[sizeof(int) + KMGR_MAX_KEY_LEN_BYTES]; |
||||||
|
} CryptoKey; |
||||||
|
|
||||||
|
extern bool kmgr_wrap_key(PgCipherCtx *ctx, CryptoKey *in, CryptoKey *out); |
||||||
|
extern bool kmgr_unwrap_key(PgCipherCtx *ctx, CryptoKey *in, CryptoKey *out); |
||||||
|
extern bool kmgr_verify_cluster_key(unsigned char *cluster_key, |
||||||
|
CryptoKey *in_keys, CryptoKey *out_keys, |
||||||
|
int nkey); |
||||||
|
extern int kmgr_run_cluster_key_command(char *cluster_key_command, |
||||||
|
char *buf, int size, char *dir); |
||||||
|
extern CryptoKey *kmgr_get_cryptokeys(const char *path, int *nkeys); |
||||||
|
|
||||||
|
#endif /* KMGR_UTILS_H */ |
@ -0,0 +1,29 @@ |
|||||||
|
/*-------------------------------------------------------------------------
|
||||||
|
* |
||||||
|
* kmgr.h |
||||||
|
* |
||||||
|
* Portions Copyright (c) 2020, 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" |
||||||
|
|
||||||
|
/* GUC parameters */ |
||||||
|
extern int file_encryption_keylen; |
||||||
|
extern char *cluster_key_command; |
||||||
|
|
||||||
|
extern Size KmgrShmemSize(void); |
||||||
|
extern void KmgrShmemInit(void); |
||||||
|
extern void BootStrapKmgr(void); |
||||||
|
extern void InitializeKmgr(void); |
||||||
|
extern const CryptoKey *KmgrGetKey(int id); |
||||||
|
|
||||||
|
#endif /* KMGR_H */ |
Loading…
Reference in new issue