1
0
mirror of https://github.com/wolfSSL/wolfssl.git synced 2026-10-24 04:09:07 +03:00

447 Commits

Author SHA1 Message Date
Paul Adelsbach
c161875a37 PR feedback: reorder cert gen, reuse rng, compiler gates, update comment 2026-10-22 08:45:37 -07:00
Tobias Frauenschläger
69291355b6 Benchmark SLH-DSA SHA2-128s verify in verify-only builds
The verify-only SLH-DSA benchmark only had a SHAKE-128s vector, because
that is the only verify-only signature in wolfcrypt/test/test.c. Builds
with only the SHA2 parameter sets showed no SLH-DSA row at all.

Add a SHA2-128s vector: a deterministic, empty-context signature of
"Hello World!" by the existing bench_slhdsa_sha2_128s_key.der. It was
generated with OpenSSL 4.0.3; wolfSSL verifies it, rejects it once a
message bit is flipped, and its own deterministic signature from the
same key is byte-identical. gen-slhdsa-mldsa-certs.sh gains the step
that produces it, and a rerun reproduces the committed files. The pq-all
verify-only benchmark config enables sha2 and requires this row too.

bench_slhdsa_verify() now takes the parameter set and vector. The row
uses the full benchmark's name, SLH-DSA-SHA2-S, and takes its strength
from the public key size, as the full benchmark does. Verify-only
builds accept -slhdsa-sha2-128s when SHA2-128s is built.

Tested on the host with --enable-slhdsa=yes,sha2,verify-only on its
own, with --enable-smallstack and with --disable-rng, with sha2 only,
with SHAKE only and 192f only (no SHA2 row, no warnings), and with the
full sha2 build.
2026-10-14 10:59:13 +02:00
Tobias Frauenschläger
cfdf7bb831 Benchmark LMS, XMSS and SLH-DSA verify in verify-only builds
The benchmark measured LMS, XMSS and SLH-DSA verify only after
generating a key and signing with it, so a build with
WOLFSSL_LMS_VERIFY_ONLY, WOLFSSL_XMSS_VERIFY_ONLY or
WOLFSSL_SLHDSA_VERIFY_ONLY compiled the algorithm out of the benchmark
entirely. Targets that only check signatures, such as a boot loader
verifying an image, got no numbers at all.

Verify-only builds now time verify against a fixed vector: LMS
L1_H10_W8, XMSS-SHA2_10_256 and SLH-DSA-SHAKE-128s. The vectors are the
verify-only KATs already in wolfcrypt/test/test.c, stored as binary
files under certs/lms, certs/xmss and certs/slhdsa and emitted into
certs_test.h by gencertbuf.pl. The rows use the same names as the full
benchmark, and a parameter set without a vector, or one that is not
compiled in, is skipped. If loading the key fails, the failing call is
printed and no row is shown.

In a verify-only build, -lms_hss, -xmss_xmssmt(_sha256),
-slhdsa-shake128s and -slhdsa are accepted only when their vector
applies, and the other XMSS and SLH-DSA parameter options are rejected,
so no option silently runs nothing.

Verify needs no RNG, so the asymmetric result printing is built
whenever one of these vectors applies, also with WC_NO_RNG. A
verify-only build with no vector no longer counts as an asymmetric
algorithm. This also fixes an LMS verify-only build with no other
asymmetric algorithm, which failed -Werror on master with an unused
bench_stats_asym_finish(). The SRTP-KDF, scrypt and Argon2 benchmarks
also call it, so it is now built for them too: on master each of them
failed to compile with no asymmetric algorithm, which an LMS
verify-only build used to hide.

The benchmark exits 0 even when verify fails, so a new pq-all CI config
builds all three verify-only with no other asymmetric algorithm and
-Werror=unused-function, runs the benchmark, and fails on any reported
benchmark error, such as a key that does not import, or on a missing
verify row.

Tested on the host with --enable-{lms,xmss,slhdsa}=yes,verify-only on
its own, with --enable-smallstack, with --disable-rng, and with
--enable-all-crypto; with edge configurations where no vector applies
(SLH-DSA 192f only, LMS max height 5, XMSS min hash size 512), both
with --disable-rng and with RNG but no other asymmetric algorithm; with
SRTP-KDF, scrypt or Argon2 as the only such user, with and without an
RNG and with LMS verify-only; and
with --enable-all and the full LMS/XMSS/SLH-DSA build, whose benchmark
output is unchanged. The new CI config passes under
parallel-make-check.py and fails with a corrupted signature byte, a
truncated public key, a missing row and an unused static function.
2026-10-14 10:59:13 +02:00
Yosuke Shimizu
abfddc8061 asn, certman: add selfIssued and verify selfSigned against own key
- DecodedCert gains selfIssued for the issuer/subject name compare,
  and the name-compare readers use it.
- ParseCertRelative() sets selfSigned for every self-issued
  certificate whose own key verifies its signature: it reuses the
  key that passed the issuer check, or calls the new
  ConfirmSelfSignature(), which verifies in its own context with no
  device.
- CheckChainCAExtKeyUsage() exempts a CA with selfSigned set, in
  place of its own issuer key compare.
- IsSelfSignedCert() in tls13.c is renamed IsSelfIssuedCert().
- ProcessPeerCerts() reports X509_V_ERR_INVALID_CA when a chain CA
  is refused with NOT_CA_ERROR.
- Comments that called the name compare self-signed now say
  self-issued.
- Tests cover a key rollover CA without keyCertSign in a TLS chain
  and as a trusted anchor in X509_verify_cert(), and the selfSigned
  and selfIssued flags from wc_ParseCert().

