From c8770a6194bacf6d35df3d130cff73a8813d8a1b Mon Sep 17 00:00:00 2001 From: Andrew Pogrebnoy Date: Mon, 29 Sep 2025 16:16:43 +0300 Subject: [PATCH 1/3] Add support for 32 byte keys It's a draft. Only Principal and WAL keys work so far --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 14 ++--- contrib/pg_tde/src/access/pg_tde_xlog_keys.c | 46 +++++++++------- contrib/pg_tde/src/access/pg_tde_xlog_smgr.c | 8 +-- .../pg_tde/src/catalog/tde_principal_key.c | 5 +- contrib/pg_tde/src/encryption/enc_aes.c | 52 ++++++++++++------- contrib/pg_tde/src/encryption/enc_tde.c | 9 ++-- .../src/include/access/pg_tde_xlog_keys.h | 2 +- .../src/include/access/pg_tde_xlog_smgr.h | 2 +- .../pg_tde/src/include/encryption/enc_aes.h | 10 ++-- .../pg_tde/src/include/encryption/enc_tde.h | 9 ++-- contrib/pg_tde/src/include/pg_tde_guc.h | 8 +++ contrib/pg_tde/src/keyring/keyring_api.c | 4 +- contrib/pg_tde/src/pg_tde.c | 2 +- contrib/pg_tde/src/pg_tde_guc.c | 38 ++++++++++++++ contrib/pg_tde/src/smgr/pg_tde_smgr.c | 11 ++-- src/bin/pg_basebackup/pg_basebackup.c | 2 +- src/bin/pg_resetwal/pg_resetwal.c | 2 +- 17 files changed, 149 insertions(+), 75 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index 70b6722a2ebc4..c1800ee6561b0 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -70,7 +70,7 @@ typedef struct TDEMapEntry uint32 type; /* Part of AAD */ uint32 _unused3; /* Part of AAD */ - uint8 encrypted_key_data[INTERNAL_KEY_LEN]; + uint8 encrypted_key_data[INTERNAL_KEY_MAX_LEN]; uint8 key_base_iv[INTERNAL_KEY_IV_LEN]; uint32 _unused1; /* Will be 1 in existing files entries. */ @@ -380,7 +380,7 @@ pg_tde_sign_principal_key_info(TDESignedPrincipalKeyInfo *signed_key_info, const errcode(ERRCODE_INTERNAL_ERROR), errmsg("could not generate iv for key map: %s", ERR_error_string(ERR_get_error(), NULL))); - AesGcmEncrypt(principal_key->keyData, + AesGcmEncrypt(principal_key->keyData, principal_key->keyLength, signed_key_info->sign_iv, MAP_ENTRY_IV_SIZE, (unsigned char *) &signed_key_info->data, sizeof(signed_key_info->data), NULL, 0, @@ -409,10 +409,10 @@ pg_tde_initialize_map_entry(TDEMapEntry *map_entry, const TDEPrincipalKey *princ errcode(ERRCODE_INTERNAL_ERROR), errmsg("could not generate iv for key map: %s", ERR_error_string(ERR_get_error(), NULL))); - AesGcmEncrypt(principal_key->keyData, + AesGcmEncrypt(principal_key->keyData, principal_key->keyLength, map_entry->entry_iv, MAP_ENTRY_IV_SIZE, (unsigned char *) map_entry, offsetof(TDEMapEntry, encrypted_key_data), - rel_key_data->key, INTERNAL_KEY_LEN, + rel_key_data->key, INTERNAL_KEY_MAX_LEN, map_entry->encrypted_key_data, map_entry->aead_tag, MAP_ENTRY_AEAD_TAG_SIZE); } @@ -573,7 +573,7 @@ pg_tde_count_encryption_keys(Oid dbOid) bool pg_tde_verify_principal_key_info(TDESignedPrincipalKeyInfo *signed_key_info, const KeyData *principal_key_data) { - return AesGcmDecrypt(principal_key_data->data, + return AesGcmDecrypt(principal_key_data->data, principal_key_data->len, signed_key_info->sign_iv, MAP_ENTRY_IV_SIZE, (unsigned char *) &signed_key_info->data, sizeof(signed_key_info->data), NULL, 0, @@ -588,10 +588,10 @@ tde_decrypt_rel_key(const TDEPrincipalKey *principal_key, TDEMapEntry *map_entry Assert(principal_key); - if (!AesGcmDecrypt(principal_key->keyData, + if (!AesGcmDecrypt(principal_key->keyData, principal_key->keyLength, map_entry->entry_iv, MAP_ENTRY_IV_SIZE, (unsigned char *) map_entry, offsetof(TDEMapEntry, encrypted_key_data), - map_entry->encrypted_key_data, INTERNAL_KEY_LEN, + map_entry->encrypted_key_data, INTERNAL_KEY_MAX_LEN, key->key, map_entry->aead_tag, MAP_ENTRY_AEAD_TAG_SIZE)) ereport(ERROR, diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_keys.c b/contrib/pg_tde/src/access/pg_tde_xlog_keys.c index 06253573fc9ac..72dffc764c648 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_keys.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_keys.c @@ -22,7 +22,8 @@ #include "pg_tde_fe.h" #endif -#define PG_TDE_WAL_KEY_FILE_MAGIC 0x014B4557 /* version ID value = WEK 01 */ +#define PG_TDE_WAL_KEY_FILE_MAGIC_OLD 0x014B4557 /* old version ID value = WEK 01 */ +#define PG_TDE_WAL_KEY_FILE_MAGIC 0x024B4557 /* version ID value = WEK 02 */ #define PG_TDE_WAL_KEY_FILE_NAME "wal_keys" typedef struct WalKeyFileHeader @@ -40,12 +41,12 @@ typedef struct WalKeyFileHeader * encrypting/decrypting existing keys from the key files, so any changes here * might break existing clusters. */ -typedef struct WalKeyFileEntry +typedef struct WalKeyFileEntryOld { uint32 _unused1; /* Part of AAD, is 1 or 2 in existing entries */ uint32 _unused2; /* Part of AAD */ - uint8 encrypted_key_data[INTERNAL_KEY_LEN]; + uint8 encrypted_key_data[INTERNAL_KEY_OLD_LEN]; uint8 key_base_iv[INTERNAL_KEY_IV_LEN]; uint32 range_type; /* WalEncryptionRangeType */ @@ -55,6 +56,20 @@ typedef struct WalKeyFileEntry /* IV and tag used when encrypting the key itself */ unsigned char entry_iv[MAP_ENTRY_IV_SIZE]; unsigned char aead_tag[MAP_ENTRY_AEAD_TAG_SIZE]; +} WalKeyFileEntryOld; + +typedef struct WalKeyFileEntry +{ + uint32 key_len; + uint32 range_type; /* WalEncryptionRangeType */ + WalLocation range_start; + + /* IV and tag used when encrypting the key itself */ + unsigned char entry_iv[MAP_ENTRY_IV_SIZE]; + unsigned char aead_tag[MAP_ENTRY_AEAD_TAG_SIZE]; + + uint8 key_base_iv[INTERNAL_KEY_IV_LEN]; + uint8 encrypted_key_data[INTERNAL_KEY_MAX_LEN]; } WalKeyFileEntry; static WALKeyCacheRec *tde_wal_key_cache = NULL; @@ -178,7 +193,7 @@ pg_tde_wal_last_range_set_location(WalLocation loc) * with the actual lsn by the first WAL write. */ void -pg_tde_create_wal_range(WalEncryptionRange *range, WalEncryptionRangeType type) +pg_tde_create_wal_range(WalEncryptionRange *range, WalEncryptionRangeType type, int key_len) { TDEPrincipalKey *principal_key; @@ -199,7 +214,7 @@ pg_tde_create_wal_range(WalEncryptionRange *range, WalEncryptionRangeType type) range->end.lsn = MaxXLogRecPtr; range->end.tli = MaxTimeLineID; - pg_tde_generate_internal_key(&range->key); + pg_tde_generate_internal_key(&range->key, key_len); pg_tde_write_wal_key_file_entry(range, principal_key); @@ -615,12 +630,13 @@ pg_tde_wal_range_from_entry(const TDEPrincipalKey *principal_key, WalKeyFileEntr range->start = entry->range_start; range->end.tli = MaxTimeLineID; range->end.lsn = MaxXLogRecPtr; + range->key.key_len = entry->key_len; memcpy(range->key.base_iv, entry->key_base_iv, INTERNAL_KEY_IV_LEN); - if (!AesGcmDecrypt(principal_key->keyData, + if (!AesGcmDecrypt(principal_key->keyData, principal_key->keyLength, entry->entry_iv, MAP_ENTRY_IV_SIZE, - (unsigned char *) entry, offsetof(WalKeyFileEntry, encrypted_key_data), - entry->encrypted_key_data, INTERNAL_KEY_LEN, + (unsigned char *) entry, offsetof(WalKeyFileEntry, range_start), + entry->encrypted_key_data, entry->key_len, range->key.key, entry->aead_tag, MAP_ENTRY_AEAD_TAG_SIZE)) ereport(ERROR, @@ -664,13 +680,7 @@ pg_tde_initialize_wal_key_file_entry(WalKeyFileEntry *entry, memset(entry, 0, sizeof(WalKeyFileEntry)); - /* - * We set this field here so that existing file entries will be consistent - * and future use of this field easier. Some existing entries will have 2 - * here. - */ - entry->_unused1 = 1; - + entry->key_len = range->key.key_len; entry->range_type = range->type; entry->range_start = range->start; memcpy(entry->key_base_iv, range->key.base_iv, INTERNAL_KEY_IV_LEN); @@ -680,10 +690,10 @@ pg_tde_initialize_wal_key_file_entry(WalKeyFileEntry *entry, errcode(ERRCODE_INTERNAL_ERROR), errmsg("could not generate iv for wal key file entry: %s", ERR_error_string(ERR_get_error(), NULL))); - AesGcmEncrypt(principal_key->keyData, + AesGcmEncrypt(principal_key->keyData, principal_key->keyLength, entry->entry_iv, MAP_ENTRY_IV_SIZE, - (unsigned char *) entry, offsetof(WalKeyFileEntry, encrypted_key_data), - range->key.key, INTERNAL_KEY_LEN, + (unsigned char *) entry, offsetof(WalKeyFileEntry, range_start), + range->key.key, range->key.key_len, entry->encrypted_key_data, entry->aead_tag, MAP_ENTRY_AEAD_TAG_SIZE); } diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c index c8e62a1edc144..a2117763c3fd0 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c @@ -221,7 +221,7 @@ TDEXLogSmgrInit() } void -TDEXLogSmgrInitWrite(bool encrypt_xlog) +TDEXLogSmgrInitWrite(bool encrypt_xlog, int key_len) { WalEncryptionRange *range; WALKeyCacheRec *keys; @@ -242,11 +242,11 @@ TDEXLogSmgrInitWrite(bool encrypt_xlog) */ if (encrypt_xlog) { - pg_tde_create_wal_range(&CurrentWalEncryptionRange, WAL_ENCRYPTION_RANGE_ENCRYPTED); + pg_tde_create_wal_range(&CurrentWalEncryptionRange, WAL_ENCRYPTION_RANGE_ENCRYPTED, key_len); } else if (range && range->type == WAL_ENCRYPTION_RANGE_ENCRYPTED) { - pg_tde_create_wal_range(&CurrentWalEncryptionRange, WAL_ENCRYPTION_RANGE_UNENCRYPTED); + pg_tde_create_wal_range(&CurrentWalEncryptionRange, WAL_ENCRYPTION_RANGE_UNENCRYPTED, key_len); } else if (range) { @@ -353,6 +353,7 @@ TDEXLogWriteEncryptedPages(int fd, const void *buf, size_t count, off_t offset, count, enc_buff, range->key.key, + range->key.key_len, &EncryptionCryptCtx); return pg_pwrite(fd, enc_buff, count, offset); @@ -584,6 +585,7 @@ TDEXLogCryptBuffer(const void *buf, void *out_buf, size_t count, off_t offset, dec_sz, o_buf, curr_key->range.key.key, + curr_key->range.key.key_len, &curr_key->crypt_ctx); } } diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index 97ae2a612babe..d0a6c8753fde7 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -62,9 +62,6 @@ typedef struct TdePrincipalKeylocalState dshash_table *sharedHash; } TdePrincipalKeylocalState; -/* Length of newly generated principal keys */ -#define PRINCIPAL_KEY_LEN 16 - /* Parameters for the principal key info shared hash */ static dshash_parameters principal_key_dsh_params = { .key_size = sizeof(Oid), @@ -534,7 +531,7 @@ pg_tde_create_principal_key_internal(Oid providerOid, errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("cannot create key \"%s\" because it already exists", key_name)); - key_info = KeyringGenerateNewKeyAndStore(provider, key_name, PRINCIPAL_KEY_LEN); + key_info = KeyringGenerateNewKeyAndStore(provider, key_name, TdeKeyLength); pfree(key_info); pfree(provider); diff --git a/contrib/pg_tde/src/encryption/enc_aes.c b/contrib/pg_tde/src/encryption/enc_aes.c index 8c98a3750741a..d5ae680a92322 100644 --- a/contrib/pg_tde/src/encryption/enc_aes.c +++ b/contrib/pg_tde/src/encryption/enc_aes.c @@ -36,6 +36,10 @@ static const EVP_CIPHER *cipher_cbc = NULL; static const EVP_CIPHER *cipher_gcm = NULL; static const EVP_CIPHER *cipher_ctr_ecb = NULL; +static const EVP_CIPHER *cipher_cbc_256 = NULL; +static const EVP_CIPHER *cipher_gcm_256 = NULL; +static const EVP_CIPHER *cipher_ctr_ecb_256 = NULL; + void AesInit(void) { @@ -45,21 +49,26 @@ AesInit(void) cipher_cbc = EVP_aes_128_cbc(); cipher_gcm = EVP_aes_128_gcm(); cipher_ctr_ecb = EVP_aes_128_ecb(); + + cipher_cbc_256 = EVP_aes_256_cbc(); + cipher_gcm_256 = EVP_aes_256_gcm(); + cipher_ctr_ecb_256 = EVP_aes_256_ecb(); } static void -AesEcbEncrypt(EVP_CIPHER_CTX **ctxPtr, const unsigned char *key, const unsigned char *in, int in_len, unsigned char *out) +AesEcbEncrypt(EVP_CIPHER_CTX **ctxPtr, const unsigned char *key, int key_len, const unsigned char *in, int in_len, unsigned char *out) { int out_len; + const EVP_CIPHER *cipher = key_len == 32 ? cipher_ctr_ecb_256 : cipher_ctr_ecb; if (*ctxPtr == NULL) { - Assert(cipher_ctr_ecb != NULL); + Assert(cipher != NULL); *ctxPtr = EVP_CIPHER_CTX_new(); EVP_CIPHER_CTX_init(*ctxPtr); - if (EVP_CipherInit_ex(*ctxPtr, cipher_ctr_ecb, NULL, key, NULL, 1) == 0) + if (EVP_CipherInit_ex(*ctxPtr, cipher, NULL, key, NULL, 1) == 0) ereport(ERROR, errmsg("EVP_CipherInit_ex failed. OpenSSL error: %s", ERR_error_string(ERR_get_error(), NULL))); @@ -74,19 +83,20 @@ AesEcbEncrypt(EVP_CIPHER_CTX **ctxPtr, const unsigned char *key, const unsigned } static void -AesRunCbc(int enc, const unsigned char *key, const unsigned char *iv, const unsigned char *in, int in_len, unsigned char *out) +AesRunCbc(int enc, const unsigned char *key, int key_len, const unsigned char *iv, const unsigned char *in, int in_len, unsigned char *out) { int out_len; int out_len_final; EVP_CIPHER_CTX *ctx = NULL; + const EVP_CIPHER *cipher = key_len == 32 ? cipher_cbc_256 : cipher_cbc; - Assert(cipher_cbc != NULL); - Assert(in_len % EVP_CIPHER_block_size(cipher_cbc) == 0); + Assert(cipher != NULL); + Assert(in_len % EVP_CIPHER_block_size(cipher) == 0); ctx = EVP_CIPHER_CTX_new(); EVP_CIPHER_CTX_init(ctx); - if (EVP_CipherInit_ex(ctx, cipher_cbc, NULL, key, iv, enc) == 0) + if (EVP_CipherInit_ex(ctx, cipher, NULL, key, iv, enc) == 0) ereport(ERROR, errmsg("EVP_CipherInit_ex failed. OpenSSL error: %s", ERR_error_string(ERR_get_error(), NULL))); @@ -112,31 +122,32 @@ AesRunCbc(int enc, const unsigned char *key, const unsigned char *iv, const unsi } void -AesEncrypt(const unsigned char *key, const unsigned char *iv, const unsigned char *in, int in_len, unsigned char *out) +AesEncrypt(const unsigned char *key, int key_len, const unsigned char *iv, const unsigned char *in, int in_len, unsigned char *out) { - AesRunCbc(1, key, iv, in, in_len, out); + AesRunCbc(1, key, key_len, iv, in, in_len, out); } void -AesDecrypt(const unsigned char *key, const unsigned char *iv, const unsigned char *in, int in_len, unsigned char *out) +AesDecrypt(const unsigned char *key, int key_len, const unsigned char *iv, const unsigned char *in, int in_len, unsigned char *out) { - AesRunCbc(0, key, iv, in, in_len, out); + AesRunCbc(0, key, key_len, iv, in, in_len, out); } void -AesGcmEncrypt(const unsigned char *key, const unsigned char *iv, int iv_len, const unsigned char *aad, int aad_len, const unsigned char *in, int in_len, unsigned char *out, unsigned char *tag, int tag_len) +AesGcmEncrypt(const unsigned char *key, int key_len, const unsigned char *iv, int iv_len, const unsigned char *aad, int aad_len, const unsigned char *in, int in_len, unsigned char *out, unsigned char *tag, int tag_len) { int out_len; int out_len_final; EVP_CIPHER_CTX *ctx; + const EVP_CIPHER *cipher = key_len == 32 ? cipher_gcm_256 : cipher_gcm; - Assert(cipher_gcm != NULL); - Assert(in_len % EVP_CIPHER_block_size(cipher_gcm) == 0); + Assert(cipher != NULL); + Assert(in_len % EVP_CIPHER_block_size(cipher) == 0); ctx = EVP_CIPHER_CTX_new(); EVP_CIPHER_CTX_init(ctx); - if (EVP_EncryptInit_ex(ctx, cipher_gcm, NULL, NULL, NULL) == 0) + if (EVP_EncryptInit_ex(ctx, cipher, NULL, NULL, NULL) == 0) ereport(ERROR, errmsg("EVP_EncryptInit_ex failed. OpenSSL error: %s", ERR_error_string(ERR_get_error(), NULL))); @@ -180,18 +191,19 @@ AesGcmEncrypt(const unsigned char *key, const unsigned char *iv, int iv_len, con } bool -AesGcmDecrypt(const unsigned char *key, const unsigned char *iv, int iv_len, const unsigned char *aad, int aad_len, const unsigned char *in, int in_len, unsigned char *out, unsigned char *tag, int tag_len) +AesGcmDecrypt(const unsigned char *key, int key_len, const unsigned char *iv, int iv_len, const unsigned char *aad, int aad_len, const unsigned char *in, int in_len, unsigned char *out, unsigned char *tag, int tag_len) { int out_len; int out_len_final; EVP_CIPHER_CTX *ctx; + const EVP_CIPHER *cipher = key_len == 32 ? cipher_gcm_256 : cipher_gcm; - Assert(in_len % EVP_CIPHER_block_size(cipher_gcm) == 0); + Assert(in_len % EVP_CIPHER_block_size(cipher) == 0); ctx = EVP_CIPHER_CTX_new(); EVP_CIPHER_CTX_init(ctx); - if (EVP_DecryptInit_ex(ctx, cipher_gcm, NULL, NULL, NULL) == 0) + if (EVP_DecryptInit_ex(ctx, cipher, NULL, NULL, NULL) == 0) ereport(ERROR, errmsg("EVP_EncryptInit_ex failed. OpenSSL error: %s", ERR_error_string(ERR_get_error(), NULL))); @@ -243,7 +255,7 @@ AesGcmDecrypt(const unsigned char *key, const unsigned char *iv, int iv_len, con * This function assumes that the out buffer is big enough: at least (blockNumber2 - blockNumber1) * 16 bytes */ void -AesCtrEncryptedZeroBlocks(void *ctxPtr, const unsigned char *key, const char *iv_prefix, uint64_t blockNumber1, uint64_t blockNumber2, unsigned char *out) +AesCtrEncryptedZeroBlocks(void *ctxPtr, const unsigned char *key, int key_len, const char *iv_prefix, uint64_t blockNumber1, uint64_t blockNumber2, unsigned char *out) { unsigned char *p; @@ -265,5 +277,5 @@ AesCtrEncryptedZeroBlocks(void *ctxPtr, const unsigned char *key, const char *iv p += sizeof(j); } - AesEcbEncrypt(ctxPtr, key, out, p - out, out); + AesEcbEncrypt(ctxPtr, key, key_len, out, p - out, out); } diff --git a/contrib/pg_tde/src/encryption/enc_tde.c b/contrib/pg_tde/src/encryption/enc_tde.c index bceaaf7cf8b52..f5efcfc0ebff9 100644 --- a/contrib/pg_tde/src/encryption/enc_tde.c +++ b/contrib/pg_tde/src/encryption/enc_tde.c @@ -27,9 +27,9 @@ iv_prefix_debug(const char *iv_prefix, char *out_hex) #endif void -pg_tde_generate_internal_key(InternalKey *int_key) +pg_tde_generate_internal_key(InternalKey *int_key, int key_len) { - if (!RAND_bytes(int_key->key, INTERNAL_KEY_LEN)) + if (!RAND_bytes(int_key->key, key_len)) ereport(ERROR, errcode(ERRCODE_INTERNAL_ERROR), errmsg("could not generate internal key: %s", @@ -39,6 +39,8 @@ pg_tde_generate_internal_key(InternalKey *int_key) errcode(ERRCODE_INTERNAL_ERROR), errmsg("could not generate IV: %s", ERR_error_string(ERR_get_error(), NULL))); + + int_key->key_len = key_len; } /* @@ -53,6 +55,7 @@ pg_tde_stream_crypt(const char *iv_prefix, uint32 data_len, char *out, const uint8 *key, + int key_len, void **ctxPtr) { const uint64 aes_start_block = start_offset / AES_BLOCK_SIZE; @@ -68,7 +71,7 @@ pg_tde_stream_crypt(const char *iv_prefix, uint32 current_batch_bytes; uint64 batch_end_block = Min(batch_start_block + NUM_AES_BLOCKS_IN_BATCH, aes_end_block); - AesCtrEncryptedZeroBlocks(ctxPtr, key, iv_prefix, batch_start_block, batch_end_block, enc_key); + AesCtrEncryptedZeroBlocks(ctxPtr, key, key_len, iv_prefix, batch_start_block, batch_end_block, enc_key); #ifdef ENCRYPTION_DEBUG { diff --git a/contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h b/contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h index 047d76414e962..dec9aac8b05e3 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h +++ b/contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h @@ -75,7 +75,7 @@ typedef struct WALKeyCacheRec } WALKeyCacheRec; extern int pg_tde_count_wal_ranges_in_file(void); -extern void pg_tde_create_wal_range(WalEncryptionRange *range, WalEncryptionRangeType type); +extern void pg_tde_create_wal_range(WalEncryptionRange *range, WalEncryptionRangeType type, int key_len); extern void pg_tde_delete_server_key(void); extern WALKeyCacheRec *pg_tde_fetch_wal_keys(WalLocation start); extern void pg_tde_free_wal_key_cache(void); diff --git a/contrib/pg_tde/src/include/access/pg_tde_xlog_smgr.h b/contrib/pg_tde/src/include/access/pg_tde_xlog_smgr.h index 8d5ec4bc08b42..3ff29d0957811 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_xlog_smgr.h +++ b/contrib/pg_tde/src/include/access/pg_tde_xlog_smgr.h @@ -12,7 +12,7 @@ extern Size TDEXLogEncryptStateSize(void); extern void TDEXLogShmemInit(void); extern void TDEXLogSmgrInit(void); -extern void TDEXLogSmgrInitWrite(bool encrypt_xlog); +extern void TDEXLogSmgrInitWrite(bool encrypt_xlog, int key_len); extern void TDEXLogSmgrInitWriteOldKeys(void); extern void TDEXLogCryptBuffer(const void *buf, void *out_buf, size_t count, off_t offset, diff --git a/contrib/pg_tde/src/include/encryption/enc_aes.h b/contrib/pg_tde/src/include/encryption/enc_aes.h index 68b9416972fd6..ecaf4b82b90b8 100644 --- a/contrib/pg_tde/src/include/encryption/enc_aes.h +++ b/contrib/pg_tde/src/include/encryption/enc_aes.h @@ -8,10 +8,10 @@ #include extern void AesInit(void); -extern void AesEncrypt(const unsigned char *key, const unsigned char *iv, const unsigned char *in, int in_len, unsigned char *out); -extern void AesDecrypt(const unsigned char *key, const unsigned char *iv, const unsigned char *in, int in_len, unsigned char *out); -extern void AesGcmEncrypt(const unsigned char *key, const unsigned char *iv, int iv_len, const unsigned char *aad, int aad_len, const unsigned char *in, int in_len, unsigned char *out, unsigned char *tag, int tag_len); -extern bool AesGcmDecrypt(const unsigned char *key, const unsigned char *iv, int iv_len, const unsigned char *aad, int aad_len, const unsigned char *in, int in_len, unsigned char *out, unsigned char *tag, int tag_len); -extern void AesCtrEncryptedZeroBlocks(void *ctxPtr, const unsigned char *key, const char *iv_prefix, uint64_t blockNumber1, uint64_t blockNumber2, unsigned char *out); +extern void AesEncrypt(const unsigned char *key, int key_len, const unsigned char *iv, const unsigned char *in, int in_len, unsigned char *out); +extern void AesDecrypt(const unsigned char *key, int key_len, const unsigned char *iv, const unsigned char *in, int in_len, unsigned char *out); +extern void AesGcmEncrypt(const unsigned char *key, int key_len, const unsigned char *iv, int iv_len, const unsigned char *aad, int aad_len, const unsigned char *in, int in_len, unsigned char *out, unsigned char *tag, int tag_len); +extern bool AesGcmDecrypt(const unsigned char *key, int key_len, const unsigned char *iv, int iv_len, const unsigned char *aad, int aad_len, const unsigned char *in, int in_len, unsigned char *out, unsigned char *tag, int tag_len); +extern void AesCtrEncryptedZeroBlocks(void *ctxPtr, const unsigned char *key, int key_len, const char *iv_prefix, uint64_t blockNumber1, uint64_t blockNumber2, unsigned char *out); #endif /* ENC_AES_H */ diff --git a/contrib/pg_tde/src/include/encryption/enc_tde.h b/contrib/pg_tde/src/include/encryption/enc_tde.h index 64299c85e33f5..2cfc271e704b5 100644 --- a/contrib/pg_tde/src/include/encryption/enc_tde.h +++ b/contrib/pg_tde/src/include/encryption/enc_tde.h @@ -5,22 +5,25 @@ #ifndef ENC_TDE_H #define ENC_TDE_H -#define INTERNAL_KEY_LEN 16 +#define INTERNAL_KEY_OLD_LEN 16 /* Old max size of an Internal Key - for compatibility */ +#define INTERNAL_KEY_MAX_LEN 32 /* Max size of an Internal Key */ #define INTERNAL_KEY_IV_LEN 16 typedef struct InternalKey { - uint8 key[INTERNAL_KEY_LEN]; + uint32 key_len; uint8 base_iv[INTERNAL_KEY_IV_LEN]; + uint8 key[INTERNAL_KEY_MAX_LEN]; } InternalKey; -extern void pg_tde_generate_internal_key(InternalKey *int_key); +extern void pg_tde_generate_internal_key(InternalKey *int_key, int key_len); extern void pg_tde_stream_crypt(const char *iv_prefix, uint32 start_offset, const char *data, uint32 data_len, char *out, const uint8 *key, + int key_len, void **ctxPtr); #endif /* ENC_TDE_H */ diff --git a/contrib/pg_tde/src/include/pg_tde_guc.h b/contrib/pg_tde/src/include/pg_tde_guc.h index c4ce064402d6d..837f4b1767e44 100644 --- a/contrib/pg_tde/src/include/pg_tde_guc.h +++ b/contrib/pg_tde/src/include/pg_tde_guc.h @@ -10,6 +10,14 @@ extern bool AllowInheritGlobalProviders; extern bool EncryptXLog; extern bool EnforceEncryption; +extern int Cipher; +extern int TdeKeyLength; + +typedef enum CipherOption +{ + TDE_CIPHER_AES_128, + TDE_CIPHER_AES_256, +} CipherOption; extern void TdeGucInit(void); diff --git a/contrib/pg_tde/src/keyring/keyring_api.c b/contrib/pg_tde/src/keyring/keyring_api.c index 11da816681f4d..6ef6d5fd2e712 100644 --- a/contrib/pg_tde/src/keyring/keyring_api.c +++ b/contrib/pg_tde/src/keyring/keyring_api.c @@ -142,8 +142,8 @@ ValidateKey(KeyInfo *key) return false; } - /* For now we only support 128-bit keys */ - if (key->data.len != KEY_DATA_SIZE_128) + /* For now we only support 128 and 256-bit keys */ + if (key->data.len != KEY_DATA_SIZE_128 && key->data.len != KEY_DATA_SIZE_256) { ereport(WARNING, errmsg("invalid key: unsupported key length \"%u\"", key->data.len)); diff --git a/contrib/pg_tde/src/pg_tde.c b/contrib/pg_tde/src/pg_tde.c index e6f32f099bbe0..afc9296eba24a 100644 --- a/contrib/pg_tde/src/pg_tde.c +++ b/contrib/pg_tde/src/pg_tde.c @@ -70,7 +70,7 @@ tde_shmem_startup(void) PrincipalKeyShmemInit(); TDEXLogShmemInit(); TDEXLogSmgrInit(); - TDEXLogSmgrInitWrite(EncryptXLog); + TDEXLogSmgrInitWrite(EncryptXLog, TdeKeyLength); LWLockRelease(AddinShmemInitLock); } diff --git a/contrib/pg_tde/src/pg_tde_guc.c b/contrib/pg_tde/src/pg_tde_guc.c index 912385cc0df75..1c8392ef75b4b 100644 --- a/contrib/pg_tde/src/pg_tde_guc.c +++ b/contrib/pg_tde/src/pg_tde_guc.c @@ -6,11 +6,36 @@ #include "utils/guc.h" +#include "keyring/keyring_api.h" #include "pg_tde_guc.h" bool AllowInheritGlobalProviders = true; bool EncryptXLog = false; bool EnforceEncryption = false; +int Cipher = TDE_CIPHER_AES_128; +int TdeKeyLength = KEY_DATA_SIZE_128; + +/* Custom GUC variable */ +static const struct config_enum_entry cipher_options[] = { + {"aes_128", TDE_CIPHER_AES_128, false}, + {"aes_256", TDE_CIPHER_AES_256, false}, +}; + +static void +assign_keys_size(int newval, void *extra) +{ + switch (newval) + { + case TDE_CIPHER_AES_128: + TdeKeyLength = KEY_DATA_SIZE_128; + break; + case TDE_CIPHER_AES_256: + TdeKeyLength = KEY_DATA_SIZE_256; + break; + default: + Assert(false); + } +} void TdeGucInit(void) @@ -51,4 +76,17 @@ TdeGucInit(void) NULL /* show_hook */ ); + DefineCustomEnumVariable("pg_tde.cipher", /* name */ + "TDE encryption algorithm.", /* short_desc */ + NULL, /* long_desc */ + &Cipher, /* value address */ + TDE_CIPHER_AES_128, /* boot value */ + cipher_options, /* options */ + PGC_SUSET, /* context */ + 0, /* flags */ + NULL, /* check_hook */ + assign_keys_size, /* assign_hook */ + NULL /* show_hook */ + ); + } diff --git a/contrib/pg_tde/src/smgr/pg_tde_smgr.c b/contrib/pg_tde/src/smgr/pg_tde_smgr.c index 1f55a57b6e937..46aa9131b2341 100644 --- a/contrib/pg_tde/src/smgr/pg_tde_smgr.c +++ b/contrib/pg_tde/src/smgr/pg_tde_smgr.c @@ -11,6 +11,7 @@ #include "encryption/enc_aes.h" #include "encryption/enc_tde.h" #include "pg_tde_event_capture.h" +#include "pg_tde_guc.h" #include "smgr/pg_tde_smgr.h" typedef enum TDEMgrRelationEncryptionStatus @@ -95,7 +96,7 @@ tde_smgr_create_key(const RelFileLocatorBackend *smgr_rlocator) { InternalKey *key = palloc_object(InternalKey); - pg_tde_generate_internal_key(key); + pg_tde_generate_internal_key(key, TdeKeyLength); if (RelFileLocatorBackendIsTemp(*smgr_rlocator)) tde_smgr_save_temp_key(&smgr_rlocator->locator, key); @@ -113,7 +114,7 @@ tde_smgr_create_key_redo(const RelFileLocator *rlocator) { InternalKey key; - pg_tde_generate_internal_key(&key); + pg_tde_generate_internal_key(&key, TdeKeyLength); pg_tde_save_smgr_key(*rlocator, &key); } @@ -234,7 +235,7 @@ tde_mdwritev(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, CalcBlockIv(forknum, bn, tdereln->relKey.base_iv, iv); - AesEncrypt(tdereln->relKey.key, iv, ((unsigned char **) buffers)[i], BLCKSZ, local_buffers[i]); + AesEncrypt(tdereln->relKey.key, tdereln->relKey.key_len, iv, ((unsigned char **) buffers)[i], BLCKSZ, local_buffers[i]); } mdwritev(reln, forknum, blocknum, @@ -298,7 +299,7 @@ tde_mdextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, CalcBlockIv(forknum, blocknum, tdereln->relKey.base_iv, iv); - AesEncrypt(tdereln->relKey.key, iv, ((unsigned char *) buffer), BLCKSZ, local_blocks); + AesEncrypt(tdereln->relKey.key, tdereln->relKey.key_len, iv, ((unsigned char *) buffer), BLCKSZ, local_blocks); mdextend(reln, forknum, blocknum, local_blocks, skipFsync); @@ -353,7 +354,7 @@ tde_mdreadv(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, CalcBlockIv(forknum, bn, tdereln->relKey.base_iv, iv); - AesDecrypt(tdereln->relKey.key, iv, ((unsigned char **) buffers)[i], BLCKSZ, ((unsigned char **) buffers)[i]); + AesDecrypt(tdereln->relKey.key, tdereln->relKey.key_len, iv, ((unsigned char **) buffers)[i], BLCKSZ, ((unsigned char **) buffers)[i]); } } diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c index e45d09f7151ea..8504f1d8b8e30 100644 --- a/src/bin/pg_basebackup/pg_basebackup.c +++ b/src/bin/pg_basebackup/pg_basebackup.c @@ -687,7 +687,7 @@ StartLogStreamer(char *startpos, uint32 timeline, char *sysidentifier, exit(1); } pg_tde_save_server_key(principalKey, false); - TDEXLogSmgrInitWrite(true); + TDEXLogSmgrInitWrite(true, 16); } #endif diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c index 3cebcb6adabe2..3e5fa01c93b9c 100644 --- a/src/bin/pg_resetwal/pg_resetwal.c +++ b/src/bin/pg_resetwal/pg_resetwal.c @@ -521,7 +521,7 @@ main(int argc, char *argv[]) * We are doing a write initialization only here and not at the startup because we * want to be sure that everything is checked and ready for writing at this point. */ - TDEXLogSmgrInitWrite(false); + TDEXLogSmgrInitWrite(false, 16); #endif /* From 55229292b7dd914060f8702fd66d02fc26f90488 Mon Sep 17 00:00:00 2001 From: Andrew Pogrebnoy Date: Tue, 30 Sep 2025 18:50:14 +0300 Subject: [PATCH 2/3] Update (rewrite) the old wal_keys file format It does make sense to rewrite old files on server start (otherwise it'll slow down random read/write). But reading all _keys files and checking the magic number could slow the server start, especially if there are a lot of databases with encrypted tables. Moreover, we'd rewrite files once but would have to scan and read them on every start... That's why this commit introduces new suffixes to the filenames in the new format. That way, we would only scan the dir and read file names, instead of opening and reading each _keys file. --- contrib/pg_tde/src/access/pg_tde_xlog_keys.c | 188 +++++++++++++++++- .../src/include/access/pg_tde_xlog_keys.h | 1 + contrib/pg_tde/src/pg_tde.c | 1 + 3 files changed, 185 insertions(+), 5 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_keys.c b/contrib/pg_tde/src/access/pg_tde_xlog_keys.c index 72dffc764c648..b31538b5a19d3 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_keys.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_keys.c @@ -23,8 +23,10 @@ #endif #define PG_TDE_WAL_KEY_FILE_MAGIC_OLD 0x014B4557 /* old version ID value = WEK 01 */ +#define PG_TDE_WAL_KEY_FILE_NAME_OLD "wal_keys" + #define PG_TDE_WAL_KEY_FILE_MAGIC 0x024B4557 /* version ID value = WEK 02 */ -#define PG_TDE_WAL_KEY_FILE_NAME "wal_keys" +#define PG_TDE_WAL_KEY_FILE_NAME "wal_keys_v2" typedef struct WalKeyFileHeader { @@ -844,11 +846,34 @@ pg_tde_get_server_key_info(void) */ fd = pg_tde_open_wal_key_file_basic(get_wal_key_file_path(), O_RDONLY, true); - /* The file does not exist. */ - if (fd < 0) - return NULL; + if (fd >= 0) + pg_tde_wal_key_file_header_read(get_wal_key_file_path(), fd, &fheader, &bytes_read); + else + { + /* + * TODO: An ugly hack for now, we need to get a key info when rewriting + * an old file... + */ + char old_wal_key_file_path[MAXPGPATH] = {0}; + + join_path_components(old_wal_key_file_path, pg_tde_get_data_dir(), PG_TDE_WAL_KEY_FILE_NAME_OLD); + + fd = pg_tde_open_wal_key_file_basic(old_wal_key_file_path, O_RDONLY, true); - pg_tde_wal_key_file_header_read(get_wal_key_file_path(), fd, &fheader, &bytes_read); + /* The file does not exist */ + if (fd < 0) + return NULL; + + bytes_read = pg_pread(fd, &fheader, sizeof(WalKeyFileHeader), 0); + + if (bytes_read > 0 && (bytes_read != sizeof(WalKeyFileHeader) + || fheader.file_version != PG_TDE_WAL_KEY_FILE_MAGIC_OLD)) + { + ereport(FATAL, + errcode_for_file_access(), + errmsg("old WAL key file \"%s\" is corrupted: %m", old_wal_key_file_path)); + } + } CloseTransientFile(fd); @@ -904,4 +929,157 @@ pg_tde_delete_server_key(void) /* Remove whole key map file */ durable_unlink(get_wal_key_file_path(), ERROR); } + +/* + * Functions for rewriting old wal_keys into a new format file. + * + * TODO: The old format should be deprecated. And this code should be removed + * eventually. + */ +static int +pg_tde_open_old_wal_key_file_read(const char *filename, + bool ignore_missing, + off_t *curr_pos) +{ + int fd; + WalKeyFileHeader fheader; + off_t bytes_read = 0; + + Assert(LWLockHeldByMeInMode(tde_lwlock_enc_keys(), LW_SHARED) || + LWLockHeldByMeInMode(tde_lwlock_enc_keys(), LW_EXCLUSIVE)); + + fd = pg_tde_open_wal_key_file_basic(filename, O_RDONLY | PG_BINARY, ignore_missing); + if (ignore_missing && fd < 0) + return fd; + + bytes_read = pg_pread(fd, &fheader, sizeof(WalKeyFileHeader), 0); + + /* File is empty */ + if (bytes_read == 0) + return fd; + + if (bytes_read != sizeof(WalKeyFileHeader) + || fheader.file_version != PG_TDE_WAL_KEY_FILE_MAGIC_OLD) + { + ereport(FATAL, + errcode_for_file_access(), + errmsg("old WAL key file \"%s\" is corrupted: %m", filename)); + } + *curr_pos = bytes_read; + + return fd; +} + +static bool +pg_tde_read_one_wal_key_file_old_entry(int fd, + WalKeyFileEntryOld *entry, + off_t *offset) +{ + off_t bytes_read = 0; + + Assert(entry); + Assert(offset); + + bytes_read = pg_pread(fd, entry, sizeof(WalKeyFileEntryOld), *offset); + + /* We've reached the end of the file. */ + if (bytes_read != sizeof(WalKeyFileEntryOld)) + return false; + + *offset += bytes_read; + + return true; +} + +static WalEncryptionRange * +pg_tde_wal_range_from_old_entry(const TDEPrincipalKey *principal_key, WalKeyFileEntryOld *entry) +{ + WalEncryptionRange *range = tde_wal_prealloc_range == NULL ? palloc0_object(WalEncryptionRange) : tde_wal_prealloc_range; + + tde_wal_prealloc_range = NULL; + + Assert(principal_key); + + range->type = entry->range_type; + range->start = entry->range_start; + range->end.tli = MaxTimeLineID; + range->end.lsn = MaxXLogRecPtr; + range->key.key_len = INTERNAL_KEY_OLD_LEN; + + memcpy(range->key.base_iv, entry->key_base_iv, INTERNAL_KEY_IV_LEN); + if (!AesGcmDecrypt(principal_key->keyData, principal_key->keyLength, + entry->entry_iv, MAP_ENTRY_IV_SIZE, + (unsigned char *) entry, offsetof(WalKeyFileEntryOld, encrypted_key_data), + entry->encrypted_key_data, INTERNAL_KEY_OLD_LEN, + range->key.key, + entry->aead_tag, MAP_ENTRY_AEAD_TAG_SIZE)) + ereport(ERROR, + errmsg("Failed to decrypt key, incorrect principal key or corrupted key file %u", principal_key->keyLength)); + + return range; +} + +void +pg_tde_update_wal_keys_file(void) +{ + LWLock *lock_pk = tde_lwlock_enc_keys(); + TDEPrincipalKey *principal_key; + TDESignedPrincipalKeyInfo signed_key_info; + char wal_key_file_path[MAXPGPATH] = {0}; + off_t read_pos, + write_pos; + int old_fd, + new_fd; + + join_path_components(wal_key_file_path, pg_tde_get_data_dir(), PG_TDE_WAL_KEY_FILE_NAME_OLD); + + /* Check if there is anything to do */ + if (access(wal_key_file_path, F_OK) != 0) + return; + + /* + * No real need in lock here as the func should be called only on the server + * start, but GetPrincipalKey() expects one. + */ + LWLockAcquire(lock_pk, LW_EXCLUSIVE); + + + old_fd = pg_tde_open_old_wal_key_file_read(wal_key_file_path, false, &read_pos); + + /* + * The old file exists and it's not empty, hece a principal key + * should exist as well. + */ + principal_key = GetPrincipalKey(GLOBAL_DATA_TDE_OID, LW_EXCLUSIVE); + if (principal_key == NULL) + { + ereport(ERROR, + errmsg("could not get server principal key"), + errdetail("Failed to updated format of WAL keys.")); + } + pg_tde_sign_principal_key_info(&signed_key_info, principal_key); + + new_fd = pg_tde_open_wal_key_file_write(get_wal_key_file_path(), &signed_key_info, true, &write_pos); + + while (1) + { + WalKeyFileEntryOld old_entry; + WalKeyFileEntry new_entry; + WalEncryptionRange *range; + + if (!pg_tde_read_one_wal_key_file_old_entry(old_fd, &old_entry, &read_pos)) + break; + + range = pg_tde_wal_range_from_old_entry(principal_key, &old_entry); + pg_tde_initialize_wal_key_file_entry(&new_entry, principal_key, range); + pg_tde_write_one_wal_key_file_entry(new_fd, &new_entry, &write_pos, get_wal_key_file_path()); + pfree(range); + } + + CloseTransientFile(old_fd); + CloseTransientFile(new_fd); + durable_unlink(wal_key_file_path, ERROR); + + LWLockRelease(lock_pk); +} #endif diff --git a/contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h b/contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h index dec9aac8b05e3..d80dd8299a0ec 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h +++ b/contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h @@ -88,5 +88,6 @@ extern void pg_tde_save_server_key(const TDEPrincipalKey *principal_key, bool wr extern void pg_tde_save_server_key_redo(const TDESignedPrincipalKeyInfo *signed_key_info); extern void pg_tde_wal_last_range_set_location(WalLocation loc); extern void pg_tde_wal_cache_extra_palloc(void); +extern void pg_tde_update_wal_keys_file(void); #endif /* PG_TDE_XLOG_KEYS_H */ diff --git a/contrib/pg_tde/src/pg_tde.c b/contrib/pg_tde/src/pg_tde.c index afc9296eba24a..e7083e0a4a53b 100644 --- a/contrib/pg_tde/src/pg_tde.c +++ b/contrib/pg_tde/src/pg_tde.c @@ -70,6 +70,7 @@ tde_shmem_startup(void) PrincipalKeyShmemInit(); TDEXLogShmemInit(); TDEXLogSmgrInit(); + pg_tde_update_wal_keys_file(); TDEXLogSmgrInitWrite(EncryptXLog, TdeKeyLength); LWLockRelease(AddinShmemInitLock); From 1ebc59ae8bd79cb477a1e319b2a278386256d920 Mon Sep 17 00:00:00 2001 From: Andrew Pogrebnoy Date: Mon, 13 Oct 2025 13:19:29 +0300 Subject: [PATCH 3/3] Add support for 32 byte keys in smgr Including migration --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 260 ++++++++++++++++-- .../pg_tde/src/include/access/pg_tde_tdemap.h | 2 + contrib/pg_tde/src/pg_tde.c | 1 + 3 files changed, 246 insertions(+), 17 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index c1800ee6561b0..77159f535bf6e 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -39,8 +39,11 @@ } #endif -#define PG_TDE_FILEMAGIC 0x03454454 /* version ID value = TDE 03 */ -#define PG_TDE_MAP_FILENAME "%d_keys" +#define PG_TDE_FILEMAGIC_OLD 0x03454454 /* old version ID value = TDE 03 */ +#define PG_TDE_MAP_OLD_FNAME_SUFFIX "_keys" + +#define PG_TDE_FILEMAGIC 0x04454454 /* version ID value = TDE 04 */ +#define PG_TDE_MAP_FILENAME "%d_keys_v2" /* TODO: should be _v2 of _v4 ? */ typedef enum { @@ -63,14 +66,14 @@ typedef struct TDEFileHeader * encrypting/decrypting existing keys from the key files, so any changes here * might break existing clusters. */ -typedef struct TDEMapEntry +typedef struct TDEMapEntryOld { Oid spcOid; /* Part of AAD */ RelFileNumber relNumber; /* Part of AAD */ uint32 type; /* Part of AAD */ uint32 _unused3; /* Part of AAD */ - uint8 encrypted_key_data[INTERNAL_KEY_MAX_LEN]; + uint8 encrypted_key_data[INTERNAL_KEY_OLD_LEN]; uint8 key_base_iv[INTERNAL_KEY_IV_LEN]; uint32 _unused1; /* Will be 1 in existing files entries. */ @@ -80,6 +83,21 @@ typedef struct TDEMapEntry /* IV and tag used when encrypting the key itself */ unsigned char entry_iv[MAP_ENTRY_IV_SIZE]; unsigned char aead_tag[MAP_ENTRY_AEAD_TAG_SIZE]; +} TDEMapEntryOld; + +typedef struct TDEMapEntry +{ + uint32 key_len; /* Part of AAD */ + Oid spcOid; /* Part of AAD */ + RelFileNumber relNumber; /* Part of AAD */ + uint32 type; /* Part of AAD */ + + /* IV and tag used when encrypting the key itself */ + unsigned char entry_iv[MAP_ENTRY_IV_SIZE]; + unsigned char aead_tag[MAP_ENTRY_AEAD_TAG_SIZE]; + + uint8 key_base_iv[INTERNAL_KEY_IV_LEN]; + uint8 encrypted_key_data[INTERNAL_KEY_MAX_LEN]; } TDEMapEntry; static void pg_tde_set_db_file_path(Oid dbOid, char *path); @@ -397,12 +415,7 @@ pg_tde_initialize_map_entry(TDEMapEntry *map_entry, const TDEPrincipalKey *princ map_entry->type = MAP_ENTRY_TYPE_KEY; memcpy(map_entry->key_base_iv, rel_key_data->base_iv, INTERNAL_KEY_IV_LEN); - /* - * We set these fields here so that existing file entries will be - * consistent and future use of these fields easier. - */ - map_entry->_unused1 = 1; - map_entry->_unused2 = 0; + map_entry->key_len = rel_key_data->key_len; if (!RAND_bytes(map_entry->entry_iv, MAP_ENTRY_IV_SIZE)) ereport(ERROR, @@ -411,8 +424,8 @@ pg_tde_initialize_map_entry(TDEMapEntry *map_entry, const TDEPrincipalKey *princ AesGcmEncrypt(principal_key->keyData, principal_key->keyLength, map_entry->entry_iv, MAP_ENTRY_IV_SIZE, - (unsigned char *) map_entry, offsetof(TDEMapEntry, encrypted_key_data), - rel_key_data->key, INTERNAL_KEY_MAX_LEN, + (unsigned char *) map_entry, offsetof(TDEMapEntry, entry_iv), + rel_key_data->key, rel_key_data->key_len, map_entry->encrypted_key_data, map_entry->aead_tag, MAP_ENTRY_AEAD_TAG_SIZE); } @@ -590,14 +603,15 @@ tde_decrypt_rel_key(const TDEPrincipalKey *principal_key, TDEMapEntry *map_entry if (!AesGcmDecrypt(principal_key->keyData, principal_key->keyLength, map_entry->entry_iv, MAP_ENTRY_IV_SIZE, - (unsigned char *) map_entry, offsetof(TDEMapEntry, encrypted_key_data), - map_entry->encrypted_key_data, INTERNAL_KEY_MAX_LEN, + (unsigned char *) map_entry, offsetof(TDEMapEntry, entry_iv), + map_entry->encrypted_key_data, map_entry->key_len, key->key, map_entry->aead_tag, MAP_ENTRY_AEAD_TAG_SIZE)) ereport(ERROR, errmsg("Failed to decrypt key, incorrect principal key or corrupted key file")); memcpy(key->base_iv, map_entry->key_base_iv, INTERNAL_KEY_IV_LEN); + key->key_len = map_entry->key_len; return key; } @@ -785,10 +799,36 @@ pg_tde_get_principal_key_info(Oid dbOid) fd = pg_tde_open_file_basic(db_map_path, O_RDONLY, true); /* The file does not exist. */ - if (fd < 0) - return NULL; + if (fd >= 0) + pg_tde_file_header_read(db_map_path, fd, &fheader, &bytes_read); + else + { + /* + * TODO: An ugly hack for now, we need to get a key info when rewriting + * an old file... + */ + char old_key_file_path[MAXPGPATH] = {0}; + char *fname = psprintf("%d"PG_TDE_MAP_OLD_FNAME_SUFFIX, dbOid); + + join_path_components(old_key_file_path, pg_tde_get_data_dir(), fname); + pfree(fname); - pg_tde_file_header_read(db_map_path, fd, &fheader, &bytes_read); + fd = pg_tde_open_file_basic(old_key_file_path, O_RDONLY, true); + + /* The file does not exist */ + if (fd < 0) + return NULL; + + bytes_read = pg_pread(fd, &fheader, sizeof(TDEFileHeader), 0); + + if (bytes_read > 0 && (bytes_read != sizeof(TDEFileHeader) + || fheader.file_version != PG_TDE_FILEMAGIC_OLD)) + { + ereport(FATAL, + errcode_for_file_access(), + errmsg("old smgr key file \"%s\" is corrupted: %m", old_key_file_path)); + } + } CloseTransientFile(fd); @@ -881,3 +921,189 @@ pg_tde_get_smgr_key(RelFileLocator rel) return rel_key; } + +#ifndef FRONTEND + +/* + * Functions for rewriting old smgr _key into a new format file. + * + * TODO: The old format should be deprecated. And this code should be removed + * eventually. + */ + +static int +pg_tde_open_old_file_read(const char *tde_filename, bool ignore_missing, off_t *curr_pos) +{ + int fd; + TDEFileHeader fheader; + off_t bytes_read = 0; + + Assert(LWLockHeldByMeInMode(tde_lwlock_enc_keys(), LW_SHARED) || + LWLockHeldByMeInMode(tde_lwlock_enc_keys(), LW_EXCLUSIVE)); + + fd = pg_tde_open_file_basic(tde_filename, O_RDONLY | PG_BINARY, ignore_missing); + if (ignore_missing && fd < 0) + return fd; + + bytes_read = pg_pread(fd, &fheader, sizeof(TDEFileHeader), 0); + + /* File is empty */ + if (bytes_read == 0) + return fd; + + if (bytes_read != sizeof(TDEFileHeader) + || fheader.file_version != PG_TDE_FILEMAGIC_OLD) + { + ereport(FATAL, + errcode_for_file_access(), + errmsg("old TDE map file \"%s\" is corrupted: %m", tde_filename)); + } + + *curr_pos = bytes_read; + + return fd; +} + +static bool +pg_tde_read_one_old_map_entry(int map_file, TDEMapEntryOld *map_entry, off_t *offset) +{ + off_t bytes_read = 0; + + Assert(map_entry); + Assert(offset); + + bytes_read = pg_pread(map_file, map_entry, sizeof(TDEMapEntryOld), *offset); + + /* We've reached the end of the file. */ + if (bytes_read != sizeof(TDEMapEntryOld)) + return false; + + *offset += bytes_read; + + return true; +} + +static InternalKey * +tde_decrypt_old_rel_key(const TDEPrincipalKey *principal_key, TDEMapEntryOld *map_entry) +{ + InternalKey *key = palloc_object(InternalKey); + + Assert(principal_key); + + if (!AesGcmDecrypt(principal_key->keyData, principal_key->keyLength, + map_entry->entry_iv, MAP_ENTRY_IV_SIZE, + (unsigned char *) map_entry, offsetof(TDEMapEntryOld, encrypted_key_data), + map_entry->encrypted_key_data, INTERNAL_KEY_OLD_LEN, + key->key, + map_entry->aead_tag, MAP_ENTRY_AEAD_TAG_SIZE)) + ereport(ERROR, + errmsg("Failed to decrypt key, incorrect principal key or corrupted key file")); + + memcpy(key->base_iv, map_entry->key_base_iv, INTERNAL_KEY_IV_LEN); + key->key_len = INTERNAL_KEY_OLD_LEN; + + return key; +} + +void +pg_tde_migrate_smgr_keys_file(void) +{ + DIR *dir; + LWLock *lock_pk = tde_lwlock_enc_keys(); + struct dirent *file; + TDEPrincipalKey *principal_key = NULL; + TDESignedPrincipalKeyInfo signed_key_info; + int old_suffix_len = strlen(PG_TDE_MAP_OLD_FNAME_SUFFIX); + + /* + * No real need in lock here as the func should be called only on the server + * start, but GetPrincipalKey() expects lock. + */ + LWLockAcquire(lock_pk, LW_EXCLUSIVE); + + dir = opendir(pg_tde_get_data_dir()); + if (dir == NULL && errno != ENOENT) + elog(ERROR, "could not open directory \"%s\": %m", + pg_tde_get_data_dir()); + + /* + * TODO: create a "current_version_v4" file after the successfull migration + * or during pg_tde dir creatation, so no need to scan the dir all the time, + * but just test the file? + */ + while (errno = 0, (file = readdir(dir)) != NULL) + { + if (strlen(file->d_name) > old_suffix_len && + strncmp(file->d_name + strlen(file->d_name) - old_suffix_len, PG_TDE_MAP_OLD_FNAME_SUFFIX, old_suffix_len) == 0) + { + char old_db_map_path[MAXPGPATH] = {0}; + char db_map_path[MAXPGPATH] = {0}; + off_t read_pos, + write_pos; + int old_fd, + new_fd; + Oid dbOid; + char *suffix; + char *old_fname; + + + dbOid = strtoul(file->d_name, &suffix, 10); + if (strcmp(suffix, PG_TDE_MAP_OLD_FNAME_SUFFIX) != 0) + continue; + + old_fname = psprintf("%d"PG_TDE_MAP_OLD_FNAME_SUFFIX, dbOid); + join_path_components(old_db_map_path, pg_tde_get_data_dir(), old_fname); + pfree(old_fname); + + old_fd = pg_tde_open_old_file_read(old_db_map_path, false, &read_pos); + + pg_tde_set_db_file_path(dbOid, db_map_path); + /* + * The old file exists and it's not empty, hece a principal key + * should exist as well. + */ + if (principal_key == NULL) + { + principal_key = GetPrincipalKey(dbOid, LW_EXCLUSIVE); + if (principal_key == NULL) + { + ereport(ERROR, + errmsg("could not get server principal key"), + errdetail("Failed to migrate the keys file of %u database.", dbOid)); + } + pg_tde_sign_principal_key_info(&signed_key_info, principal_key); + } + new_fd = pg_tde_open_file_write(db_map_path, &signed_key_info, true, &write_pos); + + while (1) + { + TDEMapEntryOld old_entry; + TDEMapEntry new_entry; + InternalKey *rel_key_data; + RelFileLocator rloc; + + if (!pg_tde_read_one_old_map_entry(old_fd, &old_entry, &read_pos)) + break; + + rloc.spcOid = old_entry.spcOid; + rloc.dbOid = dbOid; + rloc.relNumber = old_entry.relNumber; + + rel_key_data = tde_decrypt_old_rel_key(principal_key, &old_entry); + pg_tde_initialize_map_entry(&new_entry, principal_key, &rloc, rel_key_data); + pg_tde_write_one_map_entry(new_fd, &new_entry, &write_pos, db_map_path); + + pfree(rel_key_data); + } + + CloseTransientFile(old_fd); + CloseTransientFile(new_fd); + durable_unlink(old_db_map_path, ERROR); + } + } + + closedir(dir); + LWLockRelease(lock_pk); +} + +#endif \ No newline at end of file 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 40ee9eb9cbc34..263a5ed5b489a 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h +++ b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h @@ -27,4 +27,6 @@ extern void pg_tde_sign_principal_key_info(TDESignedPrincipalKeyInfo *signed_key const char *tde_sprint_key(InternalKey *k); +extern void pg_tde_migrate_smgr_keys_file(void); + #endif /* PG_TDE_MAP_H */ diff --git a/contrib/pg_tde/src/pg_tde.c b/contrib/pg_tde/src/pg_tde.c index e7083e0a4a53b..5388133bff108 100644 --- a/contrib/pg_tde/src/pg_tde.c +++ b/contrib/pg_tde/src/pg_tde.c @@ -71,6 +71,7 @@ tde_shmem_startup(void) TDEXLogShmemInit(); TDEXLogSmgrInit(); pg_tde_update_wal_keys_file(); + pg_tde_migrate_smgr_keys_file(); TDEXLogSmgrInitWrite(EncryptXLog, TdeKeyLength); LWLockRelease(AddinShmemInitLock);