PG-1416 Sign principal key info to protect against the wrong principal key

We already had protection against decrypting relation keys with the wrong
principal key but to properly protect us against new relation keys being
encrypted with the wrong principal key we need to also verify that the
principal key was correct when we fetch the principal key from the key
provider. We do so by signing the principal key info header of the key map
file using AES-128-GCM.

This way we cannot get a jumbled mess of relation keys encrypted with
multiple different principal keys.
This commit is contained in:
Andreas Karlsson 2025-04-04 19:41:38 +02:00 committed by Andreas Karlsson
parent e9d4927b2c
commit 5514727353
6 changed files with 103 additions and 55 deletions

View File

@ -66,7 +66,7 @@
typedef struct TDEFileHeader
{
int32 file_version;
TDEPrincipalKeyInfo principal_key_info;
TDESignedPrincipalKeyInfo signed_key_info;
} TDEFileHeader;
typedef struct RelKeyCacheRec
@ -125,13 +125,14 @@ static InternalKey *pg_tde_put_key_into_cache(const RelFileLocator *locator, Int
static InternalKey *pg_tde_create_key_map_entry(const RelFileLocator *newrlocator, uint32 entry_type);
static InternalKey *pg_tde_create_local_key(const RelFileLocator *newrlocator, uint32 entry_type);
static void pg_tde_generate_internal_key(InternalKey *int_key, uint32 entry_type);
static int pg_tde_file_header_write(const char *tde_filename, int fd, TDEPrincipalKeyInfo *principal_key_info, off_t *bytes_written);
static int pg_tde_file_header_write(const char *tde_filename, int fd, const TDESignedPrincipalKeyInfo *signed_key_info, off_t *bytes_written);
static void pg_tde_sign_principal_key_info(TDESignedPrincipalKeyInfo *signed_key_info, const TDEPrincipalKey *principal_key);
static off_t pg_tde_write_one_map_entry(int fd, const TDEMapEntry *map_entry, off_t *offset, const char *db_map_path);
static void pg_tde_write_key_map_entry(const RelFileLocator *rlocator, InternalKey *rel_key_data, TDEPrincipalKey *principal_key, bool write_xlog);
static bool pg_tde_delete_map_entry(const RelFileLocator *rlocator, char *db_map_path, off_t offset);
static int keyrotation_init_file(TDEPrincipalKeyInfo *new_principal_key_info, char *rotated_filename, char *filename, off_t *curr_pos);
static int keyrotation_init_file(const TDESignedPrincipalKeyInfo *signed_key_info, char *rotated_filename, const char *filename, off_t *curr_pos);
static void finalize_key_rotation(const char *path_old, const char *path_new);
static int pg_tde_open_file_write(const char *tde_filename, TDEPrincipalKeyInfo *principal_key_info, bool truncate, off_t *curr_pos);
static int pg_tde_open_file_write(const char *tde_filename, const TDESignedPrincipalKeyInfo *signed_key_info, bool truncate, off_t *curr_pos);
static void update_wal_keys_cache(void);
InternalKey *
@ -264,11 +265,18 @@ pg_tde_delete_tde_files(Oid dbOid)
}
void
pg_tde_save_principal_key_redo(TDEPrincipalKeyInfo *principal_key_info)
pg_tde_save_principal_key_redo(const TDESignedPrincipalKeyInfo *signed_key_info)
{
int map_fd;
off_t curr_pos;
char db_map_path[MAXPGPATH] = {0};
pg_tde_set_db_file_path(signed_key_info->data.databaseId, db_map_path);
LWLockAcquire(tde_lwlock_enc_keys(), LW_EXCLUSIVE);
pg_tde_save_principal_key(principal_key_info);
map_fd = pg_tde_open_file_write(db_map_path, signed_key_info, true, &curr_pos);
close(map_fd);
LWLockRelease(tde_lwlock_enc_keys());
}
@ -282,18 +290,21 @@ pg_tde_save_principal_key_redo(TDEPrincipalKeyInfo *principal_key_info)
* The caller must have an EXCLUSIVE LOCK on the files before calling this function.
*/
void
pg_tde_save_principal_key(TDEPrincipalKeyInfo *principal_key_info)
pg_tde_save_principal_key(const TDEPrincipalKey *principal_key)
{
int map_fd = -1;
off_t curr_pos = 0;
char db_map_path[MAXPGPATH] = {0};
TDESignedPrincipalKeyInfo signed_key_Info;
/* Set the file paths */
pg_tde_set_db_file_path(principal_key_info->databaseId, db_map_path);
pg_tde_set_db_file_path(principal_key->keyInfo.databaseId, db_map_path);
ereport(DEBUG2, (errmsg("pg_tde_save_principal_key")));
map_fd = pg_tde_open_file_write(db_map_path, principal_key_info, true, &curr_pos);
pg_tde_sign_principal_key_info(&signed_key_Info, principal_key);
map_fd = pg_tde_open_file_write(db_map_path, &signed_key_Info, true, &curr_pos);
close(map_fd);
}
@ -301,17 +312,17 @@ pg_tde_save_principal_key(TDEPrincipalKeyInfo *principal_key_info)
* Write TDE file header to a TDE file.
*/
static int
pg_tde_file_header_write(const char *tde_filename, int fd, TDEPrincipalKeyInfo *principal_key_info, off_t *bytes_written)
pg_tde_file_header_write(const char *tde_filename, int fd, const TDESignedPrincipalKeyInfo *signed_key_info, off_t *bytes_written)
{
TDEFileHeader fheader;
Assert(principal_key_info);
Assert(signed_key_info);
/* Create the header for this file. */
fheader.file_version = PG_TDE_FILEMAGIC;
/* Fill in the data */
fheader.principal_key_info = *principal_key_info;
fheader.signed_key_info = *signed_key_info;
*bytes_written = pg_pwrite(fd, &fheader, TDE_FILE_HEADER_SIZE, 0);
@ -335,6 +346,19 @@ pg_tde_file_header_write(const char *tde_filename, int fd, TDEPrincipalKeyInfo *
return fd;
}
static void
pg_tde_sign_principal_key_info(TDESignedPrincipalKeyInfo *signed_key_info, const TDEPrincipalKey *principal_key)
{
signed_key_info->data = principal_key->keyInfo;
if (!RAND_bytes(signed_key_info->sign_iv, MAP_ENTRY_EMPTY_IV_SIZE))
ereport(ERROR,
(errcode(ERRCODE_INTERNAL_ERROR),
errmsg("could not generate iv for key map: %s", ERR_error_string(ERR_get_error(), NULL))));
AesGcmEncrypt(principal_key->keyData, signed_key_info->sign_iv, (unsigned char *) &signed_key_info->data, sizeof(signed_key_info->data), NULL, 0, NULL, signed_key_info->aead_tag);
}
static void
pg_tde_initialize_map_entry(TDEMapEntry *map_entry, const TDEPrincipalKey *principal_key, const RelFileLocator *rlocator, const InternalKey *rel_key_data)
{
@ -399,14 +423,17 @@ pg_tde_write_key_map_entry(const RelFileLocator *rlocator, InternalKey *rel_key_
off_t curr_pos = 0;
off_t prev_pos = 0;
TDEMapEntry write_map_entry;
TDESignedPrincipalKeyInfo signed_key_Info;
Assert(rlocator);
/* Set the file paths */
pg_tde_set_db_file_path(rlocator->dbOid, db_map_path);
pg_tde_sign_principal_key_info(&signed_key_Info, principal_key);
/* Open and validate file for basic correctness. */
map_fd = pg_tde_open_file_write(db_map_path, &principal_key->keyInfo, false, &curr_pos);
map_fd = pg_tde_open_file_write(db_map_path, &signed_key_Info, false, &curr_pos);
prev_pos = curr_pos;
/*
@ -438,7 +465,7 @@ pg_tde_write_key_map_entry(const RelFileLocator *rlocator, InternalKey *rel_key_
XLogRelKey xlrec;
xlrec.mapEntry = write_map_entry;
xlrec.pkInfo = principal_key->keyInfo;
xlrec.pkInfo = signed_key_Info;
XLogBeginInsert();
XLogRegisterData((char *) &xlrec, sizeof(xlrec));
@ -466,7 +493,7 @@ pg_tde_write_key_map_entry(const RelFileLocator *rlocator, InternalKey *rel_key_
* concurrent in place updates leading to data conflicts.
*/
void
pg_tde_write_key_map_entry_redo(const TDEMapEntry *write_map_entry, TDEPrincipalKeyInfo *principal_key_info)
pg_tde_write_key_map_entry_redo(const TDEMapEntry *write_map_entry, TDESignedPrincipalKeyInfo *signed_key_info)
{
char db_map_path[MAXPGPATH] = {0};
int map_fd = -1;
@ -474,12 +501,12 @@ pg_tde_write_key_map_entry_redo(const TDEMapEntry *write_map_entry, TDEPrincipal
off_t prev_pos = 0;
/* Set the file paths */
pg_tde_set_db_file_path(principal_key_info->databaseId, db_map_path);
pg_tde_set_db_file_path(signed_key_info->data.databaseId, db_map_path);
LWLockAcquire(tde_lwlock_enc_keys(), LW_EXCLUSIVE);
/* Open and validate file for basic correctness. */
map_fd = pg_tde_open_file_write(db_map_path, principal_key_info, false, &curr_pos);
map_fd = pg_tde_open_file_write(db_map_path, signed_key_info, false, &curr_pos);
prev_pos = curr_pos;
/*
@ -631,7 +658,7 @@ pg_tde_free_key_map_entry(const RelFileLocator *rlocator, off_t offset)
* No error checking by this function.
*/
static File
keyrotation_init_file(TDEPrincipalKeyInfo *new_principal_key_info, char *rotated_filename, char *filename, off_t *curr_pos)
keyrotation_init_file(const TDESignedPrincipalKeyInfo *signed_key_info, char *rotated_filename, const char *filename, off_t *curr_pos)
{
/*
* Set the new filenames for the key rotation process - temporary at the
@ -640,7 +667,7 @@ keyrotation_init_file(TDEPrincipalKeyInfo *new_principal_key_info, char *rotated
snprintf(rotated_filename, MAXPGPATH, "%s.r", filename);
/* Create file, truncate if the rotate file already exits */
return pg_tde_open_file_write(rotated_filename, new_principal_key_info, true, curr_pos);
return pg_tde_open_file_write(rotated_filename, signed_key_info, true, curr_pos);
}
/*
@ -666,14 +693,17 @@ pg_tde_perform_rotate_key(TDEPrincipalKey *principal_key, TDEPrincipalKey *new_p
off_t curr_pos[PRINCIPAL_KEY_COUNT] = {0};
int fd[PRINCIPAL_KEY_COUNT];
char path[PRINCIPAL_KEY_COUNT][MAXPGPATH];
TDESignedPrincipalKeyInfo new_signed_key_info;
off_t map_size;
XLogPrincipalKeyRotate *xlrec;
off_t xlrec_size;
pg_tde_set_db_file_path(principal_key->keyInfo.databaseId, path[OLD_PRINCIPAL_KEY]);
pg_tde_sign_principal_key_info(&new_signed_key_info, new_principal_key);
fd[OLD_PRINCIPAL_KEY] = pg_tde_open_file_read(path[OLD_PRINCIPAL_KEY], &curr_pos[OLD_PRINCIPAL_KEY]);
fd[NEW_PRINCIPAL_KEY] = keyrotation_init_file(&new_principal_key->keyInfo, path[NEW_PRINCIPAL_KEY], path[OLD_PRINCIPAL_KEY], &curr_pos[NEW_PRINCIPAL_KEY]);
fd[NEW_PRINCIPAL_KEY] = keyrotation_init_file(&new_signed_key_info, path[NEW_PRINCIPAL_KEY], path[OLD_PRINCIPAL_KEY], &curr_pos[NEW_PRINCIPAL_KEY]);
/* Read all entries until EOF */
while (1)
@ -763,10 +793,10 @@ pg_tde_write_map_keydata_file(off_t file_size, char *file_data)
fheader = (TDEFileHeader *) file_data;
/* Set the file paths */
pg_tde_set_db_file_path(fheader->principal_key_info.databaseId, db_map_path);
pg_tde_set_db_file_path(fheader->signed_key_info.data.databaseId, db_map_path);
/* Initialize the new file and set the name */
fd_new = keyrotation_init_file(&fheader->principal_key_info, path_new, db_map_path, &curr_pos);
fd_new = keyrotation_init_file(&fheader->signed_key_info, path_new, db_map_path, &curr_pos);
if (pg_pwrite(fd_new, file_data, file_size, 0) != file_size)
{
@ -867,7 +897,7 @@ pg_tde_wal_last_key_set_lsn(XLogRecPtr lsn, const char *keyfile_path)
* is raised.
*/
static int
pg_tde_open_file_write(const char *tde_filename, TDEPrincipalKeyInfo *principal_key_info, bool truncate, off_t *curr_pos)
pg_tde_open_file_write(const char *tde_filename, const TDESignedPrincipalKeyInfo *signed_key_info, bool truncate, off_t *curr_pos)
{
int fd;
TDEFileHeader fheader;
@ -882,8 +912,8 @@ pg_tde_open_file_write(const char *tde_filename, TDEPrincipalKeyInfo *principal_
pg_tde_file_header_read(tde_filename, fd, &fheader, &bytes_read);
/* In case it's a new file, let's add the header now. */
if (bytes_read == 0 && principal_key_info)
pg_tde_file_header_write(tde_filename, fd, principal_key_info, &bytes_written);
if (bytes_read == 0 && signed_key_info)
pg_tde_file_header_write(tde_filename, fd, signed_key_info, &bytes_written);
*curr_pos = bytes_read + bytes_written;
return fd;
@ -990,6 +1020,12 @@ pg_tde_find_map_entry(const RelFileLocator *rlocator, uint32 key_type, char *db_
}
}
bool
pg_tde_verify_principal_key_info(TDESignedPrincipalKeyInfo *signed_key_info, const TDEPrincipalKey *principal_key)
{
return AesGcmDecrypt(principal_key->keyData, signed_key_info->sign_iv, (unsigned char *) &signed_key_info->data, sizeof(signed_key_info->data), NULL, 0, NULL, signed_key_info->aead_tag);
}
/*
* Decrypts a given key and returns the decrypted one.
*/
@ -1154,12 +1190,12 @@ pg_tde_read_one_map_entry2(int fd, int32 key_index, TDEMapEntry *map_entry, Oid
* Get the principal key from the map file. The caller must hold
* a LW_SHARED or higher lock on files before calling this function.
*/
TDEPrincipalKeyInfo *
TDESignedPrincipalKeyInfo *
pg_tde_get_principal_key_info(Oid dbOid)
{
int fd = -1;
TDEFileHeader fheader;
TDEPrincipalKeyInfo *principal_key_info = NULL;
TDESignedPrincipalKeyInfo *signed_key_info = NULL;
off_t bytes_read = 0;
char db_map_path[MAXPGPATH] = {0};
@ -1186,11 +1222,11 @@ pg_tde_get_principal_key_info(Oid dbOid)
*/
if (bytes_read > 0)
{
principal_key_info = palloc_object(TDEPrincipalKeyInfo);
*principal_key_info = fheader.principal_key_info;
signed_key_info = palloc_object(TDESignedPrincipalKeyInfo);
*signed_key_info = fheader.signed_key_info;
}
return principal_key_info;
return signed_key_info;
}
/*

View File

@ -61,7 +61,7 @@ tdeheap_rmgr_redo(XLogReaderState *record)
}
else if (info == XLOG_TDE_ADD_PRINCIPAL_KEY)
{
TDEPrincipalKeyInfo *mkey = (TDEPrincipalKeyInfo *) XLogRecGetData(record);
TDESignedPrincipalKeyInfo *mkey = (TDESignedPrincipalKeyInfo *) XLogRecGetData(record);
pg_tde_save_principal_key_redo(mkey);
}
@ -107,7 +107,7 @@ tdeheap_rmgr_desc(StringInfo buf, XLogReaderState *record)
{
XLogRelKey *xlrec = (XLogRelKey *) XLogRecGetData(record);
appendStringInfo(buf, "add tde internal key for relation %u/%u", xlrec->pkInfo.databaseId, xlrec->mapEntry.relNumber);
appendStringInfo(buf, "add tde internal key for relation %u/%u", xlrec->pkInfo.data.databaseId, xlrec->mapEntry.relNumber);
}
if (info == XLOG_TDE_ADD_PRINCIPAL_KEY)
{

View File

@ -343,7 +343,7 @@ set_principal_key_with_keyring(const char *key_name, const char *provider_name,
if (!already_has_key)
{
/* First key created for the database */
pg_tde_save_principal_key(&new_principal_key->keyInfo);
pg_tde_save_principal_key(new_principal_key);
/* XLog the new key */
XLogBeginInsert();
@ -703,7 +703,7 @@ pg_tde_get_key_info(PG_FUNCTION_ARGS, Oid dbOid)
static TDEPrincipalKey *
get_principal_key_from_keyring(Oid dbOid)
{
TDEPrincipalKeyInfo *principalKeyInfo;
TDESignedPrincipalKeyInfo *principalKeyInfo;
GenericKeyring *keyring;
KeyInfo *keyInfo;
KeyringReturnCodes keyring_ret;
@ -715,28 +715,34 @@ get_principal_key_from_keyring(Oid dbOid)
if (principalKeyInfo == NULL)
return NULL;
keyring = GetKeyProviderByID(principalKeyInfo->keyringId, dbOid);
keyring = GetKeyProviderByID(principalKeyInfo->data.keyringId, dbOid);
if (keyring == NULL)
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
errmsg("keyring lookup failed for principal key %s, unknown keyring with ID %d",
principalKeyInfo->name, principalKeyInfo->keyringId)));
principalKeyInfo->data.name, principalKeyInfo->data.keyringId)));
keyInfo = KeyringGetKey(keyring, principalKeyInfo->name, &keyring_ret);
keyInfo = KeyringGetKey(keyring, principalKeyInfo->data.name, &keyring_ret);
if (keyInfo == NULL)
ereport(ERROR,
(errcode(ERRCODE_NO_DATA_FOUND),
errmsg("failed to retrieve principal key %s from keyring with ID %d",
principalKeyInfo->name, principalKeyInfo->keyringId)));
principalKeyInfo->data.name, principalKeyInfo->data.keyringId)));
principalKey = palloc_object(TDEPrincipalKey);
principalKey->keyInfo = *principalKeyInfo;
principalKey->keyInfo = principalKeyInfo->data;
memcpy(principalKey->keyData, keyInfo->data.data, keyInfo->data.len);
principalKey->keyLength = keyInfo->data.len;
Assert(dbOid == principalKey->keyInfo.databaseId);
if (!pg_tde_verify_principal_key_info(principalKeyInfo, principalKey))
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
errmsg("Failed to verify principal key header for key %s, incorrect principal key or corrupted key file",
principalKeyInfo->data.name)));
pfree(keyInfo);
pfree(keyring);
pfree(principalKeyInfo);
@ -833,7 +839,7 @@ GetPrincipalKey(Oid dbOid, LWLockMode lockMode)
*newPrincipalKey = *principalKey;
newPrincipalKey->keyInfo.databaseId = dbOid;
pg_tde_save_principal_key(&newPrincipalKey->keyInfo);
pg_tde_save_principal_key(newPrincipalKey);
push_principal_key_to_cache(newPrincipalKey);

