The SSL_SESSION_set1_id and set1_id_context regression test needs
OPENSSL_ALL as well as the client cache reference path. With
NO_SESSION_CACHE_REF now the configure default, the plain
--enable-opensslall legs no longer take the reference path. The
session-cache-ref leg is the only one left on it, and it built with
opensslextra only, so the test skipped everywhere. Adding
--enable-opensslall keeps it running.
tests/swdev/Makefile localized every swdev symbol except the two entry
points with objcopy --keep-global-symbol, which macOS does not ship. On
Darwin, do the same with a second ld64 relocatable link and
-exported_symbol. The export check strips the Mach-O leading underscore
before comparing. The exported names now live in one SWDEV_EXPORTS list
that feeds both localization commands and the check.
The localization step is picked from configure's host_os rather than
uname, so cross-builds use the target's toolchain.
Add a macOS CI config that builds swdev with WOLF_CRYPTO_CB_ONLY_ED25519,
so make check depends on the ld64-localized swdev object.
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.
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.
Documentation:
- wolfSSL_get_verify_result() reports WOLFSSL_X509_V_OK once the callback
accepts. wolfSSL verified nothing, so say that this records the
callback's verdict rather than a check wolfSSL made.
- The OCSP stapling restriction is client-side. Both guards test
side != WOLFSSL_SERVER_END: a server staples its own status, which is not
part of verifying the peer. A server that asks the client to staple is
not refused, and no stapled response is checked for it either, because
ProcessPeerCertLeafRevocation is skipped with the callback.
- The list of what still applies was neither "two" nor complete. Add
PEER_KEY_ERROR and SCR_DIFFERENT_CERT_E, note MAX_CERTIFICATE_SZ next to
MAX_CHAIN_DEPTH, and state that 0 is the only accepting value, so
returning WOLFSSL_SUCCESS rejects.
- DoChainVerifyCb no longer allocates; drop MEMORY_E from its comment.
Code:
- NULL-check the certificate manager in ChainVerifyCbCheckSsl, as
ChainVerifyCbCheckCtx already does for ctx->cm.
- Re-run ChainVerifyCbCheckSsl in TLS_ASYNC_BUILD. A deferred verdict
resumes there, so TLS_ASYNC_BEGIN ran on the first pass only.
Tests:
- Pin both halves of the headline contract on the accept path: the verify
callback set with wolfSSL_set_verify() is not called, and
wolfSSL_get_verify_result() reports WOLFSSL_X509_V_OK.
- Cover a callback returning WOLFSSL_SUCCESS, which rejects.
- Rename test_tls13_chain_verify_cb_async to _defer. It exercises deferral
over TLS 1.3, not async crypto.
CI:
- Add chain-verify-cb-async, the first build combining the callback with
WOLFSSL_ASYNC_CRYPT, and the only one with async crypt and CRL but
without WOLFSSL_NONBLOCK_OCSP.
Three behaviour changes to existing configurations, introduced earlier in
this series and not called out until now:
- IsHsSuspendErr() recognizes OCSP_WANT_READ only under
WOLFSSL_NONBLOCK_OCSP, where the sites it replaced tested
WOLFSSL_ASYNC_CRYPT || WOLFSSL_NONBLOCK_OCSP. The keep-state re-entry
branch it replaced was itself under WOLFSSL_NONBLOCK_OCSP, so an
async-only build rewound into reset args and a freed pendingMsg. It now
fails cleanly instead.
- DoCertificate no longer advances serverState to SERVER_CERT_COMPLETE
when ProcessPeerCerts suspended.
- ReceiveData admits a suspended handshake error rather than refusing the
read, which under WOLFSSL_NONBLOCK_OCSP now includes OCSP_WANT_READ.
Adds an opt-in callback that takes over the peer certificate trust
decision. wolfSSL builds no chain, verifies no signature and checks no
date, revocation status, key usage or host name; the verify callback is
not called either. The callback gets only the DER certificates from the
Certificate message as WOLFSSL_BUFFER_INFO entries, certs[0] first. It
is consulted even under WOLFSSL_VERIFY_NONE, and its rejection fails the
handshake either way.
wolfSSL still decodes every certificate with ParseCert(NO_VERIFY) before
the callback runs, so malformed DER fails the handshake and the callback
is never handed it. Content wolfSSL does not understand - an unknown
critical extension, an unsupported key or signature algorithm, a
signature algorithm mismatch - is not a decoding failure and is left for
the callback to judge. The check runs once, not again on re-entry after
a deferred verdict.
The callback may return CHAIN_VERIFY_WANT_E to suspend the handshake and
be asked again with the same certificates when the application re-enters
wolfSSL_connect(), wolfSSL_accept(), wolfSSL_read() or wolfSSL_write().
A certificate received after the handshake (TLS 1.3 post-handshake
authentication) arrives inside wolfSSL_read() and is resumed by reading
again: ReceiveData() re-enters on any handshake-suspend error, and the
ticket-sent accept state leaves a suspended message alone. Rejection
fails with CHAIN_VERIFY_CB_E and one bad_certificate alert, and sets
peerVerifyRet so wolfSSL_get_verify_result() does not report X509_V_OK.
Still applied: an empty Certificate message keeps the existing
mutual-auth policy (the callback is not called for it), and the minimum
peer key sizes still gate the peer's own key, which the handshake uses
directly.
DTLS, raw public keys and OCSP stapling are not supported with the
callback. wolfSSL_CTX_SetChainVerifyCb() and wolfSSL_SetChainVerifyCb()
refuse with CHAIN_VERIFY_UNSUPPORTED_E when the context or object is
already configured for one of them, and a connection that uses one of
them anyway fails with the same error and an internal_error alert before
the callback is called. The DTLS paths are otherwise untouched.
The callback and its user context can be set on the context or on the
SSL object, the object's taking precedence. The feature is part of
--enable-all, has a CMake option and Doxygen entries.
The suspend machinery is shared with WOLFSSL_NONBLOCK_OCSP and
WOLFSSL_ASYNC_CRYPT. The open-coded pairs of comparisons against
WC_PENDING_E and OCSP_WANT_READ are replaced by IsHsSuspendErr(), and the
matching build guards by WOLFSSL_HAVE_HS_SUSPEND. That also closes three
holes which affected WOLFSSL_NONBLOCK_OCSP builds:
- DoHandShakeMsg() freed ssl->pendingMsg on a non-WC_PENDING_E suspend
while ProcPeerCertArgs still pointed into it.
- wolfSSL_connect()/wolfSSL_accept() and their TLS 1.3 counterparts
called FreeAsyncCtx() when buffered output flushed, discarding the
saved certificate state.
- ProcessPeerCerts() XFREE'd args in WOLFSSL_SMALL_STACK builds without
WOLFSSL_ASYNC_CRYPT, where args points inside ssl->async.
FreeHandshakeResources() unloads SSL-owned certificates and keys once
the handshake is done. The side check that limits this to the server
was compiled out when WOLFSSL_POST_HANDSHAKE_AUTH is defined, so a PHA
build unloaded the client's identity too, which is the side that needs
it to answer a post-handshake CertificateRequest. The client then sent
an empty Certificate and the server failed with NO_PEER_CERT, or
carried on unauthenticated when it did not require a certificate.
This hits TLS 1.3 clients that load their certificate and key on the
WOLFSSL object in builds with post-handshake auth but without
OPENSSL_EXTRA or WOLFSSL_WPAS_SMALL, both of which compile the unload
out. Identities loaded on the CTX are not owned by the SSL and were
never unloaded. A client that authenticated during the handshake was
hit as well on the first post-handshake request.
Unload on the server only, in every configuration.
test_tls13_pha stopped at wolfSSL_request_certificate() and never ran
the exchange. It now drives two post-handshake rounds to completion,
after an initial handshake with and without client auth, with the
client identity on the SSL. Both cases fail with NO_PEER_CERT without
the fix in a build with --enable-postauth and without OPENSSL_EXTRA.
Add a postauth-no-compat entry to the Linux os-check matrix, so a
GitHub Actions build runs the test with the unload compiled in;
--enable-all brings OPENSSL_EXTRA along and compiles it out.
Add an enterprise option that includes all items that would be required.
Update CMake build to match autotools. Added enterprise option.
Added CI loops for configuration options.
Add document that maps the OpenSSL configuration options to wolfSSL's.
Checked against various versions of OpenSSL.
The default key generation clears aSet, privVecsSet and pubVecSet on
success, but the small memory arm never did, so a key generated into an
object that had imported or used another key kept that key's matrix A and
vectors, and its signatures failed to verify. The full-vector verifier has
the same exposure on master; reading the cached public vector in the small
memory verify widened it.
Turn the signing KAT into a key reuse test: generate the KAT key into a
fresh object and into one that already signed and verified with a
different key, and require both to produce the same signature and to
verify it. A sign and verify on the reused object alone does not catch
this, because the signer and the verifier read the same stale caches and
agree. The comparison needs no known answer, so it also runs for the draft
and CHECK_Y/CHECK_W0 signers, which only skip the digest check.
Add pq-all rows for cached matrix A on the default signer, the only build
where the cache allocation test's assertions are compiled, and for every
cache with the small memory key generation, the only configuration where
the reuse test reaches the fix.
Signing retries until a candidate passes four infinity norm checks.
mldsa_check_low() examines every coefficient of a polynomial, but the signer
stops at the first polynomial that fails. For z and w0-cs2 that reveals
nothing: both are masked by the uniform vector y, and the count of y values
that push a coefficient out of range is 2*beta+1 whatever the secret
contributes, so every polynomial rejects with the same probability. The
reference implementation relies on the same argument. Those loops keep
their early exit.
ct0 is different: it is c*t0 with no mask, so the index of a failing
polynomial does depend on t0. Check every polynomial of ct0 rather than
stopping at the first failure. This costs nothing when the check passes,
since every polynomial is then checked anyway. The smallest and small
memory signers compute ct0 in the same row loop as w0-cs2, so they
accumulate the ct0 result in a flag of its own: the loop still stops at
the first rejecting w0-cs2 row, but a failing ct0 row no longer ends it.
The remaining hints are only cleared for a valid attempt, as a rejected
one can leave more hints than omega.
The optional WOLFSSL_MLDSA_SIGN_CHECK_Y and WOLFSSL_MLDSA_SIGN_CHECK_W0
checks run on values derived from the secret mask. Accumulate them in both
small memory signers instead of leaving the loop on the first rejecting
polynomial, so the amount of matrix A generated does not depend on which
one failed. The small memory signer's row pass still skips a round whose y
was rejected, as its columns were never accumulated, and it now checks w0
a polynomial at a time, so the mldsa_vec_check_low() guard narrows to the
y check.
Cover the range checks in the ML-DSA white-box test: both boundaries of
the accepted range, and a bad coefficient in the last position of a
polynomial and in the last polynomial of a vector, which only fail because
neither helper exits early. Add pq-all rows for the checks on the default
signer and for the w0 check alone on the small memory signer.
Derive the canonical option implications in wc_mldsa.h, straight after its
include of dilithium.h, and unconditionally: SIGN_SMALLEST_MEM, PRECALC and
PRECALC_A imply WOLFSSL_MLDSA_SIGN_SMALL_MEM, and VERIFY_SMALLEST_MEM
implies WOLFSSL_MLDSA_VERIFY_SMALL_MEM. They lived in the legacy name gate
region of dilithium.h, so WOLFSSL_NO_DILITHIUM_LEGACY_GATES silently gave
those builds the full memory signer, and the PRECALC pair lived in
wc_mldsa.c, where other translation units did not see it. Every route to
the key structure passes this point, so the key layout no longer depends
on include order, and dilithium.h now reads the build configuration before
deriving anything. The tests see the implications too, so the matrix A
cache test no longer asserts a cache that a PRECALC_A build's small memory
signer never fills. Reject a PRECALC_A value below one, including one
defined without a value.
Derive WC_ENABLE_ASYM_KEY_IMPORT and WC_ENABLE_ASYM_KEY_EXPORT from
WOLFSSL_HAVE_MLDSA rather than the legacy HAVE_DILITHIUM, so an ML-DSA
build that opts out of the legacy gates still gets the RFC 5958 helpers it
calls. settings.h maps HAVE_DILITHIUM onto WOLFSSL_HAVE_MLDSA before either
use, so the canonical name covers both spellings.
--enable-mldsa=<level> named only a parameter set and turned key
generation, signing and verification all off, so nothing was built. Each
operation now defaults on unless one of them is named.
WOLFSSL_MLDSA_NO_CHECK_KEY did not compile: wc_CheckPrivateKey() called
wc_MlDsaKey_CheckKey() unguarded while the function is compiled out. Return
NOT_COMPILED_IN for ML-DSA there, as the RSA arm does. Such a build cannot
confirm that a certificate and key match, so the *_check_private_key()
calls report a mismatch; test_wolfSSL_X509_check_private_key_mldsa now
builds in that configuration and expects it.
Several whole-vector helpers are only called by the full-vector key
generation but their guards still admitted
WOLFSSL_MLDSA_MAKE_KEY_SMALL_MEM, so MAKE_KEY_SMALL_MEM with NO_CHECK_KEY
compiled static functions with no references. Give them the exclusion
their siblings have.
Add pq-all rows for the legacy gate opt-out, a heapless verify-only build
named only by the canonical options, cached matrix A with PRECALC_A,
sign-only builds, and NO_CHECK_KEY with and without the small memory key
generation.
Small memory signing walked matrix A a row at a time and copied and
transformed the polynomial of y it needed for every element of the row.
Each polynomial of y was therefore transformed k times with an identical
result: 156 transforms per ML-DSA-65 signature where 31 are needed.
Walk the matrix a column at a time instead. Every row of the matrix uses
the same column of y, so the transform is done once and the products are
accumulated down the column into w0, which is already a vector of k
polynomials. Inverting, decomposing and encoding then run as a second
pass over the rows, with w0 replaced in place and the scratch polynomial
holding w1. w is reduced before the inverse transform on both column
accumulating paths.
Vector y stays resident, so unlike the smallest memory implementation
this does not have to regenerate it, and it keeps generating y as a whole
vector so the assembly generators are still used.
With WOLFSSL_MLDSA_SIGN_SMALL_MEM_PRECALC_A the pre-calculated rows are
multiplied as a vector, and w is sized for all k rows: the ML-DSA-44
decompose kernels take no dimension and touch every row, so the rows above
the pre-calculated ones are cleared, and mldsa_vec_encode_w1() now honours
the dimension it is given. The whole-vector helpers that path drives are
gated on a new MLDSA_SIGN_VEC_HELPERS, which names the set of callers.
Under WC_MLDSA_FAULT_HARDEN the column loop checks that the polynomial of
y it reads, walked independently of the column index, lies inside y.
Memory is unchanged: w0 was already held for the hints and the row
accumulator becomes the scratch the transform runs in.
WOLFSSL_MLDSA_SMALL_MEM_POLY64 no longer applies to signing as a 64-bit
accumulator would now be needed for every row of w, which also returns
the 2KB it was allocating. The option now only affects small memory key
generation and verification; the option list says so.
Signatures are unchanged. Signing is 12 to 29 percent quicker with the C
code and 6 to 12 percent quicker with AVX-512, which makes it faster than
the smallest memory implementation on every target rather than only where
the assembly runs. Add pq-all rows for PRECALC_A split part way, saturated
past k, and on the Intel assembly paths, and a -Wconversion row.
WOLFSSL_MLDSA_VERIFY_SMALLEST_MEM defined WOLFSSL_MLDSA_VERIFY_NO_MALLOC,
so streaming vector z a polynomial at a time was only available with the
verify buffers pinned against the key for the life of the key. The two are
independent, so let them be selected independently. The pinned buffers are
still available by defining WOLFSSL_MLDSA_VERIFY_NO_MALLOC as well.
Peak verify heap with allocated buffers, in bytes:
small mem smallest mem
ML-DSA-44 8776 5704
ML-DSA-65 9800 5704
ML-DSA-87 12104 5960
sizeof(wc_MlDsaKey) is 7928 bytes either way; with the buffers pinned
instead it is 20168 bytes for the small memory verify and 14024 bytes for
the smallest memory verify.
Make WOLFSSL_NO_MALLOC select the small memory verify along with
WOLFSSL_MLDSA_VERIFY_NO_MALLOC. The pinned verify buffers only exist under
the small memory verify, so the no-malloc option alone selected nothing and
every verification failed with MEMORY_E. It applies only where
verification is compiled in, and WOLFSSL_MLDSA_VERIFY_ALLOW_MALLOC keeps
the allocating verify for a build that still has an allocator.
Document WOLFSSL_MLDSA_VERIFY_SMALLEST_MEM and
WOLFSSL_MLDSA_VERIFY_ALLOW_MALLOC in the option list, and add pq-all rows
for the smallest memory verify with allocated buffers and with the default
signer.
Two combinations of the ML-DSA cache options did not work.
WC_MLDSA_CACHE_PUB_VECTORS declares a cached t1 vector in wc_MlDsaKey and
WOLFSSL_MLDSA_VERIFY_NO_MALLOC declares a pinned t1 scratch polynomial in
the same structure, so enabling both failed to compile with a duplicate
member. Rename the pinned scratch to vt1, and widen the mldsa_vec_decode_t1
guard so the cache builds alongside the small memory verify.
Read the cached public vector in the small memory verify. Nothing consumed
the cache there: every public key import paid an allocation and a full
decode and transform of t1 that was then discarded and redone a polynomial
at a time. The cached vector is already in the form the row loop wants, so
the multiply reads it in place.
The matrix A cache regression test asserts that signing populates key->a.
That holds for the full signing implementation, which is what the test was
written for, but the small memory implementations stream matrix A rather
than caching it, so they leave key->a as NULL and the test failed. Check
the cache only when small memory signing is off and keep the round trip
for the others. WC_MLDSA_CACHE_PRIV_VECTORS enables WC_MLDSA_CACHE_MATRIX_A,
so it saw the same failure. Add a pq-all row for the cached public vector
with the pinned verify buffers.
Small memory signing kept vector w1 as k full polynomials although it is
only ever consumed in its encoded form, once by the commit hash and once
by MakeHint, which only tests each coefficient for zero. Keep the encoded
w1 and build it a polynomial at a time from a single scratch polynomial
of w. This also removes the separate w1 encode buffer that was allocated
and freed on every rejection attempt.
Add WOLFSSL_MLDSA_SIGN_SMALLEST_MEM. It generates matrix A a column at a
time so that one polynomial of y is held instead of the whole vector and
is transformed once rather than once per row, decomposes w into w0 in
place, and regenerates y for the z calculation. It cannot be combined with
the PRECALC options, and WOLFSSL_MLDSA_SMALL_MEM_POLY64 has no effect on
signing in this mode. The z polynomial, which is not live at either
expansion, serves as the mask expansion scratch, so the signer makes one
allocation per signature.
Because y is derived twice per rejection round, bind the two derivations:
each polynomial of y is checksummed when it is first expanded and checked
when it is regenerated for z, and a mismatch fails with BAD_COND_E rather
than producing z from a different mask than the commitment used. The
checksum is a pair of Fletcher style sums, which catch any change to one
or two coefficients of a y polynomial without multiplying the secret
coefficients, as a multiply chain is not constant time on every core. The
checksums are zeroized on return. The key's SHAKE object, whose last use
regenerates a polynomial of y and would otherwise leave rho'' recoverable
from the key, is re-initialized, which clears its state but keeps the hash
type a crypto callback free needs.
Small memory key generation held t as a full vector for one final vector
encode. Encode t a polynomial at a time and overlay t and the single
decoded s2 polynomial on the s2 vector, which is dead once s2 has been
encoded into the private key.
Peak heap, in bytes, measured with a counting allocator:
sign before sign after smallest keygen before after
ML-DSA-44 16200 13896 9800 14152 10056
ML-DSA-65 21320 16968 11848 19272 13128
ML-DSA-87 27464 21320 14152 25416 17224
Signatures are unchanged. Add a signing KAT to testwolfcrypt: signing is
deterministic given the key seed and the signing seed, so every signer must
produce the same bytes, and the test compares a SHAKE-256 digest of the
signature with the default signer's. tests/api holds full signing KATs,
but unit.test is not built in the --enable-cryptonly CI rows that cover
the small and smallest memory signers.
Problems found while making these changes are fixed here too:
- mldsa_vec_expand_mask dispatched to AVX2 generators that only handle
dimensions 4, 5 and 7, and silently left y untouched for any other
dimension. Test the dimension before saving the vector registers and
fall through to the C implementation otherwise.
- WOLFSSL_MLDSA_SIGN_SMALL_MEM_PRECALC_A greater than k indexed A, w0
and w1 past their allocations, as the loops used the raw macro rather
than min(macro, k).
- WOLFSSL_MLDSA_SIGN_CHECK_W0 passed two arguments to the three argument
mldsa_vec_check_low, so the option never compiled with small memory
signing.
- WOLFSSL_MLDSA_VERIFY_SMALLEST_MEM did not build alongside small memory
signing, as mldsa_vec_ntt_full and mldsa_vec_check_low were unused.
- WOLFSSL_MLDSA_VERIFY_SMALLEST_MEM sized z at l polynomials while only
ever using one.
Add pq-all and -Wconversion CI rows for the smallest memory signer, with
and without the y and w0 checks and verification.
- don't serialize cid when no negotiated
- abort export w/ post HS in flight
- abort export before the handshake is done
- abort export of a dup'ed session
- export the KeyUpdate response, resumption secret and ticket nonce
- doc/comments/tests improvements
wolfSSL_dtls_export()/wolfSSL_dtls_import() only ever carried a DTLS 1.2
session. A DTLS 1.3 connection additionally keeps its epochs, each with
its own traffic secret, record keys, sequence numbers and replay window,
and none of that was serialized.
Export version 7 appends the DTLS 1.3 record layer state and the
Connection ID as two trailing, length-prefixed sections, so version 6
blobs still import.
Refusals, so a blob never describes a connection that cannot be
restored: a KeyUpdate in progress, a half sent fragmented message, work
parked in a write dup, and state only export/import of a DTLS 1.3
session (state only blobs stay stamped export version 6).
CI: build the session export job with Connection ID support.
Without NO_SESSION_CACHE_REF, wolfSSL_get_session() returns a reference into
the process-global client cache rather than a session object. That reference
is valid only while the slot it names still holds the session it was issued
for, which the caller cannot observe, so the safe use of the API is the one
the macro selects. Only the integration recipes defined it, and none of them
for that reason: --enable-opensslextra and --enable-opensslall, which is what
an application written against SSL_get_session() reaches for, left it unset.
settings.h now defines NO_SESSION_CACHE_REF unless WOLFSSL_SESSION_CACHE_REF
is defined, so every build selects it, including the user_settings.h, IDE and
bare-metal builds that have no configure step. Putting the rule in the header
rather than only in configure and CMake means no project has to add a macro to
keep the safe behaviour, and the ones that want the deprecated reference ask
for it instead. --enable-session-cache-ref and -DWOLFSSL_SESSION_CACHE_REF=yes
define that opt-in macro, and options.h records it alongside the negative one
so an application cannot disagree with the library it links.
A build that opts in warns at compile time and the warning names
WOLFSSL_SESSION_CACHE_REF_WARNED, which silences it while keeping the path. It
sits inside the NO_SESSION_CACHE region, so a build without a session cache
and a wolfCrypt-only build stay quiet, and it takes the #warning / #pragma
message form already used for WOLFSSL_KEYLOG_EXPORT_WARNED.
The recipes that define NO_SESSION_CACHE_REF on their own keep doing so and
still win over the option. Both build systems say so rather than leaving a
reported option disagreeing with the build: configure warns and clears its
summary line, CMake warns and syncs the cached option. configure also treats
TITAN_SESSION_CACHE as a conflict, because settings.h derives the macro from
it and that never reaches the compile line for a flag scan to find.
The os-check session-cache-ref entry existed because --enable-opensslextra
left the macro unset. It now asks for the reference path explicitly, or
test_wolfSSL_client_cache_id_overwrite would skip and the generation check
added with it would be covered nowhere.
Anything migrating has one lifetime difference to mind: the deprecated
reference stayed valid after wolfSSL_free(), the session object does not, so
code holding the result of wolfSSL_get_session() past the WOLFSSL it came from
has to move to wolfSSL_get1_session() and wolfSSL_SESSION_free().
Without NO_SESSION_CACHE_REF, wolfSSL_get_session() hands back a ClientSession
reference into the process-global cache rather than a session object, and
ClientSessionToSession() resolves it against nothing but a hash of the entry's
session ID. That ID does not change when the entry is overwritten, so a
reference kept across an overwrite still resolved, to contents it was never
issued for.
Give every cache write a generation number and record it in the ClientSession,
so a reference resolves only while the entry still holds the contents it was
issued for. The numbers come from a counter on the SessionRow rather than from
the entry, so that under SESSION_CACHE_DYNAMIC_MEM a freed and reallocated
entry cannot restart at a value an outstanding reference still holds.
The generation is not advanced when an overwrite carries the same master
secret, the benign re-add of an unchanged session; without that an older
reference would silently fall back to a full handshake. The comparison runs
through ConstantCompare so its timing says nothing about the cached secret.
WOLFSSL_CACHE_VERSION goes to 3: the ClientCache, the SessionRow prefix and
the WOLFSSL_SESSION layouts written by the session-cache save functions all
change.
The test builds the sequence out of two real handshakes on separate contexts,
behind two controls so it cannot pass by refusing everything. Every profile in
the os-check matrix that builds the unit tests defines NO_SESSION_CACHE_REF,
which compiles the reference path out, so a session-cache-ref entry configures
one that does not.
Under WOLFSSL_SMALL_CERT_VERIFY, ProcessPeerCertParse() merged sigRet back
only when the parse returned 0, so any parse error hid the signature result.
ParseCertRelative(VERIFY) runs its checks in a fixed order: decode, signer
lookup, ConfirmSignature(), name constraints, validity dates, critical
extensions. That order is what makes overriding date errors a sound policy
for a device with no real-time clock, since a date error can only reach the
application for a certificate whose signature already verified. Splitting the
signature check out inverted it.
Let a non-zero sigRet take precedence over exactly those five errors that
ParseCertRelative() only reaches after the signature has been confirmed.
Errors raised before that point keep priority as they do in a full verify, a
parse error is still reported unchanged when the signature was good, and the
raw public key exemption for the leaf is unaffected.
The test builds a leaf naming the 2048-bit test root as its issuer, signed
either by the root or by another key, in and out of its validity window, and
runs a memio handshake with a date tolerant callback. It anchors the expected
result to what wc_CheckCertSignature() makes of each leaf.