From 3fb17a2ff4298e52128ea80a677c1b7afda0400b Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Fri, 22 Nov 2024 14:49:31 +0100
Subject: [PATCH v4] pgcrypto: Make it possible to disable built-in crypto

When using OpenSSL and/or the underlying operating system in FIPS
mode no non-FIPS certified crypto implementations should be used.
While that is already possible by just not invoking the built-in
crypto in pgcrypto, this adds a GUC which prohibit the code from
being called.  This doesn't change the FIPS status of PostgreSQL
but can make it easier for sites which target FIPS compliance to
ensure that violations cannot occur.

Reviewed-by: Joe Conway <mail@joeconway.com>
Discussion: https://postgr.es/m/TYCPR01MB11684E5B636E17548D4A42248FA4D2@TYCPR01MB11684.jpnprd01.prod.outlook.com
---
 contrib/pgcrypto/expected/crypt-des.out |  7 ++++
 contrib/pgcrypto/openssl.c              | 43 +++++++++++++++++++++++++
 contrib/pgcrypto/pgcrypto.c             | 30 +++++++++++++++++
 contrib/pgcrypto/px-crypt.c             |  4 +++
 contrib/pgcrypto/px.h                   | 10 ++++++
 contrib/pgcrypto/sql/crypt-des.sql      |  6 ++++
 doc/src/sgml/pgcrypto.sgml              | 39 ++++++++++++++++++++++
 7 files changed, 139 insertions(+)

diff --git a/contrib/pgcrypto/expected/crypt-des.out b/contrib/pgcrypto/expected/crypt-des.out
index a462dcd580a..8993a2d1044 100644
--- a/contrib/pgcrypto/expected/crypt-des.out
+++ b/contrib/pgcrypto/expected/crypt-des.out
@@ -28,4 +28,11 @@ FROM ctest;
  t
 (1 row)
 
+-- check disabling of legacy crypto functions
+SET pgcrypto.legacy_crypto_enabled = off;
+UPDATE ctest SET salt = gen_salt('des');
+ERROR:  use of built-in crypto functions is disabled
+UPDATE ctest SET res = crypt(data, salt);
+ERROR:  use of built-in crypto functions is disabled
+RESET pgcrypto.legacy_crypto_enabled;
 DROP TABLE ctest;
diff --git a/contrib/pgcrypto/openssl.c b/contrib/pgcrypto/openssl.c
index 448db331a0f..c5683aa70e7 100644
--- a/contrib/pgcrypto/openssl.c
+++ b/contrib/pgcrypto/openssl.c
@@ -794,3 +794,46 @@ ResOwnerReleaseOSSLCipher(Datum res)
 {
 	free_openssl_cipher((OSSLCipher *) DatumGetPointer(res));
 }
+
+/*
+ * CheckLegacyCryptoMode
+ *
+ * Function for erroring out in case built-in crypto is executed when the user
+ * has disabled it. If legacy_crypto_enabled is set to LGC_OFF or LGC_FIPS and
+ * OpenSSL is operating in FIPS mode the function will error out, else the
+ * query executing built-in crypto can proceed.
+ */
+void
+CheckLegacyCryptoMode(void)
+{
+	int fips_enabled;
+
+	if (legacy_crypto_enabled == LGC_ON)
+		return;
+
+	if (legacy_crypto_enabled == LGC_OFF)
+		ereport(ERROR,
+				errmsg("use of built-in crypto functions is disabled"));
+
+	Assert(legacy_crypto_enabled == LGC_FIPS);
+
+	/*
+	 * EVP_default_properties_is_fips_enabled was added in OpenSSL 3.0, before
+	 * that FIPS_mode() was used to test for FIPS being enabled.  The last
+	 * upstream OpenSSL version before 3.0 which supported FIPS was 1.0.2, but
+	 * there are forks of 1.1.1 which are FIPS certified so we still need to
+	 * test with FIPS_mode() even though we don't support 1.0.2.
+	 */
+	fips_enabled =
+#if defined(EVP_default_properties_is_fips_enabled)
+	EVP_default_properties_is_fips_enabled(NULL);
+#elif defined(FIPS_mode)
+	FIPS_mode();
+#else
+	0;
+#endif
+
+	if (fips_enabled)
+		ereport(ERROR,
+				errmsg("use of non-FIPS certified crypto not allowed when OpenSSL is in FIPS mode"));
+}
diff --git a/contrib/pgcrypto/pgcrypto.c b/contrib/pgcrypto/pgcrypto.c
index ebd76eed702..fe0e2b9242a 100644
--- a/contrib/pgcrypto/pgcrypto.c
+++ b/contrib/pgcrypto/pgcrypto.c
@@ -38,16 +38,46 @@
 #include "px-crypt.h"
 #include "px.h"
 #include "utils/builtins.h"
+#include "utils/guc.h"
 #include "varatt.h"
 
 PG_MODULE_MAGIC;
 
 /* private stuff */
 
+static const struct config_enum_entry legacy_crypto_options[] = {
+	{"on", LGC_ON, false},
+	{"off", LGC_OFF, false},
+	{"fips", LGC_FIPS, false},
+	{NULL, 0, false}
+};
+
 typedef int (*PFN) (const char *name, void **res);
 static void *find_provider(text *name, PFN provider_lookup, const char *desc,
 						   int silent);
 