View File

@ -46,6 +46,13 @@ typedef struct InternalKey
#define MAP_ENTRY_EMPTY_IV_SIZE 16
#define MAP_ENTRY_EMPTY_AEAD_TAG_SIZE 16
typedef struct
{
TDEPrincipalKeyInfo data;
unsigned char sign_iv[16];
unsigned char aead_tag[16];
} TDESignedPrincipalKeyInfo;
/* We do not need the dbOid since the entries are stored in a file per db */
typedef struct TDEMapEntry
{
@ -61,7 +68,7 @@ typedef struct TDEMapEntry
typedef struct XLogRelKey
{
TDEMapEntry mapEntry;
TDEPrincipalKeyInfo pkInfo;
TDESignedPrincipalKeyInfo pkInfo;
} XLogRelKey;
/*
@ -95,7 +102,7 @@ extern void pg_tde_wal_last_key_set_lsn(XLogRecPtr lsn, const char *keyfile_path
extern InternalKey *pg_tde_create_smgr_key(const RelFileLocatorBackend *newrlocator);
extern void pg_tde_create_wal_key(InternalKey *rel_key_data, const RelFileLocator *newrlocator, uint32 flags);
extern void pg_tde_free_key_map_entry(const RelFileLocator *rlocator, off_t offset);
extern void pg_tde_write_key_map_entry_redo(const TDEMapEntry *write_map_entry, TDEPrincipalKeyInfo *principal_key_info);
extern void pg_tde_write_key_map_entry_redo(const TDEMapEntry *write_map_entry, TDESignedPrincipalKeyInfo *signed_key_info);
#define PG_TDE_MAP_FILENAME "pg_tde_%d_map"
@ -109,9 +116,10 @@ extern InternalKey *GetSMGRRelationKey(RelFileLocatorBackend rel);
extern void pg_tde_delete_tde_files(Oid dbOid);
extern TDEPrincipalKeyInfo *pg_tde_get_principal_key_info(Oid dbOid);
extern void pg_tde_save_principal_key(TDEPrincipalKeyInfo *principal_key_info);
extern void pg_tde_save_principal_key_redo(TDEPrincipalKeyInfo *principal_key_info);
extern TDESignedPrincipalKeyInfo *pg_tde_get_principal_key_info(Oid dbOid);
extern bool pg_tde_verify_principal_key_info(TDESignedPrincipalKeyInfo *signed_key_info, const TDEPrincipalKey *principal_key);
extern void pg_tde_save_principal_key(const TDEPrincipalKey *principal_key);
extern void pg_tde_save_principal_key_redo(const TDESignedPrincipalKeyInfo *signed_key_info);
extern void pg_tde_perform_rotate_key(TDEPrincipalKey *principal_key, TDEPrincipalKey *new_principal_key);
extern void pg_tde_write_map_keydata_file(off_t size, char *file_data);

View File

@ -174,15 +174,13 @@ PGTDE::append_to_file($stderr);
(undef, $stdout, $stderr) = $node->psql('postgres', 'SELECT * FROM test_enc ORDER BY id;', extra_params => ['-a']);
PGTDE::append_to_file($stdout);
PGTDE::append_to_file($stderr);
(undef, $stdout, $stderr) = $node->psql('postgres', 'CREATE TABLE test_enc2 (id serial, k integer, PRIMARY KEY (id)) USING tde_heap;', extra_params => ['-a']);
PGTDE::append_to_file($stdout);
PGTDE::append_to_file($stderr);
$stdout = $node->safe_psql('postgres', "SELECT pg_tde_change_key_provider_file('file-vault', '/tmp/change_key_provider_4.per');", extra_params => ['-a']);
PGTDE::append_to_file($stdout);
# Restart the server
PGTDE::append_to_file("-- server restart");
$rt_value = $node->stop();
$rt_value = $node->start();
# Verify
$stdout = $node->safe_psql('postgres', "SELECT pg_tde_verify_principal_key();", extra_params => ['-a']);
PGTDE::append_to_file($stdout);

View File

@ -80,15 +80,15 @@ SELECT pg_tde_change_key_provider_file('file-vault', '/tmp/change_key_provider_3
1
-- server restart
SELECT pg_tde_verify_principal_key();
psql:<stdin>:1: ERROR: Failed to verify principal key header for key test-key, incorrect principal key or corrupted key file
SELECT pg_tde_is_encrypted('test_enc');
psql:<stdin>:1: ERROR: Failed to decrypt key, incorrect principal key or corrupted key file
psql:<stdin>:1: ERROR: Failed to verify principal key header for key test-key, incorrect principal key or corrupted key file
SELECT * FROM test_enc ORDER BY id;
psql:<stdin>:1: ERROR: Failed to decrypt key, incorrect principal key or corrupted key file
psql:<stdin>:1: ERROR: Failed to verify principal key header for key test-key, incorrect principal key or corrupted key file
CREATE TABLE test_enc2 (id serial, k integer, PRIMARY KEY (id)) USING tde_heap;
psql:<stdin>:1: ERROR: Failed to verify principal key header for key test-key, incorrect principal key or corrupted key file
SELECT pg_tde_change_key_provider_file('file-vault', '/tmp/change_key_provider_4.per');
1
-- server restart
SELECT pg_tde_verify_principal_key();
SELECT pg_tde_is_encrypted('test_enc');