public inbox for [email protected]  
help / color / mirror / Atom feed
From: Michael Paquier <[email protected]>
To: Daniel Gustafsson <[email protected]>
Cc: [email protected]
Cc: [email protected]
Subject: Re: BUG #19457: RE:  pgp_sym_encrypt silently accepts non-FIPS ciphers (bf, cast5, 3des) when OpenSSL is in FIPS mod
Date: Fri, 24 Apr 2026 13:20:50 +0900
Message-ID: <[email protected]> (raw)
In-Reply-To: <[email protected]>
References: <[email protected]>
	<[email protected]>

On Tue, Apr 21, 2026 at 04:04:40PM +0200, Daniel Gustafsson wrote:
> Not just FIPS, it should check CheckBuiltinCryptoMode() to be consistent with
> the other builtin checks.

I am interesting in getting that fixed for the next point release, so
I have given it a try, finishing with the attached.  This would cause
pgp_sym_encrypt() and pgp_sym_decrypt() to complain when the builtin
mode is disabled, making things more consistent with the surroundings.

I agree that this could break environments where builtin_crypto is
off, as the functions would now be blocked, but I am not sure that
this is worth worrying about as builtin_crypto=on is the default.

Daniel, what do you think?
--
Michael

From f336e4e09f3d8dda9dd55b855f3eb2cd0913436a Mon Sep 17 00:00:00 2001
From: Michael Paquier <[email protected]>
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 <[email protected]>
Suggested-by: Daniel Gustafsson <[email protected]>
Discussion: https://postgr.es/m/[email protected]
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
     <listitem>
      <para>
       <varname>pgcrypto.builtin_crypto_enabled</varname> determines if the
-      built in crypto functions <function>gen_salt()</function>, and
-      <function>crypt()</function> are available for use. Setting this to
+      built in crypto functions <function>gen_salt()</function>,
+      <function>crypt()</function>, <function>pgp_sym_encrypt()</function>
+      and <function>pgp_pub_encrypt()</function> are available for use.
+      Setting this to
       <literal>off</literal> disables these functions. <literal>on</literal>
       (the default) enables these functions to work normally.
       <literal>fips</literal> disables these functions if
       <productname>OpenSSL</productname> is detected to operate in FIPS mode.
+      <function>pgp_sym_encrypt()</function> and
+      <function>pgp_pub_encrypt()</function> are disabled for ciphers that
+      are not FIPS-approved.
      </para>
     </listitem>
    </varlistentry>
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



Attachments:

  [text/plain] 0001-pgcrypto-Respect-builtin_crypto_enabled-for-PGP-ciph.patch (13.7K, 2-0001-pgcrypto-Respect-builtin_crypto_enabled-for-PGP-ciph.patch)
  download | inline diff:
From f336e4e09f3d8dda9dd55b855f3eb2cd0913436a Mon Sep 17 00:00:00 2001
From: Michael Paquier <[email protected]>
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 <[email protected]>
Suggested-by: Daniel Gustafsson <[email protected]>
Discussion: https://postgr.es/m/[email protected]
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
     <listitem>
      <para>
       <varname>pgcrypto.builtin_crypto_enabled</varname> determines if the
-      built in crypto functions <function>gen_salt()</function>, and
-      <function>crypt()</function> are available for use. Setting this to
+      built in crypto functions <function>gen_salt()</function>,
+      <function>crypt()</function>, <function>pgp_sym_encrypt()</function>
+      and <function>pgp_pub_encrypt()</function> are available for use.
+      Setting this to
       <literal>off</literal> disables these functions. <literal>on</literal>
       (the default) enables these functions to work normally.
       <literal>fips</literal> disables these functions if
       <productname>OpenSSL</productname> is detected to operate in FIPS mode.
+      <function>pgp_sym_encrypt()</function> and
+      <function>pgp_pub_encrypt()</function> are disabled for ciphers that
+      are not FIPS-approved.
      </para>
     </listitem>
    </varlistentry>
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



  [application/pgp-signature] signature.asc (833B, 3-signature.asc)
  download

reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: [email protected]
  Cc: [email protected], [email protected], [email protected], [email protected]
  Subject: Re: BUG #19457: RE:  pgp_sym_encrypt silently accepts non-FIPS ciphers (bf, cast5, 3des) when OpenSSL is in FIPS mod
  In-Reply-To: <[email protected]>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox