Skip to content

Add certificate validation events - #11972

Open
sameehj wants to merge 1 commit into
wolfSSL:masterfrom
sameehj:cert-events-skeleton
Open

sameehj wants to merge 1 commit into
wolfSSL:masterfrom
sameehj:cert-events-skeleton

Conversation

@sameehj

@sameehj sameehj commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

Description

Adds an optional callback that reports what happened while wolfSSL checked a
certificate or CRL. It is for security logging: an application can log each
failed check with a stable code. Regulations and standards such as the EU
Cyber Resilience Act and IEC 62351 ask for this kind of logging. Today the
application gets one error code for a whole chain and cannot tell which
certificate, field or check failed.

Fixes zd#21876

Built with --enable-cert-events (WOLFSSL_CERT_EVENTS); off by default,
included in --enable-all. When off, no struct field or ABI changes; only
the six new error codes exist.

What the callback receives

One WOLFSSL_CERT_EVENT per event:

  • DEFECT: one certificate or CRL check failed. Gives the certificate's role (peer, CA in
    the chain, trust anchor, CRL, CRL signer) and chain depth, the RFC 5280
    field, the reason (malformed, missing, unsupported, expired, signature,
    no signer, revoked, policy, constraint), and the wolfSSL error code.
  • RESULT: one verification finished; error is 0 when it passed. A
    DEFECT followed by a passed RESULT means the verify callback allowed it.
    A TLS 1.2 client that asked for the server's certificate status sends it
    once that status was checked at ServerHelloDone, since revocation can
    still fail there.
  • INFO: each certificate's signature algorithm, key type, curve and key
    size in bits, so the application can apply its own approved list.
  • NOTICE: an optional strict profile check fired (see below).

Every event carries a condition value: a stable key for each kind of
failure, named after the IEC 62351-9 and IEC 62351-3 audit events. Extension
OIDs come as content octets as well as OID sums, because the sums depend on
the build.

Each event also says which certificate it is about (subject, issuer, serial
number, when decoded) and, during a handshake, the WOLFSSL* connection, so
a log line can be tied to a peer. Host name and OCSP failures appear only in
the RESULT's error for now.

The callback only observes. The verify callback still decides; nothing about
accepting or rejecting a certificate changes unless a strict check is
enforced.

API

  • wolfSSL_CertManagerSetCertEventCb(), wolfSSL_CTX_SetCertEventCb(),
    wolfSSL_X509_STORE_SetCertEventCb(), wc_SetCertEventCallback() (no
    certificate manager).
  • wolfSSL_CertManagerSetCertEventStrict(), wolfSSL_CTX_SetCertEventStrict(),
    wolfSSL_X509_STORE_SetCertEventStrict(), wc_SetCertEventStrict().

Paths covered

TLS peer chains (each certificate with its depth), CertManagerVerify*,
X509_verify_cert(), trust anchors loaded with verification on, parsing
without a certificate manager, CRL checks of a certificate, and CRL loading.

Strict profile checks

Unique IDs present, no AKID, a CA without key usage, no CRL distribution
point, no OCSP responder, and a CRL thisUpdate in the future. Each one is
reported as a NOTICE. When its wc_CertEventStrict bit is set, it sends a
DEFECT and fails with its own error, ASN_STRICT_*_E (-1043 to -1048, with
error strings). The checks run only when a callback or an enforced check is
set, and last: when every other check passed, or once the verify callback
allowed what failed, in which case the strict failure goes to the verify
callback too. So allowing a strict failure does not skip host name, key
usage, chain length or revocation checks, and allowing one of those does
not skip the strict checks. WC_CEV_STRICT_ALL means every
check the build can run; naming one the build cannot run returns
NOT_COMPILED_IN. Trust anchors loaded with verification fail to load when
an enforced check fires. They do not overlap WOLFSSL_NO_ASN_STRICT, which
only relaxes existing rejections.

FIPS

The hooks are in asn.c, asn_orig.c, crl.c, internal.c, ssl_certman.c,
x509_str.c and ssl_api_cert.c. No FIPS module source changed. New error
codes are added to error-crypt.h span 2 and error.c.

