diff --git a/.github/configs/os-check-linux.json b/.github/configs/os-check-linux.json index 08f78b993bd..19bef80b0c1 100644 --- a/.github/configs/os-check-linux.json +++ b/.github/configs/os-check-linux.json @@ -592,5 +592,14 @@ "configure": ["--enable-leanpsk"], "extra_cflags": "-Wdeclaration-after-statement -Wnull-dereference -DTEST_LIBWOLFSSL_SOURCES_INCLUSION_SEQUENCE -Werror=implicit-function-declaration -Wno-error=cpp", "check": false, - "run": [["./wolfcrypt/test/testwolfcrypt"]]} + "run": [["./wolfcrypt/test/testwolfcrypt"]]}, +{"name": "cert-events-crl", + "comment": "Certificate validation events without the compatibility layer. --enable-all covers them with OPENSSL_EXTRA on, where the TLS verify path and the error codes it passes on differ.", + "configure": ["--enable-cert-events", "--enable-crl"]}, +{"name": "cert-events-crl-asn-original", + "comment": "As cert-events-crl with the original ASN.1 parser, which reports field failures from its own code and rejects unique IDs outright.", + "configure": ["--enable-cert-events", "--enable-crl", "--enable-asn=original"]}, +{"name": "cert-events-tiny", + "comment": "Certificate validation events without CRL and with WOLFSSL_X509_TINY, which does not decode some extensions the strict checks use.", + "configure": ["--enable-cert-events", "CPPFLAGS=-DWOLFSSL_X509_TINY"]} ] diff --git a/CMakeLists.txt b/CMakeLists.txt index d0d5ba9f3e3..2896f8ee9e4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1944,6 +1944,10 @@ add_option("WOLFSSL_CERTGENCACHE" "Enable decoded cert caching (default: disabled)" "no" "yes;no") +add_option("WOLFSSL_CERT_EVENTS" + "Enable structured certificate and CRL validation event callback (default: disabled)" + "no" "yes;no") + # Chain verify callback add_option("WOLFSSL_CHAIN_VERIFY_CB" "Enable replacing peer certificate verification with a user callback (default: disabled)" @@ -3577,6 +3581,9 @@ endif() if(WOLFSSL_CERTGENCACHE) list(APPEND WOLFSSL_DEFINITIONS "-DWOLFSSL_CERT_GEN_CACHE") endif() +if(WOLFSSL_CERT_EVENTS) + list(APPEND WOLFSSL_DEFINITIONS "-DWOLFSSL_CERT_EVENTS") +endif() if(WOLFSSL_CHAIN_VERIFY_CB) list(APPEND WOLFSSL_DEFINITIONS "-DWOLFSSL_CHAIN_VERIFY_CB") diff --git a/ChangeLog.md b/ChangeLog.md index 822e5347213..5dd9676cdcb 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -26,6 +26,26 @@ curve equation need no curve arithmetic, so both are still done on the host. Both macros are documented in `wolfcrypt/src/port/nxp/README_SE050.md` and `IDE/CRYPTOCELL/README.md`. by @SparkiDev +* Added certificate validation events (`--enable-cert-events`, + `WOLFSSL_CERT_EVENTS`; off by default and in `--enable-all`). A callback set + with `wolfSSL_CertManagerSetCertEventCb()`, `wolfSSL_CTX_SetCertEventCb()`, + `wolfSSL_X509_STORE_SetCertEventCb()` or `wc_SetCertEventCallback()` + observes each certificate and CRL check: a DEFECT event for each failed + certificate or CRL check, with the certificate's role and chain depth, the + RFC 5280 field and the reason; a RESULT event when a verification + finishes; an INFO event with each certificate's algorithms and key size; + the certificate's subject, issuer and serial number and the TLS + connection; and a stable condition value for each failure, named after the + IEC 62351-9 and IEC 62351-3 audit events, for security logging such as the + EU Cyber Resilience Act calls for. The callback cannot change a result; + the verify callback still decides. + Optional strict profile checks (unique IDs, no AKID, a CA without key + usage, no CRL distribution point, no OCSP responder, a CRL thisUpdate in + the future) are reported as NOTICE events, and each can be enforced with + `wolfSSL_CertManagerSetCertEventStrict()` to fail with its own + `ASN_STRICT_*_E` error. They run after every other check, also when the + verify callback allows another failure, so allowing one failure never + skips another check. by @sameehj ## Behavioral Changes * **Behavioral change (DSA now uses the leftmost |q| bytes of the digest)**: diff --git a/certs/test/catalog.txt b/certs/test/catalog.txt index 22284439321..83731009b43 100644 --- a/certs/test/catalog.txt +++ b/certs/test/catalog.txt @@ -19,3 +19,13 @@ cert-ext-oid-collide.der: OID, not the mapped one. Does not collide under WOLFSSL_OLD_OID_SUM. Signature is invalid; only ever parsed, never verified. Regenerate with certs/test/gen-oid-collide-cert.sh. +cert-events/*.der, cert-events/*.pem: + Copies of certs/server-cert.der, each with exactly one defect and signed + again with certs/ca-key.pem, so verification reaches the defect. + good.der has no defect. strict-*.der and crl-this-update-future.der test + the strict profile checks; strict-good.der passes them. The chain-*.pem + files are chains for a server using certs/server-key.pem, with CAs keyed by + certs/intermediate/ca-int*-key.pem under certs/ca-cert.pem; chain-good.pem + has no fault. Used by the certificate validation event tests in + tests/api/test_certman.c. Valid until 2049. Regenerate with + certs/test/gen-cert-events.py. diff --git a/certs/test/cert-events/aia-malformed.der b/certs/test/cert-events/aia-malformed.der new file mode 100644 index 00000000000..cdd7409bf16 Binary files /dev/null and b/certs/test/cert-events/aia-malformed.der differ diff --git a/certs/test/cert-events/akid-malformed.der b/certs/test/cert-events/akid-malformed.der new file mode 100644 index 00000000000..e0b124f3207 Binary files /dev/null and b/certs/test/cert-events/akid-malformed.der differ diff --git a/certs/test/cert-events/bc-malformed.der b/certs/test/cert-events/bc-malformed.der new file mode 100644 index 00000000000..46ce7970c38 Binary files /dev/null and b/certs/test/cert-events/bc-malformed.der differ diff --git a/certs/test/cert-events/ca-no-crlsign.der b/certs/test/cert-events/ca-no-crlsign.der new file mode 100644 index 00000000000..17344cb6b3e Binary files /dev/null and b/certs/test/cert-events/ca-no-crlsign.der differ diff --git a/certs/test/cert-events/chain-good.pem b/certs/test/cert-events/chain-good.pem new file mode 100644 index 00000000000..23ae52445b0 --- /dev/null +++ b/certs/test/cert-events/chain-good.pem @@ -0,0 +1,44 @@ +-----BEGIN CERTIFICATE----- +MIIDjTCCAnWgAwIBAgIEDOfhATANBgkqhkiG9w0BAQsFADAdMRswGQYDVQQDDBJD +ZXJ0IEV2ZW50cyBJbnQgQ0EwHhcNMjUwMTAxMDAwMDAwWhcNNDkxMjMxMjM1OTU5 +WjCBkTELMAkGA1UEBhMCVVMxEDAOBgNVBAgMB01vbnRhbmExEDAOBgNVBAcMB0Jv +emVtYW4xEDAOBgNVBAoMB3dvbGZTU0wxEDAOBgNVBAsMB1N1cHBvcnQxGDAWBgNV +BAMMD3d3dy53b2xmc3NsLmNvbTEgMB4GCSqGSIb3DQEJARYRZmFjdHNAd29sZnNz +bC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDAlQjhV0HycW23 +0kVBJwFlxkWu8rwkMLiVzi9O1vYciLx8n/uoZ3/+XJxRdfeKygfnNS+P4b17wC98 +q2SoF/zKXXu64CHlci5vLobYlXParBtTuV8/1xkNJU/hY2NRiwtkP61DuKUcXDSz +rgCgY8X2fwtZaHhzpowYqQJtr8MZAS64EOPGzEC0aaNGM2mHbsS7F6bz6N2tc7x7 +LyG1/WZRDL1Us+FtXxy8I3PRCQOJFNIQuWTDKtChlkq84dQaW8egwMFjeA9ENzAy +loAyI5Whd7oT0pdz4l0lyWoNwzlgpLSwaUJCCenYCLwzILNYIqeq68Th5mGDxdKW +39nQT63XAgMBAAGjYDBeMB0GA1UdDgQWBBThtKDO3gM48K71o8iI1Qzx51GhMTAf +BgNVHSMEGDAWgBTVJPMMuhIZLGVl2yrQko+ZE1Z6zzAMBgNVHRMBAf8EAjAAMA4G +A1UdDwEB/wQEAwIFoDANBgkqhkiG9w0BAQsFAAOCAQEADFPxXawK3q/t9put9pte +o8hbpqwzHz09yq5ulKczd554M+RQPDqabj6w9ew7jaa45bqgl+5lX5e4TGW0/XNV +T29z7cpk5aeaddcEqqPrftU0xDMEMeIaYxB4HZRRy+h2tefHVhpT4y7oUzTJAkXx +s9EYvAPOvhPwxkMtZKD5BOeB5LzmilLVxtjWQJjIQjxk45T6Mxij8xeEJBZ1YjBo +4bqunXBay1MI8Z0vyN9VNU4+rLY4xpy8R3SoEjUHK+TmpB6ukJr0HVd6xoCezvmz +LEmpUtwEmz53/QJIYumZNqZA+a+6SnWTq/lCrvhjP/rVr4LRe62PSqChlKwa/BXh +Eg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDlDCCAnygAwIBAgIEDOfhAjANBgkqhkiG9w0BAQsFADCBlTELMAkGA1UEBhMC +VVMxEDAOBgNVBAgMB01vbnRhbmExEDAOBgNVBAcMB0JvemVtYW4xETAPBgNVBAoM +CFNhd3Rvb3RoMRMwEQYDVQQLDApDb25zdWx0aW5nMRgwFgYDVQQDDA93d3cud29s +ZnNzbC5jb20xIDAeBgkqhkiG9w0BCQEWEWZhY3RzQHdvbGZzc2wuY29tMB4XDTI1 +MDEwMTAwMDAwMFoXDTQ5MTIzMTIzNTk1OVowHTEbMBkGA1UEAwwSQ2VydCBFdmVu +dHMgSW50IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw6JzXSFi +IM46cTinlLvbhwQcWhueSw0+yvil9w1q3COQImorWGNKKGpIqOdzH6JV2E0CO+LL +a+KDyVGPd/3cLV0jtyOafrYpaOgqTqn+MnAxnvDv7viN4/zz1yjdeh2erSMr8aZ/ +NFIpZtLlZFVk1t1LQTtVg27AEQ5uIMIWc+sw/wlGu+fMxgNEQRHGwWw2L0r5kVXK +WF43uCgQMIlAlnfPcGakVftpC+fZsjNl23I6d7crSfy2zVgQjauqy0BFdwI5GLOP +MwFId1C+jnOn3jagSY4sFq+5+0ItNWrbNDfVFFl9ZXLli2VVSyBeR/n4OtNs2Tr1 +xwFGMcN5mhi+SQIDAQABo2MwYTAdBgNVHQ4EFgQU1STzDLoSGSxlZdsq0JKPmRNW +es8wHwYDVR0jBBgwFoAUJ45nEXTDJh0/7TNjs6TYHTDl6NUwDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQELBQADggEBACbBE+e3IaQt +2VDvUXw3pZWoXuXDTCwI022Go69y7n0lUXoA1O2yPwVQmexeiIo7w3V5K4Z/RzSg +Sm30R+7XeB6Z6SQ08sm5FKXg3fNOlC7OIrcGZYqRX+ztOViKEtUqADhK2aQeMKxx +Awf586aef8Ilk8eRyt92pmbscJCa//rdn8PQEbDQQ/LOQUN/wLw32p8aEtO85xvf +fBh+UUKeFZCRPUTnHvr/Z2Vwc9cxDkR+1c228u2OZJl1EnwIQOBfZypuxgczuP8G +OHHe0Zsd7TSNUblIwEeb9GqpwRk1Jym31DZU/lmf+jhmSLH2YhJanaMXGnPPgix2 +1p6wecWxr+c= +-----END CERTIFICATE----- diff --git a/certs/test/cert-events/chain-leaf-eku-client-only.pem b/certs/test/cert-events/chain-leaf-eku-client-only.pem new file mode 100644 index 00000000000..14f16b8d9e1 --- /dev/null +++ b/certs/test/cert-events/chain-leaf-eku-client-only.pem @@ -0,0 +1,44 @@ +-----BEGIN CERTIFICATE----- +MIIDojCCAoqgAwIBAgIEDOfhDjANBgkqhkiG9w0BAQsFADAdMRswGQYDVQQDDBJD +ZXJ0IEV2ZW50cyBJbnQgQ0EwHhcNMjUwMTAxMDAwMDAwWhcNNDkxMjMxMjM1OTU5 +WjCBkTELMAkGA1UEBhMCVVMxEDAOBgNVBAgMB01vbnRhbmExEDAOBgNVBAcMB0Jv +emVtYW4xEDAOBgNVBAoMB3dvbGZTU0wxEDAOBgNVBAsMB1N1cHBvcnQxGDAWBgNV +BAMMD3d3dy53b2xmc3NsLmNvbTEgMB4GCSqGSIb3DQEJARYRZmFjdHNAd29sZnNz +bC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDAlQjhV0HycW23 +0kVBJwFlxkWu8rwkMLiVzi9O1vYciLx8n/uoZ3/+XJxRdfeKygfnNS+P4b17wC98 +q2SoF/zKXXu64CHlci5vLobYlXParBtTuV8/1xkNJU/hY2NRiwtkP61DuKUcXDSz +rgCgY8X2fwtZaHhzpowYqQJtr8MZAS64EOPGzEC0aaNGM2mHbsS7F6bz6N2tc7x7 +LyG1/WZRDL1Us+FtXxy8I3PRCQOJFNIQuWTDKtChlkq84dQaW8egwMFjeA9ENzAy +loAyI5Whd7oT0pdz4l0lyWoNwzlgpLSwaUJCCenYCLwzILNYIqeq68Th5mGDxdKW +39nQT63XAgMBAAGjdTBzMB0GA1UdDgQWBBThtKDO3gM48K71o8iI1Qzx51GhMTAf +BgNVHSMEGDAWgBTVJPMMuhIZLGVl2yrQko+ZE1Z6zzAMBgNVHRMBAf8EAjAAMA4G +A1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDAjANBgkqhkiG9w0BAQsF +AAOCAQEAYaZZL5eho/TnyO7CliZC1aMJUVSD9n98K28bxuPrtO3KAPuJd3krLvQU +dJR6Zg7e3A/whY/fn35Oi7KL0MYULO1bWUZQXO+0X2L5PR3WX2IPsG5R3RCsP3rz +JoIgajfHnmjeD35Oq1RUDwaCpt00hDW7VXqyrTZg2SG78niO/+DJDNhy9IwQFUHX +1l+z034eDTQaecb3vZsvqsn5OdynK8EGLK0sHcgwp7BK7gRg0KwVKCSoLrwDE3QC +7l9wO+xb+NAQw5xxupitWUfrQUY3ZxHXAJOLSD5guR3ho4wN2ibCotWflfBuuLNR +NzsM1UBzJLqRS3zNb7vZW+VsQWvw3g== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDlDCCAnygAwIBAgIEDOfhDzANBgkqhkiG9w0BAQsFADCBlTELMAkGA1UEBhMC +VVMxEDAOBgNVBAgMB01vbnRhbmExEDAOBgNVBAcMB0JvemVtYW4xETAPBgNVBAoM +CFNhd3Rvb3RoMRMwEQYDVQQLDApDb25zdWx0aW5nMRgwFgYDVQQDDA93d3cud29s +ZnNzbC5jb20xIDAeBgkqhkiG9w0BCQEWEWZhY3RzQHdvbGZzc2wuY29tMB4XDTI1 +MDEwMTAwMDAwMFoXDTQ5MTIzMTIzNTk1OVowHTEbMBkGA1UEAwwSQ2VydCBFdmVu +dHMgSW50IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw6JzXSFi +IM46cTinlLvbhwQcWhueSw0+yvil9w1q3COQImorWGNKKGpIqOdzH6JV2E0CO+LL +a+KDyVGPd/3cLV0jtyOafrYpaOgqTqn+MnAxnvDv7viN4/zz1yjdeh2erSMr8aZ/ +NFIpZtLlZFVk1t1LQTtVg27AEQ5uIMIWc+sw/wlGu+fMxgNEQRHGwWw2L0r5kVXK +WF43uCgQMIlAlnfPcGakVftpC+fZsjNl23I6d7crSfy2zVgQjauqy0BFdwI5GLOP +MwFId1C+jnOn3jagSY4sFq+5+0ItNWrbNDfVFFl9ZXLli2VVSyBeR/n4OtNs2Tr1 +xwFGMcN5mhi+SQIDAQABo2MwYTAdBgNVHQ4EFgQU1STzDLoSGSxlZdsq0JKPmRNW +es8wHwYDVR0jBBgwFoAUJ45nEXTDJh0/7TNjs6TYHTDl6NUwDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQELBQADggEBAK5snKw4GCEV +bR/TRttX83AE9dc8OlxTZdedHtYQ8LtWfZhzogjdbsETkpymXi+fFv5Orci5ihh+ +ev730zu0vl1uDo7abKhOLOS3ytSeTwwi37Fw2cVRiTxPq7aBrXAxobesAW2afDRu +ZaimDeQNKtoV7RmR8LcyT8YYY2fL+IvwZGac2A/yFNAUjqHQOwJEDcbMyeq0bFU0 +dSHGQrlbrjUQlQuNabKBOncaeyHxw9YPlcb67BYp+F8se7nKemFMgRJGpPj04VX1 +DqZbih17h1QGH6xgDxasHSPbGfK57oYh9+ria493rhrxtzaqYYpz39GB6X8zrjnu +m6FofCWCgRQ= +-----END CERTIFICATE----- diff --git a/certs/test/cert-events/chain-leaf-ku-no-digsig.pem b/certs/test/cert-events/chain-leaf-ku-no-digsig.pem new file mode 100644 index 00000000000..80deec1308e --- /dev/null +++ b/certs/test/cert-events/chain-leaf-ku-no-digsig.pem @@ -0,0 +1,44 @@ +-----BEGIN CERTIFICATE----- +MIIDjTCCAnWgAwIBAgIEDOfhDDANBgkqhkiG9w0BAQsFADAdMRswGQYDVQQDDBJD +ZXJ0IEV2ZW50cyBJbnQgQ0EwHhcNMjUwMTAxMDAwMDAwWhcNNDkxMjMxMjM1OTU5 +WjCBkTELMAkGA1UEBhMCVVMxEDAOBgNVBAgMB01vbnRhbmExEDAOBgNVBAcMB0Jv +emVtYW4xEDAOBgNVBAoMB3dvbGZTU0wxEDAOBgNVBAsMB1N1cHBvcnQxGDAWBgNV +BAMMD3d3dy53b2xmc3NsLmNvbTEgMB4GCSqGSIb3DQEJARYRZmFjdHNAd29sZnNz +bC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDAlQjhV0HycW23 +0kVBJwFlxkWu8rwkMLiVzi9O1vYciLx8n/uoZ3/+XJxRdfeKygfnNS+P4b17wC98 +q2SoF/zKXXu64CHlci5vLobYlXParBtTuV8/1xkNJU/hY2NRiwtkP61DuKUcXDSz +rgCgY8X2fwtZaHhzpowYqQJtr8MZAS64EOPGzEC0aaNGM2mHbsS7F6bz6N2tc7x7 +LyG1/WZRDL1Us+FtXxy8I3PRCQOJFNIQuWTDKtChlkq84dQaW8egwMFjeA9ENzAy +loAyI5Whd7oT0pdz4l0lyWoNwzlgpLSwaUJCCenYCLwzILNYIqeq68Th5mGDxdKW +39nQT63XAgMBAAGjYDBeMB0GA1UdDgQWBBThtKDO3gM48K71o8iI1Qzx51GhMTAf +BgNVHSMEGDAWgBTVJPMMuhIZLGVl2yrQko+ZE1Z6zzAMBgNVHRMBAf8EAjAAMA4G +A1UdDwEB/wQEAwIFIDANBgkqhkiG9w0BAQsFAAOCAQEAMmOO4rOcYcZjZdZrvBaT +KmF3e1N3unte3MmsCetDCrLNk80Mk8zRfQE17RXp082rXI2s+7JfarmZkxXa4wBF +rX4Vw/RPy8YWisA7snEmNIfjxbtS0UONdBkL5F08u8n8YmDIUEdQhD6OwgujUeXW ++NnXBTM35XRmqDXEJCIUyCLjMjFxruRYbN2nEhSF7KuSF+hivWDGzz/4gj2lAWe/ +7fvPAb0HaauYpfCOOCMaJmIYwPWwliA9QDewvyuI3OXBy91tHMaF+UkY6IFUhytr +RQOXVTOXND9ke9zNodGLUjkPxQnkBpllyebZujRSlMNdjlKk6aQ47FzOSwKkefB0 +kw== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDlDCCAnygAwIBAgIEDOfhDTANBgkqhkiG9w0BAQsFADCBlTELMAkGA1UEBhMC +VVMxEDAOBgNVBAgMB01vbnRhbmExEDAOBgNVBAcMB0JvemVtYW4xETAPBgNVBAoM +CFNhd3Rvb3RoMRMwEQYDVQQLDApDb25zdWx0aW5nMRgwFgYDVQQDDA93d3cud29s +ZnNzbC5jb20xIDAeBgkqhkiG9w0BCQEWEWZhY3RzQHdvbGZzc2wuY29tMB4XDTI1 +MDEwMTAwMDAwMFoXDTQ5MTIzMTIzNTk1OVowHTEbMBkGA1UEAwwSQ2VydCBFdmVu +dHMgSW50IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw6JzXSFi +IM46cTinlLvbhwQcWhueSw0+yvil9w1q3COQImorWGNKKGpIqOdzH6JV2E0CO+LL +a+KDyVGPd/3cLV0jtyOafrYpaOgqTqn+MnAxnvDv7viN4/zz1yjdeh2erSMr8aZ/ +NFIpZtLlZFVk1t1LQTtVg27AEQ5uIMIWc+sw/wlGu+fMxgNEQRHGwWw2L0r5kVXK +WF43uCgQMIlAlnfPcGakVftpC+fZsjNl23I6d7crSfy2zVgQjauqy0BFdwI5GLOP +MwFId1C+jnOn3jagSY4sFq+5+0ItNWrbNDfVFFl9ZXLli2VVSyBeR/n4OtNs2Tr1 +xwFGMcN5mhi+SQIDAQABo2MwYTAdBgNVHQ4EFgQU1STzDLoSGSxlZdsq0JKPmRNW +es8wHwYDVR0jBBgwFoAUJ45nEXTDJh0/7TNjs6TYHTDl6NUwDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQELBQADggEBAK2TiY13fREh +iaOc3+iG9cS1hDmyAV0bCQXNQeNIyu7M2OQJ014Qp6FU0Ntkng4R6+/Jo/QdhXJu +bBVebYSvrLQMiOzvuBUhuyS3J/s5ht1baiHrNkg+b1OurFED1VPKZNHXZzQDmdZV +nuN26H5jPpH5+gbHCSmZtBeaPinSBnjjVX6zqYwURKna3UO3NRET4ARYm35s3Jag +X70KedeHjJJn93oMh20PqTiwWMmbxF9VGC/EO+AOPV9pbzph/Mwupo+99uZlzWPm +JGPCq2AN0VwwA7IiZF70xvkoVBJ9sWydJvhtmwhlL9s9AqLIQSoP99jF+ygxL4UT +fsnuu2qjGno= +-----END CERTIFICATE----- diff --git a/certs/test/cert-events/chain-pathlen-exceeded.pem b/certs/test/cert-events/chain-pathlen-exceeded.pem new file mode 100644 index 00000000000..6e962eedefe --- /dev/null +++ b/certs/test/cert-events/chain-pathlen-exceeded.pem @@ -0,0 +1,64 @@ +-----BEGIN CERTIFICATE----- +MIIDmzCCAoOgAwIBAgIEDOfhCDANBgkqhkiG9w0BAQsFADArMSkwJwYDVQQDDCBD +ZXJ0IEV2ZW50cyBJbnQgQmVsb3cgUGF0aCBMZW4gMDAeFw0yNTAxMDEwMDAwMDBa +Fw00OTEyMzEyMzU5NTlaMIGRMQswCQYDVQQGEwJVUzEQMA4GA1UECAwHTW9udGFu +YTEQMA4GA1UEBwwHQm96ZW1hbjEQMA4GA1UECgwHd29sZlNTTDEQMA4GA1UECwwH +U3VwcG9ydDEYMBYGA1UEAwwPd3d3LndvbGZzc2wuY29tMSAwHgYJKoZIhvcNAQkB +FhFmYWN0c0B3b2xmc3NsLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAMCVCOFXQfJxbbfSRUEnAWXGRa7yvCQwuJXOL07W9hyIvHyf+6hnf/5cnFF1 +94rKB+c1L4/hvXvAL3yrZKgX/Mpde7rgIeVyLm8uhtiVc9qsG1O5Xz/XGQ0lT+Fj +Y1GLC2Q/rUO4pRxcNLOuAKBjxfZ/C1loeHOmjBipAm2vwxkBLrgQ48bMQLRpo0Yz +aYduxLsXpvPo3a1zvHsvIbX9ZlEMvVSz4W1fHLwjc9EJA4kU0hC5ZMMq0KGWSrzh +1Bpbx6DAwWN4D0Q3MDKWgDIjlaF3uhPSl3PiXSXJag3DOWCktLBpQkIJ6dgIvDMg +s1gip6rrxOHmYYPF0pbf2dBPrdcCAwEAAaNgMF4wHQYDVR0OBBYEFOG0oM7eAzjw +rvWjyIjVDPHnUaExMB8GA1UdIwQYMBaAFLVTZnQGSfrz9de5DFQNb1IVzo47MAwG +A1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgWgMA0GCSqGSIb3DQEBCwUAA4IBAQCz +CQjW+raVw32vb3OLunuaXcrei1Z8eEQr0zITtGA9DcbbuQKFl9J9zMa+TK0vxHcn +od8bEehanjIOx6D2O3TuwV5eXL+RAFioC7AddhSzbqcgsJ1+0oiRSFaQphBnK0Hj +5OZMNtTCbt0/CyZShizv7KPik1gvdFUOEt7WQm8I1uSZAZiSxYdXale/ucZfZQE+ +XdCZPzR3aHvMsMyEoYieKnd+N5pm73eI7dsDCmqLbxRkzMpJ44RulESU19u8at8d +YtKNmgWGOal2SpLK1CEBZQcwFUQ49BYJZxwoELzy6r2zUbq2nWOJh+Y0uYfrIq0S +Cw2lw/2U3KEgHbFZAjd3 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDMTCCAhmgAwIBAgIEDOfhBzANBgkqhkiG9w0BAQsFADAlMSMwIQYDVQQDDBpD +ZXJ0IEV2ZW50cyBJbnQgUGF0aCBMZW4gMDAeFw0yNTAxMDEwMDAwMDBaFw00OTEy +MzEyMzU5NTlaMCsxKTAnBgNVBAMMIENlcnQgRXZlbnRzIEludCBCZWxvdyBQYXRo +IExlbiAwMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz8k9WQGfHXeR +VsurBoLBgTGa4vnG+aNALYZC119BpQVCD18ra70pkuVSxlz5fp37jtZpjAORhxwf +vyRZRMzvr5IqBuGhAVsEV4oatgTiwjwQPEIxAarD8jIeAZXQkadmwSJoNlMqUgPr +tZuCAST50a77U0xaBuVuWtasWygaU+jXpc5unDTDCAvLL47f74w19bC8XQ+uCkrP +VAHSPLR47kgQVoBPg4dOZx9PFy4+LfVtyQeiPjKSDx6kC1WmH4TvnXXvZnx19+dA +OpzBM0I9L3+ZXXsE1alsQeiJFlj9OqAEvXfWY15qE1k3X/FZAUVInIv3FvRQ91q0 +WjP29UHBPQIDAQABo2MwYTAdBgNVHQ4EFgQUtVNmdAZJ+vP117kMVA1vUhXOjjsw +HwYDVR0jBBgwFoAU1STzDLoSGSxlZdsq0JKPmRNWes8wDwYDVR0TAQH/BAUwAwEB +/zAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQELBQADggEBAF8swcw3zBIwEpo3 +bNoUdwQbxOsEjllRW3ntFrXOQIN+CaeUZS4m3gFWcxc+I+BWNG9xI1MUe4e2ONB3 +rVssRcb+EwhbsBdy69wfozme4mz4TPwvCScFskBx8JTlSYmkUlIvriG/DOnxnh5V +VPz3d7PZMG8Xfs5gCmiA9Ce92dVMCvS9iLO7E7s2s8tNS4BXg7jHkErT3EXl/Em5 +PDWAB78w+Hi+BdSObrQXyqJFbvH3EU+JUp8bopC1c1kpuGaio3+BCvLjq3QWxxVq +tIQY7X4igZ6eHBwqdgPdMRrqP0w2Pxrvk2LgmIiaBVINupZuAxcp3bSbFC/9JvFR +GzYwgPQ= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDnzCCAoegAwIBAgIEDOfhCTANBgkqhkiG9w0BAQsFADCBlTELMAkGA1UEBhMC +VVMxEDAOBgNVBAgMB01vbnRhbmExEDAOBgNVBAcMB0JvemVtYW4xETAPBgNVBAoM +CFNhd3Rvb3RoMRMwEQYDVQQLDApDb25zdWx0aW5nMRgwFgYDVQQDDA93d3cud29s +ZnNzbC5jb20xIDAeBgkqhkiG9w0BCQEWEWZhY3RzQHdvbGZzc2wuY29tMB4XDTI1 +MDEwMTAwMDAwMFoXDTQ5MTIzMTIzNTk1OVowJTEjMCEGA1UEAwwaQ2VydCBFdmVu +dHMgSW50IFBhdGggTGVuIDAwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQDDonNdIWIgzjpxOKeUu9uHBBxaG55LDT7K+KX3DWrcI5AiaitYY0ooakio53Mf +olXYTQI74str4oPJUY93/dwtXSO3I5p+tilo6CpOqf4ycDGe8O/u+I3j/PPXKN16 +HZ6tIyvxpn80Uilm0uVkVWTW3UtBO1WDbsARDm4gwhZz6zD/CUa758zGA0RBEcbB +bDYvSvmRVcpYXje4KBAwiUCWd89wZqRV+2kL59myM2Xbcjp3tytJ/LbNWBCNq6rL +QEV3AjkYs48zAUh3UL6Oc6feNqBJjiwWr7n7Qi01ats0N9UUWX1lcuWLZVVLIF5H ++fg602zZOvXHAUYxw3maGL5JAgMBAAGjZjBkMB0GA1UdDgQWBBTVJPMMuhIZLGVl +2yrQko+ZE1Z6zzAfBgNVHSMEGDAWgBQnjmcRdMMmHT/tM2OzpNgdMOXo1TASBgNV +HRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOC +AQEArg2UeBHCzg64mtL7TjeqMHeCGWOHRcRsFAVOXMCv1X+5wDILGGeDj+R6LKCR +0lQ+pwCXTE4vPaD0wT1KmxamDxu2IkKyFWhO9sXUoeu+9gGf/bNiLQFcUrJfWcDm +7Kr/q2dHxgk3BiRog5c+mgVIn398HmECEE07KPL/+HmPn+Xq7m6ZepvvCKPmYJiV +HsURIJlyIRJub5Gqc9Q4/9uyw0uKKicwaJBS//LAHJrqgTHm3nwXdt1dEQnsVDbt +bpgxKHEcZAg3k6KrWpBY0BeyBbMler/B1FDvglmQNvwG3m6JnkTvN3GHfk5Jswe/ +49t0gvVXrtDazlmtqm8Pmhx2CQ== +-----END CERTIFICATE----- diff --git a/certs/test/cert-events/chain-signer-no-certsign.pem b/certs/test/cert-events/chain-signer-no-certsign.pem new file mode 100644 index 00000000000..e23ad134337 --- /dev/null +++ b/certs/test/cert-events/chain-signer-no-certsign.pem @@ -0,0 +1,44 @@ +-----BEGIN CERTIFICATE----- +MIIDlzCCAn+gAwIBAgIEDOfhBTANBgkqhkiG9w0BAQsFADAnMSUwIwYDVQQDDBxD +ZXJ0IEV2ZW50cyBJbnQgTm8gQ2VydCBTaWduMB4XDTI1MDEwMTAwMDAwMFoXDTQ5 +MTIzMTIzNTk1OVowgZExCzAJBgNVBAYTAlVTMRAwDgYDVQQIDAdNb250YW5hMRAw +DgYDVQQHDAdCb3plbWFuMRAwDgYDVQQKDAd3b2xmU1NMMRAwDgYDVQQLDAdTdXBw +b3J0MRgwFgYDVQQDDA93d3cud29sZnNzbC5jb20xIDAeBgkqhkiG9w0BCQEWEWZh +Y3RzQHdvbGZzc2wuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +wJUI4VdB8nFtt9JFQScBZcZFrvK8JDC4lc4vTtb2HIi8fJ/7qGd//lycUXX3isoH +5zUvj+G9e8AvfKtkqBf8yl17uuAh5XIuby6G2JVz2qwbU7lfP9cZDSVP4WNjUYsL +ZD+tQ7ilHFw0s64AoGPF9n8LWWh4c6aMGKkCba/DGQEuuBDjxsxAtGmjRjNph27E +uxem8+jdrXO8ey8htf1mUQy9VLPhbV8cvCNz0QkDiRTSELlkwyrQoZZKvOHUGlvH +oMDBY3gPRDcwMpaAMiOVoXe6E9KXc+JdJclqDcM5YKS0sGlCQgnp2Ai8MyCzWCKn +quvE4eZhg8XSlt/Z0E+t1wIDAQABo2AwXjAdBgNVHQ4EFgQU4bSgzt4DOPCu9aPI +iNUM8edRoTEwHwYDVR0jBBgwFoAU1STzDLoSGSxlZdsq0JKPmRNWes8wDAYDVR0T +AQH/BAIwADAOBgNVHQ8BAf8EBAMCBaAwDQYJKoZIhvcNAQELBQADggEBAL0W4MsN +3twP4O/NGU0YBCWgQTehvpP7ZdztPFuZcn7Uw9nqjsNGg/De8cwKx3/TNtafaQ9Z +/gElj5PBGsvc4V/Lr2BjW7hGBiB46O2OvAtyeIj82jjst5ZKDU67tvFfBAq1WcTN +biGPglbUBkTPatPnsMmnWtf6aYvJwpvXxPhNeCORGPzNsICw8/erVAUPEpKTg+Bp +yy58dmEjX2ebhVJJx56rpdeNMdQyVbTgrIs0l6vYegSMka9+8Xnmd5UkkIZPotuu +plmWGJ8mv80z+WWaUNwdqSSkBPSOw0nOA9LL8QQZ0azeEXnQRQqOPNGo8tlPEOGt +nP7FN7jo4ACAwCY= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDnjCCAoagAwIBAgIEDOfhBjANBgkqhkiG9w0BAQsFADCBlTELMAkGA1UEBhMC +VVMxEDAOBgNVBAgMB01vbnRhbmExEDAOBgNVBAcMB0JvemVtYW4xETAPBgNVBAoM +CFNhd3Rvb3RoMRMwEQYDVQQLDApDb25zdWx0aW5nMRgwFgYDVQQDDA93d3cud29s +ZnNzbC5jb20xIDAeBgkqhkiG9w0BCQEWEWZhY3RzQHdvbGZzc2wuY29tMB4XDTI1 +MDEwMTAwMDAwMFoXDTQ5MTIzMTIzNTk1OVowJzElMCMGA1UEAwwcQ2VydCBFdmVu +dHMgSW50IE5vIENlcnQgU2lnbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAMOic10hYiDOOnE4p5S724cEHFobnksNPsr4pfcNatwjkCJqK1hjSihqSKjn +cx+iVdhNAjviy2vig8lRj3f93C1dI7cjmn62KWjoKk6p/jJwMZ7w7+74jeP889co +3Xodnq0jK/GmfzRSKWbS5WRVZNbdS0E7VYNuwBEObiDCFnPrMP8JRrvnzMYDREER +xsFsNi9K+ZFVylheN7goEDCJQJZ3z3BmpFX7aQvn2bIzZdtyOne3K0n8ts1YEI2r +qstARXcCORizjzMBSHdQvo5zp942oEmOLBavuftCLTVq2zQ31RRZfWVy5YtlVUsg +Xkf5+DrTbNk69ccBRjHDeZoYvkkCAwEAAaNjMGEwHQYDVR0OBBYEFNUk8wy6Ehks +ZWXbKtCSj5kTVnrPMB8GA1UdIwQYMBaAFCeOZxF0wyYdP+0zY7Ok2B0w5ejVMA8G +A1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgeAMA0GCSqGSIb3DQEBCwUAA4IB +AQBtYp7VKBfbxDzwVvIj0mIRImJxndgvoP5xhieeIzfDubZfbdsLJvZq2XDLlI2p +dwnyCHNdLShq39salj8xeCE34QaiwBGrARqvd6CBOJ1vs4Xb0BNlWuRoQa0cnw9x +oRb61apTJwtzTgFTxeOA/OcTwUaOBYnRLucGU5awy5ecGkUOpbqqd5hA2KBr9/bq +ullwRNzjzwPupuPJc1W6WvfXzi3hxmT487F0XLwhlWTDGqkbM+KfE1vZsVScCs+r +VZmgcCw8sIvMp3U9Y/v/I+vnrrNU52vUlfr+4oyBt2UBoWiNcT03uFtLichAR3lZ +w6fHwSp1UphPOiQH67zF0JKp +-----END CERTIFICATE----- diff --git a/certs/test/cert-events/chain-signer-not-ca.pem b/certs/test/cert-events/chain-signer-not-ca.pem new file mode 100644 index 00000000000..a64e5da76c8 --- /dev/null +++ b/certs/test/cert-events/chain-signer-not-ca.pem @@ -0,0 +1,44 @@ +-----BEGIN CERTIFICATE----- +MIIDkTCCAnmgAwIBAgIEDOfhAzANBgkqhkiG9w0BAQsFADAhMR8wHQYDVQQDDBZD +ZXJ0IEV2ZW50cyBJbnQgTm90IENBMB4XDTI1MDEwMTAwMDAwMFoXDTQ5MTIzMTIz +NTk1OVowgZExCzAJBgNVBAYTAlVTMRAwDgYDVQQIDAdNb250YW5hMRAwDgYDVQQH +DAdCb3plbWFuMRAwDgYDVQQKDAd3b2xmU1NMMRAwDgYDVQQLDAdTdXBwb3J0MRgw +FgYDVQQDDA93d3cud29sZnNzbC5jb20xIDAeBgkqhkiG9w0BCQEWEWZhY3RzQHdv +bGZzc2wuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwJUI4VdB +8nFtt9JFQScBZcZFrvK8JDC4lc4vTtb2HIi8fJ/7qGd//lycUXX3isoH5zUvj+G9 +e8AvfKtkqBf8yl17uuAh5XIuby6G2JVz2qwbU7lfP9cZDSVP4WNjUYsLZD+tQ7il +HFw0s64AoGPF9n8LWWh4c6aMGKkCba/DGQEuuBDjxsxAtGmjRjNph27Euxem8+jd +rXO8ey8htf1mUQy9VLPhbV8cvCNz0QkDiRTSELlkwyrQoZZKvOHUGlvHoMDBY3gP +RDcwMpaAMiOVoXe6E9KXc+JdJclqDcM5YKS0sGlCQgnp2Ai8MyCzWCKnquvE4eZh +g8XSlt/Z0E+t1wIDAQABo2AwXjAdBgNVHQ4EFgQU4bSgzt4DOPCu9aPIiNUM8edR +oTEwHwYDVR0jBBgwFoAU1STzDLoSGSxlZdsq0JKPmRNWes8wDAYDVR0TAQH/BAIw +ADAOBgNVHQ8BAf8EBAMCBaAwDQYJKoZIhvcNAQELBQADggEBAJJMWcibCn6Q7dwN +HoYqGsLcQSO1jvGEcSrHmzL2RSkXnfZ5ffE+r+A4lkyj/lERu0+6qkcby8qn1OYH +EqSHAL8ax0m0UXWBCSGf4iag5Yls4dsTmCKRQlfPa5wW/ZaPivYZkKW2hwzJj+Rk +0kuYo4v1jNDVRj7t94pAqODcLmZ2UhxgEselcAUIh3HjCrpXzdxMmXKH6Q2saxBV +emqsrE1Mk21g89mfNI9oo6NaBSgRR1SMKwxWcce52w6MGotyxSFdv+vPQsvTU6Sb +aZE+efUslUxdXY7rktoNuIAL0miU2ktpXm5Ay7RfooY/Vy093jLEXkjOzfJje283 +bmRUXSs= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDlTCCAn2gAwIBAgIEDOfhBDANBgkqhkiG9w0BAQsFADCBlTELMAkGA1UEBhMC +VVMxEDAOBgNVBAgMB01vbnRhbmExEDAOBgNVBAcMB0JvemVtYW4xETAPBgNVBAoM +CFNhd3Rvb3RoMRMwEQYDVQQLDApDb25zdWx0aW5nMRgwFgYDVQQDDA93d3cud29s +ZnNzbC5jb20xIDAeBgkqhkiG9w0BCQEWEWZhY3RzQHdvbGZzc2wuY29tMB4XDTI1 +MDEwMTAwMDAwMFoXDTQ5MTIzMTIzNTk1OVowITEfMB0GA1UEAwwWQ2VydCBFdmVu +dHMgSW50IE5vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMOi +c10hYiDOOnE4p5S724cEHFobnksNPsr4pfcNatwjkCJqK1hjSihqSKjncx+iVdhN +Ajviy2vig8lRj3f93C1dI7cjmn62KWjoKk6p/jJwMZ7w7+74jeP889co3Xodnq0j +K/GmfzRSKWbS5WRVZNbdS0E7VYNuwBEObiDCFnPrMP8JRrvnzMYDREERxsFsNi9K ++ZFVylheN7goEDCJQJZ3z3BmpFX7aQvn2bIzZdtyOne3K0n8ts1YEI2rqstARXcC +ORizjzMBSHdQvo5zp942oEmOLBavuftCLTVq2zQ31RRZfWVy5YtlVUsgXkf5+DrT +bNk69ccBRjHDeZoYvkkCAwEAAaNgMF4wHQYDVR0OBBYEFNUk8wy6EhksZWXbKtCS +j5kTVnrPMB8GA1UdIwQYMBaAFCeOZxF0wyYdP+0zY7Ok2B0w5ejVMAwGA1UdEwEB +/wQCMAAwDgYDVR0PAQH/BAQDAgWgMA0GCSqGSIb3DQEBCwUAA4IBAQAxkqgrxPAW ++FbOyF8YzlRz9C8uT8P3yx38sl9sSZm9+P3Egd1bEHkCmWmrHZHQYG6IZuR2YEnd +Ibz8BhURDIf2pZKbJepF5rVlh5GVwvpoCBIzu7ZvK9MoaiQbYufk92OUkFqXcpvF +vMZK73o+IY3bidT92KUXZxTO9NnOAYfV04NAX9+BgkN6uCHzf+GeayrS/yv6JmO4 ++JITj33HwJzye8k1CIsziBz82nlGc+58n+4eI5myaMjdsNCr1JWE7ulQmm+iqOo0 +HvqUV6KXDLy2x9bTtCnBDfRWEFrSPfJAxMfCBX/S9QmtZO4cfxnkIy75lR1TPnw2 +icULR9nrcurI +-----END CERTIFICATE----- diff --git a/certs/test/cert-events/crl-bad-signature.der b/certs/test/cert-events/crl-bad-signature.der new file mode 100644 index 00000000000..ca94d2ade85 Binary files /dev/null and b/certs/test/cert-events/crl-bad-signature.der differ diff --git a/certs/test/cert-events/crl-expired.der b/certs/test/cert-events/crl-expired.der new file mode 100644 index 00000000000..b26307a7ccd Binary files /dev/null and b/certs/test/cert-events/crl-expired.der differ diff --git a/certs/test/cert-events/crl-good.der b/certs/test/cert-events/crl-good.der new file mode 100644 index 00000000000..c3f64991b83 Binary files /dev/null and b/certs/test/cert-events/crl-good.der differ diff --git a/certs/test/cert-events/crl-malformed.der b/certs/test/cert-events/crl-malformed.der new file mode 100644 index 00000000000..ef3b80223e6 Binary files /dev/null and b/certs/test/cert-events/crl-malformed.der differ diff --git a/certs/test/cert-events/crl-revokes-good.der b/certs/test/cert-events/crl-revokes-good.der new file mode 100644 index 00000000000..c3aa2f68c0e Binary files /dev/null and b/certs/test/cert-events/crl-revokes-good.der differ diff --git a/certs/test/cert-events/crl-signer-no-crlsign.der b/certs/test/cert-events/crl-signer-no-crlsign.der new file mode 100644 index 00000000000..c1cd4426ae2 Binary files /dev/null and b/certs/test/cert-events/crl-signer-no-crlsign.der differ diff --git a/certs/test/cert-events/crl-this-update-future.der b/certs/test/cert-events/crl-this-update-future.der new file mode 100644 index 00000000000..bc15409345f Binary files /dev/null and b/certs/test/cert-events/crl-this-update-future.der differ diff --git a/certs/test/cert-events/crl-unknown-critical.der b/certs/test/cert-events/crl-unknown-critical.der new file mode 100644 index 00000000000..830b77189bd Binary files /dev/null and b/certs/test/cert-events/crl-unknown-critical.der differ diff --git a/certs/test/cert-events/crl-unknown-issuer.der b/certs/test/cert-events/crl-unknown-issuer.der new file mode 100644 index 00000000000..b0e11cc14c7 Binary files /dev/null and b/certs/test/cert-events/crl-unknown-issuer.der differ diff --git a/certs/test/cert-events/crldp-malformed.der b/certs/test/cert-events/crldp-malformed.der new file mode 100644 index 00000000000..5180bfc7771 Binary files /dev/null and b/certs/test/cert-events/crldp-malformed.der differ diff --git a/certs/test/cert-events/eku-malformed.der b/certs/test/cert-events/eku-malformed.der new file mode 100644 index 00000000000..ee2ec936e89 Binary files /dev/null and b/certs/test/cert-events/eku-malformed.der differ diff --git a/certs/test/cert-events/good.der b/certs/test/cert-events/good.der new file mode 100644 index 00000000000..226c5f2b072 Binary files /dev/null and b/certs/test/cert-events/good.der differ diff --git a/certs/test/cert-events/issuer-malformed.der b/certs/test/cert-events/issuer-malformed.der new file mode 100644 index 00000000000..60d54635202 Binary files /dev/null and b/certs/test/cert-events/issuer-malformed.der differ diff --git a/certs/test/cert-events/issuer-uid.der b/certs/test/cert-events/issuer-uid.der new file mode 100644 index 00000000000..48b09db0043 Binary files /dev/null and b/certs/test/cert-events/issuer-uid.der differ diff --git a/certs/test/cert-events/ku-malformed.der b/certs/test/cert-events/ku-malformed.der new file mode 100644 index 00000000000..91ebd0fe334 Binary files /dev/null and b/certs/test/cert-events/ku-malformed.der differ diff --git a/certs/test/cert-events/leaf-bad-signature.der b/certs/test/cert-events/leaf-bad-signature.der new file mode 100644 index 00000000000..6eef306278d Binary files /dev/null and b/certs/test/cert-events/leaf-bad-signature.der differ diff --git a/certs/test/cert-events/leaf-unknown-issuer.der b/certs/test/cert-events/leaf-unknown-issuer.der new file mode 100644 index 00000000000..73f0ec66bc6 Binary files /dev/null and b/certs/test/cert-events/leaf-unknown-issuer.der differ diff --git a/certs/test/cert-events/policy-malformed.der b/certs/test/cert-events/policy-malformed.der new file mode 100644 index 00000000000..b127c188f23 Binary files /dev/null and b/certs/test/cert-events/policy-malformed.der differ diff --git a/certs/test/cert-events/san-malformed.der b/certs/test/cert-events/san-malformed.der new file mode 100644 index 00000000000..f85223ff83c Binary files /dev/null and b/certs/test/cert-events/san-malformed.der differ diff --git a/certs/test/cert-events/serial-empty.der b/certs/test/cert-events/serial-empty.der new file mode 100644 index 00000000000..1cd5ecf19e0 Binary files /dev/null and b/certs/test/cert-events/serial-empty.der differ diff --git a/certs/test/cert-events/serial-not-integer.der b/certs/test/cert-events/serial-not-integer.der new file mode 100644 index 00000000000..cc93681dedc Binary files /dev/null and b/certs/test/cert-events/serial-not-integer.der differ diff --git a/certs/test/cert-events/sigalg-mismatch.der b/certs/test/cert-events/sigalg-mismatch.der new file mode 100644 index 00000000000..d3e67377a22 Binary files /dev/null and b/certs/test/cert-events/sigalg-mismatch.der differ diff --git a/certs/test/cert-events/signature-not-bitstring.der b/certs/test/cert-events/signature-not-bitstring.der new file mode 100644 index 00000000000..35c082225fa Binary files /dev/null and b/certs/test/cert-events/signature-not-bitstring.der differ diff --git a/certs/test/cert-events/skid-malformed.der b/certs/test/cert-events/skid-malformed.der new file mode 100644 index 00000000000..1c9ee95e2c7 Binary files /dev/null and b/certs/test/cert-events/skid-malformed.der differ diff --git a/certs/test/cert-events/spki-bad-bitstring.der b/certs/test/cert-events/spki-bad-bitstring.der new file mode 100644 index 00000000000..aff7ad4419c Binary files /dev/null and b/certs/test/cert-events/spki-bad-bitstring.der differ diff --git a/certs/test/cert-events/spki-unknown-alg.der b/certs/test/cert-events/spki-unknown-alg.der new file mode 100644 index 00000000000..201e57f981d Binary files /dev/null and b/certs/test/cert-events/spki-unknown-alg.der differ diff --git a/certs/test/cert-events/strict-ca-no-ku.der b/certs/test/cert-events/strict-ca-no-ku.der new file mode 100644 index 00000000000..af0fc483d0c Binary files /dev/null and b/certs/test/cert-events/strict-ca-no-ku.der differ diff --git a/certs/test/cert-events/strict-good.der b/certs/test/cert-events/strict-good.der new file mode 100644 index 00000000000..befe99f0381 Binary files /dev/null and b/certs/test/cert-events/strict-good.der differ diff --git a/certs/test/cert-events/strict-no-akid.der b/certs/test/cert-events/strict-no-akid.der new file mode 100644 index 00000000000..0e9ee288476 Binary files /dev/null and b/certs/test/cert-events/strict-no-akid.der differ diff --git a/certs/test/cert-events/strict-no-crldp.der b/certs/test/cert-events/strict-no-crldp.der new file mode 100644 index 00000000000..d5510900008 Binary files /dev/null and b/certs/test/cert-events/strict-no-crldp.der differ diff --git a/certs/test/cert-events/strict-no-ocsp.der b/certs/test/cert-events/strict-no-ocsp.der new file mode 100644 index 00000000000..7bd2db5b625 Binary files /dev/null and b/certs/test/cert-events/strict-no-ocsp.der differ diff --git a/certs/test/cert-events/subject-malformed.der b/certs/test/cert-events/subject-malformed.der new file mode 100644 index 00000000000..8c9a94f6140 Binary files /dev/null and b/certs/test/cert-events/subject-malformed.der differ diff --git a/certs/test/cert-events/subject-uid.der b/certs/test/cert-events/subject-uid.der new file mode 100644 index 00000000000..3d027a4d777 Binary files /dev/null and b/certs/test/cert-events/subject-uid.der differ diff --git a/certs/test/cert-events/tbs-sigalg-unknown.der b/certs/test/cert-events/tbs-sigalg-unknown.der new file mode 100644 index 00000000000..d8f95146d2b Binary files /dev/null and b/certs/test/cert-events/tbs-sigalg-unknown.der differ diff --git a/certs/test/cert-events/trailing-data.der b/certs/test/cert-events/trailing-data.der new file mode 100644 index 00000000000..3de0e2f4ff9 Binary files /dev/null and b/certs/test/cert-events/trailing-data.der differ diff --git a/certs/test/cert-events/unknown-critical.der b/certs/test/cert-events/unknown-critical.der new file mode 100644 index 00000000000..b705fbef21a Binary files /dev/null and b/certs/test/cert-events/unknown-critical.der differ diff --git a/certs/test/cert-events/validity-not-time.der b/certs/test/cert-events/validity-not-time.der new file mode 100644 index 00000000000..126a05bdcd0 Binary files /dev/null and b/certs/test/cert-events/validity-not-time.der differ diff --git a/certs/test/cert-events/version-not-integer.der b/certs/test/cert-events/version-not-integer.der new file mode 100644 index 00000000000..c236e5030ae Binary files /dev/null and b/certs/test/cert-events/version-not-integer.der differ diff --git a/certs/test/cert-events/version-unsupported.der b/certs/test/cert-events/version-unsupported.der new file mode 100644 index 00000000000..dac17cf3912 Binary files /dev/null and b/certs/test/cert-events/version-unsupported.der differ diff --git a/certs/test/gen-cert-events.py b/certs/test/gen-cert-events.py new file mode 100644 index 00000000000..a5294f49fe5 --- /dev/null +++ b/certs/test/gen-cert-events.py @@ -0,0 +1,588 @@ +#!/usr/bin/env python3 +""" + Generates certs/test/cert-events/*.der, the certificates used by the + certificate validation event tests in tests/api/test_certman.c. + + Each certificate is a copy of certs/server-cert.der (same subject and + public key) with exactly one defect, signed again with certs/ca-key.pem. + Signing again matters: verification then reaches the defect instead of + stopping at a bad signature. good.der has no defect and must verify. + + Defects in an extension are placed inside the extension's OCTET STRING, + so the certificate as a whole is still valid DER. Defects in other fields + break the encoding of that field only. + + The validity period runs to 2049, so the certificates do not need to be + renewed with the other test certificates. The CA must still be the one in + certs/ca-cert.pem: run this again if that CA's key changes. + + The crl-*.der files are CRLs, each with one fault except crl-good.der. + crl-revokes-good.der revokes good.der. crl-signer-no-crlsign.der is signed + by ca-no-crlsign.der, a CA whose keyUsage lacks cRLSign. + + The strict-*.der files test the strict profile checks. strict-good.der + passes all of them: it has an AKID, a CRL distribution point and an OCSP + responder. Each other one lacks one of those, or is a CA without a + keyUsage. crl-this-update-future.der has a thisUpdate in 2049. + + The chain-*.pem files are certificate chains for a TLS server using + certs/server-key.pem: the server's certificate first, then the CAs it + sends. Their CAs use the keys in certs/intermediate/ and lead up to + certs/ca-cert.pem. + + Requires: Python 3 and an openssl command line tool (no Python packages). + Usage: python3 ./certs/test/gen-cert-events.py + (run from the top of the wolfSSL source tree) +""" +import base64 +import hashlib +import os +import subprocess +import tempfile + +SRC_CERT = './certs/server-cert.der' +CA_CERT = './certs/ca-cert.der' +CA_KEY = './certs/ca-key.pem' +SERVER_KEY = './certs/server-key.pem' +INT_KEY = './certs/intermediate/ca-int-key.pem' +INT2_KEY = './certs/intermediate/ca-int2-key.pem' +OUT_DIR = './certs/test/cert-events' + +# UTCTime, YYMMDDHHMMSSZ. 49 maps to 2049 per RFC 5280 4.1.2.5.1. +NOT_BEFORE = '250101000000Z' +NOT_AFTER = '491231235959Z' +FIRST_SERIAL = 0x0CE7E000 + +OID_SHA256_RSA = '1.2.840.113549.1.1.11' +OID_SKID = '2.5.29.14' +OID_KEY_USAGE = '2.5.29.15' +OID_ALT_NAMES = '2.5.29.17' +OID_BASIC_CA = '2.5.29.19' +OID_CRL_DIST = '2.5.29.31' +OID_CERT_POLICY = '2.5.29.32' +OID_AKID = '2.5.29.35' +OID_EXT_KEY_USAGE = '2.5.29.37' +OID_AUTH_INFO = '1.3.6.1.5.5.7.1.1' +OID_UNKNOWN = '1.3.6.1.4.1.99999.1' +OID_SHA384_RSA = '1.2.840.113549.1.1.12' +OID_COMMON_NAME = '2.5.4.3' +OID_CLIENT_AUTH = '1.3.6.1.5.5.7.3.2' +OID_CRL_NUMBER = '2.5.29.20' +OID_AD_OCSP = '1.3.6.1.5.5.7.48.1' +OID_AD_CA_ISSUERS = '1.3.6.1.5.5.7.48.2' + +# Inside an extension value: a SEQUENCE whose first element claims more bytes +# than there are. The outer certificate stays valid DER. +TRUNCATED_INNER = bytes([0x30, 0x03, 0x30, 0x05, 0x00]) + + +def tlv(tag: int, value: bytes) -> bytes: + """Encode one DER tag, length and value.""" + n = len(value) + if n < 0x80: + length = bytes([n]) + else: + enc = n.to_bytes((n.bit_length() + 7) // 8, 'big') + length = bytes([0x80 | len(enc)]) + enc + return bytes([tag]) + length + value + + +def der_sequence(*items: bytes) -> bytes: + return tlv(0x30, b''.join(items)) + + +def der_oid(dotted: str) -> bytes: + arcs = [int(a) for a in dotted.split('.')] + body = bytes([arcs[0] * 40 + arcs[1]]) + for arc in arcs[2:]: + enc = [arc & 0x7f] + arc >>= 7 + while arc: + enc.insert(0, 0x80 | (arc & 0x7f)) + arc >>= 7 + body += bytes(enc) + return tlv(0x06, body) + + +def der_integer(value: int) -> bytes: + body = value.to_bytes((value.bit_length() + 8) // 8, 'big') + return tlv(0x02, body) + + +def der_octet_string(value: bytes) -> bytes: + return tlv(0x04, value) + + +def read_tlv(data: bytes, idx: int): + """Return (tag, header length, value length) of the TLV at idx.""" + tag = data[idx] + n = data[idx + 1] + if n < 0x80: + return tag, 2, n + cnt = n & 0x7f + return tag, 2 + cnt, int.from_bytes(data[idx + 2:idx + 2 + cnt], 'big') + + +def children(data: bytes) -> list: + """Split the value of a constructed TLV into its encoded children.""" + out = [] + idx = 0 + while idx < len(data): + _, hdr, n = read_tlv(data, idx) + out.append(data[idx:idx + hdr + n]) + idx += hdr + n + return out + + +def value_of(encoded: bytes) -> bytes: + _, hdr, n = read_tlv(encoded, 0) + return encoded[hdr:hdr + n] + + +def extension(oid: str, value: bytes, critical: bool = False) -> bytes: + crit = tlv(0x01, b'\xff') if critical else b'' + return der_sequence(der_oid(oid), crit, der_octet_string(value)) + + +def load_parts(): + """Subject, issuer and key come from the existing test certificates.""" + with open(SRC_CERT, 'rb') as f: + tbs = children(value_of(f.read()))[0] + fields = children(value_of(tbs)) + # version, serial, signature, issuer, validity, subject, spki, ... + subject, spki = fields[5], fields[6] + with open(CA_CERT, 'rb') as f: + ca_tbs = children(value_of(f.read()))[0] + ca_fields = children(value_of(ca_tbs)) + issuer = ca_fields[5] + ca_skid = None + for f_ in ca_fields: + if f_[0] == 0xa3: + for ext in children(value_of(children(value_of(f_))[0])): + parts = children(value_of(ext)) + if parts[0] == der_oid(OID_SKID): + ca_skid = value_of(value_of(parts[-1])) + return subject, spki, issuer, ca_skid + + +def base_extensions(ca_skid: bytes) -> dict: + """The extensions of good.der, by name. Each defect replaces one.""" + return { + 'skid': extension(OID_SKID, der_octet_string(bytes(range(1, 21)))), + 'akid': extension(OID_AKID, der_sequence(tlv(0x80, ca_skid))), + 'bc': extension(OID_BASIC_CA, der_sequence(), critical=True), + # digitalSignature and keyEncipherment + 'ku': extension(OID_KEY_USAGE, tlv(0x03, b'\x05\xa0'), critical=True), + } + + +def bad_name(parts): + """A Name whose RDN holds an INTEGER where a SET is expected.""" + return der_sequence(der_integer(5)) + + +def unknown_key_spki(parts): + """The real key under an unknown algorithm OID.""" + spki = children(value_of(parts[1])) + return der_sequence(der_sequence(der_oid(OID_UNKNOWN), tlv(0x05, b'')), + spki[1]) + + +def bad_bitstring_spki(parts): + """A subjectPublicKey BIT STRING claiming 9 unused bits.""" + spki = children(value_of(parts[1])) + key = value_of(spki[1]) + return der_sequence(spki[0], tlv(0x03, b'\x09' + key[1:])) + + +# Defects in fields other than extensions: field name -> replacement, or a +# function of the parts read from the existing certificates. +FIELD_CASES = { + 'version-unsupported': ('version is v6, which does not exist', + {'version': tlv(0xa0, der_integer(5))}), + 'version-not-integer': ('version is an OCTET STRING', + {'version': tlv(0xa0, der_octet_string(b'\x02'))}), + 'serial-empty': ('serialNumber is an INTEGER with no content', + {'serial': tlv(0x02, b'')}), + 'serial-not-integer': ('serialNumber is an OCTET STRING', + {'serial': der_octet_string(b'\x01\x02')}), + 'tbs-sigalg-unknown': ('signature algorithm in the TBS is unknown', + {'tbs_sig_alg': der_sequence(der_oid(OID_UNKNOWN), + tlv(0x05, b''))}), + 'issuer-malformed': ('issuer Name holds an INTEGER, not a SET', + {'issuer': bad_name}), + 'validity-not-time': ('notBefore is an OCTET STRING, not a time', + {'validity': der_sequence( + der_octet_string(b'250101000000Z'), + tlv(0x17, NOT_AFTER.encode()))}), + 'subject-malformed': ('subject Name holds an INTEGER, not a SET', + {'subject': bad_name}), + 'spki-unknown-alg': ('public key algorithm is unknown', + {'spki': unknown_key_spki}), + 'spki-bad-bitstring': ('public key BIT STRING claims 9 unused bits', + {'spki': bad_bitstring_spki}), + 'sigalg-mismatch': ('outer signature algorithm differs from the TBS one', + {'outer_sig_alg': der_sequence( + der_oid(OID_SHA384_RSA), tlv(0x05, b''))}), + 'signature-not-bitstring': ('signatureValue is an OCTET STRING', + {'sig_tag': 0x04}), + 'trailing-data': ('two bytes after the certificate', + {'trailing': b'\x00\x00'}), +} + +# name: (description, extensions to replace or add, unique IDs to add) +CASES = { + 'good': ('no defect: must verify', {}, b''), + 'ku-malformed': ('keyUsage is an OCTET STRING, not a BIT STRING', + {'ku': extension(OID_KEY_USAGE, der_octet_string(b'\xa0'), + critical=True)}, b''), + 'bc-malformed': ('basicConstraints cA is a 2-byte BOOLEAN', + {'bc': extension(OID_BASIC_CA, + der_sequence(tlv(0x01, b'\xff\xff')), + critical=True)}, b''), + 'akid-malformed': ('authorityKeyIdentifier is not a SEQUENCE', + {'akid': extension(OID_AKID, + der_octet_string(b'\x00\x00'))}, b''), + 'skid-malformed': ('subjectKeyIdentifier is not an OCTET STRING', + {'skid': extension(OID_SKID, der_sequence())}, b''), + 'eku-malformed': ('extendedKeyUsage holds an INTEGER, not an OID', + {'eku': extension(OID_EXT_KEY_USAGE, + der_sequence(der_integer(1)))}, b''), + 'san-malformed': ('subjectAltName entry claims more bytes than it has', + {'san': extension(OID_ALT_NAMES, + bytes([0x30, 0x03, 0x82, 0x05, + 0x61]))}, b''), + 'crldp-malformed': ('cRLDistributionPoints is truncated inside', + {'crldp': extension(OID_CRL_DIST, TRUNCATED_INNER)}, + b''), + 'aia-malformed': ('authorityInfoAccess is truncated inside', + {'aia': extension(OID_AUTH_INFO, TRUNCATED_INNER)}, b''), + 'policy-malformed': ('certificatePolicies is truncated inside', + {'policy': extension(OID_CERT_POLICY, + TRUNCATED_INNER)}, b''), + 'unknown-critical': ('an unknown extension marked critical', + {'unknown': extension(OID_UNKNOWN, b'\x05\x00', + critical=True)}, b''), + 'issuer-uid': ('issuerUniqueID present', + {}, tlv(0x81, b'\x00\xab\xcd')), + 'subject-uid': ('subjectUniqueID present', + {}, tlv(0x82, b'\x00\xab\xcd')), +} + + +def sign(tbs: bytes, key: str = CA_KEY) -> bytes: + with tempfile.TemporaryDirectory() as tmp: + tbs_file = os.path.join(tmp, 'tbs.der') + sig_file = os.path.join(tmp, 'sig.bin') + with open(tbs_file, 'wb') as f: + f.write(tbs) + subprocess.run(['openssl', 'dgst', '-sha256', '-sign', key, + '-out', sig_file, tbs_file], check=True) + with open(sig_file, 'rb') as f: + return f.read() + + +def public_key(key: str) -> bytes: + """SubjectPublicKeyInfo of an RSA private key file.""" + out = subprocess.run(['openssl', 'rsa', '-in', key, '-pubout', + '-outform', 'DER'], check=True, + capture_output=True) + return out.stdout + + +def name(common_name: str) -> bytes: + atv = der_sequence(der_oid(OID_COMMON_NAME), + tlv(0x0c, common_name.encode())) + return der_sequence(tlv(0x31, atv)) + + +def chain_cert(serial: int, subject: bytes, spki: bytes, issuer: bytes, + issuer_skid: bytes, key: str, ca: bool = False, + path_len: int = None, key_usage: bytes = None, + eku: list = None) -> bytes: + """A certificate for a chain, with the extensions a CA or leaf needs.""" + sig_alg = der_sequence(der_oid(OID_SHA256_RSA), tlv(0x05, b'')) + validity = der_sequence(tlv(0x17, NOT_BEFORE.encode()), + tlv(0x17, NOT_AFTER.encode())) + bc = tlv(0x01, b'\xff') if ca else b'' + if path_len is not None: + bc += der_integer(path_len) + if key_usage is None: + # keyCertSign and cRLSign for a CA; else digitalSignature and + # keyEncipherment + key_usage = b'\x01\x06' if ca else b'\x05\xa0' + exts = [ + extension(OID_SKID, der_octet_string(skid_of(spki))), + extension(OID_AKID, der_sequence(tlv(0x80, issuer_skid))), + extension(OID_BASIC_CA, der_sequence(bc), critical=True), + extension(OID_KEY_USAGE, tlv(0x03, key_usage), critical=True), + ] + if eku is not None: + exts.append(extension(OID_EXT_KEY_USAGE, + der_sequence(*[der_oid(o) for o in eku]))) + tbs = der_sequence(tlv(0xa0, der_integer(2)), der_integer(serial), + sig_alg, issuer, validity, subject, spki, + tlv(0xa3, der_sequence(*exts))) + return der_sequence(tbs, sig_alg, tlv(0x03, b'\x00' + sign(tbs, key))) + + +def skid_of(spki: bytes) -> bytes: + return hashlib.sha1(spki).digest() + + +def pem(der: bytes) -> str: + b64 = base64.b64encode(der).decode() + lines = [b64[i:i + 64] for i in range(0, len(b64), 64)] + return ('-----BEGIN CERTIFICATE-----\n' + '\n'.join(lines) + + '\n-----END CERTIFICATE-----\n') + + +def build_chains(parts, serial: int): + """Chains for a TLS server, and leaves that fail against their signer.""" + subject, spki, root, root_skid = parts + int_spki = public_key(INT_KEY) + int2_spki = public_key(INT2_KEY) + out = {} + + def next_serial(): + nonlocal serial + serial += 1 + return serial + + def ca_under_root(cn, **kw): + return chain_cert(next_serial(), name(cn), int_spki, root, + root_skid, CA_KEY, **kw) + + def leaf_under(cn, **kw): + return chain_cert(next_serial(), subject, spki, name(cn), + skid_of(int_spki), INT_KEY, **kw) + + # Good: server, then a CA signed by the root. + cn = 'Cert Events Int CA' + out['chain-good'] = ('a good chain', + [leaf_under(cn), ca_under_root(cn, ca=True)]) + # The server's signer is not a CA. + cn = 'Cert Events Int Not CA' + out['chain-signer-not-ca'] = ('the signer is not a CA', + [leaf_under(cn), ca_under_root(cn)]) + # The signer is a CA that may not sign certificates. + cn = 'Cert Events Int No Cert Sign' + out['chain-signer-no-certsign'] = ( + 'the signer has keyUsage without keyCertSign', + [leaf_under(cn), ca_under_root(cn, ca=True, key_usage=b'\x07\x80')]) + # A CA with pathLenConstraint 0 signs another CA. + cn = 'Cert Events Int Path Len 0' + cn2 = 'Cert Events Int Below Path Len 0' + int2 = chain_cert(next_serial(), name(cn2), int2_spki, name(cn), + skid_of(int_spki), INT_KEY, ca=True) + leaf = chain_cert(next_serial(), subject, spki, name(cn2), + skid_of(int2_spki), INT2_KEY) + out['chain-pathlen-exceeded'] = ( + 'a CA with pathLenConstraint 0 signs another CA', + [leaf, int2, ca_under_root(cn, ca=True, path_len=0)]) + + singles = { + # Claims the root as issuer, but signed with another key. + 'leaf-bad-signature': ('signed with a key other than the issuer\'s', + chain_cert(next_serial(), subject, spki, root, + root_skid, SERVER_KEY)), + 'leaf-unknown-issuer': ('issued by a CA nobody trusts', + chain_cert(next_serial(), subject, spki, + name('Cert Events Unknown CA'), + bytes(20), CA_KEY)), + } + + # Added later, so the serials above stay the same. The server's own + # certificate may not be used for this TLS connection. + cn = 'Cert Events Int CA' + out['chain-leaf-ku-no-digsig'] = ( + 'the server\'s keyUsage has keyEncipherment only', + [leaf_under(cn, key_usage=b'\x05\x20'), ca_under_root(cn, ca=True)]) + out['chain-leaf-eku-client-only'] = ( + 'the server\'s extendedKeyUsage allows clientAuth only', + [leaf_under(cn, eku=[OID_CLIENT_AUTH]), ca_under_root(cn, ca=True)]) + + return out, singles + + +def build(serial: int, exts: dict, uids: bytes, parts, + fields: dict = None) -> bytes: + subject, spki, issuer, _ = parts + sig_alg = der_sequence(der_oid(OID_SHA256_RSA), tlv(0x05, b'')) + f = { + 'version': tlv(0xa0, der_integer(2)), + 'serial': der_integer(serial), + 'tbs_sig_alg': sig_alg, + 'issuer': issuer, + 'validity': der_sequence(tlv(0x17, NOT_BEFORE.encode()), + tlv(0x17, NOT_AFTER.encode())), + 'subject': subject, + 'spki': spki, + 'outer_sig_alg': sig_alg, + 'sig_tag': 0x03, + 'trailing': b'', + } + for name, value in (fields or {}).items(): + f[name] = value(parts) if callable(value) else value + ext_block = tlv(0xa3, der_sequence(*exts.values())) + tbs = der_sequence(f['version'], f['serial'], f['tbs_sig_alg'], + f['issuer'], f['validity'], f['subject'], f['spki'], + uids, ext_block) + signature = tlv(f['sig_tag'], b'\x00' + sign(tbs)) + return der_sequence(tbs, f['outer_sig_alg'], signature) + f['trailing'] + + +def build_crl(issuer: bytes, issuer_skid: bytes, key: str, revoked: list, + next_update: str = NOT_AFTER, extra: bytes = b'', + entry_serial: bytes = None, + this_update: str = NOT_BEFORE) -> bytes: + """A version 2 CRL with an AKID and a CRL number.""" + sig_alg = der_sequence(der_oid(OID_SHA256_RSA), tlv(0x05, b'')) + entries = [der_sequence(entry_serial or der_integer(sn), + tlv(0x17, NOT_BEFORE.encode())) + for sn in revoked] + exts = [ + extension(OID_AKID, der_sequence(tlv(0x80, issuer_skid))), + extension(OID_CRL_NUMBER, der_integer(1)), + ] + tbs = der_sequence(der_integer(1), sig_alg, issuer, + tlv(0x17, this_update.encode()), + tlv(0x17, next_update.encode()), + der_sequence(*entries) if entries else b'', + tlv(0xa0, der_sequence(*exts, extra))) + return der_sequence(tbs, sig_alg, tlv(0x03, b'\x00' + sign(tbs, key))) + + +def build_crls(parts, serial: int): + """CRLs issued by certs/ca-cert.pem, and a CA that may not sign CRLs.""" + _, _, root, root_skid = parts + int_spki = public_key(INT_KEY) + good_serial = FIRST_SERIAL # good.der + out = { + 'crl-good': ('a good CRL that revokes nothing', + build_crl(root, root_skid, CA_KEY, [])), + 'crl-revokes-good': ('revokes good.der', + build_crl(root, root_skid, CA_KEY, + [good_serial])), + 'crl-expired': ('nextUpdate is in 2025', + build_crl(root, root_skid, CA_KEY, [], + next_update='250601000000Z')), + 'crl-bad-signature': ('signed with a key other than the issuer\'s', + build_crl(root, root_skid, SERVER_KEY, [])), + 'crl-unknown-issuer': ('issued by a CA nobody trusts', + build_crl(name('Cert Events Unknown CA'), + bytes(20), CA_KEY, [])), + 'crl-malformed': ('a revoked entry\'s serial is an OCTET STRING', + build_crl(root, root_skid, CA_KEY, [1], + entry_serial=der_octet_string(b'\x01'))), + 'crl-unknown-critical': ('an unknown CRL extension marked critical', + build_crl(root, root_skid, CA_KEY, [], + extra=extension(OID_UNKNOWN, + b'\x05\x00', + critical=True))), + } + # A CA whose keyUsage has keyCertSign but not cRLSign, and a CRL it + # signed. + cn = 'Cert Events Int No CRL Sign' + out['ca-no-crlsign'] = ( + 'a CA that may not sign CRLs', + chain_cert(serial, name(cn), int_spki, root, root_skid, CA_KEY, + ca=True, key_usage=b'\x02\x04')) + out['crl-signer-no-crlsign'] = ( + 'signed by ca-no-crlsign.der', + build_crl(name(cn), skid_of(int_spki), INT_KEY, [])) + return out + + +def crl_dp(uri: str) -> bytes: + """cRLDistributionPoints with one fullName URI.""" + return extension(OID_CRL_DIST, der_sequence(der_sequence( + tlv(0xa0, tlv(0xa0, tlv(0x86, uri.encode())))))) + + +def aia(*access: tuple) -> bytes: + """authorityInfoAccess with (method OID, URI) pairs.""" + return extension(OID_AUTH_INFO, der_sequence( + *[der_sequence(der_oid(m), tlv(0x86, u.encode())) + for m, u in access])) + + +def build_strict(parts, serial: int): + """Certificates and a CRL for the strict profile checks.""" + _, _, root, root_skid = parts + full = base_extensions(root_skid) + full['crldp'] = crl_dp('http://crl.example.com/ca.crl') + full['aia'] = aia((OID_AD_OCSP, 'http://ocsp.example.com')) + no_ocsp = dict(full) + no_ocsp['aia'] = aia((OID_AD_CA_ISSUERS, 'http://ca.example.com/ca.der')) + ca_no_ku = dict(full) + del ca_no_ku['ku'] + ca_no_ku['bc'] = extension(OID_BASIC_CA, + der_sequence(tlv(0x01, b'\xff')), + critical=True) + cases = [ + ('strict-good', 'passes every strict check', full), + ('strict-no-akid', 'no authorityKeyIdentifier', + {k: v for k, v in full.items() if k != 'akid'}), + ('strict-no-crldp', 'no cRLDistributionPoints', + {k: v for k, v in full.items() if k != 'crldp'}), + ('strict-no-ocsp', 'authorityInfoAccess without an OCSP responder', + no_ocsp), + ('strict-ca-no-ku', 'a CA without keyUsage', ca_no_ku), + ] + out = {} + for i, (name_, desc, exts) in enumerate(cases): + out[name_] = (desc, build(serial + i, exts, b'', parts)) + out['crl-this-update-future'] = ( + 'thisUpdate is in 2049', + build_crl(root, root_skid, CA_KEY, [], this_update='490101000000Z')) + return out + + +def main(): + parts = load_parts() + if parts[3] is None: + raise SystemExit('CA certificate has no subjectKeyIdentifier') + os.makedirs(OUT_DIR, exist_ok=True) + jobs = [(name, desc, changes, uids, None) + for name, (desc, changes, uids) in CASES.items()] + jobs += [(name, desc, {}, b'', fields) + for name, (desc, fields) in FIELD_CASES.items()] + for i, (name, desc, changes, uids, fields) in enumerate(jobs): + exts = base_extensions(parts[3]) + exts.update(changes) + der = build(FIRST_SERIAL + i, exts, uids, parts, fields) + path = os.path.join(OUT_DIR, name + '.der') + with open(path, 'wb') as f: + f.write(der) + print('Created: %s (%s)' % (path, desc)) + + chains, singles = build_chains(parts, FIRST_SERIAL + 0x100) + for name_, (desc, certs) in chains.items(): + path = os.path.join(OUT_DIR, name_ + '.pem') + with open(path, 'w') as f: + f.write(''.join(pem(c) for c in certs)) + print('Created: %s (%s)' % (path, desc)) + for name_, (desc, der) in singles.items(): + path = os.path.join(OUT_DIR, name_ + '.der') + with open(path, 'wb') as f: + f.write(der) + print('Created: %s (%s)' % (path, desc)) + + for name_, (desc, der) in build_crls(parts, + FIRST_SERIAL + 0x200).items(): + path = os.path.join(OUT_DIR, name_ + '.der') + with open(path, 'wb') as f: + f.write(der) + print('Created: %s (%s)' % (path, desc)) + + for name_, (desc, der) in build_strict(parts, + FIRST_SERIAL + 0x300).items(): + path = os.path.join(OUT_DIR, name_ + '.der') + with open(path, 'wb') as f: + f.write(der) + print('Created: %s (%s)' % (path, desc)) + + +if __name__ == '__main__': + main() diff --git a/certs/test/include.am b/certs/test/include.am index 1455b2b9b30..2c11706d28b 100644 --- a/certs/test/include.am +++ b/certs/test/include.am @@ -121,3 +121,55 @@ 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/gen-cert-events.py \ + certs/test/cert-events/aia-malformed.der \ + certs/test/cert-events/akid-malformed.der \ + certs/test/cert-events/bc-malformed.der \ + certs/test/cert-events/ca-no-crlsign.der \ + certs/test/cert-events/chain-good.pem \ + certs/test/cert-events/chain-leaf-eku-client-only.pem \ + certs/test/cert-events/chain-leaf-ku-no-digsig.pem \ + certs/test/cert-events/chain-pathlen-exceeded.pem \ + certs/test/cert-events/chain-signer-no-certsign.pem \ + certs/test/cert-events/chain-signer-not-ca.pem \ + certs/test/cert-events/crl-bad-signature.der \ + certs/test/cert-events/crl-expired.der \ + certs/test/cert-events/crl-good.der \ + certs/test/cert-events/crl-malformed.der \ + certs/test/cert-events/crl-revokes-good.der \ + certs/test/cert-events/crl-signer-no-crlsign.der \ + certs/test/cert-events/crl-this-update-future.der \ + certs/test/cert-events/crl-unknown-critical.der \ + certs/test/cert-events/crl-unknown-issuer.der \ + certs/test/cert-events/crldp-malformed.der \ + certs/test/cert-events/eku-malformed.der \ + certs/test/cert-events/good.der \ + certs/test/cert-events/issuer-malformed.der \ + certs/test/cert-events/issuer-uid.der \ + certs/test/cert-events/ku-malformed.der \ + certs/test/cert-events/leaf-bad-signature.der \ + certs/test/cert-events/leaf-unknown-issuer.der \ + certs/test/cert-events/policy-malformed.der \ + certs/test/cert-events/san-malformed.der \ + certs/test/cert-events/serial-empty.der \ + certs/test/cert-events/serial-not-integer.der \ + certs/test/cert-events/sigalg-mismatch.der \ + certs/test/cert-events/signature-not-bitstring.der \ + certs/test/cert-events/skid-malformed.der \ + certs/test/cert-events/spki-bad-bitstring.der \ + certs/test/cert-events/spki-unknown-alg.der \ + certs/test/cert-events/strict-ca-no-ku.der \ + certs/test/cert-events/strict-good.der \ + certs/test/cert-events/strict-no-akid.der \ + certs/test/cert-events/strict-no-crldp.der \ + certs/test/cert-events/strict-no-ocsp.der \ + certs/test/cert-events/subject-malformed.der \ + certs/test/cert-events/subject-uid.der \ + certs/test/cert-events/tbs-sigalg-unknown.der \ + certs/test/cert-events/trailing-data.der \ + certs/test/cert-events/unknown-critical.der \ + certs/test/cert-events/validity-not-time.der \ + certs/test/cert-events/version-not-integer.der \ + certs/test/cert-events/version-unsupported.der diff --git a/cmake/options.h.in b/cmake/options.h.in index 1f66e60a285..3c13d69016f 100644 --- a/cmake/options.h.in +++ b/cmake/options.h.in @@ -337,6 +337,8 @@ extern "C" { #cmakedefine WOLFSSL_CERT_GEN #undef WOLFSSL_CERT_GEN_CACHE #cmakedefine WOLFSSL_CERT_GEN_CACHE +#undef WOLFSSL_CERT_EVENTS +#cmakedefine WOLFSSL_CERT_EVENTS #undef WOLFSSL_CERT_NAME_ALL #cmakedefine WOLFSSL_CERT_NAME_ALL #undef WOLFSSL_CERT_REQ diff --git a/configure.ac b/configure.ac index 03d345c2cf9..783459f7d45 100644 --- a/configure.ac +++ b/configure.ac @@ -1572,6 +1572,7 @@ then test "$enable_savecert" = "" && enable_savecert=yes test "$enable_postauth" = "" && enable_postauth=yes test "$enable_chain_verify_cb" = "" && enable_chain_verify_cb=yes + test "$enable_cert_events" = "" && enable_cert_events=yes test "$enable_hrrcookie" = "" && enable_hrrcookie=yes test "$enable_crl_monitor" = "" && enable_crl_monitor=yes test "$enable_sni" = "" && enable_sni=yes @@ -6324,6 +6325,19 @@ then fi +# CERTIFICATE VALIDATION EVENTS +AC_ARG_ENABLE([cert-events], + [AS_HELP_STRING([--enable-cert-events],[Enable structured certificate and CRL validation event callback (default: disabled)])], + [ ENABLED_CERT_EVENTS=$enableval ], + [ ENABLED_CERT_EVENTS=no ] + ) + +if test "$ENABLED_CERT_EVENTS" = "yes" +then + AM_CFLAGS="$AM_CFLAGS -DWOLFSSL_CERT_EVENTS" +fi + + # CERT SIGN CALLBACK AC_ARG_ENABLE([certsigncb], [AS_HELP_STRING([--enable-certsigncb],[Enable cert signing callback API for TPM/HSM (default: disabled)])], @@ -15242,6 +15256,7 @@ echo " * certgen: $ENABLED_CERTGEN" echo " * certreq: $ENABLED_CERTREQ" echo " * certext: $ENABLED_CERTEXT" echo " * certgencache: $ENABLED_certgencache" +echo " * cert events: $ENABLED_CERT_EVENTS" echo " * CHACHA: $ENABLED_CHACHA" echo " * XCHACHA: $ENABLED_XCHACHA" echo " * Hash DRBG: $ENABLED_HASHDRBG" diff --git a/doc/dox_comments/header_files/asn_public.h b/doc/dox_comments/header_files/asn_public.h index 51b894542a0..a720160a8bc 100644 --- a/doc/dox_comments/header_files/asn_public.h +++ b/doc/dox_comments/header_files/asn_public.h @@ -4244,6 +4244,74 @@ int wc_SetCustomExtension(Cert *cert, int critical, const char *oid, int wc_SetUnknownExtCallback(DecodedCert* cert, wc_UnknownExtCallback cb); +/*! + \ingroup ASN + + \brief This function registers a callback that observes validation events + while a certificate is parsed and verified without a Certificate Manager. + Requires WOLFSSL_CERT_EVENTS (--enable-cert-events). A Certificate Manager + sets this for the certificates it verifies; see + wolfSSL_CertManagerSetCertEventCb() for the events and their rules. + + \return 0 Returned on success. + \return BAD_FUNC_ARG Returned when cert is NULL. + + \param cert the DecodedCert struct that is to be associated with this + callback. + \param cb function to register as the event callback. NULL turns events + off. + \param ctx a pointer passed to the callback with each event. + + _Example_ + \code + DecodedCert cert; + + wc_InitDecodedCert(&cert, der, derSz, NULL); + wc_SetCertEventCallback(&cert, myCertEvent, myLogger); + ret = wc_ParseCert(&cert, CERT_TYPE, VERIFY, NULL); + wc_FreeDecodedCert(&cert); + \endcode + + \sa wolfSSL_CertManagerSetCertEventCb +*/ +int wc_SetCertEventCallback(DecodedCert* cert, WOLFSSL_CERT_EVENT_CB cb, + void* ctx); + +/*! + \ingroup ASN + + \brief This function turns on the strict profile checks for a + certificate parsed without a Certificate Manager. Requires + WOLFSSL_CERT_EVENTS. See wolfSSL_CertManagerSetCertEventStrict() for the + checks. A check that fires is sent to the event callback as a NOTICE, or + as a DEFECT when enforced; an enforced check fails wc_ParseCert() with its + ASN_STRICT_*_E error. + + \return 0 Returned on success. + \return BAD_FUNC_ARG Returned when cert is NULL or enforce has an unknown + bit. + \return NOT_COMPILED_IN Returned when a check named is not in this build. + + \param cert the DecodedCert struct to check. + \param enforce the checks to enforce, wc_CertEventStrict values ORed + together. 0 only reports them. + + _Example_ + \code + DecodedCert cert; + + wc_InitDecodedCert(&cert, der, derSz, NULL); + wc_SetCertEventCallback(&cert, myCertEvent, myLogger); + wc_SetCertEventStrict(&cert, WC_CEV_STRICT_AKID); + ret = wc_ParseCert(&cert, CERT_TYPE, VERIFY, cm); + wc_FreeDecodedCert(&cert); + \endcode + + \sa wc_SetCertEventCallback + \sa wolfSSL_CertManagerSetCertEventStrict +*/ +int wc_SetCertEventStrict(DecodedCert* cert, word32 enforce); + /*! \ingroup ASN diff --git a/doc/dox_comments/header_files/ssl.h b/doc/dox_comments/header_files/ssl.h index 5884a417171..94dca829072 100644 --- a/doc/dox_comments/header_files/ssl.h +++ b/doc/dox_comments/header_files/ssl.h @@ -11589,6 +11589,278 @@ int wolfSSL_CertManagerVerifyBuffer(WOLFSSL_CERT_MANAGER* cm, void wolfSSL_CertManagerSetVerify(WOLFSSL_CERT_MANAGER* cm, VerifyCallback verify_callback); +/*! + \ingroup CertManager + \brief Sets a callback that observes certificate validation events in the + Certificate Manager. Requires WOLFSSL_CERT_EVENTS (--enable-cert-events). + + Each failed certificate or CRL check raises a WC_CEV_KIND_DEFECT event + that says which certificate failed (role and chain depth), which field + failed and why. Checks on the connection rather than the certificate, + such as the host name and OCSP, are reported in the WC_CEV_KIND_RESULT + event's error only. + Each verification ends with a WC_CEV_KIND_RESULT event that holds the + final error, or 0 when the certificate was accepted. A defect followed by + a passed result means the verify callback allowed the failure. A TLS 1.2 + client that asked for the server's certificate status sends it once the + status was checked at ServerHelloDone, as revocation can still fail + there; that RESULT has no subject, issuer or serial. + Each certificate checked raises one WC_CEV_KIND_INFO event with its + algorithms and key size. Each strict check that fires raises a + WC_CEV_KIND_NOTICE event; see wolfSSL_CertManagerSetCertEventStrict(). + The condition field holds a stable value for each kind of failure. + + The fields of WOLFSSL_CERT_EVENT, all defined in + wolfssl/wolfcrypt/asn_public.h: + + - size: sizeof(WOLFSSL_CERT_EVENT). Fields are only ever added at the + end, so check size before reading a newer one. + - kind: DEFECT, RESULT, INFO or NOTICE (enum wc_CertEventKind). + - category: a certificate or a CRL (enum wc_CertEventCategory). + - error: the wolfSSL error code, such as ASN_AFTER_DATE_E. 0 on a RESULT + that passed and on INFO. On a NOTICE, the error the check fails with + when enforced. + - role and depth: which object it is about (enum wc_CertEventRole) and + where it sits in the chain: 0 for the peer's certificate, 1 for the CA + that signed it, and so on. -1 when not known. + - field and reason: which part failed, by its RFC 5280 name, and why + (enum wc_CertEventField, enum wc_CertEventReason). + - condition: the stable key to map to your own codes (enum + wc_CertEventCondition). The values are named after the IEC 62351-9 and + IEC 62351-3 audit events and never change. + - extOID, extOidData, extOidSz and critical: the extension, when the field + is an extension. extOID is wolfSSL's OID sum and depends on the build; + extOidData holds the OID's content octets, the same in every build. + - der and derSz: the encoded certificate or CRL. + - sigOID, keyOID, curveOID and keySz: the certificate's signature + algorithm, key type, ECC curve and key size in bits, 0 when not decoded + yet. Use them to apply your own list of approved algorithms. + - subject, issuer, serial and serialSz: which certificate it is, as far + as it was decoded. The names are one-line strings. NULL when not known, + such as on a certificate that could not be decoded, on a CRL, and on + the RESULT of X509_verify_cert(). + - ssl: the WOLFSSL object whose handshake raised the event, so that the + application can tell which peer it was. NULL outside a handshake. + + The callback only observes: it cannot change the result. Use + wolfSSL_CertManagerSetVerify() to override a failure. The event and the + data it points to are only valid during the call and come from untrusted + input. The callback must not call back into this Certificate Manager and + may be called from several threads at once. + + \return WOLFSSL_SUCCESS on success. + \return BAD_FUNC_ARG when cm is NULL. + + \param cm a pointer to a WOLFSSL_CERT_MANAGER structure, created using + wolfSSL_CertManagerNew(). + \param cb the event callback. NULL turns events off. + \param ctx a pointer passed to the callback with each event. + + _Example_ + \code + #include + + static void myCertEvent(const WOLFSSL_CERT_EVENT* ev, void* ctx) + { + if (ev->kind == WC_CEV_KIND_DEFECT) { + // log ev->error, ev->role, ev->depth, ev->field and ev->reason + } + else { + // ev->error is 0 when the certificate was accepted + } + } + + WOLFSSL_CERT_MANAGER* cm = wolfSSL_CertManagerNew(); + ... + wolfSSL_CertManagerSetCertEventCb(cm, myCertEvent, myLogger); + + \endcode + + \sa wolfSSL_CTX_SetCertEventCb + \sa wolfSSL_X509_STORE_SetCertEventCb + \sa wc_SetCertEventCallback + \sa wolfSSL_CertManagerSetVerify +*/ +int wolfSSL_CertManagerSetCertEventCb(WOLFSSL_CERT_MANAGER* cm, + WOLFSSL_CERT_EVENT_CB cb, void* ctx); + +/*! + \ingroup CertManager + \brief Sets the certificate validation event callback in the + Certificate Manager of an SSL/TLS context. Requires WOLFSSL_CERT_EVENTS. + See wolfSSL_CertManagerSetCertEventCb() for what the callback receives. + + \return WOLFSSL_SUCCESS on success. + \return BAD_FUNC_ARG when ctx is NULL or has no Certificate Manager. + + \param ctx a pointer to a WOLFSSL_CTX structure, created using + wolfSSL_CTX_new(). + \param cb the event callback. NULL turns events off. + \param cbCtx a pointer passed to the callback with each event. + + _Example_ + \code + WOLFSSL_CTX* ctx = wolfSSL_CTX_new(wolfTLS_client_method()); + ... + wolfSSL_CTX_SetCertEventCb(ctx, myCertEvent, myLogger); + \endcode + + \sa wolfSSL_CertManagerSetCertEventCb +*/ +int wolfSSL_CTX_SetCertEventCb(WOLFSSL_CTX* ctx, WOLFSSL_CERT_EVENT_CB cb, + void* cbCtx); + +/*! + \ingroup CertManager + \brief Sets the certificate validation event callback in the Certificate + Manager of an X509 store, so that X509_verify_cert() raises events. + Requires WOLFSSL_CERT_EVENTS and OPENSSL_EXTRA. See + wolfSSL_CertManagerSetCertEventCb() for what the callback receives. + + X509_verify_cert() sends a RESULT for each certificate it tries while + building the chain, so one verification can send several, and a chain + check after the last one, such as the chain length, can still fail it. + Use X509_verify_cert()'s return value for the verdict. These RESULT + events have no subject, issuer or serial: use the X509 store context. + + \return WOLFSSL_SUCCESS on success. + \return BAD_FUNC_ARG when store is NULL or has no Certificate Manager. + + \param store a pointer to a WOLFSSL_X509_STORE structure. + \param cb the event callback. NULL turns events off. + \param ctx a pointer passed to the callback with each event. + + _Example_ + \code + WOLFSSL_X509_STORE* store = wolfSSL_X509_STORE_new(); + ... + wolfSSL_X509_STORE_SetCertEventCb(store, myCertEvent, myLogger); + \endcode + + \sa wolfSSL_CertManagerSetCertEventCb +*/ +int wolfSSL_X509_STORE_SetCertEventCb(WOLFSSL_X509_STORE* store, + WOLFSSL_CERT_EVENT_CB cb, void* ctx); + +/*! + \ingroup CertManager + \brief Sets which strict profile checks the Certificate Manager enforces. + Requires WOLFSSL_CERT_EVENTS (--enable-cert-events). + + The strict checks run on the certificates the Certificate Manager + verifies, the CAs loaded as trusted, and the CRLs loaded, when an event + callback or an enforced check is set. They check what RFC 5280 asks a + certificate or CRL to include: + + - WC_CEV_STRICT_UNIQUE_ID: no issuerUniqueID or subjectUniqueID. + - WC_CEV_STRICT_AKID: an authority key identifier with a + keyIdentifier, unless self-signed. + - WC_CEV_STRICT_KEY_USAGE: a key usage on a CA. + - WC_CEV_STRICT_CRL_DP: a CRL distribution point, unless self-signed. + - WC_CEV_STRICT_OCSP: an OCSP responder in the authority information + access, unless self-signed. + - WC_CEV_STRICT_CRL_THIS_UPDATE: a CRL thisUpdate that is not in the + future. + + A check that fires and is not enforced raises a WC_CEV_KIND_NOTICE event + and leaves the result unchanged. An enforced check raises a + WC_CEV_KIND_DEFECT event and fails the certificate or CRL with its own + error, such as ASN_STRICT_NO_AKID_E. For a certificate, the verify + callback may override it; a CRL that fails is not loaded. The checks run + last: when every other check passed, or once the verify callback allowed + what failed, in which case the strict failure is then passed to the + verify callback too. So allowing one failure never skips another check. + + Trust anchors are checked when they are loaded with verification: an + enforced check that fires fails the load, and the verify callback is not + asked. Set the checks after loading trust anchors to only report them. + + WC_CEV_STRICT_ALL enforces every check this build can run. Naming a check + on its own that this build cannot run returns NOT_COMPILED_IN: for + example WC_CEV_STRICT_CRL_THIS_UPDATE without CRL date checks, or + WC_CEV_STRICT_AKID, WC_CEV_STRICT_CRL_DP or WC_CEV_STRICT_OCSP in a + WOLFSSL_X509_TINY build that does not decode that extension. + + \return WOLFSSL_SUCCESS on success. + \return BAD_FUNC_ARG when cm is NULL or enforce has an unknown bit. + \return NOT_COMPILED_IN when a check named is not in this build. + + \param cm a pointer to a WOLFSSL_CERT_MANAGER structure, created using + wolfSSL_CertManagerNew(). + \param enforce the checks to enforce, wc_CertEventStrict values ORed + together. 0 only reports them. + + _Example_ + \code + WOLFSSL_CERT_MANAGER* cm = wolfSSL_CertManagerNew(); + ... + wolfSSL_CertManagerSetCertEventCb(cm, myCertEvent, myLogger); + // Report every strict check, and fail certificates with unique IDs. + wolfSSL_CertManagerSetCertEventStrict(cm, WC_CEV_STRICT_UNIQUE_ID); + \endcode + + \sa wolfSSL_CertManagerSetCertEventCb + \sa wolfSSL_CTX_SetCertEventStrict + \sa wolfSSL_X509_STORE_SetCertEventStrict + \sa wc_SetCertEventStrict +*/ +int wolfSSL_CertManagerSetCertEventStrict(WOLFSSL_CERT_MANAGER* cm, + word32 enforce); + +/*! + \ingroup CertManager + \brief Sets which strict profile checks the Certificate Manager of a + WOLFSSL_CTX enforces. Requires WOLFSSL_CERT_EVENTS. See + wolfSSL_CertManagerSetCertEventStrict() for the checks. + + \return WOLFSSL_SUCCESS on success. + \return BAD_FUNC_ARG when ctx is NULL, has no Certificate Manager, or + enforce has an unknown bit. + \return NOT_COMPILED_IN when a check named is not in this build. + + \param ctx a pointer to a WOLFSSL_CTX structure. + \param enforce the checks to enforce, wc_CertEventStrict values ORed + together. 0 only reports them. + + _Example_ + \code + WOLFSSL_CTX* ctx = wolfSSL_CTX_new(wolfTLS_client_method()); + ... + wolfSSL_CTX_SetCertEventStrict(ctx, WC_CEV_STRICT_ALL); + \endcode + + \sa wolfSSL_CertManagerSetCertEventStrict +*/ +int wolfSSL_CTX_SetCertEventStrict(WOLFSSL_CTX* ctx, word32 enforce); + +/*! + \ingroup CertManager + \brief Sets which strict profile checks the Certificate Manager of an + X509 store enforces in X509_verify_cert(). Requires WOLFSSL_CERT_EVENTS + and OPENSSL_EXTRA. See wolfSSL_CertManagerSetCertEventStrict() for the + checks. + + \return WOLFSSL_SUCCESS on success. + \return BAD_FUNC_ARG when store is NULL, has no Certificate Manager, or + enforce has an unknown bit. + \return NOT_COMPILED_IN when a check named is not in this build. + + \param store a pointer to a WOLFSSL_X509_STORE structure. + \param enforce the checks to enforce, wc_CertEventStrict values ORed + together. 0 only reports them. + + _Example_ + \code + WOLFSSL_X509_STORE* store = wolfSSL_X509_STORE_new(); + ... + wolfSSL_X509_STORE_SetCertEventStrict(store, WC_CEV_STRICT_AKID); + \endcode + + \sa wolfSSL_CertManagerSetCertEventStrict +*/ +int wolfSSL_X509_STORE_SetCertEventStrict(WOLFSSL_X509_STORE* store, + word32 enforce); + /*! \brief Check CRL if the option is enabled and compares the cert to the CRL list. diff --git a/src/crl.c b/src/crl.c index e15b3dfa06c..41a3516eb0b 100644 --- a/src/crl.c +++ b/src/crl.c @@ -731,17 +731,183 @@ int CheckCertCRL_ex(WOLFSSL_CRL* crl, byte* issuerHash, byte* serial, extCrlInfo, extCrlInfoSz, issuerName, crl->cm, 1); } +#ifdef WOLFSSL_CERT_EVENTS +/* Report why a certificate failed its CRL check. + * + * The event is about the certificate checked. Nothing is sent for other + * errors, such as a CRL lookup that would block. + * + * @param [in] cert Decoded certificate that was checked. + * @param [in] ret Result of the CRL check. + */ +static void CertEventCrlCheck(DecodedCert* cert, int ret) +{ + switch (ret) { + case WC_NO_ERR_TRACE(CRL_CERT_REVOKED): + wc_CertEventFailureCond(cert, ret, WC_CEV_FIELD_CRL_REVOKED, + WC_CEV_REASON_REVOKED, WC_CEV_COND_CERT_REVOKED); + break; + case WC_NO_ERR_TRACE(CRL_MISSING): + wc_CertEventFailureCond(cert, ret, WC_CEV_FIELD_CHAIN, + WC_CEV_REASON_MISSING, WC_CEV_COND_CRL_MISSING); + break; + case WC_NO_ERR_TRACE(CRL_CERT_DATE_ERR): + wc_CertEventFailureCond(cert, ret, WC_CEV_FIELD_CRL_NEXT_UPDATE, + WC_CEV_REASON_EXPIRED, WC_CEV_COND_CRL_EXPIRED); + break; + /* A CRL loaded before its issuer is verified when first used. */ + case WC_NO_ERR_TRACE(ASN_CRL_CONFIRM_E): + wc_CertEventFailureCond(cert, ret, WC_CEV_FIELD_SIGNATURE, + WC_CEV_REASON_SIGNATURE, WC_CEV_COND_CRL_SIG_INVALID); + break; + case WC_NO_ERR_TRACE(ASN_CRL_NO_SIGNER_E): + wc_CertEventFailureCond(cert, ret, WC_CEV_FIELD_CHAIN, + WC_CEV_REASON_NO_SIGNER, WC_CEV_COND_CRL_NO_SIGNER); + break; + default: + break; + } +} + +/* Report why a CRL being loaded was rejected. + * + * @param [in] cm Certificate manager. + * @param [in] dcrl Decoded CRL. + * @param [in] ret Result of parsing the CRL. + * @param [in] buff Encoded CRL. + * @param [in] sz Size of encoded CRL in bytes. + */ +static void CrlEventLoad(WOLFSSL_CERT_MANAGER* cm, DecodedCRL* dcrl, int ret, + const byte* buff, word32 sz) +{ + WOLFSSL_CERT_EVENT ev; + + if ((cm == NULL) || (cm->certEventCb == NULL) || (ret >= 0) || + (ret == WC_NO_ERR_TRACE(MEMORY_E))) { + return; + } + + XMEMSET(&ev, 0, sizeof(ev)); + ev.kind = WC_CEV_KIND_DEFECT; + ev.category = WC_CEV_CAT_CRL; + ev.error = ret; + ev.role = WC_CEV_ROLE_CRL; + ev.depth = -1; + ev.sigOID = dcrl->signatureOID; + ev.der = buff; + ev.derSz = sz; + switch (ret) { + case WC_NO_ERR_TRACE(ASN_CRL_CONFIRM_E): + ev.field = WC_CEV_FIELD_SIGNATURE; + ev.reason = WC_CEV_REASON_SIGNATURE; + ev.condition = WC_CEV_COND_CRL_SIG_INVALID; + break; + case WC_NO_ERR_TRACE(ASN_CRL_NO_SIGNER_E): + ev.field = WC_CEV_FIELD_CHAIN; + if (dcrl->crlSignerFound) { + ev.reason = WC_CEV_REASON_CONSTRAINT; + ev.condition = WC_CEV_COND_CRL_SIGNER_KU; + } + else { + ev.reason = WC_CEV_REASON_NO_SIGNER; + ev.condition = WC_CEV_COND_CRL_NO_SIGNER; + } + break; + case WC_NO_ERR_TRACE(ASN_CRIT_EXT_E): + ev.field = WC_CEV_FIELD_EXTENSION; + ev.reason = WC_CEV_REASON_UNSUPPORTED; + ev.condition = WC_CEV_COND_CRL_EXT_UNKNOWN_CRITICAL; + break; + case WC_NO_ERR_TRACE(CRL_CERT_DATE_ERR): + ev.field = WC_CEV_FIELD_CRL_NEXT_UPDATE; + ev.reason = WC_CEV_REASON_EXPIRED; + ev.condition = WC_CEV_COND_CRL_EXPIRED; + break; + default: + ev.field = WC_CEV_FIELD_ENCODING; + ev.reason = WC_CEV_REASON_MALFORMED; + ev.condition = WC_CEV_COND_CRL_FORMAT; + break; + } + wc_CertEventSend(cm->certEventCb, cm->certEventCtx, &ev); +} + +/* Run the strict checks on a CRL that was loaded. + * + * A thisUpdate in the future is reported as a NOTICE, or as a DEFECT when + * the check is enforced. + * + * @param [in] cm Certificate manager. + * @param [in] dcrl Decoded CRL. + * @param [in] buff Encoded CRL. + * @param [in] sz Size of encoded CRL in bytes. + * @return 0 when no enforced check fired. + * @return ASN_STRICT_CRL_THIS_UPDATE_E when thisUpdate is in the future and + * the check is enforced. + */ +static int CrlEventStrict(WOLFSSL_CERT_MANAGER* cm, DecodedCRL* dcrl, + const byte* buff, word32 sz) +{ + int ret = 0; +#if !defined(NO_ASN_TIME) && !defined(WOLFSSL_NO_CRL_DATE_CHECK) + WOLFSSL_CERT_EVENT ev; + int enforced; + + if ((cm == NULL) || ((cm->certEventCb == NULL) && + (cm->certEventStrict == 0))) { + return 0; + } + if (XVALIDATE_DATE(dcrl->lastDate, dcrl->lastDateFormat, ASN_BEFORE, + MAX_DATE_SIZE)) { + return 0; + } + + enforced = ((cm->certEventStrict & WC_CEV_STRICT_CRL_THIS_UPDATE) != 0); + if (enforced) { + ret = ASN_STRICT_CRL_THIS_UPDATE_E; + WOLFSSL_ERROR_VERBOSE(ret); + } + + XMEMSET(&ev, 0, sizeof(ev)); + ev.kind = enforced ? WC_CEV_KIND_DEFECT : WC_CEV_KIND_NOTICE; + ev.category = WC_CEV_CAT_CRL; + ev.error = WC_NO_ERR_TRACE(ASN_STRICT_CRL_THIS_UPDATE_E); + ev.role = WC_CEV_ROLE_CRL; + ev.depth = -1; + ev.field = WC_CEV_FIELD_CRL_THIS_UPDATE; + ev.reason = WC_CEV_REASON_POLICY; + ev.condition = WC_CEV_COND_CRL_THIS_UPDATE_FUTURE; + ev.sigOID = dcrl->signatureOID; + ev.der = buff; + ev.derSz = sz; + wc_CertEventSend(cm->certEventCb, cm->certEventCtx, &ev); +#else + (void)cm; + (void)dcrl; + (void)buff; + (void)sz; +#endif + return ret; +} +#endif /* WOLFSSL_CERT_EVENTS */ + static int CheckDecodedCertCRL(WOLFSSL_CRL* crl, DecodedCert* cert, WOLFSSL_CERT_MANAGER* cm, int canRetry) { + int ret; #if defined(OPENSSL_EXTRA) || defined(OPENSSL_EXTRA_X509_SMALL) void* issuerName = cert->issuerName; #else void* issuerName = NULL; #endif - return CheckCertCRLCm(crl, cert->issuerHash, cert->serial, cert->serialSz, + + ret = CheckCertCRLCm(crl, cert->issuerHash, cert->serial, cert->serialSz, NULL, cert->extCrlInfo, cert->extCrlInfoSz, issuerName, cm, canRetry); +#ifdef WOLFSSL_CERT_EVENTS + CertEventCrlCheck(cert, ret); +#endif + return ret; } /* Is the cert ok with CRL, return 0 on success */ @@ -1011,6 +1177,21 @@ int BufferLoadCRL(WOLFSSL_CRL* crl, const byte* buff, long sz, int type, #endif ret = ParseCRL(currentEntry->certs, dcrl, myBuffer, (word32)sz, verify, crl->cm); +#ifdef WOLFSSL_CERT_EVENTS + /* A CRL loaded before its issuer is checked when first used. */ + if (!((ret == WC_NO_ERR_TRACE(ASN_CRL_NO_SIGNER_E)) && + (verify == NO_VERIFY))) { + CrlEventLoad(crl->cm, dcrl, ret, myBuffer, (word32)sz); + } + /* Also for a CRL loaded before its issuer: the date needs no signer. */ + if ((ret == 0) || ((ret == WC_NO_ERR_TRACE(ASN_CRL_NO_SIGNER_E)) && + (verify == NO_VERIFY))) { + int strictRet = CrlEventStrict(crl->cm, dcrl, myBuffer, (word32)sz); + if (strictRet != 0) { + ret = strictRet; + } + } +#endif if (ret != 0 && !(ret == WC_NO_ERR_TRACE(ASN_CRL_NO_SIGNER_E) && verify == NO_VERIFY)) { diff --git a/src/internal.c b/src/internal.c index 0c54d776a0d..ea557867aad 100644 --- a/src/internal.c +++ b/src/internal.c @@ -17603,6 +17603,72 @@ static int CheckTrustedPeerDates(const DecodedCert* cert) } #endif /* WOLFSSL_TRUST_PEER_CERT && !NO_ASN_TIME_CHECK */ +#ifdef WOLFSSL_CERT_EVENTS +/* Send the RESULT event that ends processing of the peer's certificates. + * + * @param [in] ssl SSL/TLS object. + * @param [in] args Peer certificate processing arguments. May be NULL. + * @param [in] ret Final error, 0 when the peer's certificate was accepted. + */ +static void PeerCertEventResult(WOLFSSL* ssl, ProcPeerCertArgs* args, int ret) +{ + WOLFSSL_CERT_MANAGER* cm = SSL_CM(ssl); + WOLFSSL_CERT_EVENT ev; + int haveCert = (args != NULL) && (args->certs != NULL) && + (args->count > 0); + + /* Nothing was received and nothing failed: nothing to report. */ + if ((cm == NULL) || (cm->certEventCb == NULL) || + ((!haveCert) && (ret == 0))) { + return; + } + + XMEMSET(&ev, 0, sizeof(ev)); + ev.kind = WC_CEV_KIND_RESULT; + ev.category = WC_CEV_CAT_CERT; + ev.error = ret; + ev.role = WC_CEV_ROLE_CANDIDATE; + ev.depth = 0; + /* Failures of the chain as a whole have no certificate to report on. */ + if (ret == WC_NO_ERR_TRACE(NO_PEER_CERT)) { + ev.condition = WC_CEV_COND_TLS_NO_PEER_CERT; + } + else if (ret == WC_NO_ERR_TRACE(MAX_CHAIN_ERROR)) { + ev.condition = WC_CEV_COND_TLS_CHAIN_TOO_LONG; + } +#if defined(HAVE_CERTIFICATE_STATUS_REQUEST) || \ + defined(HAVE_CERTIFICATE_STATUS_REQUEST_V2) + ssl->certEventResultHeld = 0; + /* A TLS 1.2 client checks the server's certificate status after this + * message, so the verdict is final only then. */ + if ((ret == 0) && haveCert && + (ssl->options.side == WOLFSSL_CLIENT_END) && + !IsAtLeastTLSv1_3(ssl->version) && (0 + #ifdef HAVE_CERTIFICATE_STATUS_REQUEST + || ssl->status_request + #endif + #ifdef HAVE_CERTIFICATE_STATUS_REQUEST_V2 + || ssl->status_request_v2 + #endif + )) { + ssl->certEventResultHeld = 1; + return; + } +#endif + if (haveCert) { + ev.der = args->certs[0].buffer; + ev.derSz = args->certs[0].length; + /* The peer's certificate, when it is the one still decoded. */ + if (args->dCertInit && (args->dCert != NULL) && + (args->dCert->source == args->certs[0].buffer)) { + wc_CertEventIdentity(args->dCert, &ev); + } + } + ev.ssl = ssl; + wc_CertEventSend(cm->certEventCb, cm->certEventCtx, &ev); +} +#endif /* WOLFSSL_CERT_EVENTS */ + static int ProcessPeerCertParse(WOLFSSL* ssl, ProcPeerCertArgs* args, int certType, int verify, byte** pSubjectHash, int* pAlreadySigner) { @@ -17736,6 +17802,19 @@ PRAGMA_GCC_DIAG_POP if (verify != NO_VERIFY && TLSX_CSR2_IsMulti(ssl->extensions)) { extraSigners = TLSX_CSR2_GetPendingSigners(ssl->extensions); } +#endif +#ifdef WOLFSSL_CERT_EVENTS + /* Index 0 is the peer's certificate; the rest are the CAs it sent. */ + if ((verify != NO_VERIFY) && (SSL_CM(ssl) != NULL)) { + wc_SetCertEventCallback(args->dCert, SSL_CM(ssl)->certEventCb, + SSL_CM(ssl)->certEventCtx); + /* Run by ProcessPeerCerts() after its other checks. */ + wc_CertEventStrictDefer(args->dCert, SSL_CM(ssl)->certEventStrict); + wc_CertEventSetPosition(args->dCert, (args->certIdx == 0) ? + WC_CEV_ROLE_CANDIDATE : WC_CEV_ROLE_SIGNER, args->certIdx); + wc_CertEventSetConnection(args->dCert, ssl, + args->notCaSet ? args->notCaHash : NULL); + } #endif /* Parse Certificate */ ret = ParseCertRelative(args->dCert, certType, verify, SSL_CM(ssl), extraSigners); @@ -17949,6 +18028,11 @@ static int ProcessPeerCertCheckKey(WOLFSSL* ssl, ProcPeerCertArgs* args) break; } +#ifdef WOLFSSL_CERT_EVENTS + /* A key below the configured minimum is a policy failure. */ + wc_CertEventFailure(args->dCert, ret, WC_CEV_FIELD_SPKI, + WC_CEV_REASON_POLICY); +#endif return ret; } @@ -18990,6 +19074,14 @@ static int ProcessPeerCertDecodeKey(WOLFSSL* ssl, ProcPeerCertArgs* args, break; } +#ifdef WOLFSSL_CERT_EVENTS + /* A peer key below the configured minimum is a policy failure. */ + if ((ret == WC_NO_ERR_TRACE(RSA_KEY_SIZE_E)) || + (ret == WC_NO_ERR_TRACE(ECC_KEY_SIZE_E))) { + wc_CertEventFailure(args->dCert, ret, WC_CEV_FIELD_SPKI, + WC_CEV_REASON_POLICY); + } +#endif *pRet = ret; return 0; } @@ -19893,6 +19985,14 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx, } } #endif /* IGNORE_KEY_EXTENSIONS */ + #ifdef WOLFSSL_CERT_EVENTS + /* Last of the chain CA checks, so that a verify callback + * allowing a strict failure does not also skip a check + * that has not run yet. A CA that fails is not added. */ + if (ret == 0) { + ret = wc_CertEventStrictChecks(args->dCert); + } + #endif #ifdef WOLFSSL_ALT_CERT_CHAINS /* For alternate cert chain, its okay for a CA cert to fail with ASN_NO_SIGNER_E here. The "alternate" certificate @@ -19959,6 +20059,17 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx, SetRevocationVerifyRet(ssl, ret); /* Do verify callback */ ret = DoVerifyCallbackHeldCrl(ssl, args, ret, &heldCrlRet); + #ifdef WOLFSSL_CERT_EVENTS + /* Another failure was allowed: the strict checks still + * apply. Not to a CA left out of the chain. */ + if ((ret == 0) && !skipAddCA) { + ret = wc_CertEventStrictChecks(args->dCert); + if (ret != 0) { + ret = DoVerifyCallback(SSL_CM(ssl), ssl, ret, + args); + } + } + #endif if (CrlErrWaived(ssl, ret)) ret = ssl->error = 0; #if defined(HAVE_OCSP) && defined(HAVE_CRL) && \ @@ -20005,6 +20116,15 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx, } #endif /* HAVE_CERTIFICATE_STATUS_REQUEST_V2 */ + #ifdef WOLFSSL_CERT_EVENTS + /* Not a CA: a certificate it issued is reported as + * signed by a non-CA. */ + if ((ret == 0) && !args->dCert->isCA) { + XMEMCPY(args->notCaHash, args->dCert->subjectHash, + KEYID_SIZE); + args->notCaSet = 1; + } + #endif /* If valid CA then add to Certificate Manager */ if (ret == 0 && args->dCert->isCA && !ssl->options.verifyNone && !skipAddCA) { @@ -20044,6 +20164,13 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx, ssl->peerVerifyRet = WOLFSSL_X509_V_ERR_INVALID_CA; } #endif + #ifdef WOLFSSL_CERT_EVENTS + /* The CA the peer sent may not sign certificates. */ + if (ret == WC_NO_ERR_TRACE(NOT_CA_ERROR)) { + wc_CertEventFailure(args->dCert, ret, + WC_CEV_FIELD_CHAIN, WC_CEV_REASON_CONSTRAINT); + } + #endif /* Handle error codes */ ssl->error = ret; /* Report SSL error or clear error if @@ -20409,7 +20536,6 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx, goto exit_ppc; } #endif /* HAVE_OCSP || HAVE_CRL */ - #ifdef KEEP_PEER_CERT if (args->fatal == 0) { int copyRet = 0; @@ -20493,6 +20619,21 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx, } } } + #ifdef WOLFSSL_CERT_EVENTS + /* The peer's key usage or extended key usage does not allow + * it to be used for this TLS connection. */ + if ((ret == WC_NO_ERR_TRACE(KEYUSE_SIGNATURE_E)) || + (ret == WC_NO_ERR_TRACE(KEYUSE_ENCIPHER_E))) { + wc_CertEventFailureCond(args->dCert, ret, + WC_CEV_FIELD_EXTENSION, WC_CEV_REASON_CONSTRAINT, + WC_CEV_COND_CERT_KU_PURPOSE); + } + else if (ret == WC_NO_ERR_TRACE(EXTKEYUSE_AUTH_E)) { + wc_CertEventFailureCond(args->dCert, ret, + WC_CEV_FIELD_EXTENSION, WC_CEV_REASON_CONSTRAINT, + WC_CEV_COND_CERT_EKU_PURPOSE); + } + #endif #endif /* IGNORE_KEY_EXTENSIONS */ if (args->fatal) { @@ -20689,6 +20830,15 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx, } #endif + #ifdef WOLFSSL_CERT_EVENTS + /* Last of the peer certificate checks, so that a verify callback + * allowing a strict failure does not also skip a check that has + * not run yet. */ + if ((ret == 0) && (args->count > 0) && args->dCertInit) { + ret = wc_CertEventStrictChecks(args->dCert); + } + #endif + /* Do leaf verify callback when it wasn't called yet. Skipped when * the chain verify callback owns the verdict. */ if (!UsingChainVerifyCb(ssl) && @@ -20702,6 +20852,16 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx, ret = DoVerifyCallback(SSL_CM(ssl), ssl, ret, args); #endif } + #ifdef WOLFSSL_CERT_EVENTS + /* Another failure was allowed: the strict checks still apply. */ + if ((ret == 0) && (args->count > 0) && args->dCertInit && + !UsingChainVerifyCb(ssl)) { + ret = wc_CertEventStrictChecks(args->dCert); + if (ret != 0) { + ret = DoVerifyCallback(SSL_CM(ssl), ssl, ret, args); + } + } + #endif if (CrlErrWaived(ssl, ret)) ret = ssl->error = 0; @@ -20756,6 +20916,11 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx, } #endif /* WOLFSSL_HAVE_HS_SUSPEND */ +#ifdef WOLFSSL_CERT_EVENTS + /* A suspended handshake returned above: this is the final outcome. */ + PeerCertEventResult(ssl, args, ret); +#endif + #ifdef WOLFSSL_HAVE_HS_SUSPEND /* Cleanup async */ FreeAsyncCtx(ssl, 0); @@ -21939,6 +22104,58 @@ static int ResetHandshakeStateForReneg(WOLFSSL* ssl) } #endif +#if defined(WOLFSSL_CERT_EVENTS) && \ + (defined(HAVE_CERTIFICATE_STATUS_REQUEST) || \ + defined(HAVE_CERTIFICATE_STATUS_REQUEST_V2)) +/* Send the RESULT event held for the server's certificate status. + * + * Sent once the status was checked at ServerHelloDone, or when the handshake + * fails first. It has no subject, issuer or serial. + * + * @param [in] ssl SSL/TLS object. + * @param [in] ret Result of the handshake message just processed. + */ +static void PeerCertEventHeldResult(WOLFSSL* ssl, int ret) +{ + WOLFSSL_CERT_MANAGER* cm = SSL_CM(ssl); + WOLFSSL_CERT_EVENT ev; + + if (!ssl->certEventResultHeld) { + return; + } + /* Not final yet: the message is processed again. */ + if ((ret == WC_NO_ERR_TRACE(WC_PENDING_E)) + #ifdef WOLFSSL_NONBLOCK_OCSP + || (ret == WC_NO_ERR_TRACE(OCSP_WANT_READ)) + #endif + #ifdef WOLFSSL_HAVE_HS_SUSPEND + || IsHsSuspendErr(ret) + #endif + ) { + return; + } + ssl->certEventResultHeld = 0; + if ((cm == NULL) || (cm->certEventCb == NULL)) { + return; + } + + XMEMSET(&ev, 0, sizeof(ev)); + ev.kind = WC_CEV_KIND_RESULT; + ev.category = WC_CEV_CAT_CERT; + ev.error = ret; + ev.role = WC_CEV_ROLE_CANDIDATE; + ev.depth = 0; +#ifdef KEEP_PEER_CERT + if (ssl->peerCert.derCert != NULL) { + ev.der = ssl->peerCert.derCert->buffer; + ev.derSz = ssl->peerCert.derCert->length; + } +#endif + ev.ssl = ssl; + wc_CertEventSend(cm->certEventCb, cm->certEventCtx, &ev); +} +#endif + int DoHandShakeMsgType(WOLFSSL* ssl, byte* input, word32* inOutIdx, byte type, word32 size, word32 totalSz) { @@ -22011,6 +22228,12 @@ int DoHandShakeMsgType(WOLFSSL* ssl, byte* input, word32* inOutIdx, /* sanity check msg received */ if ( (ret = SanityCheckMsgReceived(ssl, type)) != 0) { WOLFSSL_MSG("Sanity Check on handshake message type received failed"); +#if defined(WOLFSSL_CERT_EVENTS) && \ + (defined(HAVE_CERTIFICATE_STATUS_REQUEST) || \ + defined(HAVE_CERTIFICATE_STATUS_REQUEST_V2)) + /* Such as the status check at ServerHelloDone failing. */ + PeerCertEventHeldResult(ssl, ret); +#endif #ifdef WOLFSSL_NONBLOCK_OCSP if (ret == WC_NO_ERR_TRACE(OCSP_WANT_READ)) { word32 hdrSz = HANDSHAKE_HEADER_SZ; @@ -22283,6 +22506,15 @@ int DoHandShakeMsgType(WOLFSSL* ssl, byte* input, word32* inOutIdx, ret = DECODE_E; WOLFSSL_ERROR_VERBOSE(ret); } +#if defined(WOLFSSL_CERT_EVENTS) && \ + (defined(HAVE_CERTIFICATE_STATUS_REQUEST) || \ + defined(HAVE_CERTIFICATE_STATUS_REQUEST_V2)) + /* The server's certificate status was checked, or the handshake + * failed. */ + if ((ret != 0) || (type == server_hello_done)) { + PeerCertEventHeldResult(ssl, ret); + } +#endif #ifdef WOLFSSL_HAVE_HS_SUSPEND /* if suspended, offset index so this msg will be processed again */ diff --git a/src/ssl_api_cert.c b/src/ssl_api_cert.c index beb194edd6b..358be3d1c8a 100644 --- a/src/ssl_api_cert.c +++ b/src/ssl_api_cert.c @@ -1743,6 +1743,47 @@ WOLF_STACK_OF(WOLFSSL_X509_NAME)* wolfSSL_load_client_CA_file(const char* fname) #endif /* !NO_BIO */ #endif /* WOLFSSL_NO_CA_NAMES */ +#ifdef WOLFSSL_CERT_EVENTS +/* Set the validation event callback into the context's certificate manager. + * + * @param [in] ctx SSL/TLS context. + * @param [in] cb Event callback. NULL turns events off. + * @param [in] cbCtx Context passed to the callback. + * @return WOLFSSL_SUCCESS on success. + * @return BAD_FUNC_ARG when ctx is NULL or has no certificate manager. + */ +int wolfSSL_CTX_SetCertEventCb(WOLFSSL_CTX* ctx, WOLFSSL_CERT_EVENT_CB cb, + void* cbCtx) +{ + WOLFSSL_ENTER("wolfSSL_CTX_SetCertEventCb"); + if (ctx == NULL) { + return BAD_FUNC_ARG; + } + + return wolfSSL_CertManagerSetCertEventCb(ctx->cm, cb, cbCtx); +} + +/* Set the strict checks to enforce into the context's certificate manager. + * + * @param [in] ctx SSL/TLS context. + * @param [in] enforce Checks to enforce. enum wc_CertEventStrict bits, 0 to + * only report. + * @return WOLFSSL_SUCCESS on success. + * @return BAD_FUNC_ARG when ctx is NULL, has no certificate manager, or + * enforce has an unknown bit. + * @return NOT_COMPILED_IN when a check named is not in this build. + */ +int wolfSSL_CTX_SetCertEventStrict(WOLFSSL_CTX* ctx, word32 enforce) +{ + WOLFSSL_ENTER("wolfSSL_CTX_SetCertEventStrict"); + if (ctx == NULL) { + return BAD_FUNC_ARG; + } + + return wolfSSL_CertManagerSetCertEventStrict(ctx->cm, enforce); +} +#endif /* WOLFSSL_CERT_EVENTS */ + #if defined(OPENSSL_EXTRA) || defined(WOLFSSL_WPAS_SMALL) /* Get the certificate store of the SSL/TLS context. * diff --git a/src/ssl_certman.c b/src/ssl_certman.c index ac01a8343b9..03a493c7007 100644 --- a/src/ssl_certman.c +++ b/src/ssl_certman.c @@ -742,6 +742,64 @@ void wolfSSL_CertManagerSetVerify(WOLFSSL_CERT_MANAGER* cm, VerifyCallback vc) } #endif /* !NO_WOLFSSL_CM_VERIFY */ +#ifdef WOLFSSL_CERT_EVENTS +/* Set the validation event callback into certificate manager. + * + * The callback observes events only; it cannot change the result. + * + * @param [in] cm Certificate manager. + * @param [in] cb Event callback. NULL turns events off. + * @param [in] ctx Context passed to the callback. + * @return WOLFSSL_SUCCESS on success. + * @return BAD_FUNC_ARG when cm is NULL. + */ +int wolfSSL_CertManagerSetCertEventCb(WOLFSSL_CERT_MANAGER* cm, + WOLFSSL_CERT_EVENT_CB cb, void* ctx) +{ + WOLFSSL_ENTER("wolfSSL_CertManagerSetCertEventCb"); + if (cm == NULL) { + return BAD_FUNC_ARG; + } + + cm->certEventCb = cb; + cm->certEventCtx = ctx; + return WOLFSSL_SUCCESS; +} + +/* Set the strict checks to enforce into certificate manager. + * + * The strict checks run on certificates verified and CRLs loaded when an + * event callback or an enforced check is set. A check that fires is sent as a + * NOTICE, or as a DEFECT when it is enforced. An enforced check fails the + * certificate or CRL with its ASN_STRICT_*_E error. + * + * @param [in] cm Certificate manager. + * @param [in] enforce Checks to enforce. enum wc_CertEventStrict bits, 0 to + * only report. WC_CEV_STRICT_ALL is every check in this + * build. + * @return WOLFSSL_SUCCESS on success. + * @return BAD_FUNC_ARG when cm is NULL or enforce has an unknown bit. + * @return NOT_COMPILED_IN when a check named is not in this build. + */ +int wolfSSL_CertManagerSetCertEventStrict(WOLFSSL_CERT_MANAGER* cm, + word32 enforce) +{ + int ret; + + WOLFSSL_ENTER("wolfSSL_CertManagerSetCertEventStrict"); + if (cm == NULL) { + return BAD_FUNC_ARG; + } + ret = wc_CertEventStrictResolve(enforce, &enforce); + if (ret != 0) { + return ret; + } + + cm->certEventStrict = enforce; + return WOLFSSL_SUCCESS; +} +#endif /* WOLFSSL_CERT_EVENTS */ + #ifdef WC_ASN_UNKNOWN_EXT_CB void wolfSSL_CertManagerSetUnknownExtCallback(WOLFSSL_CERT_MANAGER* cm, wc_UnknownExtCallback cb) @@ -863,6 +921,14 @@ static int cm_verify_err_overridable(int err) case WC_NO_ERR_TRACE(ECC_KEY_SIZE_E): case WC_NO_ERR_TRACE(FALCON_KEY_SIZE_E): case WC_NO_ERR_TRACE(MLDSA_KEY_SIZE_E): +#ifdef WOLFSSL_CERT_EVENTS + /* Strict checks, run after every other check passed. */ + case WC_NO_ERR_TRACE(ASN_STRICT_UNIQUE_ID_E): + case WC_NO_ERR_TRACE(ASN_STRICT_NO_AKID_E): + case WC_NO_ERR_TRACE(ASN_STRICT_NO_KEY_USAGE_E): + case WC_NO_ERR_TRACE(ASN_STRICT_NO_CRL_DP_E): + case WC_NO_ERR_TRACE(ASN_STRICT_NO_OCSP_E): +#endif #ifdef HAVE_CRL case WC_NO_ERR_TRACE(CRL_CERT_REVOKED): case WC_NO_ERR_TRACE(CRL_MISSING): @@ -878,6 +944,40 @@ static int cm_verify_err_overridable(int err) #if (!defined(NO_WOLFSSL_CLIENT) || !defined(WOLFSSL_NO_CLIENT_AUTH)) || \ defined(OPENSSL_EXTRA) +#ifdef WOLFSSL_CERT_EVENTS +/* Send the RESULT event that ends one verification. + * + * @param [in] cm Certificate manager. + * @param [in] error Final error, 0 when the certificate passed. + * @param [in] depth Chain depth, -1 when not known. + * @param [in] der Encoded certificate. + * @param [in] derSz Size of encoded certificate in bytes. + * @param [in] cert Decoded certificate, or NULL when not available. + */ +void CM_CertEventResult(WOLFSSL_CERT_MANAGER* cm, int error, + int depth, const unsigned char* der, word32 derSz, const DecodedCert* cert) +{ + WOLFSSL_CERT_EVENT ev; + + if (cm->certEventCb == NULL) { + return; + } + + XMEMSET(&ev, 0, sizeof(ev)); + ev.kind = WC_CEV_KIND_RESULT; + ev.category = WC_CEV_CAT_CERT; + ev.error = error; + ev.role = (depth > 0) ? WC_CEV_ROLE_SIGNER : WC_CEV_ROLE_CANDIDATE; + ev.depth = depth; + ev.der = der; + ev.derSz = derSz; + if (cert != NULL) { + wc_CertEventIdentity(cert, &ev); + } + wc_CertEventSend(cm->certEventCb, cm->certEventCtx, &ev); +} +#endif /* WOLFSSL_CERT_EVENTS */ + /* Verify the certificate. * * Uses the verification callback if available. @@ -890,13 +990,20 @@ static int cm_verify_err_overridable(int err) * @param [in] prev_err Previous error. Passed to callback. * @param [out] crlErr CRL result, not merged into the return value. May be * NULL. + * @param [in] depth Chain depth for validation events, -1 when not known. + * @param [in] strictErr NULL to send the RESULT event. Otherwise the caller + * has more checks to run and sends it: when the + * certificate failed another check, the strict checks + * still run and their error is returned here, 0 when + * none. * @return WOLFSSL_SUCCESS on success. * @return MEMORY_E when dynamic memory allocation fails. * @return NOT_COMPILED_IN when converting from PEM to DER is not a feature of * the wolfSSL build. */ -int CM_VerifyBuffer_ex(WOLFSSL_CERT_MANAGER* cm, const unsigned char* buff, - long sz, int format, int prev_err, int* crlErr) +static int CM_VerifyBuffer_int(WOLFSSL_CERT_MANAGER* cm, + const unsigned char* buff, long sz, int format, int prev_err, int* crlErr, + int depth, int* strictErr) { int ret = 0; int crlRet = 0; @@ -904,9 +1011,11 @@ int CM_VerifyBuffer_ex(WOLFSSL_CERT_MANAGER* cm, const unsigned char* buff, DerBuffer* der = NULL; WC_DECLARE_VAR(cert, DecodedCert, 1, 0); - WOLFSSL_ENTER("CM_VerifyBuffer_ex"); + WOLFSSL_ENTER("CM_VerifyBuffer_int"); (void)prev_err; + (void)depth; + (void)strictErr; if (crlErr != NULL) { *crlErr = 0; @@ -951,6 +1060,16 @@ int CM_VerifyBuffer_ex(WOLFSSL_CERT_MANAGER* cm, const unsigned char* buff, if (cm->unknownExtCallback != NULL) wc_SetUnknownExtCallback(cert, cm->unknownExtCallback); #endif +#ifdef WOLFSSL_CERT_EVENTS + /* A previous error means a failed load is being handed to the verify + * callback, which is not a verification of its own. */ + if (prev_err == 0) { + wc_SetCertEventCallback(cert, cm->certEventCb, cm->certEventCtx); + wc_CertEventStrictDefer(cert, cm->certEventStrict); + wc_CertEventSetPosition(cert, (depth > 0) ? + WC_CEV_ROLE_SIGNER : WC_CEV_ROLE_CANDIDATE, depth); + } +#endif /* Parse DER into decoded certificate fields and verify signature * against a known CA. */ @@ -967,6 +1086,17 @@ int CM_VerifyBuffer_ex(WOLFSSL_CERT_MANAGER* cm, const unsigned char* buff, } } #endif +#ifdef WOLFSSL_CERT_EVENTS + /* Last, so that a verify callback allowing a strict failure does not also + * skip a check that has not run yet. */ + if ((ret == 0) && (crlRet == 0)) { + ret = wc_CertEventStrictChecks(cert); + } + else if (strictErr != NULL) { + /* The caller may yet accept the certificate. */ + *strictErr = wc_CertEventStrictChecks(cert); + } +#endif (void)fatal; @@ -1031,6 +1161,15 @@ int CM_VerifyBuffer_ex(WOLFSSL_CERT_MANAGER* cm, const unsigned char* buff, ret = DoVerifyCallback(cm, NULL, crlRet, args); crlRet = 0; } + #ifdef WOLFSSL_CERT_EVENTS + /* Another failure was allowed: the strict checks still apply. */ + if (ret == 0) { + ret = wc_CertEventStrictChecks(cert); + if (ret != 0) { + ret = DoVerifyCallback(cm, NULL, ret, args); + } + } + #endif } WC_FREE_VAR_EX(args, cm->heap, DYNAMIC_TYPE_TMP_BUFFER); } @@ -1054,6 +1193,14 @@ int CM_VerifyBuffer_ex(WOLFSSL_CERT_MANAGER* cm, const unsigned char* buff, #endif (void)crlRet; +#ifdef WOLFSSL_CERT_EVENTS + /* The final outcome, after any verify callback override. */ + if ((prev_err == 0) && (strictErr == NULL)) { + CM_CertEventResult(cm, ret, depth, buff, (word32)sz, + WC_VAR_OK(cert) ? cert : NULL); + } +#endif + /* Dispose of allocated memory. */ FreeDecodedCert(cert); FreeDer(&der); @@ -1063,6 +1210,69 @@ int CM_VerifyBuffer_ex(WOLFSSL_CERT_MANAGER* cm, const unsigned char* buff, return (ret == 0) ? WOLFSSL_SUCCESS : ret; } +/* Verify the certificate. + * + * Uses the verification callback if available. + * + * @param [in] cm Certificate manager. + * @param [in] buff Buffer holding encoded certificate. + * @param [in] sz Size in bytes of data in buffer. + * @param [in] format Format of encoding. Valid values: + * WOLFSSL_FILETYPE_ASN1, WOLFSSL_FILETYPE_PEM. + * @param [in] prev_err Previous error. Passed to callback. + * @param [out] crlErr CRL result, not merged into the return value. May be + * NULL. + * @return WOLFSSL_SUCCESS on success. + * @return MEMORY_E when dynamic memory allocation fails. + * @return NOT_COMPILED_IN when converting from PEM to DER is not a feature of + * the wolfSSL build. + */ +int CM_VerifyBuffer_ex(WOLFSSL_CERT_MANAGER* cm, const unsigned char* buff, + long sz, int format, int prev_err, int* crlErr) +{ + return CM_VerifyBuffer_int(cm, buff, sz, format, prev_err, crlErr, -1, + NULL); +} + +#ifdef WOLFSSL_CERT_EVENTS +/* Verify one certificate of a chain, reporting its chain depth in events. + * + * Does not send the RESULT event: the caller sends it with + * CM_CertEventResult() once its own checks and verify callback are done. + * When the certificate failed a check the caller may yet accept, the strict + * checks still run and their error is returned in strictErr. + * + * @param [in] cm Certificate manager. + * @param [in] buff Buffer holding encoded certificate. + * @param [in] sz Size in bytes of data in buffer. + * @param [in] format Format of encoding. Valid values: + * WOLFSSL_FILETYPE_ASN1, WOLFSSL_FILETYPE_PEM. + * @param [in] depth Chain depth: 0 for the certificate being verified. + * @param [out] crlErr CRL result, not merged into the return value. May be + * NULL. + * @param [out] strictErr Error of an enforced strict check when the + * certificate failed another check, otherwise 0. + * @return WOLFSSL_SUCCESS on success. + * @return BAD_FUNC_ARG when cm or buff is NULL, or sz is not positive. + * @return WOLFSSL_BAD_FILETYPE when format is not supported. + * @return Other values on failure, as CM_VerifyBuffer_ex(). + */ +int CM_VerifyBufferDepth(WOLFSSL_CERT_MANAGER* cm, const unsigned char* buff, + long sz, int format, int depth, int* crlErr, int* strictErr) +{ + *strictErr = 0; + /* Same validation as wolfSSL_CertManagerVerifyBuffer(). */ + if ((cm == NULL) || (buff == NULL) || (sz <= 0)) { + return BAD_FUNC_ARG; + } + if ((format != WOLFSSL_FILETYPE_ASN1) && (format != WOLFSSL_FILETYPE_PEM)) { + return WOLFSSL_BAD_FILETYPE; + } + return CM_VerifyBuffer_int(cm, buff, sz, format, 0, crlErr, depth, + strictErr); +} +#endif /* WOLFSSL_CERT_EVENTS */ + /* Verify the certificate. * * Uses the verification callback if available. @@ -2133,6 +2343,10 @@ int wolfSSL_CertManagerCheckCRL(WOLFSSL_CERT_MANAGER* cm, { /* Initialize decoded certificate with buffer. */ InitDecodedCert(cert, der, (word32)sz, NULL); + #ifdef WOLFSSL_CERT_EVENTS + wc_SetCertEventCallback(cert, cm->certEventCb, cm->certEventCtx); + wc_CertEventSetPosition(cert, WC_CEV_ROLE_CANDIDATE, -1); + #endif /* Parse certificate and perform CRL checks. */ ret = ParseCertRelative(cert, CERT_TYPE, VERIFY_CRL, cm, NULL); @@ -3259,6 +3473,16 @@ int AddCA(WOLFSSL_CERT_MANAGER* cm, DerBuffer** pDer, int type, int verify) } #endif +#ifdef WOLFSSL_CERT_EVENTS + /* A CA the application loads as trusted. CAs from a peer's chain are + * reported by the TLS code that verified them. */ + if ((type == WOLFSSL_USER_CA) && (verify != NO_VERIFY)) { + wc_SetCertEventCallback(cert, cm->certEventCb, cm->certEventCtx); + wc_SetCertEventStrict(cert, cm->certEventStrict); + wc_CertEventSetPosition(cert, WC_CEV_ROLE_TRUST_ANCHOR, -1); + } +#endif + WOLFSSL_MSG_CERT("\tParsing new CA"); ret = ParseCert(cert, CA_TYPE, verify, cm); diff --git a/src/x509_str.c b/src/x509_str.c index 3e65d833826..9a7a4cc1ea2 100644 --- a/src/x509_str.c +++ b/src/x509_str.c @@ -565,6 +565,30 @@ static WOLFSSL_X509_STORE_CTX_verify_cb X509StoreGetVerifyCb( return NULL; } +#ifdef WOLFSSL_CERT_EVENTS +/* Send the RESULT event for ctx->current_cert. + * + * @param [in] ctx X509 store context. + * @param [in] ret Outcome: WOLFSSL_SUCCESS or the error it failed with. + * @param [in] err Error the certificate failed with, 0 when not known. + * @param [in] depth Chain depth. + */ +static void X509StoreCertEventResult(WOLFSSL_X509_STORE_CTX* ctx, int ret, + int err, int depth) +{ + if (ret == WOLFSSL_SUCCESS) { + err = 0; + } + else if (err == 0) { + /* Rejected by the verify callback or a date check. */ + err = VERIFY_CERT_ERROR; + } + CM_CertEventResult(ctx->store->cm, err, depth, + ctx->current_cert->derCert->buffer, + ctx->current_cert->derCert->length, NULL); +} +#endif + /* Verify ctx->current_cert against the store. * * is an out-parameter, never NULL. It is set to 1 when the @@ -582,15 +606,30 @@ static int X509StoreVerifyCert(WOLFSSL_X509_STORE_CTX* ctx, int* cbRejected, int ret = WC_NO_ERR_TRACE(WOLFSSL_FAILURE); int crlRet = 0; int datesChecked = 0; +#ifdef WOLFSSL_CERT_EVENTS + /* Same depth as SetupStoreCtxError() records for this certificate. */ + int depth = (ctx->chain != NULL) ? (int)ctx->chain->num : 0; + /* The error the certificate failed with, for the RESULT event. */ + int evErr = 0; + /* An enforced strict check that fired while another check failed. */ + int strictErr = 0; +#endif WOLFSSL_ENTER("X509StoreVerifyCert"); *cbRejected = 0; if (ctx->current_cert != NULL && ctx->current_cert->derCert != NULL) { + #ifdef WOLFSSL_CERT_EVENTS + ret = CM_VerifyBufferDepth(ctx->store->cm, + ctx->current_cert->derCert->buffer, + (long)ctx->current_cert->derCert->length, + WOLFSSL_FILETYPE_ASN1, depth, &crlRet, &strictErr); + #else ret = CM_VerifyBuffer_ex(ctx->store->cm, ctx->current_cert->derCert->buffer, ctx->current_cert->derCert->length, WOLFSSL_FILETYPE_ASN1, 0, &crlRet); + #endif datesChecked = (ret == WOLFSSL_SUCCESS) || (ret == WC_NO_ERR_TRACE(ASN_BEFORE_DATE_E)) || (ret == WC_NO_ERR_TRACE(ASN_AFTER_DATE_E)); @@ -611,6 +650,9 @@ static int X509StoreVerifyCert(WOLFSSL_X509_STORE_CTX* ctx, int* cbRejected, if (crlRet != 0) { SetupStoreCtxError(ctx, crlRet); if ((verifyCb == NULL) || (verifyCb(0, ctx) != 1)) { + #ifdef WOLFSSL_CERT_EVENTS + X509StoreCertEventResult(ctx, crlRet, crlRet, depth); + #endif return crlRet; } } @@ -618,6 +660,16 @@ static int X509StoreVerifyCert(WOLFSSL_X509_STORE_CTX* ctx, int* cbRejected, #ifndef NO_ASN_TIME /* update return value with any date validation overrides */ ret = X509StoreVerifyCertDate(ctx, ret); + #endif + #ifdef WOLFSSL_CERT_EVENTS + /* The other failures were overridden: the strict checks apply. */ + if ((ret == WOLFSSL_SUCCESS) && (strictErr != 0)) { + ret = strictErr; + strictErr = 0; + } + if (ret != WOLFSSL_SUCCESS) { + evErr = ret; + } #endif SetupStoreCtxError(ctx, ret); if (verifyCb != NULL) { @@ -631,6 +683,18 @@ static int X509StoreVerifyCert(WOLFSSL_X509_STORE_CTX* ctx, int* cbRejected, if (verifyCb(ret >= 0 ? 1 : 0, ctx) == 1) { ret = WOLFSSL_SUCCESS; + #ifdef WOLFSSL_CERT_EVENTS + /* Another failure was allowed: the strict checks still + * apply. */ + if (strictErr != 0) { + ret = strictErr; + evErr = ret; + SetupStoreCtxError(ctx, ret); + if (verifyCb(0, ctx) == 1) { + ret = WOLFSSL_SUCCESS; + } + } + #endif } else if (ret >= 0 && ctx->verify_cb != NULL) { /* Returning 0 must reject a chain the cert manager accepted. */ @@ -661,6 +725,11 @@ static int X509StoreVerifyCert(WOLFSSL_X509_STORE_CTX* ctx, int* cbRejected, * as it skips date validation when other errors are present. */ ret = X509StoreVerifyCertDate(ctx, ret); + #ifdef WOLFSSL_CERT_EVENTS + if (ret != WOLFSSL_SUCCESS) { + evErr = ret; + } + #endif SetupStoreCtxError(ctx, ret); ret = ret == WOLFSSL_SUCCESS ? 1 : 0; if (verifyCb != NULL) { @@ -672,6 +741,13 @@ static int X509StoreVerifyCert(WOLFSSL_X509_STORE_CTX* ctx, int* cbRejected, } } } +#endif +#ifdef WOLFSSL_CERT_EVENTS + /* The final outcome, after the date overrides, the context's CRLs and + * the verify callback. */ + if (ctx->current_cert != NULL && ctx->current_cert->derCert != NULL) { + X509StoreCertEventResult(ctx, ret, evErr, depth); + } #endif (void)datesChecked; return ret; @@ -2337,6 +2413,50 @@ int wolfSSL_X509_STORE_add_cert(WOLFSSL_X509_STORE* store, WOLFSSL_X509* x509) return result; } +#ifdef WOLFSSL_CERT_EVENTS +/* Set the validation event callback into the store's certificate manager. + * + * X509_verify_cert() verifies through this certificate manager. + * + * @param [in] store X509 certificate store. + * @param [in] cb Event callback. NULL turns events off. + * @param [in] ctx Context passed to the callback. + * @return WOLFSSL_SUCCESS on success. + * @return BAD_FUNC_ARG when store is NULL or has no certificate manager. + */ +int wolfSSL_X509_STORE_SetCertEventCb(WOLFSSL_X509_STORE* store, + WOLFSSL_CERT_EVENT_CB cb, void* ctx) +{ + WOLFSSL_ENTER("wolfSSL_X509_STORE_SetCertEventCb"); + if (store == NULL) { + return BAD_FUNC_ARG; + } + + return wolfSSL_CertManagerSetCertEventCb(store->cm, cb, ctx); +} + +/* Set the strict checks to enforce into the store's certificate manager. + * + * @param [in] store X509 certificate store. + * @param [in] enforce Checks to enforce. enum wc_CertEventStrict bits, 0 to + * only report. + * @return WOLFSSL_SUCCESS on success. + * @return BAD_FUNC_ARG when store is NULL, has no certificate manager, or + * enforce has an unknown bit. + * @return NOT_COMPILED_IN when a check named is not in this build. + */ +int wolfSSL_X509_STORE_SetCertEventStrict(WOLFSSL_X509_STORE* store, + word32 enforce) +{ + WOLFSSL_ENTER("wolfSSL_X509_STORE_SetCertEventStrict"); + if (store == NULL) { + return BAD_FUNC_ARG; + } + + return wolfSSL_CertManagerSetCertEventStrict(store->cm, enforce); +} +#endif /* WOLFSSL_CERT_EVENTS */ + int wolfSSL_X509_STORE_set_flags(WOLFSSL_X509_STORE* store, unsigned long flag) { int ret = WOLFSSL_SUCCESS; diff --git a/tests/api/test_certman.c b/tests/api/test_certman.c index 082da1ef6f1..5ca8135336c 100644 --- a/tests/api/test_certman.c +++ b/tests/api/test_certman.c @@ -757,6 +757,1888 @@ int test_wolfSSL_CertManagerSetVerify(void) return EXPECT_RESULT(); } +#if defined(WOLFSSL_CERT_EVENTS) && !defined(NO_FILESYSTEM) && \ + !defined(NO_CERTS) && !defined(NO_TLS) && !defined(NO_ASN_TIME) && \ + !defined(NO_WOLFSSL_CM_VERIFY) && !defined(NO_RSA) && \ + (!defined(NO_WOLFSSL_CLIENT) || !defined(WOLFSSL_NO_CLIENT_AUTH)) && \ + !(defined(NO_WOLFSSL_CLIENT) && defined(NO_WOLFSSL_SERVER)) +#define TEST_CERT_EVENTS +#endif +/* Tests that use the strict CRL distribution point check. */ +#if defined(TEST_CERT_EVENTS) && \ + (!defined(WOLFSSL_X509_TINY) || defined(WOLFSSL_X509_TINY_CRL_DP)) +#define TEST_CERT_EVENTS_CRL_DP +#endif + +#ifdef TEST_CERT_EVENTS +/* Events seen by certEventLogCb. */ +typedef struct CertEventLog { + int defects; + int results; + int infos; + int notices; + word32 noticeConds; /* Bit (condition - first strict one) + * for each NOTICE seen. */ + WOLFSSL_CERT_EVENT notice; /* Last NOTICE event. */ + WOLFSSL_CERT_EVENT info; /* Last INFO event. */ + WOLFSSL_CERT_EVENT defect; /* Last DEFECT event. */ + WOLFSSL_CERT_EVENT result; /* Last RESULT event. */ + WOLFSSL_CERT_EVENT all[4]; /* First DEFECT events, in order. */ + byte oid[16]; /* Last DEFECT's extension OID, copied: the + * event's pointers are only valid during + * the callback. */ + word32 oidSz; + char subject[256]; /* Last DEFECT's or RESULT's subject, + * copied. */ + word32 serialSz; /* Its serial number's size. */ +} CertEventLog; + +/* Copy what the event says about which certificate it is. */ +static void certEventLogIdentity(CertEventLog* log, + const WOLFSSL_CERT_EVENT* ev) +{ + log->subject[0] = '\0'; + if (ev->subject != NULL) { + XSTRNCPY(log->subject, ev->subject, sizeof(log->subject) - 1); + log->subject[sizeof(log->subject) - 1] = '\0'; + } + log->serialSz = (ev->serial != NULL) ? ev->serialSz : 0; +} + +static void certEventLogCb(const WOLFSSL_CERT_EVENT* ev, void* ctx) +{ + CertEventLog* log = (CertEventLog*)ctx; + + if (ev->kind == WC_CEV_KIND_DEFECT) { + if (log->defects < (int)XELEM_CNT(log->all)) { + log->all[log->defects] = *ev; + } + log->defects++; + log->defect = *ev; + certEventLogIdentity(log, ev); + log->oidSz = 0; + if ((ev->extOidData != NULL) && (ev->extOidSz <= sizeof(log->oid))) { + XMEMCPY(log->oid, ev->extOidData, ev->extOidSz); + log->oidSz = ev->extOidSz; + } + } + else if (ev->kind == WC_CEV_KIND_RESULT) { + log->results++; + log->result = *ev; + certEventLogIdentity(log, ev); + } + else if (ev->kind == WC_CEV_KIND_INFO) { + log->infos++; + log->info = *ev; + } + else if (ev->kind == WC_CEV_KIND_NOTICE) { + log->notices++; + log->notice = *ev; + if (ev->condition >= WC_CEV_COND_CERT_UNIQUE_ID) { + log->noticeConds |= + (word32)1 << (ev->condition - WC_CEV_COND_CERT_UNIQUE_ID); + } + log->oidSz = 0; + if ((ev->extOidData != NULL) && (ev->extOidSz <= sizeof(log->oid))) { + XMEMCPY(log->oid, ev->extOidData, ev->extOidSz); + log->oidSz = ev->extOidSz; + } + } +} + +/* Bit in noticeConds for a strict check's condition. */ +#define CEV_NOTICE_BIT(cond) \ + ((word32)1 << ((cond) - WC_CEV_COND_CERT_UNIQUE_ID)) + +/* After every test certificate expires, and before 32-bit time_t wraps. */ +static time_t certEventAfterExpiryTimeCb(time_t* t) +{ + time_t when = (time_t)2145916800; /* 2038-01-01 00:00:00 UTC */ + + if (t != NULL) { + *t = when; + } + return when; +} + +#ifdef HAVE_CRL +/* While certs/test/cert-events/crl-expired.der is still valid. */ +static time_t certEventMarch2025TimeCb(time_t* t) +{ + time_t when = (time_t)1740787200; /* 2025-03-01 00:00:00 UTC */ + + if (t != NULL) { + *t = when; + } + return when; +} +#endif + +/* Before certs/server-cert.pem becomes valid. */ +static time_t certEventBeforeValidTimeCb(time_t* t) +{ + time_t when = (time_t)1577836800; /* 2020-01-01 00:00:00 UTC */ + + if (t != NULL) { + *t = when; + } + return when; +} +#endif + +int test_wolfSSL_CertManagerCertEvents(void) +{ + EXPECT_DECLS; +#ifdef TEST_CERT_EVENTS + WOLFSSL_CERT_MANAGER* cm = NULL; + CertEventLog log; + int tmp = myVerifyAction; +#ifdef WOLFSSL_PEM_TO_DER + const char* ca_cert = "./certs/ca-cert.pem"; + const char* svrCert = "./certs/server-cert.pem"; +#else + const char* ca_cert = "./certs/ca-cert.der"; + const char* svrCert = "./certs/server-cert.der"; +#endif + + XMEMSET(&log, 0, sizeof(log)); + + ExpectIntEQ(wolfSSL_CertManagerSetCertEventCb(NULL, certEventLogCb, &log), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wolfSSL_CTX_SetCertEventCb(NULL, certEventLogCb, &log), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + ExpectNotNull(cm = wolfSSL_CertManagerNew()); + ExpectIntEQ(wolfSSL_CertManagerLoadCA(cm, ca_cert, NULL), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CertManagerSetCertEventCb(cm, certEventLogCb, &log), + WOLFSSL_SUCCESS); + + /* A good certificate: no defects, one passed result. */ + ExpectIntEQ(wolfSSL_CertManagerVerify(cm, svrCert, CERT_FILETYPE), + WOLFSSL_SUCCESS); + ExpectIntEQ(log.defects, 0); + ExpectIntEQ(log.results, 1); + ExpectIntEQ(log.result.size, (int)sizeof(WOLFSSL_CERT_EVENT)); + ExpectIntEQ(log.result.error, 0); + ExpectIntEQ(log.result.category, WC_CEV_CAT_CERT); + ExpectIntEQ(log.result.role, WC_CEV_ROLE_CANDIDATE); + ExpectIntEQ(log.result.depth, -1); + ExpectNotNull(log.result.der); + ExpectIntGT(log.result.derSz, 0); + /* Which certificate it was. Not in a TLS handshake: no connection. */ + ExpectNotNull(XSTRSTR(log.subject, "CN=www.wolfssl.com")); + ExpectIntGT(log.serialSz, 0); + ExpectNull(log.result.ssl); + /* One INFO with the algorithms, for the application's own policy. */ + ExpectIntEQ(log.infos, 1); + ExpectIntEQ(log.info.role, WC_CEV_ROLE_CANDIDATE); + ExpectIntEQ(log.info.sigOID, CTC_SHA256wRSA); + ExpectIntEQ(log.info.keyOID, RSAk); + ExpectIntEQ(log.info.keySz, 2048); + ExpectIntEQ(log.info.curveOID, 0); + ExpectIntEQ(log.info.error, 0); + + /* Move the clock past the certificate's notAfter. The defect says where + * and why; the return code is unchanged. */ + ExpectIntEQ(wc_SetTimeCb(certEventAfterExpiryTimeCb), 0); + XMEMSET(&log, 0, sizeof(log)); + ExpectIntEQ(wolfSSL_CertManagerVerify(cm, svrCert, CERT_FILETYPE), + WC_NO_ERR_TRACE(ASN_AFTER_DATE_E)); + ExpectIntEQ(log.defects, 1); + ExpectIntEQ(log.defect.error, WC_NO_ERR_TRACE(ASN_AFTER_DATE_E)); + ExpectIntEQ(log.defect.category, WC_CEV_CAT_CERT); + ExpectIntEQ(log.defect.role, WC_CEV_ROLE_CANDIDATE); + ExpectIntEQ(log.defect.depth, -1); + ExpectIntEQ(log.defect.field, WC_CEV_FIELD_VALIDITY); + ExpectIntEQ(log.defect.reason, WC_CEV_REASON_EXPIRED); + ExpectIntEQ(log.defect.condition, WC_CEV_COND_CERT_EXPIRED); + /* The algorithms are known by the time the dates are checked. */ + ExpectIntEQ(log.defect.keyOID, RSAk); + ExpectIntEQ(log.defect.keySz, 2048); + ExpectIntEQ(log.results, 1); + ExpectIntEQ(log.result.error, WC_NO_ERR_TRACE(ASN_AFTER_DATE_E)); + + /* The verify callback allows it: the defect is still reported, and the + * result says the verification passed. */ + XMEMSET(&log, 0, sizeof(log)); + wolfSSL_CertManagerSetVerify(cm, myVerify); + myVerifyAction = VERIFY_OVERRIDE_ERROR; + ExpectIntEQ(wolfSSL_CertManagerVerify(cm, svrCert, CERT_FILETYPE), + WOLFSSL_SUCCESS); + ExpectIntEQ(log.defects, 1); + ExpectIntEQ(log.results, 1); + ExpectIntEQ(log.result.error, 0); + wolfSSL_CertManagerSetVerify(cm, NULL); + + /* Move the clock before the certificate's notBefore. */ + ExpectIntEQ(wc_SetTimeCb(certEventBeforeValidTimeCb), 0); + XMEMSET(&log, 0, sizeof(log)); + ExpectIntEQ(wolfSSL_CertManagerVerify(cm, svrCert, CERT_FILETYPE), + WC_NO_ERR_TRACE(ASN_BEFORE_DATE_E)); + ExpectIntEQ(log.defects, 1); + ExpectIntEQ(log.defect.error, WC_NO_ERR_TRACE(ASN_BEFORE_DATE_E)); + ExpectIntEQ(log.defect.field, WC_CEV_FIELD_VALIDITY); + ExpectIntEQ(log.defect.reason, WC_CEV_REASON_NOT_YET_VALID); + ExpectIntEQ(log.defect.condition, WC_CEV_COND_CERT_NOT_YET_VALID); + ExpectIntEQ(log.results, 1); + ExpectIntEQ(log.result.error, WC_NO_ERR_TRACE(ASN_BEFORE_DATE_E)); + + /* Clearing the callback stops events. */ + XMEMSET(&log, 0, sizeof(log)); + ExpectIntEQ(wolfSSL_CertManagerSetCertEventCb(cm, NULL, NULL), + WOLFSSL_SUCCESS); + ExpectIntNE(wolfSSL_CertManagerVerify(cm, svrCert, CERT_FILETYPE), + WOLFSSL_SUCCESS); + ExpectIntEQ(log.defects, 0); + ExpectIntEQ(log.results, 0); + + (void)wc_SetTimeCb(NULL); + wolfSSL_CertManagerFree(cm); + myVerifyAction = tmp; +#endif + + return EXPECT_RESULT(); +} + +/* Every broken certificate gets exactly one RESULT event, and it holds the + * same error the API returned. */ +int test_wolfSSL_CertManagerCertEvents_badCerts(void) +{ + EXPECT_DECLS; +#ifdef TEST_CERT_EVENTS + static const struct { + const char* file; + int expected; /* Return code with the ASN template parser. */ + } bad[] = { + #if defined(OPENSSL_ALL) || defined(WOLFSSL_QT) + /* Self-issued with no signer: reported as self-signed for OpenSSL + * compatibility. */ + { "./certs/test/server-garbage.der", ASN_SELF_SIGNED_E }, + #else + { "./certs/test/server-garbage.der", ASN_NO_SIGNER_E }, + #endif + { "./certs/test/cert-bad-oid.der", ASN_PARSE_E }, + { "./certs/test/cert-bad-neg-int.der", ASN_EXPECT_0_E }, + { "./certs/test/cert-bad-utf8.der", ASN_PARSE_E }, + #ifdef HAVE_ECC + { "./certs/test/server-cert-ecc-badsig.der", ASN_SIG_CONFIRM_E }, + { "./certs/test/cert-ext-oid-collide.der", ASN_SIG_CONFIRM_E }, + #endif + }; + WOLFSSL_CERT_MANAGER* cm = NULL; + CertEventLog log; + size_t i; + int ret; +#ifdef WOLFSSL_PEM_TO_DER + const char* ca_cert = "./certs/ca-cert.pem"; + const char* ca_ecc_cert = "./certs/ca-ecc-cert.pem"; +#else + const char* ca_cert = "./certs/ca-cert.der"; + const char* ca_ecc_cert = "./certs/ca-ecc-cert.der"; +#endif + + ExpectNotNull(cm = wolfSSL_CertManagerNew()); + ExpectIntEQ(wolfSSL_CertManagerLoadCA(cm, ca_cert, NULL), + WOLFSSL_SUCCESS); +#ifdef HAVE_ECC + ExpectIntEQ(wolfSSL_CertManagerLoadCA(cm, ca_ecc_cert, NULL), + WOLFSSL_SUCCESS); +#else + (void)ca_ecc_cert; +#endif + ExpectIntEQ(wolfSSL_CertManagerSetCertEventCb(cm, certEventLogCb, &log), + WOLFSSL_SUCCESS); + + for (i = 0; i < sizeof(bad) / sizeof(bad[0]); i++) { + XMEMSET(&log, 0, sizeof(log)); + ret = wolfSSL_CertManagerVerify(cm, bad[i].file, + WOLFSSL_FILETYPE_ASN1); + ExpectIntNE(ret, WOLFSSL_SUCCESS); + #ifdef WOLFSSL_ASN_TEMPLATE + ExpectIntEQ(ret, bad[i].expected); + #endif + ExpectIntEQ(log.results, 1); + ExpectIntEQ(log.result.kind, WC_CEV_KIND_RESULT); + ExpectIntEQ(log.result.error, ret); + if (EXPECT_FAIL()) { + fprintf(stderr, "cert event test failed for %s\n", bad[i].file); + break; + } + } + + wolfSSL_CertManagerFree(cm); +#endif + + return EXPECT_RESULT(); +} + +/* Each generated certificate in certs/test/cert-events has one defect in one + * extension. The event names the extension by its OID and says why it + * failed; the return code is unchanged. */ +int test_wolfSSL_CertEvents_extensions(void) +{ + EXPECT_DECLS; +#ifdef TEST_CERT_EVENTS + static const byte oidSkid[] = { 0x55, 0x1d, 0x0e }; + static const byte oidKu[] = { 0x55, 0x1d, 0x0f }; + static const byte oidSan[] = { 0x55, 0x1d, 0x11 }; + static const byte oidBc[] = { 0x55, 0x1d, 0x13 }; + static const byte oidCrlDp[] = { 0x55, 0x1d, 0x1f }; +#if (defined(WOLFSSL_SEP) || defined(WOLFSSL_CERT_EXT)) && \ + (!defined(WOLFSSL_X509_TINY) || defined(WOLFSSL_X509_TINY_POLICIES)) + static const byte oidPolicy[] = { 0x55, 0x1d, 0x20 }; +#endif + static const byte oidAkid[] = { 0x55, 0x1d, 0x23 }; + static const byte oidEku[] = { 0x55, 0x1d, 0x25 }; + static const byte oidAia[] = { 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, + 0x01, 0x01 }; + /* 1.3.6.1.4.1.99999.1 */ + static const byte oidUnknown[] = { 0x2b, 0x06, 0x01, 0x04, 0x01, 0x86, + 0x8d, 0x1f, 0x01 }; + static const struct { + const char* file; + int error; /* ASN template parser */ + int reason; + word32 oidSum; /* 0 when wolfSSL has no constant */ + const byte* oid; + word32 oidSz; + byte critical; + } cases[] = { + { "ku-malformed", ASN_PARSE_E, WC_CEV_REASON_MALFORMED, + KEY_USAGE_OID, oidKu, sizeof(oidKu), 1 }, + { "bc-malformed", ASN_PARSE_E, WC_CEV_REASON_MALFORMED, + BASIC_CA_OID, oidBc, sizeof(oidBc), 1 }, + /* Tiny builds do not decode some extensions. */ + #if !defined(WOLFSSL_X509_TINY) || defined(WOLFSSL_X509_TINY_AKI) + { "akid-malformed", ASN_PARSE_E, WC_CEV_REASON_MALFORMED, + AUTH_KEY_OID, oidAkid, sizeof(oidAkid), 0 }, + #endif + #if !defined(WOLFSSL_X509_TINY) || defined(WOLFSSL_X509_TINY_SKI) + { "skid-malformed", ASN_PARSE_E, WC_CEV_REASON_MALFORMED, + SUBJ_KEY_OID, oidSkid, sizeof(oidSkid), 0 }, + #endif + { "eku-malformed", ASN_PARSE_E, WC_CEV_REASON_MALFORMED, + EXT_KEY_USAGE_OID, oidEku, sizeof(oidEku), 0 }, + { "san-malformed", ASN_PARSE_E, WC_CEV_REASON_MALFORMED, + ALT_NAMES_OID, oidSan, sizeof(oidSan), 0 }, + #if !defined(WOLFSSL_X509_TINY) || defined(WOLFSSL_X509_TINY_CRL_DP) + { "crldp-malformed", ASN_PARSE_E, WC_CEV_REASON_MALFORMED, + CRL_DIST_OID, oidCrlDp, sizeof(oidCrlDp), 0 }, + #endif + #if !defined(WOLFSSL_X509_TINY) || defined(WOLFSSL_X509_TINY_AIA) + { "aia-malformed", ASN_PARSE_E, WC_CEV_REASON_MALFORMED, + AUTH_INFO_OID, oidAia, sizeof(oidAia), 0 }, + #endif + #if (defined(WOLFSSL_SEP) || defined(WOLFSSL_CERT_EXT)) && \ + (!defined(WOLFSSL_X509_TINY) || defined(WOLFSSL_X509_TINY_POLICIES)) + /* Other builds do not parse certificate policies. */ + { "policy-malformed", ASN_PARSE_E, WC_CEV_REASON_MALFORMED, + CERT_POLICY_OID, oidPolicy, sizeof(oidPolicy), 0 }, + #endif + { "unknown-critical", ASN_CRIT_EXT_E, WC_CEV_REASON_UNSUPPORTED, + 0, oidUnknown, sizeof(oidUnknown), 1 }, + }; + /* Certificates that pass today. The unique IDs are rejected only by the + * optional strict checks, which are not enabled here. The original + * parser does not accept unique IDs at all. */ + static const char* const clean[] = { + "good", + #ifdef WOLFSSL_ASN_TEMPLATE + "issuer-uid", "subject-uid" + #endif + }; + WOLFSSL_CERT_MANAGER* cm = NULL; + CertEventLog log; + char file[64]; + size_t i; + int ret; +#ifdef WOLFSSL_PEM_TO_DER + const char* ca_cert = "./certs/ca-cert.pem"; +#else + const char* ca_cert = "./certs/ca-cert.der"; +#endif + + ExpectNotNull(cm = wolfSSL_CertManagerNew()); + ExpectIntEQ(wolfSSL_CertManagerLoadCA(cm, ca_cert, NULL), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CertManagerSetCertEventCb(cm, certEventLogCb, &log), + WOLFSSL_SUCCESS); + + for (i = 0; i < XELEM_CNT(clean); i++) { + XMEMSET(&log, 0, sizeof(log)); + (void)XSNPRINTF(file, sizeof(file), "./certs/test/cert-events/%s.der", + clean[i]); + ExpectIntEQ(wolfSSL_CertManagerVerify(cm, file, + WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS); + ExpectIntEQ(log.defects, 0); + ExpectIntEQ(log.results, 1); + ExpectIntEQ(log.result.error, 0); + if (EXPECT_FAIL()) { + fprintf(stderr, "cert event test failed for %s\n", file); + break; + } + } + + for (i = 0; i < XELEM_CNT(cases); i++) { + XMEMSET(&log, 0, sizeof(log)); + (void)XSNPRINTF(file, sizeof(file), "./certs/test/cert-events/%s.der", + cases[i].file); + ret = wolfSSL_CertManagerVerify(cm, file, WOLFSSL_FILETYPE_ASN1); + ExpectIntNE(ret, WOLFSSL_SUCCESS); + #ifdef WOLFSSL_ASN_TEMPLATE + ExpectIntEQ(ret, cases[i].error); + #endif + /* One defect, in the extension, with the same error. */ + ExpectIntEQ(log.defects, 1); + ExpectIntEQ(log.defect.error, ret); + ExpectIntEQ(log.defect.role, WC_CEV_ROLE_CANDIDATE); + ExpectIntEQ(log.defect.field, WC_CEV_FIELD_EXTENSION); + ExpectIntEQ(log.defect.reason, cases[i].reason); + ExpectIntEQ(log.defect.condition, + (cases[i].reason == WC_CEV_REASON_UNSUPPORTED) ? + WC_CEV_COND_CERT_EXT_UNKNOWN_CRITICAL : + WC_CEV_COND_CERT_EXT_VALUE); + ExpectIntEQ(log.defect.critical, cases[i].critical); + if (cases[i].oidSum != 0) { + ExpectIntEQ(log.defect.extOID, cases[i].oidSum); + } + ExpectIntEQ(log.oidSz, cases[i].oidSz); + if (EXPECT_SUCCESS()) { + ExpectBufEQ(log.oid, cases[i].oid, cases[i].oidSz); + } + ExpectIntEQ(log.results, 1); + ExpectIntEQ(log.result.error, ret); + if (EXPECT_FAIL()) { + fprintf(stderr, "cert event test failed for %s\n", file); + break; + } + } + + wolfSSL_CertManagerFree(cm); +#endif + + return EXPECT_RESULT(); +} + +/* Each generated certificate in certs/test/cert-events has one defect in a + * field outside the extensions. The event names the field, and its error is + * the one the API returned. */ +int test_wolfSSL_CertEvents_fields(void) +{ + EXPECT_DECLS; +#ifdef TEST_CERT_EVENTS + static const struct { + const char* file; + int field; + int error; /* ASN template parser */ + int reason; + int condition; + } cases[] = { + { "version-unsupported", WC_CEV_FIELD_VERSION, + ASN_PARSE_E, WC_CEV_REASON_MALFORMED, + WC_CEV_COND_CERT_VERSION }, + { "version-not-integer", WC_CEV_FIELD_VERSION, + ASN_PARSE_E, WC_CEV_REASON_MALFORMED, + WC_CEV_COND_CERT_VERSION }, + { "serial-empty", WC_CEV_FIELD_SERIAL, + ASN_PARSE_E, WC_CEV_REASON_MALFORMED, + WC_CEV_COND_CERT_FORMAT }, + { "serial-not-integer", WC_CEV_FIELD_SERIAL, + ASN_PARSE_E, WC_CEV_REASON_MALFORMED, + WC_CEV_COND_CERT_FORMAT }, + /* An unknown algorithm in the TBS no longer matches the outer one. */ + { "tbs-sigalg-unknown", WC_CEV_FIELD_SIGALG, + ASN_SIG_OID_E, WC_CEV_REASON_MALFORMED, + WC_CEV_COND_CERT_SIG_ALG_MISMATCH }, + { "issuer-malformed", WC_CEV_FIELD_ISSUER, + ASN_PARSE_E, WC_CEV_REASON_MALFORMED, + WC_CEV_COND_CERT_ISSUER }, + { "validity-not-time", WC_CEV_FIELD_VALIDITY, + ASN_PARSE_E, WC_CEV_REASON_MALFORMED, + WC_CEV_COND_CERT_FORMAT }, + { "subject-malformed", WC_CEV_FIELD_SUBJECT, + ASN_PARSE_E, WC_CEV_REASON_MALFORMED, + WC_CEV_COND_CERT_SUBJECT }, + { "spki-unknown-alg", WC_CEV_FIELD_SPKI, + ASN_UNKNOWN_OID_E, WC_CEV_REASON_UNSUPPORTED, + WC_CEV_COND_CERT_KEY_UNSUPPORTED }, + { "spki-bad-bitstring", WC_CEV_FIELD_SPKI, + ASN_PARSE_E, WC_CEV_REASON_MALFORMED, + WC_CEV_COND_CERT_FORMAT }, + { "sigalg-mismatch", WC_CEV_FIELD_SIGALG, + ASN_SIG_OID_E, WC_CEV_REASON_MALFORMED, + WC_CEV_COND_CERT_SIG_ALG_MISMATCH }, + { "signature-not-bitstring", WC_CEV_FIELD_SIGNATURE, + ASN_BITSTR_E, WC_CEV_REASON_MALFORMED, + WC_CEV_COND_CERT_FORMAT }, + #ifndef WOLFSSL_NO_ASN_STRICT + /* Only strict builds reject data after the certificate. */ + { "trailing-data", WC_CEV_FIELD_ENCODING, + ASN_PARSE_E, WC_CEV_REASON_MALFORMED, + WC_CEV_COND_CERT_FORMAT }, + #endif /* Failures against the signer. */ + { "leaf-bad-signature", WC_CEV_FIELD_SIGNATURE, + ASN_SIG_CONFIRM_E, WC_CEV_REASON_SIGNATURE, + WC_CEV_COND_CERT_SIG_INVALID }, + { "leaf-unknown-issuer", WC_CEV_FIELD_CHAIN, + ASN_NO_SIGNER_E, WC_CEV_REASON_NO_SIGNER, + WC_CEV_COND_CERT_NO_ISSUER }, + }; + WOLFSSL_CERT_MANAGER* cm = NULL; + CertEventLog log; + char file[64]; + size_t i; + int ret; +#ifdef WOLFSSL_PEM_TO_DER + const char* ca_cert = "./certs/ca-cert.pem"; +#else + const char* ca_cert = "./certs/ca-cert.der"; +#endif + + ExpectNotNull(cm = wolfSSL_CertManagerNew()); + ExpectIntEQ(wolfSSL_CertManagerLoadCA(cm, ca_cert, NULL), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CertManagerSetCertEventCb(cm, certEventLogCb, &log), + WOLFSSL_SUCCESS); + + for (i = 0; i < XELEM_CNT(cases); i++) { + XMEMSET(&log, 0, sizeof(log)); + (void)XSNPRINTF(file, sizeof(file), "./certs/test/cert-events/%s.der", + cases[i].file); + ret = wolfSSL_CertManagerVerify(cm, file, WOLFSSL_FILETYPE_ASN1); + ExpectIntNE(ret, WOLFSSL_SUCCESS); + /* Exactly one defect, even when two checks see the same failure. */ + ExpectIntEQ(log.defects, 1); + ExpectIntEQ(log.defect.error, ret); + ExpectIntEQ(log.defect.role, WC_CEV_ROLE_CANDIDATE); + ExpectIntEQ(log.defect.reason, cases[i].reason); + ExpectIntEQ(log.defect.condition, cases[i].condition); + ExpectIntEQ(log.defect.extOID, 0); + ExpectNull(log.defect.extOidData); + ExpectIntEQ(log.defect.field, cases[i].field); + #ifdef WOLFSSL_ASN_TEMPLATE + ExpectIntEQ(ret, cases[i].error); + #endif + ExpectIntEQ(log.results, 1); + ExpectIntEQ(log.result.error, ret); + if (EXPECT_FAIL()) { + fprintf(stderr, "cert event test failed for %s\n", file); + break; + } + } + + wolfSSL_CertManagerFree(cm); +#endif + + return EXPECT_RESULT(); +} + +/* Parsing without a certificate manager uses the DecodedCert callback. */ +int test_wolfSSL_CertEvents_noCertManager(void) +{ + EXPECT_DECLS; +#ifdef TEST_CERT_EVENTS + DecodedCert cert; + CertEventLog log; + byte* der = NULL; + size_t derSz = 0; + + XMEMSET(&log, 0, sizeof(log)); + ExpectIntEQ(wc_SetCertEventCallback(NULL, certEventLogCb, &log), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + /* A self-signed CA verifies against its own key, so no manager is + * needed. */ + ExpectIntEQ(load_file("./certs/ca-cert.der", &der, &derSz), 0); + + /* Today it parses cleanly: no events. */ + wc_InitDecodedCert(&cert, der, (word32)derSz, NULL); + ExpectIntEQ(wc_SetCertEventCallback(&cert, certEventLogCb, &log), 0); + ExpectIntEQ(wc_ParseCert(&cert, CA_TYPE, VERIFY, NULL), 0); + wc_FreeDecodedCert(&cert); + ExpectIntEQ(log.defects, 0); + + /* In 2038 it has expired: one defect, no RESULT event (results come + * from the certificate manager). */ + ExpectIntEQ(wc_SetTimeCb(certEventAfterExpiryTimeCb), 0); + wc_InitDecodedCert(&cert, der, (word32)derSz, NULL); + ExpectIntEQ(wc_SetCertEventCallback(&cert, certEventLogCb, &log), 0); + ExpectIntEQ(wc_ParseCert(&cert, CA_TYPE, VERIFY, NULL), + WC_NO_ERR_TRACE(ASN_AFTER_DATE_E)); + wc_FreeDecodedCert(&cert); + ExpectIntEQ(log.defects, 1); + ExpectIntEQ(log.defect.field, WC_CEV_FIELD_VALIDITY); + ExpectIntEQ(log.defect.reason, WC_CEV_REASON_EXPIRED); + ExpectIntEQ(log.results, 0); + + /* Without a callback the same failure raises nothing. */ + XMEMSET(&log, 0, sizeof(log)); + wc_InitDecodedCert(&cert, der, (word32)derSz, NULL); + ExpectIntEQ(wc_ParseCert(&cert, CA_TYPE, VERIFY, NULL), + WC_NO_ERR_TRACE(ASN_AFTER_DATE_E)); + wc_FreeDecodedCert(&cert); + ExpectIntEQ(log.defects, 0); + + (void)wc_SetTimeCb(NULL); + XFREE(der, NULL, DYNAMIC_TYPE_TMP_BUFFER); +#endif + + return EXPECT_RESULT(); +} + +#ifdef TEST_CERT_EVENTS +/* Accept every certificate, as a Warn-Allow policy would. */ +static int certEventAllowCb(int preverify, WOLFSSL_X509_STORE_CTX* store) +{ + (void)preverify; + (void)store; + return 1; +} + +#if defined(TEST_CERT_EVENTS_CRL_DP) && defined(OPENSSL_EXTRA) && \ + defined(HAVE_SSL_MEMIO_TESTS_DEPENDENCIES) +/* Accept a strict check failure only. */ +static int certEventAllowStrictCb(int preverify, WOLFSSL_X509_STORE_CTX* store) +{ + return preverify || + (store->error == WC_NO_ERR_TRACE(ASN_STRICT_NO_CRL_DP_E)); +} +#endif +#endif + +/* The TLS handshake reports each certificate the peer sends, with its + * position in the chain, and one RESULT event for the whole chain. */ +int test_wolfSSL_CertEvents_tls(void) +{ + EXPECT_DECLS; +#if defined(TEST_CERT_EVENTS) && defined(HAVE_SSL_MEMIO_TESTS_DEPENDENCIES) + test_ssl_memio_ctx test_ctx; + CertEventLog log; + int i; + int ret; + + /* Case 1: a good chain. Case 2: the clock is past every certificate's + * notAfter. Case 3: as case 2, with a verify callback that allows it. */ + for (i = 1; i <= 3; i++) { + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + XMEMSET(&log, 0, sizeof(log)); + ret = TEST_FAIL; + + ExpectIntEQ(test_ssl_memio_setup(&test_ctx), TEST_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_SetCertEventCb(test_ctx.c_ctx, + certEventLogCb, &log), WOLFSSL_SUCCESS); + /* The client verifies the server; OpenSSL-compatible defaults would + * otherwise turn verification off. The server does not ask for a + * client certificate, so only the client's checks are exercised. */ + wolfSSL_set_verify(test_ctx.c_ssl, WOLFSSL_VERIFY_PEER, + (i == 3) ? certEventAllowCb : NULL); + wolfSSL_set_verify(test_ctx.s_ssl, WOLFSSL_VERIFY_NONE, NULL); + + if (EXPECT_SUCCESS()) { + if (i > 1) { + (void)wc_SetTimeCb(certEventAfterExpiryTimeCb); + } + ret = test_ssl_memio_do_handshake(&test_ctx, 10, NULL); + (void)wc_SetTimeCb(NULL); + } + + /* One RESULT for the chain, about the peer's own certificate. */ + ExpectIntEQ(log.results, 1); + ExpectIntEQ(log.result.role, WC_CEV_ROLE_CANDIDATE); + ExpectIntEQ(log.result.depth, 0); + ExpectNotNull(log.result.der); + + if (i == 1) { + ExpectIntEQ(ret, TEST_SUCCESS); + ExpectIntEQ(log.defects, 0); + ExpectIntEQ(log.result.error, 0); + } + else { + int j; + + /* Each expired certificate is reported with its own position: + * depth 0 is the peer, deeper ones are the CAs that signed it. */ + ExpectIntGE(log.defects, 1); + for (j = 0; j < log.defects && j < (int)XELEM_CNT(log.all); j++) { + ExpectIntEQ(log.all[j].error, + WC_NO_ERR_TRACE(ASN_AFTER_DATE_E)); + ExpectIntEQ(log.all[j].field, WC_CEV_FIELD_VALIDITY); + ExpectIntEQ(log.all[j].reason, WC_CEV_REASON_EXPIRED); + ExpectIntEQ(log.all[j].role, (log.all[j].depth == 0) ? + WC_CEV_ROLE_CANDIDATE : WC_CEV_ROLE_SIGNER); + ExpectIntGE(log.all[j].depth, 0); + /* The connection whose handshake raised it. */ + ExpectPtrEq(log.all[j].ssl, test_ctx.c_ssl); + } + ExpectPtrEq(log.result.ssl, test_ctx.c_ssl); + /* The chain is walked from the CA the peer sent (depth 1) down + * to the peer's certificate (depth 0). */ + ExpectIntEQ(log.all[0].depth, 1); + ExpectIntEQ(log.all[0].role, WC_CEV_ROLE_SIGNER); + if (i == 2) { + /* The walk stops at the first failure, and the handshake + * fails with the error the result holds. */ + ExpectIntEQ(log.defects, 1); + ExpectIntNE(ret, TEST_SUCCESS); + ExpectIntEQ(log.result.error, + WC_NO_ERR_TRACE(ASN_AFTER_DATE_E)); + ExpectIntEQ(log.result.error, + wolfSSL_get_error(test_ctx.c_ssl, 0)); + ExpectIntNE(log.result.error, 0); + } + else { + /* Allowed: the walk goes on to the peer's certificate, every + * defect is still logged, and the chain passes. */ + ExpectIntEQ(log.defects, 2); + ExpectIntEQ(log.all[1].depth, 0); + ExpectIntEQ(log.all[1].role, WC_CEV_ROLE_CANDIDATE); + ExpectIntEQ(ret, TEST_SUCCESS); + ExpectIntEQ(log.result.error, 0); + } + } + + test_ssl_memio_cleanup(&test_ctx); + } +#endif + + return EXPECT_RESULT(); +} + +/* Chains a TLS server sends, each with one fault, and the leaves that fail + * against their signer. Each fault is reported once, on the certificate it + * is about, with its position in the chain. */ +int test_wolfSSL_CertEvents_chain(void) +{ + EXPECT_DECLS; +#if defined(TEST_CERT_EVENTS) && defined(HAVE_SSL_MEMIO_TESTS_DEPENDENCIES) + static const struct { + const char* file; + int error; /* 0 when the chain is good */ + int field; + int reason; + int condition; + int role; + int depth; + int minRsaKeySz; /* 0 to keep the default */ + } cases[] = { + { "chain-good", 0, 0, 0, 0, 0, 0, 0 }, + /* The signer is not taken as a CA, so the server's certificate has + * no signer. */ + /* The peer sent the issuer, but it is not a CA. */ + { "chain-signer-not-ca", ASN_NO_SIGNER_E, WC_CEV_FIELD_CHAIN, + WC_CEV_REASON_NO_SIGNER, WC_CEV_COND_CERT_NOT_CA, + WC_CEV_ROLE_CANDIDATE, 0, 0 }, + { "chain-signer-no-certsign", NOT_CA_ERROR, WC_CEV_FIELD_CHAIN, + WC_CEV_REASON_CONSTRAINT, WC_CEV_COND_CERT_ISSUER_KU, + WC_CEV_ROLE_SIGNER, 1, 0 }, + /* The CA below the pathLenConstraint 0 one breaks it. */ + { "chain-pathlen-exceeded", ASN_PATHLEN_INV_E, WC_CEV_FIELD_CHAIN, + WC_CEV_REASON_CONSTRAINT, WC_CEV_COND_CERT_PATH_LEN, + WC_CEV_ROLE_SIGNER, 1, 0 }, + /* Every key is 2048 bits; ask for more. The CA sent is checked + * first. */ + { "chain-good", RSA_KEY_SIZE_E, WC_CEV_FIELD_SPKI, + WC_CEV_REASON_POLICY, WC_CEV_COND_CERT_KEY_TOO_SMALL, + WC_CEV_ROLE_SIGNER, 1, 4096 }, + #ifndef IGNORE_KEY_EXTENSIONS + /* The server's own certificate may not be used for this. */ + { "chain-leaf-ku-no-digsig", KEYUSE_SIGNATURE_E, + WC_CEV_FIELD_EXTENSION, WC_CEV_REASON_CONSTRAINT, + WC_CEV_COND_CERT_KU_PURPOSE, WC_CEV_ROLE_CANDIDATE, 0, 0 }, + { "chain-leaf-eku-client-only", EXTKEYUSE_AUTH_E, + WC_CEV_FIELD_EXTENSION, WC_CEV_REASON_CONSTRAINT, + WC_CEV_COND_CERT_EKU_PURPOSE, WC_CEV_ROLE_CANDIDATE, 0, 0 }, + #endif + }; + test_ssl_memio_ctx test_ctx; + CertEventLog log; + char file[64]; + size_t i; + int ret; + + for (i = 0; i < XELEM_CNT(cases); i++) { + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + XMEMSET(&log, 0, sizeof(log)); + (void)XSNPRINTF(file, sizeof(file), "./certs/test/cert-events/%s.pem", + cases[i].file); + test_ctx.s_cb.certPemFile = file; + test_ctx.s_cb.keyPemFile = "./certs/server-key.pem"; + ExpectIntEQ(test_ssl_memio_setup(&test_ctx), TEST_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_SetCertEventCb(test_ctx.c_ctx, + certEventLogCb, &log), WOLFSSL_SUCCESS); + wolfSSL_set_verify(test_ctx.c_ssl, WOLFSSL_VERIFY_PEER, NULL); + wolfSSL_set_verify(test_ctx.s_ssl, WOLFSSL_VERIFY_NONE, NULL); + if (cases[i].minRsaKeySz != 0) { + ExpectIntEQ(wolfSSL_SetMinRsaKey_Sz(test_ctx.c_ssl, + (short)cases[i].minRsaKeySz), WOLFSSL_SUCCESS); + } + ret = TEST_FAIL; + if (EXPECT_SUCCESS()) { + ret = test_ssl_memio_do_handshake(&test_ctx, 10, NULL); + } + + ExpectIntEQ(log.results, 1); + if (cases[i].error == 0) { + ExpectIntEQ(ret, TEST_SUCCESS); + ExpectIntEQ(log.defects, 0); + ExpectIntEQ(log.result.error, 0); + ExpectIntEQ(log.result.condition, WC_CEV_COND_NONE); + /* One INFO for each certificate: the CA, then the server's. */ + ExpectIntEQ(log.infos, 2); + ExpectIntEQ(log.info.depth, 0); + ExpectIntEQ(log.info.keySz, 2048); + } + else { + ExpectIntNE(ret, TEST_SUCCESS); + ExpectIntEQ(wolfSSL_get_error(test_ctx.c_ssl, 0), + cases[i].error); + /* One defect, on the certificate at fault. */ + ExpectIntEQ(log.defects, 1); + ExpectIntEQ(log.defect.error, cases[i].error); + ExpectIntEQ(log.defect.field, cases[i].field); + ExpectIntEQ(log.defect.reason, cases[i].reason); + ExpectIntEQ(log.defect.condition, cases[i].condition); + ExpectIntEQ(log.defect.role, cases[i].role); + ExpectIntEQ(log.defect.depth, cases[i].depth); + ExpectIntEQ(log.result.error, cases[i].error); + } + if (EXPECT_FAIL()) { + fprintf(stderr, "cert event test failed for case %d, %s\n", + (int)i, file); + } + + test_ssl_memio_cleanup(&test_ctx); + if (EXPECT_FAIL()) { + break; + } + } +#endif + + return EXPECT_RESULT(); +} + +/* A server that requires a client certificate and gets none reports it on + * the RESULT event: there is no certificate to report a defect on. */ +int test_wolfSSL_CertEvents_noPeerCert(void) +{ + EXPECT_DECLS; +#if defined(TEST_CERT_EVENTS) && defined(HAVE_SSL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_WOLFSSL_SERVER) + test_ssl_memio_ctx test_ctx; + CertEventLog log; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + XMEMSET(&log, 0, sizeof(log)); + /* The client has no certificate to send. */ + test_ctx.c_cb.certPemFile = ""; + test_ctx.c_cb.keyPemFile = ""; + ExpectIntEQ(test_ssl_memio_setup(&test_ctx), TEST_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_SetCertEventCb(test_ctx.s_ctx, certEventLogCb, + &log), WOLFSSL_SUCCESS); + wolfSSL_set_verify(test_ctx.s_ssl, + WOLFSSL_VERIFY_PEER | WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); + if (EXPECT_SUCCESS()) { + ExpectIntNE(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), + TEST_SUCCESS); + } + ExpectIntEQ(wolfSSL_get_error(test_ctx.s_ssl, 0), + WC_NO_ERR_TRACE(NO_PEER_CERT)); + ExpectIntEQ(log.defects, 0); + ExpectIntEQ(log.results, 1); + ExpectIntEQ(log.result.error, WC_NO_ERR_TRACE(NO_PEER_CERT)); + ExpectIntEQ(log.result.condition, WC_CEV_COND_TLS_NO_PEER_CERT); + ExpectNull(log.result.der); + + test_ssl_memio_cleanup(&test_ctx); +#endif + + return EXPECT_RESULT(); +} + +#if defined(TEST_CERT_EVENTS) && defined(HAVE_SSL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(HAVE_CRL) && defined(HAVE_OCSP) && \ + defined(HAVE_CERTIFICATE_STATUS_REQUEST) && !defined(WOLFSSL_NO_TLS12) +/* Acknowledge status_request, but send no CertificateStatus. */ +static int certEventStatusNoneCb(WOLFSSL* ssl, void* arg) +{ + (void)ssl; + (void)arg; + return WOLFSSL_OCSP_STATUS_CB_NOACK; +} + +static int certEventStatusSrvReady(WOLFSSL_CTX* ctx) +{ + EXPECT_DECLS; + + ExpectIntEQ(wolfSSL_CTX_EnableOCSPStapling(ctx), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_set_tlsext_status_cb(ctx, certEventStatusNoneCb), + WOLFSSL_SUCCESS); + return EXPECT_RESULT(); +} +#endif + +/* A TLS 1.2 client that asks for the server's certificate status checks it + * at ServerHelloDone, so the RESULT event waits for that verdict. Here no + * status arrives and the CRL is missing: the RESULT must say so, not pass. */ +int test_wolfSSL_CertEvents_heldStatus(void) +{ + EXPECT_DECLS; +#if defined(TEST_CERT_EVENTS) && defined(HAVE_SSL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(HAVE_CRL) && defined(HAVE_OCSP) && \ + defined(HAVE_CERTIFICATE_STATUS_REQUEST) && !defined(WOLFSSL_NO_TLS12) + test_ssl_memio_ctx test_ctx; + CertEventLog log; + int ret = TEST_FAIL; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + XMEMSET(&log, 0, sizeof(log)); + test_ctx.c_cb.method = wolfTLSv1_2_client_method; + test_ctx.s_cb.method = wolfTLSv1_2_server_method; + test_ctx.s_cb.ctx_ready = certEventStatusSrvReady; + ExpectIntEQ(test_ssl_memio_setup(&test_ctx), TEST_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_SetCertEventCb(test_ctx.c_ctx, certEventLogCb, + &log), WOLFSSL_SUCCESS); + /* CRL checking with no CRL loaded. */ + ExpectIntEQ(wolfSSL_CTX_EnableCRL(test_ctx.c_ctx, WOLFSSL_CRL_CHECK), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_EnableOCSPStapling(test_ctx.c_ctx), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseOCSPStapling(test_ctx.c_ssl, WOLFSSL_CSR_OCSP, 0), + WOLFSSL_SUCCESS); + wolfSSL_set_verify(test_ctx.c_ssl, WOLFSSL_VERIFY_PEER, NULL); + if (EXPECT_SUCCESS()) { + ret = test_ssl_memio_do_handshake(&test_ctx, 10, NULL); + } + ExpectIntNE(ret, TEST_SUCCESS); + ExpectIntEQ(wolfSSL_get_error(test_ctx.c_ssl, 0), + WC_NO_ERR_TRACE(CRL_MISSING)); + ExpectIntEQ(log.results, 1); + ExpectIntEQ(log.result.error, WC_NO_ERR_TRACE(CRL_MISSING)); + ExpectPtrEq(log.result.ssl, test_ctx.c_ssl); + test_ssl_memio_cleanup(&test_ctx); + + /* With a CRL that does not revoke it, one passed RESULT. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + XMEMSET(&log, 0, sizeof(log)); + test_ctx.c_cb.method = wolfTLSv1_2_client_method; + test_ctx.s_cb.method = wolfTLSv1_2_server_method; + test_ctx.s_cb.ctx_ready = certEventStatusSrvReady; + ExpectIntEQ(test_ssl_memio_setup(&test_ctx), TEST_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_SetCertEventCb(test_ctx.c_ctx, certEventLogCb, + &log), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_EnableCRL(test_ctx.c_ctx, WOLFSSL_CRL_CHECK), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_LoadCRLFile(test_ctx.c_ctx, "./certs/crl/crl.pem", + WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_EnableOCSPStapling(test_ctx.c_ctx), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseOCSPStapling(test_ctx.c_ssl, WOLFSSL_CSR_OCSP, 0), + WOLFSSL_SUCCESS); + wolfSSL_set_verify(test_ctx.c_ssl, WOLFSSL_VERIFY_PEER, NULL); + ret = TEST_FAIL; + if (EXPECT_SUCCESS()) { + ret = test_ssl_memio_do_handshake(&test_ctx, 10, NULL); + } + ExpectIntEQ(ret, TEST_SUCCESS); + ExpectIntEQ(log.results, 1); + ExpectIntEQ(log.result.error, 0); + ExpectPtrEq(log.result.ssl, test_ctx.c_ssl); + test_ssl_memio_cleanup(&test_ctx); +#endif + + return EXPECT_RESULT(); +} + +/* A CA the application loads as trusted is reported in the trust anchor + * role. */ +int test_wolfSSL_CertEvents_trustAnchor(void) +{ + EXPECT_DECLS; +#ifdef TEST_CERT_EVENTS + WOLFSSL_CERT_MANAGER* cm = NULL; + CertEventLog log; + byte* der = NULL; + size_t derSz = 0; + + XMEMSET(&log, 0, sizeof(log)); + ExpectNotNull(cm = wolfSSL_CertManagerNew()); + ExpectIntEQ(wolfSSL_CertManagerSetCertEventCb(cm, certEventLogCb, &log), + WOLFSSL_SUCCESS); + + /* A good CA: its algorithms, and no defect. */ + ExpectIntEQ(load_file("./certs/ca-cert.der", &der, &derSz), 0); + ExpectIntEQ(wolfSSL_CertManagerLoadCABuffer_ex(cm, der, (long)derSz, + WOLFSSL_FILETYPE_ASN1, 0, WOLFSSL_LOAD_FLAG_NONE), WOLFSSL_SUCCESS); + XFREE(der, NULL, DYNAMIC_TYPE_TMP_BUFFER); + der = NULL; + ExpectIntEQ(log.defects, 0); + ExpectIntEQ(log.infos, 1); + ExpectIntEQ(log.info.role, WC_CEV_ROLE_TRUST_ANCHOR); + ExpectIntEQ(log.info.depth, -1); + ExpectIntEQ(log.info.keySz, 2048); + + /* An expired CA, loaded without permission to ignore its dates. */ + XMEMSET(&log, 0, sizeof(log)); + ExpectIntEQ(load_file("./certs/test/expired/expired-ca.der", &der, + &derSz), 0); + ExpectIntEQ(wolfSSL_CertManagerLoadCABuffer_ex(cm, der, (long)derSz, + WOLFSSL_FILETYPE_ASN1, 0, WOLFSSL_LOAD_FLAG_NONE), + WC_NO_ERR_TRACE(ASN_AFTER_DATE_E)); + XFREE(der, NULL, DYNAMIC_TYPE_TMP_BUFFER); + ExpectIntEQ(log.defects, 1); + ExpectIntEQ(log.defect.role, WC_CEV_ROLE_TRUST_ANCHOR); + ExpectIntEQ(log.defect.depth, -1); + ExpectIntEQ(log.defect.condition, WC_CEV_COND_CERT_EXPIRED); + + wolfSSL_CertManagerFree(cm); +#endif + + return EXPECT_RESULT(); +} + +/* The INFO event gives the key type, size and curve of an ECC key. */ +int test_wolfSSL_CertEvents_infoEcc(void) +{ + EXPECT_DECLS; +#if defined(TEST_CERT_EVENTS) && defined(HAVE_ECC) && !defined(NO_ECC256) && \ + !defined(NO_ECC_SECP) + WOLFSSL_CERT_MANAGER* cm = NULL; + CertEventLog log; +#ifdef WOLFSSL_PEM_TO_DER + const char* ca_cert = "./certs/ca-ecc-cert.pem"; +#else + const char* ca_cert = "./certs/ca-ecc-cert.der"; +#endif + + XMEMSET(&log, 0, sizeof(log)); + ExpectNotNull(cm = wolfSSL_CertManagerNew()); + ExpectIntEQ(wolfSSL_CertManagerLoadCA(cm, ca_cert, NULL), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CertManagerSetCertEventCb(cm, certEventLogCb, &log), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CertManagerVerify(cm, "./certs/server-ecc.der", + WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS); + ExpectIntEQ(log.infos, 1); + ExpectIntEQ(log.info.keyOID, ECDSAk); + ExpectIntEQ(log.info.curveOID, ECC_SECP256R1_OID); + ExpectIntEQ(log.info.keySz, 256); + ExpectIntEQ(log.info.sigOID, CTC_SHA256wECDSA); + + wolfSSL_CertManagerFree(cm); +#endif + + return EXPECT_RESULT(); +} + +#if defined(TEST_CERT_EVENTS) && defined(HAVE_CRL) +/* A certificate manager that trusts ca-cert, with CRL checking on. */ +static WOLFSSL_CERT_MANAGER* certEventCrlCm(CertEventLog* log) +{ + WOLFSSL_CERT_MANAGER* cm = wolfSSL_CertManagerNew(); + byte* der = NULL; + size_t derSz = 0; + + if ((cm == NULL) || + (load_file("./certs/ca-cert.der", &der, &derSz) != 0) || + (wolfSSL_CertManagerLoadCABuffer(cm, der, (long)derSz, + WOLFSSL_FILETYPE_ASN1) != WOLFSSL_SUCCESS) || + (wolfSSL_CertManagerEnableCRL(cm, WOLFSSL_CRL_CHECK) != + WOLFSSL_SUCCESS) || + (wolfSSL_CertManagerSetCertEventCb(cm, certEventLogCb, log) != + WOLFSSL_SUCCESS)) { + wolfSSL_CertManagerFree(cm); + cm = NULL; + } + XFREE(der, NULL, DYNAMIC_TYPE_TMP_BUFFER); + return cm; +} + +/* Load a CRL from certs/test/cert-events. */ +static int certEventLoadCrl(WOLFSSL_CERT_MANAGER* cm, const char* name) +{ + char file[64]; + byte* der = NULL; + size_t derSz = 0; + int ret; + + (void)XSNPRINTF(file, sizeof(file), "./certs/test/cert-events/%s.der", + name); + ret = load_file(file, &der, &derSz); + if (ret == 0) { + ret = wolfSSL_CertManagerLoadCRLBuffer(cm, der, (long)derSz, + WOLFSSL_FILETYPE_ASN1); + } + XFREE(der, NULL, DYNAMIC_TYPE_TMP_BUFFER); + return ret; +} +#endif + +/* Checking a certificate against CRLs reports on that certificate why it + * failed: revoked, no CRL from its issuer, or the CRL has expired. */ +int test_wolfSSL_CertEvents_crlCheck(void) +{ + EXPECT_DECLS; +#if defined(TEST_CERT_EVENTS) && defined(HAVE_CRL) + static const struct { + const char* crl; /* NULL to load no CRL */ + int error; + int field; + int reason; + int condition; + } cases[] = { + { "crl-good", 0, 0, 0, 0 }, + { "crl-revokes-good", CRL_CERT_REVOKED, WC_CEV_FIELD_CRL_REVOKED, + WC_CEV_REASON_REVOKED, WC_CEV_COND_CERT_REVOKED }, + { NULL, CRL_MISSING, WC_CEV_FIELD_CHAIN, WC_CEV_REASON_MISSING, + WC_CEV_COND_CRL_MISSING }, + #ifndef WOLFSSL_NO_CRL_DATE_CHECK + /* Loaded in March 2025, while it was still valid, and used now. */ + { "crl-expired", CRL_CERT_DATE_ERR, WC_CEV_FIELD_CRL_NEXT_UPDATE, + WC_CEV_REASON_EXPIRED, WC_CEV_COND_CRL_EXPIRED }, + #endif + }; + WOLFSSL_CERT_MANAGER* cm = NULL; + CertEventLog log; + size_t i; + + for (i = 0; i < XELEM_CNT(cases); i++) { + XMEMSET(&log, 0, sizeof(log)); + ExpectNotNull(cm = certEventCrlCm(&log)); + if (cases[i].crl != NULL) { + if (cases[i].error == WC_NO_ERR_TRACE(CRL_CERT_DATE_ERR)) { + ExpectIntEQ(wc_SetTimeCb(certEventMarch2025TimeCb), 0); + } + ExpectIntEQ(certEventLoadCrl(cm, cases[i].crl), WOLFSSL_SUCCESS); + (void)wc_SetTimeCb(NULL); + } + ExpectIntEQ(log.defects, 0); + ExpectIntEQ(wolfSSL_CertManagerVerify(cm, + "./certs/test/cert-events/good.der", WOLFSSL_FILETYPE_ASN1), + (cases[i].error == 0) ? WOLFSSL_SUCCESS : cases[i].error); + ExpectIntEQ(log.results, 1); + ExpectIntEQ(log.result.error, cases[i].error); + if (cases[i].error == 0) { + ExpectIntEQ(log.defects, 0); + } + else { + ExpectIntEQ(log.defects, 1); + ExpectIntEQ(log.defect.category, WC_CEV_CAT_CERT); + ExpectIntEQ(log.defect.role, WC_CEV_ROLE_CANDIDATE); + ExpectIntEQ(log.defect.error, cases[i].error); + ExpectIntEQ(log.defect.field, cases[i].field); + ExpectIntEQ(log.defect.reason, cases[i].reason); + ExpectIntEQ(log.defect.condition, cases[i].condition); + } + if (EXPECT_FAIL()) { + fprintf(stderr, "cert event test failed for case %d\n", (int)i); + } + wolfSSL_CertManagerFree(cm); + cm = NULL; + if (EXPECT_FAIL()) { + break; + } + } +#endif + + return EXPECT_RESULT(); +} + +/* A CRL that is rejected when loaded is reported in the CRL category. */ +int test_wolfSSL_CertEvents_crlLoad(void) +{ + EXPECT_DECLS; +#if defined(TEST_CERT_EVENTS) && defined(HAVE_CRL) + static const struct { + const char* crl; + int error; + int field; + int reason; + int condition; + } cases[] = { + { "crl-bad-signature", ASN_CRL_CONFIRM_E, WC_CEV_FIELD_SIGNATURE, + WC_CEV_REASON_SIGNATURE, WC_CEV_COND_CRL_SIG_INVALID }, + { "crl-unknown-issuer", ASN_CRL_NO_SIGNER_E, WC_CEV_FIELD_CHAIN, + WC_CEV_REASON_NO_SIGNER, WC_CEV_COND_CRL_NO_SIGNER }, + #if !defined(WOLFSSL_X509_TINY) && !defined(IGNORE_KEY_EXTENSIONS) + /* Tiny builds report it as an unknown issuer, and the key usage is + * not checked with IGNORE_KEY_EXTENSIONS. */ + { "crl-signer-no-crlsign", ASN_CRL_NO_SIGNER_E, WC_CEV_FIELD_CHAIN, + WC_CEV_REASON_CONSTRAINT, WC_CEV_COND_CRL_SIGNER_KU }, + #endif + { "crl-malformed", ASN_PARSE_E, WC_CEV_FIELD_ENCODING, + WC_CEV_REASON_MALFORMED, WC_CEV_COND_CRL_FORMAT }, + { "crl-unknown-critical", ASN_CRIT_EXT_E, WC_CEV_FIELD_EXTENSION, + WC_CEV_REASON_UNSUPPORTED, WC_CEV_COND_CRL_EXT_UNKNOWN_CRITICAL }, + #ifndef WOLFSSL_NO_CRL_DATE_CHECK + /* Loading checks nextUpdate too. */ + { "crl-expired", CRL_CERT_DATE_ERR, WC_CEV_FIELD_CRL_NEXT_UPDATE, + WC_CEV_REASON_EXPIRED, WC_CEV_COND_CRL_EXPIRED }, + #endif + }; + WOLFSSL_CERT_MANAGER* cm = NULL; + CertEventLog log; + byte* der = NULL; + size_t derSz = 0; + size_t i; + int ret; + + for (i = 0; i < XELEM_CNT(cases); i++) { + XMEMSET(&log, 0, sizeof(log)); + ExpectNotNull(cm = certEventCrlCm(&log)); + /* The CA that may not sign CRLs is trusted for this one. */ + if (XSTRCMP(cases[i].crl, "crl-signer-no-crlsign") == 0) { + ExpectIntEQ(load_file("./certs/test/cert-events/ca-no-crlsign.der", + &der, &derSz), 0); + ExpectIntEQ(wolfSSL_CertManagerLoadCABuffer(cm, der, (long)derSz, + WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS); + XFREE(der, NULL, DYNAMIC_TYPE_TMP_BUFFER); + der = NULL; + XMEMSET(&log, 0, sizeof(log)); + } + ret = certEventLoadCrl(cm, cases[i].crl); + ExpectIntNE(ret, WOLFSSL_SUCCESS); + #ifdef WOLFSSL_ASN_TEMPLATE + ExpectIntEQ(ret, cases[i].error); + #endif + ExpectIntEQ(log.defects, 1); + ExpectIntEQ(log.defect.category, WC_CEV_CAT_CRL); + ExpectIntEQ(log.defect.role, WC_CEV_ROLE_CRL); + ExpectIntEQ(log.defect.error, ret); + #ifndef WOLFSSL_ASN_TEMPLATE + /* The original parser returns a generic error for some of these. */ + if (ret != cases[i].error) { + ExpectIntEQ(log.defect.condition, WC_CEV_COND_CRL_FORMAT); + } + else + #endif + { + ExpectIntEQ(log.defect.field, cases[i].field); + ExpectIntEQ(log.defect.reason, cases[i].reason); + ExpectIntEQ(log.defect.condition, cases[i].condition); + } + /* Known once decoding got that far. */ + if (log.defect.condition != WC_CEV_COND_CRL_FORMAT) { + ExpectIntEQ(log.defect.sigOID, CTC_SHA256wRSA); + } + if (EXPECT_FAIL()) { + fprintf(stderr, "cert event test failed for %s\n", cases[i].crl); + } + wolfSSL_CertManagerFree(cm); + cm = NULL; + if (EXPECT_FAIL()) { + break; + } + } +#endif + + return EXPECT_RESULT(); +} + +/* Each strict check reports a NOTICE and leaves the result alone, until it + * is enforced: then it fails with its own error and reports a DEFECT. */ +int test_wolfSSL_CertEvents_strict(void) +{ + EXPECT_DECLS; +#ifdef TEST_CERT_EVENTS + static const byte akidOid[] = { 0x55, 0x1d, 0x23 }; + static const byte kuOid[] = { 0x55, 0x1d, 0x0f }; + static const byte crlDpOid[] = { 0x55, 0x1d, 0x1f }; + static const byte aiaOid[] = { 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, + 0x01, 0x01 }; + static const struct { + const char* file; + word32 check; /* 0 when no check fires */ + int error; + int condition; + int field; + int reason; + const byte* oid; + word32 oidSz; + } cases[] = { + { "strict-good", 0, 0, 0, 0, 0, NULL, 0 }, + /* Tiny builds do not decode the extensions some checks look at. */ + #if !defined(WOLFSSL_X509_TINY) || defined(WOLFSSL_X509_TINY_AKI) + { "strict-no-akid", WC_CEV_STRICT_AKID, ASN_STRICT_NO_AKID_E, + WC_CEV_COND_CERT_NO_AKID, WC_CEV_FIELD_EXTENSION, + WC_CEV_REASON_MISSING, akidOid, sizeof(akidOid) }, + #endif + { "strict-ca-no-ku", WC_CEV_STRICT_KEY_USAGE, + ASN_STRICT_NO_KEY_USAGE_E, WC_CEV_COND_CERT_NO_KEY_USAGE, + WC_CEV_FIELD_EXTENSION, WC_CEV_REASON_MISSING, kuOid, + sizeof(kuOid) }, + #if !defined(WOLFSSL_X509_TINY) || defined(WOLFSSL_X509_TINY_CRL_DP) + { "strict-no-crldp", WC_CEV_STRICT_CRL_DP, ASN_STRICT_NO_CRL_DP_E, + WC_CEV_COND_CERT_NO_CRL_DP, WC_CEV_FIELD_EXTENSION, + WC_CEV_REASON_MISSING, crlDpOid, sizeof(crlDpOid) }, + #endif + #if !defined(WOLFSSL_X509_TINY) || defined(WOLFSSL_X509_TINY_AIA) + /* Has authorityInfoAccess, but only a caIssuers entry. */ + { "strict-no-ocsp", WC_CEV_STRICT_OCSP, ASN_STRICT_NO_OCSP_E, + WC_CEV_COND_CERT_NO_OCSP, WC_CEV_FIELD_EXTENSION, + WC_CEV_REASON_MISSING, aiaOid, sizeof(aiaOid) }, + #endif + #ifdef WOLFSSL_ASN_TEMPLATE + /* The original parser rejects unique IDs outright. */ + { "issuer-uid", WC_CEV_STRICT_UNIQUE_ID, ASN_STRICT_UNIQUE_ID_E, + WC_CEV_COND_CERT_UNIQUE_ID, WC_CEV_FIELD_ISSUER_UID, + WC_CEV_REASON_POLICY, NULL, 0 }, + { "subject-uid", WC_CEV_STRICT_UNIQUE_ID, ASN_STRICT_UNIQUE_ID_E, + WC_CEV_COND_CERT_UNIQUE_ID, WC_CEV_FIELD_SUBJECT_UID, + WC_CEV_REASON_POLICY, NULL, 0 }, + #endif + }; + WOLFSSL_CERT_MANAGER* cm = NULL; + CertEventLog log; + char file[64]; + size_t i; + + ExpectNotNull(cm = wolfSSL_CertManagerNew()); + ExpectIntEQ(wolfSSL_CertManagerLoadCA(cm, "./certs/ca-cert.pem", NULL), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CertManagerSetCertEventStrict(NULL, 0), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wolfSSL_CertManagerSetCertEventStrict(cm, + WC_CEV_STRICT_ALL + 1), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); +#if defined(WOLFSSL_X509_TINY) && !defined(WOLFSSL_X509_TINY_CRL_DP) + /* A check this build cannot run is refused, but ALL is accepted. */ + ExpectIntEQ(wolfSSL_CertManagerSetCertEventStrict(cm, + WC_CEV_STRICT_CRL_DP), WC_NO_ERR_TRACE(NOT_COMPILED_IN)); +#endif + ExpectIntEQ(wolfSSL_CertManagerSetCertEventCb(cm, certEventLogCb, &log), + WOLFSSL_SUCCESS); + + for (i = 0; i < XELEM_CNT(cases); i++) { + (void)XSNPRINTF(file, sizeof(file), "./certs/test/cert-events/%s.der", + cases[i].file); + + /* Reported only: the certificate passes. */ + XMEMSET(&log, 0, sizeof(log)); + ExpectIntEQ(wolfSSL_CertManagerSetCertEventStrict(cm, 0), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CertManagerVerify(cm, file, WOLFSSL_FILETYPE_ASN1), + WOLFSSL_SUCCESS); + ExpectIntEQ(log.defects, 0); + ExpectIntEQ(log.result.error, 0); + if (cases[i].check == 0) { + ExpectIntEQ(log.notices, 0); + } + else { + ExpectIntGE(log.notices, 1); + ExpectIntNE(log.noticeConds & CEV_NOTICE_BIT(cases[i].condition), + 0); + } + /* The strict-*.der files have exactly one problem: check it all. */ + if ((cases[i].check != 0) && (XSTRNCMP(cases[i].file, "strict-", + 7) == 0)) { + ExpectIntEQ(log.notices, 1); + ExpectIntEQ(log.notice.category, WC_CEV_CAT_CERT); + ExpectIntEQ(log.notice.role, WC_CEV_ROLE_CANDIDATE); + ExpectIntEQ(log.notice.error, cases[i].error); + ExpectIntEQ(log.notice.condition, cases[i].condition); + ExpectIntEQ(log.notice.field, cases[i].field); + ExpectIntEQ(log.notice.reason, cases[i].reason); + ExpectIntEQ(log.notice.critical, 0); + ExpectNotNull(log.notice.der); + ExpectIntEQ(log.oidSz, cases[i].oidSz); + if ((cases[i].oid != NULL) && (log.oidSz == cases[i].oidSz)) { + ExpectIntEQ(XMEMCMP(log.oid, cases[i].oid, log.oidSz), 0); + } + } + + /* Enforced: the certificate fails with the check's own error. */ + XMEMSET(&log, 0, sizeof(log)); + ExpectIntEQ(wolfSSL_CertManagerSetCertEventStrict(cm, + (cases[i].check == 0) ? (word32)WC_CEV_STRICT_ALL : + cases[i].check), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CertManagerVerify(cm, file, WOLFSSL_FILETYPE_ASN1), + (cases[i].check == 0) ? WOLFSSL_SUCCESS : cases[i].error); + if (cases[i].check == 0) { + ExpectIntEQ(log.defects, 0); + ExpectIntEQ(log.result.error, 0); + } + else { + ExpectIntEQ(log.defects, 1); + ExpectIntEQ(log.defect.error, cases[i].error); + ExpectIntEQ(log.defect.condition, cases[i].condition); + ExpectIntEQ(log.defect.field, cases[i].field); + ExpectIntEQ(log.result.error, cases[i].error); + ExpectIntEQ(log.noticeConds & CEV_NOTICE_BIT(cases[i].condition), + 0); + } + + if (EXPECT_FAIL()) { + fprintf(stderr, "cert event test failed for %s\n", file); + break; + } + } + +#if !defined(WOLFSSL_X509_TINY) || defined(WOLFSSL_X509_TINY_AKI) + /* A verify callback may allow an enforced check: the DEFECT is still + * reported, and the RESULT says it passed. */ + XMEMSET(&log, 0, sizeof(log)); + ExpectIntEQ(wolfSSL_CertManagerSetCertEventStrict(cm, WC_CEV_STRICT_AKID), + WOLFSSL_SUCCESS); + wolfSSL_CertManagerSetVerify(cm, certEventAllowCb); + ExpectIntEQ(wolfSSL_CertManagerVerify(cm, + "./certs/test/cert-events/strict-no-akid.der", WOLFSSL_FILETYPE_ASN1), + WOLFSSL_SUCCESS); + ExpectIntEQ(log.defects, 1); + ExpectIntEQ(log.defect.condition, WC_CEV_COND_CERT_NO_AKID); + ExpectIntEQ(log.result.error, 0); + wolfSSL_CertManagerSetVerify(cm, NULL); + + /* Enforced with no callback: the check still runs. */ + ExpectIntEQ(wolfSSL_CertManagerSetCertEventCb(cm, NULL, NULL), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CertManagerSetCertEventStrict(cm, WC_CEV_STRICT_AKID), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CertManagerVerify(cm, + "./certs/test/cert-events/strict-no-akid.der", WOLFSSL_FILETYPE_ASN1), + WC_NO_ERR_TRACE(ASN_STRICT_NO_AKID_E)); + /* Self-issued certificates need no AKID. */ + ExpectIntEQ(wolfSSL_CertManagerVerify(cm, "./certs/ca-cert.pem", + WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS); +#endif + + /* Each error has its own string. */ + ExpectStrEQ(wc_GetErrorString(ASN_STRICT_NO_AKID_E), + "Certificate has no authority key identifier, rejected by strict " + "checks"); + + wolfSSL_CertManagerFree(cm); +#endif + + return EXPECT_RESULT(); +} + +/* A trust anchor is checked when loaded: a CA without keyUsage. The + * self-issued certs/ca-cert.pem has none either. */ +int test_wolfSSL_CertEvents_strictTrustAnchor(void) +{ + EXPECT_DECLS; +#ifdef TEST_CERT_EVENTS + WOLFSSL_CERT_MANAGER* cm = NULL; + CertEventLog log; + const char* file = "./certs/test/cert-events/strict-ca-no-ku.der"; + byte* der = NULL; + size_t derSz = 0; + + XMEMSET(&log, 0, sizeof(log)); + ExpectNotNull(cm = wolfSSL_CertManagerNew()); + ExpectIntEQ(wolfSSL_CertManagerSetCertEventCb(cm, certEventLogCb, &log), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CertManagerLoadCA(cm, "./certs/ca-cert.pem", NULL), + WOLFSSL_SUCCESS); + ExpectIntEQ(log.notices, 1); + ExpectIntEQ(log.notice.condition, WC_CEV_COND_CERT_NO_KEY_USAGE); + + XMEMSET(&log, 0, sizeof(log)); + ExpectIntEQ(load_file(file, &der, &derSz), 0); + ExpectIntEQ(wolfSSL_CertManagerLoadCABuffer(cm, der, (long)derSz, + WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS); + ExpectIntEQ(log.notices, 1); + ExpectIntEQ(log.notice.condition, WC_CEV_COND_CERT_NO_KEY_USAGE); + ExpectIntEQ(log.notice.role, WC_CEV_ROLE_TRUST_ANCHOR); + + XMEMSET(&log, 0, sizeof(log)); + ExpectIntEQ(wolfSSL_CertManagerSetCertEventStrict(cm, + WC_CEV_STRICT_KEY_USAGE), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CertManagerLoadCABuffer(cm, der, (long)derSz, + WOLFSSL_FILETYPE_ASN1), WC_NO_ERR_TRACE(ASN_STRICT_NO_KEY_USAGE_E)); + ExpectIntEQ(log.defects, 1); + ExpectIntEQ(log.defect.error, WC_NO_ERR_TRACE(ASN_STRICT_NO_KEY_USAGE_E)); + ExpectIntEQ(log.defect.role, WC_CEV_ROLE_TRUST_ANCHOR); + + XFREE(der, NULL, DYNAMIC_TYPE_TMP_BUFFER); + wolfSSL_CertManagerFree(cm); +#endif + + return EXPECT_RESULT(); +} + +/* The strict checks without a certificate manager. */ +int test_wolfSSL_CertEvents_strictNoCertManager(void) +{ + EXPECT_DECLS; +#ifdef TEST_CERT_EVENTS_CRL_DP + DecodedCert cert; + CertEventLog log; + byte* der = NULL; + size_t derSz = 0; + + ExpectIntEQ(wc_SetCertEventStrict(NULL, 0), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(load_file("./certs/test/cert-events/strict-no-crldp.der", + &der, &derSz), 0); + + /* Not turned on: nothing is checked. */ + XMEMSET(&log, 0, sizeof(log)); + wc_InitDecodedCert(&cert, der, (word32)derSz, NULL); + ExpectIntEQ(wc_SetCertEventCallback(&cert, certEventLogCb, &log), 0); + ExpectIntEQ(wc_ParseCert(&cert, CERT_TYPE, NO_VERIFY, NULL), 0); + wc_FreeDecodedCert(&cert); + ExpectIntEQ(log.notices, 0); + + /* Reported only. */ + wc_InitDecodedCert(&cert, der, (word32)derSz, NULL); + ExpectIntEQ(wc_SetCertEventCallback(&cert, certEventLogCb, &log), 0); + ExpectIntEQ(wc_SetCertEventStrict(&cert, 0), 0); + ExpectIntEQ(wc_ParseCert(&cert, CERT_TYPE, NO_VERIFY, NULL), 0); + wc_FreeDecodedCert(&cert); + ExpectIntEQ(log.notices, 1); + ExpectIntEQ(log.notice.condition, WC_CEV_COND_CERT_NO_CRL_DP); + ExpectIntEQ(log.notice.depth, -1); + + /* Enforced. */ + XMEMSET(&log, 0, sizeof(log)); + wc_InitDecodedCert(&cert, der, (word32)derSz, NULL); + ExpectIntEQ(wc_SetCertEventCallback(&cert, certEventLogCb, &log), 0); + ExpectIntEQ(wc_SetCertEventStrict(&cert, WC_CEV_STRICT_CRL_DP), 0); + ExpectIntEQ(wc_ParseCert(&cert, CERT_TYPE, NO_VERIFY, NULL), + WC_NO_ERR_TRACE(ASN_STRICT_NO_CRL_DP_E)); + wc_FreeDecodedCert(&cert); + ExpectIntEQ(log.defects, 1); + ExpectIntEQ(log.defect.condition, WC_CEV_COND_CERT_NO_CRL_DP); + + XFREE(der, NULL, DYNAMIC_TYPE_TMP_BUFFER); +#endif + + return EXPECT_RESULT(); +} + +/* A CRL whose thisUpdate is in the future, checked when loaded. */ +int test_wolfSSL_CertEvents_strictCrl(void) +{ + EXPECT_DECLS; +#if defined(TEST_CERT_EVENTS) && defined(HAVE_CRL) && \ + !defined(WOLFSSL_NO_CRL_DATE_CHECK) + WOLFSSL_CERT_MANAGER* cm = NULL; + CertEventLog log; + + XMEMSET(&log, 0, sizeof(log)); + ExpectNotNull(cm = certEventCrlCm(&log)); + ExpectIntEQ(certEventLoadCrl(cm, "crl-good"), WOLFSSL_SUCCESS); + ExpectIntEQ(log.notices, 0); + wolfSSL_CertManagerFree(cm); + cm = NULL; + + /* Each manager gets one CRL: both have CRL number 1. */ + XMEMSET(&log, 0, sizeof(log)); + ExpectNotNull(cm = certEventCrlCm(&log)); + ExpectIntEQ(certEventLoadCrl(cm, "crl-this-update-future"), + WOLFSSL_SUCCESS); + ExpectIntEQ(log.notices, 1); + ExpectIntEQ(log.defects, 0); + ExpectIntEQ(log.notice.category, WC_CEV_CAT_CRL); + ExpectIntEQ(log.notice.role, WC_CEV_ROLE_CRL); + ExpectIntEQ(log.notice.field, WC_CEV_FIELD_CRL_THIS_UPDATE); + ExpectIntEQ(log.notice.reason, WC_CEV_REASON_POLICY); + ExpectIntEQ(log.notice.condition, WC_CEV_COND_CRL_THIS_UPDATE_FUTURE); + ExpectIntEQ(log.notice.error, + WC_NO_ERR_TRACE(ASN_STRICT_CRL_THIS_UPDATE_E)); + wolfSSL_CertManagerFree(cm); + cm = NULL; + + XMEMSET(&log, 0, sizeof(log)); + ExpectNotNull(cm = certEventCrlCm(&log)); + ExpectIntEQ(wolfSSL_CertManagerSetCertEventStrict(cm, + WC_CEV_STRICT_CRL_THIS_UPDATE), WOLFSSL_SUCCESS); + ExpectIntEQ(certEventLoadCrl(cm, "crl-this-update-future"), + WC_NO_ERR_TRACE(ASN_STRICT_CRL_THIS_UPDATE_E)); + ExpectIntEQ(log.notices, 0); + ExpectIntEQ(log.defects, 1); + ExpectIntEQ(log.defect.category, WC_CEV_CAT_CRL); + ExpectIntEQ(log.defect.condition, WC_CEV_COND_CRL_THIS_UPDATE_FUTURE); + /* The rejected CRL was not kept: no CRL from this issuer. */ + ExpectIntEQ(wolfSSL_CertManagerVerify(cm, + "./certs/test/cert-events/good.der", WOLFSSL_FILETYPE_ASN1), + WC_NO_ERR_TRACE(CRL_MISSING)); + wolfSSL_CertManagerFree(cm); +#elif defined(TEST_CERT_EVENTS) + WOLFSSL_CERT_MANAGER* cm = NULL; + + /* Without CRL dates there is nothing to enforce. */ + ExpectNotNull(cm = wolfSSL_CertManagerNew()); + ExpectIntEQ(wolfSSL_CertManagerSetCertEventStrict(cm, + WC_CEV_STRICT_CRL_THIS_UPDATE), WC_NO_ERR_TRACE(NOT_COMPILED_IN)); + wolfSSL_CertManagerFree(cm); +#endif + + return EXPECT_RESULT(); +} + +/* Revocation is checked before the strict checks, so allowing a strict + * failure does not skip it. good.der has no CRL distribution point. */ +#if defined(TEST_CERT_EVENTS_CRL_DP) && defined(HAVE_CRL) +/* Allow a missing CRL only, as a soft-fail CRL policy would. */ +static int certEventAllowCrlMissingCb(int preverify, + WOLFSSL_X509_STORE_CTX* store) +{ +#ifdef OPENSSL_COMPATIBLE_DEFAULTS + /* The callback sees the OpenSSL error code. */ + return preverify || + (store->error == WOLFSSL_X509_V_ERR_UNABLE_TO_GET_CRL); +#else + return preverify || (store->error == WC_NO_ERR_TRACE(CRL_MISSING)); +#endif +} +#endif + +int test_wolfSSL_CertEvents_strictRevocation(void) +{ + EXPECT_DECLS; +#if defined(TEST_CERT_EVENTS_CRL_DP) && defined(HAVE_CRL) + WOLFSSL_CERT_MANAGER* cm = NULL; + CertEventLog log; + const char* file = "./certs/test/cert-events/good.der"; + + /* Not revoked: the strict check fails it. */ + XMEMSET(&log, 0, sizeof(log)); + ExpectNotNull(cm = certEventCrlCm(&log)); + ExpectIntEQ(wolfSSL_CertManagerSetCertEventStrict(cm, + WC_CEV_STRICT_CRL_DP), WOLFSSL_SUCCESS); + ExpectIntEQ(certEventLoadCrl(cm, "crl-good"), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CertManagerVerify(cm, file, WOLFSSL_FILETYPE_ASN1), + WC_NO_ERR_TRACE(ASN_STRICT_NO_CRL_DP_E)); + ExpectIntEQ(log.defects, 1); + ExpectIntEQ(log.defect.condition, WC_CEV_COND_CERT_NO_CRL_DP); + wolfSSL_CertManagerFree(cm); + cm = NULL; + + /* Revoked: revocation is reported, and the strict checks do not run. */ + XMEMSET(&log, 0, sizeof(log)); + ExpectNotNull(cm = certEventCrlCm(&log)); + ExpectIntEQ(wolfSSL_CertManagerSetCertEventStrict(cm, + WC_CEV_STRICT_CRL_DP), WOLFSSL_SUCCESS); + ExpectIntEQ(certEventLoadCrl(cm, "crl-revokes-good"), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CertManagerVerify(cm, file, WOLFSSL_FILETYPE_ASN1), + WC_NO_ERR_TRACE(CRL_CERT_REVOKED)); + ExpectIntEQ(log.defects, 1); + ExpectIntEQ(log.defect.condition, WC_CEV_COND_CERT_REVOKED); + ExpectIntEQ(log.notices, 0); + ExpectIntEQ(log.result.error, WC_NO_ERR_TRACE(CRL_CERT_REVOKED)); + wolfSSL_CertManagerFree(cm); + cm = NULL; + + /* No CRL, and the verify callback allows that: the strict check still + * runs and fails it. */ + XMEMSET(&log, 0, sizeof(log)); + ExpectNotNull(cm = certEventCrlCm(&log)); + ExpectIntEQ(wolfSSL_CertManagerSetCertEventStrict(cm, + WC_CEV_STRICT_CRL_DP), WOLFSSL_SUCCESS); + wolfSSL_CertManagerSetVerify(cm, certEventAllowCrlMissingCb); + ExpectIntEQ(wolfSSL_CertManagerVerify(cm, file, WOLFSSL_FILETYPE_ASN1), + WC_NO_ERR_TRACE(ASN_STRICT_NO_CRL_DP_E)); + ExpectIntEQ(log.defects, 2); + ExpectIntEQ(log.all[0].condition, WC_CEV_COND_CRL_MISSING); + ExpectIntEQ(log.all[1].condition, WC_CEV_COND_CERT_NO_CRL_DP); + ExpectIntEQ(log.result.error, WC_NO_ERR_TRACE(ASN_STRICT_NO_CRL_DP_E)); + wolfSSL_CertManagerFree(cm); +#endif + + return EXPECT_RESULT(); +} + +/* In TLS, each certificate the peer sends is checked, with its position. The + * chain-good.pem certificates have no CRL distribution point and no OCSP + * responder. */ +int test_wolfSSL_CertEvents_strictTls(void) +{ + EXPECT_DECLS; +#if defined(TEST_CERT_EVENTS_CRL_DP) && \ + defined(HAVE_SSL_MEMIO_TESTS_DEPENDENCIES) + test_ssl_memio_ctx test_ctx; + CertEventLog log; + int i; + int ret; + + ExpectIntEQ(wolfSSL_CTX_SetCertEventStrict(NULL, 0), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + /* Case 0: reported only. Case 1: enforced. Case 2: enforced, and the + * verify callback allows it. */ + for (i = 0; i < 3; i++) { + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + XMEMSET(&log, 0, sizeof(log)); + test_ctx.s_cb.certPemFile = "./certs/test/cert-events/chain-good.pem"; + test_ctx.s_cb.keyPemFile = "./certs/server-key.pem"; + ExpectIntEQ(test_ssl_memio_setup(&test_ctx), TEST_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_SetCertEventCb(test_ctx.c_ctx, + certEventLogCb, &log), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_SetCertEventStrict(test_ctx.c_ctx, + (i == 0) ? 0 : (word32)WC_CEV_STRICT_CRL_DP), WOLFSSL_SUCCESS); + wolfSSL_set_verify(test_ctx.c_ssl, WOLFSSL_VERIFY_PEER, + (i == 2) ? certEventAllowCb : NULL); + wolfSSL_set_verify(test_ctx.s_ssl, WOLFSSL_VERIFY_NONE, NULL); + ret = TEST_FAIL; + if (EXPECT_SUCCESS()) { + ret = test_ssl_memio_do_handshake(&test_ctx, 10, NULL); + } + + if (i == 0) { + /* Both certificates, the CA first. */ + ExpectIntEQ(ret, TEST_SUCCESS); + ExpectIntEQ(log.defects, 0); + ExpectIntEQ(log.notices, 4); + ExpectIntEQ(log.noticeConds, + CEV_NOTICE_BIT(WC_CEV_COND_CERT_NO_CRL_DP) | + CEV_NOTICE_BIT(WC_CEV_COND_CERT_NO_OCSP)); + ExpectIntEQ(log.notice.depth, 0); + ExpectIntEQ(log.notice.role, WC_CEV_ROLE_CANDIDATE); + ExpectIntEQ(log.result.error, 0); + } + else { + /* The CA the server sent fails first. */ + ExpectIntGE(log.defects, 1); + ExpectIntEQ(log.all[0].error, + WC_NO_ERR_TRACE(ASN_STRICT_NO_CRL_DP_E)); + ExpectIntEQ(log.all[0].condition, WC_CEV_COND_CERT_NO_CRL_DP); + ExpectIntEQ(log.all[0].depth, 1); + ExpectIntEQ(log.all[0].role, WC_CEV_ROLE_SIGNER); + if (i == 1) { + ExpectIntNE(ret, TEST_SUCCESS); + ExpectIntEQ(wolfSSL_get_error(test_ctx.c_ssl, 0), + WC_NO_ERR_TRACE(ASN_STRICT_NO_CRL_DP_E)); + ExpectIntEQ(log.result.error, + WC_NO_ERR_TRACE(ASN_STRICT_NO_CRL_DP_E)); + } + else { + ExpectIntEQ(ret, TEST_SUCCESS); + ExpectIntEQ(log.result.error, 0); + } + } + if (EXPECT_FAIL()) { + fprintf(stderr, "cert event test failed for case %d\n", i); + } + + test_ssl_memio_cleanup(&test_ctx); + if (EXPECT_FAIL()) { + break; + } + } + +#ifdef OPENSSL_EXTRA + /* Allowing a strict failure must not skip the checks after it: here the + * expected host name, which does not match. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + XMEMSET(&log, 0, sizeof(log)); + test_ctx.s_cb.certPemFile = "./certs/test/cert-events/chain-good.pem"; + test_ctx.s_cb.keyPemFile = "./certs/server-key.pem"; + ExpectIntEQ(test_ssl_memio_setup(&test_ctx), TEST_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_SetCertEventCb(test_ctx.c_ctx, certEventLogCb, + &log), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_SetCertEventStrict(test_ctx.c_ctx, + WC_CEV_STRICT_CRL_DP), WOLFSSL_SUCCESS); + wolfSSL_set_verify(test_ctx.c_ssl, WOLFSSL_VERIFY_PEER, + certEventAllowStrictCb); + wolfSSL_set_verify(test_ctx.s_ssl, WOLFSSL_VERIFY_NONE, NULL); + ExpectIntEQ(wolfSSL_X509_VERIFY_PARAM_set1_host( + wolfSSL_get0_param(test_ctx.c_ssl), "not-the-server.example", 0), + WOLFSSL_SUCCESS); + ret = TEST_FAIL; + if (EXPECT_SUCCESS()) { + ret = test_ssl_memio_do_handshake(&test_ctx, 10, NULL); + } + ExpectIntNE(ret, TEST_SUCCESS); + ExpectIntEQ(wolfSSL_get_error(test_ctx.c_ssl, 0), + WC_NO_ERR_TRACE(DOMAIN_NAME_MISMATCH)); + ExpectIntEQ(log.result.error, WC_NO_ERR_TRACE(DOMAIN_NAME_MISMATCH)); + test_ssl_memio_cleanup(&test_ctx); +#endif +#endif + + return EXPECT_RESULT(); +} + +/* X509_verify_cert() reports events through the store's certificate + * manager, as used by nginx and other OpenSSL-compatible applications. */ +int test_wolfSSL_X509_STORE_CertEvents(void) +{ + EXPECT_DECLS; +#if defined(TEST_CERT_EVENTS) && defined(OPENSSL_EXTRA) + WOLFSSL_X509_STORE* store = NULL; + WOLFSSL_X509_STORE_CTX* sctx = NULL; + WOLFSSL_X509* ca = NULL; + WOLFSSL_X509* leaf = NULL; + CertEventLog log; + + XMEMSET(&log, 0, sizeof(log)); + ExpectIntEQ(wolfSSL_X509_STORE_SetCertEventCb(NULL, certEventLogCb, + &log), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + ExpectNotNull(store = wolfSSL_X509_STORE_new()); + ExpectNotNull(ca = wolfSSL_X509_load_certificate_file( + "./certs/ca-cert.der", WOLFSSL_FILETYPE_ASN1)); + ExpectIntEQ(wolfSSL_X509_STORE_add_cert(store, ca), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_X509_STORE_SetCertEventCb(store, certEventLogCb, + &log), WOLFSSL_SUCCESS); + ExpectNotNull(leaf = wolfSSL_X509_load_certificate_file( + "./certs/server-cert.der", WOLFSSL_FILETYPE_ASN1)); + + /* A good chain: no defects, and the last result passed. */ + ExpectNotNull(sctx = wolfSSL_X509_STORE_CTX_new()); + ExpectIntEQ(wolfSSL_X509_STORE_CTX_init(sctx, store, leaf, NULL), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_X509_verify_cert(sctx), WOLFSSL_SUCCESS); + ExpectIntEQ(log.defects, 0); + ExpectIntGE(log.results, 1); + ExpectIntEQ(log.result.error, 0); + wolfSSL_X509_STORE_CTX_free(sctx); + sctx = NULL; + + /* An expired leaf: the OpenSSL error is unchanged, and the event says + * which field failed and why. */ + ExpectIntEQ(wc_SetTimeCb(certEventAfterExpiryTimeCb), 0); + XMEMSET(&log, 0, sizeof(log)); + ExpectNotNull(sctx = wolfSSL_X509_STORE_CTX_new()); + ExpectIntEQ(wolfSSL_X509_STORE_CTX_init(sctx, store, leaf, NULL), + WOLFSSL_SUCCESS); + ExpectIntNE(wolfSSL_X509_verify_cert(sctx), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_X509_STORE_CTX_get_error(sctx), + WOLFSSL_X509_V_ERR_CERT_HAS_EXPIRED); + ExpectIntGE(log.defects, 1); + ExpectIntEQ(log.defect.error, WC_NO_ERR_TRACE(ASN_AFTER_DATE_E)); + ExpectIntEQ(log.defect.field, WC_CEV_FIELD_VALIDITY); + ExpectIntEQ(log.defect.reason, WC_CEV_REASON_EXPIRED); + /* The event's depth matches the depth OpenSSL-style callers see. */ + ExpectIntEQ(log.defect.depth, + wolfSSL_X509_STORE_CTX_get_error_depth(sctx)); + ExpectIntEQ(log.defect.role, WC_CEV_ROLE_CANDIDATE); + ExpectIntGE(log.results, 1); + ExpectIntEQ(log.result.error, WC_NO_ERR_TRACE(ASN_AFTER_DATE_E)); + (void)wc_SetTimeCb(NULL); + + wolfSSL_X509_STORE_CTX_free(sctx); + sctx = NULL; + wolfSSL_X509_free(leaf); + leaf = NULL; + + /* An enforced strict check fails X509_verify_cert(). */ + ExpectIntEQ(wolfSSL_X509_STORE_SetCertEventStrict(NULL, 0), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wolfSSL_X509_STORE_SetCertEventStrict(store, + WC_CEV_STRICT_AKID), WOLFSSL_SUCCESS); + ExpectNotNull(leaf = wolfSSL_X509_load_certificate_file( + "./certs/test/cert-events/strict-no-akid.der", WOLFSSL_FILETYPE_ASN1)); + XMEMSET(&log, 0, sizeof(log)); + ExpectNotNull(sctx = wolfSSL_X509_STORE_CTX_new()); + ExpectIntEQ(wolfSSL_X509_STORE_CTX_init(sctx, store, leaf, NULL), + WOLFSSL_SUCCESS); + ExpectIntNE(wolfSSL_X509_verify_cert(sctx), WOLFSSL_SUCCESS); + ExpectIntEQ(log.defects, 1); + ExpectIntEQ(log.defect.condition, WC_CEV_COND_CERT_NO_AKID); + ExpectIntEQ(log.result.error, WC_NO_ERR_TRACE(ASN_STRICT_NO_AKID_E)); + + wolfSSL_X509_STORE_CTX_free(sctx); + wolfSSL_X509_free(leaf); + wolfSSL_X509_free(ca); + wolfSSL_X509_STORE_free(store); +#endif + + return EXPECT_RESULT(); +} + int test_wolfSSL_CertManagerNameConstraint(void) { EXPECT_DECLS; diff --git a/tests/api/test_certman.h b/tests/api/test_certman.h index 37b4e4fa1dd..8178a4940a9 100644 --- a/tests/api/test_certman.h +++ b/tests/api/test_certman.h @@ -30,6 +30,26 @@ int test_wolfSSL_CertManagerLoadCABuffer_ex(void); int test_wolfSSL_CertManagerLoadCABufferType(void); int test_wolfSSL_CertManagerGetCerts(void); int test_wolfSSL_CertManagerSetVerify(void); +int test_wolfSSL_CertManagerCertEvents(void); +int test_wolfSSL_CertManagerCertEvents_badCerts(void); +int test_wolfSSL_CertEvents_noCertManager(void); +int test_wolfSSL_X509_STORE_CertEvents(void); +int test_wolfSSL_CertEvents_tls(void); +int test_wolfSSL_CertEvents_chain(void); +int test_wolfSSL_CertEvents_noPeerCert(void); +int test_wolfSSL_CertEvents_heldStatus(void); +int test_wolfSSL_CertEvents_trustAnchor(void); +int test_wolfSSL_CertEvents_infoEcc(void); +int test_wolfSSL_CertEvents_crlCheck(void); +int test_wolfSSL_CertEvents_crlLoad(void); +int test_wolfSSL_CertEvents_strict(void); +int test_wolfSSL_CertEvents_strictTrustAnchor(void); +int test_wolfSSL_CertEvents_strictNoCertManager(void); +int test_wolfSSL_CertEvents_strictCrl(void); +int test_wolfSSL_CertEvents_strictRevocation(void); +int test_wolfSSL_CertEvents_strictTls(void); +int test_wolfSSL_CertEvents_extensions(void); +int test_wolfSSL_CertEvents_fields(void); int test_wolfSSL_CertManagerNameConstraint(void); int test_wolfSSL_CertManagerNameConstraint2(void); int test_wolfSSL_CertManagerNameConstraint3(void); @@ -76,6 +96,27 @@ int test_wolfSSL_trust_peer_cert_dates(void); TEST_DECL_GROUP("certman", test_wolfSSL_CertManagerLoadCABufferType), \ TEST_DECL_GROUP("certman", test_wolfSSL_CertManagerGetCerts), \ TEST_DECL_GROUP("certman", test_wolfSSL_CertManagerSetVerify), \ + TEST_DECL_GROUP("certman", test_wolfSSL_CertManagerCertEvents), \ + TEST_DECL_GROUP("certman", \ + test_wolfSSL_CertManagerCertEvents_badCerts), \ + TEST_DECL_GROUP("certman", test_wolfSSL_CertEvents_noCertManager), \ + TEST_DECL_GROUP("certman", test_wolfSSL_X509_STORE_CertEvents), \ + TEST_DECL_GROUP("certman", test_wolfSSL_CertEvents_tls), \ + TEST_DECL_GROUP("certman", test_wolfSSL_CertEvents_chain), \ + TEST_DECL_GROUP("certman", test_wolfSSL_CertEvents_noPeerCert), \ + TEST_DECL_GROUP("certman", test_wolfSSL_CertEvents_heldStatus), \ + TEST_DECL_GROUP("certman", test_wolfSSL_CertEvents_trustAnchor), \ + TEST_DECL_GROUP("certman", test_wolfSSL_CertEvents_infoEcc), \ + TEST_DECL_GROUP("certman", test_wolfSSL_CertEvents_crlCheck), \ + TEST_DECL_GROUP("certman", test_wolfSSL_CertEvents_crlLoad), \ + TEST_DECL_GROUP("certman", test_wolfSSL_CertEvents_strict), \ + TEST_DECL_GROUP("certman", test_wolfSSL_CertEvents_strictTrustAnchor), \ + TEST_DECL_GROUP("certman", test_wolfSSL_CertEvents_strictNoCertManager), \ + TEST_DECL_GROUP("certman", test_wolfSSL_CertEvents_strictCrl), \ + TEST_DECL_GROUP("certman", test_wolfSSL_CertEvents_strictRevocation), \ + TEST_DECL_GROUP("certman", test_wolfSSL_CertEvents_strictTls), \ + TEST_DECL_GROUP("certman", test_wolfSSL_CertEvents_extensions), \ + TEST_DECL_GROUP("certman", test_wolfSSL_CertEvents_fields), \ TEST_DECL_GROUP("certman", test_wolfSSL_CertManagerNameConstraint), \ TEST_DECL_GROUP("certman", test_wolfSSL_CertManagerNameConstraint2), \ TEST_DECL_GROUP("certman", test_wolfSSL_CertManagerNameConstraint3), \ diff --git a/wolfcrypt/src/asn.c b/wolfcrypt/src/asn.c index a1d8bae76dc..e75d4fc4a74 100644 --- a/wolfcrypt/src/asn.c +++ b/wolfcrypt/src/asn.c @@ -13308,6 +13308,9 @@ void InitDecodedCert_ex(DecodedCert* cert, cert->maxIdx = inSz; /* can't go over this index */ cert->heap = heap; cert->maxPathLen = WOLFSSL_MAX_PATH_LEN; + #ifdef WOLFSSL_CERT_EVENTS + cert->certEventDepth = -1; /* not known until a caller says */ + #endif #if defined(WOLFSSL_CERT_GEN) || defined(WOLFSSL_CERT_EXT) #ifdef WOLFSSL_CERT_NAME_ALL cert->subjectNEnc = CTC_UTF8; @@ -23725,6 +23728,760 @@ int wc_SetUnknownExtCallback32Ex(DecodedCert* cert, #endif /* WC_ASN_UNKNOWN_EXT_CB */ +#ifdef WOLFSSL_CERT_EVENTS +/* Set the validation event callback on a decoded certificate. + * + * Use this when parsing without a certificate manager. The certificate + * manager sets it for certificates it verifies. + * + * @param [in, out] cert Decoded certificate. + * @param [in] cb Event callback. NULL turns events off. + * @param [in] ctx Context passed to the callback. + * @return 0 on success. + * @return BAD_FUNC_ARG when cert is NULL. + */ +int wc_SetCertEventCallback(DecodedCert* cert, WOLFSSL_CERT_EVENT_CB cb, + void* ctx) +{ + if (cert == NULL) { + return BAD_FUNC_ARG; + } + + cert->certEventCb = cb; + cert->certEventCtx = ctx; + return 0; +} + +/* Check strict check bits and resolve WC_CEV_STRICT_ALL. + * + * WC_CEV_STRICT_ALL means every check this build can run. Naming a check on + * its own that this build cannot run is an error, since it would never fire. + * + * @param [in] enforce enum wc_CertEventStrict bits. + * @param [out] resolved Bits to use. + * @return 0 on success. + * @return BAD_FUNC_ARG when enforce has an unknown bit. + * @return NOT_COMPILED_IN when a check named is not in this build. + */ +int wc_CertEventStrictResolve(word32 enforce, word32* resolved) +{ + word32 avail = (word32)WC_CEV_STRICT_ALL; + +#if defined(WOLFSSL_X509_TINY) && !defined(WOLFSSL_X509_TINY_AKI) + /* The AKID is not decoded. */ + avail &= ~(word32)WC_CEV_STRICT_AKID; +#endif +#if defined(WOLFSSL_X509_TINY) && !defined(WOLFSSL_X509_TINY_CRL_DP) + /* The CRL distribution points are not decoded. */ + avail &= ~(word32)WC_CEV_STRICT_CRL_DP; +#endif +#if defined(WOLFSSL_X509_TINY) && !defined(WOLFSSL_X509_TINY_AIA) + /* The authority information access is not decoded. */ + avail &= ~(word32)WC_CEV_STRICT_OCSP; +#endif +#if !defined(HAVE_CRL) || defined(NO_ASN_TIME) || \ + defined(WOLFSSL_NO_CRL_DATE_CHECK) + /* No CRL dates are checked. */ + avail &= ~(word32)WC_CEV_STRICT_CRL_THIS_UPDATE; +#endif + + if ((enforce & ~(word32)WC_CEV_STRICT_ALL) != 0) { + return BAD_FUNC_ARG; + } + if (enforce == (word32)WC_CEV_STRICT_ALL) { + enforce = avail; + } + else if ((enforce & ~avail) != 0) { + return NOT_COMPILED_IN; + } + + *resolved = enforce; + return 0; +} + +/* Turn on the strict checks for a decoded certificate. + * + * Each check that fires is sent to the event callback as a NOTICE, or as a + * DEFECT when it is enforced. An enforced check fails ParseCertRelative() + * with its ASN_STRICT_*_E error. The checks run only on a certificate that + * otherwise passed. ParseCertRelative() does not check revocation, so a + * caller that does should check it first: see wc_CertEventStrictDefer(). + * + * @param [in, out] cert Decoded certificate. + * @param [in] enforce Checks to enforce. enum wc_CertEventStrict bits. + * WC_CEV_STRICT_ALL is every check in this build. + * @return 0 on success. + * @return BAD_FUNC_ARG when cert is NULL or enforce has an unknown bit. + * @return NOT_COMPILED_IN when a check named is not in this build. + */ +int wc_SetCertEventStrict(DecodedCert* cert, word32 enforce) +{ + int ret; + + if (cert == NULL) { + return BAD_FUNC_ARG; + } + ret = wc_CertEventStrictResolve(enforce, &enforce); + if (ret == 0) { + cert->certEventStrictOn = 1; + cert->certEventStrict = enforce; + } + return ret; +} + +/* Turn on the strict checks for a decoded certificate, run by the caller. + * + * ParseCertRelative() does not run them. The caller runs them with + * wc_CertEventStrictChecks() after its other checks, revocation included, so + * that a verify callback allowing a strict failure does not skip any. + * + * @param [in, out] cert Decoded certificate. + * @param [in] enforce Checks to enforce. enum wc_CertEventStrict bits. + */ +void wc_CertEventStrictDefer(DecodedCert* cert, word32 enforce) +{ + if (cert != NULL) { + cert->certEventStrictOn = 2; + cert->certEventStrict = enforce; + } +} + +/* Record the TLS connection whose handshake verifies the certificate. + * + * @param [in, out] cert Decoded certificate. + * @param [in] ssl TLS connection (WOLFSSL*). + * @param [in] notCaHash Subject hash of a certificate the peer sent + * that is not a CA, or NULL. A certificate whose + * issuer it is fails as signed by a non-CA. + */ +void wc_CertEventSetConnection(DecodedCert* cert, void* ssl, + const byte* notCaHash) +{ + if (cert != NULL) { + cert->certEventSsl = ssl; + cert->certEventNotCaSet = (notCaHash != NULL); + if (notCaHash != NULL) { + XMEMCPY(cert->certEventNotCa, notCaHash, KEYID_SIZE); + } + } +} + +/* Fill in which certificate an event is about, as far as it was decoded. + * + * @param [in] cert Decoded certificate. + * @param [out] ev Event. + */ +void wc_CertEventIdentity(const DecodedCert* cert, WOLFSSL_CERT_EVENT* ev) +{ + if (cert->subject[0] != '\0') { + ev->subject = cert->subject; + } + if (cert->issuer[0] != '\0') { + ev->issuer = cert->issuer; + } + if (cert->serialSz > 0) { + ev->serial = cert->serial; + ev->serialSz = (word32)cert->serialSz; + } + ev->ssl = cert->certEventSsl; +} + +/* Record where the certificate sits in the chain being verified. + * + * @param [in, out] cert Decoded certificate. + * @param [in] role Role of the certificate. enum wc_CertEventRole. + * @param [in] depth Chain depth, -1 when not known. + */ +void wc_CertEventSetPosition(DecodedCert* cert, int role, int depth) +{ + if (cert != NULL) { + cert->certEventRole = role; + cert->certEventDepth = depth; + } +} + +/* Fill in the size of the event and call the callback. + * + * Does nothing when no callback is set. + * + * @param [in] cb Event callback. + * @param [in] ctx Context passed to the callback. + * @param [in, out] ev Event to send. + */ +void wc_CertEventSend(WOLFSSL_CERT_EVENT_CB cb, void* ctx, + WOLFSSL_CERT_EVENT* ev) +{ + if ((cb != NULL) && (ev != NULL)) { + ev->size = (word32)sizeof(WOLFSSL_CERT_EVENT); + cb(ev, ctx); + } +} + +/* Size of the certificate's public key in bits. + * + * @param [in] cert Decoded certificate. + * @return Key size in bits, or 0 when not known. + */ +static int CertEventKeyBits(const DecodedCert* cert) +{ + int bits = 0; + +#ifndef NO_RSA + if (((cert->keyOID == RSAk) + #ifdef WC_RSA_PSS + || (cert->keyOID == RSAPSSk) + #endif + ) && (cert->publicKey != NULL)) { + const byte* n = NULL; + const byte* e = NULL; + word32 nSz = 0; + word32 eSz = 0; + word32 idx = 0; + + if (wc_RsaPublicKeyDecode_ex(cert->publicKey, &idx, cert->pubKeySize, + &n, &nSz, &e, &eSz) == 0) { + /* Count the modulus bits, skipping leading zeros. */ + while ((nSz > 0) && (*n == 0)) { + n++; + nSz--; + } + if (nSz > 0) { + byte top = n[0]; + bits = (int)nSz * 8; + while ((top & 0x80) == 0) { + bits--; + top = (byte)(top << 1); + } + } + } + } +#endif +#ifdef HAVE_ECC + if (cert->keyOID == ECDSAk) { + int curveId = CheckCurve(cert->pkCurveOID); + if (curveId >= 0) { + int sz = wc_ecc_get_curve_size_from_id(curveId); + if (sz > 0) { + bits = sz * 8; + } + } + } +#endif +#ifdef HAVE_ED25519 + if (cert->keyOID == ED25519k) { + bits = 256; + } +#endif +#ifdef HAVE_ED448 + if (cert->keyOID == ED448k) { + bits = 456; + } +#endif + + return bits; +} + +/* Fill in the certificate's algorithms. Values not decoded yet stay 0. + * + * @param [in] cert Decoded certificate. + * @param [in, out] ev Event to fill in. + */ +static void CertEventAlgorithms(const DecodedCert* cert, + WOLFSSL_CERT_EVENT* ev) +{ + ev->sigOID = cert->signatureOID; + ev->keyOID = cert->keyOID; +#if defined(HAVE_ECC) || defined(HAVE_ED25519) || defined(HAVE_ED448) || \ + defined(WOLFSSL_HAVE_MLDSA) || defined(HAVE_FALCON) || \ + defined(WOLFSSL_HAVE_SLHDSA) || defined(WOLFSSL_HAVE_LMS) || \ + defined(WOLFSSL_HAVE_XMSS) || defined(WOLFSSL_HAVE_FRODOKEM) || \ + defined(WOLFSSL_HAVE_MLKEM) + if (cert->keyOID == ECDSAk) { + ev->curveOID = cert->pkCurveOID; + } +#endif + if (cert->publicKey != NULL) { + ev->keySz = CertEventKeyBits(cert); + } +} + +/* Choose the condition that describes a failure. + * + * @param [in] cert Decoded certificate. + * @param [in] error wolfSSL error code. + * @param [in] field Field that failed. enum wc_CertEventField. + * @param [in] reason Why it failed. enum wc_CertEventReason. + * @return Condition. enum wc_CertEventCondition. + */ +static int CertEventCondition(const DecodedCert* cert, int error, int field, + int reason) +{ + switch (reason) { + case WC_CEV_REASON_EXPIRED: + return WC_CEV_COND_CERT_EXPIRED; + case WC_CEV_REASON_NOT_YET_VALID: + return WC_CEV_COND_CERT_NOT_YET_VALID; + case WC_CEV_REASON_SIGNATURE: + return WC_CEV_COND_CERT_SIG_INVALID; + case WC_CEV_REASON_NO_SIGNER: + /* The peer sent the issuer, but it is not a CA. */ + if (cert->certEventNotCaSet && (XMEMCMP(cert->issuerHash, + cert->certEventNotCa, KEYID_SIZE) == 0)) { + return WC_CEV_COND_CERT_NOT_CA; + } + return cert->selfIssued ? WC_CEV_COND_CERT_UNTRUSTED_ROOT : + WC_CEV_COND_CERT_NO_ISSUER; + case WC_CEV_REASON_POLICY: + return (field == WC_CEV_FIELD_SPKI) ? + WC_CEV_COND_CERT_KEY_TOO_SMALL : WC_CEV_COND_NONE; + case WC_CEV_REASON_CONSTRAINT: + if ((error == WC_NO_ERR_TRACE(ASN_PATHLEN_INV_E)) || + (error == WC_NO_ERR_TRACE(ASN_PATHLEN_SIZE_E))) { + return WC_CEV_COND_CERT_PATH_LEN; + } + if (error == WC_NO_ERR_TRACE(ASN_NAME_INVALID_E)) { + return WC_CEV_COND_CERT_NAME_CONSTRAINTS; + } + return WC_CEV_COND_CERT_ISSUER_KU; + case WC_CEV_REASON_UNSUPPORTED: + if (field == WC_CEV_FIELD_EXTENSION) { + return WC_CEV_COND_CERT_EXT_UNKNOWN_CRITICAL; + } + if (field == WC_CEV_FIELD_SPKI) { + return WC_CEV_COND_CERT_KEY_UNSUPPORTED; + } + if ((field == WC_CEV_FIELD_SIGALG) || + (field == WC_CEV_FIELD_TBS_SIGALG)) { + return WC_CEV_COND_CERT_SIG_ALG_UNSUPPORTED; + } + return WC_CEV_COND_CERT_FORMAT; + default: + break; + } + + if (error == WC_NO_ERR_TRACE(ASN_SIG_OID_E)) { + return WC_CEV_COND_CERT_SIG_ALG_MISMATCH; + } + switch (field) { + case WC_CEV_FIELD_EXTENSION: + return WC_CEV_COND_CERT_EXT_VALUE; + case WC_CEV_FIELD_VERSION: + return WC_CEV_COND_CERT_VERSION; + case WC_CEV_FIELD_ISSUER: + return WC_CEV_COND_CERT_ISSUER; + case WC_CEV_FIELD_SUBJECT: + return WC_CEV_COND_CERT_SUBJECT; + default: + return WC_CEV_COND_CERT_FORMAT; + } +} + +/* Fill in and send an event about a decoded certificate. + * + * @param [in] cert Decoded certificate. + * @param [in] kind DEFECT or NOTICE. enum wc_CertEventKind. + * @param [in] error wolfSSL error code. + * @param [in] field Field that failed. enum wc_CertEventField. + * @param [in] reason Why it failed. enum wc_CertEventReason. + * @param [in] extOID Extension OID sum when field is an extension. + * @param [in] oidData Extension OID content octets. May be NULL. + * @param [in] oidSz Size of OID content octets in bytes. + * @param [in] critical Whether the extension was marked critical. + * @param [in] condition Condition, or WC_CEV_COND_NONE to choose one from the + * error, field and reason. + */ +static void CertEventSendKind(DecodedCert* cert, int kind, int error, + int field, int reason, word32 extOID, const byte* oidData, word32 oidSz, + byte critical, int condition) +{ + WOLFSSL_CERT_EVENT ev; + + if ((cert == NULL) || (cert->certEventCb == NULL)) { + return; + } + if (kind == WC_CEV_KIND_DEFECT) { + cert->certEventReported = error; + } + + XMEMSET(&ev, 0, sizeof(ev)); + ev.condition = (condition != WC_CEV_COND_NONE) ? condition : + CertEventCondition(cert, error, field, reason); + CertEventAlgorithms(cert, &ev); + wc_CertEventIdentity(cert, &ev); + ev.kind = kind; + ev.category = WC_CEV_CAT_CERT; + ev.error = error; + ev.role = cert->certEventRole; + ev.depth = cert->certEventDepth; + ev.field = field; + ev.reason = reason; + ev.extOID = extOID; + ev.extOidData = oidData; + ev.extOidSz = oidSz; + ev.critical = (byte)((critical != 0) ? 1 : 0); + ev.der = cert->source; + ev.derSz = cert->maxIdx; + wc_CertEventSend(cert->certEventCb, cert->certEventCtx, &ev); +} + +/* Fill in and send a DEFECT event for a decoded certificate. + * + * @param [in] cert Decoded certificate. + * @param [in] error wolfSSL error code. + * @param [in] field Field that failed. enum wc_CertEventField. + * @param [in] reason Why it failed. enum wc_CertEventReason. + * @param [in] extOID Extension OID sum when field is an extension. + * @param [in] oidData Extension OID content octets. May be NULL. + * @param [in] oidSz Size of OID content octets in bytes. + * @param [in] critical Whether the extension was marked critical. + * @param [in] condition Condition, or WC_CEV_COND_NONE to choose one from the + * error, field and reason. + */ +static void CertEventDefectOid(DecodedCert* cert, int error, int field, + int reason, word32 extOID, const byte* oidData, word32 oidSz, + byte critical, int condition) +{ + CertEventSendKind(cert, WC_CEV_KIND_DEFECT, error, field, reason, extOID, + oidData, oidSz, critical, condition); +} + +/* Report a failed check on a decoded certificate. + * + * Only called on error paths. Does nothing when no callback is set. + * + * @param [in] cert Decoded certificate. + * @param [in] error wolfSSL error code. + * @param [in] field Field that failed. enum wc_CertEventField. + * @param [in] reason Why it failed. enum wc_CertEventReason. + * @param [in] extOID Extension OID sum when field is an extension. + * @param [in] critical Whether the extension was marked critical. + */ +void wc_CertEventDefect(DecodedCert* cert, int error, int field, int reason, + word32 extOID, byte critical) +{ + CertEventDefectOid(cert, error, field, reason, extOID, NULL, 0, critical, + WC_CEV_COND_NONE); +} + +/* Report a failure to decode the certificate's structure. + * + * Sends nothing when a more specific event, such as one for an extension, + * was already sent for this certificate, or when the error is not about the + * certificate: running out of memory, or a date that is checked later. + * + * @param [in] cert Decoded certificate. + * @param [in] error wolfSSL error code. Nothing is sent unless negative. + * @param [in] field Field being decoded. enum wc_CertEventField. + */ +void wc_CertEventField(DecodedCert* cert, int error, int field) +{ + int reason = WC_CEV_REASON_MALFORMED; + + switch (error) { + case WC_NO_ERR_TRACE(ASN_UNKNOWN_OID_E): + case WC_NO_ERR_TRACE(ASN_SIG_HASH_E): + case WC_NO_ERR_TRACE(ASN_SIG_KEY_E): + case WC_NO_ERR_TRACE(ECC_CURVE_OID_E): + case WC_NO_ERR_TRACE(NOT_COMPILED_IN): + reason = WC_CEV_REASON_UNSUPPORTED; + break; + default: + break; + } + wc_CertEventFailure(cert, error, field, reason); +} + +/* Report a failure, unless the same error was just reported for this + * certificate. + * + * Sends nothing for errors that are not about the certificate, such as + * running out of memory, or for dates, which are reported separately. + * + * @param [in] cert Decoded certificate. + * @param [in] error wolfSSL error code. Nothing is sent unless negative. + * @param [in] field Field that failed. enum wc_CertEventField. + * @param [in] reason Why it failed. enum wc_CertEventReason. + */ +void wc_CertEventFailure(DecodedCert* cert, int error, int field, int reason) +{ + wc_CertEventFailureCond(cert, error, field, reason, WC_CEV_COND_NONE); +} + +/* Report a failure with a given condition, unless the same error was just + * reported for this certificate. + * + * The same error reaches here again as it is passed up from where it was + * found; a different error is a further failed check and is reported. + * + * Used where wolfCrypt cannot tell the condition from the error, such as the + * TLS checks on what the certificate may be used for. + * + * @param [in] cert Decoded certificate. + * @param [in] error wolfSSL error code. Nothing is sent unless negative. + * @param [in] field Field that failed. enum wc_CertEventField. + * @param [in] reason Why it failed. enum wc_CertEventReason. + * @param [in] condition Condition. enum wc_CertEventCondition. + */ +void wc_CertEventFailureCond(DecodedCert* cert, int error, int field, + int reason, int condition) +{ + if ((cert == NULL) || (error >= 0) || + (error == cert->certEventReported)) { + return; + } + switch (error) { + case WC_NO_ERR_TRACE(MEMORY_E): + case WC_NO_ERR_TRACE(BAD_FUNC_ARG): + case WC_NO_ERR_TRACE(WC_PENDING_E): + case WC_NO_ERR_TRACE(ASN_BEFORE_DATE_E): + case WC_NO_ERR_TRACE(ASN_AFTER_DATE_E): + return; + default: + break; + } + CertEventDefectOid(cert, error, field, reason, 0, NULL, 0, 0, condition); +} + +/* Send the INFO event with the certificate's algorithms and key size. + * + * Sent once for each certificate whose public key was decoded, whatever the + * outcome, so the application can apply its own list of approved algorithms + * and key sizes. + * + * @param [in] cert Decoded certificate. + */ +static void CertEventInfo(DecodedCert* cert) +{ + WOLFSSL_CERT_EVENT ev; + + if ((cert == NULL) || (cert->certEventCb == NULL) || + cert->certEventInfoSent || (cert->keyOID == 0) || + (cert->publicKey == NULL)) { + return; + } + cert->certEventInfoSent = 1; + + XMEMSET(&ev, 0, sizeof(ev)); + CertEventAlgorithms(cert, &ev); + wc_CertEventIdentity(cert, &ev); + ev.kind = WC_CEV_KIND_INFO; + ev.category = WC_CEV_CAT_CERT; + ev.role = cert->certEventRole; + ev.depth = cert->certEventDepth; + ev.der = cert->source; + ev.derSz = cert->maxIdx; + wc_CertEventSend(cert->certEventCb, cert->certEventCtx, &ev); +} + +/* A strict profile check: what is reported, and the error when enforced. */ +typedef struct CertEventStrictCheck { + word32 check; /* enum wc_CertEventStrict bit */ + int error; /* Returned when enforced. */ + int condition; /* enum wc_CertEventCondition */ + word32 extOID; /* Extension checked, 0 for unique IDs. */ +} CertEventStrictCheck; + +static const CertEventStrictCheck certEventStrictChecks[] = { + { WC_CEV_STRICT_UNIQUE_ID, WC_NO_ERR_TRACE(ASN_STRICT_UNIQUE_ID_E), + WC_CEV_COND_CERT_UNIQUE_ID, 0 }, + { WC_CEV_STRICT_AKID, WC_NO_ERR_TRACE(ASN_STRICT_NO_AKID_E), + WC_CEV_COND_CERT_NO_AKID, AUTH_KEY_OID }, + { WC_CEV_STRICT_KEY_USAGE, WC_NO_ERR_TRACE(ASN_STRICT_NO_KEY_USAGE_E), + WC_CEV_COND_CERT_NO_KEY_USAGE, KEY_USAGE_OID }, + { WC_CEV_STRICT_CRL_DP, WC_NO_ERR_TRACE(ASN_STRICT_NO_CRL_DP_E), + WC_CEV_COND_CERT_NO_CRL_DP, CRL_DIST_OID }, + { WC_CEV_STRICT_OCSP, WC_NO_ERR_TRACE(ASN_STRICT_NO_OCSP_E), + WC_CEV_COND_CERT_NO_OCSP, AUTH_INFO_OID }, +}; + +/* Whether the certificate is self-signed, as wolfSSL judged it. + * + * A root CA whose AKID names its own key is trusted without checking its + * signature, so selfSigned is not set for it. + * + * @param [in] cert Decoded certificate. + * @return 1 when self-signed, otherwise 0. + */ +static int CertEventSelfSigned(const DecodedCert* cert) +{ + if (!cert->selfIssued) { + return 0; + } + if (cert->selfSigned) { + return 1; + } + return cert->extAuthKeyIdSet && cert->extSubjKeyIdSet && + (XMEMCMP(cert->extAuthKeyId, cert->extSubjKeyId, KEYID_SIZE) == 0); +} + +/* Whether a strict check fires on a certificate. + * + * Self-signed certificates are exempt from the checks on what the issuer + * should include: AKID, CRL distribution point and OCSP responder. The AKID + * check fires when the extension or its keyIdentifier is missing; RFC 5280 + * 4.2.1.1 requires both. + * + * @param [in] cert Decoded certificate. + * @param [in] check The check. enum wc_CertEventStrict bit. + * @return 1 when it fires, otherwise 0. + */ +static int CertEventStrictFires(const DecodedCert* cert, word32 check) +{ + switch (check) { + case WC_CEV_STRICT_UNIQUE_ID: + return (cert->certEventUid != 0); + case WC_CEV_STRICT_AKID: + #if defined(WOLFSSL_X509_TINY) && !defined(WOLFSSL_X509_TINY_AKI) + /* The AKID is not decoded. */ + return 0; + #else + return (!CertEventSelfSigned(cert)) && (!cert->extAuthKeyIdSet); + #endif + case WC_CEV_STRICT_KEY_USAGE: + return cert->isCA && (!cert->extKeyUsageSet); + case WC_CEV_STRICT_CRL_DP: + #if defined(WOLFSSL_X509_TINY) && !defined(WOLFSSL_X509_TINY_CRL_DP) + /* The CRL distribution points are not decoded. */ + return 0; + #else + return (!CertEventSelfSigned(cert)) && (!cert->extCRLdistSet); + #endif + case WC_CEV_STRICT_OCSP: + #if defined(WOLFSSL_X509_TINY) && !defined(WOLFSSL_X509_TINY_AIA) + /* The authority information access is not decoded. */ + return 0; + #else + return (!CertEventSelfSigned(cert)) && (cert->extAuthInfo == NULL); + #endif + default: + return 0; + } +} + +/* Run the strict profile checks on a decoded certificate, once. + * + * Each check that fires is reported: as a NOTICE, or as a DEFECT when it is + * enforced. The checks only run when turned on with wc_SetCertEventStrict() + * or wc_CertEventStrictDefer(), when an event callback or an enforced check + * is set, and when the certificate was fully decoded. Callers run them once + * no other failure is pending: when nothing else failed, or when the verify + * callback allowed what did. Later calls do nothing. + * + * @param [in] cert Decoded certificate. + * @return 0 when no enforced check fired. + * @return ASN_STRICT_*_E of the first enforced check that fired. + */ +int wc_CertEventStrictChecks(DecodedCert* cert) +{ + int ret = 0; + size_t i; + + if ((cert == NULL) || (cert->certEventStrictOn == 0) || + (!cert->certEventDecoded) || + ((cert->certEventCb == NULL) && (cert->certEventStrict == 0))) { + return 0; + } + /* Run once. */ + cert->certEventStrictOn = 0; +#ifdef WOLFSSL_CERT_REQ + if (cert->isCSR) { + return 0; + } +#endif + + for (i = 0; i < XELEM_CNT(certEventStrictChecks); i++) { + const CertEventStrictCheck* c = &certEventStrictChecks[i]; + int enforced = ((cert->certEventStrict & c->check) != 0); + int field = WC_CEV_FIELD_EXTENSION; + const byte* oid = NULL; + word32 oidSz = 0; + + if (!CertEventStrictFires(cert, c->check)) { + continue; + } + if (c->extOID != 0) { + oid = OidFromId(c->extOID, oidCertExtType, &oidSz); + } + else { + field = (cert->certEventUid & 1) ? WC_CEV_FIELD_ISSUER_UID : + WC_CEV_FIELD_SUBJECT_UID; + } + CertEventSendKind(cert, enforced ? WC_CEV_KIND_DEFECT : + WC_CEV_KIND_NOTICE, c->error, field, (c->extOID != 0) ? + WC_CEV_REASON_MISSING : WC_CEV_REASON_POLICY, c->extOID, oid, + oidSz, 0, c->condition); + if (enforced && (ret == 0)) { + ret = c->error; + WOLFSSL_ERROR_VERBOSE(ret); + } + } + + return ret; +} + +/* Report why verifying a certificate against its signer failed. + * + * Called once ParseCertRelative() is done. A failure already reported, such + * as a field that failed to decode, is not reported again; any other failure + * is reported so that each failure has an event. + * + * @param [in] cert Decoded certificate. + * @param [in] error Return of ParseCertRelative(). + */ +static void CertEventChain(DecodedCert* cert, int error) +{ + switch (error) { + case WC_NO_ERR_TRACE(ASN_NO_SIGNER_E): + case WC_NO_ERR_TRACE(ASN_SELF_SIGNED_E): + wc_CertEventFailure(cert, error, WC_CEV_FIELD_CHAIN, + WC_CEV_REASON_NO_SIGNER); + break; + case WC_NO_ERR_TRACE(ASN_SIG_CONFIRM_E): + wc_CertEventFailure(cert, error, WC_CEV_FIELD_SIGNATURE, + WC_CEV_REASON_SIGNATURE); + break; + case WC_NO_ERR_TRACE(ASN_PATHLEN_INV_E): + case WC_NO_ERR_TRACE(ASN_PATHLEN_SIZE_E): + case WC_NO_ERR_TRACE(ASN_NAME_INVALID_E): + case WC_NO_ERR_TRACE(KEYUSAGE_E): + wc_CertEventFailure(cert, error, WC_CEV_FIELD_CHAIN, + WC_CEV_REASON_CONSTRAINT); + break; + default: + wc_CertEventField(cert, error, WC_CEV_FIELD_NONE); + break; + } +} + +/* Report a certificate extension that failed to decode. + * + * An unknown extension marked critical is unsupported; any other failure + * means the extension is malformed. + * + * @param [in] cert Decoded certificate. + * @param [in] error wolfSSL error code. + * @param [in] oid Extension OID sum, 0 when not known. + * @param [in] oidData Extension OID content octets. May be NULL. + * @param [in] oidSz Size of OID content octets in bytes. + * @param [in] critical Whether the extension was marked critical. + */ +static void CertEventExtension(DecodedCert* cert, int error, word32 oid, + const byte* oidData, word32 oidSz, + byte critical) +{ + CertEventDefectOid(cert, error, WC_CEV_FIELD_EXTENSION, + (error == WC_NO_ERR_TRACE(ASN_CRIT_EXT_E)) ? + WC_CEV_REASON_UNSUPPORTED : WC_CEV_REASON_MALFORMED, + oid, oidData, oidSz, critical, + WC_CEV_COND_NONE); +} + + /* Report a failure decoding a field of the certificate. */ + #define CERT_EVENT_FIELD(cert, err, f) wc_CertEventField(cert, err, f) +#else + #define CERT_EVENT_FIELD(cert, err, f) WC_DO_NOTHING +#endif /* WOLFSSL_CERT_EVENTS */ + /* * Processing the Certificate Extensions. This does not modify the current * index. It is works starting with the recorded extensions pointer. @@ -23870,10 +24627,23 @@ static int DecodeCertExtensions(DecodedCert* cert) #else (void)isUnknownExt; #endif +#ifdef WOLFSSL_CERT_EVENTS + if (ret != 0) { + CertEventExtension(cert, ret, oid, + dataASN[CERTEXTASN_IDX_OID].data.oid.data, + dataASN[CERTEXTASN_IDX_OID].data.oid.length, critical); + } +#endif /* Move index on to next extension. */ idx += length; } +#ifdef WOLFSSL_CERT_EVENTS + else { + /* The extension's own wrapper is malformed. */ + CertEventExtension(cert, ret, 0, NULL, 0, critical); + } +#endif /* Don't fail criticality until all other extensions have been checked. */ if (ret == WC_NO_ERR_TRACE(ASN_CRIT_EXT_E)) { @@ -24105,6 +24875,71 @@ static int CheckDate(ASNGetData *dataASN, int dateType) return ret; } +#ifdef WOLFSSL_CERT_EVENTS +/* Find which field of the certificate failed to parse. + * + * GetASN_Items() records each item's offset before checking it, and the data + * starts zeroed, so the last item with an offset is the one that failed. + * + * @param [in] dataASN Template data from a failed GetASN_Items() call. + * @return Field that failed. enum wc_CertEventField. + */ +static int CertEventTemplateField(const ASNGetData* dataASN) +{ + int i; + + for (i = (int)x509CertASN_Length - 1; i > 0; i--) { + if (dataASN[i].offset != 0) { + break; + } + } + + /* The two time formats are optional choices, so a value that is neither + * is skipped and the failure shows at the next required item. A failure + * after the validity with no notBefore or notAfter found is in the + * validity. */ + if ((i >= X509CERTASN_IDX_TBS_SUBJECT_SEQ) && + (((dataASN[X509CERTASN_IDX_TBS_VALIDITY_NOTB_UTC].tag == 0) && + (dataASN[X509CERTASN_IDX_TBS_VALIDITY_NOTB_GT].tag == 0)) || + ((dataASN[X509CERTASN_IDX_TBS_VALIDITY_NOTA_UTC].tag == 0) && + (dataASN[X509CERTASN_IDX_TBS_VALIDITY_NOTA_GT].tag == 0)))) { + return WC_CEV_FIELD_VALIDITY; + } + + if (i >= X509CERTASN_IDX_SIGNATURE) + return WC_CEV_FIELD_SIGNATURE; + if (i >= X509CERTASN_IDX_SIGALGO_SEQ) + return WC_CEV_FIELD_SIGALG; + if (i >= X509CERTASN_IDX_TBS_EXT) + return WC_CEV_FIELD_EXTENSION; + if (i == X509CERTASN_IDX_TBS_SUBJECTUID) + return WC_CEV_FIELD_SUBJECT_UID; + if (i == X509CERTASN_IDX_TBS_ISSUERUID) + return WC_CEV_FIELD_ISSUER_UID; + if (i >= X509CERTASN_IDX_TBS_SPUBKEYINFO_SEQ) + return WC_CEV_FIELD_SPKI; + if (i >= X509CERTASN_IDX_TBS_SUBJECT_SEQ) + return WC_CEV_FIELD_SUBJECT; + if (i >= X509CERTASN_IDX_TBS_VALIDITY_SEQ) + return WC_CEV_FIELD_VALIDITY; + if (i >= X509CERTASN_IDX_TBS_ISSUER_SEQ) + return WC_CEV_FIELD_ISSUER; + if (i >= X509CERTASN_IDX_TBS_ALGOID_SEQ) + return WC_CEV_FIELD_TBS_SIGALG; + if (i == X509CERTASN_IDX_TBS_SERIAL) + return WC_CEV_FIELD_SERIAL; + if (i >= X509CERTASN_IDX_TBS_VER) + return WC_CEV_FIELD_VERSION; + return WC_CEV_FIELD_ENCODING; +} + + /* Record the field about to be decoded, for the event sent on failure. */ + #define CERT_EVENT_AT(field, ret, f) \ + do { if ((ret) == 0) (field) = (f); } while (0) +#else + #define CERT_EVENT_AT(field, ret, f) WC_DO_NOTHING +#endif /* WOLFSSL_CERT_EVENTS */ + /* Decode a certificate. Internal/non-public API. * * @param [in] cert Certificate object. @@ -24146,6 +24981,9 @@ static int DecodeCertInternal(DecodedCert* cert, int verify, int* criticalExt, word32 pubKeyOffset = 0; word32 pubKeyEnd = 0; int done = 0; +#ifdef WOLFSSL_CERT_EVENTS + int cevField = WC_CEV_FIELD_ENCODING; +#endif #if defined(HAVE_RPK) /* try to parse the cert as Raw Public Key cert */ @@ -24236,6 +25074,20 @@ static int DecodeCertInternal(DecodedCert* cert, int verify, int* criticalExt, ret = ASN_PARSE_E; } #endif + #ifdef WOLFSSL_CERT_EVENTS + if (ret < 0) { + cevField = CertEventTemplateField(dataASN); + } + else { + /* Unique IDs are skipped; note them for the strict checks. */ + if (dataASN[X509CERTASN_IDX_TBS_ISSUERUID].tag != 0) { + cert->certEventUid |= 1; + } + if (dataASN[X509CERTASN_IDX_TBS_SUBJECTUID].tag != 0) { + cert->certEventUid |= 2; + } + } + #endif } #ifndef WOLFSSL_NO_ASN_STRICT /* cert->srcIdx is now just past the certificate's outer SEQUENCE. Reject @@ -24250,6 +25102,7 @@ static int DecodeCertInternal(DecodedCert* cert, int verify, int* criticalExt, ret = ASN_PARSE_E; } #endif /* !WOLFSSL_NO_ASN_STRICT */ + CERT_EVENT_AT(cevField, ret, WC_CEV_FIELD_VERSION); /* Check version is valid/supported - can't be negative. */ if ((ret == 0) && (version > MAX_X509_VERSION)) { WOLFSSL_MSG("Unexpected certificate version"); @@ -24264,6 +25117,7 @@ static int DecodeCertInternal(DecodedCert* cert, int verify, int* criticalExt, cert->version = version; cert->serialSz = (int)serialSz; + CERT_EVENT_AT(cevField, ret, WC_CEV_FIELD_SERIAL); /* RFC 5280 requires serial number to be present and at least 1 byte */ if (cert->serialSz == 0) { WOLFSSL_MSG("Error: certificate serial number is empty " @@ -24322,6 +25176,7 @@ static int DecodeCertInternal(DecodedCert* cert, int verify, int* criticalExt, done = 1; } + CERT_EVENT_AT(cevField, ret, WC_CEV_FIELD_SIGALG); if ((ret == 0) && (!done)) { /* Store the signature information. */ cert->sigIndex = dataASN[X509CERTASN_IDX_SIGALGO_SEQ].offset; @@ -24414,6 +25269,7 @@ static int DecodeCertInternal(DecodedCert* cert, int verify, int* criticalExt, if ((ret == 0) && (!done) && (dataASN[X509CERTASN_IDX_TBS_EXT_SEQ].data.ref.data != NULL)) { #ifndef ALLOW_V1_EXTENSIONS + CERT_EVENT_AT(cevField, ret, WC_CEV_FIELD_VERSION); /* Certificate extensions were only defined in version 2. */ if (cert->version < 2) { WOLFSSL_MSG("\tv1 and v2 certs not allowed extensions"); @@ -24434,12 +25290,14 @@ static int DecodeCertInternal(DecodedCert* cert, int verify, int* criticalExt, /* Dispose of memory before allocating for extension decoding. */ FREE_ASNGETDATA(dataASN, cert->heap); + CERT_EVENT_AT(cevField, ret, WC_CEV_FIELD_ISSUER); if ((ret == 0) && (issuer != NULL)) { idx = 0; /* Put issuer into cert and calculate hash. */ ret = GetCertName(cert, cert->issuer, cert->issuerHash, ASN_ISSUER, issuer, &idx, issuerSz); } + CERT_EVENT_AT(cevField, ret, WC_CEV_FIELD_SUBJECT); if ((ret == 0) && (subject != NULL)) { idx = 0; /* Put subject into cert and calculate hash. */ @@ -24463,11 +25321,13 @@ static int DecodeCertInternal(DecodedCert* cert, int verify, int* criticalExt, } } + CERT_EVENT_AT(cevField, ret, WC_CEV_FIELD_SPKI); if ((ret == 0) && (!stopAtPubKey)) { /* Parse the public key. */ idx = pubKeyOffset; ret = GetCertKey(cert, cert->source, &idx, pubKeyEnd); } + CERT_EVENT_AT(cevField, ret, WC_CEV_FIELD_EXTENSION); if ((ret == 0) && (!stopAtPubKey) && (!stopAfterPubKey) && (cert->extensions != NULL)) { /* Decode the extension data starting at [3]. */ @@ -24501,6 +25361,7 @@ static int DecodeCertInternal(DecodedCert* cert, int verify, int* criticalExt, * narrow: when extensions are not parsed (stopAtPubKey/stopAfterPubKey) * isCA is not populated, so the exemption never fires and serial-0 is * always rejected on those paths. */ + CERT_EVENT_AT(cevField, ret, WC_CEV_FIELD_SERIAL); if ((ret == 0) && (cert->serialSz == 1) && (cert->serial[0] == 0)) { if (!(cert->isCA && cert->selfIssued) #ifdef WOLFSSL_CERT_REQ @@ -24513,6 +25374,10 @@ static int DecodeCertInternal(DecodedCert* cert, int verify, int* criticalExt, } #endif +#ifdef WOLFSSL_CERT_EVENTS + /* One event for a structural failure; extensions report their own. */ + wc_CertEventField(cert, ret, cevField); +#endif return ret; } @@ -25865,8 +26730,40 @@ static WC_NO_INLINE int ConfirmSelfSignature(DecodedCert* cert) return ret; } +static int ParseCertRelativeInternal(DecodedCert* cert, int type, int verify, + WOLFSSL_CERT_MANAGER* cm, Signer *extraCAList); + +/* Parse a certificate and verify it against its signer. + * + * With WOLFSSL_CERT_EVENTS, a failure that was not reported while decoding + * is reported here, so that each failure has an event, and the strict checks + * run when turned on. + * + * @param [in, out] cert Decoded certificate. + * @param [in] type Type of certificate. + * @param [in] verify What verification to do. + * @param [in] cm Certificate manager. May be NULL. + * @param [in] extraCAList Extra signers to try. May be NULL. + * @return 0 on success. + * @return Negative on failure. + */ int ParseCertRelative(DecodedCert* cert, int type, int verify, WOLFSSL_CERT_MANAGER* cm, Signer *extraCAList) +{ + int ret = ParseCertRelativeInternal(cert, type, verify, cm, extraCAList); +#ifdef WOLFSSL_CERT_EVENTS + CertEventChain(cert, ret); + /* Deferred checks are run by the caller, after its other checks. */ + if ((ret == 0) && (cert->certEventStrictOn == 1)) { + ret = wc_CertEventStrictChecks(cert); + } + CertEventInfo(cert); +#endif + return ret; +} + +static int ParseCertRelativeInternal(DecodedCert* cert, int type, int verify, + WOLFSSL_CERT_MANAGER* cm, Signer *extraCAList) { int ret = 0; #ifndef WOLFSSL_ASN_TEMPLATE @@ -26126,6 +27023,7 @@ int ParseCertRelative(DecodedCert* cert, int type, int verify, if (cert->version < 2) { WOLFSSL_MSG("\tv1 and v2 certs not allowed extensions"); WOLFSSL_ERROR_VERBOSE(ASN_VERSION_E); + CERT_EVENT_FIELD(cert, ASN_VERSION_E, WC_CEV_FIELD_VERSION); return ASN_VERSION_E; } #endif @@ -26138,8 +27036,11 @@ int ParseCertRelative(DecodedCert* cert, int type, int verify, if ((ret = DecodeCertExtensions(cert)) < 0) { if (ret == WC_NO_ERR_TRACE(ASN_CRIT_EXT_E)) cert->criticalExt = ret; - else + else { + /* Only when no single extension was reported. */ + CERT_EVENT_FIELD(cert, ret, WC_CEV_FIELD_EXTENSION); return ret; + } } /* advance past extensions */ @@ -26153,10 +27054,12 @@ int ParseCertRelative(DecodedCert* cert, int type, int verify, &confirmOID, #endif cert->maxIdx)) < 0) { + CERT_EVENT_FIELD(cert, ret, WC_CEV_FIELD_SIGALG); return ret; } if ((ret = GetSignature(cert)) < 0) { + CERT_EVENT_FIELD(cert, ret, WC_CEV_FIELD_SIGNATURE); return ret; } @@ -26166,6 +27069,7 @@ int ParseCertRelative(DecodedCert* cert, int type, int verify, #endif ) { WOLFSSL_ERROR_VERBOSE(ASN_SIG_OID_E); + CERT_EVENT_FIELD(cert, ASN_SIG_OID_E, WC_CEV_FIELD_SIGALG); return ASN_SIG_OID_E; } @@ -26186,6 +27090,7 @@ int ParseCertRelative(DecodedCert* cert, int type, int verify, ) { WOLFSSL_MSG("Trailing data after certificate"); WOLFSSL_ERROR_VERBOSE(ASN_PARSE_E); + CERT_EVENT_FIELD(cert, ASN_PARSE_E, WC_CEV_FIELD_ENCODING); return ASN_PARSE_E; } #endif /* !WOLFSSL_NO_ASN_STRICT */ @@ -26223,6 +27128,11 @@ int ParseCertRelative(DecodedCert* cert, int type, int verify, #endif /* HAVE_RPK */ } #endif +#ifdef WOLFSSL_CERT_EVENTS + /* Decoded: the strict checks can run even when a later check fails + * and the verify callback allows it. */ + cert->certEventDecoded = 1; +#endif #ifdef HAVE_OCSP /* Map the OCSP responder-certificate mode onto a mode the signer @@ -26725,6 +27635,12 @@ int ParseCertRelative(DecodedCert* cert, int type, int verify, if (cert->badDate != 0) { if (verify != VERIFY_SKIP_DATE) { + #ifdef WOLFSSL_CERT_EVENTS + wc_CertEventDefect(cert, cert->badDate, WC_CEV_FIELD_VALIDITY, + (cert->badDate == WC_NO_ERR_TRACE(ASN_BEFORE_DATE_E)) ? + WC_CEV_REASON_NOT_YET_VALID : WC_CEV_REASON_EXPIRED, + 0, 0); + #endif return cert->badDate; } WOLFSSL_MSG_CERT_LOG("Date error: Verify option is skipping"); @@ -39496,6 +40412,10 @@ static int PaseCRL_CheckSignature(DecodedCRL* dcrl, const byte* sigParams, if (ret == 0) { WOLFSSL_MSG("Found CRL issuer CA"); + #ifdef WOLFSSL_CERT_EVENTS + /* A later ASN_CRL_NO_SIGNER_E means the issuer may not sign CRLs. */ + dcrl->crlSignerFound = 1; + #endif /* Verify CRL signature with CA. */ ret = VerifyCRL_Signature(&sigCtx, buff + dcrl->certBegin, dcrl->sigIndex - dcrl->certBegin, dcrl->signature, dcrl->sigLength, diff --git a/wolfcrypt/src/asn_orig.c b/wolfcrypt/src/asn_orig.c index c8d0b820f6d..e7a25fba63e 100644 --- a/wolfcrypt/src/asn_orig.c +++ b/wolfcrypt/src/asn_orig.c @@ -1578,27 +1578,36 @@ static int GetCertHeader(DecodedCert* cert) { int ret = 0, len; - if (GetSequence(cert->source, &cert->srcIdx, &len, cert->maxIdx) < 0) + if (GetSequence(cert->source, &cert->srcIdx, &len, cert->maxIdx) < 0) { + CERT_EVENT_FIELD(cert, ASN_PARSE_E, WC_CEV_FIELD_ENCODING); return ASN_PARSE_E; + } /* Reset the max index for the size indicated in the outer wrapper. */ cert->maxIdx = (word32)len + cert->srcIdx; cert->certBegin = cert->srcIdx; - if (GetSequence(cert->source, &cert->srcIdx, &len, cert->maxIdx) < 0) + if (GetSequence(cert->source, &cert->srcIdx, &len, cert->maxIdx) < 0) { + CERT_EVENT_FIELD(cert, ASN_PARSE_E, WC_CEV_FIELD_ENCODING); return ASN_PARSE_E; + } cert->sigIndex = (word32)len + cert->srcIdx; - if (cert->sigIndex > cert->maxIdx) + if (cert->sigIndex > cert->maxIdx) { + CERT_EVENT_FIELD(cert, ASN_PARSE_E, WC_CEV_FIELD_ENCODING); return ASN_PARSE_E; + } if (GetExplicitVersion(cert->source, &cert->srcIdx, &cert->version, - cert->sigIndex) < 0) + cert->sigIndex) < 0) { + CERT_EVENT_FIELD(cert, ASN_PARSE_E, WC_CEV_FIELD_VERSION); return ASN_PARSE_E; + } ret = wc_GetSerialNumber(cert->source, &cert->srcIdx, cert->serial, &cert->serialSz, cert->sigIndex); if (ret < 0) { + CERT_EVENT_FIELD(cert, ret, WC_CEV_FIELD_SERIAL); return ret; } @@ -2809,13 +2818,17 @@ int wc_GetPubX509(DecodedCert* cert, int verify, int* badDate) #endif /* Using the sigIndex as the upper bound because that's where the * actual certificate data ends. */ - if ((ret = GetSigAlg(cert, &cert->signatureOID, cert->sigIndex)) < 0) + if ((ret = GetSigAlg(cert, &cert->signatureOID, cert->sigIndex)) < 0) { + CERT_EVENT_FIELD(cert, ret, WC_CEV_FIELD_TBS_SIGALG); return ret; + } WOLFSSL_MSG("Got Algo ID"); - if ( (ret = GetName(cert, ASN_ISSUER, (int)cert->sigIndex)) < 0) + if ( (ret = GetName(cert, ASN_ISSUER, (int)cert->sigIndex)) < 0) { + CERT_EVENT_FIELD(cert, ret, WC_CEV_FIELD_ISSUER); return ret; + } if ( (ret = GetValidity(cert, verify, (int)cert->sigIndex)) < 0) { /* Only a date error is deferred for the caller to override. A @@ -2823,6 +2836,7 @@ int wc_GetPubX509(DecodedCert* cert, int verify, int* badDate) * something a verify callback may wave through. */ if ((ret != WC_NO_ERR_TRACE(ASN_BEFORE_DATE_E)) && (ret != WC_NO_ERR_TRACE(ASN_AFTER_DATE_E))) { + CERT_EVENT_FIELD(cert, ret, WC_CEV_FIELD_VALIDITY); return ret; } *badDate = ret; @@ -2831,8 +2845,10 @@ int wc_GetPubX509(DecodedCert* cert, int verify, int* badDate) } #endif - if ( (ret = GetName(cert, ASN_SUBJECT, (int)cert->sigIndex)) < 0) + if ( (ret = GetName(cert, ASN_SUBJECT, (int)cert->sigIndex)) < 0) { + CERT_EVENT_FIELD(cert, ret, WC_CEV_FIELD_SUBJECT); return ret; + } WOLFSSL_MSG("Got Subject Name"); return ret; @@ -2872,8 +2888,10 @@ int DecodeToKey(DecodedCert* cert, int verify) } ret = GetCertKey(cert, cert->source, &cert->srcIdx, cert->maxIdx); - if (ret != 0) + if (ret != 0) { + CERT_EVENT_FIELD(cert, ret, WC_CEV_FIELD_SPKI); return ret; + } WOLFSSL_MSG("Got Key"); @@ -4459,6 +4477,9 @@ static int DecodeCertExtensions(DecodedCert* cert) while (idx < (word32)sz) { word32 localIdx; + #ifdef WOLFSSL_CERT_EVENTS + word32 oidIdx = 0; + #endif if (GetSequence(input, &idx, &length, sz) < 0) { WOLFSSL_MSG("\tfail: should be a SEQUENCE"); @@ -4466,6 +4487,9 @@ static int DecodeCertExtensions(DecodedCert* cert) } oid = 0; + #ifdef WOLFSSL_CERT_EVENTS + oidIdx = idx; + #endif if ((ret = GetObjectId(input, &idx, &oid, oidCertExtType, sz)) < 0) { WOLFSSL_MSG("\tfail: OBJECT ID"); return ret; @@ -4500,6 +4524,19 @@ static int DecodeCertExtensions(DecodedCert* cert) ret = DecodeExtensionType(input + idx, (word32)length, oid, critical, cert, NULL); + #ifdef WOLFSSL_CERT_EVENTS + if (ret < 0) { + /* GetObjectId() has already checked the OID's encoding. */ + int oidSz = 0; + if (GetASNObjectId(input, &oidIdx, &oidSz, (word32)sz) == 0) { + CertEventExtension(cert, ret, oid, input + oidIdx, + (word32)oidSz, critical); + } + else { + CertEventExtension(cert, ret, oid, NULL, 0, critical); + } + } + #endif if (ret == WC_NO_ERR_TRACE(ASN_CRIT_EXT_E)) { ret = 0; criticalFail = 1; @@ -9846,6 +9883,10 @@ int ParseCRL(RevokedCert* rcert, DecodedCRL* dcrl, const byte* buff, word32 sz, } WOLFSSL_MSG("Found CRL issuer CA"); +#ifdef WOLFSSL_CERT_EVENTS + /* A later ASN_CRL_NO_SIGNER_E means the issuer may not sign CRLs. */ + dcrl->crlSignerFound = 1; +#endif ret = VerifyCRL_Signature(&sigCtx, buff + dcrl->certBegin, dcrl->sigIndex - dcrl->certBegin, dcrl->signature, dcrl->sigLength, dcrl->signatureOID, sigParams, sigParamsSz, ca, dcrl->heap); diff --git a/wolfcrypt/src/error.c b/wolfcrypt/src/error.c index 21f0aad71e7..5a027997fd6 100644 --- a/wolfcrypt/src/error.c +++ b/wolfcrypt/src/error.c @@ -798,6 +798,26 @@ static const char* wc_GetErrorStringIdx(int error) case WC_SPAN2_TO_IDX(HWPUF_ZEROIZE_E): return "HWPUF zeroize failed"; + case WC_SPAN2_TO_IDX(ASN_STRICT_UNIQUE_ID_E): + return "Certificate has a unique identifier, rejected by strict checks"; + + case WC_SPAN2_TO_IDX(ASN_STRICT_NO_AKID_E): + return "Certificate has no authority key identifier, rejected by " + "strict checks"; + + case WC_SPAN2_TO_IDX(ASN_STRICT_NO_KEY_USAGE_E): + return "CA certificate has no key usage, rejected by strict checks"; + + case WC_SPAN2_TO_IDX(ASN_STRICT_NO_CRL_DP_E): + return "Certificate has no CRL distribution point, rejected by " + "strict checks"; + + case WC_SPAN2_TO_IDX(ASN_STRICT_NO_OCSP_E): + return "Certificate has no OCSP responder, rejected by strict checks"; + + case WC_SPAN2_TO_IDX(ASN_STRICT_CRL_THIS_UPDATE_E): + return "CRL thisUpdate is in the future, rejected by strict checks"; + default: return wc_unknown_error_str; } @@ -1060,6 +1080,12 @@ static WC_MAYBE_UNUSED void wc_ErrorStringsCheck(int error) case HWPUF_GENERATE_KEY_E: case HWPUF_GET_KEY_E: case HWPUF_ZEROIZE_E: + case ASN_STRICT_UNIQUE_ID_E: + case ASN_STRICT_NO_AKID_E: + case ASN_STRICT_NO_KEY_USAGE_E: + case ASN_STRICT_NO_CRL_DP_E: + case ASN_STRICT_NO_OCSP_E: + case ASN_STRICT_CRL_THIS_UPDATE_E: case MAX_CODE_E: case WC_SPAN1_MIN_CODE_E: case MIN_CODE_E: diff --git a/wolfssl/internal.h b/wolfssl/internal.h index d293decb603..a4f74fa5e43 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -2979,6 +2979,11 @@ struct WOLFSSL_CERT_MANAGER { #ifdef HAVE_CRL_UPDATE_CB CbUpdateCRL cbUpdateCRL; /* notify thru cb that crl has updated */ #endif +#ifdef WOLFSSL_CERT_EVENTS + WOLFSSL_CERT_EVENT_CB certEventCb; /* validation event callback */ + void* certEventCtx; /* passed to certEventCb */ + word32 certEventStrict; /* strict checks to enforce */ +#endif }; WOLFSSL_LOCAL int CM_SaveCertCache(WOLFSSL_CERT_MANAGER* cm, @@ -2993,6 +2998,14 @@ WOLFSSL_LOCAL int CM_GetCertCacheMemSize(WOLFSSL_CERT_MANAGER* cm); WOLFSSL_LOCAL int CM_VerifyBuffer_ex(WOLFSSL_CERT_MANAGER* cm, const byte* buff, long sz, int format, int prev_err, int* crlErr); +#ifdef WOLFSSL_CERT_EVENTS +WOLFSSL_LOCAL int CM_VerifyBufferDepth(WOLFSSL_CERT_MANAGER* cm, + const byte* buff, long sz, int format, + int depth, int* crlErr, int* strictErr); +WOLFSSL_LOCAL void CM_CertEventResult(WOLFSSL_CERT_MANAGER* cm, int error, + int depth, const byte* der, + word32 derSz, const DecodedCert* cert); +#endif #ifndef NO_CERTS @@ -3033,6 +3046,11 @@ typedef struct ProcPeerCertArgs { #ifdef HAVE_CERTIFICATE_STATUS_REQUEST word16 csrAcked:1; /* survives the staple checks clearing status_request */ #endif +#ifdef WOLFSSL_CERT_EVENTS + word16 notCaSet:1; /* notCaHash is set */ + byte notCaHash[KEYID_SIZE]; /* Subject hash of a certificate the peer + * sent that is not a CA */ +#endif } ProcPeerCertArgs; WOLFSSL_LOCAL int DoVerifyCallback(WOLFSSL_CERT_MANAGER* cm, WOLFSSL* ssl, int cert_err, ProcPeerCertArgs* args); @@ -7289,6 +7307,12 @@ struct WOLFSSL { #ifdef HAVE_CERTIFICATE_STATUS_REQUEST_V2 byte status_request_v2; #endif + #if defined(WOLFSSL_CERT_EVENTS) && \ + (defined(HAVE_CERTIFICATE_STATUS_REQUEST) || \ + defined(HAVE_CERTIFICATE_STATUS_REQUEST_V2)) + /* The peer certificate's RESULT event waits for its status. */ + byte certEventResultHeld; + #endif #if defined(HAVE_CRL) && (defined(HAVE_CERTIFICATE_STATUS_REQUEST) || \ defined(HAVE_CERTIFICATE_STATUS_REQUEST_V2)) /* Leaf CRL verdict taken while the DecodedCert was alive, applied at diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index 24ebab8000d..dd351504a69 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -2603,6 +2603,13 @@ WOLFSSL_API WOLFSSL_X509_STORE_CTX *wolfSSL_X509_STORE_CTX_get0_parent_ctx( WOLFSSL_X509_STORE_CTX *ctx); WOLFSSL_API int wolfSSL_X509_STORE_set_flags(WOLFSSL_X509_STORE* store, unsigned long flag); +#ifdef WOLFSSL_CERT_EVENTS +/* Register a validation event callback on the store, for X509_verify_cert(). */ +WOLFSSL_API int wolfSSL_X509_STORE_SetCertEventCb(WOLFSSL_X509_STORE* store, + WOLFSSL_CERT_EVENT_CB cb, void* ctx); +WOLFSSL_API int wolfSSL_X509_STORE_SetCertEventStrict( + WOLFSSL_X509_STORE* store, word32 enforce); +#endif WOLFSSL_API int wolfSSL_X509_STORE_get_by_subject(WOLFSSL_X509_STORE_CTX* ctx, int idx, WOLFSSL_X509_NAME* name, WOLFSSL_X509_OBJECT* obj); WOLFSSL_API WOLFSSL_X509_VERIFY_PARAM *wolfSSL_X509_STORE_CTX_get0_param( @@ -4673,6 +4680,23 @@ WOLFSSL_API void wolfSSL_CTX_SetPerformTlsRecordProcessingCb(WOLFSSL_CTX* ctx, WOLFSSL_API void wolfSSL_CertManagerFree(WOLFSSL_CERT_MANAGER* cm); WOLFSSL_API int wolfSSL_CertManager_up_ref(WOLFSSL_CERT_MANAGER* cm); +#ifdef WOLFSSL_CERT_EVENTS + /* Register a callback that observes certificate validation events: one + * DEFECT event per failed check, and one RESULT event when a verification + * finishes. Events never change the verification result; only an + * enforced strict check does. */ + WOLFSSL_API int wolfSSL_CertManagerSetCertEventCb(WOLFSSL_CERT_MANAGER* cm, + WOLFSSL_CERT_EVENT_CB cb, void* ctx); + WOLFSSL_API int wolfSSL_CTX_SetCertEventCb(WOLFSSL_CTX* ctx, + WOLFSSL_CERT_EVENT_CB cb, void* cbCtx); + /* Enforce strict checks: each wc_CertEventStrict bit set turns that + * check's NOTICE into a failure. */ + WOLFSSL_API int wolfSSL_CertManagerSetCertEventStrict( + WOLFSSL_CERT_MANAGER* cm, word32 enforce); + WOLFSSL_API int wolfSSL_CTX_SetCertEventStrict(WOLFSSL_CTX* ctx, + word32 enforce); +#endif + #ifdef WC_ASN_UNKNOWN_EXT_CB /* Register a callback invoked for each certificate extension whose OID * the parser does not recognize. Returning 0 accepts the extension; a diff --git a/wolfssl/wolfcrypt/asn.h b/wolfssl/wolfcrypt/asn.h index 6bb3427a208..25bf54376a9 100644 --- a/wolfssl/wolfcrypt/asn.h +++ b/wolfssl/wolfcrypt/asn.h @@ -2311,6 +2311,27 @@ struct DecodedCert { void* unknownExtCallbackExCtx; void* unknownExtCallback32ExCtx; #endif +#ifdef WOLFSSL_CERT_EVENTS + WOLFSSL_CERT_EVENT_CB certEventCb; /* Validation event callback */ + void* certEventCtx; /* Callback context */ + int certEventRole; /* enum wc_CertEventRole */ + int certEventDepth; /* Chain depth, -1 unknown */ + int certEventReported; /* Error of the last + * DEFECT sent, 0 if none */ + byte certEventInfoSent; /* The INFO was sent */ + byte certEventStrictOn; /* Strict checks: 0 off, + * 1 in ParseCertRelative(), + * 2 run by the caller */ + word32 certEventStrict; /* Checks to enforce */ + byte certEventUid; /* Unique IDs present: 1 + * issuer, 2 subject */ + byte certEventDecoded; /* Fully decoded */ + byte certEventNotCaSet; /* certEventNotCa is set */ + byte certEventNotCa[KEYID_SIZE]; /* Subject hash + * of a certificate the peer + * sent that is not a CA */ + void* certEventSsl; /* TLS connection, or NULL */ +#endif #ifdef WOLFSSL_DUAL_ALG_CERTS /* Subject Alternative Public Key Info */ byte *sapkiDer; @@ -2589,6 +2610,32 @@ WOLFSSL_API int wc_SetUnknownExtCallback32Ex(DecodedCert* cert, void *ctx); #endif +#ifdef WOLFSSL_CERT_EVENTS +WOLFSSL_API int wc_SetCertEventCallback(DecodedCert* cert, + WOLFSSL_CERT_EVENT_CB cb, void* ctx); +WOLFSSL_API int wc_SetCertEventStrict(DecodedCert* cert, word32 enforce); +WOLFSSL_LOCAL int wc_CertEventStrictResolve(word32 enforce, word32* resolved); +WOLFSSL_LOCAL void wc_CertEventStrictDefer(DecodedCert* cert, word32 enforce); +WOLFSSL_LOCAL int wc_CertEventStrictChecks(DecodedCert* cert); +WOLFSSL_LOCAL void wc_CertEventSetConnection(DecodedCert* cert, void* ssl, + const byte* notCaHash); +WOLFSSL_LOCAL void wc_CertEventIdentity(const DecodedCert* cert, + WOLFSSL_CERT_EVENT* ev); +WOLFSSL_LOCAL void wc_CertEventSetPosition(DecodedCert* cert, int role, + int depth); +WOLFSSL_LOCAL void wc_CertEventSend(WOLFSSL_CERT_EVENT_CB cb, void* ctx, + WOLFSSL_CERT_EVENT* ev); +WOLFSSL_LOCAL void wc_CertEventDefect(DecodedCert* cert, int error, + int field, int reason, word32 extOID, + byte critical); +WOLFSSL_LOCAL void wc_CertEventField(DecodedCert* cert, int error, int field); +WOLFSSL_LOCAL void wc_CertEventFailure(DecodedCert* cert, int error, + int field, int reason); +WOLFSSL_LOCAL void wc_CertEventFailureCond(DecodedCert* cert, int error, + int field, int reason, + int condition); +#endif + WOLFSSL_LOCAL int DecodePolicyOID(char *out, word32 outSz, const byte *in, word32 inSz); WOLFSSL_LOCAL int EncodePolicyOID(byte *out, word32 *outSz, @@ -3339,6 +3386,9 @@ struct DecodedCRL { WC_BITFIELD extAuthKeyIdSet:1; /* Auth key identifier set indicator */ #endif WC_BITFIELD crlNumberSet:1; /* CRL number set indicator */ +#ifdef WOLFSSL_CERT_EVENTS + WC_BITFIELD crlSignerFound:1; /* An issuer was found to verify */ +#endif #ifdef WC_ASN_UNKNOWN_EXT_CB wc_UnknownExtCallback unknownExtCallback; wc_UnknownExtCallback32 unknownExtCallback32; diff --git a/wolfssl/wolfcrypt/asn_public.h b/wolfssl/wolfcrypt/asn_public.h index 63dc5426298..c2c065087a7 100644 --- a/wolfssl/wolfcrypt/asn_public.h +++ b/wolfssl/wolfcrypt/asn_public.h @@ -1190,6 +1190,193 @@ WOLFSSL_API int wc_Asn1_PrintAll(Asn1* asn1, Asn1PrintOptions* opts, #endif /* WOLFSSL_ASN_PRINT */ +#ifdef WOLFSSL_CERT_EVENTS +/* Certificate and CRL validation events. + * + * An event describes one failed check (DEFECT), the outcome of one + * verification (RESULT), a certificate's algorithms (INFO), or a strict + * profile check that fired without failing (NOTICE). Values are only ever + * appended to these enums so that existing applications keep working. */ + +/* What the event reports. */ +enum wc_CertEventKind { + WC_CEV_KIND_DEFECT = 0, /* A check failed. May still be recovered. */ + WC_CEV_KIND_RESULT = 1, /* End of one verification. */ + WC_CEV_KIND_INFO = 2, /* A certificate's algorithms and key size, + * once for each certificate checked. */ + WC_CEV_KIND_NOTICE = 3 /* A strict check fired. The result is + * unchanged unless the check is enforced, + * when a DEFECT is sent instead. */ +}; + +/* Kind of object being validated. */ +enum wc_CertEventCategory { + WC_CEV_CAT_CERT = 0, + WC_CEV_CAT_CRL = 1 +}; + +/* Which object in the chain failed. */ +enum wc_CertEventRole { + WC_CEV_ROLE_CANDIDATE = 0, /* End-entity or peer certificate. */ + WC_CEV_ROLE_SIGNER = 1, /* CA that signed the candidate. */ + WC_CEV_ROLE_TRUST_ANCHOR = 2, /* CA being loaded as trusted. */ + WC_CEV_ROLE_CRL = 3, + WC_CEV_ROLE_CRL_SIGNER = 4 +}; + +/* Which part of the object failed, using RFC 5280 names. */ +enum wc_CertEventField { + WC_CEV_FIELD_NONE = 0, /* RESULT events, or not known. */ + WC_CEV_FIELD_ENCODING = 1, /* Not DER or not parseable. */ + WC_CEV_FIELD_VERSION = 2, + WC_CEV_FIELD_SERIAL = 3, + WC_CEV_FIELD_TBS_SIGALG = 4, + WC_CEV_FIELD_ISSUER = 5, + WC_CEV_FIELD_VALIDITY = 6, + WC_CEV_FIELD_SUBJECT = 7, + WC_CEV_FIELD_SPKI = 8, + WC_CEV_FIELD_ISSUER_UID = 9, + WC_CEV_FIELD_SUBJECT_UID = 10, + WC_CEV_FIELD_EXTENSION = 11, /* See extOID. */ + WC_CEV_FIELD_SIGALG = 12, + WC_CEV_FIELD_SIGNATURE = 13, + WC_CEV_FIELD_CHAIN = 14, /* Signer lookup, CA flag, path. */ + WC_CEV_FIELD_CRL_THIS_UPDATE = 15, + WC_CEV_FIELD_CRL_NEXT_UPDATE = 16, + WC_CEV_FIELD_CRL_REVOKED = 17 +}; + +/* Why the check failed. */ +enum wc_CertEventReason { + WC_CEV_REASON_NONE = 0, /* RESULT events, or not known. */ + WC_CEV_REASON_MALFORMED = 1, /* Present but badly encoded. */ + WC_CEV_REASON_MISSING = 2, /* Required but not present. */ + WC_CEV_REASON_UNSUPPORTED = 3, /* Unknown OID or not compiled in. */ + WC_CEV_REASON_NOT_YET_VALID = 4, + WC_CEV_REASON_EXPIRED = 5, + WC_CEV_REASON_SIGNATURE = 6, /* Signature does not verify. */ + WC_CEV_REASON_NO_SIGNER = 7, /* Issuer not found or not a CA. */ + WC_CEV_REASON_REVOKED = 8, + WC_CEV_REASON_POLICY = 9, /* A policy check fired: a strict + * check, or a key that is too + * small. */ + WC_CEV_REASON_CONSTRAINT = 10 /* A constraint is broken: the + * signer's CA flag or key usage, + * path length, name constraints, or + * the uses the certificate allows. */ +}; + +/* One stable value per condition, for applications to map to their own + * codes. Named after the IEC 62351-9 and IEC 62351-3 audit events. */ +enum wc_CertEventCondition { + WC_CEV_COND_NONE = 0, + WC_CEV_COND_CERT_FORMAT = 1, /* Not decodable; see field. */ + WC_CEV_COND_CERT_VERSION = 2, + WC_CEV_COND_CERT_ISSUER = 3, /* Issuer name invalid. */ + WC_CEV_COND_CERT_SUBJECT = 4, + WC_CEV_COND_CERT_EXT_UNKNOWN_CRITICAL = 5, + WC_CEV_COND_CERT_EXT_VALUE = 6, /* See extOidData. */ + WC_CEV_COND_CERT_EXPIRED = 7, + WC_CEV_COND_CERT_NOT_YET_VALID = 8, + WC_CEV_COND_CERT_SIG_INVALID = 9, + WC_CEV_COND_CERT_SIG_ALG_UNSUPPORTED = 10, + WC_CEV_COND_CERT_SIG_ALG_MISMATCH = 11, /* Inner and outer differ. */ + WC_CEV_COND_CERT_KEY_UNSUPPORTED = 12, + WC_CEV_COND_CERT_KEY_TOO_SMALL = 13, /* Below the minimum set. */ + WC_CEV_COND_CERT_NO_ISSUER = 14, /* No trusted CA matches. */ + WC_CEV_COND_CERT_UNTRUSTED_ROOT = 15, /* Self-issued, not trusted. */ + WC_CEV_COND_CERT_NOT_CA = 16, /* Issuer the peer sent is not + * a CA. */ + WC_CEV_COND_CERT_ISSUER_KU = 17, /* CA without keyCertSign. */ + WC_CEV_COND_CERT_PATH_LEN = 18, + WC_CEV_COND_CERT_NAME_CONSTRAINTS = 19, + WC_CEV_COND_CERT_KU_PURPOSE = 20, /* Key usage forbids the use. */ + WC_CEV_COND_CERT_EKU_PURPOSE = 21, /* EKU forbids the use. */ + WC_CEV_COND_TLS_NO_PEER_CERT = 22, + WC_CEV_COND_TLS_CHAIN_TOO_LONG = 23, + /* Revocation, on the certificate checked. */ + WC_CEV_COND_CERT_REVOKED = 24, + WC_CEV_COND_CRL_MISSING = 25, /* No CRL from its issuer. */ + WC_CEV_COND_CRL_EXPIRED = 26, /* Past the CRL's nextUpdate. */ + /* A CRL that is rejected, when loaded or when used. */ + WC_CEV_COND_CRL_FORMAT = 27, + WC_CEV_COND_CRL_SIG_INVALID = 28, + WC_CEV_COND_CRL_NO_SIGNER = 29, /* Issuer not trusted. */ + WC_CEV_COND_CRL_SIGNER_KU = 30, /* Issuer lacks cRLSign. */ + WC_CEV_COND_CRL_EXT_UNKNOWN_CRITICAL = 31, + /* Strict checks, sent as NOTICE, or as DEFECT when enforced. */ + WC_CEV_COND_CERT_UNIQUE_ID = 32, /* Issuer or subject UID. */ + WC_CEV_COND_CERT_NO_AKID = 33, /* Or no keyIdentifier. */ + WC_CEV_COND_CERT_NO_KEY_USAGE = 34, /* A CA without key usage. */ + WC_CEV_COND_CERT_NO_CRL_DP = 35, + WC_CEV_COND_CERT_NO_OCSP = 36, /* No OCSP responder in AIA. */ + WC_CEV_COND_CRL_THIS_UPDATE_FUTURE = 37 /* When the CRL is loaded. */ +}; + +/* Strict checks to enforce, ORed together. A check that is not enforced is + * still reported as a NOTICE when an event callback is set. An enforced check + * fails the certificate with its ASN_STRICT_*_E error, which the verify + * callback may override, or rejects the CRL being loaded. Self-signed + * certificates are exempt from the AKID, CRL_DP and OCSP checks. */ +enum wc_CertEventStrict { + WC_CEV_STRICT_UNIQUE_ID = 0x01, /* ASN_STRICT_UNIQUE_ID_E */ + WC_CEV_STRICT_AKID = 0x02, /* ASN_STRICT_NO_AKID_E */ + WC_CEV_STRICT_KEY_USAGE = 0x04, /* ASN_STRICT_NO_KEY_USAGE_E */ + WC_CEV_STRICT_CRL_DP = 0x08, /* ASN_STRICT_NO_CRL_DP_E */ + WC_CEV_STRICT_OCSP = 0x10, /* ASN_STRICT_NO_OCSP_E */ + WC_CEV_STRICT_CRL_THIS_UPDATE = 0x20, /* ASN_STRICT_CRL_THIS_UPDATE_E */ + WC_CEV_STRICT_ALL = 0x3F +}; + +/* One validation event. Pointers are only valid during the callback and + * point at untrusted input: escape before logging. */ +typedef struct WOLFSSL_CERT_EVENT { + word32 size; /* sizeof(WOLFSSL_CERT_EVENT). */ + int kind; /* enum wc_CertEventKind */ + int category; /* enum wc_CertEventCategory */ + int error; /* wolfSSL error code, 0 on success. */ + int role; /* enum wc_CertEventRole */ + int depth; /* Chain depth, -1 when not known. */ + int field; /* enum wc_CertEventField */ + int reason; /* enum wc_CertEventReason */ + word32 extOID; /* Extension OID sum, 0 when none. The + * sums depend on the build: compare + * with wolfSSL's OID constants or use + * extOidData. */ + const unsigned char* extOidData; /* Extension OID content octets, the + * same in every build. NULL when + * none. */ + word32 extOidSz; + byte critical; /* 1 when the extension was marked + * critical, otherwise 0. */ + const unsigned char* der; /* Encoded object, may be NULL. */ + word32 derSz; + int condition; /* enum wc_CertEventCondition */ + /* The certificate's algorithms, 0 when not known yet. The values are + * wolfSSL's types, such as CTC_SHA256wRSA, RSAk and ECDSAk. */ + word32 sigOID; /* Signature algorithm. */ + word32 keyOID; /* Public key type. */ + word32 curveOID; /* ECC curve OID sum, 0 if not ECC. */ + int keySz; /* Public key size in bits. */ + /* Which certificate, when it was decoded far enough. NULL when not + * known. */ + const char* subject; /* Subject name, one line. */ + const char* issuer; /* Issuer name, one line. */ + const unsigned char* serial; /* Serial number, big-endian. */ + word32 serialSz; + /* The TLS connection (WOLFSSL*) whose handshake raised the event, NULL + * outside a handshake. Use it to find the peer, for example its socket + * with wolfSSL_get_fd(). */ + void* ssl; +} WOLFSSL_CERT_EVENT; + +/* Observes events only: it cannot accept a certificate. Use the verify + * callback to override a failure. Must not call back into the certificate + * manager that raised the event, and may run on several threads at once. */ +typedef void (*WOLFSSL_CERT_EVENT_CB)(const WOLFSSL_CERT_EVENT* ev, + void* ctx); +#endif /* WOLFSSL_CERT_EVENTS */ + #ifdef __cplusplus } /* extern "C" */ #endif diff --git a/wolfssl/wolfcrypt/error-crypt.h b/wolfssl/wolfcrypt/error-crypt.h index df07b63fdc0..23e05b4fc72 100644 --- a/wolfssl/wolfcrypt/error-crypt.h +++ b/wolfssl/wolfcrypt/error-crypt.h @@ -365,9 +365,17 @@ enum wolfCrypt_ErrorCodes { HWPUF_GENERATE_KEY_E = -1040, /* HWPUF generate key failed */ HWPUF_GET_KEY_E = -1041, /* HWPUF get key failed */ HWPUF_ZEROIZE_E = -1042, /* HWPUF zeroize failed */ - - WC_SPAN2_LAST_E = -1042, /* Update to indicate last used error code */ - WC_LAST_E = -1042, /* the last code used either here or in + ASN_STRICT_UNIQUE_ID_E = -1043, /* Strict check: unique ID present */ + ASN_STRICT_NO_AKID_E = -1044, /* Strict check: no authority key ID */ + ASN_STRICT_NO_KEY_USAGE_E = -1045, /* Strict check: CA has no key usage */ + ASN_STRICT_NO_CRL_DP_E = -1046, /* Strict check: no CRL distribution + * point */ + ASN_STRICT_NO_OCSP_E = -1047, /* Strict check: no OCSP responder */ + ASN_STRICT_CRL_THIS_UPDATE_E = -1048, /* Strict check: CRL thisUpdate is + * in the future */ + + WC_SPAN2_LAST_E = -1048, /* Update to indicate last used error code */ + WC_LAST_E = -1048, /* the last code used either here or in * error-ssl.h */ WC_SPAN2_MIN_CODE_E = -1999, /* Last usable code in span 2 */