Issue: F-12625
2026-10-13 08:44:40 +09:00
JacobBarthelmeh
20d01e1a8f Merge pull request #11440 from aidankeefe2022/x509-depth-fix
Fix: X509 honor explicit depth 0
2026-10-05 08:58:49 -06:00
philljj
add37e64f3 Merge pull request #11352 from Frauschi/pqc-smime-2-mlkem-asn1-x509
ML-KEM: add ASN.1 key encoding and X.509 certificate support
2026-10-01 11:46:41 -05:00
Aidan Keefe
e0fd0c677f X509_verify_cert: honor explicit depth 0, don't count anchor against depth
Fenrir 12740: X509_STORE_CTX_set_depth(ctx, 0) was treated as unset
and silently got WOLFSSL_X509_STORE_DEFAULT_MAX_DEPTH.
- set_depth() now always marks the depth as explicit
  so 0 means no intermediate CAs are allowed.
- An explicit negative depth rejects every chain with
  CERT_CHAIN_TOO_LONG before any path is built, instead of falling
  back to the default.
- X509_STORE_CTX_init() sets the depth to the default chain max depth.
- A positive ctx->depth written directly to the struct (no setter) is
  still honored. The budget is clamped so INT_MAX cannot overflow it.

A trust anchor from X509_STORE_CTX_trusted_stack() or store->certs is
found by the same lookup as an untrusted intermediate and cost one extra
pass, so explicit depth N only allowed N-1 intermediates there (and
depth 0 rejected a leaf issued directly by the anchor). When the budget
runs out on a caller-trusted anchor that would end the path (self-issued
or PARTIAL_CHAIN), accept it instead of reporting CHAIN_TOO_LONG.

An issuer cycle among the untrusted certs (A <- B <- A) was followed
until the depth budget ran out, ~2^31 signature checks at INT_MAX. The
untrusted-issuer lookup now skips certs already on the path, so the
cycle ends the search with UNABLE_TO_GET_ISSUER_CERT_LOCALLY. Adds
loop-a/loop-b/loop-leaf fixtures to untrusted_anchor/ for the test.

Also fetch the verify callback once in X509_verify_cert() and pass it
to X509StoreVerifyCert(), X509StoreCheckPathLen() and the host/IP
checks, and factor the PARTIAL_CHAIN test into
X509StoreCertIsPartialChain().
2026-09-30 10:48:28 -06:00
Tobias Frauenschläger
837e0b6aec certs: generate the ML-KEM test material with renewcerts.sh
The README described how to regenerate the ML-KEM certificates in prose,
which amounts to "write a program". They are produced by
certs/renewcerts.sh instead, using the same OpenSSL 3.5 or newer binary
its ML-DSA section already requires.

An ML-KEM key cannot sign, so it can sign neither a certificate nor a
certificate request. Each certificate therefore comes from a throwaway
request carrying only the subject name, with x509 -req -force_pubkey
swapping in the ML-KEM public key before the ML-DSA-87 test CA signs it,
and keyUsage marked critical asserting keyEncipherment and nothing else,
per RFC 9935 Section 5.

The keys are written in the priv-only PKCS#8 shape, which is the RFC 9935
Section 6 expandedKey form: that decodes in a build without key
generation, which the seed form does not.

ML-KEM is probed separately from ML-DSA so a binary carrying one provider
and not the other still regenerates what it can.

The MLKEM_TYPE certificate generation paths had no coverage, so
mlkem_certgen_test drives wc_MakeCert_ex, wc_SignCert_ex and
wc_SetSubjectKeyIdFromPublicKey_ex for all three parameter sets. It signs
with ECDSA rather than ML-DSA, which is available in far more builds and
which the paths under test do not care about.
2026-09-29 17:26:23 +02:00
Tobias Frauenschlaeger
77d8c59a81 ML-KEM: support ML-KEM keys in X.509 certificates
Build on the ML-KEM key OIDs and ASN.1 codec so an ML-KEM key can be
carried in a certificate, both when parsing one and when issuing one.

Add MLKEM_KEY and MLKEM_TYPE, accept the ML-KEM key OIDs when decoding a
certificate's SubjectPublicKeyInfo and when validating PKCS#8 algorithm
parameters, and teach the private key to certificate matching how to
pair an ML-KEM key with its certificate. That last path needs the
parameter set before it can initialize a key object, so
wc_MlKemKey_TypeFromOidSum maps a decoded key OID back to a parameter
set.

Thread an ML-KEM key through certificate generation alongside the
existing FrodoKEM key, covering MakeAnyCert, MakeCertReq and the subject
and authority key identifier helpers, so wc_MakeCert_ex accepts
MLKEM_TYPE. An ML-KEM encapsulation key exceeds MAX_PUBLIC_KEY_SZ, so
the key identifier helper sizes its buffer the way it already does for
FrodoKEM.

No change is needed to keep a KEM key away from signature verification.
ConfirmSignature has no case for these OIDs and its default rejects them
with ASN_UNKNOWN_OID_E, which is how FrodoKEM behaves too.

Certificate parsing does not reject an ML-KEM certificate that asserts
keyUsage bits beyond keyEncipherment. Refusing to parse it would make a
non-conforming certificate unreadable even for inspection, and the
requirement binds the issuer rather than the parser. The check belongs
where the key is used for key establishment.

Add ML-KEM test certificates under certs/mlkem. A KEM cannot sign, so
these cannot be self-signed and are issued by the ML-DSA-87 test key in
certs/mldsa instead. They follow the CNSA 2.0 key establishment profile:
keyUsage critical and asserting keyEncipherment alone, an ML-KEM
subjectPublicKeyInfo with the parameters field absent, and an ML-DSA-87
signature. They are generated in tree because ML-KEM certificate
issuance is not available in OpenSSL before 3.5.

Tested by parsing each certificate, confirming its subject public key
decodes only as the expected parameter set, and confirming the matching
private key reproduces the certificate's public key. Verified that a
build with ML-KEM disabled still compiles.
2026-09-29 17:25:51 +02:00
Tobias Frauenschläger
1c9b695cb1 PKCS#7: decode indefinite-length AuthEnvelopedData
wc_PKCS7_DecodeAuthEnvelopedData() failed with ASN_PARSE_E on the output
of openssl cms -encrypt -aes-256-gcm -stream. It read at most one OCTET
STRING out of a constructed encryptedContent, rejected an indefinite
length there, and expected the authenticated attributes or the tag
straight after the content, where BER puts the end-of-contents of the
content and of the EncryptedContentInfo.

The fragments are now joined into one buffer before the AEAD runs,
since GCM and CCM need the whole ciphertext to check the tag, and both
end-of-contents are consumed when present. An empty fragment or an empty
content is rejected with ASN_PARSE_E, as an empty definite-length
content already was, and the join stops with BUFFER_E as soon as the
content outgrows the output buffer.

Fed in chunks it also failed in two places. The indefinite
EncryptedContentInfo gave no length to size the read by, so too little
was buffered to parse the content type and algorithm; the worst case is
now buffered, as the EnvelopedData decoder already does. And the bytes
still expected after the tag were computed as maxLen minus the total
read, which an indefinite message has already passed, so the unsigned
result wrapped and the decoder asked for 4 GB more input. It now waits
only for what is buffered.

test_wc_PKCS7_DecodeAuthEnvelopedData_fragmented re-encodes AES-256-GCM
messages, with and without authenticated attributes, in 16, 7, 1, 33 and
4096-byte fragments, decodes them whole and in 1 and 13-byte chunks, and
checks that a flipped tag bit and an indefinite [0] with no content are
rejected and that too small an output stops the join early. test_wc_PKCS7_DecodeOpenSslStream
gains certs/test-stream-dec-aes256gcm.p7b from renewcerts.sh.
2026-09-25 15:05:14 +02:00
Tobias Frauenschläger
6481bf1f47 PKCS#7: decrypt every fragment of an indefinite-length EnvelopedData
When the encryptedContent of an EnvelopedData is a constructed,
indefinite-length [0] holding several OCTET STRINGs, as
openssl cms -encrypt -stream writes it, wc_PKCS7_DecodeEnvelopedData()
decrypted each fragment into a cache sized for that fragment alone and
handed it to the stream output callback. With no callback set, every
fragment but the last was dropped, only the last was copied to the
start of the output buffer, and the return value still counted the
whole content. The caller got success and a buffer holding one block
of plaintext followed by whatever it held before.

Decrypt each fragment onto the end of the cache instead, so without a
callback the whole plaintext is there for the padding check and the
copy to output that follow the loop. With a callback, each fragment is
still passed on as it is decrypted, and the last after its padding
check; a callback that refuses a fragment ends the decode with
BUFFER_E, as it already did for the last one. Without a callback the
decode stops with BUFFER_E as soon as the plaintext can no longer fit
the output buffer, rather than joining the whole content first. When
the cache grows, the plaintext in the old buffer is wiped before it is
freed.

Four more problems in the same loop:

- Every fragment had to be a whole number of cipher blocks, which BER
  does not require. Ciphertext short of a block now waits in a carry
  buffer for the next fragment, and a content that does not end on a
  block boundary is rejected with BUFFER_E.
- Streaming, wc_PKCS7_StreamEndCase() shifts the buffered input down
  without moving the index, and the loop only re-read its position when
  a stale length said it had run out. After a fragment smaller than the
  data buffered, the next header was parsed from the wrong offset. The
  position is now re-read at the top of every iteration.
- Whether a fragment is the last is decided by the end-of-contents that
  follows it, so the refill check now also asks for those two bytes.
  Without that, a chunk ending exactly after the last fragment decrypted
  it as a middle one and then failed on the end-of-contents.
- A NO_PKCS7_STREAM build never set up the cipher for fragmented content
  at all, so it could not decrypt any of it.

A decryption callback cannot decrypt fragmented content: it was given
the content IV for every fragment, and the wc_PKCS7_DecryptContentEx()
call that followed it ran with no key set, so that path never produced
plaintext and handed the cipher a NULL Aes, which not every port checks
for. Fragmented content with a decryptionCb set is now rejected with
BAD_FUNC_ARG before any cipher is touched.

The new state lives in PKCS7State, which is private to pkcs7.c; the
public wc_PKCS7 structure does not change.

test_wc_PKCS7_DecodeEnvelopedData_fragmented re-encodes a message the
way OpenSSL streams it, with 16, 7, 1, 33 and 4096-byte fragments, and
decodes it whole, fed in 1 and 13-byte chunks with and without a stream
callback, and split right after the last fragment. It also checks that
a callback refusing its second fragment stops the decode there, that
too small an output stops it before the whole message is read, that
stale bytes in the output buffer do not survive, that an output buffer
one byte short gets BUFFER_E, and that a content short of a block is
rejected. test_wc_PKCS7_DecodeOpenSslStream decodes
certs/test-stream-dec-aes256.p7b, made by openssl cms -stream in
renewcerts.sh. Without ASN_BER_TO_DER the fragmented messages are still
rejected with BER_INDEF_E, which the test checks.
2026-09-25 15:05:14 +02:00
Tobias Frauenschläger
faec5fd3d3 Check the CRL when a peer certificate names no OCSP responder
A client with both OCSP and CRL enabled skipped the CRL check for a
certificate carrying no OCSP responder URL. OcspNoUrlPolicy() maps
OCSP_NO_URL onto 0 for the historical soft-fail default, which is
indistinguishable from a responder answering CERT_GOOD, so the CRL fallback
never ran.

Decide whether the CRL check is still needed from the raw OCSP result, before
the no-URL policy collapses it, for both the leaf and the chain.

