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.
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.
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.
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.
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.
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).
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.
Previously parsing a SubjectInfoAccess certificate containing a
SubjectInfoAccess extension that did not contain an id-ad-caRepository
resulted in an error.
Replace the liboqs-based pre-standardization SPHINCS+ implementation
with the native FIPS 205 SLH-DSA implementation across the
certificate / ASN.1 / X.509 layers, and add SLH-DSA-rooted test
certificates plus TLS 1.3 .conf scenarios that exercise the new
verification path. All liboqs SPHINCS+ code is removed.
This enables SLH-DSA for certificate chain authentication: CA
certificates signed with SLH-DSA, certificate signature verification
against an SLH-DSA root. TLS 1.3 entity authentication via
CertificateVerify with SLH-DSA will be added in a follow-up PR.
Follows RFC 9909 (X.509 Algorithm Identifiers for SLH-DSA) and
NIST FIPS 205. Supports both SHAKE and SHA-2 parameter families
across all twelve standardized variants.
DER codec:
- New PrivateKeyDecode, PublicKeyDecode, KeyToDer, PrivateKeyToDer,
PublicKeyToDer with RFC 9909 encoding (bare OCTET STRING containing
4*n raw bytes = SK.seed || SK.prf || PK.seed || PK.root, no nested
wrapper). OID auto-detection across all twelve SHAKE / SHA-2 variants.
- PublicKeyDecode raw-bytes fast path mirrors wc_Falcon_PublicKeyDecode
and wc_Dilithium_PublicKeyDecode so callers (notably
wolfssl_x509_make_der and ConfirmSignature, which pass the raw
BIT STRING contents stashed by StoreKey) decode correctly. Honours
the caller's *inOutIdx start offset.
- Error paths in Private/PublicKeyDecode preserve params/flags/
inOutIdx and only ForceZero the buffer half each helper actually
writes; skip the wipe entirely on BAD_LENGTH_E (no bytes touched).
- ImportPublic uses |= on flags so a Private-then-Public import
sequence retains FLAG_PRIVATE.
OID dispatch:
- 12 standardized NIST OIDs (6 SHAKE + 6 SHA-2) per RFC 9909. The
pre-standardization OID-collision mechanism is removed since NIST
OIDs do not collide.
- wc_SlhDsaOidToParam / wc_SlhDsaOidToCertType return NOT_COMPILED_IN
(rather than -1) for recognised SLH-DSA OIDs whose parameter set
isn't built; wc_IsSlhDsaOid recognises both. The x509 dispatch
surfaces this as a precise diagnostic instead of the generic
"No public key found".
- wc_GetKeyOID picks a placeholder parameter from whatever variant is
compiled in and #errors at compile time if none is.
- asn_orig.c EncodeCert / EncodeCertReq accept SHA-2 SLH-DSA keyTypes
alongside SHAKE.
Tests and fixtures:
- Test cert chain in certs/slhdsa/: SLH-DSA-SHAKE-128s and
SLH-DSA-SHA2-128s self-signed roots that sign reused ML-DSA-44
entity keys (server + client), plus the gen script
(gen-slhdsa-mldsa-certs.sh, OpenSSL >= 3.5).
- New TLS 1.3 .conf scenarios under tests/suites.c dispatch:
test-tls13-slhdsa-shake.conf, test-tls13-slhdsa-sha2.conf, and a
wrong-CA negative test test-tls13-slhdsa-fail.conf.
- DER round-trip and on-disk decode tests; bench_slhdsa_*_key.der
fixtures regenerated with wolfSSL's own encoder so the codec is
pinned to RFC 9909.
- New unit test test_wc_slhdsa_x509_i2d_roundtrip exercises the raw
PublicKeyDecode entry point that wolfssl_x509_make_der relies on.
- test_wc_slhdsa_check_key now tests both Public-then-Private and
Private-then-Public import orderings.
Build / ABI:
- DYNAMIC_TYPE_SPHINCS = 98 kept as RESERVED with a tombstone comment
for ABI stability; new code should use DYNAMIC_TYPE_SLHDSA (107).
- All build system / IDE project files updated; SPHINCS+ sources,
headers, and test data removed.
- Dead bench_slhdsa_*_key arrays removed from gencertbuf.pl and
certs_test.h; the .der files on disk drive the decode tests.
Moved E[CD][25519||448] APIs to pk.c
Move public key PEM APIs to pk.c.
Move wolfSSL loading and using of private keys and certificates to
ssl_load.c
Move PKCS#7 and PKCS#12 APIs to ssl_p7p12.c.
Move session and session cache APIs to ssl_sess.c.
Other minor fixes.
Add support for:
- SM2 elliptic curve and SM2 sign/verify
- SM3 digest
- SM4 cipher with modes ECB/CBC/CTR/GCM/CCM
Add APIs for SM3 and SM4.
Add SM2 sign and verify APIs.
Add support for SM3 in wc_Hash and wc_Hmac API.
Add support for SM3 and SM4 through EVP layer.
Add support for SM2-SM3 certificates. Support key ID and name hash being
with SHA-1/256 or SM3.
Add support for TLS 1.3 cipher suites: TLS-SM4-GCM-SM3, TLS-SM4-CCM-SM3
Add support for TLS 1.2 SM cipher suite: ECDHE-ECDSA-SM4-CBC-SM3
Add support for SM3 in wc_PRF_TLS.
Add SM2-SM3 certificates and keys. Generated with GmSSL-3.0.0 and
OpenSSL.
PEM example converts PEM to DER and DER to PEM.
Supports encrypting DER and writing out as PEM.
Added better support for 40-bit RC2-CBC PBE.
Added AES-128-CBC encryption support with PKCS#5v2.
Fixed handling of iterations to support writing 24-bit values.
Declared enum types for pass to PKCS#8 encryption APIs.
Add more DER and PEM files to certs directory.
Add testing of 'pem' with pem.test script.
Also:
* added HAVE_FALCON guards as needed.
* corrected minor falcon bugs as I found them.
* handling OID sum collision between DILITHIUM_LEVEL5 and DILITHIUM_AES_LEVEL3
Tested with the following commands:
examples/server/server -v 4 -l TLS_AES_256_GCM_SHA384 \
-c ~/tmp/dilithium_aes_level5_entity_cert.pem \
-k ~/tmp/dilithium_aes_level5_entity_key.pem \
-A ~/tmp/dilithium_aes_level5_root_cert.pem --pqc P521_KYBER_LEVEL5
examples/client/client -v 4 -l TLS_AES_256_GCM_SHA384 \
-c ~/tmp/dilithium_aes_level5_entity_cert.pem \
-k ~/tmp/dilithium_aes_level5_entity_key.pem \
-A ~/tmp/dilithium_aes_level5_root_cert.pem --pqc P521_KYBER_LEVEL5
with permutations of SHAKE,AES variants and levels 2,3,5
Add support for parsing and verifying certificates with RSA-PSS
signatures. Including check PSS parameters in key with those in
signature algorithm.
Add support for parsing private RSA PSS key.
Add support for parsing public RSA PSS key.
* This PR resolves issues with using TLS client authentication (mutual auth) with crypto callbacks. The TLS client auth will not be sent without a private key being set. The solution is to allow setting a public key only if crypto callbacks is enabled and a devId is set.
* Fix to allow using crypto callbacks with TLS mutual authentication where a private key is not available.
* Fix for ED25519 sign when only a private key is loaded.
* Fix to enable crypto callbacks for ED25519 and Curve25519 in TLS by using the _ex init functions.
* Fix for wc_PemToDer return code where a PKCS8 header does not exist.
* Remove duplicate logs in DoCertificateVerify.
* Doxygen API updates: Added crypto callback help and updated use_PrivateKey with info about public key use.
* * Added crypto callback tests for TLS client and server with mutual auth for RSA, ECC and ED25519.
* Enhanced the API unit test TLS code to allow setting CA, cert and key.
* Revert ED25519 changes. Opt to calculate public key directly when required for signing in the TLS crypto callback test. Build configuration fixes.
* Fix to use proper devId in `ProcessBufferTryDecode`.
* Various build fixes due to changes in PR. G++ issue with `missing-field-initializers`. Unused api.c func with DTLS and session export. Duplicate `eccKeyPubFile` def.
* Added crypto callback TLS tests at WOLFSSL object level. Fix for ED25519/ED448 with client mutual auth where the private key is not set till WOLFSSL object. Fix issues with `wolfSSL_CTX_GetDevId` where devId is set on WOLFSSL object. Enable the `_id` API's for crypto callbacks.
* Proper fix for `eccKeyPubFile` name conflict. Was causing RSA test to fail (expected DER, not PEM).
* add DIST_POINT compatibility functions
* switch X509_LU_* from enum to define, prevent compiler type warnings
* refactoring, adding in comments, and formating
* refactoring and a memory leak fix
* cast return value for g++ warning
* refactor wolfSSL_sk_DIST_POINT_pop_free and remove NULL assign after free
* fix get next DIST_POINT node for free function
Co-authored-by: Jacob Barthelmeh <jacob@wolfssl.com>
* Remove NTRU and OQS
* Keep the DTLS serialization format backwards compatible.
* Remove n from mygetopt_long() call.
* Fix over-zealous deletion.
* Resolve problems found by @SparkiDev
- `EVP_PKEY_assign_RSA` should store the private key in DER format, not the
public key.
- The last call to `infoCb` in `wolfSSL_BIO_write` should provide the length of
the data to write.
- We should be able to parse RSA public keys starting with BEGIN RSA PUBLIC KEY
and ending with END RSA PUBLIC KEY.