From 96ae6bbd142190b1504a82499fb3b4772c16efd1 Mon Sep 17 00:00:00 2001
From: Nicolas Williams <nico@twosigma.com>
Date: Mon, 21 Nov 2022 12:06:59 -0600
Subject: [PATCH 01/12] cf: Check for OpenSSL 3.0

This check is admittedly lame.  But it's all I have time for at the
moment.  A better check would be a program that includes the correct
headers and succeeds if the OpenSSL version macro indicates it's at
3.0 or higher.  Or perhaps we could run the openssl(1) version command-
line and parse its output.  But checking for functions that are in 3.0
and not 1.1 will do for the time being.

(cherry picked from commit ac8c1341fbb1d03b32d6feb0c18e1276c3d43c75)
---
 cf/crypto.m4 | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/cf/crypto.m4 b/cf/crypto.m4
index b8b011dd15..059b0aa9e5 100644
--- a/cf/crypto.m4
+++ b/cf/crypto.m4
@@ -134,6 +134,14 @@ if test "$with_openssl" != "no"; then
         LDFLAGS="${saved_LDFLAGS}"
 fi
 
+if test "$openssl" = "yes"; then
+    AC_CHECK_LIB([crypto],
+                 [OSSL_EC_curve_nid2name],
+                 [AC_DEFINE_UNQUOTED([HAVE_OPENSSL_30], 1,
+                                     [whether OpenSSL is 3.0 or higher])]
+                 )
+fi
+
 LIB_hcrypto='$(top_builddir)/lib/hcrypto/libhcrypto.la'
 LIB_hcrypto_a='$(top_builddir)/lib/hcrypto/.libs/libhcrypto.a'
 LIB_hcrypto_so='$(top_builddir)/lib/hcrypto/.libs/libhcrypto.so'

From 3d0f1cc3ff28829803420057af2669454ad11421 Mon Sep 17 00:00:00 2001
From: Nicolas Williams <nico@twosigma.com>
Date: Mon, 21 Nov 2022 12:09:07 -0600
Subject: [PATCH 02/12] cf: Check for OpenSSL FIPS

(cherry picked from commit 83cd1255f69dd5fff535fcc2c2147ca021c75d95)
---
 cf/crypto.m4 | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/cf/crypto.m4 b/cf/crypto.m4
index 059b0aa9e5..a8ef519841 100644
--- a/cf/crypto.m4
+++ b/cf/crypto.m4
@@ -140,6 +140,15 @@ if test "$openssl" = "yes"; then
                  [AC_DEFINE_UNQUOTED([HAVE_OPENSSL_30], 1,
                                      [whether OpenSSL is 3.0 or higher])]
                  )
+    AC_CHECK_HEADERS([openssl/fips.h],
+                     [AC_DEFINE_UNQUOTED([HAVE_OPENSSL_FIPS_H], 1,
+                                         [whether openssl/fips.h is available])]
+                     )
+    AC_CHECK_LIB([crypto],
+                 [FIPS_mode_set],
+                 [AC_DEFINE_UNQUOTED([HAVE_OPENSSL_FIPS_MODE_SET_API], 1,
+                                     [whether FIPS_mode_set API is available])]
+                 )
 fi
 
 LIB_hcrypto='$(top_builddir)/lib/hcrypto/libhcrypto.la'

From 3f668b6e7a13bfc50ea28cc32b7ad4ed67c0f0e7 Mon Sep 17 00:00:00 2001
From: Nicolas Williams <nico@twosigma.com>
Date: Thu, 17 Nov 2022 18:57:35 -0600
Subject: [PATCH 03/12] hcrypto: OpenSSL's EVP_Cipher() returns a length

(cherry picked from commit 47e6c68de8594e03a7b73ecbc759dc0451cb3966)
---
 lib/hcrypto/evp-openssl.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/hcrypto/evp-openssl.c b/lib/hcrypto/evp-openssl.c
index ae38e27c00..e7b119475e 100644
--- a/lib/hcrypto/evp-openssl.c
+++ b/lib/hcrypto/evp-openssl.c
@@ -158,7 +158,7 @@ cipher_do_cipher(hc_EVP_CIPHER_CTX *ctx, unsigned char *out,
     struct ossl_cipher_ctx *ossl_ctx = ctx->cipher_data;
 
     assert(ossl_ctx != NULL);
-    return EVP_Cipher(ossl_ctx->ossl_cipher_ctx, out, in, len);
+    return EVP_Cipher(ossl_ctx->ossl_cipher_ctx, out, in, len) == 0 ? 0 : 1;
 }
 
 static int

From cc5897eae61da950d6bc8df9eb0fb0627e6fe4c9 Mon Sep 17 00:00:00 2001
From: Nicolas Williams <nico@twosigma.com>
Date: Mon, 21 Nov 2022 14:24:31 -0600
Subject: [PATCH 04/12] hcrypto: Fix EVP_CipherUpdate() bugs

(cherry picked from commit 11846fcabb8f98e12bd9b5b950e650de6eb42451)
---
 lib/hcrypto/evp.c | 33 +++++++++++++++++----------------
 1 file changed, 17 insertions(+), 16 deletions(-)