A chain certificate whose OCSP lookup was handed to a staple is the one case
where that result is not in yet, since the CertificateStatus entry answering
it has not been parsed. Take the CRL check there anyway, while the
DecodedCert is alive, but keep only CRL_CERT_REVOKED from it: an inconclusive
CRL_MISSING would otherwise reject a certificate whose valid staple is still
on its way, which no CRL ever said anything about.

The tests are gated out under WOLFSSL_CRL_ALLOW_MISSING_CDP, where a
certificate naming no distribution point has its CRL check skipped by design
and the in-tree test certificates name none.

test_ocsp_checkall_staple_crl_missing covers both directions with a fully
stapled chain under OCSP and CRL CHECKALL: an empty CRL store no longer
rejects it, and certs/ocsp/root-ca-crl-revoked.pem, which gencrls.sh now
produces beside the other OCSP CRLs, still does.
2026-09-19 16:36:24 +02:00
Eric Blankenhorn
7d0e520eef Enforce RFC 8446 SHA-1 certificate rule on TLS 1.3 chains 2026-08-17 09:58:02 -05:00
Kareem
2cad68a931 Fix test failure for WOLFSSL_OLD_OID_SUM 2026-08-10 16:46:36 -07:00
Kareem
754b220617 Remove carry over of old buffer in wolfSSL_X509_set_ext.
Thanks to Kushal Khemka & Mayank Jangid (OpenSec Intelligence) for the report.
2026-08-10 16:46:36 -07:00
Tobias Frauenschläger
ac75f181cd Add SLH-DSA support for the TLS 1.3 and DTLS 1.3 handshake
Implement SLH-DSA (SPHINCS+, FIPS 205) as an entity authentication
algorithm for the TLS 1.3 and DTLS 1.3 handshake, following
draft-reddy-tls-slhdsa. All twelve parameter sets (SHAKE and SHA2 families,
128/192/256 in the f and s variants) are wired into the handshake for
signing and verifying the CertificateVerify message; test certificates and
configs cover the 128f and 128s sets.

Handshake integration:
- Map the SLH-DSA signature schemes to and from the wire in the
  signature_algorithms extension and CertificateVerify. The mapping,
  advertisement, and OID handling are gated per parameter set so a build
  only offers, accepts, and maps the variants actually compiled in
  (including partial SHA2 builds).
- Sign and verify the CertificateVerify with an SLH-DSA entity key, and
  load SLH-DSA private keys and certificates (ssl_load.c, ssl.c,
  ssl_api_pk.c, asn.c).
- Preserve the verify return code on a failed SLH-DSA CertificateVerify
  rather than flattening every non-zero result to SIG_VERIFY_E.
  wc_SlhDsaKey_Verify already returns SIG_VERIFY_E on a real mismatch, so
  the failure semantics are unchanged while WC_PENDING_E (async crypto
  callbacks) and hard errors now propagate, matching ML-DSA and Falcon.

Protocol version gating:
- SLH-DSA is defined for TLS 1.3 only, so the schemes are no longer offered
  to a TLS 1.2 peer, and MatchSigAlgo and PickHashSigAlgo pin an SLH-DSA
  certificate both to the scheme for its exact parameter set and to
  TLS 1.3.
- Reject a Falcon, ML-DSA or SLH-DSA key in the TLS 1.2 CertificateVerify
  with SIG_TYPE_E. No signature scheme below TLS 1.3 covers a post-quantum
  key, the record is reserved for a classic signature, and the signing
  switches have no post-quantum case, so continuing would have sent the
  reserved buffer's uninitialized tail.

Streamed CertificateVerify send:
- SLH-DSA signatures are large (up to ~50 KB). When the CertificateVerify
  body exceeds a single record, generate the signature into a
  connection-level buffer and emit it one record at a time so the output
  buffer never has to hold the whole signature. This keeps peak memory near
  one signature plus a single fragment and resumes correctly across a
  non-blocking WANT_WRITE without recomputing the randomized signature.
  Gated by WOLFSSL_TLS13_STREAM_CERT_VERIFY (TLS 1.3, non-async, PQC
  signatures); DTLS and WOLFSSL_ASYNC_CRYPT keep the existing in-place
  fragmented path.
- Drop a half-sent streamed CertificateVerify in wolfSSL_clear. Left in
  place, the resume guard would fire on the next handshake and re-send the
  previous one's signature into a different transcript.
- Dual-algorithm (WOLFSSL_DUAL_ALG_CERTS, BOTH) CertificateVerify bodies are
  streamed as well. The combined two-signature body may include a
  variable-length signature, so the body buffer is sized from the
  per-signature upper bounds and the exact length is recorded after signing;
  the small trailing slack is never sent.

Buffer sizing:
- Keep MAX_X509_SIZE a fixed 9 KB for post-quantum builds. It sizes a
  static per-certificate slot embedded by value in every cached session, so
  it must not scale with a post-quantum signature; nor may it derive from
  the enabled ML-DSA level, or a level-restricted build would silently drop
  certificates that a full build keeps.
- Add MAX_CERT_WIRE_SZ for the largest certificate that may appear in a
  handshake message, sized from the enabled post-quantum signatures, and
  derive MAX_CERTIFICATE_SZ from it instead of from MAX_X509_SIZE.
- Add MAX_CERT_MSG_DEPTH for the chain depth assumed when sizing the
  certificate message. MAX_CHAIN_DEPTH bounds how deep a chain may be
  verified, while this sizes a buffer an unauthenticated peer can make us
  allocate, so it is trimmed to 5 when a post-quantum certificate has
  inflated the per-certificate size. Classic builds are unchanged.
- Size the CertificateVerify buffers from the actual signature length
  instead of the worst-case WC_MAX_CERT_VERIFY_SZ, which balloons with
  SLH-DSA. WC_MAX_CERT_VERIFY_SZ is retained for API compatibility and its
  growth is documented in README.md.
