From 57fe7e80df5189fc0cfe0a1ef13bd473ebadcea3 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 21 Sep 2020 15:21:18 +0200
Subject: [PATCH] Fix OpenSSL 3 support in pgcrypto v2

---
 configure                  | 13 +++++++++++++
 configure.ac               |  3 +++
 contrib/pgcrypto/openssl.c | 32 +++++++++++++++++++++++++++++++-
 doc/src/sgml/pgcrypto.sgml |  6 ++++++
 src/include/pg_config.h.in |  3 +++
 5 files changed, 56 insertions(+), 1 deletion(-)

diff --git a/configure b/configure
index 19a3cd09a0..1a409e45fe 100755
--- a/configure
+++ b/configure
@@ -12437,6 +12437,19 @@ if test "x$ac_cv_func_CRYPTO_lock" = xyes; then :
 #define HAVE_CRYPTO_LOCK 1
 _ACEOF
 
+fi
+done
+
+  # OpenSSL 3.0.0 introduce the concept of providers, which pgcrypto need to
+  # use to support since deprecated ciphers.
+  for ac_func in OSSL_PROVIDER_load
+do :
+  ac_fn_c_check_func "$LINENO" "OSSL_PROVIDER_load" "ac_cv_func_OSSL_PROVIDER_load"
+if test "x$ac_cv_func_OSSL_PROVIDER_load" = xyes; then :
+  cat >>confdefs.h <<_ACEOF
+#define HAVE_OSSL_PROVIDER_LOAD 1
+_ACEOF
+
 fi
 done
 
diff --git a/configure.ac b/configure.ac
index 6b9d0487a8..dd5b95d7e1 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1233,6 +1233,9 @@ if test "$with_openssl" = yes ; then
   # thread-safety. In 1.1.0, it's no longer required, and CRYPTO_lock()
   # function was removed.
   AC_CHECK_FUNCS([CRYPTO_lock])
+  # OpenSSL 3.0.0 introduce the concept of providers, which pgcrypto need to
+  # use to support since deprecated ciphers.
+  AC_CHECK_FUNCS([OSSL_PROVIDER_load])
 fi
 
 if test "$with_pam" = yes ; then
diff --git a/contrib/pgcrypto/openssl.c b/contrib/pgcrypto/openssl.c
index 3057afb339..7e843394d1 100644
--- a/contrib/pgcrypto/openssl.c
+++ b/contrib/pgcrypto/openssl.c
@@ -34,6 +34,9 @@
 #include <openssl/evp.h>
 #include <openssl/err.h>
 #include <openssl/rand.h>
+#ifdef HAVE_OSSL_PROVIDER_LOAD
+#include <openssl/provider.h>
+#endif
 
 #include "px.h"
 #include "utils/memutils.h"
@@ -64,6 +67,10 @@ typedef struct OSSLDigest
 	struct OSSLDigest *prev;
 } OSSLDigest;
 
+#ifdef HAVE_OSSL_PROVIDER_LOAD
+static OSSL_PROVIDER *legacy_provider = NULL;
+static OSSL_PROVIDER *default_provider = NULL;
+#endif
 static OSSLDigest *open_digests = NULL;
 static bool digest_resowner_callback_registered = false;
 
@@ -173,8 +180,20 @@ px_find_digest(const char *name, PX_MD **res)
 
 	if (!px_openssl_initialized)
 	{
-		px_openssl_initialized = 1;
+#ifdef HAVE_OSSL_PROVIDER_LOAD
+		if (legacy_provider == NULL)
+			legacy_provider = OSSL_PROVIDER_load(NULL, "legacy");
+		if (default_provider == NULL)
+			default_provider = OSSL_PROVIDER_load(NULL, "default");
+#endif
+
+		/*
+		 * OpenSSL_add_all_algorithms is deprecated in OpenSSL 1.1.0 and no
+		 * longer required in 1.1.0 and later versions as initialization is
+		 * performed automatically.
+		 */
 		OpenSSL_add_all_algorithms();
+		px_openssl_initialized = 1;
 	}
 
 	if (!digest_resowner_callback_registered)
@@ -367,6 +386,8 @@ gen_ossl_decrypt(PX_Cipher *c, const uint8 *data, unsigned dlen,
 	{
 		if (!EVP_DecryptInit_ex(od->evp_ctx, od->evp_ciph, NULL, NULL, NULL))
 			return PXE_CIPHER_INIT;
+		if (!EVP_CIPHER_CTX_set_padding(od->evp_ctx, 0))
+			return PXE_CIPHER_INIT;
 		if (!EVP_CIPHER_CTX_set_key_length(od->evp_ctx, od->klen))
 			return PXE_CIPHER_INIT;
 		if (!EVP_DecryptInit_ex(od->evp_ctx, NULL, NULL, od->key, od->iv))
@@ -391,6 +412,8 @@ gen_ossl_encrypt(PX_Cipher *c, const uint8 *data, unsigned dlen,
 	{
 		if (!EVP_EncryptInit_ex(od->evp_ctx, od->evp_ciph, NULL, NULL, NULL))
 			return PXE_CIPHER_INIT;
+		if (!EVP_CIPHER_CTX_set_padding(od->evp_ctx, 0))
+			return PXE_CIPHER_INIT;
 		if (!EVP_CIPHER_CTX_set_key_length(od->evp_ctx, od->klen))
 			return PXE_CIPHER_INIT;
 		if (!EVP_EncryptInit_ex(od->evp_ctx, NULL, NULL, od->key, od->iv))
@@ -751,6 +774,13 @@ px_find_cipher(const char *name, PX_Cipher **res)
 	EVP_CIPHER_CTX *ctx;
 	OSSLCipher *od;
 
+#ifdef HAVE_OSSL_PROVIDER_LOAD
+	if (legacy_provider == NULL)
+		legacy_provider = OSSL_PROVIDER_load(NULL, "legacy");
+	if (default_provider == NULL)
+		default_provider = OSSL_PROVIDER_load(NULL, "default");
+#endif
+
 	name = px_resolve_alias(ossl_aliases, name);
 	for (i = ossl_cipher_types; i->name; i++)
 		if (strcmp(i->name, name) == 0)
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index 8748c64e2d..5c6d54042e 100644
--- a/doc/src/sgml/pgcrypto.sgml
+++ b/doc/src/sgml/pgcrypto.sgml
@@ -1233,6 +1233,12 @@ gen_random_uuid() returns uuid
     </tgroup>
    </table>
 
+   <para>
+    When compiled against <productname>OpenSSL</productname> 3.0.0, the legacy
+    provider will be automatically loaded in order to support the ciphers in
+    the above table.
+   </para>
+
    <para>
     Notes:
    </para>
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index fb270df678..e4844f67c1 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -385,6 +385,9 @@
 /* Define to 1 if you have the `OPENSSL_init_ssl' function. */
 #undef HAVE_OPENSSL_INIT_SSL
 
+/* Define to 1 if you have the `OSSL_PROVIDER_load' function */
+#undef HAVE_OSSL_PROVIDER_LOAD
+
 /* Define to 1 if you have the <ossp/uuid.h> header file. */
 #undef HAVE_OSSP_UUID_H
 
-- 
2.21.1 (Apple Git-122.3)

