git: 9a5a7c90d5e5 - releng/14.1 - openssl: Avoid type errors in EAI-related name check logic.

From: Ed Maste <emaste_at_FreeBSD.org>
Date: Wed, 04 Sep 2024 21:07:35 UTC
The branch releng/14.1 has been updated by emaste:

URL: https://cgit.FreeBSD.org/src/commit/?id=9a5a7c90d5e5971fe2b9c9265e9279a6f173a8f3

commit 9a5a7c90d5e5971fe2b9c9265e9279a6f173a8f3
Author:     Viktor Dukhovni <viktor@openssl.org>
AuthorDate: 2024-06-19 11:04:11 +0000
Commit:     Ed Maste <emaste@FreeBSD.org>
CommitDate: 2024-09-04 20:46:54 +0000

    openssl: Avoid type errors in EAI-related name check logic.
    
    The incorrectly typed data is read only, used in a compare operation, so
    neither remote code execution, nor memory content disclosure were possible.
    However, applications performing certificate name checks were vulnerable to
    denial of service.
    
    The GENERAL_TYPE data type is a union, and we must take care to access the
    correct member, based on `gen->type`, not all the member fields have the same
    structure, and a segfault is possible if the wrong member field is read.
    
    The code in question was lightly refactored with the intent to make it more
    obviously correct.
    
    Security:       CVE-2024-6119
    Obtained from:  OpenSSL Project
    
    (cherry picked from commit 1486960d6cdb052e4fc0109a56a0597b4e902ba1)
    (cherry picked from commit 5946b0c6cbc77e6c5f62f5f7e635c6036e14f4d0)
    
    Approved by:    so
---
 crypto/openssl/crypto/x509/v3_utl.c                | 78 +++++++++++++++-------
 crypto/openssl/test/recipes/25-test_eai_data.t     | 12 +++-
 .../test/recipes/25-test_eai_data/kdc-cert.pem     | 21 ++++++
 .../recipes/25-test_eai_data/kdc-root-cert.pem     | 16 +++++
 .../openssl/test/recipes/25-test_eai_data/kdc.sh   | 41 ++++++++++++
 5 files changed, 142 insertions(+), 26 deletions(-)

