From e3f3d31a3db23f6c8437cd0efe45f67a7f4fc6aa Mon Sep 17 00:00:00 2001
From: Robbie Harwood <rharwood@redhat.com>
Date: Sat, 15 May 2021 21:18:06 -0400
Subject: [PATCH] Handle OpenSSL 3's providers

OpenSSL 3 compartmentalizes what algorithms it uses, which for us means
another hoop to jump through to use dubious cryptography.  (Right now,
we need to load "legacy" in order to access MD4 and RC4.)

Use our normal initializer logic to set up providers both in the OpenSSL
provider an the PKINIT plugin.  Since DT_FINI is too late, release them
using atexit() as OpenSSL does.

(cherry picked from commit bea5a703a06da1f1ab56821b77a2d3661cb0dda4)
[rharwood@redhat.com: work around des3 removal and rc4 fips changes]
---
 src/configure.ac                              |  1 +
 src/lib/crypto/openssl/enc_provider/aes.c     | 16 ++++++
 .../crypto/openssl/enc_provider/camellia.c    | 16 ++++++
 src/lib/crypto/openssl/enc_provider/rc4.c     |  4 ++
 .../crypto/openssl/hash_provider/hash_evp.c   |  5 ++
 src/lib/crypto/openssl/init.c                 | 53 +++++++++++++++++++
 src/plugins/preauth/pkinit/Makefile.in        |  1 +
 .../preauth/pkinit/pkinit_crypto_openssl.c    | 33 ++++++++++--
 8 files changed, 126 insertions(+), 3 deletions(-)

diff --git a/src/configure.ac b/src/configure.ac
index 9c2e816fe..20066918b 100644
--- a/src/configure.ac
+++ b/src/configure.ac
@@ -284,6 +284,7 @@ AC_SUBST(CRYPTO_IMPL_LIBS)
 
 if test "$CRYPTO_IMPL" = openssl; then
    AC_CHECK_FUNCS(EVP_KDF_fetch)