- Order Scv13Args widest member first so it carries no interior padding and
  still fits ssl->async->args under WOLFSSL_ASYNC_CRYPT together with
  WOLFSSL_DUAL_ALG_CERTS.

Dual-algorithm certificates:
- Reserve the two signature length prefixes in the in-place
  CertificateVerify sizing that the streamed path already accounted for.
- Build the PreTBS for an alternative signature check from the certificate
  size minus both signatures, and retry once at a size the canonical
  re-encode cannot exceed when that estimate turns out short. The estimate
  keeps the allocation small on constrained targets, and wc_GeneratePreTBS
  reports an encoder failure as WOLFSSL_FAILURE, which is zero, so a
  non-positive result is now an error instead of silently skipping
  ConfirmSignature and reading as a verified signature.

Device held private keys:
- Support an SLH-DSA private key that lives in a device and is referenced
  by id or label. The parameter set cannot be recovered from a device side
  identifier, so it is carried from the key type down to
  wc_SlhDsaKey_Init_id and wc_SlhDsaKey_Init_label, and the key is released
  with wc_SlhDsaKey_Free once the certificate and key pair is checked.

Robustness:
- Check the SlhDsaParamToType, wc_SlhDsaKey_PublicSizeFromParam and
  wc_SlhDsaParamToOid results in the certificate and key load paths.
- Zeroize an SLH-DSA key before wc_SlhDsaKey_Init, which can return
  NOT_COMPILED_IN before it clears the object, in both the certificate load
  path and AllocKey.
- Take the alternative key's parameter set from the certificate's sapkiOID
  rather than keyOID, which describes the native key.
- Re-initialise across hash families in wc_SlhDsaKey_PublicKeyDecode as
  wc_SlhDsaKey_PrivateKeyDecode already does. The hash objects share a union
  selected by family, so importing across families writes the new family's
  state over the old one's and orphans it.
- Copy pkCurveOID in SetSSL_CTX when only SLH-DSA is enabled, matching the
  struct member guard. Without it the field stayed zero and the signature
  scheme matching above was dead in exactly that build.
- Derive the per parameter set WOLFSSL_SLHDSA_PARAM_NO_* macros from the
  group level exclusions, and select WC_SLHDSA_DEFAULT_PARAM with those
  same macros, so the parameter table and the TLS mappings cannot disagree.
- Add SLH-DSA to the lean build WOLFSSL_MAX_SIGALGO carve-out, since twelve
  more entries no longer fit the small list.
- Prefix the new SLHDSA_ALL_NO_* macros in the installed header with WC_.

Tests and certificates:
- Add SLH-DSA entity (client and server) certificates for the SHAKE and
  SHA2 128f and 128s parameter sets, and update the generation script.
- Add TLS 1.3 and DTLS 1.3 entity-cert CertificateVerify test configs
  covering the fragmented (128f) and single-record (128s) send paths for
  both hash families, wired into suites.c. These sign with the entity key,
  so they are excluded from verify-only builds.
- Interrupt the streamed CertificateVerify with one WANT_WRITE and with
  several on the same record, and assert the handshake still completes and
  re-emits identical bytes, which the blocking .conf handshakes never
  exercise. The record to interrupt is counted first, because the server's
  record batching differs between builds. Where the flight is flushed as a
  single write the send is retried below SendTls13CertificateVerify, so
  these do not by themselves cover the fragOffset resume path.
- Drive the streamed path with an ML-DSA leaf under a negotiated
  max_fragment_length, covering it for a non SLH-DSA algorithm.
- Reject a TLS 1.2 handshake that presents an SLH-DSA client certificate.
- Map every compiled-in scheme from its wire code point to the key OID, and
  extend the exhaustive SaToNid coverage with the twelve new algorithms.
- Accept an SLH-DSA private key referenced by id and by label.

Build configuration:
- configure.ac: --enable-slhdsa now keeps the certificate/ASN code enabled
  (as --enable-mldsa does), so an SLH-DSA-only build with RSA, ECC and DH
  disabled configures instead of erroring that ASN is off.
- Guard the WOLFSSL, WOLFSSL_CTX and WOLFSSL_X509 pkCurveOID members for
  WOLFSSL_HAVE_SLHDSA, so an SLH-DSA-only build declares the field the
  handshake and CopyDecodedToX509 already reference under an SLH-DSA guard.
- Mark checkKeySz used in the SLH-DSA branch of ProcessBufferCertPublicKey;
  SLH-DSA is the only certificate signature algorithm with no minimum-size
  check, so an SLH-DSA-only build otherwise tripped -Wunused-parameter.
- Propagate haveSlhDsaSig in wolfSSL_set_SSL_CTX, which copied the Falcon
  and ML-DSA flags but not the SLH-DSA one.
- CI: add a SHA2-only SLH-DSA build (--enable-slhdsa=sha2) so the
  SHAKE-disabled combined-maxima guards are exercised, and an async crypto
  build with dual-algorithm certificates, which is the only configuration
  that compiles the in-place fragmented CertificateVerify send.
2026-08-04 22:23:03 +02:00
Sean Parkinson
ca53afe35d Merge pull request #10785 from stenslae/ml-dsa-ssl-error-queue-fix
openssl compat errors and mldsa oid fix
2026-07-21 10:08:11 +10:00
Sean Parkinson
78df69d051 FrodoKEM: ASN.1 keys, X.509 certicates
Added support for encoding and decoding keys in ASN.1.
Added support for X.509 certificates and CSRs.
Generated certificates and CSRs. Not fo FrodoKEM-640 as is not in the specs.
2026-07-17 22:38:55 +10:00
Emma Stensland
5799ac6126 openssl compat errors and mldsa oid fix 2026-07-13 11:56:57 -06:00
Tobias Frauenschläger
bc3288204e PKCS#7: add ML-DSA (FIPS 204) SignedData support
Add ML-DSA signing and verification for CMS/PKCS#7 SignedData, following
RFC 9882. ML-DSA is used in CMS "pure" mode: the signature is computed
over the complete message (the DER SET OF signed attributes, or the
eContent when none are present) with an empty context string and absent
signatureAlgorithm parameters, rather than over a pre-computed DigestInfo
as with RSA/ECDSA.