Planned follow-ups (separate PRs)

  • More strict profile checks, for RFC 5280 rules wolfSSL accepts today:
    version and serial number, names, time encoding, empty or wrongly
    critical extensions, CA consistency, subject alternative names, the TLS
    leaf and the CRL profile. Same pattern as here: a NOTICE by default, and
    enforce bits grouped by area.
  • Events for OCSP responses, and for TLS handshake failures that are not
    about a certificate (IEC 62351-3).
  • wolfSSH: pass the event callback through to wolfSSL.

Found while checking the RFC 5280 rules, to be fixed on their own: a
certificate version value of 3 is accepted, and a valid critical
certificatePolicies, policyConstraints or inhibitAnyPolicy extension is
rejected in the default build.

Testing

  • certs/test/gen-cert-events.py (Python 3 and the openssl command line)
    generates certs/test/cert-events/: copies of server-cert.der with one
    defect each, chains with one fault each, CRLs with one fault each, and
    the strict-check certificates. All valid until 2049.
  • 20 tests in tests/api/test_certman.c, one or more for every condition,
    over the certificate manager, TLS (memio), X509_verify_cert(), trust
    anchors, parsing without a certificate manager, and CRLs. Includes
    regression tests that allowing a strict failure does not skip the host
    name check, that allowing another failure does not skip the strict
    checks, and that the RESULT waits for the TLS 1.2 certificate status.
  • Built and tested locally with: --enable-cert-events --enable-crl; the
    same with --enable-asn=original, --enable-smallstack plus
    WOLFSSL_NO_CRL_DATE_CHECK and IGNORE_KEY_EXTENSIONS,
    --enable-debug-trace-errcodes, and WOLFSSL_X509_TINY;
    --enable-cert-events without CRL, also with WOLFSSL_X509_TINY; C89
    with -Werror; --enable-all (full API suite). Built only:
    NO_ASN_TIME, --enable-cryptonly, and events off.
  • Three new CI entries in os-check-linux.json: cert-events-crl,
    cert-events-crl-asn-original and cert-events-tiny. --enable-all
    entries already cover the rest.

Checklist

  • added tests
  • updated/added doxygen
  • updated appropriate READMEs (certs/test/catalog.txt)
  • Updated manual and documentation (ChangeLog.md)

An optional callback that reports what happened while wolfSSL checked a
certificate or CRL, for security logging. Today an application gets one
error code for a whole chain and cannot tell which certificate, field or
check failed.

Built with --enable-cert-events (WOLFSSL_CERT_EVENTS, also in CMake and
--enable-all); off by default. When off, no struct or ABI changes.

Each WOLFSSL_CERT_EVENT says:
- kind: DEFECT for each failed certificate or CRL check, RESULT when a
  verification ends (0 when it passed, so a DEFECT then a passed RESULT
  means the verify callback allowed it), INFO with each certificate's
  algorithms and key size, and NOTICE for a strict check that fired
  without failing.
- the certificate's role (peer, chain CA, trust anchor, CRL, CRL signer)
  and chain depth, the RFC 5280 field, the reason and the error code.
- a stable condition value for each kind of failure, named after the
  IEC 62351-9 and IEC 62351-3 audit events.
- the extension OID as content octets, since OID sums depend on the build.
- which certificate: its subject, issuer and serial number, and the TLS
  connection (WOLFSSL*) whose handshake raised the event.

The callback only observes; the verify callback still decides. Set with
wolfSSL_CertManagerSetCertEventCb(), wolfSSL_CTX_SetCertEventCb(),
wolfSSL_X509_STORE_SetCertEventCb() or wc_SetCertEventCallback(). Events
come from TLS peer chains, CertManagerVerify*, X509_verify_cert(), trust
anchors loaded with verification, parsing without a certificate manager,
CRL checks and CRL loading, with both ASN.1 parsers.

