diff --git a/certs/intermediate/include.am b/certs/intermediate/include.am index e58cbb2de9a..23634bd3fda 100644 --- a/certs/intermediate/include.am +++ b/certs/intermediate/include.am @@ -64,4 +64,10 @@ EXTRA_DIST += \ certs/intermediate/untrusted_anchor/leaf-cert.pem \ certs/intermediate/untrusted_anchor/leaf-key.pem \ certs/intermediate/untrusted_anchor/leaf-deep-cert.pem \ - certs/intermediate/untrusted_anchor/leaf-deep-key.pem + certs/intermediate/untrusted_anchor/leaf-deep-key.pem \ + certs/intermediate/untrusted_anchor/loop-a-cert.pem \ + certs/intermediate/untrusted_anchor/loop-a-key.pem \ + certs/intermediate/untrusted_anchor/loop-b-cert.pem \ + certs/intermediate/untrusted_anchor/loop-b-key.pem \ + certs/intermediate/untrusted_anchor/loop-leaf-cert.pem \ + certs/intermediate/untrusted_anchor/loop-leaf-key.pem diff --git a/certs/intermediate/untrusted_anchor/gen_certs.sh b/certs/intermediate/untrusted_anchor/gen_certs.sh index 431d831c90e..730b126692b 100755 --- a/certs/intermediate/untrusted_anchor/gen_certs.sh +++ b/certs/intermediate/untrusted_anchor/gen_certs.sh @@ -19,6 +19,8 @@ # trust anchor) # int-ca-tampered int-ca with the final byte of its signatureValue # flipped (valid TBSCertificate, broken outer signature) +# loop-a, loop-b two CAs that issued each other (an issuer cycle that +# never reaches a root); loop-leaf is issued by loop-a # # The certificates intentionally omit subjectKeyIdentifier / # authorityKeyIdentifier; the test relies on this, so the script aborts at the @@ -42,7 +44,7 @@ RSA_BITS=2048 CA_EXT=$(mktemp) LEAF_EXT=$(mktemp) -trap 'rm -f "$CA_EXT" "$LEAF_EXT" *.csr *.srl' EXIT +trap 'rm -f "$CA_EXT" "$LEAF_EXT" *.csr *.srl loop-*-seed.pem' EXIT # No pathlen so the first intermediate can still issue the second one in the # two-intermediate positive control; no key identifiers (see header). @@ -107,6 +109,27 @@ genkey leaf-deep-key.pem signcert leaf-deep.csr leaf-deep-cert.pem int-ca2-cert.pem int-ca2-key.pem \ "$LEAF_EXT" +# Issuer cycle: loop-a is signed by loop-b's key and loop-b by loop-a's. Each +# is signed through a throwaway self-signed seed carrying the other's name and +# key, since neither final cert exists yet. ----------------------------------- +genkey loop-a-key.pem +genkey loop-b-key.pem +genroot loop-a-key.pem loop-a-seed.pem "wolfSSL Untrusted-Anchor Test Loop A" +genroot loop-b-key.pem loop-b-seed.pem "wolfSSL Untrusted-Anchor Test Loop B" +"$OPENSSL" req -new -key loop-a-key.pem -sha256 \ + -subj "/CN=wolfSSL Untrusted-Anchor Test Loop A" -out loop-a.csr +"$OPENSSL" req -new -key loop-b-key.pem -sha256 \ + -subj "/CN=wolfSSL Untrusted-Anchor Test Loop B" -out loop-b.csr +signcert loop-a.csr loop-a-cert.pem loop-b-seed.pem loop-b-key.pem "$CA_EXT" +signcert loop-b.csr loop-b-cert.pem loop-a-seed.pem loop-a-key.pem "$CA_EXT" + +# Leaf issued by loop-a ------------------------------------------------------- +genkey loop-leaf-key.pem +"$OPENSSL" req -new -key loop-leaf-key.pem -sha256 \ + -subj "/CN=www.example.test" -out loop-leaf.csr +signcert loop-leaf.csr loop-leaf-cert.pem loop-a-cert.pem loop-a-key.pem \ + "$LEAF_EXT" + # Tampered intermediate: flip the final byte of the DER (last byte of the # signatureValue) so the TBSCertificate stays valid but the outer signature no # longer verifies. @@ -121,7 +144,8 @@ rm -f int-ca.der int-ca-tampered.der # Guard: these test certificates must not carry key identifiers (see header). for c in root-ca-cert.pem alt-ca-cert.pem int-ca-cert.pem int-ca2-cert.pem \ - leaf-cert.pem leaf-deep-cert.pem; do + leaf-cert.pem leaf-deep-cert.pem loop-a-cert.pem loop-b-cert.pem \ + loop-leaf-cert.pem; do if "$OPENSSL" x509 -in "$c" -noout -text \ | grep -q "Key Identifier"; then echo "ERROR: $c carries a subject/authority key identifier." >&2 diff --git a/certs/intermediate/untrusted_anchor/loop-a-cert.pem b/certs/intermediate/untrusted_anchor/loop-a-cert.pem new file mode 100644 index 00000000000..9cabda2d29d --- /dev/null +++ b/certs/intermediate/untrusted_anchor/loop-a-cert.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDETCCAfmgAwIBAgIUDVfT0weC64i2DklltrgmNk+YkxYwDQYJKoZIhvcNAQEL +BQAwLzEtMCsGA1UEAwwkd29sZlNTTCBVbnRydXN0ZWQtQW5jaG9yIFRlc3QgTG9v +cCBCMCAXDTI2MDkxMTE5MzczNloYDzIwNTYwOTEwMTkzNzM2WjAvMS0wKwYDVQQD +DCR3b2xmU1NMIFVudHJ1c3RlZC1BbmNob3IgVGVzdCBMb29wIEEwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCXgVGA2j4kzXRqPU31wxIUTVvx7NYislW5 +sKTrICcKdkuZ/YpJDgsETo2J2VW5W1Mbl4o8D5irLRpyNrKesk0/yTQRXylgyETK +DyOP587IlsYnDPh+cyOG15SOZRNTrIhUugOOkO94C75b9J1iwKpjU4Q6b0zZTs2Y +g1LpluRaFWC17/Tcuzx2RsDdudhTw0YStbsY+EDsxOkAPGS52oDAtrVdPN1fT4py +NJi0tdh0QR6yibPctHG/PiW2LnYo7Hz9UzNgo91fb8spqhBq5CSavwENngVCaaJ+ +8l3kuK1RIthWJVw+1DfbtO99T/BYl4qQfsSISidUo5URzmEy4QwpAgMBAAGjIzAh +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUA +A4IBAQBoBBA0QkL6M17lIYz4+wADI28vSVSg1obcZJOQR6eQV/cwN7Jj86uXOvd0 +UfanAImJ2RU8nOCCW2oPFQw2gsmvwqkzuDn9Z02743fOvvQNjKkufKievmySwhCH +E9HTsB5UZq7u9TmGe6XXAf2Mfx77WkcytT0Ji1FOub66+qaUC1n5qYgRB1pX2Wc7 +dN6hPx9eMT+tucbpk4XZ8NHR/d5nuiFzYUj5KZ80U5WPOid38NLRKEnGMj8injWr +xWSvurINodjGJfCM+YSjzYaXNqrD/pruyE+1IzSdfzwIXqfEW1C8Pr25aMZVf0Ci +h72ck9bFykMMveb0aiug/1p9IVWX +-----END CERTIFICATE----- diff --git a/certs/intermediate/untrusted_anchor/loop-a-key.pem b/certs/intermediate/untrusted_anchor/loop-a-key.pem new file mode 100644 index 00000000000..33156d18a57 --- /dev/null +++ b/certs/intermediate/untrusted_anchor/loop-a-key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCXgVGA2j4kzXRq +PU31wxIUTVvx7NYislW5sKTrICcKdkuZ/YpJDgsETo2J2VW5W1Mbl4o8D5irLRpy +NrKesk0/yTQRXylgyETKDyOP587IlsYnDPh+cyOG15SOZRNTrIhUugOOkO94C75b +9J1iwKpjU4Q6b0zZTs2Yg1LpluRaFWC17/Tcuzx2RsDdudhTw0YStbsY+EDsxOkA +PGS52oDAtrVdPN1fT4pyNJi0tdh0QR6yibPctHG/PiW2LnYo7Hz9UzNgo91fb8sp +qhBq5CSavwENngVCaaJ+8l3kuK1RIthWJVw+1DfbtO99T/BYl4qQfsSISidUo5UR +zmEy4QwpAgMBAAECggEAOb/0Zk5KQ9MA3gIyNky+FnkKZCQn15/sp1Ra+LkPyKAg +3zjSvlgZAPJEZ1gtofLasfjIm25CfnU50HFjErTaV0cFjt2cMyUDe5Xbs13j53f5 +XidGjUSsNRj1qnWCsXFhohIpiu1aKoMnc3vBhkP0btoF3BRS0b6FBe6A60M2FdPc +HZ6knFCxPDyj1Uis1yduMflzT2d7/qpkE4QY9qp41b7k8T22D4lh6TI9yObT6foZ +GoUZ7+KQryEctG8C0XAsuLR8vfk6QZqSwjKnkPAoMKVY4J77etX/+3VGw8WNIKRH +z80jGO3r8nB3sj3ULQsvMyyCyTHMOfvlkbtmpgyDZQKBgQDGeYj+6KSDjDC4tpmS +YpB76oBu5Daaz4CA60e1KmneuDFAW+zV/NvNCGDL3ebsoJVKAajLus32EsuZNb3r +63gb8CBE66mgwxujSdMA32ei8wI8JXXBQKnmu9G3o8qcvpVjRL+3w43pLCLl5OiT +Cb2bYSEhNNTDuOyK3snudIL6pwKBgQDDarmDu2e3F0zy83D2iKhuQtCpVud4KWWK +Uhei1cF+PEvBRI1JevM1IeLlNPVsjSK0T9RzdiRqNurrylGergbRgHQk0RW9TdpY +yhcM4nZQw9oP7mZOArJ0RNoE9pQ5MVZ+ZyFsjGYD6gzi9tJOXjgjxeNkfVXU83cu +IcAKUwUsrwKBgC/a+hdMjO5TDSRckubi8SePwWv7htcJFVkAYgfitChJjUaRY6et +Emp7jKtbU+TWS2jOwZ5t6l9c5M6KUMLjwdAqs/0qPXpP0QgsMr5RH7IAmhdaiOwR +5WWG/+ghjCcDJQ4Mc5Whh0Qjqj54VUOu45LldpGFJZATtKU/Vr0yRqqdAoGAFwjE +d0em0QkZE+YoIZc4K08K43sP67UNeAF3McV6elPHJi/ba5bnhhLJePUGHNaCCbkD +NNzvK+nvRP3jIw4mVZmLly9iHZ9IpoCjuOwUwC5lzMzwGC/vMw9Ol5ktHZS4OkwT +v4TJHZsAC9zGK2Clw2m3BQhVDrXoDXMC/PSOPpcCgYB4M0NMbNVmfpUagrt3RgWi +BqLzRaJHk309LjnWBr2Y9tURB1H6iRqZn8mGvKMXKyk4zF2bfoj+9UdTuJYsds4F +wIqeVWDvm4UGBF3UbiAq/EITZVnL6s5W+GkKTet0iNR1mG3nLbxCAnVbawvKdQSr +0Mn+tXyYMBIsQIwIO+JNwA== +-----END PRIVATE KEY----- diff --git a/certs/intermediate/untrusted_anchor/loop-b-cert.pem b/certs/intermediate/untrusted_anchor/loop-b-cert.pem new file mode 100644 index 00000000000..01a93bde82f --- /dev/null +++ b/certs/intermediate/untrusted_anchor/loop-b-cert.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDETCCAfmgAwIBAgIUDVfT0weC64i2DklltrgmNk+YkxcwDQYJKoZIhvcNAQEL +BQAwLzEtMCsGA1UEAwwkd29sZlNTTCBVbnRydXN0ZWQtQW5jaG9yIFRlc3QgTG9v +cCBBMCAXDTI2MDkxMTE5MzczNloYDzIwNTYwOTEwMTkzNzM2WjAvMS0wKwYDVQQD +DCR3b2xmU1NMIFVudHJ1c3RlZC1BbmNob3IgVGVzdCBMb29wIEIwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDYxM0fHDnG6cBxj5oOa8Ife+ltuSS5maX5 +RgS6cgjaaPhrCNxyuTB3ohOTwF5SwjfZaFKKFv1iQXx1yzk1xcyxdpooTLKD+9tY +lnDHVQxMMnDSoHU4UqiEuk7MqbydldAkzKAMKoRgSg46IwRfPzNy23wcuhiqKQkC +eXoGbwHZ0QS4xPAecljkIGsgpdE/PyjZKoXoi3XmBzxDgkr/2ofdI8bUbE01DaiO +aUjK5pg9XbjLL/5FCq5nloONWQdgQ4pg5r8eit+/UZWl19WA4aqnWnCiipt+CF7a +hJpKFCizC6OgNLrYmDH7NRJxZwhnZ5+CySCwadpcOIo7/W8+IdQBAgMBAAGjIzAh +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUA +A4IBAQBvUyykt2mcG2S0ivkX8THJ8aqkJ2PbbXaHMYM3wEl7GT2PyL7EfJR3vDSZ +UIUIvisRDuygrk2a+AIivbf2JrNX6X8e9CwQ6lRs9acs4Uy2PMJH8fAOW5rXEewe +b4RALcn1UzhsO3XwC2eamEz89RcnNfKH752CExgo/cbqLEW87zwTq38K3bFjMtfr +8LDyptakhZA8cps7boE/VeloDYtEITtwXROZ0+sbvmF1GNTlAI4JzQNw8CxoAHaY +f40YZDGB+uow8SDHOww4yJrC42XnA+s95VvjewP5gjmn4sJTiV6KDjoXH/SxDG9/ +Nwetk5Az5eLlHPPU3eAy5/IEOAj5 +-----END CERTIFICATE----- diff --git a/certs/intermediate/untrusted_anchor/loop-b-key.pem b/certs/intermediate/untrusted_anchor/loop-b-key.pem new file mode 100644 index 00000000000..bfb9e415e9f --- /dev/null +++ b/certs/intermediate/untrusted_anchor/loop-b-key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDYxM0fHDnG6cBx +j5oOa8Ife+ltuSS5maX5RgS6cgjaaPhrCNxyuTB3ohOTwF5SwjfZaFKKFv1iQXx1 +yzk1xcyxdpooTLKD+9tYlnDHVQxMMnDSoHU4UqiEuk7MqbydldAkzKAMKoRgSg46 +IwRfPzNy23wcuhiqKQkCeXoGbwHZ0QS4xPAecljkIGsgpdE/PyjZKoXoi3XmBzxD +gkr/2ofdI8bUbE01DaiOaUjK5pg9XbjLL/5FCq5nloONWQdgQ4pg5r8eit+/UZWl +19WA4aqnWnCiipt+CF7ahJpKFCizC6OgNLrYmDH7NRJxZwhnZ5+CySCwadpcOIo7 +/W8+IdQBAgMBAAECggEAVaoks/w4GxNqkZZiF/ve6glSnSGTUWPWszXIDZSeSg48 +yjPkxtPHwyeCnBvkSsllP95Z7CecuDVQBc8ry0PhmmpYDsAMtlsUeqRtamECbKRs +cNinqnwEC/wCwx/7Ib3AVaKmR1iQGut5RuFqy2dTIjt6B3JF6A0yec9e+WbqMNUO +qJT8IfuUAbLweUuM6hy+sTHaoHENhPiX110yBTe0PJVCUi7mA05cjj0onZD+ysqC +zj5w4hOeo3bQA3Lb8RnE4Fb6AGkhzokywzJ/Vi/p4Uwj/zWxF7U9fQ5tP+Q9iQwX +4bYaxWVVF3CVMwGog8b1ZxxcF8XD3GeBxcEvbYBEBwKBgQD2q55WmNkZ5vuXyLDv +29CfW5MHEzq5kC3pft+/oHwkOqCnYwkNfmiXiPgCaAZmDfbuE5WGrahJPysezhX5 +pwQQLp83e4Mo+cT0XXpmzCyHgzfWbDg6VGUHkD7xwzTL/JRfuk4+c31bNABaKtZ9 +46+OIcditvSFFina0dRUn8tQswKBgQDg96knt/ZY0aIoVPnnkISJYekA7ui+IlIV +GFXZDflFy8jNtvOfc4vJ4e0EyVS9LmAg/EH55Y4HDE3OD/lTxkS9sNDFU4TeGZJA +UrTVplGDGzgh75KXHg9nnbyKGD3tMxJhlnCqHXO77fX2cABStoejPye8TXyXgxRr +3MUoDfC6ewKBgFW0zaBDHXPdiN91MN02YCdulwhnSwk8qmct2Spo5Zlk8XsXcwbI +PCkiwyF66XY+NeUE0vhnkEha9W2Pp50Fxeilela5yJnNJvzNGgFrwbEYC6eqFGNX +/X6CRQyViduymA8m2ZbjY9Pznoe1yK8XFGFrJS8MaKtxRvur2n3Iw8j3AoGBAI4W +vocwoO6OUV4p9cF1PNDMv27wdp4IbVGXkjlKoyObR5PutscCIst638oDI3ttfBe5 +XC5q956p/cZlvnNUKfypXeMUEjp5ai8oTcEXkCo/md+NQkgiG1a1gOyzzQExHa33 +Z+d6PKv46z4glYOod9j6va0oP8Yua0qJIRlgXKhbAoGACqdIfpQYRP00ye2sThaT +OQxwfRmb+0qt4RvJL+9Ssrqwa0S97VPpZNM5Yb1UM4PF5C2h8ttCEXCSI6TGat7k +TcofugZJPpAqpVEynw7W4pgNPI06ukAFhFtgQE+UxVgP4M8XwGnD/Vbl6/9BEVlo +EFaRPrTOloKpCpyKM9ndwPk= +-----END PRIVATE KEY----- diff --git a/certs/intermediate/untrusted_anchor/loop-leaf-cert.pem b/certs/intermediate/untrusted_anchor/loop-leaf-cert.pem new file mode 100644 index 00000000000..a8c2bd75167 --- /dev/null +++ b/certs/intermediate/untrusted_anchor/loop-leaf-cert.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDLDCCAhSgAwIBAgIUDVfT0weC64i2DklltrgmNk+YkxgwDQYJKoZIhvcNAQEL +BQAwLzEtMCsGA1UEAwwkd29sZlNTTCBVbnRydXN0ZWQtQW5jaG9yIFRlc3QgTG9v +cCBBMCAXDTI2MDkxMTE5MzczNloYDzIwNTYwOTEwMTkzNzM2WjAbMRkwFwYDVQQD +DBB3d3cuZXhhbXBsZS50ZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEAx2HjDeSayaotSJY7H6sVkNaIG5sIxTe0PYsyXjXhixjNYNnP4x4DFS5IYznS +g36R1ZvczKHIwi1iczmhi3RXa3jKYEQPFNhYobWjOhenbvs+Zl7DA+YFem8IWDAo +PfeFL7+r/P6EQiZSrZq9B0yaQKhbE96YDE3ulznNi3T/v4GZNl+9/4L49sVwShlu +AYodLT9g4Tnlmq+DY3lLVtpH6xvjeKoigEuqD3WzVnMC+lCI6EGUStI8OL9aU1Hv +mW26g6/s5O97ACweGDHOv8dqdgs960lo1hM7kL3f8vfAtCVWPScVp27VVsUP03ef +FFE4Sdb8Z4MJDQgVNGEgxPdgXwIDAQABo1IwUDAMBgNVHRMBAf8EAjAAMA4GA1Ud +DwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATAbBgNVHREEFDASghB3d3cu +ZXhhbXBsZS50ZXN0MA0GCSqGSIb3DQEBCwUAA4IBAQAGBfC2KnFbifzE7l9SEVbl +UpaPxJaFwE+QKt035a1h+UU5Oxd6evmjw/EnmsyrruUo2N2wjymlz/7o496FcO9a +Yd20VQvoHgLsD5yKFah+owzGIV936zTMLihey8IjCRM7ukV8CzMGPTpjKefrdExF +BdiPqqnLdN7Ki/HdiecY11qyvtaeVNOJMIiQV45wu64JrxfrEFUOafIYrZGmwrSc +2dHKfCVWgOU/bMgEA887q/yBD5kUXjAn6IcV0pxczyQrL+8ozGarVzJ96J8YJfPH +zBc94el/U+vPNz7NcXu4lgcwYUopwK3HrKhSKAFuCf7+LovJB3JwE5D8EBF099cH +-----END CERTIFICATE----- diff --git a/certs/intermediate/untrusted_anchor/loop-leaf-key.pem b/certs/intermediate/untrusted_anchor/loop-leaf-key.pem new file mode 100644 index 00000000000..956d9b9c476 --- /dev/null +++ b/certs/intermediate/untrusted_anchor/loop-leaf-key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDHYeMN5JrJqi1I +ljsfqxWQ1ogbmwjFN7Q9izJeNeGLGM1g2c/jHgMVLkhjOdKDfpHVm9zMocjCLWJz +OaGLdFdreMpgRA8U2FihtaM6F6du+z5mXsMD5gV6bwhYMCg994Uvv6v8/oRCJlKt +mr0HTJpAqFsT3pgMTe6XOc2LdP+/gZk2X73/gvj2xXBKGW4Bih0tP2DhOeWar4Nj +eUtW2kfrG+N4qiKAS6oPdbNWcwL6UIjoQZRK0jw4v1pTUe+ZbbqDr+zk73sALB4Y +Mc6/x2p2Cz3rSWjWEzuQvd/y98C0JVY9JxWnbtVWxQ/Td58UUThJ1vxngwkNCBU0 +YSDE92BfAgMBAAECggEAM7peuR0FV6rSItHvNvEhIDm4iEj5xokENy9k+vJJuNfP +eQApokgIDre6ShwXVMxCs8PDTkfvGQnDUBe0pMSjZZuqDb26l/LlU9cXKRw38WA3 +uUF+oQVNiI1EAfVEi4crQ88bpQC3eZ4Nt0K9Jiamon3LW2VV12NkslFp1XOeYqWc +bNbIpeMmZNYQfMejZR29W9Px4gv1Os9Quq+vhqb+3zoTRwP08otDcd4IdqgZZqHH +CiRYPd39ujfDd6dUxqn4TXuIN9Mdf8x/7yeCG2A2jeuc3oUVxMLWTjNvfjc1eKlk +/YtY2ZuLlLRYKorNfTpZHV7hJbnWf7fGchsxmCs1wQKBgQD0f3YAM3TZ8XgzBs4e +2pdygdz+Cs8FiDYiCN9Y8SKpUlNlHB5Cmp5lBnMdxmtEgfEByY3PxUxZnP0bi0Pi +3A5Q/A04gC33Pfp/znWldoXpRNQySWqCxDJSCSYcu6bNbmeNmnEVn0BAcziAUzqp +MYpOg3dQpKHyD4dxjlfNPbWLYQKBgQDQwxcg3yJlyO9E1qWg5VDpP9k+arVDfdTa +exI+EIWI6Z7bD5sY0SbwT+rdpCMGYev2LOjR34pRW8M+Yjup2dUqwPyrdbv4GU8O +oJZdyevKe9DY68/ttKRD1gJdZX96gkTnK6BrL/guStm9i113XlC3lPWfYD8ct+Dv +n7ArDUpDvwKBgE4H7+0Yfw6fljES1u3Z7cPJ1nHtmSy20DAEjOOh8eIAOEVot++h +0TOE33B9RvbSUcy0OuI4oaloBQ+pXr0kZh1KlB2YKxNhYHUxuzJXpV1RbjeuXqPl +Yfn0mYlKCm498iarR7QS4zquizXJWkttWCq+onHGWTkb46pGN5d1Pl+BAoGAUxGu +PFhNua7Q9tVqprFZFtdfPKT9Z9PERQO/6I5udGJx4b09/AwNsIY8cOGFRl7ko9qO +8iQ/R8x5znMMRrP1TMPqRtfcaga4HJvt+XgFqVbHbem3nWDKFGwuGLfwl/EKNep4 +NO6rlNAYAIJaOMuA1uwhKMAFnQnWKcOjOB5btMUCgYBSx34LNg8e16gcjCc7V/91 +Ej/UoQinArqNZXQyLEAwqbnjHTGGCgM4gCyWkJTcZG2Ef4lnkIylRBZLpMGnxYcn +0TEEoCOhlYqJxPHjsX/a0SwqSWvwsfDy+C/kSC+l9yZSWKoxTJDlwqZqJajedOsp +7CS8Oc4f5XuI9rconiegCw== +-----END PRIVATE KEY----- diff --git a/src/x509_str.c b/src/x509_str.c index fa0d61637e9..5a1990e77f5 100644 --- a/src/x509_str.c +++ b/src/x509_str.c @@ -221,6 +221,9 @@ int wolfSSL_X509_STORE_CTX_init(WOLFSSL_X509_STORE_CTX* ctx, #ifdef HAVE_EX_DATA XMEMSET(&ctx->ex_data, 0, sizeof(ctx->ex_data)); #endif + ctx->depth = 0; + ctx->depthSet = 0; + ctx->userCtx = NULL; ctx->verify_cb = NULL; ctx->error = 0; @@ -558,16 +561,14 @@ static WOLFSSL_X509_STORE_CTX_verify_cb X509StoreGetVerifyCb( * A caller must stop chain building when it is set: a veto must not be turned * into a retry with another issuer, and must not be cleared by the * partial-chain fallback in wolfSSL_X509_verify_cert(). */ -static int X509StoreVerifyCert(WOLFSSL_X509_STORE_CTX* ctx, int* cbRejected) +static int X509StoreVerifyCert(WOLFSSL_X509_STORE_CTX* ctx, int* cbRejected, + WOLFSSL_X509_STORE_CTX_verify_cb verifyCb) { int ret = WC_NO_ERR_TRACE(WOLFSSL_FAILURE); - WOLFSSL_X509_STORE_CTX_verify_cb verifyCb; WOLFSSL_ENTER("X509StoreVerifyCert"); *cbRejected = 0; - verifyCb = X509StoreGetVerifyCb(ctx); - if (ctx->current_cert != NULL && ctx->current_cert->derCert != NULL) { ret = wolfSSL_CertManagerVerifyBuffer(ctx->store->cm, ctx->current_cert->derCert->buffer, @@ -850,20 +851,18 @@ static int X509StoreCertIsTrusted(WOLFSSL_X509_STORE* store, * * Returns WOLFSSL_SUCCESS if the path satisfies every pathLenConstraint, or * WOLFSSL_FAILURE (with ctx->error set) on the first violation. */ -static int X509StoreCheckPathLen(WOLFSSL_X509_STORE_CTX* ctx) +static int X509StoreCheckPathLen(WOLFSSL_X509_STORE_CTX* ctx, + WOLFSSL_X509_STORE_CTX_verify_cb verifyCb) { int num; int i; word32 maxPathLen = 0; byte haveConstraint = 0; WOLFSSL_X509* anchor; - WOLFSSL_X509_STORE_CTX_verify_cb verifyCb; if (ctx == NULL || ctx->chain == NULL) return WOLFSSL_SUCCESS; - verifyCb = X509StoreGetVerifyCb(ctx); - num = wolfSSL_sk_X509_num(ctx->chain); /* A pathLen violation requires at least one intermediate between the leaf * (index 0) and the trust anchor, i.e. a chain of three or more. */ @@ -928,6 +927,21 @@ static int X509StoreCheckPathLen(WOLFSSL_X509_STORE_CTX* ctx) return WOLFSSL_SUCCESS; } +/* Returns 1 if X509_V_FLAG_PARTIAL_CHAIN is set on ctx or on its store, + * otherwise 0. ctx and ctx->store must be non-NULL. */ +static int X509StoreCertPartialChainEnabled(WOLFSSL_X509_STORE_CTX* ctx) +{ + /* PARTIAL_CHAIN lets any trusted cert end the path */ + if (ctx->flags & WOLFSSL_PARTIAL_CHAIN) { + return 1; + } + if (ctx->store->param != NULL && + (ctx->store->param->flags & WOLFSSL_PARTIAL_CHAIN)) { + return 1; + } + return 0; +} + /* Verifies certificate chain using WOLFSSL_X509_STORE_CTX * returns 1 on success or <= 0 on failure. */ @@ -944,9 +958,7 @@ int wolfSSL_X509_verify_cert(WOLFSSL_X509_STORE_CTX* ctx) WOLF_STACK_OF(WOLFSSL_X509)* certsToUse = NULL; WOLF_STACK_OF(WOLFSSL_X509)* failedCerts = NULL; WOLF_STACK_OF(WOLFSSL_X509)* origTrustedSk = NULL; -#ifndef WOLFSSL_X509_STORE_ALLOW_NON_CA_INTERMEDIATE WOLFSSL_X509_STORE_CTX_verify_cb verifyCb; -#endif WOLFSSL_ENTER("wolfSSL_X509_verify_cert"); if (ctx == NULL || ctx->store == NULL || ctx->store->cm == NULL @@ -954,9 +966,7 @@ int wolfSSL_X509_verify_cert(WOLFSSL_X509_STORE_CTX* ctx) return WOLFSSL_FATAL_ERROR; } -#ifndef WOLFSSL_X509_STORE_ALLOW_NON_CA_INTERMEDIATE verifyCb = X509StoreGetVerifyCb(ctx); -#endif /* Chain building mutates the working stack: caller-supplied intermediates * are appended and X509VerifyCertSetupRetry moves failed certs out of it. @@ -1020,9 +1030,17 @@ int wolfSSL_X509_verify_cert(WOLFSSL_X509_STORE_CTX* ctx) ret = WOLFSSL_FAILURE; goto exit; } - - if (ctx->depth > 0) { - depth = ctx->depth + 1; + /* An explicit negative depth leaves no room for any chain, + * not even a trusted leaf: reject before building one. */ + if (ctx->depthSet && ctx->depth < 0) { + SetupStoreCtxError_ex(ctx, WOLFSSL_X509_V_ERR_CERT_CHAIN_TOO_LONG, 0); + ret = WOLFSSL_FAILURE; + goto exit; + } + /* The struct is public, so also honor a positive depth written directly + * (no setter). Clamp so the + 1 can't overflow. */ + if (ctx->depthSet || ctx->depth > 0) { + depth = (ctx->depth < INT_MAX) ? ctx->depth + 1 : INT_MAX; } else { depth = WOLFSSL_X509_STORE_DEFAULT_MAX_DEPTH + 1; @@ -1033,9 +1051,12 @@ int wolfSSL_X509_verify_cert(WOLFSSL_X509_STORE_CTX* ctx) while(done == 0 && depth > 0) { issuer = NULL; - /* Try to find an untrusted issuer first */ - ret = X509StoreGetIssuerEx(&issuer, certsToUse, - ctx->current_cert); + /* Try to find an untrusted issuer first. Skip certs already on the + * path so an issuer cycle (A <- B <- A) ends the search instead of + * running until the depth budget is spent. current_cert is not on + * ctx->chain yet, so a self-issued terminus is still found. */ + ret = X509StoreGetIssuerSkip(&issuer, certsToUse, + ctx->current_cert, ctx->chain); if (ret == WOLFSSL_SUCCESS) { if (ctx->current_cert == issuer) { X509StoreChainPush(ctx->chain, ctx->current_cert); @@ -1080,7 +1101,7 @@ int wolfSSL_X509_verify_cert(WOLFSSL_X509_STORE_CTX* ctx) &depth, origDepth); continue; } - ret = X509StoreVerifyCert(ctx, &cbRejected); + ret = X509StoreVerifyCert(ctx, &cbRejected, verifyCb); if (cbRejected) { /* The application vetoed this certificate. Stop instead of * looking for another issuer: the decision is the @@ -1114,7 +1135,7 @@ int wolfSSL_X509_verify_cert(WOLFSSL_X509_STORE_CTX* ctx) ret = WOLFSSL_FATAL_ERROR; goto exit; } - ret = X509StoreVerifyCert(ctx, &cbRejected); + ret = X509StoreVerifyCert(ctx, &cbRejected, verifyCb); if (cbRejected) { /* An application veto is final. The partial-chain fallback * below must not accept the chain here and clear ctx->error: @@ -1126,9 +1147,7 @@ int wolfSSL_X509_verify_cert(WOLFSSL_X509_STORE_CTX* ctx) /* WOLFSSL_PARTIAL_CHAIN may only terminate the chain at a * certificate the caller actually trusts, so verify that * ctx->current_cert is itself in the original trust set. */ - if (((ctx->flags & WOLFSSL_PARTIAL_CHAIN) || - (ctx->store->param != NULL && - (ctx->store->param->flags & WOLFSSL_PARTIAL_CHAIN))) && + if (X509StoreCertPartialChainEnabled(ctx) && X509StoreCertIsTrusted(ctx->store, ctx->current_cert, origTrustedSk)) { X509StoreChainPush(ctx->chain, ctx->current_cert); @@ -1190,22 +1209,64 @@ int wolfSSL_X509_verify_cert(WOLFSSL_X509_STORE_CTX* ctx) } /* Success requires the path to have reached a configured trust anchor - * (done == 1) or to have terminated at a caller-trusted self-signed - * certificate via the break above (done == 0 with depth still > 0). A - * loop that instead ran out of its depth budget (depth <= 0) without - * completing must fail closed: ret may still be WOLFSSL_SUCCESS from the - * last link, but no trust anchor was reached. */ + * (done == 1), to have terminated at a caller-trusted self-signed + * certificate via the break above (done == 0 with depth still > 0), or + * to have spent its depth budget exactly on a trust anchor (below). A + * loop that ran out of its depth budget (depth <= 0) without completing + * must fail with CERT_CHAIN_TOO_LONG unless the cert it stopped on is + * caller-trusted and either self-issued or accepted by + * X509_V_FLAG_PARTIAL_CHAIN: the trust anchor never counts against depth. + * ret may still be WOLFSSL_SUCCESS from the last link, so it cannot be + * relied on here. */ if (ret == WOLFSSL_SUCCESS && done == 0 && depth <= 0) { - SetupStoreCtxError_ex(ctx, WOLFSSL_X509_V_ERR_CERT_CHAIN_TOO_LONG, - wolfSSL_sk_X509_num(ctx->chain)); - ret = WOLFSSL_FAILURE; + int anchorEndsPath = 0; + + /* A trusted anchor from certsToUse is not an intermediate, so accept + * it here if it ends the path instead of counting it against depth. */ + if (X509StoreCertIsTrusted(ctx->store, ctx->current_cert, + origTrustedSk)) { + /* self-issued anchor: ends the path without a verify */ + if (wolfSSL_X509_check_issued(ctx->current_cert, + ctx->current_cert) == WOLFSSL_X509_V_OK) { + anchorEndsPath = 1; + } + else if (X509StoreCertPartialChainEnabled(ctx)) { + /* verify the anchor so the verify callback sees it and + * can veto it. */ + if (wolfSSL_CertManagerUnloadTempIntermediateCerts( + ctx->store->cm) != WOLFSSL_SUCCESS) { + ret = WOLFSSL_FATAL_ERROR; + goto exit; + } + ret = X509StoreVerifyCert(ctx, &cbRejected, verifyCb); + if (cbRejected) { + ret = WOLFSSL_FAILURE; + goto exit; + } + if (ret != WOLFSSL_SUCCESS) { + /* the partial chain is accepted at a caller-trusted + * cert; clear the error from the failed verify */ + ctx->error = 0; + } + anchorEndsPath = 1; + } + } + + if (anchorEndsPath) { + ret = X509StoreChainPush(ctx->chain, ctx->current_cert); + } + else { + SetupStoreCtxError_ex(ctx, WOLFSSL_X509_V_ERR_CERT_CHAIN_TOO_LONG, + wolfSSL_sk_X509_num(ctx->chain)); + ret = WOLFSSL_FAILURE; + } } /* RFC 5280 sec. 6.1.4: the per-certificate CertManager verification above * does not enforce the issuer's BasicConstraints pathLenConstraint on this * API path, so check it over the assembled path before reporting success. */ if (ret == WOLFSSL_SUCCESS) { - ret = X509StoreCheckPathLen(ctx); + ret = X509StoreCheckPathLen(ctx, verifyCb); } /* Enforce hostname / IP verification from X509_VERIFY_PARAM if set. @@ -1218,9 +1279,6 @@ int wolfSSL_X509_verify_cert(WOLFSSL_X509_STORE_CTX* ctx) * Without that call a callback installed to inspect or override * verification errors never sees a hostname or IP mismatch. */ if (ctx->param != NULL) { - WOLFSSL_X509_STORE_CTX_verify_cb idVerifyCb = - X509StoreGetVerifyCb(ctx); - if (ret == WOLFSSL_SUCCESS && ctx->param->hostName[0] != '\0') { if (wolfSSL_X509_check_host(orig, ctx->param->hostName, @@ -1229,7 +1287,7 @@ int wolfSSL_X509_verify_cert(WOLFSSL_X509_STORE_CTX* ctx) ctx->error = WOLFSSL_X509_V_ERR_HOSTNAME_MISMATCH; ctx->error_depth = 0; ctx->current_cert = orig; - if (idVerifyCb == NULL || idVerifyCb(0, ctx) != 1) { + if (verifyCb == NULL || verifyCb(0, ctx) != 1) { ret = WOLFSSL_FAILURE; } } @@ -1241,7 +1299,7 @@ int wolfSSL_X509_verify_cert(WOLFSSL_X509_STORE_CTX* ctx) ctx->error = WOLFSSL_X509_V_ERR_IP_ADDRESS_MISMATCH; ctx->error_depth = 0; ctx->current_cert = orig; - if (idVerifyCb == NULL || idVerifyCb(0, ctx) != 1) { + if (verifyCb == NULL || verifyCb(0, ctx) != 1) { ret = WOLFSSL_FAILURE; } } @@ -1446,11 +1504,17 @@ int wolfSSL_X509_STORE_CTX_set_ex_data_with_cleanup( #endif /* HAVE_EX_DATA_CLEANUP_HOOKS */ #if defined(WOLFSSL_APACHE_HTTPD) || defined(OPENSSL_EXTRA) +/* Set the maximum number of intermediate CAs allowed between the leaf and the + * trust anchor; neither the leaf nor the anchor counts, so 0 allows none. + * A negative depth rejects every chain. X509_STORE_CTX_init() resets + * the depth, so call this after init. */ void wolfSSL_X509_STORE_CTX_set_depth(WOLFSSL_X509_STORE_CTX* ctx, int depth) { WOLFSSL_ENTER("wolfSSL_X509_STORE_CTX_set_depth"); - if (ctx) + if (ctx != NULL) { ctx->depth = depth; + ctx->depthSet = 1; + } } #endif diff --git a/tests/api/test_ossl_x509_str.c b/tests/api/test_ossl_x509_str.c index 97c923087f3..352045406c7 100644 --- a/tests/api/test_ossl_x509_str.c +++ b/tests/api/test_ossl_x509_str.c @@ -1865,7 +1865,8 @@ static int test_untrusted_inter_no_stale_anchor(X509* leaf, X509* inter, * The chain is genuine and verifies at the default depth (covered by * test_untrusted_inter_two_level); here the depth is capped below the chain * length so the budget is consumed before the trusted root is reached. The - * fix must report it as "certificate chain too long". */ + * fix must report it as "certificate chain too long". Also covers valid, + * negative, INT_MAX and unset depths. */ static int test_untrusted_inter_depth_exhaustion(X509* leafDeep, X509* inter, X509* inter2, X509* root) { @@ -1881,10 +1882,40 @@ static int test_untrusted_inter_depth_exhaustion(X509* leafDeep, X509* inter, ExpectIntGT(sk_X509_push(untrusted, inter2), 0); ExpectNotNull(ctx = X509_STORE_CTX_new()); ExpectIntEQ(X509_STORE_CTX_init(ctx, store, leafDeep, untrusted), 1); - /* Cap the path-building budget below the chain length so the walk runs - * out of depth before it can reach the trusted root. */ + /* depth N allows N intermediates; this chain has two */ + X509_STORE_CTX_set_depth(ctx, 0); + ExpectIntEQ(X509_verify_cert(ctx), 0); + ExpectIntEQ(X509_STORE_CTX_get_error(ctx), + X509_V_ERR_CERT_CHAIN_TOO_LONG); + /* Check that depth of 1 also fails */ + ExpectIntEQ(X509_STORE_CTX_init(ctx, store, leafDeep, untrusted), 1); X509_STORE_CTX_set_depth(ctx, 1); ExpectIntEQ(X509_verify_cert(ctx), 0); + ExpectIntEQ(X509_STORE_CTX_get_error(ctx), + X509_V_ERR_CERT_CHAIN_TOO_LONG); + /* check that correct depth value is accepted */ + ExpectIntEQ(X509_STORE_CTX_init(ctx, store, leafDeep, untrusted), 1); + X509_STORE_CTX_set_depth(ctx, 2); + ExpectIntEQ(X509_verify_cert(ctx), 1); + /* init clears a depth set before it */ + X509_STORE_CTX_set_depth(ctx, 0); + ExpectIntEQ(X509_STORE_CTX_init(ctx, store, leafDeep, untrusted), 1); + ExpectIntEQ(X509_verify_cert(ctx), 1); + /* negative depth rejects the chain */ + ExpectIntEQ(X509_STORE_CTX_init(ctx, store, leafDeep, untrusted), 1); + X509_STORE_CTX_set_depth(ctx, -1); + ExpectIntEQ(X509_verify_cert(ctx), 0); + ExpectIntEQ(X509_STORE_CTX_get_error(ctx), + X509_V_ERR_CERT_CHAIN_TOO_LONG); + /* INT_MAX must not overflow the depth budget */ + ExpectIntEQ(X509_STORE_CTX_init(ctx, store, leafDeep, untrusted), 1); + X509_STORE_CTX_set_depth(ctx, INT_MAX); + ExpectIntEQ(X509_verify_cert(ctx), 1); + /* a positive depth written directly (no setter) is still honored */ + ExpectIntEQ(X509_STORE_CTX_init(ctx, store, leafDeep, untrusted), 1); + if (ctx != NULL) + ctx->depth = 1; + ExpectIntEQ(X509_verify_cert(ctx), 0); ExpectIntEQ(X509_STORE_CTX_get_error(ctx), X509_V_ERR_CERT_CHAIN_TOO_LONG); X509_STORE_CTX_free(ctx); @@ -1893,6 +1924,184 @@ static int test_untrusted_inter_depth_exhaustion(X509* leafDeep, X509* inter, return EXPECT_RESULT(); } +/* Cert the callback below vetoes, and whether it was asked about it. */ +static X509* depthRejectCert; +static int depthRejectSeen; + +static int depth_reject_cb(int ok, X509_STORE_CTX* store_ctx) +{ + if (ok && X509_STORE_CTX_get_current_cert(store_ctx) == depthRejectCert) { + depthRejectSeen = 1; + /* Reject a certificate the verification itself accepted. */ + return 0; + } + return ok; +} + +/* The trust anchor never counts against depth, including one from + * trusted_stack or store->certs + PARTIAL_CHAIN. + * + * int-ca <- root verifies at depth 0 + * leaf <- int-ca <- root verifies at depth 1, too long at depth 0 + * leaf <- int-ca int-ca in store->certs: verifies at depth 0 + * only with PARTIAL_CHAIN (store or ctx flag); + * int-ca is not self-issued, so without it the + * chain is too long */ +static int test_untrusted_inter_depth_trusted_stack(X509* leaf, X509* inter, + X509* root) +{ + EXPECT_DECLS; + X509_STORE* store = NULL; + X509_STORE_CTX* ctx = NULL; + STACK_OF(X509)* trusted = NULL; + STACK_OF(X509)* untrusted = NULL; + + ExpectNotNull(store = X509_STORE_new()); + ExpectNotNull(trusted = sk_X509_new_null()); + ExpectIntGT(sk_X509_push(trusted, root), 0); + ExpectNotNull(untrusted = sk_X509_new_null()); + ExpectIntGT(sk_X509_push(untrusted, inter), 0); + ExpectNotNull(ctx = X509_STORE_CTX_new()); + + ExpectIntEQ(X509_STORE_CTX_init(ctx, store, inter, NULL), 1); + X509_STORE_CTX_trusted_stack(ctx, trusted); + X509_STORE_CTX_set_depth(ctx, 0); + ExpectIntEQ(X509_verify_cert(ctx), 1); + + /* A negative depth rejects every chain, even a cert issued + * directly by the anchor or the self-signed anchor itself */ + ExpectIntEQ(X509_STORE_CTX_init(ctx, store, inter, NULL), 1); + X509_STORE_CTX_trusted_stack(ctx, trusted); + X509_STORE_CTX_set_depth(ctx, -1); + ExpectIntEQ(X509_verify_cert(ctx), 0); + ExpectIntEQ(X509_STORE_CTX_get_error(ctx), + X509_V_ERR_CERT_CHAIN_TOO_LONG); + ExpectIntEQ(X509_STORE_CTX_get_error_depth(ctx), 0); + + ExpectIntEQ(X509_STORE_CTX_init(ctx, store, root, NULL), 1); + X509_STORE_CTX_trusted_stack(ctx, trusted); + X509_STORE_CTX_set_depth(ctx, 0); + ExpectIntEQ(X509_verify_cert(ctx), 1); + + ExpectIntEQ(X509_STORE_CTX_init(ctx, store, root, NULL), 1); + X509_STORE_CTX_trusted_stack(ctx, trusted); + X509_STORE_CTX_set_depth(ctx, INT_MIN); + ExpectIntEQ(X509_verify_cert(ctx), 0); + ExpectIntEQ(X509_STORE_CTX_get_error(ctx), + X509_V_ERR_CERT_CHAIN_TOO_LONG); + + ExpectIntEQ(X509_STORE_CTX_init(ctx, store, leaf, untrusted), 1); + X509_STORE_CTX_trusted_stack(ctx, trusted); + X509_STORE_CTX_set_depth(ctx, 1); + ExpectIntEQ(X509_verify_cert(ctx), 1); + + ExpectIntEQ(X509_STORE_CTX_init(ctx, store, leaf, untrusted), 1); + X509_STORE_CTX_trusted_stack(ctx, trusted); + X509_STORE_CTX_set_depth(ctx, 0); + ExpectIntEQ(X509_verify_cert(ctx), 0); + ExpectIntEQ(X509_STORE_CTX_get_error(ctx), + X509_V_ERR_CERT_CHAIN_TOO_LONG); + X509_STORE_CTX_free(ctx); + ctx = NULL; + X509_STORE_free(store); + store = NULL; + + /* int-ca in store->certs is trusted but not self-issued: without + * PARTIAL_CHAIN it cannot end the path, so depth 0 is too long */ + ExpectNotNull(store = X509_STORE_new()); + ExpectIntEQ(X509_STORE_add_cert(store, inter), 1); + ExpectNotNull(ctx = X509_STORE_CTX_new()); + ExpectIntEQ(X509_STORE_CTX_init(ctx, store, leaf, NULL), 1); + X509_STORE_CTX_set_depth(ctx, 0); + ExpectIntEQ(X509_verify_cert(ctx), 0); + ExpectIntEQ(X509_STORE_CTX_get_error(ctx), + X509_V_ERR_CERT_CHAIN_TOO_LONG); + + /* ctx-level PARTIAL_CHAIN anchors the leaf at int-ca at depth 0 */ + ExpectIntEQ(X509_STORE_CTX_init(ctx, store, leaf, NULL), 1); + X509_STORE_CTX_set_flags(ctx, X509_V_FLAG_PARTIAL_CHAIN); + X509_STORE_CTX_set_depth(ctx, 0); + ExpectIntEQ(X509_verify_cert(ctx), 1); + /* fresh ctx below: init does not clear the ctx-level flag */ + X509_STORE_CTX_free(ctx); + ctx = NULL; + + /* store-level PARTIAL_CHAIN does the same */ + ExpectIntEQ(X509_STORE_set_flags(store, X509_V_FLAG_PARTIAL_CHAIN), 1); + ExpectNotNull(ctx = X509_STORE_CTX_new()); + ExpectIntEQ(X509_STORE_CTX_init(ctx, store, leaf, NULL), 1); + X509_STORE_CTX_set_depth(ctx, 0); + ExpectIntEQ(X509_verify_cert(ctx), 1); + + /* With root in the CertManager, int-ca verifies and only the verify + * callback rejects it. That veto must hold at depth 0, where the budget + * runs out on int-ca, just as it does at depth 1. */ + ExpectIntEQ(X509_STORE_add_cert(store, root), 1); + depthRejectCert = inter; + + ExpectIntEQ(X509_STORE_CTX_init(ctx, store, leaf, NULL), 1); + X509_STORE_CTX_set_verify_cb(ctx, depth_reject_cb); + X509_STORE_CTX_set_depth(ctx, 1); + depthRejectSeen = 0; + ExpectIntEQ(X509_verify_cert(ctx), 0); + ExpectIntEQ(depthRejectSeen, 1); + ExpectIntEQ(X509_STORE_CTX_get_error(ctx), X509_V_ERR_UNSPECIFIED); + + ExpectIntEQ(X509_STORE_CTX_init(ctx, store, leaf, NULL), 1); + X509_STORE_CTX_set_verify_cb(ctx, depth_reject_cb); + X509_STORE_CTX_set_depth(ctx, 0); + depthRejectSeen = 0; + ExpectIntEQ(X509_verify_cert(ctx), 0); + ExpectIntEQ(depthRejectSeen, 1); + ExpectIntEQ(X509_STORE_CTX_get_error(ctx), X509_V_ERR_UNSPECIFIED); + depthRejectCert = NULL; + + X509_STORE_CTX_free(ctx); + X509_STORE_free(store); + sk_X509_free(untrusted); + sk_X509_free(trusted); + return EXPECT_RESULT(); +} + +/* Issuer cycle: loop-a and loop-b are CAs that issued each other and + * loop-leaf is issued by loop-a; none reach the trusted root. Path building + * must not walk leaf <- loop-a <- loop-b <- loop-a <- ... until the depth + * budget runs out: at INT_MAX ("unlimited") that is ~2^31 signature checks + * and chain pushes. A cert already on the path is never reused as an issuer, + * so the cycle, not the budget, ends the search. */ +static int test_untrusted_inter_issuer_cycle(X509* loopLeaf, X509* loopA, + X509* loopB, X509* root) +{ + EXPECT_DECLS; + X509_STORE* store = NULL; + X509_STORE_CTX* ctx = NULL; + STACK_OF(X509)* untrusted = NULL; + + ExpectNotNull(store = X509_STORE_new()); + ExpectIntEQ(X509_STORE_add_cert(store, root), 1); + ExpectNotNull(untrusted = sk_X509_new_null()); + ExpectIntGT(sk_X509_push(untrusted, loopA), 0); + ExpectIntGT(sk_X509_push(untrusted, loopB), 0); + ExpectNotNull(ctx = X509_STORE_CTX_new()); + + ExpectIntEQ(X509_STORE_CTX_init(ctx, store, loopLeaf, untrusted), 1); + X509_STORE_CTX_set_depth(ctx, INT_MAX); + ExpectIntEQ(X509_verify_cert(ctx), 0); + ExpectIntEQ(X509_STORE_CTX_get_error(ctx), + X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY); + + /* default depth: still "no issuer", not "chain too long" */ + ExpectIntEQ(X509_STORE_CTX_init(ctx, store, loopLeaf, untrusted), 1); + ExpectIntEQ(X509_verify_cert(ctx), 0); + ExpectIntEQ(X509_STORE_CTX_get_error(ctx), + X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY); + + X509_STORE_CTX_free(ctx); + X509_STORE_free(store); + sk_X509_free(untrusted); + return EXPECT_RESULT(); +} + /* Caller-owned trusted stack (X509_STORE_CTX_set0_trusted_stack): chain * building appends the caller-supplied intermediates to an internal working * copy, never to the caller's stack. If the caller's stack were modified and @@ -2312,6 +2521,9 @@ int test_X509_verify_cert_untrusted_inter(void) X509* tamperedInter = NULL; X509* root = NULL; X509* wrongRoot = NULL; + X509* loopA = NULL; + X509* loopB = NULL; + X509* loopLeaf = NULL; int sanityRes = 0; int twoLevelRes = 0; int emptyStoreRes = 0; @@ -2320,6 +2532,8 @@ int test_X509_verify_cert_untrusted_inter(void) int reusedStoreRes = 0; int noStaleRes = 0; int depthExhaustRes = 0; + int depthTrustedStackRes = 0; + int issuerCycleRes = 0; int trustedStackCleanupRes = 0; int trustedStackUnchangedRes = 0; int retryRes = 0; @@ -2341,6 +2555,10 @@ int test_X509_verify_cert_untrusted_inter(void) ExpectNotNull(root = untrusted_inter_load(UA_CERT_DIR "root-ca-cert.pem")); ExpectNotNull(wrongRoot = untrusted_inter_load(UA_CERT_DIR "alt-ca-cert.pem")); + ExpectNotNull(loopA = untrusted_inter_load(UA_CERT_DIR "loop-a-cert.pem")); + ExpectNotNull(loopB = untrusted_inter_load(UA_CERT_DIR "loop-b-cert.pem")); + ExpectNotNull(loopLeaf = + untrusted_inter_load(UA_CERT_DIR "loop-leaf-cert.pem")); /* Run every sub-case unconditionally - each reports its own result - so a * regression in one does not mask the others. */ @@ -2360,6 +2578,8 @@ int test_X509_verify_cert_untrusted_inter(void) root); depthExhaustRes = test_untrusted_inter_depth_exhaustion(leafDeep, inter, inter2, root); + depthTrustedStackRes = test_untrusted_inter_depth_trusted_stack(leaf, + inter, root); trustedStackCleanupRes = test_untrusted_inter_trusted_stack_cleanup( leaf, inter, root); trustedStackUnchangedRes = @@ -2384,6 +2604,7 @@ int test_X509_verify_cert_untrusted_inter(void) ExpectIntEQ(reusedStoreRes, 1); ExpectIntEQ(noStaleRes, 1); ExpectIntEQ(depthExhaustRes, 1); + ExpectIntEQ(depthTrustedStackRes, 1); ExpectIntEQ(trustedStackCleanupRes, 1); ExpectIntEQ(trustedStackUnchangedRes, 1); ExpectIntEQ(retryRes, 1); @@ -2394,6 +2615,12 @@ int test_X509_verify_cert_untrusted_inter(void) #endif ExpectIntEQ(storeStackRes, 1); } + /* Own guard so a missing loop fixture does not skip the sub-cases above. */ + if (loopA != NULL && loopB != NULL && loopLeaf != NULL && root != NULL) { + issuerCycleRes = test_untrusted_inter_issuer_cycle(loopLeaf, loopA, + loopB, root); + ExpectIntEQ(issuerCycleRes, 1); + } X509_free(leaf); X509_free(leafDeep); @@ -2402,6 +2629,9 @@ int test_X509_verify_cert_untrusted_inter(void) X509_free(tamperedInter); X509_free(root); X509_free(wrongRoot); + X509_free(loopA); + X509_free(loopB); + X509_free(loopLeaf); #undef UA_CERT_DIR #endif /* OPENSSL_EXTRA && !NO_RSA && !NO_CERTS && !NO_FILESYSTEM */ return EXPECT_RESULT(); diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index 69c7d802491..03c678a62cf 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -709,6 +709,7 @@ struct WOLFSSL_X509_STORE_CTX { #endif #if defined(WOLFSSL_APACHE_HTTPD) || defined(OPENSSL_EXTRA) int depth; /* used in X509_STORE_CTX_*_depth */ + WC_BITFIELD depthSet:1; /* depth set via set_depth() */ #endif void* userCtx; /* user ctx */ int error; /* current error */