diff --git a/certs/check_cert_chains.sh b/certs/check_cert_chains.sh index e217c640478..e8b1cd936ed 100755 --- a/certs/check_cert_chains.sh +++ b/certs/check_cert_chains.sh @@ -5,24 +5,30 @@ # rather than as a runtime TLS handshake failure. Two checks per pair: # 1. identity: leaf issuer DN == CA subject DN and leaf AKID == CA SKID; runs # on any OpenSSL and catches the DN drift that has actually broken CI. -# 2. crypto: openssl verify, only when OpenSSL supports the CA's algorithm -# (ML-DSA needs 3.5+; otherwise skipped with notice). Exit 0 if all consistent. +# 2. crypto: openssl verify, only when this OpenSSL will verify the leaf's +# signature algorithm (ML-DSA needs 3.5+, SHA-1 is refused outright by some +# distributions); otherwise skipped with notice. Exit 0 if all consistent. # Run from the certs directory regardless of the caller's working directory. cd "$(dirname "$0")" || exit 1 # Pairs to check: " ", one per line. alg-class is -# "classic" (RSA/ECDSA) or "mldsa" (needs OpenSSL 3.5+ to verify); it is stated -# here, not parsed, since old OpenSSL cannot decode an ML-DSA cert. Add lines here. +# "classic" (RSA/ECDSA), "mldsa" (needs OpenSSL 3.5+ to verify) or "sha1"; it is +# stated here, not parsed, since old OpenSSL cannot decode an ML-DSA cert. A +# multi-certificate file is checked on its first certificate, which is the leaf. +# Add lines here. pairs="rsapss/ecc-leaf-rsapss.pem rsapss/ca-rsapss.pem classic -mldsa/ecc-leaf-mldsa44.pem mldsa/mldsa44-cert.pem mldsa" +mldsa/ecc-leaf-mldsa44.pem mldsa/mldsa44-cert.pem mldsa +server-cert-sha1.pem ca-cert.pem sha1 +client-cert-sha1.pem ca-cert.pem sha1 +server-cert-sha1-root.pem ca-cert.pem classic" failed=0 # Report whether this OpenSSL can cryptographically verify the given signature # algorithm class. Returns 0 (supported) or 1 (not supported). # -# $1 Algorithm class from the pairs table (classic or mldsa). +# $1 Algorithm class from the pairs table (classic, mldsa or sha1). crypto_supported() { case $1 in mldsa) @@ -30,6 +36,12 @@ crypto_supported() { | grep -iq 'ML-DSA' return $? ;; + sha1) + # Stock OpenSSL still verifies a SHA-1 signature at the default + # security level, but distributions that apply a system crypto + # policy refuse it. Rely on the identity check, which is portable. + return 1 + ;; *) # classic: RSA, RSA-PSS and ECDSA verify on any modern OpenSSL. return 0 @@ -37,6 +49,26 @@ crypto_supported() { esac } +# Read a key identifier from an "openssl x509 -ext" dump on stdin and print it +# as bare hex. Two layouts occur: a bare value (subjectKeyIdentifier, and an +# authorityKeyIdentifier built from keyid alone) and a labelled value where +# keyid, DirName and serial each get a line. Only the keyid is wanted, so stop +# at the first value rather than joining every hex run in the dump. POSIX awk +# because grep -o is not portable and could silently yield an empty id. +extract_key_id() { + awk ' + /[Kk]eyid:/ { + sub(/^.*[Kk]eyid:/, "") + print + exit + } + match($0, /[0-9A-Fa-f][0-9A-Fa-f](:[0-9A-Fa-f][0-9A-Fa-f])+/) { + print substr($0, RSTART, RLENGTH) + exit + } + ' | tr -cd '0-9A-Fa-f' +} + # Identity check ($1 leaf, $2 CA): leaf issuer DN == CA subject DN and, when # both present, leaf AKID == CA SKID. Signature-independent (works on any # OpenSSL). Returns 0 on match, 1 on mismatch. @@ -55,15 +87,10 @@ identity_matches() { return 1 fi - # Pick the colon-separated hex key id out of the value line with POSIX awk - # (grep -o is not POSIX and could silently yield empty ids, skipping this - # check). The header's hex letters never form the "hh:hh" pattern. - akid=`openssl x509 -in "$leaf" -noout -ext authorityKeyIdentifier 2>/dev/null \ - | awk 'match($0,/[0-9A-Fa-f][0-9A-Fa-f](:[0-9A-Fa-f][0-9A-Fa-f])+/){print substr($0,RSTART,RLENGTH)}' \ - | tr -cd '0-9A-Fa-f'` - skid=`openssl x509 -in "$ca" -noout -ext subjectKeyIdentifier 2>/dev/null \ - | awk 'match($0,/[0-9A-Fa-f][0-9A-Fa-f](:[0-9A-Fa-f][0-9A-Fa-f])+/){print substr($0,RSTART,RLENGTH)}' \ - | tr -cd '0-9A-Fa-f'` + akid=`openssl x509 -in "$leaf" -noout -ext authorityKeyIdentifier \ + 2>/dev/null | extract_key_id` + skid=`openssl x509 -in "$ca" -noout -ext subjectKeyIdentifier \ + 2>/dev/null | extract_key_id` if [ -n "$akid" ] && [ -n "$skid" ] && [ "$akid" != "$skid" ]; then echo "MISMATCH (AKID/SKID): $leaf vs $ca" echo " leaf AKID : $akid" @@ -100,7 +127,7 @@ do failed=1 fi else - echo "OK (identity only, crypto skipped - openssl lacks algorithm): $leaf -> $ca" + echo "OK (identity only, crypto verify skipped): $leaf -> $ca" fi done < server-sha1-req.pem + check_result $? "Step 1" + + openssl x509 -req -in server-sha1-req.pem -sha1 -extfile wolfssl.cnf -extensions wolfssl_opts -days 1000 -CA ca-cert.pem -CAkey ca-key.pem -set_serial 51 > server-sha1-tmp.pem + check_result $? "Step 2" + + rm server-sha1-req.pem + + openssl x509 -in server-sha1-tmp.pem -text > server-cert-sha1.pem + check_result $? "Step 3" + rm server-sha1-tmp.pem + echo "End of section" + echo "---------------------------------------------------------------------" + ########################################################### + ####### update and sign server-cert-sha1-root.pem ######### + ########################################################### + # SHA-256 leaf with a self signed SHA-1 root appended. RFC 8446 4.4.2.2 + # lets the trust anchor be omitted, so its SHA-1 signature must not stop + # the chain from being sent. + echo "Updating server-cert-sha1-root.pem" + echo "" + echo -e "US\\nMontana\\nBozeman\\nSawtooth\\nConsulting\\nwww.wolfssl.com\\nfacts@wolfssl.com\\n.\\n.\\n" | openssl req -new -key ca-key.pem -config ./wolfssl.cnf -nodes -out ca-sha1-req.pem + check_result $? "Step 1" + + openssl x509 -req -in ca-sha1-req.pem -sha1 -days 1000 -extfile wolfssl.cnf -extensions wolfssl_opts -signkey ca-key.pem -set_serial 52 -out ca-sha1-tmp.pem + check_result $? "Step 2" + + rm ca-sha1-req.pem + + openssl x509 -in server-cert.pem -text > server-cert-sha1-root.pem + check_result $? "Step 3" + openssl x509 -in ca-sha1-tmp.pem -text >> server-cert-sha1-root.pem + check_result $? "Step 4" + rm ca-sha1-tmp.pem + echo "End of section" + echo "---------------------------------------------------------------------" + ########################################################### + ########## update and sign client-cert-sha1.pem ########### + ########################################################### + # SHA-1 signed client leaf, issued by ca-cert.pem rather than self signed + # so it is not exempt from the RFC 8446 4.4.2.2 trust anchor rule. Used to + # check the client falls back to an empty certificate_list. + echo "Updating client-cert-sha1.pem" + echo "" + echo -e "US\\nMontana\\nBozeman\\nwolfSSL_2048\\nProgramming-2048\\nwww.wolfssl.com\\nfacts@wolfssl.com\\n.\\n.\\n" | openssl req -new -key client-key.pem -config ./wolfssl.cnf -nodes > client-sha1-req.pem + check_result $? "Step 1" + + openssl x509 -req -in client-sha1-req.pem -sha1 -extfile wolfssl.cnf -extensions wolfssl_opts -days 1000 -CA ca-cert.pem -CAkey ca-key.pem -set_serial 53 > client-sha1-tmp.pem + check_result $? "Step 2" + + rm client-sha1-req.pem + + openssl x509 -in client-sha1-tmp.pem -text > client-cert-sha1.pem + check_result $? "Step 3" + rm client-sha1-tmp.pem + echo "End of section" + echo "---------------------------------------------------------------------" + ########################################################### ########## update and sign server-revoked-key.pem ######### ########################################################### echo "Updating server-revoked-cert.pem" diff --git a/certs/server-cert-sha1-root.pem b/certs/server-cert-sha1-root.pem new file mode 100644 index 00000000000..6ac97f2e3c4 --- /dev/null +++ b/certs/server-cert-sha1-root.pem @@ -0,0 +1,183 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 1 (0x1) + Signature Algorithm: sha256WithRSAEncryption + Issuer: C = US, ST = Montana, L = Bozeman, O = Sawtooth, OU = Consulting, CN = www.wolfssl.com, emailAddress = facts@wolfssl.com + Validity + Not Before: Jun 11 21:44:29 2026 GMT + Not After : Mar 7 21:44:29 2029 GMT + Subject: C = US, ST = Montana, L = Bozeman, O = wolfSSL, OU = Support, CN = www.wolfssl.com, emailAddress = facts@wolfssl.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:c0:95:08:e1:57:41:f2:71:6d:b7:d2:45:41:27: + 01:65:c6:45:ae:f2:bc:24:30:b8:95:ce:2f:4e:d6: + f6:1c:88:bc:7c:9f:fb:a8:67:7f:fe:5c:9c:51:75: + f7:8a:ca:07:e7:35:2f:8f:e1:bd:7b:c0:2f:7c:ab: + 64:a8:17:fc:ca:5d:7b:ba:e0:21:e5:72:2e:6f:2e: + 86:d8:95:73:da:ac:1b:53:b9:5f:3f:d7:19:0d:25: + 4f:e1:63:63:51:8b:0b:64:3f:ad:43:b8:a5:1c:5c: + 34:b3:ae:00:a0:63:c5:f6:7f:0b:59:68:78:73:a6: + 8c:18:a9:02:6d:af:c3:19:01:2e:b8:10:e3:c6:cc: + 40:b4:69:a3:46:33:69:87:6e:c4:bb:17:a6:f3:e8: + dd:ad:73:bc:7b:2f:21:b5:fd:66:51:0c:bd:54:b3: + e1:6d:5f:1c:bc:23:73:d1:09:03:89:14:d2:10:b9: + 64:c3:2a:d0:a1:96:4a:bc:e1:d4:1a:5b:c7:a0:c0: + c1:63:78:0f:44:37:30:32:96:80:32:23:95:a1:77: + ba:13:d2:97:73:e2:5d:25:c9:6a:0d:c3:39:60:a4: + b4:b0:69:42:42:09:e9:d8:08:bc:33:20:b3:58:22: + a7:aa:eb:c4:e1:e6:61:83:c5:d2:96:df:d9:d0:4f: + ad:d7 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + B3:11:32:C9:92:98:84:E2:C9:F8:D0:3B:6E:03:42:CA:1F:0E:8E:3C + X509v3 Authority Key Identifier: + keyid:27:8E:67:11:74:C3:26:1D:3F:ED:33:63:B3:A4:D8:1D:30:E5:E8:D5 + DirName:/C=US/ST=Montana/L=Bozeman/O=Sawtooth/OU=Consulting/CN=www.wolfssl.com/emailAddress=facts@wolfssl.com + serial:67:19:D2:A8:7F:E3:2D:FA:75:7A:4F:E7:B2:02:D9:AD:C4:77:5E:F8 + X509v3 Basic Constraints: + CA:TRUE + X509v3 Subject Alternative Name: + DNS:example.com, IP Address:127.0.0.1 + X509v3 Extended Key Usage: + TLS Web Server Authentication, TLS Web Client Authentication + Signature Algorithm: sha256WithRSAEncryption + Signature Value: + 88:7c:f7:57:61:57:5f:fc:da:3b:f8:1e:a3:20:54:3e:f4:2d: + f8:eb:05:14:8b:ba:1b:c8:55:f5:e5:48:09:2b:19:d3:36:88: + 64:6d:c2:61:65:6b:71:53:20:e7:c9:43:51:6d:c2:12:2b:78: + 4d:bc:f3:0d:88:ec:73:3d:db:8d:2f:1d:6f:31:d6:5b:ba:a5: + 9d:0a:fb:50:19:7c:9d:ae:f0:c4:9e:a0:23:91:dc:21:89:75: + 5c:00:0b:1b:4e:ff:08:c2:85:86:f0:e2:1b:4e:88:2c:cc:0b: + c5:c3:5d:0f:5f:5d:27:29:73:cd:95:08:40:67:bf:23:76:48: + fd:14:b2:61:e0:18:1a:83:57:4a:15:af:2f:63:10:d4:65:29: + b1:7f:41:37:d6:5e:0c:39:c7:c3:3d:36:f6:38:62:56:ed:5e: + 1c:4e:a9:fa:61:53:1c:9a:77:a4:40:f7:3c:ed:e4:86:60:68: + f2:39:5a:a0:b9:14:48:86:95:43:29:97:a8:e1:00:32:ad:ed: + 2b:2a:b5:ce:26:e9:86:5a:a2:4f:03:20:26:72:c9:6b:9d:4f: + f8:c1:09:0f:5a:16:d5:78:71:1d:20:a6:58:03:3b:6b:26:92: + 25:a2:f0:ce:d5:21:7c:db:15:60:a8:8e:04:8e:52:b7:e2:13: + cd:0e:0c:94 +-----BEGIN CERTIFICATE----- +MIIE6zCCA9OgAwIBAgIBATANBgkqhkiG9w0BAQsFADCBlTELMAkGA1UEBhMCVVMx +EDAOBgNVBAgMB01vbnRhbmExEDAOBgNVBAcMB0JvemVtYW4xETAPBgNVBAoMCFNh +d3Rvb3RoMRMwEQYDVQQLDApDb25zdWx0aW5nMRgwFgYDVQQDDA93d3cud29sZnNz +bC5jb20xIDAeBgkqhkiG9w0BCQEWEWZhY3RzQHdvbGZzc2wuY29tMB4XDTI2MDYx +MTIxNDQyOVoXDTI5MDMwNzIxNDQyOVowgZExCzAJBgNVBAYTAlVTMRAwDgYDVQQI +DAdNb250YW5hMRAwDgYDVQQHDAdCb3plbWFuMRAwDgYDVQQKDAd3b2xmU1NMMRAw +DgYDVQQLDAdTdXBwb3J0MRgwFgYDVQQDDA93d3cud29sZnNzbC5jb20xIDAeBgkq +hkiG9w0BCQEWEWZhY3RzQHdvbGZzc2wuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAwJUI4VdB8nFtt9JFQScBZcZFrvK8JDC4lc4vTtb2HIi8fJ/7 +qGd//lycUXX3isoH5zUvj+G9e8AvfKtkqBf8yl17uuAh5XIuby6G2JVz2qwbU7lf +P9cZDSVP4WNjUYsLZD+tQ7ilHFw0s64AoGPF9n8LWWh4c6aMGKkCba/DGQEuuBDj +xsxAtGmjRjNph27Euxem8+jdrXO8ey8htf1mUQy9VLPhbV8cvCNz0QkDiRTSELlk +wyrQoZZKvOHUGlvHoMDBY3gPRDcwMpaAMiOVoXe6E9KXc+JdJclqDcM5YKS0sGlC +Qgnp2Ai8MyCzWCKnquvE4eZhg8XSlt/Z0E+t1wIDAQABo4IBRjCCAUIwHQYDVR0O +BBYEFLMRMsmSmITiyfjQO24DQsofDo48MIHVBgNVHSMEgc0wgcqAFCeOZxF0wyYd +P+0zY7Ok2B0w5ejVoYGbpIGYMIGVMQswCQYDVQQGEwJVUzEQMA4GA1UECAwHTW9u +dGFuYTEQMA4GA1UEBwwHQm96ZW1hbjERMA8GA1UECgwIU2F3dG9vdGgxEzARBgNV +BAsMCkNvbnN1bHRpbmcxGDAWBgNVBAMMD3d3dy53b2xmc3NsLmNvbTEgMB4GCSqG +SIb3DQEJARYRZmFjdHNAd29sZnNzbC5jb22CFGcZ0qh/4y36dXpP57IC2a3Ed174 +MAwGA1UdEwQFMAMBAf8wHAYDVR0RBBUwE4ILZXhhbXBsZS5jb22HBH8AAAEwHQYD +VR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMA0GCSqGSIb3DQEBCwUAA4IBAQCI +fPdXYVdf/No7+B6jIFQ+9C346wUUi7obyFX15UgJKxnTNohkbcJhZWtxUyDnyUNR +bcISK3hNvPMNiOxzPduNLx1vMdZbuqWdCvtQGXydrvDEnqAjkdwhiXVcAAsbTv8I +woWG8OIbTogszAvFw10PX10nKXPNlQhAZ78jdkj9FLJh4Bgag1dKFa8vYxDUZSmx +f0E31l4MOcfDPTb2OGJW7V4cTqn6YVMcmnekQPc87eSGYGjyOVqguRRIhpVDKZeo +4QAyre0rKrXOJumGWqJPAyAmcslrnU/4wQkPWhbVeHEdIKZYAztrJpIlovDO1SF8 +2xVgqI4EjlK34hPNDgyU +-----END CERTIFICATE----- +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 52 (0x34) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C = US, ST = Montana, L = Bozeman, O = Sawtooth, OU = Consulting, CN = www.wolfssl.com, emailAddress = facts@wolfssl.com + Validity + Not Before: Aug 6 16:28:00 2026 GMT + Not After : May 2 16:28:00 2029 GMT + Subject: C = US, ST = Montana, L = Bozeman, O = Sawtooth, OU = Consulting, CN = www.wolfssl.com, emailAddress = facts@wolfssl.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:bf:0c:ca:2d:14:b2:1e:84:42:5b:cd:38:1f:4a: + f2:4d:75:10:f1:b6:35:9f:df:ca:7d:03:98:d3:ac: + de:03:66:ee:2a:f1:d8:b0:7d:6e:07:54:0b:10:98: + 21:4d:80:cb:12:20:e7:cc:4f:de:45:7d:c9:72:77: + 32:ea:ca:90:bb:69:52:10:03:2f:a8:f3:95:c5:f1: + 8b:62:56:1b:ef:67:6f:a4:10:41:95:ad:0a:9b:e3: + a5:c0:b0:d2:70:76:50:30:5b:a8:e8:08:2c:7c:ed: + a7:a2:7a:8d:38:29:1c:ac:c7:ed:f2:7c:95:b0:95: + 82:7d:49:5c:38:cd:77:25:ef:bd:80:75:53:94:3c: + 3d:ca:63:5b:9f:15:b5:d3:1d:13:2f:19:d1:3c:db: + 76:3a:cc:b8:7d:c9:e5:c2:d7:da:40:6f:d8:21:dc: + 73:1b:42:2d:53:9c:fe:1a:fc:7d:ab:7a:36:3f:98: + de:84:7c:05:67:ce:6a:14:38:87:a9:f1:8c:b5:68: + cb:68:7f:71:20:2b:f5:a0:63:f5:56:2f:a3:26:d2: + b7:6f:b1:5a:17:d7:38:99:08:fe:93:58:6f:fe:c3: + 13:49:08:16:0b:a7:4d:67:00:52:31:67:23:4e:98: + ed:51:45:1d:b9:04:d9:0b:ec:d8:28:b3:4b:bd:ed: + 36:79 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + 27:8E:67:11:74:C3:26:1D:3F:ED:33:63:B3:A4:D8:1D:30:E5:E8:D5 + X509v3 Authority Key Identifier: + keyid:27:8E:67:11:74:C3:26:1D:3F:ED:33:63:B3:A4:D8:1D:30:E5:E8:D5 + DirName:/C=US/ST=Montana/L=Bozeman/O=Sawtooth/OU=Consulting/CN=www.wolfssl.com/emailAddress=facts@wolfssl.com + serial:34 + X509v3 Basic Constraints: + CA:TRUE + X509v3 Subject Alternative Name: + DNS:example.com, IP Address:127.0.0.1 + X509v3 Extended Key Usage: + TLS Web Server Authentication, TLS Web Client Authentication + Signature Algorithm: sha1WithRSAEncryption + Signature Value: + 4d:44:d1:17:db:2d:9f:a7:6a:bd:0d:ff:dd:d9:51:ce:28:43: + 10:40:88:ca:7d:45:47:24:1d:c6:11:44:af:b4:59:68:24:49: + 3a:82:12:4c:41:5e:aa:78:80:b3:e7:55:f5:74:21:06:eb:30: + 70:d3:84:9b:fb:0d:a8:7b:16:c5:00:f9:53:0e:fc:89:f9:a1: + bb:06:e6:9b:33:94:73:87:ac:0f:ac:c8:39:42:d3:d3:9d:9c: + 63:b3:eb:0f:1a:9b:49:4a:60:7c:6b:16:aa:56:8b:9d:bf:3a: + c9:a6:ef:be:bf:d0:73:c4:58:23:d6:ab:b6:e1:6b:b8:4f:db: + 9e:66:13:21:d4:ed:6b:9f:43:59:39:8e:74:b5:9e:62:41:36: + 1d:18:27:5d:21:c7:27:f6:35:fc:9b:e2:d6:19:3d:3c:fc:49: + 75:1a:a1:dd:3d:47:4f:54:38:4f:f9:36:78:91:5e:9f:26:4e: + cb:10:82:1e:e8:7f:30:84:17:63:6a:44:28:c7:af:57:ea:90: + e6:e7:cc:57:8b:ea:3b:44:5c:47:e4:db:25:47:ab:67:5f:8b: + 9a:49:09:fb:8b:c3:49:9c:cd:18:fa:3e:40:64:fe:da:41:95: + 88:f0:62:3a:c1:13:f7:69:bc:d4:7b:3c:af:07:25:4a:e4:6c: + 07:cd:2b:12 +-----BEGIN CERTIFICATE----- +MIIE3DCCA8SgAwIBAgIBNDANBgkqhkiG9w0BAQUFADCBlTELMAkGA1UEBhMCVVMx +EDAOBgNVBAgMB01vbnRhbmExEDAOBgNVBAcMB0JvemVtYW4xETAPBgNVBAoMCFNh +d3Rvb3RoMRMwEQYDVQQLDApDb25zdWx0aW5nMRgwFgYDVQQDDA93d3cud29sZnNz +bC5jb20xIDAeBgkqhkiG9w0BCQEWEWZhY3RzQHdvbGZzc2wuY29tMB4XDTI2MDgw +NjE2MjgwMFoXDTI5MDUwMjE2MjgwMFowgZUxCzAJBgNVBAYTAlVTMRAwDgYDVQQI +DAdNb250YW5hMRAwDgYDVQQHDAdCb3plbWFuMREwDwYDVQQKDAhTYXd0b290aDET +MBEGA1UECwwKQ29uc3VsdGluZzEYMBYGA1UEAwwPd3d3LndvbGZzc2wuY29tMSAw +HgYJKoZIhvcNAQkBFhFmYWN0c0B3b2xmc3NsLmNvbTCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBAL8Myi0Ush6EQlvNOB9K8k11EPG2NZ/fyn0DmNOs3gNm +7irx2LB9bgdUCxCYIU2AyxIg58xP3kV9yXJ3MurKkLtpUhADL6jzlcXxi2JWG+9n +b6QQQZWtCpvjpcCw0nB2UDBbqOgILHztp6J6jTgpHKzH7fJ8lbCVgn1JXDjNdyXv +vYB1U5Q8PcpjW58VtdMdEy8Z0TzbdjrMuH3J5cLX2kBv2CHccxtCLVOc/hr8fat6 +Nj+Y3oR8BWfOahQ4h6nxjLVoy2h/cSAr9aBj9VYvoybSt2+xWhfXOJkI/pNYb/7D +E0kIFgunTWcAUjFnI06Y7VFFHbkE2Qvs2CizS73tNnkCAwEAAaOCATMwggEvMB0G +A1UdDgQWBBQnjmcRdMMmHT/tM2OzpNgdMOXo1TCBwgYDVR0jBIG6MIG3gBQnjmcR +dMMmHT/tM2OzpNgdMOXo1aGBm6SBmDCBlTELMAkGA1UEBhMCVVMxEDAOBgNVBAgM +B01vbnRhbmExEDAOBgNVBAcMB0JvemVtYW4xETAPBgNVBAoMCFNhd3Rvb3RoMRMw +EQYDVQQLDApDb25zdWx0aW5nMRgwFgYDVQQDDA93d3cud29sZnNzbC5jb20xIDAe +BgkqhkiG9w0BCQEWEWZhY3RzQHdvbGZzc2wuY29tggE0MAwGA1UdEwQFMAMBAf8w +HAYDVR0RBBUwE4ILZXhhbXBsZS5jb22HBH8AAAEwHQYDVR0lBBYwFAYIKwYBBQUH +AwEGCCsGAQUFBwMCMA0GCSqGSIb3DQEBBQUAA4IBAQBNRNEX2y2fp2q9Df/d2VHO +KEMQQIjKfUVHJB3GEUSvtFloJEk6ghJMQV6qeICz51X1dCEG6zBw04Sb+w2oexbF +APlTDvyJ+aG7BuabM5Rzh6wPrMg5QtPTnZxjs+sPGptJSmB8axaqVoudvzrJpu++ +v9BzxFgj1qu24Wu4T9ueZhMh1O1rn0NZOY50tZ5iQTYdGCddIccn9jX8m+LWGT08 +/El1GqHdPUdPVDhP+TZ4kV6fJk7LEIIe6H8whBdjakQox69X6pDm58xXi+o7RFxH +5NslR6tnX4uaSQn7i8NJnM0Y+j5AZP7aQZWI8GI6wRP3abzUezyvByVK5GwHzSsS +-----END CERTIFICATE----- diff --git a/certs/server-cert-sha1.pem b/certs/server-cert-sha1.pem new file mode 100644 index 00000000000..755b7526c4a --- /dev/null +++ b/certs/server-cert-sha1.pem @@ -0,0 +1,92 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 51 (0x33) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C = US, ST = Montana, L = Bozeman, O = Sawtooth, OU = Consulting, CN = www.wolfssl.com, emailAddress = facts@wolfssl.com + Validity + Not Before: Aug 6 16:27:32 2026 GMT + Not After : May 2 16:27:32 2029 GMT + Subject: C = US, ST = Montana, L = Bozeman, O = wolfSSL, OU = Support, CN = www.wolfssl.com, emailAddress = facts@wolfssl.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:c0:95:08:e1:57:41:f2:71:6d:b7:d2:45:41:27: + 01:65:c6:45:ae:f2:bc:24:30:b8:95:ce:2f:4e:d6: + f6:1c:88:bc:7c:9f:fb:a8:67:7f:fe:5c:9c:51:75: + f7:8a:ca:07:e7:35:2f:8f:e1:bd:7b:c0:2f:7c:ab: + 64:a8:17:fc:ca:5d:7b:ba:e0:21:e5:72:2e:6f:2e: + 86:d8:95:73:da:ac:1b:53:b9:5f:3f:d7:19:0d:25: + 4f:e1:63:63:51:8b:0b:64:3f:ad:43:b8:a5:1c:5c: + 34:b3:ae:00:a0:63:c5:f6:7f:0b:59:68:78:73:a6: + 8c:18:a9:02:6d:af:c3:19:01:2e:b8:10:e3:c6:cc: + 40:b4:69:a3:46:33:69:87:6e:c4:bb:17:a6:f3:e8: + dd:ad:73:bc:7b:2f:21:b5:fd:66:51:0c:bd:54:b3: + e1:6d:5f:1c:bc:23:73:d1:09:03:89:14:d2:10:b9: + 64:c3:2a:d0:a1:96:4a:bc:e1:d4:1a:5b:c7:a0:c0: + c1:63:78:0f:44:37:30:32:96:80:32:23:95:a1:77: + ba:13:d2:97:73:e2:5d:25:c9:6a:0d:c3:39:60:a4: + b4:b0:69:42:42:09:e9:d8:08:bc:33:20:b3:58:22: + a7:aa:eb:c4:e1:e6:61:83:c5:d2:96:df:d9:d0:4f: + ad:d7 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + B3:11:32:C9:92:98:84:E2:C9:F8:D0:3B:6E:03:42:CA:1F:0E:8E:3C + X509v3 Authority Key Identifier: + keyid:27:8E:67:11:74:C3:26:1D:3F:ED:33:63:B3:A4:D8:1D:30:E5:E8:D5 + DirName:/C=US/ST=Montana/L=Bozeman/O=Sawtooth/OU=Consulting/CN=www.wolfssl.com/emailAddress=facts@wolfssl.com + serial:67:19:D2:A8:7F:E3:2D:FA:75:7A:4F:E7:B2:02:D9:AD:C4:77:5E:F8 + X509v3 Basic Constraints: + CA:TRUE + X509v3 Subject Alternative Name: + DNS:example.com, IP Address:127.0.0.1 + X509v3 Extended Key Usage: + TLS Web Server Authentication, TLS Web Client Authentication + Signature Algorithm: sha1WithRSAEncryption + Signature Value: + 32:12:bf:01:29:16:56:ef:e2:7a:8c:f9:97:cd:dd:60:fc:62: + 59:eb:87:2f:92:81:eb:68:b5:90:c9:24:4f:33:42:b1:df:ea: + 23:c8:7e:5b:51:b6:42:fe:4d:82:50:5c:b9:c2:70:a5:48:76: + 29:09:55:77:de:39:af:13:12:35:ce:62:54:5a:2b:19:33:f1: + 36:1b:7d:d9:ca:a1:a6:e7:b3:90:a3:06:1b:3a:17:7d:da:1f: + 01:ff:30:9d:33:7e:ea:68:f3:8f:fe:5f:8c:1d:08:b8:6d:77: + 61:78:87:81:65:98:9e:e9:13:f8:c2:36:ab:6f:25:7c:6c:0a: + 7e:04:6d:b4:75:f1:16:bd:bf:1a:15:a8:c9:77:33:4d:6f:93: + bf:98:b4:76:e4:2e:f9:5b:00:73:82:51:be:56:e7:cf:dc:4b: + ca:88:91:d5:5e:5a:ec:76:d1:a0:0f:ef:a8:c6:51:45:09:38: + 7f:fa:a5:32:87:09:4a:86:ce:cb:b3:17:61:df:c2:68:c3:24: + 0e:ac:c3:1f:af:2a:36:8c:d2:2a:a0:d8:fd:68:e5:5d:29:05: + 1f:0d:70:ef:63:8e:28:f4:1f:9d:5a:f7:fc:ef:7d:31:1d:53: + 0a:a3:66:74:b0:f4:48:cc:07:34:87:39:a7:20:3b:0e:1d:fc: + dc:4d:1e:61 +-----BEGIN CERTIFICATE----- +MIIE6zCCA9OgAwIBAgIBMzANBgkqhkiG9w0BAQUFADCBlTELMAkGA1UEBhMCVVMx +EDAOBgNVBAgMB01vbnRhbmExEDAOBgNVBAcMB0JvemVtYW4xETAPBgNVBAoMCFNh +d3Rvb3RoMRMwEQYDVQQLDApDb25zdWx0aW5nMRgwFgYDVQQDDA93d3cud29sZnNz +bC5jb20xIDAeBgkqhkiG9w0BCQEWEWZhY3RzQHdvbGZzc2wuY29tMB4XDTI2MDgw +NjE2MjczMloXDTI5MDUwMjE2MjczMlowgZExCzAJBgNVBAYTAlVTMRAwDgYDVQQI +DAdNb250YW5hMRAwDgYDVQQHDAdCb3plbWFuMRAwDgYDVQQKDAd3b2xmU1NMMRAw +DgYDVQQLDAdTdXBwb3J0MRgwFgYDVQQDDA93d3cud29sZnNzbC5jb20xIDAeBgkq +hkiG9w0BCQEWEWZhY3RzQHdvbGZzc2wuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAwJUI4VdB8nFtt9JFQScBZcZFrvK8JDC4lc4vTtb2HIi8fJ/7 +qGd//lycUXX3isoH5zUvj+G9e8AvfKtkqBf8yl17uuAh5XIuby6G2JVz2qwbU7lf +P9cZDSVP4WNjUYsLZD+tQ7ilHFw0s64AoGPF9n8LWWh4c6aMGKkCba/DGQEuuBDj +xsxAtGmjRjNph27Euxem8+jdrXO8ey8htf1mUQy9VLPhbV8cvCNz0QkDiRTSELlk +wyrQoZZKvOHUGlvHoMDBY3gPRDcwMpaAMiOVoXe6E9KXc+JdJclqDcM5YKS0sGlC +Qgnp2Ai8MyCzWCKnquvE4eZhg8XSlt/Z0E+t1wIDAQABo4IBRjCCAUIwHQYDVR0O +BBYEFLMRMsmSmITiyfjQO24DQsofDo48MIHVBgNVHSMEgc0wgcqAFCeOZxF0wyYd +P+0zY7Ok2B0w5ejVoYGbpIGYMIGVMQswCQYDVQQGEwJVUzEQMA4GA1UECAwHTW9u +dGFuYTEQMA4GA1UEBwwHQm96ZW1hbjERMA8GA1UECgwIU2F3dG9vdGgxEzARBgNV +BAsMCkNvbnN1bHRpbmcxGDAWBgNVBAMMD3d3dy53b2xmc3NsLmNvbTEgMB4GCSqG +SIb3DQEJARYRZmFjdHNAd29sZnNzbC5jb22CFGcZ0qh/4y36dXpP57IC2a3Ed174 +MAwGA1UdEwQFMAMBAf8wHAYDVR0RBBUwE4ILZXhhbXBsZS5jb22HBH8AAAEwHQYD +VR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMA0GCSqGSIb3DQEBBQUAA4IBAQAy +Er8BKRZW7+J6jPmXzd1g/GJZ64cvkoHraLWQySRPM0Kx3+ojyH5bUbZC/k2CUFy5 +wnClSHYpCVV33jmvExI1zmJUWisZM/E2G33ZyqGm57OQowYbOhd92h8B/zCdM37q +aPOP/l+MHQi4bXdheIeBZZie6RP4wjarbyV8bAp+BG20dfEWvb8aFajJdzNNb5O/ +mLR25C75WwBzglG+VufP3EvKiJHVXlrsdtGgD++oxlFFCTh/+qUyhwlKhs7Lsxdh +38JowyQOrMMfryo2jNIqoNj9aOVdKQUfDXDvY44o9B+dWvf8730xHVMKo2Z0sPRI +zAc0hzmnIDsOHfzcTR5h +-----END CERTIFICATE----- diff --git a/src/tls13.c b/src/tls13.c index 83ac9bd39a5..5022aad3074 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -4132,6 +4132,48 @@ static int EchCalcAcceptance(WOLFSSL* ssl, byte* label, word16 labelSz, } #endif +#if !defined(NO_CERTS) && !defined(WOLFSSL_NO_SIGALG) && \ + (!defined(NO_WOLFSSL_CLIENT) || !defined(NO_WOLFSSL_SERVER)) +/* Record whether the peer's advertised algorithms permit SHA-1 signed + * certificates. + * + * RFC 8446 Section 4.2.3 has signature_algorithms cover certificate signatures + * when signature_algorithms_cert is absent. + * + * ssl The SSL/TLS object. + * peerSuites The peer's signature_algorithms list. + */ +static void SetPeerSha1CertOk(WOLFSSL* ssl, const Suites* peerSuites) +{ + const byte* list = NULL; + word16 listSz = 0; + word16 i; + + ssl->options.peerSha1CertOk = 0; + + if (ssl->certHashSigAlgoSz > 0) { + list = ssl->certHashSigAlgo; + listSz = ssl->certHashSigAlgoSz; + } + else if (peerSuites != NULL) { + list = peerSuites->hashSigAlgo; + listSz = peerSuites->hashSigAlgoSz; + } + else { + return; + } + + for (i = 0; (word16)(i + 1) < listSz; i += 2) { + /* Only rsa_pkcs1_sha1, dsa_sha1 and ecdsa_sha1 carry sha_mac as the + * first byte of the signature scheme. */ + if (list[i] == sha_mac) { + ssl->options.peerSha1CertOk = 1; + break; + } + } +} +#endif /* !NO_CERTS && !WOLFSSL_NO_SIGALG && (client || server) */ + #ifndef NO_WOLFSSL_CLIENT #if defined(HAVE_SESSION_TICKET) || !defined(NO_PSK) #if defined(OPENSSL_EXTRA) && !defined(WOLFSSL_PSK_ONE_ID) && \ @@ -6235,6 +6277,11 @@ static int DoTls13CertificateRequest(WOLFSSL* ssl, const byte* input, WOLFSSL_ENTER("DoTls13CertificateRequest"); XMEMSET(&peerSuites, 0, sizeof(Suites)); +#if !defined(WOLFSSL_NO_SIGALG) + /* Post-handshake auth can deliver several requests; each one's cert + * signature algorithms replace the last rather than adding to them. */ + ssl->certHashSigAlgoSz = 0; +#endif #ifdef WOLFSSL_CALLBACKS if (ssl->hsInfoOn) AddPacketName(ssl, "CertificateRequest"); @@ -6322,6 +6369,11 @@ static int DoTls13CertificateRequest(WOLFSSL* ssl, const byte* input, WOLFSSL_ERROR_VERBOSE(INVALID_PARAMETER); return INVALID_PARAMETER; } + +#if !defined(NO_CERTS) && !defined(WOLFSSL_NO_SIGALG) + SetPeerSha1CertOk(ssl, &peerSuites); +#endif + #ifdef WOLFSSL_CERT_SETUP_CB if ((ret = CertSetupCbWrapper(ssl)) != 0) return ret; @@ -7807,6 +7859,11 @@ int DoTls13ClientHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx, XMEMCPY(ssl->clSuites->suites, input + args->idx, ssl->clSuites->suiteSz); args->idx += ssl->clSuites->suiteSz; ssl->clSuites->hashSigAlgoSz = 0; +#if !defined(NO_CERTS) && !defined(WOLFSSL_NO_SIGALG) + /* Discard any list kept from a previous ClientHello on this object; a + * second hello that drops signature_algorithms_cert must not inherit it. */ + ssl->certHashSigAlgoSz = 0; +#endif /* Compression */ b = input[args->idx++]; @@ -7856,6 +7913,10 @@ int DoTls13ClientHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx, goto exit_dch; } +#if !defined(NO_CERTS) && !defined(WOLFSSL_NO_SIGALG) + SetPeerSha1CertOk(ssl, ssl->clSuites); +#endif + #if (defined(HAVE_SESSION_TICKET) || !defined(NO_PSK)) && \ defined(HAVE_TLS_EXTENSIONS) /* RFC 8446 Section 4.2.11: the pre_shared_key extension MUST be the @@ -9860,6 +9921,150 @@ static int SetupOcspResp(WOLFSSL* ssl) } #endif +#if !defined(NO_CERTS) && !defined(WOLFSSL_NO_SIGALG) +/* Get the signature algorithm OID sum of a DER encoded certificate. + * + * der Buffer holding the DER encoded certificate. + * derSz Length of the DER encoded certificate. + * oid On success, the OID sum of the signature algorithm. + * returns 0 on success, otherwise a negative ASN error. + */ +static int GetCertSigAlgoOid(const byte* der, word32 derSz, word32* oid) +{ + word32 idx = 0; + int len = 0; + int ret; + + ret = GetSequence(der, &idx, &len, derSz); + if (ret >= 0) + ret = GetSequence(der, &idx, &len, derSz); + if (ret >= 0) { + /* signatureAlgorithm immediately follows the tbsCertificate. */ + if ((word32)len > derSz - idx) { + ret = ASN_PARSE_E; + } + else { + idx += (word32)len; + ret = GetAlgoId(der, &idx, oid, oidSigType, derSz); + } + } + + return ret; +} + +/* Certificate is signed with the deprecated SHA-1 hash. An unrecognized or + * unparsable algorithm is not SHA-1; the peer still verifies the chain. + * + * der Buffer holding the DER encoded certificate. + * derSz Length of the DER encoded certificate. + * returns 1 when SHA-1 signed, 0 otherwise. + */ +static int IsSha1SignedCert(const byte* der, word32 derSz) +{ + word32 oid = 0; + + if (GetCertSigAlgoOid(der, derSz, &oid) != 0) + return 0; + + return (oid == CTC_SHAwRSA) || (oid == CTC_SHAwECDSA) || + (oid == CTC_SHAwDSA); +} + +/* Certificate is self signed and therefore a trust anchor. RFC 8446 + * Section 4.4.2.2 allows it to be left out of the chain entirely, so its + * signature does not constrain what may be sent. + * + * ssl The SSL/TLS object. + * der Buffer holding the DER encoded certificate. + * derSz Length of the DER encoded certificate. + * returns 1 when self signed, 0 otherwise. + */ +static int IsSelfSignedCert(WOLFSSL* ssl, const byte* der, word32 derSz) +{ + DecodedCert* cert; + int selfSigned = 0; + + cert = (DecodedCert*)XMALLOC(sizeof(DecodedCert), ssl->heap, + DYNAMIC_TYPE_DCERT); + if (cert == NULL) + return 0; + + InitDecodedCert(cert, der, derSz, ssl->heap); + if (ParseCertRelative(cert, CERT_TYPE, NO_VERIFY, NULL, NULL) == 0) + selfSigned = (cert->selfSigned != 0); + FreeDecodedCert(cert); + XFREE(cert, ssl->heap, DYNAMIC_TYPE_DCERT); + + return selfSigned; +} + +/* Check the chain about to be sent against what the peer advertised. + * + * RFC 8446 Section 4.4.2.2 permits a fallback chain the peer did not advertise + * support for, but the chain "MUST NOT" use SHA-1 unless the peer's + * advertisement permits it. Section 4.4.2.3 requires client certificates to be + * signed with an acceptable algorithm "as described in Section 4.4.2.2", so the + * same rule covers both sides. How a failure is resolved differs by side and is + * left to the caller. + * + * ssl The SSL/TLS object. + * returns 0 when the chain may be sent, MATCH_SUITE_ERROR otherwise. + */ +static int CheckCertChainSigAlgo(WOLFSSL* ssl) +{ + byte* chain; + byte* cur; + word32 chainSz; + word32 len; + word32 idx = 0; + int ret = 0; + + if (ssl->options.peerSha1CertOk) + return 0; + + if (ssl->buffers.certificate == NULL || + ssl->buffers.certificate->buffer == NULL) { + return 0; + } + + if (IsSha1SignedCert(ssl->buffers.certificate->buffer, + ssl->buffers.certificate->length) && + !IsSelfSignedCert(ssl, ssl->buffers.certificate->buffer, + ssl->buffers.certificate->length)) { + ret = MATCH_SUITE_ERROR; + } + + if (ret == 0 && ssl->buffers.certChain != NULL && + ssl->buffers.certChain->buffer != NULL && + ssl->buffers.certChainCnt > 0) { + chain = ssl->buffers.certChain->buffer; + chainSz = ssl->buffers.certChain->length; + + while (ret == 0) { + cur = chain + idx; + /* NextCert() length includes the CERT_HEADER_SZ byte prefix and + * is 0 at the end of the list. Keep this terminator matching the + * send loop so both walk the same certificates. */ + len = NextCert(chain, chainSz, &idx); + if (len == 0) + break; + if (len <= CERT_HEADER_SZ) + continue; + cur += CERT_HEADER_SZ; + len -= CERT_HEADER_SZ; + + if (IsSha1SignedCert(cur, len) && !IsSelfSignedCert(ssl, cur, len)) + ret = MATCH_SUITE_ERROR; + } + } + + if (ret != 0) + WOLFSSL_MSG("Chain is SHA-1 signed but peer did not advertise SHA-1"); + + return ret; +} +#endif /* !NO_CERTS && !WOLFSSL_NO_SIGALG */ + /* handle generation TLS v1.3 certificate (11) */ /* Send the certificate for this end and any CAs that help with validation. * This message is always encrypted in TLS v1.3. @@ -9921,6 +10126,30 @@ static int SendTls13Certificate(WOLFSSL* ssl) } #endif +#ifndef WOLFSSL_NO_SIGALG + /* Run before the blank certificate branch below so a client with nothing + * acceptable can fall into it. */ + if (ssl->options.sendVerify != SEND_BLANK_CERT && ssl->fragOffset == 0 && + CheckCertChainSigAlgo(ssl) != 0) { + if (ssl->options.side == WOLFSSL_SERVER_END) { + SendAlert(ssl, alert_fatal, handshake_failure); + WOLFSSL_ERROR_VERBOSE(MATCH_SUITE_ERROR); + return MATCH_SUITE_ERROR; + } + #ifndef WOLFSSL_NO_CLIENT_CERT_ERROR + /* RFC 8446 Section 4.4.2: a client with no acceptable certificate + * sends an empty certificate_list rather than failing. */ + WOLFSSL_MSG("Client chain not acceptable, sending blank certificate"); + ssl->options.sendVerify = SEND_BLANK_CERT; + #else + WOLFSSL_MSG("Client chain not acceptable and blank cert not allowed"); + SendAlert(ssl, alert_fatal, certificate_required); + WOLFSSL_ERROR_VERBOSE(NO_CERT_ERROR); + return NO_CERT_ERROR; + #endif + } +#endif + if (ssl->options.sendVerify == SEND_BLANK_CERT) { certSz = 0; certChainSz = 0; diff --git a/tests/api/test_tls13.c b/tests/api/test_tls13.c index c5fe3189c36..625567d619d 100644 --- a/tests/api/test_tls13.c +++ b/tests/api/test_tls13.c @@ -6580,6 +6580,149 @@ int test_tls13_cert_req_sigalgs(void) return EXPECT_RESULT(); } +/* RFC 8446 Section 4.4.2.2: the chain a server sends MUST NOT be SHA-1 signed + * unless the client's advertisement permits SHA-1. */ +int test_tls13_sha1_cert_chain(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_CERTS) && !defined(NO_RSA) && !defined(NO_SHA) && \ + !defined(WOLFSSL_NO_SIGALG) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(NO_WOLFSSL_SERVER) && !defined(NO_FILESYSTEM) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + const char* sha1CertFile = "./certs/server-cert-sha1.pem"; + const char* sha1RootFile = "./certs/server-cert-sha1-root.pem"; + const char* sha1CliFile = "./certs/client-cert-sha1.pem"; + + /* A TLS 1.3 client does not offer SHA-1, so the SHA-1 signed leaf must + * not be sent. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_use_certificate_chain_file(ssl_s, sha1CertFile), + WOLFSSL_SUCCESS); + ExpectIntNE(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + /* Read the raw error; wolfSSL_get_error() remaps this one to + * WOLFSSL_ERROR_SYSCALL when OPENSSL_EXTRA is on. */ + ExpectIntEQ(ssl_s->error, WC_NO_ERR_TRACE(MATCH_SUITE_ERROR)); + + wolfSSL_free(ssl_c); ssl_c = NULL; + wolfSSL_free(ssl_s); ssl_s = NULL; + wolfSSL_CTX_free(ctx_c); ctx_c = NULL; + wolfSSL_CTX_free(ctx_s); ctx_s = NULL; + +#if defined(OPENSSL_EXTRA) && defined(WC_RSA_PSS) + /* Same certificate, but this client advertises rsa_pkcs1_sha1, which the + * RFC allows the server to honor. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_use_certificate_chain_file(ssl_s, sha1CertFile), + WOLFSSL_SUCCESS); + /* The signature algorithm list parser has no name for SHA-1 once old TLS + * versions are compiled out, so append the scheme directly. */ + ExpectIntEQ(wolfSSL_set1_sigalgs_list(ssl_c, "RSA-PSS+SHA256"), + WOLFSSL_SUCCESS); + if (EXPECT_SUCCESS()) { + ssl_c->suites->hashSigAlgo[ssl_c->suites->hashSigAlgoSz++] = sha_mac; + ssl_c->suites->hashSigAlgo[ssl_c->suites->hashSigAlgoSz++] = + rsa_sa_algo; + } + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + wolfSSL_free(ssl_c); ssl_c = NULL; + wolfSSL_free(ssl_s); ssl_s = NULL; + wolfSSL_CTX_free(ctx_c); ctx_c = NULL; + wolfSSL_CTX_free(ctx_s); ctx_s = NULL; +#endif /* OPENSSL_EXTRA && WC_RSA_PSS */ + + /* A SHA-256 signed chain is unaffected. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_use_certificate_chain_file(ssl_s, svrCertFile), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + wolfSSL_free(ssl_c); ssl_c = NULL; + wolfSSL_free(ssl_s); ssl_s = NULL; + wolfSSL_CTX_free(ctx_c); ctx_c = NULL; + wolfSSL_CTX_free(ctx_s); ctx_s = NULL; + + /* The self signed root may be dropped from the chain entirely, so its + * SHA-1 signature does not block the handshake. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_use_certificate_chain_file(ssl_s, sha1RootFile), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + wolfSSL_free(ssl_c); ssl_c = NULL; + wolfSSL_free(ssl_s); ssl_s = NULL; + wolfSSL_CTX_free(ctx_c); ctx_c = NULL; + wolfSSL_CTX_free(ctx_s); ctx_s = NULL; + + /* A client whose own chain is SHA-1 signed sends an empty + * certificate_list instead of failing (RFC 8446 Section 4.4.2). The + * server asks for a certificate but does not require one. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + if (EXPECT_SUCCESS()) + wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_PEER, NULL); + ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx_s, caCertFile, 0), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_use_certificate_chain_file(ssl_c, sha1CliFile), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_use_PrivateKey_file(ssl_c, cliKeyFile, CERT_FILETYPE), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(ssl_c->options.sendVerify, SEND_BLANK_CERT); + ExpectIntEQ(ssl_s->options.havePeerCert, 0); + + wolfSSL_free(ssl_c); ssl_c = NULL; + wolfSSL_free(ssl_s); ssl_s = NULL; + wolfSSL_CTX_free(ctx_c); ctx_c = NULL; + wolfSSL_CTX_free(ctx_s); ctx_s = NULL; + +#if defined(OPENSSL_EXTRA) && defined(WC_RSA_PSS) + /* Same client chain, but the CertificateRequest advertises + * rsa_pkcs1_sha1, so the client sends its real certificate. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + if (EXPECT_SUCCESS()) + wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_PEER, NULL); + ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx_s, caCertFile, 0), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set1_sigalgs_list(ssl_s, "RSA-PSS+SHA256"), + WOLFSSL_SUCCESS); + if (EXPECT_SUCCESS()) { + ssl_s->suites->hashSigAlgo[ssl_s->suites->hashSigAlgoSz++] = sha_mac; + ssl_s->suites->hashSigAlgo[ssl_s->suites->hashSigAlgoSz++] = + rsa_sa_algo; + } + ExpectIntEQ(wolfSSL_use_certificate_chain_file(ssl_c, sha1CliFile), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_use_PrivateKey_file(ssl_c, cliKeyFile, CERT_FILETYPE), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(ssl_c->options.sendVerify, SEND_CERT); + ExpectIntEQ(ssl_s->options.havePeerCert, 1); + + wolfSSL_free(ssl_c); ssl_c = NULL; + wolfSSL_free(ssl_s); ssl_s = NULL; + wolfSSL_CTX_free(ctx_c); ctx_c = NULL; + wolfSSL_CTX_free(ctx_s); ctx_s = NULL; +#endif /* OPENSSL_EXTRA && WC_RSA_PSS */ +#endif + + return EXPECT_RESULT(); +} + int test_tls13_derive_keys_no_key(void) { EXPECT_DECLS; diff --git a/tests/api/test_tls13.h b/tests/api/test_tls13.h index 30ac62952fd..4b534fb2104 100644 --- a/tests/api/test_tls13.h +++ b/tests/api/test_tls13.h @@ -49,6 +49,7 @@ int test_tls13_warning_alert_is_fatal(void); int test_tls13_unknown_ext_rejected(void); int test_tls13_hrr_recognized_ext_downgrade(void); int test_tls13_cert_req_sigalgs(void); +int test_tls13_sha1_cert_chain(void); int test_tls13_derive_keys_no_key(void); int test_tls13_pqc_hybrid_truncated_keyshare(void); int test_tls13_pqc_hybrid_malformed_ecdh(void); @@ -130,6 +131,7 @@ int test_tls13_pha_status_request(void); TEST_DECL_GROUP("tls13", test_tls13_plaintext_alert), \ TEST_DECL_GROUP("tls13", test_tls13_warning_alert_is_fatal), \ TEST_DECL_GROUP("tls13", test_tls13_cert_req_sigalgs), \ + TEST_DECL_GROUP("tls13", test_tls13_sha1_cert_chain), \ TEST_DECL_GROUP("tls13", test_tls13_derive_keys_no_key), \ TEST_DECL_GROUP("tls13", test_tls13_pqc_hybrid_truncated_keyshare), \ TEST_DECL_GROUP("tls13", test_tls13_pqc_hybrid_malformed_ecdh), \ diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 49e7cafe811..9b16c110c1c 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -5550,6 +5550,10 @@ struct Options { #ifdef WOLFSSL_EARLY_DATA word16 clientInEarlyData:1; /* Client is in wolfSSL_read_early_data */ #endif +#if defined(WOLFSSL_TLS13) && !defined(NO_CERTS) && !defined(WOLFSSL_NO_SIGALG) + word16 peerSha1CertOk:1; /* Peer advertised a SHA-1 signature + * scheme for certificates */ +#endif #ifdef WOLFSSL_DTLS byte haveMcast; /* using multicast ? */ #endif