+   AC_CHECK_FUNCS(OSSL_PROVIDER_load)
 fi
 
 AC_ARG_WITH([prng-alg],
diff --git a/src/lib/crypto/openssl/enc_provider/aes.c b/src/lib/crypto/openssl/enc_provider/aes.c
index 6b4622fe9..31c90a69d 100644
--- a/src/lib/crypto/openssl/enc_provider/aes.c
+++ b/src/lib/crypto/openssl/enc_provider/aes.c
@@ -68,6 +68,10 @@ cbc_enc(krb5_key key, const krb5_data *ivec, krb5_crypto_iov *data,
     EVP_CIPHER_CTX  *ctx;
     struct iov_cursor cursor;
 
+    ret = krb5int_crypto_init();
+    if (ret)
+        return ret;
+
     ctx = EVP_CIPHER_CTX_new();
     if (ctx == NULL)
         return ENOMEM;
@@ -102,6 +106,10 @@ cbc_decr(krb5_key key, const krb5_data *ivec, krb5_crypto_iov *data,
     EVP_CIPHER_CTX   *ctx;
     struct iov_cursor cursor;
 
+    ret = krb5int_crypto_init();
+    if (ret)
+        return ret;
+
     ctx = EVP_CIPHER_CTX_new();
     if (ctx == NULL)
         return ENOMEM;
@@ -137,6 +145,10 @@ cts_encr(krb5_key key, const krb5_data *ivec, krb5_crypto_iov *data,
     struct iov_cursor      cursor;
     AES_KEY                enck;
 
+    ret = krb5int_crypto_init();
+    if (ret)
+        return ret;
+
     memset(iv_cts,0,sizeof(iv_cts));
     if (ivec && ivec->data){
         if (ivec->length != sizeof(iv_cts))
@@ -190,6 +202,10 @@ cts_decr(krb5_key key, const krb5_data *ivec, krb5_crypto_iov *data,
     struct iov_cursor      cursor;
     AES_KEY                deck;
 
+    ret = krb5int_crypto_init();
+    if (ret)
+        return ret;
+
     memset(iv_cts,0,sizeof(iv_cts));
     if (ivec && ivec->data){
         if (ivec->length != sizeof(iv_cts))
diff --git a/src/lib/crypto/openssl/enc_provider/camellia.c b/src/lib/crypto/openssl/enc_provider/camellia.c
index f79679a0b..7cc7fc6fb 100644
--- a/src/lib/crypto/openssl/enc_provider/camellia.c
+++ b/src/lib/crypto/openssl/enc_provider/camellia.c
@@ -92,6 +92,10 @@ cbc_enc(krb5_key key, const krb5_data *ivec, krb5_crypto_iov *data,
     EVP_CIPHER_CTX  *ctx;
     struct iov_cursor cursor;
 
+    ret = krb5int_crypto_init();
+    if (ret)
+        return ret;
+
     ctx = EVP_CIPHER_CTX_new();
     if (ctx == NULL)
         return ENOMEM;
@@ -126,6 +130,10 @@ cbc_decr(krb5_key key, const krb5_data *ivec, krb5_crypto_iov *data,
     EVP_CIPHER_CTX   *ctx;
     struct iov_cursor cursor;
 
+    ret = krb5int_crypto_init();
+    if (ret)
+        return ret;
+
     ctx = EVP_CIPHER_CTX_new();
     if (ctx == NULL)
         return ENOMEM;
@@ -161,6 +169,10 @@ cts_encr(krb5_key key, const krb5_data *ivec, krb5_crypto_iov *data,
     struct iov_cursor      cursor;
     CAMELLIA_KEY           enck;
 
+    ret = krb5int_crypto_init();
+    if (ret)
+        return ret;
+
     memset(iv_cts,0,sizeof(iv_cts));
     if (ivec && ivec->data){
         if (ivec->length != sizeof(iv_cts))
@@ -214,6 +226,10 @@ cts_decr(krb5_key key, const krb5_data *ivec, krb5_crypto_iov *data,
     struct iov_cursor      cursor;
     CAMELLIA_KEY           deck;
 
+    ret = krb5int_crypto_init();
+    if (ret)
+        return ret;
+
     memset(iv_cts,0,sizeof(iv_cts));
     if (ivec && ivec->data){
         if (ivec->length != sizeof(iv_cts))
diff --git a/src/lib/crypto/openssl/enc_provider/rc4.c b/src/lib/crypto/openssl/enc_provider/rc4.c
index 9bf407899..a10cb5192 100644
--- a/src/lib/crypto/openssl/enc_provider/rc4.c
+++ b/src/lib/crypto/openssl/enc_provider/rc4.c
@@ -66,6 +66,10 @@ k5_arcfour_docrypt(krb5_key key, const krb5_data *state, krb5_crypto_iov *data,
     EVP_CIPHER_CTX *ctx = NULL;
     struct arcfour_state *arcstate;
 
+    ret = krb5int_crypto_init();
+    if (ret)
+        return ret;
+
     if (FIPS_mode())
         return KRB5_CRYPTO_INTERNAL;
 
diff --git a/src/lib/crypto/openssl/hash_provider/hash_evp.c b/src/lib/crypto/openssl/hash_provider/hash_evp.c
index 2eb5139c0..09d7b3896 100644
--- a/src/lib/crypto/openssl/hash_provider/hash_evp.c
+++ b/src/lib/crypto/openssl/hash_provider/hash_evp.c
@@ -41,6 +41,11 @@ hash_evp(const EVP_MD *type, const krb5_crypto_iov *data, size_t num_data,
     const krb5_data *d;
     size_t i;
     int ok;
+    krb5_error_code ret;
+
+    ret = krb5int_crypto_init();
+    if (ret)
+        return ret;
 
     if (output->length != (unsigned int)EVP_MD_size(type))
         return KRB5_CRYPTO_INTERNAL;
diff --git a/src/lib/crypto/openssl/init.c b/src/lib/crypto/openssl/init.c
index 1139bce53..f72dbfe81 100644
--- a/src/lib/crypto/openssl/init.c
+++ b/src/lib/crypto/openssl/init.c
@@ -26,12 +26,65 @@
 
 #include "crypto_int.h"
 
+#ifdef HAVE_OSSL_PROVIDER_LOAD
+
+/*
+ * Starting in OpenSSL 3, algorithms are grouped into containers called
+ * "providers", not all of which are loaded by default.  At time of writing,
+ * we need MD4 and RC4 from the legacy provider.  Oddly, 3DES is not in
+ * legacy.
+ */
+
+#include <openssl/provider.h>
+
+static OSSL_PROVIDER *legacy_provider = NULL;
+static OSSL_PROVIDER *default_provider = NULL;
+
+static void
+unload_providers(void)
+{
+    if (default_provider != NULL)
+        (void)OSSL_PROVIDER_unload(default_provider);
+    if (legacy_provider != NULL)
+        (void)OSSL_PROVIDER_unload(legacy_provider);
+    default_provider = NULL;
+    legacy_provider = NULL;
+}
+
+int
+krb5int_crypto_impl_init(void)
+{
+    legacy_provider = OSSL_PROVIDER_load(NULL, "legacy");
+    default_provider = OSSL_PROVIDER_load(NULL, "default");
+
+    /*
+     * Someone might build openssl without the legacy provider.  They will
+     * have a bad time, but some things will still work.  I don't know think
+     * this configuration is worth supporting.
+     */
+    if (legacy_provider == NULL || default_provider == NULL)
+        abort();
+
+    /*
+     * If we attempt to do this with our normal LIBFINIFUNC logic (DT_FINI),
+     * OpenSSL will have cleaned itself up by the time we're invoked.  OpenSSL
+     * registers its cleanup (OPENSSL_cleanup) with atexit() - do the same and
+     * we'll be higher on the stack.
+     */
+    atexit(unload_providers);
+    return 0;
+}
+
+#else /* !HAVE_OSSL_PROVIDER_LOAD */
+
 int
 krb5int_crypto_impl_init(void)
 {
     return 0;
 }
 
+#endif
+
 void
 krb5int_crypto_impl_cleanup(void)
 {
diff --git a/src/plugins/preauth/pkinit/Makefile.in b/src/plugins/preauth/pkinit/Makefile.in
index 15ca0eb48..d20fb18a8 100644
--- a/src/plugins/preauth/pkinit/Makefile.in
+++ b/src/plugins/preauth/pkinit/Makefile.in
@@ -5,6 +5,7 @@ MODULE_INSTALL_DIR = $(KRB5_PA_MODULE_DIR)
 LIBBASE=pkinit
 LIBMAJOR=0
 LIBMINOR=0
+LIBINITFUNC=pkinit_openssl_init
 RELDIR=../plugins/preauth/pkinit
 # Depends on libk5crypto and libkrb5
 SHLIB_EXPDEPS = \
diff --git a/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c b/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c
index 350c2118a..42e5c581d 100644
--- a/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c
+++ b/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c
@@ -44,6 +44,13 @@
 #include <openssl/params.h>
 #endif
 
+#ifdef HAVE_OSSL_PROVIDER_LOAD
+#include <openssl/provider.h>
+
+static OSSL_PROVIDER *legacy_provider = NULL;
+static OSSL_PROVIDER *default_provider = NULL;
+#endif
+
 static krb5_error_code pkinit_init_pkinit_oids(pkinit_plg_crypto_context );
 static void pkinit_fini_pkinit_oids(pkinit_plg_crypto_context );
 
@@ -2937,12 +2944,32 @@ cleanup:
     return retval;
 }
 
+/* pkinit_openssl_init() and unload_providers() are largely duplicated from
+ * lib/crypto/openssl/init.c - see explanations there. */
+static void
+unload_providers(void)
+{
+    if (default_provider != NULL)
+        (void)OSSL_PROVIDER_unload(default_provider);
+    if (legacy_provider != NULL)
+        (void)OSSL_PROVIDER_unload(legacy_provider);
+    default_provider = NULL;
+    legacy_provider = NULL;
+}
+
 int
 pkinit_openssl_init()
 {
-    /* Initialize OpenSSL. */
-    ERR_load_crypto_strings();
-    OpenSSL_add_all_algorithms();
+#ifdef HAVE_OSSL_PROVIDER_LOAD
+    legacy_provider = OSSL_PROVIDER_load(NULL, "legacy");
+    default_provider = OSSL_PROVIDER_load(NULL, "default");
+
+    if (legacy_provider == NULL || default_provider == NULL)
+        abort();
+
+    atexit(unload_providers);
+#endif
+
     return 0;
 }
 