diff --git a/crypto/openssl/crypto/x509/v3_utl.c b/crypto/openssl/crypto/x509/v3_utl.c
index 6e4ef26ed608..304463d572c6 100644
--- a/crypto/openssl/crypto/x509/v3_utl.c
+++ b/crypto/openssl/crypto/x509/v3_utl.c
@@ -916,36 +916,64 @@ static int do_x509_check(X509 *x, const char *chk, size_t chklen,
             ASN1_STRING *cstr;
 
             gen = sk_GENERAL_NAME_value(gens, i);
-            if ((gen->type == GEN_OTHERNAME) && (check_type == GEN_EMAIL)) {
-                if (OBJ_obj2nid(gen->d.otherName->type_id) ==
-                    NID_id_on_SmtpUTF8Mailbox) {
-                    san_present = 1;
-
-                    /*
-                     * If it is not a UTF8String then that is unexpected and we
-                     * treat it as no match
-                     */
-                    if (gen->d.otherName->value->type == V_ASN1_UTF8STRING) {
-                        cstr = gen->d.otherName->value->value.utf8string;
-
-                        /* Positive on success, negative on error! */
-                        if ((rv = do_check_string(cstr, 0, equal, flags,
-                                                chk, chklen, peername)) != 0)
-                            break;
-                    }
-                } else
+            switch (gen->type) {
+            default:
+                continue;
+            case GEN_OTHERNAME:
+		switch (OBJ_obj2nid(gen->d.otherName->type_id)) {
+                default:
                     continue;
-            } else {
-                if ((gen->type != check_type) && (gen->type != GEN_OTHERNAME))
+                case NID_id_on_SmtpUTF8Mailbox:
+                    /*-
+                     * https://datatracker.ietf.org/doc/html/rfc8398#section-3
+                     *
+                     *   Due to name constraint compatibility reasons described
+                     *   in Section 6, SmtpUTF8Mailbox subjectAltName MUST NOT
+                     *   be used unless the local-part of the email address
+                     *   contains non-ASCII characters. When the local-part is
+                     *   ASCII, rfc822Name subjectAltName MUST be used instead
+                     *   of SmtpUTF8Mailbox. This is compatible with legacy
+                     *   software that supports only rfc822Name (and not
+                     *   SmtpUTF8Mailbox). [...]
+                     *
+                     *   SmtpUTF8Mailbox is encoded as UTF8String.
+                     *
+                     * If it is not a UTF8String then that is unexpected, and
+                     * we ignore the invalid SAN (neither set san_present nor
+                     * consider it a candidate for equality).  This does mean
+                     * that the subject CN may be considered, as would be the
+                     * case when the malformed SmtpUtf8Mailbox SAN is instead
+                     * simply absent.
+                     *
+                     * When CN-ID matching is not desirable, applications can
+                     * choose to turn it off, doing so is at this time a best
+                     * practice.
+                     */
+                    if (check_type != GEN_EMAIL
+                        || gen->d.otherName->value->type != V_ASN1_UTF8STRING)
+                        continue;
+                    alt_type = 0;
+                    cstr = gen->d.otherName->value->value.utf8string;
+                    break;
+                }
+                break;
+            case GEN_EMAIL:
+                if (check_type != GEN_EMAIL)
                     continue;
-            }
-            san_present = 1;
-            if (check_type == GEN_EMAIL)
                 cstr = gen->d.rfc822Name;
-            else if (check_type == GEN_DNS)
+                break;
+            case GEN_DNS:
+                if (check_type != GEN_DNS)
+                    continue;
                 cstr = gen->d.dNSName;
-            else
+                break;
+            case GEN_IPADD:
+                if (check_type != GEN_IPADD)
+                    continue;
                 cstr = gen->d.iPAddress;
+                break;
+            }
+            san_present = 1;
             /* Positive on success, negative on error! */
             if ((rv = do_check_string(cstr, alt_type, equal, flags,
                                       chk, chklen, peername)) != 0)
diff --git a/crypto/openssl/test/recipes/25-test_eai_data.t b/crypto/openssl/test/recipes/25-test_eai_data.t
index 522982ddfb80..e18735d89aad 100644
--- a/crypto/openssl/test/recipes/25-test_eai_data.t
+++ b/crypto/openssl/test/recipes/25-test_eai_data.t
@@ -21,16 +21,18 @@ setup("test_eai_data");
 #./util/wrap.pl apps/openssl verify -nameopt utf8 -no_check_time -CAfile test/recipes/25-test_eai_data/utf8_chain.pem test/recipes/25-test_eai_data/ascii_leaf.pem
 #./util/wrap.pl apps/openssl verify -nameopt utf8 -no_check_time -CAfile test/recipes/25-test_eai_data/ascii_chain.pem test/recipes/25-test_eai_data/utf8_leaf.pem
 
-plan tests => 12;
+plan tests => 16;
 
 require_ok(srctop_file('test','recipes','tconversion.pl'));
 my $folder = "test/recipes/25-test_eai_data";
 
 my $ascii_pem = srctop_file($folder, "ascii_leaf.pem");
 my $utf8_pem  = srctop_file($folder, "utf8_leaf.pem");
+my $kdc_pem   = srctop_file($folder, "kdc-cert.pem");
 
 my $ascii_chain_pem = srctop_file($folder, "ascii_chain.pem");
 my $utf8_chain_pem  = srctop_file($folder, "utf8_chain.pem");
+my $kdc_chain_pem  = srctop_file($folder, "kdc-root-cert.pem");
 
 my $out;
 my $outcnt = 0;
@@ -56,10 +58,18 @@ SKIP: {
 
 ok(run(app(["openssl", "verify", "-nameopt", "utf8", "-no_check_time", "-CAfile", $ascii_chain_pem, $ascii_pem])));
 ok(run(app(["openssl", "verify", "-nameopt", "utf8", "-no_check_time", "-CAfile", $utf8_chain_pem, $utf8_pem])));
+ok(run(app(["openssl", "verify", "-nameopt", "utf8", "-no_check_time", "-CAfile", $kdc_chain_pem, $kdc_pem])));
 
 ok(!run(app(["openssl", "verify", "-nameopt", "utf8", "-no_check_time", "-CAfile", $ascii_chain_pem, $utf8_pem])));
 ok(!run(app(["openssl", "verify", "-nameopt", "utf8", "-no_check_time", "-CAfile", $utf8_chain_pem,  $ascii_pem])));
 
+# Check an otherName does not get misparsed as an DNS name, (should trigger ASAN errors if violated).
+ok(run(app(["openssl", "verify", "-nameopt", "utf8", "-no_check_time", "-verify_hostname", 'mx1.example.com', "-CAfile", $kdc_chain_pem,  $kdc_pem])));
+# Check an otherName does not get misparsed as an email address, (should trigger ASAN errors if violated).
+ok(run(app(["openssl", "verify", "-nameopt", "utf8", "-no_check_time", "-verify_email", 'joe@example.com', "-CAfile", $kdc_chain_pem,  $kdc_pem])));
+# We expect SmtpUTF8Mailbox to be a UTF8 String, not an IA5String.
+ok(!run(app(["openssl", "verify", "-nameopt", "utf8", "-no_check_time", "-verify_email", 'moe@example.com', "-CAfile", $kdc_chain_pem,  $kdc_pem])));
+
 #Check that we get the expected failure return code
 with({ exit_checker => sub { return shift == 2; } },
      sub {
diff --git a/crypto/openssl/test/recipes/25-test_eai_data/kdc-cert.pem b/crypto/openssl/test/recipes/25-test_eai_data/kdc-cert.pem
new file mode 100644
index 000000000000..e8a2c6f55d45
--- /dev/null
+++ b/crypto/openssl/test/recipes/25-test_eai_data/kdc-cert.pem
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDbDCCAlSgAwIBAgIBAjANBgkqhkiG9w0BAQsFADAPMQ0wCwYDVQQDDARSb290
+MCAXDTI0MDYyMDA2MTQxNVoYDzIxMjQwNjIwMDYxNDE1WjAXMRUwEwYDVQQDDAxU
+RVNULkVYQU1QTEUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6wfP+
+6go79dkpo/dGLMlPZ7Gw/Q6gUYrCWZWUEgEeRVHCrqOlgUEyA+PcWas/XDPUxXry
+BQlJHLvlqamAQn8gs4QPBARFYWKNiTVGyaRkgNA1N5gqyZdrP9UE+ZJmdqxRAAe8
+vvpGZWSgevPhLUiSCFYDiD0Rtji2Hm3rGUrReQFBQDEw2pNGwz9zIaxUs08kQZcx
+Yzyiplz5Oau+R/6sAgUwDlrD9xOlUxx/tA/MSDIfkK8qioU11uUZtO5VjkNQy/bT
+7zQMmXxWgm2MIgOs1u4YN7YGOtgqHE9v9iPHHfgrkbQDtVDGQsa8AQEhkUDSCtW9
+3VFAKx6dGNXYzFwfAgMBAAGjgcgwgcUwHQYDVR0OBBYEFFR5tZycW19DmtbL4Zqj
+te1c2vZLMAkGA1UdIwQCMAAwCQYDVR0TBAIwADCBjQYDVR0RBIGFMIGCoD8GBisG
+AQUCAqA1MDOgDhsMVEVTVC5FWEFNUExFoSEwH6ADAgEBoRgwFhsGa3JidGd0GwxU
+RVNULkVYQU1QTEWgHQYIKwYBBQUHCAmgERYPbW9lQGV4YW1wbGUuY29tgQ9qb2VA
+ZXhhbXBsZS5jb22CD214MS5leGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAAOCAQEA
+T0xzVtVpRtaOzIhgzw7XQUdzWD5UEGSJJ1cBCOmKUWwDLTAouCYLFB4TbEE7MMUb
+iuMy60bjmVtvfJIXorGUgSadRe5RWJ5DamJWvPA0Q9x7blnEcXqEF+9Td+ypevgU
+UYHFmg83OYwxOsFXZ5cRuXMk3WCsDHQIBi6D1L6oDDZ2pfArs5mqm3thQKVlqyl1
+El3XRYEdqAz/5eCOFNfwxF0ALxjxVr/Z50StUZU8I7Zfev6+kHhyrR7dqzYJImv9
+0fTCOBEMjIETDsrA70OxAMu4V16nrWZdJdvzblS2qrt97Omkj+2kiPAJFB76RpwI
+oDQ9fKfUOAmUFth2/R/eGA==
+-----END CERTIFICATE-----
diff --git a/crypto/openssl/test/recipes/25-test_eai_data/kdc-root-cert.pem b/crypto/openssl/test/recipes/25-test_eai_data/kdc-root-cert.pem
new file mode 100644
index 000000000000..a74c96bf3146
--- /dev/null
+++ b/crypto/openssl/test/recipes/25-test_eai_data/kdc-root-cert.pem
@@ -0,0 +1,16 @@
+-----BEGIN CERTIFICATE-----
+MIICnDCCAYQCCQCBswYcrlZSHjANBgkqhkiG9w0BAQsFADAPMQ0wCwYDVQQDDARS
+b290MCAXDTI0MDYyMDA2MTQxNVoYDzIxMjQwNjIwMDYxNDE1WjAPMQ0wCwYDVQQD
+DARSb290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqRj8S4kBbIUj
+61kZfi6nE35Q38U140+qt4uAiwAhKumfVHlBM0zQ98WFt5zMHIBQwIb3yjc2zj+0
+qzUnQfwm1r/RfcMmBPEti9Ge+aEMSsds2gMXziOFM8wd2aAFPy7UVE0XpEWofsRK
+MGi61MKVdPSbGIxBwY9VW38/7D/wf1HtJe7y0xpuecR7GB2XAs+qST59NjuF+7wS
+dLM8Hb3TATgeYbXXWsRJgwz+SPzExg5WmLnU+7y4brZ32dHtdSmkRVSgSlaIf7Xj
+3Tc6Zi7I+W/JYk7hy1zUexVdWCak4PHcoWrXe0gNNN/t8VfLfMExt5z/HIylXnU7
+pGUyqZlTGQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAHpLF1UCRy7b6Hk0rLokxI
+lgwiH9BU9mktigAGASvkbllpt+YbUbWnuYAvpHBGiP1qZtfX2r96UrSJaGO9BEzT
+Gp9ThnSjoj4Srul0+s/NArU22irFLmDzbalgevAmm9gMGkdqkiIm/mXbwrPj0ncl
+KGicevXryVpvaP62eZ8cc3C4p97frMmXxRX8sTdQpD/gRI7prdEILRSKveqT+AEW
+7rFGM5AOevb4U8ddop8A3D/kX0wcCAIBF6jCNk3uEJ57jVcagL04kPnVfdRiedTS
+vfq1DRNcD29d1H/9u0fHdSn1/+8Ep3X+afQ3C6//5NvOEaXcIGO4QSwkprQydfv8
+-----END CERTIFICATE-----
diff --git a/crypto/openssl/test/recipes/25-test_eai_data/kdc.sh b/crypto/openssl/test/recipes/25-test_eai_data/kdc.sh
new file mode 100755
index 000000000000..7a8dbc719fb7
--- /dev/null
+++ b/crypto/openssl/test/recipes/25-test_eai_data/kdc.sh
@@ -0,0 +1,41 @@
+#! /usr/bin/env bash
+
+# Create a root CA, signing a leaf cert with a KDC principal otherName SAN, and
+# also a non-UTF8 smtpUtf8Mailbox SAN followed by an rfc822Name SAN and a DNS
+# name SAN.  In the vulnerable EAI code, the KDC principal `otherName` should
+# trigger ASAN errors in DNS name checks, while the non-UTF8 `smtpUtf8Mailbox`
+# should likewise lead to ASAN issues with email name checks.
+
+rm -f root-key.pem root-cert.pem
+openssl req -nodes -new -newkey rsa:2048 -keyout kdc-root-key.pem \
+        -x509 -subj /CN=Root -days 36524 -out kdc-root-cert.pem
+
+exts=$(
+    printf "%s\n%s\n%s\n%s = " \
+        "subjectKeyIdentifier = hash" \
+        "authorityKeyIdentifier = keyid" \
+        "basicConstraints = CA:false" \
+        "subjectAltName"
+    printf "%s, " "otherName:1.3.6.1.5.2.2;SEQUENCE:kdc_princ_name"
+    printf "%s, " "otherName:1.3.6.1.5.5.7.8.9;IA5:moe@example.com"
+    printf "%s, " "email:joe@example.com"
+    printf "%s\n" "DNS:mx1.example.com"
+    printf "[kdc_princ_name]\n"
+    printf "realm = EXP:0, GeneralString:TEST.EXAMPLE\n"
+    printf "principal_name = EXP:1, SEQUENCE:kdc_principal_seq\n"
+    printf "[kdc_principal_seq]\n"
+    printf "name_type = EXP:0, INTEGER:1\n"
+    printf "name_string = EXP:1, SEQUENCE:kdc_principal_components\n"
+    printf "[kdc_principal_components]\n"
+    printf "princ1 = GeneralString:krbtgt\n"
+    printf "princ2 = GeneralString:TEST.EXAMPLE\n"
+    )
+
+printf "%s\n" "$exts"
+
+openssl req -nodes -new -newkey rsa:2048 -keyout kdc-key.pem \
+    -subj "/CN=TEST.EXAMPLE" |
+    openssl x509 -req -out kdc-cert.pem \
+        -CA "kdc-root-cert.pem" -CAkey "kdc-root-key.pem" \
+        -set_serial 2 -days 36524 \
+        -extfile <(printf "%s\n" "$exts")