Commit cc5dd4ff66 for openssl.org

commit cc5dd4ff66ff0cb000a15396620e9c00714d6f64
Author: Craig Lorentzen <crlorentzen@gmail.com>
Date:   Fri Apr 24 17:25:29 2026 +0000

    Map rsaesOaep SubjectPublicKeyInfo to RSA

    TPM 1.2 Endorsement Key certificates use id-RSAES-OAEP
    (NID_rsaesOaep) as their SubjectPublicKeyInfo algorithm
    identifier per TCG Credential Profiles V1.2 section 3.2.7.
    The underlying key is a standard RSAPublicKey.  Without
    this mapping, X509_get_pubkey() fails with a decode error
    and X509_verify_cert() cannot validate these certificates.

    Add NID_rsaesOaep handling to the three SPKI decode paths,
    each of which points at the other two so future changes stay
    in sync:

     - x509_pubkey_decode(): remap the NID to NID_rsaEncryption
       for the legacy ameth lookup.  This path is reached via
       d2i_RSA_PUBKEY()/ossl_d2i_PUBKEY_legacy(), which is in
       turn invoked by the provider RSA decoder's rsa_d2i_PUBKEY,
       so it is load-bearing even when the provider path is in
       use.

     - x509_pubkey_ex_d2i_ex(): use "RSA" as the decoder keytype
       name so OSSL_DECODER_CTX_new_for_pkey() selects the RSA
       provider decoder.  The NID check precedes OBJ_obj2txt()
       so the text conversion is skipped when unused.

     - ossl_spki2typespki_der_decode(): same remap in the
       SPKI-to-type-SPKI provider decoder chain.  Flatten the
       existing SM2 special case while here: the original code
       relied on a dangling else across the #endif, which made
       the rsaesOaep branch awkward to add.  The new structure
       initializes dataname to empty, applies each special case
       in turn, and falls back to OBJ_obj2txt() only when no
       override applied.  strcpy() is replaced with
       OPENSSL_strlcpy() for consistency with surrounding code.

    The OAEP AlgorithmIdentifier parameters (which carry a
    TCG-specific pSourceAlgorithm "TCPA" for TPM EKs) are
    deliberately not interpreted; only the RSAPublicKey body is
    consumed.

    Add a test using a real TPM 1.2 EK certificate.  The test
    exercises both the provider decoder path (via X509_from_strings
    + X509_get0_pubkey) and, when deprecated APIs are available,
    the legacy path (via d2i_RSA_PUBKEY), confirming the key
    decodes to an RSA EVP_PKEY of the expected size.

    Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
    Reviewed-by: Dmitry Belyavskiy <beldmit@gmail.com>
    MergeDate: Sun May  3 14:44:24 2026
    (Merged from https://github.com/openssl/openssl/pull/30961)

diff --git a/CHANGES.md b/CHANGES.md
index 049c0e7288..e8c2167746 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -31,6 +31,15 @@ OpenSSL Releases

 ### Changes between 4.0 and 4.1 [xx XXX xxxx]

+ * SubjectPublicKeyInfo blobs whose AlgorithmIdentifier uses id-RSAES-OAEP
+   (NID_rsaesOaep, 1.2.840.113549.1.1.7) with a plain RSAPublicKey body
+   are now decoded as RSA keys.  This is required for interoperability
+   with TPM 1.2 Endorsement Key certificates per TCG Credential Profiles
+   V1.2 section 3.2.7.  The OAEP AlgorithmIdentifier parameters are not
+   interpreted.
+
+   *Craig Lorentzen*
+
  * Added test framework for testing function memory allocation failures.

    *Jakub Zelenka*
diff --git a/crypto/x509/x_pubkey.c b/crypto/x509/x_pubkey.c
index d085d74992..78eba3707a 100644
--- a/crypto/x509/x_pubkey.c
+++ b/crypto/x509/x_pubkey.c
@@ -1,5 +1,5 @@
 /*
- * Copyright 1995-2025 The OpenSSL Project Authors. All Rights Reserved.
+ * Copyright 1995-2026 The OpenSSL Project Authors. All Rights Reserved.
  *
  * Licensed under the Apache License 2.0 (the "License").  You may not use
  * this file except in compliance with the License.  You can obtain a copy
@@ -199,8 +199,19 @@ static int x509_pubkey_ex_d2i_ex(ASN1_VALUE **pval,
         }
         p = in_saved;

-        if (OBJ_obj2txt(txtoidname, sizeof(txtoidname),
-                pubkey->algor->algorithm, 0)
+        /*
+         * TPM 1.2 Endorsement Key certificates use NID_rsaesOaep in the
+         * SPKI AlgorithmIdentifier with a plain RSAPublicKey body, per
+         * TCG Credential Profiles V1.2 section 3.2.7.  Map the OID to
+         * "RSA" here so the provider decoder is selected; the OAEP
+         * AlgorithmIdentifier parameters are not interpreted.  Keep
+         * this in sync with x509_pubkey_decode() and
+         * ossl_spki2typespki_der_decode().
+         */
+        if (OBJ_obj2nid(pubkey->algor->algorithm) == NID_rsaesOaep) {
+            OPENSSL_strlcpy(txtoidname, "RSA", sizeof(txtoidname));
+        } else if (OBJ_obj2txt(txtoidname, sizeof(txtoidname),
+                       pubkey->algor->algorithm, 0)
             <= 0) {
             ERR_clear_last_mark();
             goto end;
@@ -409,6 +420,16 @@ static int x509_pubkey_decode(EVP_PKEY **ppkey, const X509_PUBKEY *key)
     if (!key->flag_force_legacy)
         return 0;

+    /*
+     * NID_rsaesOaep uses the same underlying RSAPublicKey body as
+     * NID_rsaEncryption (TCG Credential Profiles V1.2 section 3.2.7).
+     * Remap so EVP_PKEY_set_type() below finds the RSA ameth.  Keep
+     * this in sync with x509_pubkey_ex_d2i_ex() and
+     * ossl_spki2typespki_der_decode().
+     */
+    if (nid == NID_rsaesOaep)
+        nid = NID_rsaEncryption;
+
     pkey = EVP_PKEY_new();
     if (pkey == NULL) {
         ERR_raise(ERR_LIB_X509, ERR_R_EVP_LIB);
diff --git a/providers/implementations/encode_decode/decode_spki2typespki.c b/providers/implementations/encode_decode/decode_spki2typespki.c
index ad1fd0ea3e..6fd680c23f 100644
--- a/providers/implementations/encode_decode/decode_spki2typespki.c
+++ b/providers/implementations/encode_decode/decode_spki2typespki.c
@@ -1,5 +1,5 @@
 /*
- * Copyright 2020-2025 The OpenSSL Project Authors. All Rights Reserved.
+ * Copyright 2020-2026 The OpenSSL Project Authors. All Rights Reserved.
  *
  * Licensed under the Apache License 2.0 (the "License").  You may not use
  * this file except in compliance with the License.  You can obtain a copy
@@ -122,14 +122,29 @@ int ossl_spki2typespki_der_decode(unsigned char *der, long len, int selection,
         goto end;
     X509_ALGOR_get0(&oid, NULL, NULL, algor);

+    /*
+     * Resolve the SPKI AlgorithmIdentifier OID to the key type name expected
+     * by the downstream provider decoder.  Most OIDs map one-to-one to a key
+     * type via OBJ_obj2txt(), but a few need special handling:
+     *
+     *   - SM2 abuses id-ecPublicKey, so the EC parameters must be inspected
+     *     to tell EC and SM2 apart.
+     *   - TPM 1.2 Endorsement Key certificates use NID_rsaesOaep with a
+     *     plain RSAPublicKey body per TCG Credential Profiles V1.2 section
+     *     3.2.7; the OAEP AlgorithmIdentifier parameters are not interpreted
+     *     here.  Keep this in sync with x509_pubkey_decode() and
+     *     x509_pubkey_ex_d2i_ex() in crypto/x509/x_pubkey.c.
+     */
+    dataname[0] = '\0';
 #ifndef OPENSSL_NO_EC
-    /* SM2 abuses the EC oid, so this could actually be SM2 */
     if (OBJ_obj2nid(oid) == NID_X9_62_id_ecPublicKey
         && ossl_x509_algor_is_sm2(algor))
-        strcpy(dataname, "SM2");
-    else
+        OPENSSL_strlcpy(dataname, "SM2", sizeof(dataname));
 #endif
-        if (OBJ_obj2txt(dataname, sizeof(dataname), oid, 0) <= 0)
+    if (dataname[0] == '\0' && OBJ_obj2nid(oid) == NID_rsaesOaep)
+        OPENSSL_strlcpy(dataname, "RSA", sizeof(dataname));
+    if (dataname[0] == '\0'
+        && OBJ_obj2txt(dataname, sizeof(dataname), oid, 0) <= 0)
         goto end;

     ossl_X509_PUBKEY_INTERNAL_free(xpub);
diff --git a/test/x509_test.c b/test/x509_test.c
index f5f5cc586a..5ce8a647c0 100644
--- a/test/x509_test.c
+++ b/test/x509_test.c
@@ -445,6 +445,96 @@ err:
     return ret;
 }

+/*
+ * TPM 1.2 Endorsement Key certificate with a NID_rsaesOaep
+ * SubjectPublicKeyInfo AlgorithmIdentifier (per TCG Credential
+ * Profiles V1.2 section 3.2.7).  The AlgorithmIdentifier carries
+ * a TCG-specific pSourceAlgorithm ("TCPA") in its parameters,
+ * which we deliberately do not interpret.  The key body itself
+ * is a standard RSAPublicKey.
+ */
+static const char *kRsaesOaepCert[] = {
+    "-----BEGIN CERTIFICATE-----\n",
+    "MIIDhDCCAmygAwIBAgIUBchBXcXPAWxNMJEsLXEXHv/eVZswDQYJKoZIhvcNAQEL\n",
+    "BQAwVTELMAkGA1UEBhMCQ0gxHjAcBgNVBAoTFVNUTWljcm9lbGVjdHJvbmljcyBO\n",
+    "VjEmMCQGA1UEAxMdU1RNIFRQTSBFSyBJbnRlcm1lZGlhdGUgQ0EgMDIwHhcNMjEw\n",
+    "OTA0MDAwMDAwWhcNMzEwOTA0MDAwMDAwWjAAMIIBNzAiBgkqhkiG9w0BAQcwFaIT\n",
+    "MBEGCSqGSIb3DQEBCQQEVENQQQOCAQ8AMIIBCgKCAQEAxpd3DnecpD87acEsYp4J\n",
+    "stM2q5Ss3CkjAP2Ei8yGjbO6DG/6WBIZjTdI5RfIcInoqN4QMso94vm8VqijdRI+\n",
+    "Zo5hLTCPLKXYwa6UG5yIPZ3ENQdhgZWeEPWe+pp9VUwz8wi78Ifk+CCV6Xp/5kQi\n",
+    "DCsR+RYbOVb9QgR6kjq+cx1z8YFp5u+k3Pl9tMq9xgIp5E6hT2MaS12KnoN8+hYI\n",
+    "mfCYVnpzBeQaHDp1KUoyDK6xGt86VxB0QyRbniHI38qgQL6qhO7z96aQ0pNGoQde\n",
+    "QUxFf/sETurQ5zSf+3btnS8afjxdVBKzj3isv5BaQrt0mdB7+3XWD+ASda33SY12\n",
+    "6wIDAQABo4GLMIGIMB8GA1UdIwQYMBaAFFcfgGtHzOeb+jWUfO2IuNEAWuCeMEIG\n",
+    "A1UdIAQ7MDkwNwYEVR0gADAvMC0GCCsGAQUFBwIBFiFodHRwOi8vd3d3LnN0LmNv\n",
+    "bS9UUE0vcmVwb3NpdG9yeS8wDAYDVR0TAQH/BAIwADATBgNVHSUBAf8ECTAHBgVn\n",
+    "gQUIATANBgkqhkiG9w0BAQsFAAOCAQEAMOhFPNcebyCRFOBztlWhmDb2DHTCD0nC\n",
+    "DVobH4WZJXGf4bkYNO3mOLyWtHEVzb36kiq7enh3f/eGhDPwKB8axlozpR5KAvER\n",
+    "szKNO8iLGOjuYzI2A4DazkttczFfzSB9QDgJrwTNEfIJtwRm2HQSiL0zzuEQOnaS\n",
+    "UWyt/iKn4/34BjEeaw4/Ld7+f06LXqSr18SUr0LTB2kk+Zzf0Och1C+G1CNLgJMM\n",
+    "MNQikAv0xdaOMX3HzA+phFlLbw/x8sboMlzmrbr92a/4Fp5WvmOSHH3ciwTtbAQn\n",
+    "A2TfExNOaKD2BG5FnB7c66puw2/yVxhveocQYgmT9XtMrNX00vEZJQ==\n",
+    "-----END CERTIFICATE-----\n",
+    NULL
+};
+
+/*
+ * Verify that a SubjectPublicKeyInfo with an id-RSAES-OAEP
+ * AlgorithmIdentifier decodes to an RSA EVP_PKEY via both the
+ * provider decoder path (exercised by X509_from_strings() +
+ * X509_get0_pubkey()) and the legacy type-specific path
+ * (exercised by d2i_RSA_PUBKEY() when available).
+ */
+static int test_rsaesoaep_spki(void)
+{
+    int ret = 0;
+    X509 *cert = NULL;
+    EVP_PKEY *pkey = NULL;
+#ifndef OPENSSL_NO_DEPRECATED_3_0
+    const X509_PUBKEY *xpk = NULL;
+    unsigned char *spki_der = NULL, *q;
+    const unsigned char *p;
+    int spki_len;
+    RSA *rsa = NULL;
+#endif
+
+    /* Provider / OSSL_DECODER path. */
+    if (!TEST_ptr(cert = X509_from_strings(kRsaesOaepCert))
+        || !TEST_ptr(pkey = X509_get0_pubkey(cert))
+        || !TEST_int_eq(EVP_PKEY_get_base_id(pkey), EVP_PKEY_RSA)
+        || !TEST_int_ge(EVP_PKEY_get_bits(pkey), 2048))
+        goto err;
+
+#ifndef OPENSSL_NO_DEPRECATED_3_0
+    /*
+     * Legacy path: d2i_RSA_PUBKEY() routes through
+     * ossl_d2i_PUBKEY_legacy() which sets flag_force_legacy=1,
+     * so this exercises the NID_rsaesOaep -> NID_rsaEncryption
+     * remap in x509_pubkey_decode().
+     */
+    if (!TEST_ptr(xpk = X509_get_X509_PUBKEY(cert))
+        || !TEST_int_gt((spki_len = i2d_X509_PUBKEY(xpk, NULL)), 0)
+        || !TEST_ptr(spki_der = OPENSSL_malloc(spki_len)))
+        goto err;
+    q = spki_der;
+    if (!TEST_int_eq(i2d_X509_PUBKEY(xpk, &q), spki_len))
+        goto err;
+    p = spki_der;
+    if (!TEST_ptr(rsa = d2i_RSA_PUBKEY(NULL, &p, spki_len))
+        || !TEST_int_ge(RSA_bits(rsa), 2048))
+        goto err;
+#endif
+
+    ret = 1;
+err:
+#ifndef OPENSSL_NO_DEPRECATED_3_0
+    RSA_free(rsa);
+    OPENSSL_free(spki_der);
+#endif
+    X509_free(cert);
+    return ret;
+}
+
 OPT_TEST_DECLARE_USAGE("<pss-self-signed-cert.pem>\n")

 int setup_tests(void)
@@ -484,6 +574,7 @@ int setup_tests(void)
     ADD_TEST(test_x509_revoked_delete_last_extension);
     ADD_TEST(test_drop_empty_cert_keyids);
     ADD_TEST(test_drop_empty_csr_keyids);
+    ADD_TEST(test_rsaesoaep_spki);
     return 1;
 }