diff --git a/certs/test/alt-chain-forged-ca/gen-alt-chain-forged-ca.sh b/certs/test/alt-chain-forged-ca/gen-alt-chain-forged-ca.sh new file mode 100755 index 00000000000..af22d5156e8 --- /dev/null +++ b/certs/test/alt-chain-forged-ca/gen-alt-chain-forged-ca.sh @@ -0,0 +1,83 @@ +#!/usr/bin/env bash +# +# gen-alt-chain-forged-ca.sh +# Generate the forged-CA alternate chain test certs. +# +# realRoot trusted anchor, CN=Root, fixed SKID DEADBEEF... +# evilRoot attacker key with the same name and SKID as realRoot. Only +# used to sign attackerCA; never sent or trusted. +# attackerCA CA issued by evilRoot. Its issuer name and AKID point at +# realRoot, so the peer finds realRoot as signer but the +# signature does not verify. +# intermediate unrelated self-signed CA, gives ASN_SELF_SIGNED_E which the +# alternate cert chain code bypasses. +# leaf issued by attackerCA. +# +# server-chain.pem is sent as: leaf, intermediate, attackerCA. Only it, +# realRoot-cert.pem and leaf-key.pem (the server's key) are kept. All other +# keys and certs are made fresh in a temp dir and removed once the chain is +# made. + +set -e + +DIR="$(cd "$(dirname "$0")" && pwd)" +WORK="$(mktemp -d)" +trap 'rm -f "$WORK"/*; rmdir "$WORK"' EXIT + +cat > "$WORK/ext.cnf" <<'CNF' +[req] +distinguished_name = dn +prompt = no +[dn] +[root] +basicConstraints = critical, CA:TRUE +keyUsage = critical, digitalSignature, keyCertSign, cRLSign +subjectKeyIdentifier = DE:AD:BE:EF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF +[ca] +basicConstraints = critical, CA:TRUE +keyUsage = critical, digitalSignature, keyCertSign, cRLSign +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid +[leaf] +basicConstraints = critical, CA:FALSE +keyUsage = critical, digitalSignature, keyEncipherment +extendedKeyUsage = serverAuth +subjectAltName = DNS:leaf.example.com +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid +CNF + +# $1 key-path $2 out +root(){ + openssl req -new -x509 -key "$1" -subj "/O=Test/CN=Root" \ + -days 7300 -sha256 -set_serial 1 -config "$WORK/ext.cnf" \ + -extensions root -out "$2" +} + +# $1 key-path $2 CN $3 issuer-cert $4 issuer-key-path $5 ext $6 out $7 serial +child(){ + openssl req -new -key "$1" -subj "/O=Test/CN=$2" \ + -config "$WORK/ext.cnf" -out "$WORK/c.csr" + openssl x509 -req -in "$WORK/c.csr" -CA "$3" -CAkey "$4" \ + -set_serial "$7" -days 7300 -sha256 -extfile "$WORK/ext.cnf" \ + -extensions "$5" -out "$6" +} + +for k in realRoot evilRoot attackerCA intermediate; do + openssl genrsa -out "$WORK/$k-key.pem" 2048 +done +openssl genrsa -out "$DIR/leaf-key.pem" 2048 + +root "$WORK/realRoot-key.pem" "$DIR/realRoot-cert.pem" +root "$WORK/evilRoot-key.pem" "$WORK/evilRoot-cert.pem" +child "$WORK/attackerCA-key.pem" Attacker "$WORK/evilRoot-cert.pem" \ + "$WORK/evilRoot-key.pem" ca "$WORK/attackerCA-cert.pem" 2 +openssl req -new -x509 -key "$WORK/intermediate-key.pem" \ + -subj "/O=Test/CN=Intermediate" -days 7300 -sha256 -set_serial 3 \ + -config "$WORK/ext.cnf" -extensions ca -out "$WORK/intermediate-cert.pem" +child "$DIR/leaf-key.pem" leaf.example.com "$WORK/attackerCA-cert.pem" \ + "$WORK/attackerCA-key.pem" leaf "$WORK/leaf-cert.pem" 4 + +cat "$WORK/leaf-cert.pem" "$WORK/intermediate-cert.pem" \ + "$WORK/attackerCA-cert.pem" > "$DIR/server-chain.pem" +echo "Done" diff --git a/certs/test/alt-chain-forged-ca/leaf-key.pem b/certs/test/alt-chain-forged-ca/leaf-key.pem new file mode 100644 index 00000000000..ceee4245ab0 --- /dev/null +++ b/certs/test/alt-chain-forged-ca/leaf-key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDHqNwFqKxe/AEZ +VWZaHNniBMakNXNx7mtE1sV2+GrdkJQE5MLzpXAvjdPJtS6NuQPgBWvm8dAyYC7o +gBXSPFdvy2XwiJan1hEycgivINNbLYNmDS1PZK4XfNNqYupySA+hcIhmujkXKfut +WdivXlaOx0YUpw3XZpphd9Ry9aJ1Tn49jMb4zUXoGtZ8CvudLCdlcu5pXFwQqBfu +hX5K/f0y1v3ho1F4DJaTOEhXyH00vEZpJL/kbGW0lgymTkltZmm0LX9Judt+O3Zi +pT/RSQ+4fqHuvLH3ndGfJifZ3rodoerDbWlR/s+CWGO14zSugV39QLRh6xHgmbRU +i5mguhUzAgMBAAECggEAAzghFnarrNTLk1dLd3yxzXfXpUv270MLKyJHyQBkmYT1 +9gXWpN7c6hwtA+2uPfrznPGWPBq+PImzex+eYNqE0f8jRvEU2/wfukLMPaXXjC4c +L8TBOPkxG2mkvDlMrKIMnat2vF1LtvVmb8GcNLVEy2soIWrq7+p65vZrkDaPGezh +e2WI30saRLK0mFsJYOJ7IpD0WOcZWA/9QKBPxXpBkeGf8MJbujiHXAtLk6BXn19o +A2CfuXcYkamkXh48/R8ENu75i2PXqoRqX+TldcQcNXwQA5/el50So8ur2Bg1Jhfb +/Iig8X4rVKhrlpWgE+iSu0BooOjvUmEj9chjflXVTQKBgQDla4HuSbwYCCGoXiR9 +mTfl5V+yz/QYHsq+H8Lw2fUJicgRRVO1TMmoGNP+C0BeVp9JKnoPK1ZdlDvYuu7n +01XIJ9u/VBv13/b0s+QaCuAoN4AnO0a5jWKMRPp+65YBPhIUiHJBpQX2870+u6nM +vYnyWlc3FSRrlOLaYFY+XFUHfQKBgQDeyqxraolshiwtpEa8P3COevESA1BRhVjB +nScwfNOYBvYDp1vGYFoe9iza8wjxzY9Et20bP7sy5VvFKod/mpjAFeEUDsRUohmO +IfU4m6hPjrBZfWQ7jOI54Gf5UnjG2glJ3VF6pVA/uwZb8idRbUJ2zHmLUoz7sLLq +yR9Ej68ObwKBgG15jKnRo6DwGGeIbO9GMudQOgdQtlDgOKza6SIXT5d6mO6s31Hv +T3cYSei7I78gpKe8Tjx42KCVAK3eHeyf6xo08uZs017TvbttMBbBQ8/NFCkkXw5i +CjmXHNV9w7naDbSgm6z5e0wpaQ/Lay5Y77+nHmEZhKSVil7cg6sEUkrdAoGAKkvb +9ACj2mCWnoujijWZDtO0VLEgXHmuYgON3xyzpxcbOZJaUA0Wzu35kg+10QIvguI3 +6oZs6bq1c3chhk45Va5Rrtcm2tJze5Ji1b5nc8MpI7Q0hUG36Tj7RFAt8cKPJODJ +iTk9RPwitu4AHOtbROaI5LRlY5smPIC2WPPWTi0CgYAyMSUMrKoHp9HKthk6pErV ++u7t8qOjq7+l+/dX25GKQxoSu8SGNSI1uQSzHjD7Fs6yMqolerIHZo8/+PGXWIYh +j/b6PIneLhlRtYbatYbA8e38Peu4eqUSZG7SixjcJLZSoIxFhgtPpR+PfTTGlgQa +CneNQ6VLSbsPw2jpwNLf4Q== +-----END PRIVATE KEY----- diff --git a/certs/test/alt-chain-forged-ca/realRoot-cert.pem b/certs/test/alt-chain-forged-ca/realRoot-cert.pem new file mode 100644 index 00000000000..2f149ce9ade --- /dev/null +++ b/certs/test/alt-chain-forged-ca/realRoot-cert.pem @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC+TCCAeGgAwIBAgIBATANBgkqhkiG9w0BAQsFADAeMQ0wCwYDVQQKDARUZXN0 +MQ0wCwYDVQQDDARSb290MB4XDTI2MTEwNTIyMDQzNloXDTQ2MTAzMTIyMDQzNlow +HjENMAsGA1UECgwEVGVzdDENMAsGA1UEAwwEUm9vdDCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBAJcCodfd6jQD404Bp2L6Oi5SWRUQVvmGCN08P9FdoBT+ +CPoW9txRzXQ4jJcNUbD9+gcQm1NhNn1IS/rHE4ITWt5Gf2YaQnQkXUz6YdAZlSeO +TSV8abDp3fF4qLXlMyRtbDXShy3U4RAsNWd0Eu19/b9zWnGGRmA4v2ElFUmsYw4R +l1L48X+pX8XTQAg8RFGoryGgf3SuINR4qFkODFD5T5+GtkRPFbz8Tiztm3I7unY4 +l28RsWMQ++uQ+aPGa0ZxOmf3Mv4daBnZzWt6nYKyOp6Sx1V7ssDI5+MvFbtEC785 +3eRm/dYDBWLm5jPQMMNibrUHvAbT/jNL/T6lbM7TNvECAwEAAaNCMEAwDwYDVR0T +AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFN6tvu8AESIzRFVm +d4iZqrvM3e7/MA0GCSqGSIb3DQEBCwUAA4IBAQAQgpJ4KYMDe0sVloDcQKxTnuXX ++B48VP1YodWwpPZiRsokl9wxJ/bzaCTNPragH40PSouLwwxrsLVEC/PYzHlnW5lO +XGHXxnB35l0KcNpAef2oBb5z18WkkpgW1r0WGP2ns0ZG4yL6uN2Lb5qKtNnfPqHH +wpF08n2Emxjk5WRWI7jwZDA25MrGSMge5WnG+e+EdSED652zglTXmLtdwJESSEqg +XfsswK+cbMQeNh0hGKQhzfoUHTwB6g2/eFesgNKHQR4yAolbFGJ8LqMIDVOzBFIj +WQcIEPloNI5k7YBMOjVf3+eDoGUVVus4to+lUGwkr/tOrfFpiT3DRGYQEuD5 +-----END CERTIFICATE----- diff --git a/certs/test/alt-chain-forged-ca/server-chain.pem b/certs/test/alt-chain-forged-ca/server-chain.pem new file mode 100644 index 00000000000..ad119690182 --- /dev/null +++ b/certs/test/alt-chain-forged-ca/server-chain.pem @@ -0,0 +1,58 @@ +-----BEGIN CERTIFICATE----- +MIIDWzCCAkOgAwIBAgIBBDANBgkqhkiG9w0BAQsFADAiMQ0wCwYDVQQKDARUZXN0 +MREwDwYDVQQDDAhBdHRhY2tlcjAeFw0yNjExMDUyMjA0MzZaFw00NjEwMzEyMjA0 +MzZaMCoxDTALBgNVBAoMBFRlc3QxGTAXBgNVBAMMEGxlYWYuZXhhbXBsZS5jb20w +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDHqNwFqKxe/AEZVWZaHNni +BMakNXNx7mtE1sV2+GrdkJQE5MLzpXAvjdPJtS6NuQPgBWvm8dAyYC7ogBXSPFdv +y2XwiJan1hEycgivINNbLYNmDS1PZK4XfNNqYupySA+hcIhmujkXKfutWdivXlaO +x0YUpw3XZpphd9Ry9aJ1Tn49jMb4zUXoGtZ8CvudLCdlcu5pXFwQqBfuhX5K/f0y +1v3ho1F4DJaTOEhXyH00vEZpJL/kbGW0lgymTkltZmm0LX9Judt+O3ZipT/RSQ+4 +fqHuvLH3ndGfJifZ3rodoerDbWlR/s+CWGO14zSugV39QLRh6xHgmbRUi5mguhUz +AgMBAAGjgZMwgZAwDAYDVR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0l +BAwwCgYIKwYBBQUHAwEwGwYDVR0RBBQwEoIQbGVhZi5leGFtcGxlLmNvbTAdBgNV +HQ4EFgQUZWEHg66piqeBE79Yh++du4czvuEwHwYDVR0jBBgwFoAUIQq1riRwrOOl +Lf8TAmqaOC3UasYwDQYJKoZIhvcNAQELBQADggEBAGlQh2YNCTasmEy+B+Lre8FT +JhYUXFB7vYj6/0bkkiivf+DH++m3My13DDTgBWU6MaZWos7EJkDTsk1zeEkagU0W +bDyj+Rbv2wbYWRD+ePWXJ/Mlum+gQ9Pb6Gk+p71DWLTHxqTxe+GUuz72liLyHOOE +Wf/9aXd2pEchbsoAE/MLgjkR9ZXoUIGn1xz73LgX0OkfU6G27+0LILngmdgG1vFS ++niQ9uoJreqqRA6Ux8fdwt/dDrbR5jj6BameFXG/aPwXX7rVvxPaiRDUcuS7yWfj +hCWm3K6XzB0zw1QWvWdXjo1vBg2zD8qxcc1iKpplO57X6n7byEhbsD07voAcqnA= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDCTCCAfGgAwIBAgIBAzANBgkqhkiG9w0BAQsFADAmMQ0wCwYDVQQKDARUZXN0 +MRUwEwYDVQQDDAxJbnRlcm1lZGlhdGUwHhcNMjYxMTA1MjIwNDM2WhcNNDYxMDMx +MjIwNDM2WjAmMQ0wCwYDVQQKDARUZXN0MRUwEwYDVQQDDAxJbnRlcm1lZGlhdGUw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC+2lHDnIc9TZztNLvfHt0J +6F5n+l4djINJlO7qm0ionlAUxXGQ/yBD7tE4u7isVQdpTu8GVgMvFlzhRXGywtMW +WzQfmDhb5L44ImGvvzpBHg3PUoS6PfjCPetAQr3y2ffzZZb23inN/BkXKXyHxu/n +FkOZhC7XQPF2a0/Asux+ML8Y7bXZlkFGW7PHvWLgb2yIiR3l0/YN296jNtqv8k4z +sZGwscuQbcO2OYx64w7LuHb2t39K3y/l0AW2RizEOVme0RS1L9tqL6etPjRclw8w +rnI+Rvbm4vYNnT8Rr6oHmCbMbyaVVdgi9bAomDqBdECb8W95XQns0NjOOLfKkj99 +AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1Ud +DgQWBBQi1spsbHe3+fvuV6UCgLLcbyMwljANBgkqhkiG9w0BAQsFAAOCAQEAHWnP +efm8loo+GdhBAYqkx9WzI6GdTSL+NqA5n5gvFN/IHvWq0lKO8/NKhGLYAiwxgT9g +89ou9s8P6fQfakhKIv9SKh94pPNzGwAMMY5hERf56ia6lbUDm19XzOf/R20tx6tg +SStOqdbOML2/Pl1psUTWTzKr/KwmE/5kDvuinuXPccQ/Plq8n7BofuZiZA6xn9B+ +VETMynOl4zaEEN09cKSaOB76oq02chsFNwMjNy7WUy9w2XiuewSOCX1+l9q/HXSB +b8cD22aeScGDMFVc31BAvn5jG0G25aLBF3G3IbCm/VstR1S6PHIhya+29NMbEfG/ +JGwx+TKcPMbOHSZ5vA== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDHjCCAgagAwIBAgIBAjANBgkqhkiG9w0BAQsFADAeMQ0wCwYDVQQKDARUZXN0 +MQ0wCwYDVQQDDARSb290MB4XDTI2MTEwNTIyMDQzNloXDTQ2MTAzMTIyMDQzNlow +IjENMAsGA1UECgwEVGVzdDERMA8GA1UEAwwIQXR0YWNrZXIwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQClhVsnbALuXcBp9QsI6Fx+gaaugUtmh6KrvYfL +Ydip3MoifFI/UcYk70CHBYzF7hPUzkn10dRZqma4mOkBQ3etL2evEvUZUhsXTVLI +ogRFton4u5XBsdn6fBK0j9ytjdPZcPuPQNtyxj6IXLEPZpAfT0iOOHHzunWPDEpG +T3+DVjUkTt+rWNgPur8FdbOU8iDT11L3eSdoQRQUbavCM5+fiQGSw22iF3iZ7Y6j +breCyRSKCV6fvajHvulU6UgxnOm4hGAc6atHUuKc0NqCPdziIyz7qlIr5CZxecOg +MeaQqWkqhcJUtt5Mmu0Ph5KYH0ydbDbzRQW3L0Vw5BDJwqjrAgMBAAGjYzBhMA8G +A1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBQhCrWuJHCs +46Ut/xMCapo4LdRqxjAfBgNVHSMEGDAWgBTerb7vABEiM0RVZneImaq7zN3u/zAN +BgkqhkiG9w0BAQsFAAOCAQEAs9WndeHFpWEXSHYYdWJUU4fewSvGQLfAU0ZYF6Vf +bb7g2OkZc8yW3bQwV0oZUrHHfJUpMimabV21YXAjbbHDYs4iGmGGAxKHst5JSxPK +MYwn4QNr2lzsqcO6qYqsf8jJMR86B+pBvkPLU/bIY15MMzks+9mGqSyoj56UBO6n +dlu9EC+6aMX1sH51Z8sNWM+Z0OcfEv6/1NdXJdgapbfLmZT8dzmUsZ1/sO4KoToD +F54+AIllAUHC2qgAn9KjWba4xtQ0BYaHiDMBRdCZfFZoieyme93SoSYsADIbwZN+ +Rd6BEXGa83TNqC/PWmJGcl3F1G+e01ssvsavBHwD9Ty9MA== +-----END CERTIFICATE----- diff --git a/certs/test/include.am b/certs/test/include.am index 0857c3b5d34..a407eec1955 100644 --- a/certs/test/include.am +++ b/certs/test/include.am @@ -127,3 +127,9 @@ EXTRA_DIST += \ certs/test/nc-ancestor/03-leaf-chain.pem \ certs/test/nc-ancestor/03-valid-leaf-cert.pem \ certs/test/nc-ancestor/03-valid-leaf-key.pem + +EXTRA_DIST += \ + certs/test/alt-chain-forged-ca/gen-alt-chain-forged-ca.sh \ + certs/test/alt-chain-forged-ca/realRoot-cert.pem \ + certs/test/alt-chain-forged-ca/leaf-key.pem \ + certs/test/alt-chain-forged-ca/server-chain.pem diff --git a/src/internal.c b/src/internal.c index 1c5196874d2..870bd7beae4 100644 --- a/src/internal.c +++ b/src/internal.c @@ -19873,6 +19873,13 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx, && !args->haveTrustPeer #endif /* WOLFSSL_TRUST_PEER_CERT */ ) { + #if (defined(WOLFSSL_ALT_CERT_CHAINS) || \ + (defined(__APPLE__) && defined(WOLFSSL_SYS_CA_CERTS))) && \ + (defined(OPENSSL_EXTRA) || defined(OPENSSL_EXTRA_X509_SMALL)) + /* Error recorded by earlier certs, restored when this + * cert's CA error below is bypassed instead of wiped. */ + unsigned long peerVerifyRet = ssl->peerVerifyRet; + #endif int skipAddCA = 0; #if defined(HAVE_OCSP) && defined(HAVE_CRL) int ocspNoUrl = 0; @@ -20171,10 +20178,13 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx, } ret = 0; /* clear errors and continue */ + /* Only this cert's error is waived. Keep the + * errors from earlier certs, including + * args->verifyErr, so the chain is still + * rejected if it should be. */ #if defined(OPENSSL_EXTRA) || defined(OPENSSL_EXTRA_X509_SMALL) - ssl->peerVerifyRet = 0; + ssl->peerVerifyRet = peerVerifyRet; #endif - args->verifyErr = 0; /* do not add to certificate manager */ skipAddCA = 1; @@ -20209,10 +20219,12 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx, WOLFSSL_MSG("Bypassing errors to allow for Apple native" " CA validation"); ret = 0; /* clear errors and continue */ - args->verifyErr = 0; + /* Only this cert's error is waived. Keep the errors + * from earlier certs, including args->verifyErr, so + * the chain is still rejected if it should be. */ #if defined(OPENSSL_EXTRA) \ || defined(OPENSSL_EXTRA_X509_SMALL) - ssl->peerVerifyRet = 0; + ssl->peerVerifyRet = peerVerifyRet; #endif /* do not add to certificate manager */ skipAddCA = 1; diff --git a/tests/api/test_ssl_cert.c b/tests/api/test_ssl_cert.c index 64c29841f44..857db379407 100644 --- a/tests/api/test_ssl_cert.c +++ b/tests/api/test_ssl_cert.c @@ -3978,6 +3978,146 @@ int test_wolfSSL_chain_ca_ext_key_usage(void) return EXPECT_RESULT(); } +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(WOLFSSL_ALT_CERT_CHAINS) && defined(OPENSSL_EXTRA) && \ + defined(WOLFSSL_PEM_TO_DER) && !defined(NO_RSA) && !defined(NO_TLS) && \ + !defined(NO_SHA256) && !defined(NO_FILESYSTEM) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) +static int test_alt_chain_forged_ca_cb_calls; + +/* Accept every certificate and leave the decision to + * wolfSSL_get_verify_result() once the handshake is done. */ +static int test_alt_chain_forged_ca_cb(int preverify, + WOLFSSL_X509_STORE_CTX* store) +{ + (void)preverify; + (void)store; + test_alt_chain_forged_ca_cb_calls++; + return 1; +} + +#ifndef NO_WOLFSSL_CM_VERIFY +static int test_alt_chain_forged_ca_cm_calls; + +/* Certificate manager callback that rejects every certificate that failed. */ +static int test_alt_chain_forged_ca_cm_cb(int preverify, + WOLFSSL_X509_STORE_CTX* store) +{ + (void)store; + test_alt_chain_forged_ca_cm_calls++; + return preverify; +} +#endif +#endif + +/* Test that bypassing a CA error for the alternate certificate chain does not + * also erase an error recorded by an earlier certificate in the chain. + * + * The server sends leaf, intermediate, attackerCA. attackerCA names the + * trusted root as its issuer, with a matching AKID, but is signed by another + * key, so its signature check fails. The intermediate is an unrelated + * self-signed CA, so it fails with ASN_SELF_SIGNED_E, which the alternate + * chain code bypasses. The leaf is issued by attackerCA. + * + * Cases: + * 0. No callback: the forged chain is refused. + * 1. A verify callback accepts every certificate and then checks + * wolfSSL_get_verify_result(). That must report the forged CA's + * WOLFSSL_X509_V_ERR_INVALID_CA. Before the fix, the intermediate's + * bypass reset peerVerifyRet to 0, so the leaf was reported as verified. + * 2. A certificate manager callback rejects the forged CA but the verify + * callback overrides the error, so the chain carries on with + * args->verifyErr set. The handshake must still fail at the leaf. Before + * the fix, the intermediate's bypass cleared args->verifyErr and the + * handshake completed. + * + * @return TEST_SUCCESS on success. + */ +int test_wolfSSL_alt_chain_forged_ca(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(WOLFSSL_ALT_CERT_CHAINS) && defined(OPENSSL_EXTRA) && \ + defined(WOLFSSL_PEM_TO_DER) && !defined(NO_RSA) && !defined(NO_TLS) && \ + !defined(NO_SHA256) && !defined(NO_FILESYSTEM) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) + const char* rootFile = + "./certs/test/alt-chain-forged-ca/realRoot-cert.pem"; + const char* chainFile = + "./certs/test/alt-chain-forged-ca/server-chain.pem"; + const char* keyFile = + "./certs/test/alt-chain-forged-ca/leaf-key.pem"; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + int mode; +#ifndef NO_WOLFSSL_CM_VERIFY + const int lastMode = 2; +#else + const int lastMode = 1; +#endif + + for (mode = 0; mode <= lastMode && EXPECT_SUCCESS(); mode++) { + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, + &ssl_s, wolfSSLv23_client_method, wolfSSLv23_server_method), 0); + + ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx_c, rootFile, NULL), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_use_certificate_chain_file(ssl_s, chainFile), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_use_PrivateKey_file(ssl_s, keyFile, + WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS); + + if (mode == 0) { + /* Without a callback the forged chain must be refused. */ + wolfSSL_set_verify(ssl_c, WOLFSSL_VERIFY_PEER, NULL); + ExpectIntNE(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntNE(wolfSSL_get_verify_result(ssl_c), WOLFSSL_X509_V_OK); + } + else if (mode == 1) { + test_alt_chain_forged_ca_cb_calls = 0; + wolfSSL_set_verify(ssl_c, WOLFSSL_VERIFY_PEER, + test_alt_chain_forged_ca_cb); + /* The callback waives every error, so the handshake completes, + * but the forged CA's error must still be reported. */ + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntGT(test_alt_chain_forged_ca_cb_calls, 0); + ExpectIntEQ(wolfSSL_get_verify_result(ssl_c), + WOLFSSL_X509_V_ERR_INVALID_CA); + } +#ifndef NO_WOLFSSL_CM_VERIFY + else { + test_alt_chain_forged_ca_cb_calls = 0; + test_alt_chain_forged_ca_cm_calls = 0; + if (ctx_c != NULL) { + wolfSSL_CertManagerSetVerify( + wolfSSL_CTX_GetCertManager(ctx_c), + test_alt_chain_forged_ca_cm_cb); + } + wolfSSL_set_verify(ssl_c, WOLFSSL_VERIFY_PEER, + test_alt_chain_forged_ca_cb); + /* The certificate manager's rejection of the forged CA must + * survive the intermediate's bypass and fail the leaf. */ + ExpectIntNE(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntGT(test_alt_chain_forged_ca_cm_calls, 0); + ExpectIntGT(test_alt_chain_forged_ca_cb_calls, 0); + } +#endif + + 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 + return EXPECT_RESULT(); +} + #if defined(WOLFSSL_SMALL_CERT_VERIFY) && \ defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && !defined(NO_RSA) && \ !defined(NO_TLS) && !defined(NO_SHA256) && !defined(NO_ASN_TIME) && \ diff --git a/tests/api/test_ssl_cert.h b/tests/api/test_ssl_cert.h index 1f802590194..e6bdb22e5b3 100644 --- a/tests/api/test_ssl_cert.h +++ b/tests/api/test_ssl_cert.h @@ -73,6 +73,7 @@ int test_wolfSSL_verify_tls13_failnocert_only(void); int test_wolfSSL_verify_empty_server_cert(void); int test_wolfSSL_verify_post_handshake_defers(void); int test_wolfSSL_chain_ca_ext_key_usage(void); +int test_wolfSSL_alt_chain_forged_ca(void); int test_wolfSSL_small_cert_verify_sig_error(void); int test_wolfSSL_session_chain_oversized_cert(void); int test_wolfSSL_session_ticket_oversized_peer_cert(void); @@ -130,6 +131,7 @@ int test_wolfSSL_SESSION_get0_peer_full_chain(void); TEST_DECL_GROUP("ssl_cert", \ test_wolfSSL_verify_post_handshake_defers), \ TEST_DECL_GROUP("ssl_cert", test_wolfSSL_chain_ca_ext_key_usage), \ + TEST_DECL_GROUP("ssl_cert", test_wolfSSL_alt_chain_forged_ca), \ TEST_DECL_GROUP("ssl_cert", \ test_wolfSSL_small_cert_verify_sig_error), \ TEST_DECL_GROUP("ssl_cert", \