Optional strict profile checks report a unique ID, no AKID keyIdentifier,
a CA without key usage, no CRL distribution point, no OCSP responder and
a CRL thisUpdate in the future. Each can be enforced with
wolfSSL_CertManagerSetCertEventStrict() and friends, failing with its own
error, ASN_STRICT_*_E (-1043 to -1048). They run after every other
check, also when the verify callback allows another failure, so allowing
one failure never skips another check.
WC_CEV_STRICT_ALL means every check the build can run. Self-signed
certificates are exempt from the issuer checks.

Tests: certs/test/gen-cert-events.py generates certs/test/cert-events/,
certificates, chains and CRLs with one fault each, valid until 2049.
tests/api/test_certman.c covers each condition over the certificate
manager, TLS, X509_verify_cert(), trust anchors and CRLs. Two Linux CI
entries build the feature without the compatibility layer.
Copilot AI balanced review requested due to automatic review settings November 3, 2026 14:42
@sameehj
sameehj requested review from MarkAtwood and dgarske November 3, 2026 14:44
@sameehj sameehj self-assigned this Nov 3, 2026
@github-actions

github-actions Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

MemBrowse Memory Report · Explore what changed →

gcc-arm-cortex-m0plus

  • FLASH: .text -4 B (-0.0%, 70,675 B / 262,144 B, total: 27% used)

gcc-arm-cortex-m4

  • FLASH: .rodata.CSWTCH.2 +24 B, .rodata.str1.1 +382 B (+0.2%, 208,966 B / 262,144 B, total: 80% used)

gcc-arm-cortex-m4-crypto-only

  • FLASH: .rodata.CSWTCH.2 +24 B, .rodata.str1.1 +382 B (+0.2%, 182,057 B / 262,144 B, total: 69% used)

gcc-arm-cortex-m4-openssl-compat

  • FLASH: .rodata +408 B (+0.1%, 797,652 B / 1,048,576 B, total: 76% used)

gcc-arm-cortex-m4-pkcs7

  • FLASH: .rodata.CSWTCH.2 +24 B, .rodata.str1.1 +382 B (+0.2%, 223,210 B / 262,144 B, total: 85% used)

gcc-arm-cortex-m4-pq

  • FLASH: .rodata +400 B (+0.1%, 312,352 B / 1,048,576 B, total: 30% used)

gcc-arm-cortex-m4-rsa-only

  • FLASH: .rodata +400 B (+0.1%, 343,000 B / 1,048,576 B, total: 33% used)

gcc-arm-cortex-m4-tls13

  • FLASH: .rodata.CSWTCH.2 +24 B, .rodata.str1.1 +382 B (+0.2%, 249,272 B / 262,144 B, total: 95% used)

gcc-arm-cortex-m7

  • FLASH: .rodata.CSWTCH.2 +24 B, .rodata.str1.1 +382 B (+0.2%, 209,030 B / 262,144 B, total: 80% used)

gcc-arm-cortex-m7-pq

  • FLASH: .rodata +400 B (+0.1%, 313,248 B / 1,048,576 B, total: 30% used)

gcc-arm-cortex-m7-tls13

  • FLASH: .rodata.CSWTCH.2 +24 B, .rodata.str1.1 +382 B (+0.2%, 249,336 B / 262,144 B, total: 95% used)

stm32-sim-stm32h753

  • FLASH: .text +416 B (+0.2%, 205,004 B / 2,097,152 B, total: 10% used)

zephyr-frdm_rw612

  • FLASH: log_const_area -8 B, rodata -8 B, text +1,552 B (+0.5%, 284,000 B / 67,108,864 B, total: 0% used)

No memory changes detected for:


Catch memory regressions in CI with MemBrowse. Free for open source.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved critical findings allow strict-error overrides to bypass additional enforced checks and revocation validation, or report a successful CA load without installing the CA.

6 open issues
What changed in this PR

Adds optional certificate and CRL validation events to wolfSSL for structured security logging, alongside configurable strict-profile checks.

Changes:

  • Introduces event callbacks with stable condition codes, certificate identity, algorithm details, and verification outcomes.
  • Integrates reporting and strict checks into certificate parsing, TLS, certificate-manager, X509-store, and CRL paths.
  • Adds build options, API documentation, test registrations, certificate fixtures, and CI configurations.