diff --git a/lib/hcrypto/evp.c b/lib/hcrypto/evp.c
index 06935449e4..2b7df3ddef 100644
--- a/lib/hcrypto/evp.c
+++ b/lib/hcrypto/evp.c
@@ -857,37 +857,34 @@ EVP_CipherUpdate(EVP_CIPHER_CTX *ctx, void *out, int *outlen,
 
     *outlen = 0;
 
-    /**
-     * If there in no spare bytes in the left from last Update and the
-     * input length is on the block boundery, the EVP_CipherUpdate()
-     * function can take a shortcut (and preformance gain) and
-     * directly encrypt the data, otherwise we hav to fix it up and
-     * store extra it the EVP_CIPHER_CTX.
+    /*
+     * If there in no bytes left over from the last Update and the
+     * input length is on a block boundary, then we can take a
+     * shortcut (and preformance gain) and directly encrypt the
+     * data.
      */
-    if (ctx->buf_len == 0 && (inlen & ctx->block_mask) == 0) {
+    if (ctx->buf_len == 0 && inlen && (inlen & ctx->block_mask) == 0) {
 	ret = (*ctx->cipher->do_cipher)(ctx, out, in, inlen);
 	if (ret == 1)
 	    *outlen = inlen;
-	else
+        else
 	    *outlen = 0;
 	return ret;
     }
 
-
     blocksize = EVP_CIPHER_CTX_block_size(ctx);
     left = blocksize - ctx->buf_len;
     assert(left > 0);
 
     if (ctx->buf_len) {
-
-	/* if total buffer is smaller then input, store locally */
+	/* If we can't fill one block in the buffer, save the input there */
 	if (inlen < left) {
 	    memcpy(ctx->buf + ctx->buf_len, in, inlen);
 	    ctx->buf_len += inlen;
 	    return 1;
 	}
 
-	/* fill in local buffer and encrypt */
+	/* Fill the buffer and encrypt */
 	memcpy(ctx->buf + ctx->buf_len, in, left);
 	ret = (*ctx->cipher->do_cipher)(ctx, out, ctx->buf, blocksize);
 	memset(ctx->buf, 0, blocksize);
@@ -905,12 +902,16 @@ EVP_CipherUpdate(EVP_CIPHER_CTX *ctx, void *out, int *outlen,
 	ctx->buf_len = (inlen & ctx->block_mask);
 	inlen &= ~ctx->block_mask;
 
-	ret = (*ctx->cipher->do_cipher)(ctx, out, in, inlen);
-	if (ret != 1)
-	    return ret;
+        if (inlen) {
+            /* Encrypt all the whole blocks of input that we have */
+            ret = (*ctx->cipher->do_cipher)(ctx, out, in, inlen);
+            if (ret != 1)
+                return ret;
+        }
 
 	*outlen += inlen;
 
+        /* Save the tail of the input, if any */
 	in = ((unsigned char *)in) + inlen;
 	memcpy(ctx->buf, in, ctx->buf_len);
     }
@@ -969,7 +970,7 @@ EVP_CipherFinal_ex(EVP_CIPHER_CTX *ctx, void *out, int *outlen)
  * @param in in data to the operation.
  * @param size length of data.
  *
- * @return 1 on success.
+ * @return bytes encrypted on success, zero on failure.
  */
 
 int

From 30d589528e8477a37711cc1d7a2b88c26f4bd324 Mon Sep 17 00:00:00 2001
From: Nicolas Williams <nico@twosigma.com>
Date: Thu, 17 Nov 2022 19:00:49 -0600
Subject: [PATCH 05/12] hcrypto: Use builtin 1DES/RC2/RC4 (OpenSSL 3)

At some point before we make an 8.0 release we'll probably just remove
all the legacy, weak ciphers and hashes (except MD5, most likely).

To drop these we'll have to re-generate PKCS#12 test samples using
stronger PBEs, and possible add new PBE types.

(cherry picked from commit 2ddea96ba231433dd74e707e14b42cd88b92d8b7)
---
 lib/hcrypto/evp-openssl.c  | 24 +++++++++++++++++
 lib/hcrypto/evp.c          | 55 ++++++++++++++++++++++++++++++++++++++
 lib/hcrypto/evp.h          |  2 +-
 lib/hcrypto/test_cipher.c  |  6 -----
 lib/hcrypto/test_crypto.in | 10 +++----
 5 files changed, 85 insertions(+), 12 deletions(-)

diff --git a/lib/hcrypto/evp-openssl.c b/lib/hcrypto/evp-openssl.c
index e7b119475e..c6ab8034c8 100644
--- a/lib/hcrypto/evp-openssl.c
+++ b/lib/hcrypto/evp-openssl.c
@@ -80,6 +80,24 @@
 #define EVP_MD_CTX_free EVP_MD_CTX_destroy
 #endif
 
+#if defined(HAVE_OPENSSL_FIPS_H) || defined(HAVE_OPENSSL_FIPS_MODE_SET_API)
+int _heim_openssl_fips_enabled(void);
+int
+_heim_openssl_fips_enabled(void)
+{
+    static int fips_enabled_res = -1;
+
+    if (fips_enabled_res != -1)
+        return fips_enabled_res;
+
+#ifdef HAVE_OPENSSL_30
+    return fips_enabled_res = !!EVP_default_properties_is_fips_enabled(NULL);
+#else
+    return fips_enabled_res = !!FIPS_mode();
+#endif
+}
+#endif
+
 /* A HEIM_BASE_ONCE argument struct for per-EVP one-time initialization */
 struct once_init_cipher_ctx {
     const hc_EVP_CIPHER **hc_memoizep;
@@ -438,7 +456,9 @@ OSSL_CIPHER_ALGORITHM(des_ede3_cbc, hc_EVP_CIPH_CBC_MODE)
  *
  * @ingroup hcrypto_evp
  */
+#ifndef HAVE_OPENSSL_30
 OSSL_CIPHER_ALGORITHM(des_cbc, hc_EVP_CIPH_CBC_MODE)
+#endif
 
 /**
  * The AES-128 cipher type (OpenSSL provider)
@@ -494,6 +514,7 @@ OSSL_CIPHER_ALGORITHM(aes_192_cfb8, hc_EVP_CIPH_CFB8_MODE)
  */
 OSSL_CIPHER_ALGORITHM(aes_256_cfb8, hc_EVP_CIPH_CFB8_MODE)
 
+#ifndef HAVE_OPENSSL_30
 /*
  * RC2 is only needed for tests of PKCS#12 support, which currently uses
  * the RC2 PBE.  So no RC2 -> tests fail.
@@ -530,6 +551,7 @@ OSSL_CIPHER_ALGORITHM(rc2_40_cbc,
 OSSL_CIPHER_ALGORITHM(rc2_64_cbc,
                       hc_EVP_CIPH_CBC_MODE |
                       hc_EVP_CIPH_VARIABLE_LENGTH)
+#endif
 
 /**
  * The Camellia-128 cipher type - OpenSSL
@@ -558,6 +580,7 @@ OSSL_CIPHER_ALGORITHM(camellia_192_cbc, hc_EVP_CIPH_CBC_MODE)
  */
 OSSL_CIPHER_ALGORITHM(camellia_256_cbc, hc_EVP_CIPH_CBC_MODE)
 
+#ifndef HAVE_OPENSSL_30
 /**
  * The RC4 cipher type (OpenSSL provider)
  *
@@ -588,6 +611,7 @@ OSSL_CIPHER_ALGORITHM(rc4_40,
  * @ingroup hcrypto_evp
  */
 OSSL_MD_ALGORITHM(md4)
+#endif
 
 /**
  * The MD5 hash algorithm (OpenSSL provider)
diff --git a/lib/hcrypto/evp.c b/lib/hcrypto/evp.c
index 2b7df3ddef..54fa32be64 100644
--- a/lib/hcrypto/evp.c
+++ b/lib/hcrypto/evp.c
@@ -59,6 +59,7 @@
 #  define HCRYPTO_DEF_PROVIDER pkcs11_hcrypto
 # elif HAVE_HCRYPTO_W_OPENSSL
 #  define HCRYPTO_DEF_PROVIDER ossl
+#  define HCRYPTO_DEF_PROVIDER_IS_OPENSSL
 # else
 #  define HCRYPTO_DEF_PROVIDER hcrypto
 # endif
@@ -69,6 +70,11 @@
 
 #define EVP_DEF_OP(_prov,_op) HC_CONCAT4(EVP_,_prov,_,_op)()
 
+#if defined(HAVE_OPENSSL_FIPS_H) || defined(HAVE_OPENSSL_FIPS_MODE_SET_API)
+extern int _heim_openssl_fips_enabled(void);
+#endif
+
+
 /**
  * @page page_evp EVP - generic crypto interface
  *
@@ -463,6 +469,13 @@ const EVP_MD *
 EVP_md4(void) HC_DEPRECATED_CRYPTO
 {
     hcrypto_validate();
+#if defined(HCRYPTO_DEF_PROVIDER_IS_OPENSSL) && defined(HAVE_OPENSSL_30)
+#if defined(HAVE_OPENSSL_FIPS_H) || defined(HAVE_OPENSSL_FIPS_MODE_SET_API)
+    if (_heim_openssl_fips_enabled())
+        return NULL;
+#endif
+    return EVP_DEF_OP(hcrypto, md4);
+#endif
     return EVP_DEF_OP(HCRYPTO_DEF_PROVIDER, md4);
 }
 
@@ -1049,6 +1062,13 @@ const EVP_CIPHER *
 EVP_rc2_cbc(void)
 {
     hcrypto_validate();
+#if defined(HCRYPTO_DEF_PROVIDER_IS_OPENSSL) && defined(HAVE_OPENSSL_30)
+#if defined(HAVE_OPENSSL_FIPS_H) || defined(HAVE_OPENSSL_FIPS_MODE_SET_API)
+    if (_heim_openssl_fips_enabled())
+        return NULL;
+#endif
+    return EVP_DEF_OP(hcrypto, rc2_cbc);
+#endif
     return EVP_DEF_OP(HCRYPTO_DEF_PROVIDER, rc2_cbc);
 }
 
@@ -1064,6 +1084,13 @@ const EVP_CIPHER *
 EVP_rc2_40_cbc(void)
 {
     hcrypto_validate();
+#if defined(HCRYPTO_DEF_PROVIDER_IS_OPENSSL) && defined(HAVE_OPENSSL_30)
+#if defined(HAVE_OPENSSL_FIPS_H) || defined(HAVE_OPENSSL_FIPS_MODE_SET_API)
+    if (_heim_openssl_fips_enabled())
+        return NULL;
+#endif
+    return EVP_DEF_OP(hcrypto, rc2_40_cbc);
+#endif
     return EVP_DEF_OP(HCRYPTO_DEF_PROVIDER, rc2_40_cbc);
 }
 
@@ -1079,6 +1106,13 @@ const EVP_CIPHER *
 EVP_rc2_64_cbc(void)
 {
     hcrypto_validate();
+#if defined(HCRYPTO_DEF_PROVIDER_IS_OPENSSL) && defined(HAVE_OPENSSL_30)
+#if defined(HAVE_OPENSSL_FIPS_H) || defined(HAVE_OPENSSL_FIPS_MODE_SET_API)
+    if (_heim_openssl_fips_enabled())
+        return NULL;
+#endif
+    return EVP_DEF_OP(hcrypto, rc2_64_cbc);
+#endif
     return EVP_DEF_OP(HCRYPTO_DEF_PROVIDER, rc2_64_cbc);
 }
 
@@ -1094,6 +1128,13 @@ const EVP_CIPHER *
 EVP_rc4(void)
 {
     hcrypto_validate();
+#if defined(HCRYPTO_DEF_PROVIDER_IS_OPENSSL) && defined(HAVE_OPENSSL_30)
+#if defined(HAVE_OPENSSL_FIPS_H) || defined(HAVE_OPENSSL_FIPS_MODE_SET_API)
+    if (_heim_openssl_fips_enabled())
+        return NULL;
+#endif
+    return EVP_DEF_OP(hcrypto, rc4);
+#endif
     return EVP_DEF_OP(HCRYPTO_DEF_PROVIDER, rc4);
 }
 
@@ -1109,6 +1150,13 @@ const EVP_CIPHER *
 EVP_rc4_40(void)
 {
     hcrypto_validate();
+#if defined(HCRYPTO_DEF_PROVIDER_IS_OPENSSL) && defined(HAVE_OPENSSL_30)
+#if defined(HAVE_OPENSSL_FIPS_H) || defined(HAVE_OPENSSL_FIPS_MODE_SET_API)
+    if (_heim_openssl_fips_enabled())
+        return NULL;
+#endif
+    return EVP_DEF_OP(hcrypto, rc4_40);
+#endif
     return EVP_DEF_OP(HCRYPTO_DEF_PROVIDER, rc4_40);
 }
 
@@ -1124,6 +1172,13 @@ const EVP_CIPHER *
 EVP_des_cbc(void)
 {
     hcrypto_validate();
+#if defined(HCRYPTO_DEF_PROVIDER_IS_OPENSSL) && defined(HAVE_OPENSSL_30)
+#if defined(HAVE_OPENSSL_FIPS_H) || defined(HAVE_OPENSSL_FIPS_MODE_SET_API)
+    if (_heim_openssl_fips_enabled())
+        return NULL;
+#endif
+    return EVP_DEF_OP(hcrypto, des_cbc);
+#endif
     return EVP_DEF_OP(HCRYPTO_DEF_PROVIDER, des_cbc);
 }
 
diff --git a/lib/hcrypto/evp.h b/lib/hcrypto/evp.h
index 820c500f42..7019abafd6 100644
--- a/lib/hcrypto/evp.h
+++ b/lib/hcrypto/evp.h
@@ -193,7 +193,7 @@ struct hc_CIPHER_CTX {
     unsigned long flags;
     void *cipher_data;
     int final_used;
-    int block_mask;
+    unsigned block_mask;
     unsigned char final[EVP_MAX_BLOCK_LENGTH];
 };
 
diff --git a/lib/hcrypto/test_cipher.c b/lib/hcrypto/test_cipher.c
index 26bf42c1b0..f648b657a6 100644
--- a/lib/hcrypto/test_cipher.c
+++ b/lib/hcrypto/test_cipher.c
@@ -421,14 +421,8 @@ main(int argc, char **argv)
 	ret += test_cipher(i, EVP_ossl_aes_256_cbc(), &aes_tests[i]);
     for (i = 0; i < sizeof(aes_cfb_tests)/sizeof(aes_cfb_tests[0]); i++)
 	ret += test_cipher(i, EVP_ossl_aes_128_cfb8(), &aes_cfb_tests[i]);
-    for (i = 0; i < sizeof(rc2_tests)/sizeof(rc2_tests[0]); i++)
-	ret += test_cipher(i, EVP_ossl_rc2_cbc(), &rc2_tests[i]);
-    for (i = 0; i < sizeof(rc2_40_tests)/sizeof(rc2_40_tests[0]); i++)
-	ret += test_cipher(i, EVP_ossl_rc2_40_cbc(), &rc2_40_tests[i]);
     for (i = 0; i < sizeof(des_ede3_tests)/sizeof(des_ede3_tests[0]); i++)
 	ret += test_cipher(i, EVP_ossl_des_ede3_cbc(), &des_ede3_tests[i]);
-    for (i = 0; i < sizeof(rc4_tests)/sizeof(rc4_tests[0]); i++)
-	ret += test_cipher(i, EVP_ossl_rc4(), &rc4_tests[i]);
 #endif /* PKCS11_MODULE_PATH */
 
     return ret;
diff --git a/lib/hcrypto/test_crypto.in b/lib/hcrypto/test_crypto.in
index d5b3893164..91c4d8f109 100644
--- a/lib/hcrypto/test_crypto.in
+++ b/lib/hcrypto/test_crypto.in
@@ -82,11 +82,11 @@ for a in unix fortuna egd w32crypto ;do
 	    { echo "rand output same!" ; exit 1; }
 done
 
-./example_evp_cipher 1 ${srcdir}/test_crypto.in test-out-1 || \
-    { echo "1 failed" ; exit 1; }
-
-for a in 7 15 16 17 31 32 33 ; do
-    ./example_evp_cipher $a ${srcdir}/test_crypto.in test-out-$a
+for a in 1 7 15 16 17 31 32 33 ; do
+    ./example_evp_cipher $a ${srcdir}/test_crypto.in test-out-$a ||
+        { echo "$s failed" ; exit 1; }
+done
+for a in   7 15 16 17 31 32 33 ; do
     cmp test-out-1 test-out-$a || { echo "cmp $a failed" ; exit 1; }
 done
 

From 6aab05177295e2bc79ec81311eb622b1e9329fbb Mon Sep 17 00:00:00 2001
From: Nicolas Williams <nico@twosigma.com>
Date: Sun, 16 Aug 2020 16:58:51 -0500
Subject: [PATCH 06/12] hx509: Add hx509_enomem()

---
 lib/hx509/error.c | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/lib/hx509/error.c b/lib/hx509/error.c
index be09414bff..58abf0f003 100644
--- a/lib/hx509/error.c
+++ b/lib/hx509/error.c
@@ -119,6 +119,21 @@ hx509_set_error_string(hx509_context context, int flags, int code,
     va_end(ap);
 }
 
+/**
+ * Sets ENOMEM as the error on a hx509 context.
+ *
+ * @param context A hx509 context.
+ *
+ * @ingroup hx509_error
+ */
+
+int
+hx509_enomem(hx509_context context)
+{
+    hx509_set_error_string(context, 0, ENOMEM, "Out of memory");
+    return ENOMEM;
+}
+
 /**
  * Get an error string from context associated with error_code.
  *

From 917e18ba53c822e7b679dc36a7a0c205801b85ca Mon Sep 17 00:00:00 2001
From: Nicolas Williams <nico@twosigma.com>
Date: Thu, 4 Jul 2019 17:57:18 -0500
Subject: [PATCH 07/12] hx509: Add PKCS#8 private key format option

---
 lib/hx509/crypto.c | 66 ++++++++++++++++++++++++++++++++++++++++++++++
 lib/hx509/hx509.h  |  3 ++-
 2 files changed, 68 insertions(+), 1 deletion(-)

diff --git a/lib/hx509/crypto.c b/lib/hx509/crypto.c
index 0df91699b5..84382d8ad3 100644
--- a/lib/hx509/crypto.c
+++ b/lib/hx509/crypto.c
@@ -1302,6 +1302,34 @@ hx509_parse_private_key(hx509_context context,
 
     *private_key = NULL;
 
+    if (format == HX509_KEY_FORMAT_PKCS8) {
+        PKCS8PrivateKeyInfo ki;
+        hx509_private_key key;
+
+        ret = decode_PKCS8PrivateKeyInfo(data, len, &ki, NULL);
+        if (ret) {
+	    hx509_set_error_string(context, 0, HX509_PARSING_KEY_FAILED,
+				   "Failed to parse PKCS#8-encoded private "
+                                   "key");
+	    return HX509_PARSING_KEY_FAILED;
+        }
+
+        /* Re-enter to parse DER-encoded key from PKCS#8 envelope */
+        ret = hx509_parse_private_key(context, &ki.privateKeyAlgorithm,
+                                      ki.privateKey.data, ki.privateKey.length,
+                                      HX509_KEY_FORMAT_DER, &key);
+        free_PKCS8PrivateKeyInfo(&ki);
+        if (ret) {
+	    hx509_set_error_string(context, 0, HX509_PARSING_KEY_FAILED,
+				   "Failed to parse RSA key from PKCS#8 "
+                                   "envelope");
+	    return HX509_PARSING_KEY_FAILED;
+        }
+
+        *private_key = key;
+        return ret;
+    }
+
     ops = hx509_find_private_alg(&keyai->algorithm);
     if (ops == NULL) {
 	hx509_clear_error_string(context);
@@ -1601,6 +1629,44 @@ _hx509_private_key_export(hx509_context context,
 	hx509_clear_error_string(context);
 	return HX509_UNIMPLEMENTED_OPERATION;
     }
+    if (format == HX509_KEY_FORMAT_PKCS8) {
+        PKCS8PrivateKeyInfo ki;
+        size_t size;
+        int ret;
+
+        memset(&ki, 0, sizeof(ki));
+        ki.attributes = NULL; /* No localKeyId needed */
+        ki.privateKey.data = NULL;
+        ki.privateKeyAlgorithm.algorithm.components = NULL;
+        ret = der_parse_hex_heim_integer("00", &ki.version);
+        if (ret == 0)
+            ret = _hx509_private_key_oid(context, key,
+                                         &ki.privateKeyAlgorithm.algorithm);
+        if (ret == 0)
+            /* Re-enter */
+            ret = _hx509_private_key_export(context, key, HX509_KEY_FORMAT_DER,
+                                            &ki.privateKey);
+
+        /*
+         * XXX To set ki.privateKeyAlgorithm.parameters we'll need to either
+         * move this code into the *key->ops->export() functions, or expand
+         * their signature to allow them to set it for us, or add a method to
+         * hx509_private_key_ops that allows us to get the parameters from the
+         * backend.
+         */
+        ki.privateKeyAlgorithm.parameters = NULL;
+
+        if (ret == 0)
+            ASN1_MALLOC_ENCODE(PKCS8PrivateKeyInfo, data->data, data->length,
+                               &ki, &size, ret);
+        free_PKCS8PrivateKeyInfo(&ki);
+        if (ret == 0 && size != data->length)
+            ret = EINVAL;
+        if (ret)
+            hx509_set_error_string(context, 0, ret,
+                                   "Private key PKCS#8 encoding failed");
+        return ret;
+    }
     return (*key->ops->export)(context, key, format, data);
 }
 
diff --git a/lib/hx509/hx509.h b/lib/hx509/hx509.h
index 781f4a59cc..10f3c083ab 100644
--- a/lib/hx509/hx509.h
+++ b/lib/hx509/hx509.h
@@ -81,7 +81,8 @@ enum {
 enum {
     HX509_KEY_FORMAT_GUESS = 0,
     HX509_KEY_FORMAT_DER = 1,
-    HX509_KEY_FORMAT_WIN_BACKUPKEY = 2
+    HX509_KEY_FORMAT_WIN_BACKUPKEY = 2,
+    HX509_KEY_FORMAT_PKCS8 = 3,
 };
 typedef uint32_t hx509_key_format_t;
 

From 7e91cd0123c9b7f216cd0447456cc27dfc3341f5 Mon Sep 17 00:00:00 2001
From: Nicolas Williams <nico@twosigma.com>
Date: Sat, 19 Nov 2022 23:43:27 -0600
Subject: [PATCH 08/12] hx509: Pass PKCS#8 keys to lower layers

OpenSSL's d2i_ECPrivateKey() is deprecated, so we have to use
d2i_PrivateKey(), but d2i_PrivateKey() wants the whole PKCS#8 blob so it
can know what kind of key it is.  So we need to let the hx509 EC layer
get that blob.  The internal APIs need some refactoring, so for now we
use a hack where we try to parse the private key with and without the
PKCS#8 wrapper.

(cherry picked from commit cce8ae99274619daa9c765490757e771e9d04a64)
---
 lib/hx509/collector.c | 10 ++++++++++
 lib/hx509/crypto.c    | 46 ++++++++++++++++++++-----------------------
 2 files changed, 31 insertions(+), 25 deletions(-)

diff --git a/lib/hx509/collector.c b/lib/hx509/collector.c
index 15f8163f80..d02f07095c 100644
--- a/lib/hx509/collector.c
+++ b/lib/hx509/collector.c
@@ -147,6 +147,16 @@ _hx509_collector_private_key_add(hx509_context context,
 				       key_data->data, key_data->length,
 				       HX509_KEY_FORMAT_DER,
 				       &key->private_key);
+        if (ret && localKeyId) {
+            int ret2;
+
+            ret2 = hx509_parse_private_key(context, alg,
+                                           localKeyId->data, localKeyId->length,
+                                           HX509_KEY_FORMAT_PKCS8,
+                                           &key->private_key);
+            if (ret2 == 0)
+                ret = 0;
+        }
 	if (ret)
 	    goto out;
     }
diff --git a/lib/hx509/crypto.c b/lib/hx509/crypto.c
index 84382d8ad3..169d35f7c2 100644
--- a/lib/hx509/crypto.c
+++ b/lib/hx509/crypto.c
@@ -1302,10 +1302,27 @@ hx509_parse_private_key(hx509_context context,
 
     *private_key = NULL;
 
-    if (format == HX509_KEY_FORMAT_PKCS8) {
+    ops = hx509_find_private_alg(&keyai->algorithm);
+    if (ops == NULL) {
+	hx509_clear_error_string(context);
+	return HX509_SIG_ALG_NO_SUPPORTED;
+    }
+
+    ret = hx509_private_key_init(private_key, ops, NULL);
+    if (ret) {
+	hx509_set_error_string(context, 0, ret, "out of memory");
+	return ret;
+    }
+
+    ret = (*ops->import)(context, keyai, data, len, format, *private_key);
+    if (ret)
+	hx509_private_key_free(private_key);
+
+    if (ret && format == HX509_KEY_FORMAT_PKCS8) {
         PKCS8PrivateKeyInfo ki;
         hx509_private_key key;
 
+        /* Re-enter to try parsing the DER-encoded key from PKCS#8 envelope */
         ret = decode_PKCS8PrivateKeyInfo(data, len, &ki, NULL);
         if (ret) {
 	    hx509_set_error_string(context, 0, HX509_PARSING_KEY_FAILED,
@@ -1313,39 +1330,18 @@ hx509_parse_private_key(hx509_context context,
                                    "key");
 	    return HX509_PARSING_KEY_FAILED;
         }
-
-        /* Re-enter to parse DER-encoded key from PKCS#8 envelope */
         ret = hx509_parse_private_key(context, &ki.privateKeyAlgorithm,
                                       ki.privateKey.data, ki.privateKey.length,
                                       HX509_KEY_FORMAT_DER, &key);
         free_PKCS8PrivateKeyInfo(&ki);
         if (ret) {
-	    hx509_set_error_string(context, 0, HX509_PARSING_KEY_FAILED,
-				   "Failed to parse RSA key from PKCS#8 "
+            hx509_set_error_string(context, 0, HX509_PARSING_KEY_FAILED,
+                                   "Failed to parse RSA key from PKCS#8 "
                                    "envelope");
-	    return HX509_PARSING_KEY_FAILED;
+            return HX509_PARSING_KEY_FAILED;
         }
-
         *private_key = key;
-        return ret;
-    }
-
-    ops = hx509_find_private_alg(&keyai->algorithm);
-    if (ops == NULL) {
-	hx509_clear_error_string(context);
-	return HX509_SIG_ALG_NO_SUPPORTED;
     }
-
-    ret = hx509_private_key_init(private_key, ops, NULL);
-    if (ret) {
-	hx509_set_error_string(context, 0, ret, "out of memory");
-	return ret;
-    }
-
-    ret = (*ops->import)(context, keyai, data, len, format, *private_key);
-    if (ret)
-	hx509_private_key_free(private_key);
-
     return ret;
 }
 

From f266859ce927571abd0feeb478cd2d4952382ae8 Mon Sep 17 00:00:00 2001
From: Nicolas Williams <nico@twosigma.com>
Date: Tue, 22 Nov 2022 11:57:49 -0500
Subject: [PATCH 09/12] hx509: PKCS#12 missing error check

lib/hx509/test_req.in change left out due to conflicts.

(Partial cherry-pick from commit 0d5b2381861f14fed24be2ca73592a06d17e5adc)
---
 lib/hx509/ks_p12.c | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/lib/hx509/ks_p12.c b/lib/hx509/ks_p12.c
index b7df0be32a..941b104137 100644
--- a/lib/hx509/ks_p12.c
+++ b/lib/hx509/ks_p12.c
@@ -84,14 +84,14 @@ keyBag_parser(hx509_context context,
     if (ret)
 	return ret;
 
-    _hx509_collector_private_key_add(context,
-				     c,
-				     &ki.privateKeyAlgorithm,
-				     NULL,
-				     &ki.privateKey,
-				     os);
+    ret = _hx509_collector_private_key_add(context,
+                                           c,
+                                           &ki.privateKeyAlgorithm,
+                                           NULL,
+                                           &ki.privateKey,
+                                           os);
     free_PKCS8PrivateKeyInfo(&ki);
-    return 0;
+    return ret;
 }
 
 static int

From 55c186b86ee1a93caa6bffe87455f9c159575cd3 Mon Sep 17 00:00:00 2001
From: Jeffrey Altman <jaltman@auristor.com>
Date: Tue, 22 Nov 2022 12:15:37 -0500
Subject: [PATCH 10/12] hx509: OpenSSL 3.0 support

(cherry-picked from commit 264f0bd1a2477640143e5d11fd4027cbdcd619bf)
---
 lib/hx509/crypto-ec.c          | 506 ++++++++++++++++++++++++++-------
 lib/hx509/libhx509-exports.def |   5 +
 lib/hx509/version-script.map   |   2 +-
 lib/libedit/config.h.in        | 100 +++++--
 4 files changed, 493 insertions(+), 120 deletions(-)

diff --git a/lib/hx509/crypto-ec.c b/lib/hx509/crypto-ec.c
index 4777171cae..0f3c1f14c4 100644
--- a/lib/hx509/crypto-ec.c
+++ b/lib/hx509/crypto-ec.c
@@ -34,11 +34,15 @@
 #include <config.h>
 
 #ifdef HAVE_HCRYPTO_W_OPENSSL
+#include <openssl/evp.h>
 #include <openssl/ec.h>
 #include <openssl/ecdsa.h>
 #include <openssl/rsa.h>
 #include <openssl/bn.h>
 #include <openssl/objects.h>
+#ifdef HAVE_OPENSSL_30
+#include <openssl/core_names.h>
+#endif
 #define HEIM_NO_CRYPTO_HDRS
 #endif /* HAVE_HCRYPTO_W_OPENSSL */
 
@@ -53,43 +57,50 @@ void
 _hx509_private_eckey_free(void *eckey)
 {
 #ifdef HAVE_HCRYPTO_W_OPENSSL
+#ifdef HAVE_OPENSSL_30
+    EVP_PKEY_free(eckey);
+#else
     EC_KEY_free(eckey);
 #endif
+#endif
 }
 
 #ifdef HAVE_HCRYPTO_W_OPENSSL
-static int
-heim_oid2ecnid(heim_oid *oid)
-{
-    /*
-     * Now map to openssl OID fun
-     */
-
-    if (der_heim_oid_cmp(oid, ASN1_OID_ID_EC_GROUP_SECP256R1) == 0)
-	return NID_X9_62_prime256v1;
+static struct oid2nid_st {
+    const heim_oid *oid;
+    int nid;
+} oid2nid[] = {
+    { ASN1_OID_ID_EC_GROUP_SECP256R1, NID_X9_62_prime256v1 },
 #ifdef NID_secp521r1
-    else if (der_heim_oid_cmp(oid, ASN1_OID_ID_EC_GROUP_SECP521R1) == 0)
-        return NID_secp521r1;
+    { ASN1_OID_ID_EC_GROUP_SECP521R1, NID_secp521r1 },
 #endif
 #ifdef NID_secp384r1
-    else if (der_heim_oid_cmp(oid, ASN1_OID_ID_EC_GROUP_SECP384R1) == 0)
-        return NID_secp384r1;
+    { ASN1_OID_ID_EC_GROUP_SECP384R1, NID_secp384r1 },
 #endif
 #ifdef NID_secp160r1
-    else if (der_heim_oid_cmp(oid, ASN1_OID_ID_EC_GROUP_SECP160R1) == 0)
-	return NID_secp160r1;
+    { ASN1_OID_ID_EC_GROUP_SECP160R1, NID_secp160r1 },
 #endif
 #ifdef NID_secp160r2
-    else if (der_heim_oid_cmp(oid, ASN1_OID_ID_EC_GROUP_SECP160R2) == 0)
-	return NID_secp160r2;
+    { ASN1_OID_ID_EC_GROUP_SECP160R2, NID_secp160r2 },
 #endif
+    /* XXX Add more!  Add X25519! */
+};
+
+int
+_hx509_ossl_oid2nid(heim_oid *oid)
+{
+    size_t i;
 
+    for (i = 0; i < sizeof(oid2nid)/sizeof(oid2nid[0]); i++)
+        if (der_heim_oid_cmp(oid, oid2nid[i].oid) == 0)
+            return oid2nid[i].nid;
     return NID_undef;
 }
 
 static int
-parse_ECParameters(hx509_context context,
-		   heim_octet_string *parameters, int *nid)
+ECParameters2nid(hx509_context context,
+                 heim_octet_string *parameters,
+                 int *nid)
 {
     ECParameters ecparam;
     size_t size;
@@ -117,7 +128,7 @@ parse_ECParameters(hx509_context context,
 	return HX509_CRYPTO_SIG_INVALID_FORMAT;
     }
 
-    *nid = heim_oid2ecnid(&ecparam.u.namedCurve);
+    *nid = _hx509_ossl_oid2nid(&ecparam.u.namedCurve);
     free_ECParameters(&ecparam);
     if (*nid == NID_undef) {
 	hx509_set_error_string(context, 0, ret,
@@ -127,6 +138,39 @@ parse_ECParameters(hx509_context context,
     return 0;
 }
 
+#ifdef HAVE_OPENSSL_30
+static const EVP_MD *
+signature_alg2digest_evp_md(hx509_context context,
+                            const AlgorithmIdentifier *digest_alg)
+{
+    if ((&digest_alg->algorithm == &asn1_oid_id_sha512 ||
+         der_heim_oid_cmp(&digest_alg->algorithm, &asn1_oid_id_sha512) == 0))
+        return EVP_sha512();
+    if ((&digest_alg->algorithm == &asn1_oid_id_sha384 ||
+         der_heim_oid_cmp(&digest_alg->algorithm, &asn1_oid_id_sha384) == 0))
+        return EVP_sha384();
+    if ((&digest_alg->algorithm == &asn1_oid_id_sha256 ||
+         der_heim_oid_cmp(&digest_alg->algorithm, &asn1_oid_id_sha256) == 0))
+        return EVP_sha256();
+    if ((&digest_alg->algorithm == &asn1_oid_id_secsig_sha_1 ||
+         der_heim_oid_cmp(&digest_alg->algorithm, &asn1_oid_id_secsig_sha_1) == 0))
+        return EVP_sha1();
+    if ((&digest_alg->algorithm == &asn1_oid_id_rsa_digest_md5 ||
+         der_heim_oid_cmp(&digest_alg->algorithm,
+                          &asn1_oid_id_rsa_digest_md5) == 0))
+        return EVP_md5();
+
+    /*
+     * XXX Decode the `digest_alg->algorithm' OID and include it in the error
+     * message.
+     */
+    hx509_set_error_string(context, 0, EINVAL,
+                           "Digest algorithm not found");
+    return NULL;
+}
+#endif
+
+
 
 /*
  *
@@ -140,6 +184,106 @@ ecdsa_verify_signature(hx509_context context,
 		       const heim_octet_string *data,
 		       const heim_octet_string *sig)
 {
+#ifdef HAVE_OPENSSL_30
+    const AlgorithmIdentifier *digest_alg = sig_alg->digest_alg;
+    const EVP_MD *md = signature_alg2digest_evp_md(context, digest_alg);
+    const SubjectPublicKeyInfo *spi;
+    const char *curve_sn = NULL; /* sn == short name in OpenSSL parlance */
+    OSSL_PARAM params[2];
+    EVP_PKEY_CTX *pctx = NULL;
+    EVP_MD_CTX *mdctx = NULL;
+    EVP_PKEY *template = NULL;
+    EVP_PKEY *public = NULL;
+    const unsigned char *p;
+    size_t len;
+    char *curve_sn_dup = NULL;
+    int groupnid;
+    int ret = 0;
+
+    spi = &signer->tbsCertificate.subjectPublicKeyInfo;
+    if (der_heim_oid_cmp(&spi->algorithm.algorithm,
+                         ASN1_OID_ID_ECPUBLICKEY) != 0)
+        hx509_set_error_string(context, 0,
+                               ret =  HX509_CRYPTO_SIG_INVALID_FORMAT,
+                               /* XXX Include the OID in the message */
+                               "Unsupported subjectPublicKey algorithm");
+    if (ret == 0)
+        ret = ECParameters2nid(context, spi->algorithm.parameters, &groupnid);
+    if (ret == 0 && (curve_sn = OBJ_nid2sn(groupnid)) == NULL)
+        hx509_set_error_string(context, 0,
+                               ret = HX509_CRYPTO_SIG_INVALID_FORMAT,
+                               "Could not resolve curve NID %d to its short name",
+                               groupnid);
+    if (ret == 0 && (curve_sn_dup = strdup(curve_sn)) == NULL)
+        ret = hx509_enomem(context);
+    if (ret == 0 && (mdctx = EVP_MD_CTX_new()) == NULL)
+        ret = hx509_enomem(context);
+
+    /*
+     * In order for d2i_PublicKey() to work we need to create a template key
+     * that has the curve parameters for the subjectPublicKey.
+     *
+     * Or maybe we could learn to use the OSSL_DECODER(3) API.  But this works,
+     * at least until OpenSSL deprecates d2i_PublicKey() and forces us to use
+     * OSSL_DECODER(3).
+     */
+    if (ret == 0) {
+        /*
+         * Apparently there's no error checking to be done here?  Why does
+         * OSSL_PARAM_construct_utf8_string() want a non-const for the value?
+         * Is that a bug in OpenSSL?
+         */
+        params[0] = OSSL_PARAM_construct_utf8_string(OSSL_PKEY_PARAM_GROUP_NAME,
+                                                     curve_sn_dup, 0);
+        params[1] = OSSL_PARAM_construct_end();
+
+        if ((pctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL)) == NULL)
+            ret = hx509_enomem(context);
+    }
+    if (ret == 0 && EVP_PKEY_fromdata_init(pctx) != 1)
+        ret = hx509_enomem(context);
+    if (ret == 0 &&
+        EVP_PKEY_fromdata(pctx, &template,
+                          OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS, params) != 1)
+        hx509_set_error_string(context, 0,
+                               ret = HX509_CRYPTO_SIG_INVALID_FORMAT,
+                               "Could not set up to parse key for curve %s",
+                               curve_sn);
+
+    /* Finally we can decode the subjectPublicKey */
+    p = spi->subjectPublicKey.data;
+    len = spi->subjectPublicKey.length / 8;
+    if (ret == 0 &&
+        (public = d2i_PublicKey(EVP_PKEY_EC, &template, &p, len)) == NULL)
+        ret = HX509_CRYPTO_SIG_INVALID_FORMAT;
+
+    /* EVP_DigestVerifyInit() will allocate a new pctx */
+    EVP_PKEY_CTX_free(pctx);
+    pctx = NULL;
+
+    if (ret == 0 &&
+        EVP_DigestVerifyInit(mdctx, &pctx, md, NULL, public) != 1)
+        hx509_set_error_string(context, 0,
+                               ret = HX509_CRYPTO_SIG_INVALID_FORMAT,
+                               "Could not initialize "
+                               "OpenSSL signature verification");
+    if (ret == 0 &&
+        EVP_DigestVerifyUpdate(mdctx, data->data, data->length) != 1)
+        hx509_set_error_string(context, 0,
+                               ret = HX509_CRYPTO_SIG_INVALID_FORMAT,
+                               "Could not initialize "
+                               "OpenSSL signature verification");
+    if (ret == 0 &&
+        EVP_DigestVerifyFinal(mdctx, sig->data, sig->length) != 1)
+        hx509_set_error_string(context, 0,
+                               ret = HX509_CRYPTO_SIG_INVALID_FORMAT,
+                               "Signature verification failed");
+
+    EVP_MD_CTX_free(mdctx);
+    EVP_PKEY_free(template);
+    free(curve_sn_dup);
+    return ret;
+#else
     const AlgorithmIdentifier *digest_alg;
     const SubjectPublicKeyInfo *spi;
     heim_octet_string digest;
@@ -153,28 +297,28 @@ ecdsa_verify_signature(hx509_context context,
     digest_alg = sig_alg->digest_alg;
 
     ret = _hx509_create_signature(context,
-				  NULL,
-				  digest_alg,
-				  data,
-				  NULL,
-				  &digest);
+                                 NULL,
+                                 digest_alg,
+                                 data,
+                                 NULL,
+                                 &digest);
     if (ret)
-	return ret;
+       return ret;
 
     /* set up EC KEY */
     spi = &signer->tbsCertificate.subjectPublicKeyInfo;
 
     if (der_heim_oid_cmp(&spi->algorithm.algorithm, ASN1_OID_ID_ECPUBLICKEY) != 0)
-	return HX509_CRYPTO_SIG_INVALID_FORMAT;
+       return HX509_CRYPTO_SIG_INVALID_FORMAT;
 
     /*
      * Find the group id
      */
 
-    ret = parse_ECParameters(context, spi->algorithm.parameters, &groupnid);
+    ret = ECParameters2nid(context, spi->algorithm.parameters, &groupnid);
     if (ret) {
-	der_free_octet_string(&digest);
-	return ret;
+       der_free_octet_string(&digest);
+       return ret;
     }
 
     /*
@@ -190,20 +334,21 @@ ecdsa_verify_signature(hx509_context context,
     len = spi->subjectPublicKey.length / 8;
 
     if (o2i_ECPublicKey(&key, &p, len) == NULL) {
-	EC_KEY_free(key);
-	return HX509_CRYPTO_SIG_INVALID_FORMAT;
+       EC_KEY_free(key);
+       return HX509_CRYPTO_SIG_INVALID_FORMAT;
     }
 
     ret = ECDSA_verify(-1, digest.data, digest.length,
-		       sig->data, sig->length, key);
+                      sig->data, sig->length, key);
     der_free_octet_string(&digest);
     EC_KEY_free(key);
     if (ret != 1) {
-	ret = HX509_CRYPTO_SIG_INVALID_FORMAT;
-	return ret;
+       ret = HX509_CRYPTO_SIG_INVALID_FORMAT;
+       return ret;
     }
 
     return 0;
+#endif
 }
 
 static int
@@ -215,6 +360,56 @@ ecdsa_create_signature(hx509_context context,
 		       AlgorithmIdentifier *signatureAlgorithm,
 		       heim_octet_string *sig)
 {
+#ifdef HAVE_OPENSSL_30
+    const AlgorithmIdentifier *digest_alg = sig_alg->digest_alg;
+    const EVP_MD *md = signature_alg2digest_evp_md(context, digest_alg);
+    EVP_MD_CTX *mdctx = NULL;
+    EVP_PKEY_CTX *pctx = NULL;
+    const heim_oid *sig_oid;
+    int ret = 0;
+
+    sig->data = NULL;
+    sig->length = 0;
+    if (signer->ops && der_heim_oid_cmp(signer->ops->key_oid, ASN1_OID_ID_ECPUBLICKEY) != 0)
+	_hx509_abort("internal error passing private key to wrong ops");
+
+    sig_oid = sig_alg->sig_oid;
+    digest_alg = sig_alg->digest_alg;
+
+    if (signatureAlgorithm)
+        ret = _hx509_set_digest_alg(signatureAlgorithm, sig_oid,
+                                    "\x05\x00", 2);
+    mdctx = EVP_MD_CTX_new();
+    if (mdctx == NULL)
+        ret = hx509_enomem(context);
+    if (ret == 0 && EVP_DigestSignInit(mdctx, &pctx, md, NULL,
+                                       signer->private_key.ecdsa) != 1)
+        ret = HX509_CMS_FAILED_CREATE_SIGATURE;
+    if (ret == 0 && EVP_DigestSignUpdate(mdctx, data->data, data->length) != 1)
+        ret = HX509_CMS_FAILED_CREATE_SIGATURE;
+    if (ret == 0 && EVP_DigestSignFinal(mdctx, NULL, &sig->length) != 1)
+        ret = HX509_CMS_FAILED_CREATE_SIGATURE;
+    if (ret == 0 && (sig->data = malloc(sig->length)) == NULL)
+        ret = hx509_enomem(context);
+    if (ret == 0 && EVP_DigestSignFinal(mdctx, sig->data, &sig->length) != 1)
+        ret = HX509_CMS_FAILED_CREATE_SIGATURE;
+
+    if (ret == HX509_CMS_FAILED_CREATE_SIGATURE) {
+        /* XXX Extract error detail from OpenSSL */
+	hx509_set_error_string(context, 0, ret,
+			       "ECDSA sign failed");
+    }
+
+    if (ret) {
+        if (signatureAlgorithm)
+            free_AlgorithmIdentifier(signatureAlgorithm);
+        free(sig->data);
+        sig->data = NULL;
+        sig->length = 0;
+    }
+    EVP_MD_CTX_free(mdctx);
+    return ret;
+#else
     const AlgorithmIdentifier *digest_alg;
     heim_octet_string indata;
     const heim_oid *sig_oid;
@@ -222,7 +417,7 @@ ecdsa_create_signature(hx509_context context,
     int ret;
 
     if (signer->ops && der_heim_oid_cmp(signer->ops->key_oid, ASN1_OID_ID_ECPUBLICKEY) != 0)
-	_hx509_abort("internal error passing private key to wrong ops");
+        _hx509_abort("internal error passing private key to wrong ops");
 
     sig_oid = sig_alg->sig_oid;
     digest_alg = sig_alg->digest_alg;
@@ -230,59 +425,63 @@ ecdsa_create_signature(hx509_context context,
     if (signatureAlgorithm) {
         ret = _hx509_set_digest_alg(signatureAlgorithm, sig_oid,
                                     "\x05\x00", 2);
-	if (ret) {
-	    hx509_clear_error_string(context);
-	    return ret;
-	}
+        if (ret) {
+            hx509_clear_error_string(context);
+            return ret;
+        }
     }
 
     ret = _hx509_create_signature(context,
-				  NULL,
-				  digest_alg,
-				  data,
-				  NULL,
-				  &indata);
+                                  NULL,
+                                  digest_alg,
+                                  data,
+                                  NULL,
+                                  &indata);
     if (ret)
-	goto error;
+        goto error;
 
     sig->length = ECDSA_size(signer->private_key.ecdsa);
     sig->data = malloc(sig->length);
     if (sig->data == NULL) {
-	der_free_octet_string(&indata);
-	ret = ENOMEM;
-	hx509_set_error_string(context, 0, ret, "out of memory");
-	goto error;
+        der_free_octet_string(&indata);
+        ret = ENOMEM;
+        hx509_set_error_string(context, 0, ret, "out of memory");
+        goto error;
     }
 
     siglen = sig->length;
 
     ret = ECDSA_sign(-1, indata.data, indata.length,
-		     sig->data, &siglen, signer->private_key.ecdsa);
+                     sig->data, &siglen, signer->private_key.ecdsa);
     der_free_octet_string(&indata);
     if (ret != 1) {
-	ret = HX509_CMS_FAILED_CREATE_SIGATURE;
-	hx509_set_error_string(context, 0, ret,
-			       "ECDSA sign failed: %d", ret);
-	goto error;
+        ret = HX509_CMS_FAILED_CREATE_SIGATURE;
+        hx509_set_error_string(context, 0, ret,
+                               "ECDSA sign failed: %d", ret);
+        goto error;
     }
     if (siglen > sig->length)
-	_hx509_abort("ECDSA signature prelen longer the output len");
+        _hx509_abort("ECDSA signature prelen longer the output len");
 
     sig->length = siglen;
 
     return 0;
- error:
+error:
     if (signatureAlgorithm)
-	free_AlgorithmIdentifier(signatureAlgorithm);
+        free_AlgorithmIdentifier(signatureAlgorithm);
     return ret;
+#endif
 }
 
 static int
 ecdsa_available(const hx509_private_key signer,
 		const AlgorithmIdentifier *sig_alg)
 {
+#ifdef HAVE_OPENSSL_30
     const struct signature_alg *sig;
-    const EC_GROUP *group;
+    size_t group_name_len = 0;
+    char group_name_buf[96];
+    EC_GROUP *group = NULL;
     BN_CTX *bnctx = NULL;
     BIGNUM *order = NULL;
     int ret = 0;
@@ -291,34 +490,75 @@ ecdsa_available(const hx509_private_key signer,
 	_hx509_abort("internal error passing private key to wrong ops");
 
     sig = _hx509_find_sig_alg(&sig_alg->algorithm);
-
     if (sig == NULL || sig->digest_size == 0)
 	return 0;
 
+    if (EVP_PKEY_get_group_name(signer->private_key.ecdsa, group_name_buf,
+                                sizeof(group_name_buf),
+                                &group_name_len) != 1 ||
+        group_name_len >= sizeof(group_name_buf)) {
+        return 0;
+    }
+    group = EC_GROUP_new_by_curve_name(OBJ_txt2nid(group_name_buf));
+    bnctx = BN_CTX_new();
+    order = BN_new();
+    if (group && bnctx && order &&
+        EC_GROUP_get_order(group, order, bnctx) == 1)
+	ret = 1;
+
+#if 0
+    /*
+     * If anything, require a digest at least as wide as the EC key size
+     *
+     *  if (BN_num_bytes(order) > sig->digest_size)
+     *      ret = 0;
+     */
+#endif
+
+    BN_CTX_free(bnctx);
+    BN_clear_free(order);
+    EC_GROUP_free(group);
+    return ret;
+#else
+    const struct signature_alg *sig;
+    const EC_GROUP *group;
+    BN_CTX *bnctx = NULL;
+    BIGNUM *order = NULL;
+    int ret = 0;
+
+    if (der_heim_oid_cmp(signer->ops->key_oid, &asn1_oid_id_ecPublicKey) != 0)
+       _hx509_abort("internal error passing private key to wrong ops");
+
+    sig = _hx509_find_sig_alg(&sig_alg->algorithm);
+
+    if (sig == NULL || sig->digest_size == 0)
+       return 0;
+
     group = EC_KEY_get0_group(signer->private_key.ecdsa);
     if (group == NULL)
-	return 0;
+       return 0;
 
     bnctx = BN_CTX_new();
     order = BN_new();
     if (order == NULL)
-	goto err;
+       goto err;
 
     if (EC_GROUP_get_order(group, order, bnctx) != 1)
-	goto err;
+       goto err;
 
 #if 0
     /* If anything, require a digest at least as wide as the EC key size */
     if (BN_num_bytes(order) > sig->digest_size)
 #endif
-	ret = 1;
+       ret = 1;
  err:
     if (bnctx)
-	BN_CTX_free(bnctx);
+       BN_CTX_free(bnctx);
     if (order)
-	BN_clear_free(order);
+       BN_clear_free(order);
 
-    return ret;
+     return ret;
+#endif
 }
 
 static int
@@ -347,55 +587,119 @@ ecdsa_private_key_import(hx509_context context,
 			 hx509_key_format_t format,
 			 hx509_private_key private_key)
 {
+#ifdef HAVE_OPENSSL_30
+    const unsigned char *p = data;
+    EVP_PKEY *key = NULL;
+    int ret = 0;
+
+    switch (format) {
+    case HX509_KEY_FORMAT_PKCS8:
+        key = d2i_PrivateKey(EVP_PKEY_EC, NULL, &p, len);
+	if (key == NULL) {
+	    hx509_set_error_string(context, 0, HX509_PARSING_KEY_FAILED,
+				   "Failed to parse EC private key");
+	    return HX509_PARSING_KEY_FAILED;
+	}
+	break;
+
+    default:
+	return HX509_CRYPTO_KEY_FORMAT_UNSUPPORTED;
+    }
+
+    /*
+     * We used to have to call EC_KEY_new(), then EC_KEY_set_group() the group
+     * (curve) on the resulting EC_KEY _before_ we could d2i_ECPrivateKey() the
+     * key, but that's all deprecated in OpenSSL 3.0.
+     *
+     * In fact, it's not clear how ever to assign a group to a private key,
+     * but that's what the documentation for d2i_PrivateKey() says: that
+     * its `EVP_PKEY **' argument must be non-NULL pointing to a key that
+     * has had the group set.
+     *
+     * However, from code inspection it's clear that when the ECParameters
+     * are present in the private key payload passed to d2i_PrivateKey(),
+     * the group will be taken from that.
+     *
+     * What we'll do is that if we have `keyai->parameters' we'll check if the
+     * key we got is for the same group.
+     */
+    if (keyai->parameters) {
+        size_t gname_len = 0;
+        char buf[96];
+        int got_group_nid = NID_undef;
+        int want_groupnid = NID_undef;
+
+        ret = ECParameters2nid(context, keyai->parameters, &want_groupnid);
+        if (ret == 0 &&
+            (EVP_PKEY_get_group_name(key, buf, sizeof(buf), &gname_len) != 1 ||
+             gname_len >= sizeof(buf)))
+            ret = HX509_ALG_NOT_SUPP;
+        if (ret == 0)
+            got_group_nid = OBJ_txt2nid(buf);
+        if (ret == 0 &&
+            (got_group_nid == NID_undef || want_groupnid != got_group_nid))
+            ret = HX509_ALG_NOT_SUPP;
+    }
+
+    if (ret == 0) {
+        private_key->private_key.ecdsa = key;
+        private_key->signature_alg = ASN1_OID_ID_ECDSA_WITH_SHA256;
+        key = NULL;
+    }
+
+    EVP_PKEY_free(key);
+    return ret;
+#else
     const unsigned char *p = data;
     EC_KEY **pkey = NULL;
     EC_KEY *key;
 
     if (keyai->parameters) {
-	EC_GROUP *group;
-	int groupnid;
-	int ret;
-
-	ret = parse_ECParameters(context, keyai->parameters, &groupnid);
-	if (ret)
-	    return ret;
-
-	key = EC_KEY_new();
-	if (key == NULL)
-	    return ENOMEM;
-
-	group = EC_GROUP_new_by_curve_name(groupnid);
-	if (group == NULL) {
-	    EC_KEY_free(key);
-	    return ENOMEM;
-	}
-	EC_GROUP_set_asn1_flag(group, OPENSSL_EC_NAMED_CURVE);
-	if (EC_KEY_set_group(key, group) == 0) {
-	    EC_KEY_free(key);
-	    EC_GROUP_free(group);
-	    return ENOMEM;
-	}
-	EC_GROUP_free(group);
-	pkey = &key;
+       EC_GROUP *group;
+       int groupnid;
+       int ret;
+
+       ret = ECParameters2nid(context, keyai->parameters, &groupnid);
+       if (ret)
+           return ret;
+
+       key = EC_KEY_new();
+       if (key == NULL)
+           return ENOMEM;
+
+       group = EC_GROUP_new_by_curve_name(groupnid);
+       if (group == NULL) {
+           EC_KEY_free(key);
+           return ENOMEM;
+       }
+       EC_GROUP_set_asn1_flag(group, OPENSSL_EC_NAMED_CURVE);
+       if (EC_KEY_set_group(key, group) != 1) {
+           EC_KEY_free(key);
+           EC_GROUP_free(group);
+           return ENOMEM;
+       }
+       EC_GROUP_free(group);
+       pkey = &key;
     }
 
     switch (format) {
     case HX509_KEY_FORMAT_DER:
 
-	private_key->private_key.ecdsa = d2i_ECPrivateKey(pkey, &p, len);
-	if (private_key->private_key.ecdsa == NULL) {
-	    hx509_set_error_string(context, 0, HX509_PARSING_KEY_FAILED,
-				   "Failed to parse EC private key");
-	    return HX509_PARSING_KEY_FAILED;
-	}
-	private_key->signature_alg = ASN1_OID_ID_ECDSA_WITH_SHA256;
-	break;
+       private_key->private_key.ecdsa = d2i_ECPrivateKey(pkey, &p, len);
+       if (private_key->private_key.ecdsa == NULL) {
+           hx509_set_error_string(context, 0, HX509_PARSING_KEY_FAILED,
+                                  "Failed to parse EC private key");
+           return HX509_PARSING_KEY_FAILED;
+       }
+       private_key->signature_alg = ASN1_OID_ID_ECDSA_WITH_SHA256;
+       break;
 
     default:
-	return HX509_CRYPTO_KEY_FORMAT_UNSUPPORTED;
+       return HX509_CRYPTO_KEY_FORMAT_UNSUPPORTED;
     }
 
     return 0;
+#endif
 }
 
 static int
diff --git a/lib/hx509/libhx509-exports.def b/lib/hx509/libhx509-exports.def
index f441773015..8bd98fc349 100644
--- a/lib/hx509/libhx509-exports.def
+++ b/lib/hx509/libhx509-exports.def
@@ -14,6 +14,11 @@ EXPORTS
 	_hx509_generate_private_key_is_ca
 	_hx509_map_file_os
 	_hx509_name_from_Name
+	_hx509_private_key_export
+	_hx509_private_key_exportable
+	_hx509_private_key_get_internal
+	_hx509_private_key_oid
+	_hx509_private_key_ref
 	hx509_private_key2SPKI
 	hx509_private_key_free
 	_hx509_private_key_ref
diff --git a/lib/hx509/version-script.map b/lib/hx509/version-script.map
index f040cd8344..15a8aa7d4d 100644
--- a/lib/hx509/version-script.map
+++ b/lib/hx509/version-script.map
@@ -16,6 +16,7 @@ HEIMDAL_X509_1.2 {
 		_hx509_generate_private_key_is_ca;
 		_hx509_map_file_os;
 		_hx509_name_from_Name;
+		_hx509_ossl_oid2nid;
 		_hx509_private_key_ref;
 		_hx509_request_add_dns_name;
 		_hx509_request_add_email;
@@ -252,4 +253,3 @@ HEIMDAL_X509_1.3 {
 	global:
 		hx509_ca_tbs_set_signature_algorithm;
 };
-
From 7e818fa77eb980a7bddd043c26c3d2028a1c9450 Mon Sep 17 00:00:00 2001
From: Nicolas Williams <nico@twosigma.com>
Date: Sat, 19 Nov 2022 15:09:47 -0600
Subject: [PATCH 11/12] krb5: OpenSSL 3.0 support

(cherry picked from commit 6336cf69d43665a6e97434be453ed895f4b25509)
---
 lib/krb5/pkinit-ec.c | 73 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 73 insertions(+)

diff --git a/lib/krb5/pkinit-ec.c b/lib/krb5/pkinit-ec.c
index 33bc62c8dc..34cefd506f 100644
--- a/lib/krb5/pkinit-ec.c
+++ b/lib/krb5/pkinit-ec.c
@@ -56,6 +56,7 @@
 #include <openssl/ecdh.h>
 #include <openssl/evp.h>
 #include <openssl/bn.h>
+#include <openssl/dh.h>
 #define HEIM_NO_CRYPTO_HDRS
 #endif
 
@@ -125,6 +126,9 @@ _krb5_build_authpack_subjectPK_EC(krb5_context context,
     if (ret)
         return ret;
 
+#ifdef HAVE_OPENSSL_30
+    ctx->u.eckey = EVP_EC_gen(OSSL_EC_curve_nid2name(NID_X9_62_prime256v1));
+#else
     ctx->u.eckey = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
     if (ctx->u.eckey == NULL)
         return krb5_enomem(context);
@@ -132,8 +136,13 @@ _krb5_build_authpack_subjectPK_EC(krb5_context context,
     ret = EC_KEY_generate_key(ctx->u.eckey);
     if (ret != 1)
         return EINVAL;
+#endif
 
+#ifdef HAVE_OPENSSL_30
+    xlen = i2d_PublicKey(ctx->u.eckey, NULL);
+#else
     xlen = i2o_ECPublicKey(ctx->u.eckey, NULL);
+#endif
     if (xlen <= 0)
         return EINVAL;
 
@@ -143,7 +152,11 @@ _krb5_build_authpack_subjectPK_EC(krb5_context context,
 
     a->clientPublicValue->subjectPublicKey.data = p;
 
+#ifdef HAVE_OPENSSL_30
+    xlen = i2d_PublicKey(ctx->u.eckey, &p);
+#else
     xlen = i2o_ECPublicKey(ctx->u.eckey, &p);
+#endif
     if (xlen <= 0) {
         a->clientPublicValue->subjectPublicKey.data = NULL;
         free(p);
@@ -171,6 +184,61 @@ _krb5_pk_rd_pa_reply_ecdh_compute_key(krb5_context context,
                                       int *out_sz)
 {
 #ifdef HAVE_HCRYPTO_W_OPENSSL
+#ifdef HAVE_OPENSSL_30
+    krb5_error_code ret = 0;
+    EVP_PKEY_CTX *pctx = NULL;
+    EVP_PKEY *template = NULL;
+    EVP_PKEY *public = NULL;
+    size_t shared_len = 0;
+
+    if ((template = EVP_PKEY_new()) == NULL)
+        ret = krb5_enomem(context);
+    if (ret == 0 &&
+        EVP_PKEY_copy_parameters(template, ctx->u.eckey) != 1)
+        ret = krb5_enomem(context);
+    if (ret == 0 && (pctx = EVP_PKEY_CTX_new(ctx->u.eckey, NULL)) == NULL)
+        ret = krb5_enomem(context);
+    if (ret == 0 && EVP_PKEY_derive_init(pctx) != 1)
+        ret = krb5_enomem(context);
+    if (ret == 0 &&
+        EVP_PKEY_CTX_set_ecdh_kdf_type(pctx, EVP_PKEY_ECDH_KDF_NONE) != 1)
+        ret = krb5_enomem(context);
+    if (ret == 0 &&
+        (public = d2i_PublicKey(EVP_PKEY_EC, &template, &in, in_sz)) == NULL)
+        krb5_set_error_message(context,
+                               ret = HX509_PARSING_KEY_FAILED,
+                               "PKINIT: Can't parse the KDC's ECDH public key");
+    if (ret == 0 &&
+        EVP_PKEY_derive_set_peer_ex(pctx, public, 1) != 1)
+        krb5_set_error_message(context,
+                               ret = KRB5KRB_ERR_GENERIC,
+                               "Could not derive ECDH shared secret for PKINIT key exchange "
+                               "(EVP_PKEY_derive_set_peer_ex)");
+    if (ret == 0 &&
+        (EVP_PKEY_derive(pctx, NULL, &shared_len) != 1 || shared_len == 0))
+        krb5_set_error_message(context,
+                               ret = KRB5KRB_ERR_GENERIC,
+                               "Could not derive ECDH shared secret for PKINIT key exchange "
+                               "(EVP_PKEY_derive to get length)");
+    if (ret == 0 && shared_len > INT_MAX)
+        krb5_set_error_message(context,
+                               ret = KRB5KRB_ERR_GENERIC,
+                               "Could not derive ECDH shared secret for PKINIT key exchange "
+                               "(shared key too large)");
+    if (ret == 0 && (*out = malloc(shared_len)) == NULL)
+        ret = krb5_enomem(context);
+    if (ret == 0 && EVP_PKEY_derive(pctx, *out, &shared_len) != 1)
+        krb5_set_error_message(context,
+                               ret = KRB5KRB_ERR_GENERIC,
+                               "Could not derive ECDH shared secret for PKINIT key exchange "
+                               "(EVP_PKEY_derive)");
+    if (ret == 0)
+        *out_sz = shared_len;
+    EVP_PKEY_CTX_free(pctx); // move
+    EVP_PKEY_free(template);
+
+    return ret;
+#else
     krb5_error_code ret = 0;
     int dh_gen_keylen;
 
@@ -219,6 +287,7 @@ _krb5_pk_rd_pa_reply_ecdh_compute_key(krb5_context context,
     *out_sz = dh_gen_keylen;
 
     return ret;
+#endif
 #else
     krb5_set_error_message(context, ENOTSUP,
                            N_("PKINIT: ECDH not supported", ""));
@@ -230,8 +299,12 @@ void
 _krb5_pk_eckey_free(void *eckey)
 {
 #ifdef HAVE_HCRYPTO_W_OPENSSL
+#ifdef HAVE_OPENSSL_30
+    EVP_PKEY_free(eckey);
+#else
     EC_KEY_free(eckey);
 #endif
+#endif
 }
 
 #else

From cad7cd5ce178b2e5f03b383c559f980a00b20063 Mon Sep 17 00:00:00 2001
From: Nicolas Williams <nico@twosigma.com>
Date: Sat, 19 Nov 2022 22:03:50 -0600
Subject: [PATCH 12/12] kdc: OpenSSL 3.0 support

(cherry picked from commit cd02c50be56e4345373e009ed6950b9550df94ff)
---
 kdc/pkinit-ec.c | 350 +++++++++++++++++++++++++++++++++++++++---------
 1 file changed, 290 insertions(+), 60 deletions(-)

diff --git a/kdc/pkinit-ec.c b/kdc/pkinit-ec.c
index c718aa7996..31a5fe7dec 100644
--- a/kdc/pkinit-ec.c
+++ b/kdc/pkinit-ec.c
@@ -52,10 +52,16 @@
  */
 
 #ifdef HAVE_HCRYPTO_W_OPENSSL
-#include <openssl/ec.h>
-#include <openssl/ecdh.h>
 #include <openssl/evp.h>
+#include <openssl/ec.h>
+#include <openssl/ecdsa.h>
+#include <openssl/rsa.h>
 #include <openssl/bn.h>
+#include <openssl/dh.h>
+#include <openssl/objects.h>
+#ifdef HAVE_OPENSSL_30
+#include <openssl/core_names.h>
+#endif
 #define HEIM_NO_CRYPTO_HDRS
 #endif /* HAVE_HCRYPTO_W_OPENSSL */
 
@@ -69,37 +75,101 @@
 #include <pkinit_asn1.h>
 
 #include <hx509.h>
-
-#ifdef HAVE_HCRYPTO_W_OPENSSL
-static void
-free_client_ec_param(krb5_context context,
-                     EC_KEY *ec_key_pk,
-                     EC_KEY *ec_key_key)
-{
-    if (ec_key_pk != NULL)
-        EC_KEY_free(ec_key_pk);
-    if (ec_key_key != NULL)
-        EC_KEY_free(ec_key_key);
-}
-#endif
+#include "../lib/hx509/hx_locl.h"
+#include <hx509-private.h>
 
 void
 _kdc_pk_free_client_ec_param(krb5_context context,
-                             void *ec_key_pk,
-                             void *ec_key_key)
+                             void *k0,
+                             void *k1)
 {
 #ifdef HAVE_HCRYPTO_W_OPENSSL
-    free_client_ec_param(context, ec_key_pk, ec_key_key);
+#ifdef HAVE_OPENSSL_30
+    EVP_PKEY_free(k0);
+    EVP_PKEY_free(k1);
+#else
+    EC_KEY_free(k0);
+    EC_KEY_free(k1);
+#endif
 #endif
 }
 
 #ifdef HAVE_HCRYPTO_W_OPENSSL
+#ifdef HAVE_OPENSSL_30
+static krb5_error_code
+generate_ecdh_keyblock_ossl30(krb5_context context,
+                              EVP_PKEY *ec_key_pub,   /* the client's public key */
+                              EVP_PKEY **ec_key_priv, /* the KDC's ephemeral private */
+                              unsigned char **dh_gen_key, /* shared secret */
+                              size_t *dh_gen_keylen)
+{
+    EVP_PKEY_CTX *pctx = NULL;
+    EVP_PKEY *ephemeral = NULL;
+    krb5_error_code ret = 0;
+    unsigned char *p = NULL;
+    size_t size = 0;
+
+    if (ec_key_pub == NULL)
+        /* XXX This seems like an internal error that should be impossible */
+        krb5_set_error_message(context, ret = KRB5KRB_ERR_GENERIC,
+                               "Missing client ECDH key agreement public key");
+    if (ret == 0 &&
+        (ephemeral =
+             EVP_EC_gen(OSSL_EC_curve_nid2name(NID_X9_62_prime256v1))) == NULL)
+        krb5_set_error_message(context, ret = KRB5KRB_ERR_GENERIC,
+                               "Could not generate an ECDH key agreement private key");
+    if (ret == 0 &&
+        (pctx = EVP_PKEY_CTX_new(ephemeral, NULL)) == NULL)
+        ret = krb5_enomem(context);
+    if (ret == 0 && EVP_PKEY_derive_init(pctx) != 1)
+        ret = krb5_enomem(context);
+    if (ret == 0 &&
+        EVP_PKEY_CTX_set_ecdh_kdf_type(pctx, EVP_PKEY_ECDH_KDF_NONE) != 1)
+        krb5_set_error_message(context, ret = KRB5KRB_ERR_GENERIC,
+                               "Could not generate an ECDH key agreement private key "
+                               "(EVP_PKEY_CTX_set_dh_kdf_type)");
+    if (ret == 0 &&
+        EVP_PKEY_derive_set_peer_ex(pctx, ec_key_pub, 1) != 1)
+        krb5_set_error_message(context, ret = KRB5KRB_ERR_GENERIC,
+                               "Could not generate an ECDH key agreement private key "
+                               "(EVP_PKEY_derive_set_peer_ex)");
+    if (ret == 0 &&
+        (EVP_PKEY_derive(pctx, NULL, &size) != 1 || size == 0))
+        krb5_set_error_message(context, ret = KRB5KRB_ERR_GENERIC,
+                               "Could not generate an ECDH key agreement private key "
+                               "(EVP_PKEY_derive)");
+    if (ret == 0 && (p = malloc(size)) == NULL)
+        ret = krb5_enomem(context);
+    if (ret == 0 &&
+        (EVP_PKEY_derive(pctx, p, &size) != 1 || size == 0))
+        krb5_set_error_message(context, ret = KRB5KRB_ERR_GENERIC,
+                               "Could not generate an ECDH key agreement private key "
+                               "(EVP_PKEY_derive)");
+
+    if (ret) {
+        EVP_PKEY_free(ephemeral);
+        ephemeral = NULL;
+        free(p);
+        p = NULL;
+        size = 0;
+    }
+
+    *ec_key_priv = ephemeral;
+    *dh_gen_keylen = size;
+    *dh_gen_key = p;
+
+    EVP_PKEY_CTX_free(pctx);
+    return ret;
+}
+#else
+
+/* The empty line above is intentional to work around an mkproto bug */
 static krb5_error_code
-generate_ecdh_keyblock(krb5_context context,
-                       EC_KEY *ec_key_pk,    /* the client's public key */
-                       EC_KEY **ec_key_key,  /* the KDC's ephemeral private */
-                       unsigned char **dh_gen_key, /* shared secret */
-                       size_t *dh_gen_keylen)
+generate_ecdh_keyblock_ossl11(krb5_context context,
+                              EC_KEY *ec_key_pk,    /* the client's public key */
+                              EC_KEY **ec_key_key,  /* the KDC's ephemeral private */
+                              unsigned char **dh_gen_key, /* shared secret */
+                              size_t *dh_gen_keylen)
 {
     const EC_GROUP *group;
     EC_KEY *ephemeral;
@@ -136,7 +206,7 @@ generate_ecdh_keyblock(krb5_context context,
     EC_KEY_set_group(ephemeral, group);
 
     if (EC_KEY_generate_key(ephemeral) != 1) {
-	EC_KEY_free(ephemeral);
+       EC_KEY_free(ephemeral);
         return krb5_enomem(context);
     }
 
@@ -165,6 +235,7 @@ generate_ecdh_keyblock(krb5_context context,
 
     return 0;
 }
+#endif
 #endif /* HAVE_HCRYPTO_W_OPENSSL */
 
 krb5_error_code
@@ -175,20 +246,128 @@ _kdc_generate_ecdh_keyblock(krb5_context context,
                             size_t *dh_gen_keylen)
 {
 #ifdef HAVE_HCRYPTO_W_OPENSSL
-    return generate_ecdh_keyblock(context, ec_key_pk,
-                                  (EC_KEY **)ec_key_key,
-                                  dh_gen_key, dh_gen_keylen);
+#ifdef HAVE_OPENSSL_30
+    return generate_ecdh_keyblock_ossl30(context, ec_key_pk,
+                                         (EVP_PKEY **)ec_key_key,
+                                         dh_gen_key, dh_gen_keylen);
+#else
+    return generate_ecdh_keyblock_ossl11(context, ec_key_pk,
+                                         (EC_KEY **)ec_key_key,
+                                         dh_gen_key, dh_gen_keylen);
+#endif
 #else
     return ENOTSUP;
 #endif /* HAVE_HCRYPTO_W_OPENSSL */
 }
 
 #ifdef HAVE_HCRYPTO_W_OPENSSL
+#ifdef HAVE_OPENSSL_30
 static krb5_error_code
-get_ecdh_param(krb5_context context,
-               krb5_kdc_configuration *config,
-               SubjectPublicKeyInfo *dh_key_info,
-               EC_KEY **out)
+get_ecdh_param_ossl30(krb5_context context,
+                      krb5_kdc_configuration *config,
+                      SubjectPublicKeyInfo *dh_key_info,
+                      EVP_PKEY **out)
+{
+    EVP_PKEY_CTX *pctx = NULL;
+    EVP_PKEY *template = NULL;
+    EVP_PKEY *public = NULL;
+    OSSL_PARAM params[2];
+    krb5_error_code ret = 0;
+    ECParameters ecp;
+    const unsigned char *p;
+    const char *curve_sn = NULL;
+    size_t len;
+    char *curve_sn_dup = NULL;
+    int groupnid = NID_undef;
+
+    /* XXX Algorithm agility; XXX KRB5_BADMSGTYPE?? */
+
+    /*
+     * In order for d2i_PublicKey() to work we need to create a template key
+     * that has the curve parameters for the subjectPublicKey.
+     *
+     * Or maybe we could learn to use the OSSL_DECODER(3) API.  But this works,
+     * at least until OpenSSL deprecates d2i_PublicKey() and forces us to use
+     * OSSL_DECODER(3).
+     */
+
+    memset(&ecp, 0, sizeof(ecp));
+
+    if (dh_key_info->algorithm.parameters == NULL)
+	krb5_set_error_message(context, ret = KRB5_BADMSGTYPE,
+			       "PKINIT missing algorithm parameter "
+			       "in clientPublicValue");
+    if (ret == 0)
+        ret = decode_ECParameters(dh_key_info->algorithm.parameters->data,
+                                  dh_key_info->algorithm.parameters->length,
+                                  &ecp, &len);
+    if (ret == 0 && ecp.element != choice_ECParameters_namedCurve)
+        krb5_set_error_message(context, ret = KRB5_BADMSGTYPE,
+                               "PKINIT client used an unnamed curve");
+    if (ret == 0 &&
+        (groupnid = _hx509_ossl_oid2nid(&ecp.u.namedCurve)) == NID_undef)
+        krb5_set_error_message(context, ret = KRB5_BADMSGTYPE,
+                               "PKINIT client used an unsupported curve");
+    if (ret == 0 && (curve_sn = OBJ_nid2sn(groupnid)) == NULL)
+        krb5_set_error_message(context, ret = KRB5_BADMSGTYPE,
+                               "Could not resolve curve NID %d to its short name",
+                               groupnid);
+    if (ret == 0 && (curve_sn_dup = strdup(curve_sn)) == NULL)
+        ret = krb5_enomem(context);
+    if (ret == 0) {
+        if (der_heim_oid_cmp(&ecp.u.namedCurve, &asn1_oid_id_ec_group_secp256r1) != 0)
+            krb5_set_error_message(context, ret = KRB5_BADMSGTYPE,
+                                   "PKINIT client used an unsupported curve");
+    }
+    if (ret == 0) {
+        /*
+         * Apparently there's no error checking to be done here?  Why does
+         * OSSL_PARAM_construct_utf8_string() want a non-const for the value?
+         * Is that a bug in OpenSSL?
+         */
+        params[0] = OSSL_PARAM_construct_utf8_string(OSSL_PKEY_PARAM_GROUP_NAME,
+                                                     curve_sn_dup, 0);
+        params[1] = OSSL_PARAM_construct_end();
+
+        if ((pctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL)) == NULL)
+            ret = krb5_enomem(context);
+    }
+    if (ret == 0 && EVP_PKEY_fromdata_init(pctx) != 1)
+        ret = krb5_enomem(context);
+    if (ret == 0 &&
+        EVP_PKEY_fromdata(pctx, &template, OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS,
+                          params) != 1)
+        krb5_set_error_message(context, ret = KRB5_BADMSGTYPE,
+                               "Could not set up to parse key for curve %s",
+                               curve_sn);
+
+    p = dh_key_info->subjectPublicKey.data;
+    len = dh_key_info->subjectPublicKey.length / 8;
+    if (ret == 0 &&
+        (public = d2i_PublicKey(EVP_PKEY_EC, &template, &p, len)) == NULL)
+        krb5_set_error_message(context, ret = KRB5_BADMSGTYPE,
+                               "Could not decode PKINIT client ECDH key");
+
+    if (ret) {
+        EVP_PKEY_free(public);
+        public = NULL;
+    }
+
+    *out = public;
+
+    /* FYI the EVP_PKEY_CTX takes ownership of the `template' key */
+    EVP_PKEY_CTX_free(pctx);
+    free_ECParameters(&ecp);
+    free(curve_sn_dup);
+    return ret;
+}
+#else
+
+static krb5_error_code
+get_ecdh_param_ossl11(krb5_context context,
+                      krb5_kdc_configuration *config,
+                      SubjectPublicKeyInfo *dh_key_info,
+                      EC_KEY **out)
 {
     ECParameters ecp;
     EC_KEY *public = NULL;
@@ -198,30 +377,31 @@ get_ecdh_param(krb5_context context,
     int nid;
 
     if (dh_key_info->algorithm.parameters == NULL) {
-	krb5_set_error_message(context, KRB5_BADMSGTYPE,
-			       "PKINIT missing algorithm parameter "
-			       "in clientPublicValue");
-	return KRB5_BADMSGTYPE;
+       krb5_set_error_message(context, KRB5_BADMSGTYPE,
+                              "PKINIT missing algorithm parameter "
+                              "in clientPublicValue");
+       return KRB5_BADMSGTYPE;
     }
+    /* XXX Algorithm agility; XXX KRB5_BADMSGTYPE?? */
 
     memset(&ecp, 0, sizeof(ecp));
 
     ret = decode_ECParameters(dh_key_info->algorithm.parameters->data,
-			      dh_key_info->algorithm.parameters->length, &ecp, &len);
+                             dh_key_info->algorithm.parameters->length, &ecp, &len);
     if (ret)
-	goto out;
+       goto out;
 
     if (ecp.element != choice_ECParameters_namedCurve) {
-	ret = KRB5_BADMSGTYPE;
-	goto out;
+       ret = KRB5_BADMSGTYPE;
+       goto out;
     }
 
     if (der_heim_oid_cmp(&ecp.u.namedCurve, &asn1_oid_id_ec_group_secp256r1) == 0)
-	nid = NID_X9_62_prime256v1;
+       nid = NID_X9_62_prime256v1;
     else {
-	ret = KRB5_BADMSGTYPE;
-	goto out;
-    }
+       ret = KRB5_BADMSGTYPE;
+       goto out;
+   }
 
     /* XXX verify group is ok */
 
@@ -230,20 +410,21 @@ get_ecdh_param(krb5_context context,
     p = dh_key_info->subjectPublicKey.data;
     len = dh_key_info->subjectPublicKey.length / 8;
     if (o2i_ECPublicKey(&public, &p, len) == NULL) {
-	ret = KRB5_BADMSGTYPE;
-	krb5_set_error_message(context, ret,
-			       "PKINIT failed to decode ECDH key");
-	goto out;
+       ret = KRB5_BADMSGTYPE;
+       krb5_set_error_message(context, ret,
+                              "PKINIT failed to decode ECDH key");
+       goto out;
     }
     *out = public;
     public = NULL;
 
  out:
     if (public)
-	EC_KEY_free(public);
+       EC_KEY_free(public);
     free_ECParameters(&ecp);
     return ret;
 }
+#endif
 #endif /* HAVE_HCRYPTO_W_OPENSSL */
 
 krb5_error_code
@@ -253,7 +434,11 @@ _kdc_get_ecdh_param(krb5_context context,
                     void **out)
 {
 #ifdef HAVE_HCRYPTO_W_OPENSSL
-    return get_ecdh_param(context, config, dh_key_info, (EC_KEY **)out);
+#ifdef HAVE_OPENSSL_30
+    return get_ecdh_param_ossl30(context, config, dh_key_info, (EVP_PKEY **)out);
+#else
+    return get_ecdh_param_ossl11(context, config, dh_key_info, (EC_KEY **)out);
+#endif
 #else
     return ENOTSUP;
 #endif /* HAVE_HCRYPTO_W_OPENSSL */
@@ -265,13 +450,51 @@ _kdc_get_ecdh_param(krb5_context context,
  */
 
 #ifdef HAVE_HCRYPTO_W_OPENSSL
+#ifdef HAVE_OPENSSL_30
 static krb5_error_code
-serialize_ecdh_key(krb5_context context,
-                   EC_KEY *key,
-                   unsigned char **out,
-                   size_t *out_len)
+serialize_ecdh_key_ossl30(krb5_context context,
+                          EVP_PKEY *key,
+                          unsigned char **out,
+                          size_t *out_len)
+{
+    unsigned char *p;
+    int len;
+
+    *out = NULL;
+    *out_len = 0;
+
+    len = i2d_PublicKey(key, NULL);
+    if (len <= 0) {
+        krb5_set_error_message(context, EOVERFLOW,
+                               "PKINIT failed to encode ECDH key");
+        return EOVERFLOW;
+    }
+
+    *out = malloc(len);
+    if (*out == NULL)
+        return krb5_enomem(context);
+
+    p = *out;
+    len = i2d_PublicKey(key, &p);
+    if (len <= 0) {
+        free(*out);
+        *out = NULL;
+	krb5_set_error_message(context, EINVAL /* XXX Better error please */,
+			       "PKINIT failed to encode ECDH key");
+        return EINVAL;
+    }
+
+    *out_len = len * 8;
+    return 0;
+}
+#else
+
+static krb5_error_code
+serialize_ecdh_key_ossl11(krb5_context context,
+                          EC_KEY *key,
+                          unsigned char **out,
+                          size_t *out_len)
 {
-    krb5_error_code ret = 0;
     unsigned char *p;
     int len;
 
@@ -279,8 +502,11 @@ serialize_ecdh_key(krb5_context context,
     *out_len = 0;
 
     len = i2o_ECPublicKey(key, NULL);
-    if (len <= 0)
+    if (len <= 0) {
+        krb5_set_error_message(context, EOVERFLOW,
+                               "PKINIT failed to encode ECDH key");
         return EOVERFLOW;
+    }
 
     *out = malloc(len);
     if (*out == NULL)
@@ -291,16 +517,16 @@ serialize_ecdh_key(krb5_context context,
     if (len <= 0) {
         free(*out);
         *out = NULL;
-        ret = EINVAL; /* XXX Better error please */
-	krb5_set_error_message(context, ret,
+	krb5_set_error_message(context, EINVAL /* XXX Better error please */,
 			       "PKINIT failed to encode ECDH key");
-        return ret;
+        return EINVAL;
     }
 
     *out_len = len * 8;
-    return ret;
+    return 0;
 }
 #endif
+#endif
 
 krb5_error_code
 _kdc_serialize_ecdh_key(krb5_context context,
@@ -309,7 +535,11 @@ _kdc_serialize_ecdh_key(krb5_context context,
                         size_t *out_len)
 {
 #ifdef HAVE_HCRYPTO_W_OPENSSL
-    return serialize_ecdh_key(context, key, out, out_len);
+#ifdef HAVE_OPENSSL_30
+    return serialize_ecdh_key_ossl30(context, key, out, out_len);
+#else
+    return serialize_ecdh_key_ossl11(context, key, out, out_len);
+#endif
 #else
     return ENOTSUP;
 #endif
