From 5767d4500657542c776951d9cf57bbecd775a074 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Thu, 5 Jun 2025 09:13:05 +0200 Subject: [PATCH] PG-1651 Do not try the fetch key from old provider when changing provider settings We used to verify that all keys are still accessible by fetching the key from cache and if not cached from the current provider settings when changing the key provider setting but this makes things fragile and prone to user error in the case where a principal key is not cached. Instead we can rely on the AEAD tag in the header of each file, which has two advantages 1) the cached and non-cache code path works the same and 2) we do not need to care about if the old provider is accessible, only that the new one is and that it contains the right key (i.e. one that can verify the header tag). --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 4 +- .../pg_tde/src/catalog/tde_principal_key.c | 42 ++++++------- .../pg_tde/src/include/access/pg_tde_tdemap.h | 2 +- contrib/pg_tde/t/change_key_provider.pl | 29 +++++++-- .../pg_tde/t/expected/change_key_provider.out | 59 ++++++++++++++++--- 5 files changed, 100 insertions(+), 36 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index b0e327eb83622..d54784ce02959 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -696,9 +696,9 @@ pg_tde_count_relations(Oid dbOid) } bool -pg_tde_verify_principal_key_info(TDESignedPrincipalKeyInfo *signed_key_info, const TDEPrincipalKey *principal_key) +pg_tde_verify_principal_key_info(TDESignedPrincipalKeyInfo *signed_key_info, const KeyData *principal_key_data) { - return AesGcmDecrypt(principal_key->keyData, + return AesGcmDecrypt(principal_key_data->data, signed_key_info->sign_iv, MAP_ENTRY_IV_SIZE, (unsigned char *) &signed_key_info->data, sizeof(signed_key_info->data), NULL, 0, diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index 516ab7ddb4c00..da4e88cc2133f 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -677,9 +677,7 @@ pg_tde_get_key_info(PG_FUNCTION_ARGS, Oid dbOid) #endif /* FRONTEND */ /* - * Gets principal key form the keyring. - * - * Caller should hold an exclusive tde_lwlock_enc_keys lock + * Get principal key form the keyring. */ static TDEPrincipalKey * get_principal_key_from_keyring(Oid dbOid) @@ -710,6 +708,12 @@ get_principal_key_from_keyring(Oid dbOid) errmsg("failed to retrieve principal key %s from keyring with ID %d", principalKeyInfo->data.name, principalKeyInfo->data.keyringId)); + if (!pg_tde_verify_principal_key_info(principalKeyInfo, &keyInfo->data)) + 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)); + principalKey = palloc_object(TDEPrincipalKey); principalKey->keyInfo = principalKeyInfo->data; @@ -718,12 +722,6 @@ get_principal_key_from_keyring(Oid dbOid) 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); @@ -929,7 +927,7 @@ pg_tde_is_provider_used(Oid databaseOid, Oid providerId) void pg_tde_verify_provider_keys_in_use(GenericKeyring *modified_provider) { - TDEPrincipalKey *existing_principal_key; + TDESignedPrincipalKeyInfo *existing_principal_key; HeapTuple tuple; SysScanDesc scan; Relation rel; @@ -940,11 +938,11 @@ pg_tde_verify_provider_keys_in_use(GenericKeyring *modified_provider) LWLockAcquire(tde_lwlock_enc_keys(), LW_EXCLUSIVE); /* Check the server key that is used for WAL encryption */ - existing_principal_key = GetPrincipalKeyNoDefault(GLOBAL_DATA_TDE_OID, LW_EXCLUSIVE); + existing_principal_key = pg_tde_get_principal_key_info(GLOBAL_DATA_TDE_OID); if (existing_principal_key != NULL && - existing_principal_key->keyInfo.keyringId == modified_provider->keyring_id) + existing_principal_key->data.keyringId == modified_provider->keyring_id) { - char *key_name = existing_principal_key->keyInfo.name; + char *key_name = existing_principal_key->data.name; KeyringReturnCodes return_code; KeyInfo *proposed_key; @@ -956,8 +954,7 @@ pg_tde_verify_provider_keys_in_use(GenericKeyring *modified_provider) key_name, modified_provider->provider_name, return_code)); } - if (proposed_key->data.len != existing_principal_key->keyLength || - memcmp(proposed_key->data.data, existing_principal_key->keyData, proposed_key->data.len) != 0) + if (!pg_tde_verify_principal_key_info(existing_principal_key, &proposed_key->data)) { ereport(ERROR, errmsg("key \"%s\" from modified key provider \"%s\" does not match existing server key", @@ -965,6 +962,9 @@ pg_tde_verify_provider_keys_in_use(GenericKeyring *modified_provider) } } + if (existing_principal_key) + pfree(existing_principal_key); + /* Check all databases for usage of keys from this key provider. */ rel = table_open(DatabaseRelationId, AccessShareLock); scan = systable_beginscan(rel, 0, false, NULL, 0, NULL); @@ -973,11 +973,11 @@ pg_tde_verify_provider_keys_in_use(GenericKeyring *modified_provider) { Form_pg_database database = (Form_pg_database) GETSTRUCT(tuple); - existing_principal_key = GetPrincipalKeyNoDefault(database->oid, LW_EXCLUSIVE); + existing_principal_key = pg_tde_get_principal_key_info(database->oid); if (existing_principal_key != NULL && - existing_principal_key->keyInfo.keyringId == modified_provider->keyring_id) + existing_principal_key->data.keyringId == modified_provider->keyring_id) { - char *key_name = existing_principal_key->keyInfo.name; + char *key_name = existing_principal_key->data.name; KeyringReturnCodes return_code; KeyInfo *proposed_key; @@ -989,14 +989,16 @@ pg_tde_verify_provider_keys_in_use(GenericKeyring *modified_provider) key_name, database->datname.data, modified_provider->provider_name, return_code)); } - if (proposed_key->data.len != existing_principal_key->keyLength || - memcmp(proposed_key->data.data, existing_principal_key->keyData, proposed_key->data.len) != 0) + if (!pg_tde_verify_principal_key_info(existing_principal_key, &proposed_key->data)) { ereport(ERROR, errmsg("key \"%s\" from modified key provider \"%s\" does not match existing key used by database \"%s\"", key_name, modified_provider->provider_name, database->datname.data)); } } + + if (existing_principal_key) + pfree(existing_principal_key); } systable_endscan(scan); table_close(rel, AccessShareLock); diff --git a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h index e19f787af2b1e..471840554093e 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h +++ b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h @@ -103,7 +103,7 @@ extern int pg_tde_count_relations(Oid dbOid); extern void pg_tde_delete_tde_files(Oid dbOid); 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 bool pg_tde_verify_principal_key_info(TDESignedPrincipalKeyInfo *signed_key_info, const KeyData *principal_key_data); extern void pg_tde_save_principal_key(const TDEPrincipalKey *principal_key, bool write_xlog); 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, bool write_xlog); diff --git a/contrib/pg_tde/t/change_key_provider.pl b/contrib/pg_tde/t/change_key_provider.pl index edf482c2ba760..4dbc262cf2d95 100644 --- a/contrib/pg_tde/t/change_key_provider.pl +++ b/contrib/pg_tde/t/change_key_provider.pl @@ -12,8 +12,6 @@ unlink('/tmp/change_key_provider_1.per'); unlink('/tmp/change_key_provider_2.per'); -unlink('/tmp/change_key_provider_3.per'); -unlink('/tmp/change_key_provider_4.per'); my $node = PostgreSQL::Test::Cluster->new('main'); $node->init; @@ -26,7 +24,7 @@ "SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/change_key_provider_1.per');" ); PGTDE::psql($node, 'postgres', - "SELECT pg_tde_list_all_database_key_providers();"); + "SELECT * FROM pg_tde_list_all_database_key_providers();"); PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_key_using_database_key_provider('test-key', 'file-vault');" ); @@ -48,7 +46,7 @@ "SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_provider_2.per');" ); PGTDE::psql($node, 'postgres', - "SELECT pg_tde_list_all_database_key_providers();"); + "SELECT * FROM pg_tde_list_all_database_key_providers();"); PGTDE::psql($node, 'postgres', "SELECT pg_tde_verify_key();"); PGTDE::psql($node, 'postgres', "SELECT pg_tde_is_encrypted('test_enc');"); @@ -57,7 +55,28 @@ PGTDE::append_to_result_file("-- server restart"); $node->restart; -# Verify +PGTDE::psql($node, 'postgres', "SELECT pg_tde_verify_key();"); +PGTDE::psql($node, 'postgres', "SELECT pg_tde_is_encrypted('test_enc');"); +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;'); + +# Move file, restart and then change provider +PGTDE::append_to_result_file( + "-- mv /tmp/change_key_provider_2.per /tmp/change_key_provider_1.per"); +move('/tmp/change_key_provider_2.per', '/tmp/change_key_provider_1.per'); + +PGTDE::append_to_result_file("-- server restart"); +$node->restart; + +PGTDE::psql($node, 'postgres', "SELECT pg_tde_verify_key();"); +PGTDE::psql($node, 'postgres', "SELECT pg_tde_is_encrypted('test_enc');"); +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;'); + +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_provider_1.per');" +); +PGTDE::psql($node, 'postgres', + "SELECT * FROM pg_tde_list_all_database_key_providers();"); + PGTDE::psql($node, 'postgres', "SELECT pg_tde_verify_key();"); PGTDE::psql($node, 'postgres', "SELECT pg_tde_is_encrypted('test_enc');"); PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;'); diff --git a/contrib/pg_tde/t/expected/change_key_provider.out b/contrib/pg_tde/t/expected/change_key_provider.out index 3ebd708be0850..4d912c77936d6 100644 --- a/contrib/pg_tde/t/expected/change_key_provider.out +++ b/contrib/pg_tde/t/expected/change_key_provider.out @@ -5,10 +5,10 @@ SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/change_key_prov (1 row) -SELECT pg_tde_list_all_database_key_providers(); - pg_tde_list_all_database_key_providers ------------------------------------------------------------------------ - (1,file-vault,file,"{""path"" : ""/tmp/change_key_provider_1.per""}") +SELECT * FROM pg_tde_list_all_database_key_providers(); + id | provider_name | provider_type | options +----+---------------+---------------+--------------------------------------------- + 1 | file-vault | file | {"path" : "/tmp/change_key_provider_1.per"} (1 row) SELECT pg_tde_set_key_using_database_key_provider('test-key', 'file-vault'); @@ -45,10 +45,10 @@ SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_p (1 row) -SELECT pg_tde_list_all_database_key_providers(); - pg_tde_list_all_database_key_providers ------------------------------------------------------------------------ - (1,file-vault,file,"{""path"" : ""/tmp/change_key_provider_2.per""}") +SELECT * FROM pg_tde_list_all_database_key_providers(); + id | provider_name | provider_type | options +----+---------------+---------------+--------------------------------------------- + 1 | file-vault | file | {"path" : "/tmp/change_key_provider_2.per"} (1 row) SELECT pg_tde_verify_key(); @@ -90,5 +90,48 @@ SELECT * FROM test_enc ORDER BY id; 2 | 6 (2 rows) +-- mv /tmp/change_key_provider_2.per /tmp/change_key_provider_1.per +-- server restart +SELECT pg_tde_verify_key(); +psql::1: ERROR: failed to retrieve principal key test-key from keyring with ID 1 +SELECT pg_tde_is_encrypted('test_enc'); + pg_tde_is_encrypted +--------------------- + t +(1 row) + +SELECT * FROM test_enc ORDER BY id; +psql::1: ERROR: failed to retrieve principal key test-key from keyring with ID 1 +SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_provider_1.per'); + pg_tde_change_database_key_provider_file +------------------------------------------ + +(1 row) + +SELECT * FROM pg_tde_list_all_database_key_providers(); + id | provider_name | provider_type | options +----+---------------+---------------+--------------------------------------------- + 1 | file-vault | file | {"path" : "/tmp/change_key_provider_1.per"} +(1 row) + +SELECT pg_tde_verify_key(); + pg_tde_verify_key +------------------- + +(1 row) + +SELECT pg_tde_is_encrypted('test_enc'); + pg_tde_is_encrypted +--------------------- + t +(1 row) + +SELECT * FROM test_enc ORDER BY id; + id | k +----+--- + 1 | 5 + 2 | 6 +(2 rows) + DROP EXTENSION pg_tde CASCADE; psql::1: NOTICE: drop cascades to table test_enc