File Description
wolfssl/​wolfcrypt/​error-crypt.h Defines six strict-check error codes.
wolfssl/​wolfcrypt/​asn.h Adds parser event state and helper declarations.
wolfssl/​wolfcrypt/​asn_public.h Defines public event types and strict-check flags.
wolfssl/​ssl.h Declares event callback and enforcement APIs.
wolfssl/​internal.h Adds certificate-manager and handshake event state.
wolfcrypt/​src/​error.c Adds strict-check error strings.
wolfcrypt/​src/​asn.c Implements event reporting and strict certificate checks.
wolfcrypt/​src/​asn_orig.c Adds original-parser failure reporting.
tests/​api/​test_certman.h Registers certificate-event tests.
src/​x509_str.c Integrates events into X509-store verification.
src/​ssl_certman.c Adds manager APIs and verification events.
src/​ssl_api_cert.c Exposes TLS-context configuration APIs.
src/​internal.c Reports TLS certificate events and deferred status results.
src/​crl.c Adds CRL events and strict thisUpdate checks.
doc/​dox_comments/​header_files/​ssl.h Documents manager, context, and store APIs.
doc/​dox_comments/​header_files/​asn_public.h Documents standalone parser APIs.
configure.ac Adds the Autotools feature option.
CMakeLists.txt Adds the CMake feature option.
cmake/​options.h.in Exports the event feature define.
ChangeLog.md Describes validation events and strict checks.
certs/​test/​include.am Includes event fixtures in source distributions.
certs/​test/​gen-cert-events.py Generates certificate, chain, and CRL fixtures.
certs/​test/​cert-events/​chain-signer-not-ca.pem Tests a signer without CA status.
certs/​test/​cert-events/​chain-signer-no-certsign.pem Tests missing certificate-signing permission.
certs/​test/​cert-events/​chain-pathlen-exceeded.pem Tests a path-length violation.
certs/​test/​cert-events/​chain-leaf-ku-no-digsig.pem Tests missing leaf digital-signature usage.
certs/​test/​cert-events/​chain-leaf-eku-client-only.pem Tests a server leaf restricted to client authentication.
certs/​test/​cert-events/​chain-good.pem Provides a valid reference chain.
certs/​test/​catalog.txt Describes fixtures and regeneration.
.github/​configs/​os-check-linux.json Adds three certificate-event CI configurations.

Review effort: Balanced

Comment thread src/ssl_certman.c
Comment on lines +926 to +930
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):
Comment thread src/ssl_certman.c
Comment on lines +1092 to +1093
if ((ret == 0) && (crlRet == 0)) {
ret = wc_CertEventStrictChecks(cert);
Comment thread wolfcrypt/src/asn.c
Comment on lines +24413 to +24414
if (enforced && (ret == 0)) {
ret = c->error;
Comment thread CMakeLists.txt
Comment on lines +1947 to +1949
add_option("WOLFSSL_CERT_EVENTS"
"Enable structured certificate and CRL validation event callback (default: disabled)"
"no" "yes;no")
Comment thread src/crl.c
Comment on lines +759 to +761
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);
Comment thread wolfcrypt/src/asn.c
if (curveId >= 0) {
int sz = wc_ecc_get_curve_size_from_id(curveId);
if (sz > 0) {
bits = sz * 8;
@sameehj
sameehj removed the request for review from dgarske November 3, 2026 15:36
@MarkAtwood

Copy link
Copy Markdown
Contributor

this is too big to review as one 5.3k line commit, can you split it into a stack? rough cut: 1) event struct, cm/ctx setters, defect+result on the tls peer chain and CertManagerVerify, template asn.c only, with the sel conditions. 2) x509_store, trust anchor load, no-cm parsing. 3) crl. 4) info events. 5) asn_orig parity. 6) strict checks and the 6 new error codes, those change accept/reject so they need their own review and maybe aren't needed for zd 21876. i'll review 1 first. after your vacation is fine

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants