From 06594cb71bc0e9eed56d18fe3e991a0abe045a95 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <dgustafsson@postgresql.org>
Date: Tue, 21 Jan 2025 23:53:39 +0100
Subject: [PATCH v10 2/2] 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.

Author: Daniel Gustafsson <daniel@yesql.se>
Author: Joe Conway <mail@joeconway.com>
Reviewed-by: Joe Conway <mail@joeconway.com>
Reviewed-by: Peter Eisentraut <peter@eisentraut.org>
Reviewed-by: Hayato Kuroda <kuroda.hayato@fujitsu.com>
Discussion: https://postgr.es/m/16b4a157-9ea1-44d0-b7b3-4c85df5de97b@joeconway.com
---
 contrib/pgcrypto/expected/crypt-des.out |  7 +++++
 contrib/pgcrypto/openssl.c              | 29 ++++++++++++++++++-
 contrib/pgcrypto/pgcrypto.c             | 31 ++++++++++++++++++++
 contrib/pgcrypto/px-crypt.c             |  4 +++
 contrib/pgcrypto/px.h                   |  9 ++++++
 contrib/pgcrypto/sql/crypt-des.sql      |  6 ++++
 doc/src/sgml/pgcrypto.sgml              | 38 +++++++++++++++++++++++++
 7 files changed, 123 insertions(+), 1 deletion(-)

diff --git a/contrib/pgcrypto/expected/crypt-des.out b/contrib/pgcrypto/expected/crypt-des.out
index a462dcd580a..70ca7b357b2 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 built in crypto functions
+SET pgcrypto.builtin_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.builtin_crypto_enabled;
 DROP TABLE ctest;
diff --git a/contrib/pgcrypto/openssl.c b/contrib/pgcrypto/openssl.c
index b2984045980..75f40a2d034 100644
--- a/contrib/pgcrypto/openssl.c
+++ b/contrib/pgcrypto/openssl.c
@@ -31,6 +31,7 @@
 
 #include "postgres.h"
 
+#include <openssl/crypto.h>
 #include <openssl/evp.h>
 #include <openssl/err.h>
 #include <openssl/rand.h>
@@ -804,11 +805,12 @@ bool
 CheckFIPSMode(void)
 {
 	int			fips_enabled = 0;
+
 	/*
 	 * 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
+	 * there are forks of 1.1.1 which are FIPS validated so we still need to
 	 * test with FIPS_mode() even though we don't support 1.0.2.
 	 */
 	fips_enabled =
@@ -820,3 +822,28 @@ CheckFIPSMode(void)
 
 	return (fips_enabled == 1);
 }
+
+/*
+ * CheckBuiltinCryptoMode
+ *
+ * Function for erroring out in case built-in crypto is executed when the user
+ * has disabled it. If builtin_crypto_enabled is set to BC_OFF or BC_FIPS and
+ * OpenSSL is operating in FIPS mode the function will error out, else the
+ * query executing built-in crypto can proceed.
+ */
+void
+CheckBuiltinCryptoMode(void)
+{
+	if (builtin_crypto_enabled == BC_ON)
+		return;
+
+	if (builtin_crypto_enabled == BC_OFF)
+		ereport(ERROR,
+				errmsg("use of built-in crypto functions is disabled"));
+
+	Assert(builtin_crypto_enabled == BC_FIPS);
+
+	if (CheckFIPSMode() == true)
+		ereport(ERROR,
+				errmsg("use of non-FIPS validated crypto not allowed when OpenSSL is in FIPS mode"));
+}
diff --git a/contrib/pgcrypto/pgcrypto.c b/contrib/pgcrypto/pgcrypto.c
index ee2a010e402..b7e5383b9a6 100644
--- a/contrib/pgcrypto/pgcrypto.c
+++ b/contrib/pgcrypto/pgcrypto.c
@@ -38,16 +38,47 @@
 #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 builtin_crypto_options[] = {
+	{"on", BC_ON, false},
+	{"off", BC_OFF, false},
+	{"fips", BC_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			builtin_crypto_enabled = BC_ON;
+
+/*
+ * Entrypoint of this module.
+ */
+void
+_PG_init(void)
+{
+	DefineCustomEnumVariable("pgcrypto.builtin_crypto_enabled",
+							 "Sets if builtin crypto functions are enabled.",
+							 "\"on\" enables builtin crypto, \"off\" unconditionally disables and \"fips\" "
+							 "will disable builtin crypto if OpenSSL is in FIPS mode",
+							 &builtin_crypto_enabled,
+							 BC_ON,
+							 builtin_crypto_options,
+							 PGC_SUSET,
+							 0,
+							 NULL,
+							 NULL,
+							 NULL);
+	MarkGUCPrefixReserved("pgcrypto");
+}
+
 /* 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..96ce9384aff 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;
 
+	CheckBuiltinCryptoMode();
+
 	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];
 
+	CheckBuiltinCryptoMode();
+
 	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 c2c2fc31245..37013cd9f82 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 BuiltinCryptoOptions
+{
+	BC_ON,
+	BC_OFF,
+	BC_FIPS,
+}			BuiltinCryptoOptions;
 
 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	builtin_crypto_enabled;
+
 struct px_digest
 {
 	unsigned	(*result_size) (PX_MD *h);
@@ -183,6 +191,7 @@ void		px_set_debug_handler(void (*handler) (const char *));
 void		px_memset(void *ptr, int c, size_t len);
 
 bool		CheckFIPSMode(void);
+void		CheckBuiltinCryptoMode(void);
 
 #ifdef PX_DEBUG
 void		px_debug(const char *fmt,...) pg_attribute_printf(1, 2);
diff --git a/contrib/pgcrypto/sql/crypt-des.sql b/contrib/pgcrypto/sql/crypt-des.sql
index a85ec1e6555..58cc9d2720a 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 built in crypto functions
+SET pgcrypto.builtin_crypto_enabled = off;
+UPDATE ctest SET salt = gen_salt('des');
+UPDATE ctest SET res = crypt(data, salt);
+RESET pgcrypto.builtin_crypto_enabled;
+
 DROP TABLE ctest;
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index 838d7532a52..dc19ffd8bc1 100644
--- a/doc/src/sgml/pgcrypto.sgml
+++ b/doc/src/sgml/pgcrypto.sgml
@@ -1165,6 +1165,44 @@ fips_mode() returns boolean
   </para>
  </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-builtin_crypto_enabled">
+    <term>
+     <varname>pgcrypto.builtin_crypto_enabled</varname> (<type>enum</type>)
+     <indexterm>
+      <primary><varname>pgcrypto.builtin_crypto_enabled</varname> configuration
+      parameter</primary>
+     </indexterm>
+    </term>
+    <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
+      <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, this parameter is set
+   in <filename>postgresql.conf</filename>, although superusers can alter it
+   on-the-fly within their own sessions.
+  </para>
+ </sect2>
+
  <sect2 id="pgcrypto-notes">
   <title>Notes</title>
 
-- 
2.39.3 (Apple Git-146)