wolfcrypt/src/pkcs7.c:
- New ML-DSA helpers: wc_PKCS7_MlDsaLevelFromOID, wc_PKCS7_BuildPureSigMessage,
  wc_PKCS7_MlDsaSign and wc_PKCS7_MlDsaVerify, wired into the per-algorithm
  switch sites (GetSignSize, SignedDataGetEncAlgoId, SetPublicKeyOID,
  CheckPublicKeyDer) and the sign/verify dispatchers. Only the final FIPS 204
  ML-DSA OIDs are accepted; pre-standard draft Dilithium OIDs are not.
- GetSignSize derives the ML-DSA signature length from the parameter set.
- InitWithCert copies the signer public key into the RSA-sized publicKey buffer
  only for RSA/ECC certs (the raw-sign callback consumers); large PQC keys such
  as ML-DSA would overflow it and are never read back, so publicKeySz stays 0.
- wc_MlDsaKey is always heap allocated (it embeds multi-KB key buffers); the
  accompanying DecodedCert uses the WC_DECLARE_VAR/WC_ALLOC_VAR_EX macros for
  stack-vs-heap handling under WOLFSSL_SMALL_STACK.
- wc_PKCS7_SignedDataBuildSignature skips building the DigestInfo for ML-DSA,
  which signs the full message in pure mode and never consumes it.
- wc_PKCS7_MlDsaSign wraps the ML-DSA private-key decode in
  PRIVATE_KEY_UNLOCK/PRIVATE_KEY_LOCK. Unlike RSA/ECC, the FIPS module gates
  wc_MlDsaKey_PrivateKeyDecode behind the private-key read lock, so signing
  would otherwise fail with FIPS_PRIVATE_KEY_LOCKED_E under --enable-fips. The
  macros are no-ops in non-FIPS builds.

wolfssl/wolfcrypt/pkcs7.h:
- Document that the fixed-size signer public key buffer (publicKey/publicKeySz)
  holds only RSA/ECC keys; it stays RSA-sized.

wolfcrypt/src/hash.c:
- Map the SHAKE128/SHAKE256 OIDs to their hash types in wc_OidGetHash().

certs/mldsa:
- Add expanded-only PKCS#8 DER private keys (mldsa44/65/87-key.der) matching
  the self-signed ML-DSA certificates, with README and include.am updates.
  The expanded-only shape (no seed) decodes via wc_MlDsaKey_ImportPrivRaw
  without keygen-from-seed or the ASN template, so pkcs7signed_mldsa_test also
  passes in WOLFSSL_MLDSA_NO_MAKE_KEY and non-WOLFSSL_ASN_TEMPLATE builds.

certs/renewcerts.sh:
- Generate the mldsa<N>-key.der files from the matching mldsa<N>-key.pem in the
  expanded-only shape (openssl pkey -provparam ml-dsa.output_formats=priv), so
  a regeneration keeps the DER key in step with the cert. The OpenSSL detection
  probe now requires both ML-DSA keygen and that conversion across all three
  levels, so the block runs fully (matched cert+key) or is skipped entirely
  rather than aborting mid-way.

wolfcrypt/test/test.c:
- Add pkcs7signed_mldsa_test(): round-trip encode/verify of SignedData across
  ML-DSA-44/65/87, with and without signed attributes, including a check that
  the digest algorithm parameters are encoded as expected. The message-digest
  OID is selected from the enabled hash set (SHA-512, else SHA-256, else SHA-1)
  so the test builds when SHA-512 is disabled. A negative case confirms ML-DSA
  rejects a caller-supplied pre-computed content hash with BAD_FUNC_ARG.
2026-07-09 18:09:03 +02:00
Sean Parkinson
ae023a5643 Time-Stamp Protocol (RFC 3161)
Implementation in wolfCrypt
OpenSSL compatibility layer in wolfSSL
Added tests, certificates, examples.
2026-07-08 09:33:47 +10:00
Yosuke Shimizu
b87edc2040 certs: re-sign orphaned rsapss/mldsa leaves and add chain guard 2026-07-02 10:34:06 +09:00
Daniel Pouzzner
fd3b489ea5 Merge pull request #10787 from stenslae/update-wolfssl-email
Updated email to facts@wolfssl.com
2026-07-01 17:52:20 -05:00
Daniel Pouzzner
7afcc3eef6 Merge pull request #10687 from rlm2002/zd-NameConstraints
Name Constraints cert chain walk
2026-07-01 17:24:52 -05:00
aidan garske
fef29abf0e certs: regenerate ecc-leaf-mldsa44 and ecc-leaf-rsapss from renew scripts 2026-06-29 13:46:14 -07:00
Emma Stensland
92e76d4667 updated email to facts@wolfssl.com 2026-06-26 14:44:16 -06:00
aidan garske
93d16f431c Address tinytls13 review: ML-DSA-44, canonical ML-DSA/ML-KEM macros, optimizer-measured static-mem bucket guidance, footprint hygiene 2026-06-24 15:43:01 -07:00
Aidan Garske
41fad5f307 Fix and expand tinytls13 footprint profile across CI configs
Make every --enable-tinytls13 spelling build and pass locally, and grow the
CI matrix to cover them. These are fixes found while testing the configs the
CI workflow had not actually exercised.