+int legacy_crypto_enabled = LGC_ON;
+
+/*
+ * Entrypoint of this module.
+ */
+void
+_PG_init(void)
+{
+	DefineCustomEnumVariable("pgcrypto.legacy_crypto_enabled",
+							 "Sets if builtin crypto functions are enabled.",
+							 "Yes enables builtin crypto, No unconditionally disables and OpenSSL "
+							 "will disable if OpenSSL is in FIPS mode",
+							 &legacy_crypto_enabled,
+							 LGC_ON,
+							 legacy_crypto_options,
+							 PGC_SUSET,
+							 0,
+							 NULL,
+							 NULL,
+							 NULL);
+}
+
 /* SQL function: hash(bytea, text) returns bytea */
 PG_FUNCTION_INFO_V1(pg_digest);
 
diff --git a/contrib/pgcrypto/px-crypt.c b/contrib/pgcrypto/px-crypt.c
index 0913ff2c1bc..b97ea4fd36b 100644
--- a/contrib/pgcrypto/px-crypt.c
+++ b/contrib/pgcrypto/px-crypt.c
@@ -91,6 +91,8 @@ px_crypt(const char *psw, const char *salt, char *buf, unsigned len)
 {
 	const struct px_crypt_algo *c;
 
+	CheckLegacyCryptoMode();
+
 	for (c = px_crypt_list; c->id; c++)
 	{
 		if (!c->id_len)
@@ -135,6 +137,8 @@ px_gen_salt(const char *salt_type, char *buf, int rounds)
 	char	   *p;
 	char		rbuf[16];
 
+	CheckLegacyCryptoMode();
+
 	for (g = gen_list; g->name; g++)
 		if (pg_strcasecmp(g->name, salt_type) == 0)
 			break;
diff --git a/contrib/pgcrypto/px.h b/contrib/pgcrypto/px.h
index 471bb4ec727..8f21dad54ec 100644
--- a/contrib/pgcrypto/px.h
+++ b/contrib/pgcrypto/px.h
@@ -89,6 +89,12 @@
 #define PXE_PGP_UNSUPPORTED_PUBALGO -122
 #define PXE_PGP_MULTIPLE_SUBKEYS	-123
 
+typedef enum LegacyCryptoOptions
+{
+	LGC_ON,
+	LGC_OFF,
+	LGC_FIPS,
+} LegacyCryptoOptions;
 
 typedef struct px_digest PX_MD;
 typedef struct px_alias PX_Alias;
@@ -96,6 +102,8 @@ typedef struct px_hmac PX_HMAC;
 typedef struct px_cipher PX_Cipher;
 typedef struct px_combo PX_Combo;
 
+extern int legacy_crypto_enabled;
+
 struct px_digest
 {
 	unsigned	(*result_size) (PX_MD *h);
@@ -182,6 +190,8 @@ void		px_set_debug_handler(void (*handler) (const char *));
 
 void		px_memset(void *ptr, int c, size_t len);
 
+void		CheckLegacyCryptoMode(void);
+
 #ifdef PX_DEBUG
 void		px_debug(const char *fmt,...) pg_attribute_printf(1, 2);
 #else
diff --git a/contrib/pgcrypto/sql/crypt-des.sql b/contrib/pgcrypto/sql/crypt-des.sql
index a85ec1e6555..5b75dc5bb49 100644
--- a/contrib/pgcrypto/sql/crypt-des.sql
+++ b/contrib/pgcrypto/sql/crypt-des.sql
@@ -18,4 +18,10 @@ UPDATE ctest SET res = crypt(data, salt);
 SELECT res = crypt(data, res) AS "worked"
 FROM ctest;
 
+-- check disabling of legacy crypto functions
+SET pgcrypto.legacy_crypto_enabled = off;
+UPDATE ctest SET salt = gen_salt('des');
+UPDATE ctest SET res = crypt(data, salt);
+RESET pgcrypto.legacy_crypto_enabled;
+
 DROP TABLE ctest;
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index 396c67f0cde..c54b0e8459f 100644
--- a/doc/src/sgml/pgcrypto.sgml
+++ b/doc/src/sgml/pgcrypto.sgml
@@ -1222,6 +1222,45 @@ gen_random_uuid() returns uuid
   </sect3>
  </sect2>
 
+ <sect2 id="pgcrypto-configuration-parameters">
+  <title>Configuration Parameters</title>
+
+ <para>
+  There is one configuration parameter that controls the behavior of
+  <filename>pgcrypto</filename>.
+ </para>
+
+  <variablelist>
+   <varlistentry id="pgcrypto-configuration-parameters-legacy_crypto_enabled">
+    <term>
+     <varname>pgcrypto.legacy_crypto_enabled</varname> (<type>enum</type>)
+     <indexterm>
+      <primary><varname>pgcrypto.legacy_crypto_enabled</varname> configuration
+      parameter</primary>
+     </indexterm>
+    </term>
+    <listitem>
+     <para>
+      <varname>pgcrypto.legacy_crypto_enabled</varname> determines if the
+      built in legacy crypto functions <literal>pg_gen_salt</literal>,
+      <literal>pg_gen_salt_rounds</literal>, and <literal>pg_crypt</literal>
+      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 <application>OpenSSL</application> is detected to operate
+      in FIPS mode.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+
+  <para>
+   In ordinary usage, these parameters are set
+   in <filename>postgresql.conf</filename>, although superusers can alter them
+   on-the-fly within their own sessions.
+  </para>
+ </sect2>
+
  <sect2 id="pgcrypto-author">
   <title>Author</title>
 
-- 
2.39.3 (Apple Git-146)

