From f336e4e09f3d8dda9dd55b855f3eb2cd0913436a Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Fri, 24 Apr 2026 13:12:06 +0900 Subject: [PATCH] pgcrypto: Respect builtin_crypto_enabled for PGP ciphers pgp_sym_encrypt() and pgp_pub_encrypt() silently accepted non-FIPS-approved cipher algorithms even if OpenSSL was in FIPS mode and pgcrypto.builtin_crypto_enabled was set to its 'fips' mode. This causes pgcrypto to be non-compliant. A new flag is added to the information list of ciphers, upon which a filtering is done should FIPS be enabled, depending on the builtin crypto mode. Reported-by: Shishir Sharma Suggested-by: Daniel Gustafsson Discussion: https://postgr.es/m/19457-4bab15c17aea36c7@postgresql.org Backpatch-through: 18 --- doc/src/sgml/pgcrypto.sgml | 9 +- contrib/pgcrypto/Makefile | 2 +- contrib/pgcrypto/expected/pgp-fips-cipher.out | 77 +++++++++++++++ .../pgcrypto/expected/pgp-fips-cipher_1.out | 95 +++++++++++++++++++ contrib/pgcrypto/meson.build | 3 +- contrib/pgcrypto/pgp.c | 32 +++++-- contrib/pgcrypto/sql/pgp-fips-cipher.sql | 46 +++++++++ 7 files changed, 250 insertions(+), 14 deletions(-) create mode 100644 contrib/pgcrypto/expected/pgp-fips-cipher.out create mode 100644 contrib/pgcrypto/expected/pgp-fips-cipher_1.out create mode 100644 contrib/pgcrypto/sql/pgp-fips-cipher.sql diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml index 6fc2069ad3ec..96b043097eaa 100644 --- a/doc/src/sgml/pgcrypto.sgml +++ b/doc/src/sgml/pgcrypto.sgml @@ -1236,12 +1236,17 @@ fips_mode() returns boolean pgcrypto.builtin_crypto_enabled determines if the - built in crypto functions gen_salt(), and - crypt() are available for use. Setting this to + built in crypto functions gen_salt(), + crypt(), pgp_sym_encrypt() + and pgp_pub_encrypt() are available for use. + Setting this to off disables these functions. on (the default) enables these functions to work normally. fips disables these functions if OpenSSL is detected to operate in FIPS mode. + pgp_sym_encrypt() and + pgp_pub_encrypt() are disabled for ciphers that + are not FIPS-approved. diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile index 17d2b0c5ed17..dde8933f706d 100644 --- a/contrib/pgcrypto/Makefile +++ b/contrib/pgcrypto/Makefile @@ -45,7 +45,7 @@ REGRESS = init md5 sha1 hmac-md5 hmac-sha1 blowfish rijndael \ crypt-des crypt-md5 crypt-blowfish crypt-xdes \ pgp-armor pgp-decrypt pgp-encrypt pgp-encrypt-md5 $(CF_PGP_TESTS) \ pgp-pubkey-decrypt pgp-pubkey-encrypt pgp-pubkey-session \ - pgp-info crypt-shacrypt + pgp-info crypt-shacrypt pgp-fips-cipher ifdef USE_PGXS PG_CONFIG = pg_config diff --git a/contrib/pgcrypto/expected/pgp-fips-cipher.out b/contrib/pgcrypto/expected/pgp-fips-cipher.out new file mode 100644 index 000000000000..eed6db0a6490 --- /dev/null +++ b/contrib/pgcrypto/expected/pgp-fips-cipher.out @@ -0,0 +1,77 @@ +-- +-- PGP FIPS cipher restrictions +-- +-- crypto functions disabled. All PGP encryption are blocked. +SET pgcrypto.builtin_crypto_enabled = off; +SELECT pgp_sym_encrypt('data', 'key'); +ERROR: use of built-in crypto functions is disabled +SELECT pgp_sym_encrypt('data', 'key', 'cipher-algo=aes256'); +ERROR: use of built-in crypto functions is disabled +SELECT pgp_sym_encrypt('data', 'key', 'cipher-algo=bf'); +ERROR: use of built-in crypto functions is disabled +SELECT pgp_sym_encrypt('data', 'key', 'cipher-algo=3des'); +ERROR: use of built-in crypto functions is disabled +RESET pgcrypto.builtin_crypto_enabled; +-- crypto functions enabled. All work. +SET pgcrypto.builtin_crypto_enabled = on; +SELECT pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=aes128'), + 'key', 'expect-cipher-algo=aes128'); + pgp_sym_decrypt +----------------- + Secret. +(1 row) + +SELECT pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=aes192'), + 'key', 'expect-cipher-algo=aes192'); + pgp_sym_decrypt +----------------- + Secret. +(1 row) + +SELECT pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=aes256'), + 'key', 'expect-cipher-algo=aes256'); + pgp_sym_decrypt +----------------- + Secret. +(1 row) + +SELECT pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=bf'), + 'key', 'expect-cipher-algo=bf'); + pgp_sym_decrypt +----------------- + Secret. +(1 row) + +SELECT pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=3des'), + 'key', 'expect-cipher-algo=3des'); + pgp_sym_decrypt +----------------- + Secret. +(1 row) + +SELECT pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=cast5'), + 'key', 'expect-cipher-algo=cast5'); + pgp_sym_decrypt +----------------- + Secret. +(1 row) + +RESET pgcrypto.builtin_crypto_enabled; +-- crypto functions with FIPS mode. +SELECT fips_mode() AS is_fips \gset +\if :is_fips +SET pgcrypto.builtin_crypto_enabled = fips; +-- non-AES ciphers must error +SELECT pgp_sym_encrypt('data', 'key', 'cipher-algo=bf'); +SELECT pgp_sym_encrypt('data', 'key', 'cipher-algo=3des'); +SELECT pgp_sym_encrypt('data', 'key', 'cipher-algo=cast5'); +-- AES ciphers work +SELECT pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=aes128'), + 'key', 'expect-cipher-algo=aes128'); +SELECT pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=aes256'), + 'key', 'expect-cipher-algo=aes256'); +-- AES round trip under FIPS +SELECT pgp_sym_decrypt(pgp_sym_encrypt('FIPS round trip test', 'key', + 'cipher-algo=aes256'), 'key'); +RESET pgcrypto.builtin_crypto_enabled; +\endif diff --git a/contrib/pgcrypto/expected/pgp-fips-cipher_1.out b/contrib/pgcrypto/expected/pgp-fips-cipher_1.out new file mode 100644 index 000000000000..8ba974cb4c7a --- /dev/null +++ b/contrib/pgcrypto/expected/pgp-fips-cipher_1.out @@ -0,0 +1,95 @@ +-- +-- PGP FIPS cipher restrictions +-- +-- crypto functions disabled. All PGP encryption are blocked. +SET pgcrypto.builtin_crypto_enabled = off; +SELECT pgp_sym_encrypt('data', 'key'); +ERROR: use of built-in crypto functions is disabled +SELECT pgp_sym_encrypt('data', 'key', 'cipher-algo=aes256'); +ERROR: use of built-in crypto functions is disabled +SELECT pgp_sym_encrypt('data', 'key', 'cipher-algo=bf'); +ERROR: use of built-in crypto functions is disabled +SELECT pgp_sym_encrypt('data', 'key', 'cipher-algo=3des'); +ERROR: use of built-in crypto functions is disabled +RESET pgcrypto.builtin_crypto_enabled; +-- crypto functions enabled. All work. +SET pgcrypto.builtin_crypto_enabled = on; +SELECT pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=aes128'), + 'key', 'expect-cipher-algo=aes128'); + pgp_sym_decrypt +----------------- + Secret. +(1 row) + +SELECT pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=aes192'), + 'key', 'expect-cipher-algo=aes192'); + pgp_sym_decrypt +----------------- + Secret. +(1 row) + +SELECT pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=aes256'), + 'key', 'expect-cipher-algo=aes256'); + pgp_sym_decrypt +----------------- + Secret. +(1 row) + +SELECT pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=bf'), + 'key', 'expect-cipher-algo=bf'); + pgp_sym_decrypt +----------------- + Secret. +(1 row) + +SELECT pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=3des'), + 'key', 'expect-cipher-algo=3des'); + pgp_sym_decrypt +----------------- + Secret. +(1 row) + +SELECT pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=cast5'), + 'key', 'expect-cipher-algo=cast5'); + pgp_sym_decrypt +----------------- + Secret. +(1 row) + +RESET pgcrypto.builtin_crypto_enabled; +-- crypto functions with FIPS mode. +SELECT fips_mode() AS is_fips \gset +\if :is_fips +SET pgcrypto.builtin_crypto_enabled = fips; +-- non-AES ciphers must error +SELECT pgp_sym_encrypt('data', 'key', 'cipher-algo=bf'); +ERROR: cipher bf is not FIPS approved +SELECT pgp_sym_encrypt('data', 'key', 'cipher-algo=3des'); +ERROR: cipher 3des is not FIPS approved +SELECT pgp_sym_encrypt('data', 'key', 'cipher-algo=cast5'); +ERROR: cipher cast5 is not FIPS approved +-- AES ciphers work +SELECT pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=aes128'), + 'key', 'expect-cipher-algo=aes128'); + pgp_sym_decrypt +----------------- + Secret. +(1 row) + +SELECT pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=aes256'), + 'key', 'expect-cipher-algo=aes256'); + pgp_sym_decrypt +----------------- + Secret. +(1 row) + +-- AES round trip under FIPS +SELECT pgp_sym_decrypt(pgp_sym_encrypt('FIPS round trip test', 'key', + 'cipher-algo=aes256'), 'key'); + pgp_sym_decrypt +---------------------- + FIPS round trip test +(1 row) + +RESET pgcrypto.builtin_crypto_enabled; +\endif diff --git a/contrib/pgcrypto/meson.build b/contrib/pgcrypto/meson.build index 4f255c8cb05d..f922c1fb8bdd 100644 --- a/contrib/pgcrypto/meson.build +++ b/contrib/pgcrypto/meson.build @@ -54,7 +54,8 @@ pgcrypto_regress = [ 'pgp-pubkey-encrypt', 'pgp-pubkey-session', 'pgp-info', - 'crypt-shacrypt' + 'crypt-shacrypt', + 'pgp-fips-cipher', ] pgcrypto_openssl_sources = files( diff --git a/contrib/pgcrypto/pgp.c b/contrib/pgcrypto/pgp.c index 8a6a6c2adf1f..2d5375910a9c 100644 --- a/contrib/pgcrypto/pgp.c +++ b/contrib/pgcrypto/pgp.c @@ -63,6 +63,7 @@ struct cipher_info const char *int_name; int key_len; int block_len; + bool fips_allowed; }; static const struct digest_info digest_list[] = { @@ -77,16 +78,16 @@ static const struct digest_info digest_list[] = { }; static const struct cipher_info cipher_list[] = { - {"3des", PGP_SYM_DES3, "3des-ecb", 192 / 8, 64 / 8}, - {"cast5", PGP_SYM_CAST5, "cast5-ecb", 128 / 8, 64 / 8}, - {"bf", PGP_SYM_BLOWFISH, "bf-ecb", 128 / 8, 64 / 8}, - {"blowfish", PGP_SYM_BLOWFISH, "bf-ecb", 128 / 8, 64 / 8}, - {"aes", PGP_SYM_AES_128, "aes-ecb", 128 / 8, 128 / 8}, - {"aes128", PGP_SYM_AES_128, "aes-ecb", 128 / 8, 128 / 8}, - {"aes192", PGP_SYM_AES_192, "aes-ecb", 192 / 8, 128 / 8}, - {"aes256", PGP_SYM_AES_256, "aes-ecb", 256 / 8, 128 / 8}, - {"twofish", PGP_SYM_TWOFISH, "twofish-ecb", 256 / 8, 128 / 8}, - {NULL, 0, NULL} + {"3des", PGP_SYM_DES3, "3des-ecb", 192 / 8, 64 / 8, false}, + {"cast5", PGP_SYM_CAST5, "cast5-ecb", 128 / 8, 64 / 8, false}, + {"bf", PGP_SYM_BLOWFISH, "bf-ecb", 128 / 8, 64 / 8, false}, + {"blowfish", PGP_SYM_BLOWFISH, "bf-ecb", 128 / 8, 64 / 8, false}, + {"aes", PGP_SYM_AES_128, "aes-ecb", 128 / 8, 128 / 8, true}, + {"aes128", PGP_SYM_AES_128, "aes-ecb", 128 / 8, 128 / 8, true}, + {"aes192", PGP_SYM_AES_192, "aes-ecb", 192 / 8, 128 / 8, true}, + {"aes256", PGP_SYM_AES_256, "aes-ecb", 256 / 8, 128 / 8, true}, + {"twofish", PGP_SYM_TWOFISH, "twofish-ecb", 256 / 8, 128 / 8, false}, + {NULL, 0, NULL, 0, 0, false} }; static const struct cipher_info * @@ -162,6 +163,17 @@ pgp_load_cipher(int code, PX_Cipher **res) if (i == NULL) return PXE_PGP_CORRUPT_DATA; + CheckBuiltinCryptoMode(); + + /* + * In FIPS mode, only allow ciphers that are FIPS approved. + */ + if (builtin_crypto_enabled == BC_FIPS && + CheckFIPSMode() && + !i->fips_allowed) + ereport(ERROR, + errmsg("cipher %s is not FIPS approved", i->name)); + err = px_find_cipher(i->int_name, res); if (err == 0) return 0; diff --git a/contrib/pgcrypto/sql/pgp-fips-cipher.sql b/contrib/pgcrypto/sql/pgp-fips-cipher.sql new file mode 100644 index 000000000000..cb425a9ccdf9 --- /dev/null +++ b/contrib/pgcrypto/sql/pgp-fips-cipher.sql @@ -0,0 +1,46 @@ +-- +-- PGP FIPS cipher restrictions +-- + +-- crypto functions disabled. All PGP encryption are blocked. +SET pgcrypto.builtin_crypto_enabled = off; +SELECT pgp_sym_encrypt('data', 'key'); +SELECT pgp_sym_encrypt('data', 'key', 'cipher-algo=aes256'); +SELECT pgp_sym_encrypt('data', 'key', 'cipher-algo=bf'); +SELECT pgp_sym_encrypt('data', 'key', 'cipher-algo=3des'); +RESET pgcrypto.builtin_crypto_enabled; + +-- crypto functions enabled. All work. +SET pgcrypto.builtin_crypto_enabled = on; +SELECT pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=aes128'), + 'key', 'expect-cipher-algo=aes128'); +SELECT pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=aes192'), + 'key', 'expect-cipher-algo=aes192'); +SELECT pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=aes256'), + 'key', 'expect-cipher-algo=aes256'); +SELECT pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=bf'), + 'key', 'expect-cipher-algo=bf'); +SELECT pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=3des'), + 'key', 'expect-cipher-algo=3des'); +SELECT pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=cast5'), + 'key', 'expect-cipher-algo=cast5'); +RESET pgcrypto.builtin_crypto_enabled; + +-- crypto functions with FIPS mode. +SELECT fips_mode() AS is_fips \gset +\if :is_fips +SET pgcrypto.builtin_crypto_enabled = fips; +-- non-AES ciphers must error +SELECT pgp_sym_encrypt('data', 'key', 'cipher-algo=bf'); +SELECT pgp_sym_encrypt('data', 'key', 'cipher-algo=3des'); +SELECT pgp_sym_encrypt('data', 'key', 'cipher-algo=cast5'); +-- AES ciphers work +SELECT pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=aes128'), + 'key', 'expect-cipher-algo=aes128'); +SELECT pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=aes256'), + 'key', 'expect-cipher-algo=aes256'); +-- AES round trip under FIPS +SELECT pgp_sym_decrypt(pgp_sym_encrypt('FIPS round trip test', 'key', + 'cipher-algo=aes256'), 'key'); +RESET pgcrypto.builtin_crypto_enabled; +\endif -- 2.53.0