- internal.h, internal.c, ssl_load.c: include ML-DSA and Falcon in the
  pkCurveOID member and producer guards so the PSK plus ML-DSA build compiles.
- tls13.c: gate the DoTls13CertificateVerify definition on NO_CERTS to match
  its call site.
- settings.h: let the AES-256 adder survive the floor, default the
  user_settings path to the SHA-256 floor, make WOLFSSL_NO_MALLOC opt-in so
  the test suite still runs, and keep ML-DSA ASN.1 for the cert profile.
- configure.ac: drive ENABLED_ASM and emit WOLFSSL_NO_ASM for the small C
  floor, restrict SP math to P-256, strip ML-DSA ASN.1 only on the PSK floor,
  and print a notice for the reduced security cert verify.
- examples: guard the cert loading paths for NO_CERTS and treat NO_CERTS as
  PSK mode in echoserver and echoclient.
- Add examples/configs/tinytls13_smoke.c, an in memory TLS 1.3 handshake test
  that drives PSK, ECDSA, ML-DSA-65 and RSA-PSS chain verify, plus forced
  cipher suites, for builds with no example or unit test harness.
- certs: add ECDSA leaves signed by the ML-DSA-65 and RSA-PSS CAs so the cert
  profiles drive a real PQC and PSS chain verify in CI.
- .github/workflows/tinytls13.yml: cover every profile and adder, run the
  smoke handshake on the build verified configs, and least privilege the
  workflow token.
2026-06-22 12:08:58 -07:00
Ruby Martin
0052ec44dd add regression tests for name-constraint ancestor walk
- test_wolfSSL_CertManagerNameConstraint_valid_chain
  - test_wolfSSL_CertManagerNameConstraint_skid_disambiguates
  - Cert/key fixtures under certs/test/nc-ancestor/
  - gen-nc-ancestor.sh to regenerate from committed keys
2026-06-18 14:52:08 -06:00
Tobias Frauenschläger
3e2c46001e x509: require a trusted anchor in wolfSSL_X509_verify_cert()
Ensure caller-supplied intermediate certificates cannot terminate the
chain during compatibility-layer verification; a path must reach a
configured trust anchor. Add a regression test and supporting certs.
2026-06-12 17:29:31 +02:00
Tobias Frauenschläger
e05a453944 Support RFC 9802 LMS and XMSS in X.509 certificate and CSR generation
Extend wc_MakeCert_ex/wc_SignCert_ex/wc_MakeCertReq_ex to issue HSS/LMS and
XMSS/XMSS^MT certificates and PKCS#10 requests, building on the existing
RFC 9802 verification support. New LMS_TYPE/XMSS_TYPE/XMSSMT_TYPE selectors,
wc_{Lms,Xmss}Key_PublicKeyToDer SPKI encoders, runtime signature-buffer
sizing, and sigType/key consistency checks. Generation is ASN.1-template
only, matching where the verification path lives.

Tests generate self-signed roots, CSRs and a CA->ECC-leaf chain in-process
and verify them, replacing the patched Bouncy Castle fixtures (only the stock
RFC 9802-aligned LMS interop anchor is kept).
2026-06-10 10:51:33 +02:00
Ruby Martin
5c3100ed5c Remove non-RFC-compliant OCSP responder chain walk. The chain walk
authorized any responder issued by an ancestor of the target's issuer;
  RFC 6960 4.2.2.2 requires direct issuance by the CA identified in the
  request.

    - Remove CheckOcspResponderChain() and WOLFSSL_NO_OCSP_ISSUER_CHAIN_CHECK.
    - Drop now-unused vp parameter from CheckOcspResponder() and the
      OcspRespCheck() helper; cascade through template and non-template
      paths.

  OCSP test blobs:

    - Re-sign resp_server1_cert with intermediate1-ca (CA-direct path).
    - Add resp_server1_cert_ancestor_responder for the negative test.
    - Embed server1_cert_pem[] in test_ocsp_test_blobs.h so the new test
      runs under NO_FILESYSTEM; matching entry added to
      create_ocsp_test_blobs.py.
    - Regenerate response[] in test_certman.c with intermediate1-ca as
      signer; recipe switched from Wireshark export to openssl -respout
      + xxd -i for reproducibility.
    - Fix self-XOR in test_wolfSSL_CertManagerCheckOCSPResponse so the
      serial byte actually flips (^= 0xFF).

  Live OCSP coverage:

    - Add ocsp-responder-int1 (delegated responder issued directly by
      intermediate1-ca, with id-kp-OCSPSigning EKU) for the
      responder->intermediate->root chain.
    - scripts/ocsp-stapling.test: intermediate1 responder switched to
      ocsp-responder-int1 (delegated path).
    - scripts/ocsp-stapling2.test, scripts/ocsp-stapling_tls13multi.test:
      intermediate2 and intermediate3 sign their OCSP responses with
      their own CA keys (CA-direct path); root block unchanged
      (ocsp-responder-cert is still RFC-compliant for root-issued certs).
    - .github/workflows/ocsp.yml: server1 OCSP responder switched to
      ocsp-responder-int1 to match the cert chain.
    - New test_ocsp_ancestor_responder_rejected confirms the
      ancestor-issued response is rejected with OCSP_LOOKUP_FAIL.
2026-06-02 16:20:37 -06:00
Tobias Frauenschläger
637c07798a Finalize ML-DSA renaming 2026-05-26 14:54:30 +02:00
David Garske
497ed9843e Merge pull request #10303 from julek-wolfssl/zd/21675
ocsp: bind responder authorization to CertID issuerKeyHash
2026-05-13 10:33:17 -07:00
David Garske
3e6efbac52 Merge pull request #9567 from jackctj117/serial-0
Allow serial number 0 for root CA certificates
2026-05-12 09:19:56 -07:00
Juliusz Sosinowicz
49906319f6 ocsp: address review feedback on PR 10303
- Use KEYID_SIZE for Signer key-hash comparisons since
  Signer.{subject,issuer}KeyHash is sized KEYID_SIZE, not OCSP_DIGEST_SIZE.
- Rename subjectHash/issuerHash to subjectNameHash/issuerNameHash in
  CheckOcspResponder/CheckOcspResponderChain to make the name-vs-key
  hash distinction explicit.
- Expand the Signer.issuerKeyHash field comment to clarify it is the
  subject key hash of the immediate issuer CA.
- Add an imposter-root-ca cert (same DN as root-ca, different RSA key)
  for tests that need to exercise the new CertID issuerKeyHash binding.
2026-05-12 14:36:00 +02:00
David Garske
3351eb429a Merge pull request #10354 from embhorn/zd21725
Fix IPSAN and registeredID handling
2026-05-08 12:15:37 -07:00
Sean Parkinson
69a378461a Merge pull request #10406 from Frauschi/lms_xmss_certs
Support RFC 9802 LMS and XMSS in X.509 verification
2026-05-08 11:48:34 +10:00
Tobias Frauenschläger
28468b44f5 Support RFC 9802 LMS and XMSS in X.509 verification
Wire the stateful hash-based signature schemes HSS/LMS (RFC 8554) and
XMSS / XMSS^MT (RFC 8391) into the X.509 cert-verification path per
RFC 9802.

asn:
- Register id-alg-hss-lms-hashsig (1.2.840.113549.1.9.16.3.17),
  id-alg-xmss-hashsig (1.3.6.1.5.5.7.6.34) and id-alg-xmssmt-hashsig
  (1.3.6.1.5.5.7.6.35) in oid_sum.h, asn.c and asn1_oid_sum.pl.
- Plumb the new keyOIDs through GetCertKey, SigOidMatchesKeyOid,
  HashForSignature, FreeSignatureCtx and ConfirmSignature so leaf
  and CA certificates parse, load and verify end-to-end.
- Rename IsSigAlgoECC -> IsSigAlgoNoParams; the function has tested
  "AlgorithmIdentifier omits NULL parameters" since PQC algos were
  added, and HSS/LMS + XMSS only made the original name more
  misleading.

wc_lms / wc_xmss:
- Add wc_XmssKey_ImportPubRaw_ex which derives parameters from the
  4-byte OID prefix at the start of the raw public key, taking an
  is_xmssmt hint to disambiguate the overlapping XMSS / XMSS^MT OID
  spaces.
- Extend wc_LmsKey_ImportPubRaw with the same auto-derive from
  u32str(L) || lmsType || lmOtsType when key->params is NULL; this
  also fixes a latent NULL-deref when the legacy precondition was
  violated.
- Reject WC_*_STATE_OK in both ImportPubRaw paths so re-importing
  on a private-key-loaded handle can't desync priv/pub.
- Tighten wc_XmssKey_Verify's length check to strict equality,
  matching wc_LmsKey_Verify and the documented contract of using
  wc_XmssKey_GetSigLen for the buffer size.

tests / fixtures:
- Bouncy Castle 1.81 fixtures in certs/lms and certs/xmss covering
  every supported parameter set, plus CA->leaf chains per family
  and one BC-native LMS fixture as a cross-impl interop gate.
- New api tests verify each fixture end-to-end, tamper TBS and
  signature bytes, exercise the wolfCrypt-level negative paths
  (NOT_COMPILED_IN, BUFFER_E, BAD_FUNC_ARG, BAD_STATE_E, OID/family
  mismatch, partial-write invariants, lenient VERIFYONLY re-import,
  strict sigLen check) and confirm the outer signatureAlgorithm
  OID is rejected when it disagrees with the SPKI in both
  XMSS<->XMSS^MT directions.
2026-05-07 17:14:31 +02:00
Eric Blankenhorn
c55b77b382 Fix handling of registeredID 2026-05-07 07:33:56 -05:00
David Garske
490c1062e4 Merge pull request #10274 from gasbytes/crl-idp-extension-fix-follow-up
Reject CRLs with unrecognized critical entry extensions per RFC 5280 section 5.3
2026-05-06 12:13:28 -07:00
David Garske
44564dd5fd Merge pull request #10368 from holtrop-wolfssl/gh10359
Allow SubjectInfoAccess extension without id-ad-caRepository entry
2026-05-05 12:49:19 -07:00
jackctj117
5da6625a10 tests/api.c, certs/test-serial0/include.am: drop em dash and trailing blank line 2026-05-05 13:46:05 -06:00
jackctj117
2cd42c9e16 Skoll review 2026-05-05 13:46:05 -06:00
jackctj117
ee744b1f0b Allow serial number 0 for root CA certificates 2026-05-05 13:44:47 -06:00
David Garske
00abce3474 Merge pull request #10310 from cconlon/d2iMLDSA
Add ML-DSA SPKI/PKCS#8 DER support to d2i_PUBKEY and d2i_PrivateKey
2026-05-05 12:11:49 -07:00
Chris Conlon
fe4473fbea Add ML-DSA SPKI/PKCS#8 DER support to d2i_PUBKEY and d2i_PrivateKey. 2026-05-04 09:24:02 -06:00
Takashi Kojo
048a2e66e1 check candidate runnability before probing ML-DSA support 2026-05-02 08:15:22 +09:00
Takashi Kojo
1a6dee2bb3 Add ML-DSA to X509_get_pubkey and EVP_PKEY_base_id 2026-05-02 08:13:08 +09:00
Josh Holtrop
3ca70e585a Allow SubjectInfoAccess extension without id-ad-caRepository entry
Previously parsing a SubjectInfoAccess certificate containing a
SubjectInfoAccess extension that did not contain an id-ad-caRepository
resulted in an error.
2026-05-01 07:57:04 -04:00