openssl.test starts its servers, then waits in check_server_ready until
each one accepts connections. The RSA-PSS server starts after that loop,
so the first wolfSSL client connects once s_server has survived 0.1
seconds. On a busy node s_server has not bound the port by then and the
client fails with "Connection refused".
Run check_server_ready for the RSA-PSS server too.
scripts/falcon_fpr_ct_check.py compiles the integer fpr emulation with
gcc and clang for Cortex-M0+, M3, M7 and M33, RV32 and RV64, i386,
x86_64, AArch64, ESP32 and ESP32-S3 at -Os and -O2, and reports any
conditional branch outside the fixed-count loops of fpr_div and
fpr_sqrt and any compiler runtime call other than the 64-bit multiply.
A second pass compiles all of falcon.c with -g for the default and
smallest memory signers at -Os, -O2 and -O3, and reports any branch the
line table attributes to the fpr block: inlining into the FFT, sampler
and key generation code can undo what the block shows on its own. Stub
libc headers stand in for a sysroot.
It is meant for local runs after changes to the fpr code. A missing
toolchain is an error unless --allow-missing is given, and the cross
compilers can be named with ARM_GCC, RV_GCC, A64_GCC, XT_GCC and
XT3_GCC. Against master it fails with clang on Cortex-M0, RISC-V and
i386 and with gcc on ESP32, and the second pass fails on x86_64 when
the barriers are dropped there.
The ACME identifier OID was added to wolfssl/wolfcrypt/oid_sum.h by hand
after the file was generated, because scripts/asn1_oid_sum.pl had no way
to emit an entry wrapped in an #ifdef. Regenerating the header therefore
deleted it, silently, and the loss only showed up as a build failure in a
WOLFSSL_ACME_OID build.
Give print_enum an optional per-entry "guard" field that wraps just that
entry in #ifdef/#endif, in both the WOLFSSL_OLD_OID_SUM and the xor
halves, and describe the ACME identifier 1.3.6.1.5.5.7.1.31 in the
generator like every other extension OID.
The regenerated header is byte for byte what was in the tree, so the
hand-written sums were right; what was missing was the generator's
knowledge of them.
wolfCrypt implemented ML-KEM only in its raw FIPS 203 encodings. There
were no ML-KEM key OIDs anywhere in the ASN.1 layer and no way to read
or write a SubjectPublicKeyInfo or a PKCS#8 private key, so an ML-KEM
key could not be carried in any DER structure.
Add the FIPS 203 key OIDs from the NIST arc 2.16.840.1.101.3.4.4.x to
the OID sum generator and regenerate oid_sum.h. The old-scheme byte sums
(416 to 418) do not collide, so unlike FrodoKEM no remapping is needed.
Add the matching OID octet strings and OidFromId cases to asn.c.
Add wc_MlKemKey_PublicKeyToDer, wc_MlKemKey_PrivateKeyToDer,
wc_MlKemKey_PublicKeyDecode and wc_MlKemKey_PrivateKeyDecode, following
the FrodoKEM codec. The private key is emitted in the expanded form,
which SetAsymKeyDer wraps in the nested OCTET STRING that the standard
private key CHOICE uses for its expandedKey alternative. The seed and
seed-with-expanded alternatives are not produced.
Decoding requires the key object to be initialized for a parameter set
and the OID in the DER to name that same set. ML-KEM cannot auto-detect
the way FrodoKEM does, because WC_ML_KEM_512 is zero and so cannot be
told apart from an unset type in a zeroed object.
Tested by round-tripping every enabled parameter set through both
encodings and confirming that a key recovered from DER still agrees on
an encapsulated shared secret, that a public key DER naming a different
parameter set is rejected, and against OpenSSL asn1parse: the
SubjectPublicKeyInfo carries the expected OID with the parameters field
absent and a 1568 byte ML-KEM-1024 encapsulation key.
falcon_native_check_key converted f and g into [0, q) ahead of the NTT
with a branch on the sign of each secret coefficient. It now adds q under a
mask taken from the sign bit, as falcon_compute_public already does.
key->level is a public field, and a level written into it without
wc_falcon_set_level() could make an import, export or DER encode copy past
the key buffers: with dynamic keys they are sized for the old level, and in
a level-restricted build the inline arrays only hold the level that was
built. falcon_level_params now takes the key and, with dynamic keys, checks
the level against the recorded kSz; the new falcon_level_ok() puts the same
check in front of every public function that touches p or k. Both sit
outside the native-only block, since crypto-callback-only builds keep those
public functions. The signing caches had the same problem, and
falcon_sign_key_setup now drops a cache whose recorded length does not fit
the key's current level instead of reusing it.
test_wc_falcon_level_overwrite covers all of it.
The Falcon white-box reported failures as ordinary notes, and the CI step
grepped for note wording that 11 of the failure messages did not contain.
Failures now print [wb][FAIL] and the run ends with
"done (N notes, with failures)", like the other white-boxes; notes are left
for the residual decisions only. The CI step greps that line and builds the
white-box with ASan and UBSan.
Nothing in CI built Falcon with WOLF_CRYPTO_CB_ONLY_FALCON, so
cryptocb-only.yml gains a falcon-onlycb-no-swdev entry. swdev has no Falcon
handlers yet, so the entry builds without it and runs the tests that need
no device.
Smaller items:
- falcon_sm_intt_p takes logn and forms n^-1 from p - 1 = 5 * 2^13, so it
needs no division (a library call on Cortex-M0).
- wb_sm_sign_core_err wipes its scratch after signing and the basis when
key generation fails.
- scripts/falcon_sm_tables.py parses its arguments with argparse, so --help
works and an unknown option is an error rather than printing the tables.
--enable-falcon=smallest-mem (WOLFSSL_FALCON_SIGN_SMALLEST_MEM, implies
small-mem) signs in a 20*n byte working area, following T. Pornin, "A
RAM-Efficient Implementation of Falcon" (IACR ePrint 2026/1915). Peak heap per
signature goes from 46KB to 12KB at Falcon-512 and from 92KB to 22KB at
Falcon-1024. With the default integer fpr backend signing is also about twice
as fast as small-mem (2.15ms to 1.03ms at Falcon-512 on an arm64 host); with
the native double backend it is about 1.5 times slower.
The signer never holds the ffLDL tree or a full basis in FFT form:
- ff* + gg* and Ff* + Gg* are computed exactly as integers, mod q and mod
p = 40961 with a CRT. q*p covers every key that decodes, so unlike the
paper's 18433 no key precondition or adjustment bitmap is needed.
- Self-adjoint polynomials are held at half size and transformed with the
paper's self-adjoint FFT, whose first stages stay on integers. The tree
is built during sampling from the half-size LDL, the right child of the
root is q^2/(ff* + gg*), and the node values are stored as L/(1 + X).
- Only the fractional part of each target, (cf mod q)/q and (-cF mod q)/q,
is sampled, and it is added at the leaves. The sampled integers are kept
and s2 = f*z0 + F*z1 is rebuilt mod p, so no inverse FFT runs at all.
- Each candidate is checked as a verifier would, s1 = c - s2*g/f, before
the norm test.
- Neither c nor the decoded basis is held. The sponge that absorbed
nonce || msg is kept (416 bytes) and c is squeezed again from a copy
wherever it is needed, which costs the same for any message length;
falcon_hash_to_point is split into absorb and squeeze halves for this.
f, g and F are decoded from the encoded key into the upper half of the
NTT buffer they are lifted into, and G is rebuilt once per attempt in the
idle second target buffer, or all four are read from the basis cache.
Together this saves 6n bytes for about 1% of signing time with the
integer fpr backend and 3-7% with native doubles. The copy is squeezed in
a non-inlined helper so that it is never on the stack under the sampler.
Algorithm 12 of the paper returns t - z at a leaf without the late-added
fraction e; it has to include it, or the left target misses the e1*L term
and no attempt is ever short.
G is recomputed with the mod-q NTT and a constant-time batch inversion rather
than falcon_complete_private, whose FFT scratch is larger than the whole
signing area. Keys whose f is not invertible mod q are rejected there.
The nonce and sampler seeding are unchanged, and the sampler is called in the
same order as by the other signers, so with the same randomness the signature
is the same except where rounding moves a sampler centre across a threshold:
2 of 10,000 signatures in a comparison against small-mem. The pinned
deterministic vectors are reproduced as they are.
The constant tables (R = 1/(2cos(phi)), C = 4cos(phi)^2, D = -tan(phi), and the
mod-p NTT twiddles) come from scripts/falcon_sm_tables.py, which rounds
correctly from Decimal arithmetic and, with --check, diffs its output against
falcon.c the way scripts/puf_bch_genpoly.py does for puf.c; the smallest-mem
CI legs run that check. Level-1-only builds keep only the first half of each
table.
The whitebox gains smallest-mem twins of the restart loop, sign_core error
latch and G recomputation tests; CI gains smallest-mem backend legs and runs
the liboqs interop for it as well.
* Stop multi-msg-record depending on the peer's session ticket support
scripts/multi-msg-record.test fails all eleven of its TLS 1.2 cases on any
machine running tlslite-ng 0.8.0-beta1, whenever wolfSSL is built with
session tickets. The wolfSSL client aborts the handshake with
SESSION_TICKET_EXPECT_E (-390).
That beta echoes an empty session_ticket extension in its ServerHello and
sets send_session_ticket unconditionally, then sends no NewSessionTicket
because its default HandshakeSettings carry no ticket keys. Rejecting that
is correct behaviour on our side, per RFC 5077. Upstream fixed the server
in 0.8.0-beta2 by gating the echo on ticket_count and ticketKeys, so only
that one release is affected.
HAVE_SESSION_TICKET alone decides it. A PRB-dtls run on the affected node
makes that plain: three configurations, one commit, one box, and only the
third, --enable-dtls --enable-dtls13 --enable-dtls-mtu
--enable-session-ticket, failed. It is not specific to --enable-all, which
merely happens to turn the feature on.
Hide the extension from the tlslite server for the duration of the TLS 1.2
case, using the monkey patch the test already installs. Session tickets are
not what this test covers, and releases from 0.8.0-beta2 on never echo the
extension here anyway, so this changes nothing on a current tlslite-ng.
Also report the wolfSSL client's return code and stderr on failure. Both
runners preferred the server side traceback and dropped the client's output
entirely, so a failing CI log showed a bare TLSAbruptCloseError from the
socket layer and no sign of the -390 that caused it.
Verified on master and on the failing branch, x86_64 Linux and macOS arm64,
against tlslite-ng 0.8.0-beta1 and 0.8.2: 15 passed, 0 failed, 1 skipped in
every combination. Before the change, 0.8.0-beta1 gives 4 passed, 11 failed.
* Test that a promised but absent session ticket is rejected
RFC 5077 requires a server that puts the session_ticket extension in its
ServerHello to follow with a NewSessionTicket. wolfSSL answers a server
that skips it with SESSION_TICKET_EXPECT_E, but nothing tested that. The
behaviour only came to light because a tlslite-ng beta got it wrong, and a
refactor could relax it into accepting the ticketless handshake without any
test noticing.
Drive the case deliberately: configure the tlslite server with ticket keys
so it promises a ticket on every release, replace _serverSendTickets with
an empty generator so it sends none, and require the client to fail with
-390.
The case skips itself when the client offers no session_ticket extension,
which is how a build without HAVE_SESSION_TICKET reports.
Checked both ways round. Leaving _serverSendTickets intact makes the
handshake succeed and the case fail, so the assertion is specific to the
missing ticket and not to any handshake error. Hiding the extension from
the server, which is how a build without HAVE_SESSION_TICKET looks, makes
the case skip rather than fail.
* Pin tlslite-ng in the os-check workflow
scripts/multi-msg-record.test speaks TLS to a tlslite-ng server, so that
package decides what the 24 --enable-all entries in os-check-linux.json
and the macOS entry are tested against, and whether the test runs at all
rather than reporting SKIP. Installing it unpinned means any upstream
release lands across the whole matrix at once, on whichever unrelated PR
happens to run next.
Pin it to 0.8.2, matching how advisory.yml and sbom.yml pin their Python
dependencies. No behaviour change today: 0.8.2 is what an unpinned install
already resolves to.
In ParseCipherList(), accept the whole-string cipher list "ALL:eNULL" in builds
without OPENSSL_EXTRA/OPENSSL_ALL, passing SUITES_NULL_EXPLICIT as the haveNull
arg to InitSuites(), so that the RFC 9150 integrity-only TLS 1.3 suites, and the
legacy NULL suites, are included, matching the semantics of the eNULL keyword in
keyword-capable builds.
Previously these builds rejected the list outright; in scripts/openssl.test this
was masked by the example server's -x flag, silently leaving the server on the
default suite list, which included the NULL suites until PR #10594 removed them.
Default suite lists are unaffected: every default-list path still passes
haveNull=1, which the WOLFSSL_TLS13_NULL_CIPHER_IN_DEFAULT gate rejects, and in
OPENSSL_EXTRA/OPENSSL_ALL builds the new match arm is compiled out, so the only
behavioral change anywhere is that a previously-failing
wolfSSL_CTX_set_cipher_list("ALL:eNULL") call now succeeds.
scripts/openssl.test: remove stray debugging echo.
myEccKeyGen() generated the key share straight into the library's key object,
and myEccSharedSecret() is handed only the peer's key, so a TLS 1.3 client had
no private key left to reach and every handshake ended with:
wolfSSL_connect error -170, ECC input argument wrong type, invalid input
TEST_PK_PRIVKEY already kept the key on the application's side, which is what
a real PK-callback application does anyway; it just was not the default. Keep
it whenever the connection is TLS or DTLS v1.3 as well, and have the shared
secret callback pick the key by the same question its key gen asked, not by a
flag key gen has not set yet. The union holding it stops being
TEST_PK_PRIVKEY-only, and its comment stops naming TLS v1.2 as the version
that needs it.
Neither example zeroed its PkCbInfo, so hasKeyGen started as stack garbage.
Only the TEST_PK_PRIVKEY paths read it before, which hid that; zero it in
both.
scripts/pkcallbacks.test only ever ran the build's default version, and that
is TLS v1.2 wherever it is compiled in, so none of the above was covered. Run
the default and then each version the build has.
scripts/external.test connects to www.wolfssl.com without a server_name
extension, so the CDN in front of that host answers with its own default
certificate rather than the wolfSSL one. That chain currently runs through
the RSA-4096 GlobalSign Root R46, while the intended chain is RSA-2048
throughout, so the test only passes on builds able to verify a 4096-bit
signature.
Two PRB configurations fail on master because of it. The fastmath leg with
FP_MAX_BITS=6144 reports BUFFER_E, and the 32-bit leg reports
ASN_SIG_CONFIRM_E because a build without WOLFSSL_X86_64_BUILD,
WOLFSSL_AARCH64_BUILD or OPENSSL_EXTRA defaults SP_INT_BITS to 3072. Both
are correct refusals to handle a key larger than the build supports, so the
test, not the library, is what needs fixing.
Name the host with -S in both client invocations. The option is a no-op
where the build lacks SNI, so those configurations keep their current
behavior, and the check uses the client's own "-S check" probe to decide.
Verified by building master with CFLAGS=-DSP_INT_BITS=3072, which
reproduces the BUFFER_E failure, and confirming the test passes with this
change in the same build and in an --enable-all build.
Fix guard around inclunde of chacha20_poly1305.h in internal.h for when
session ticket is not ChaCha20-Poly1305.
Fix port allocation and readiness testing in scripts.
Fix other issues in the scripts as well.
Update test.h code around ready file.
For LTO compiles, all functions must be used.
Make sure the assembly functions are marked as 'used' when not called
internally.
Fix a race in the example client that intermittently failed
scripts/unit.test on TLS 1.3 non-blocking session resumption.
RISC-V 64-bit: s0 needed but functions need to omit frame pointer.
Have source matching generated code again.
snifferWorker() looped while the shutdown flag was clear and only then
drained its queue. Reading a capture file, main() enqueues every packet
and then sets that flag and joins, normally all before the worker thread
is scheduled for the first time, so the worker woke up, saw the flag and
returned without decoding a single packet. Instrumenting the loop shows
it exiting with zero iterations and a non-empty queue, at -O0 as well as
-O1, so this is an ordering race rather than a hoisted load.
Keep going after shutdown for as long as packets are queued. The check
has to be its own helper that takes the worker's semaphore, because a
placeholder head means an empty queue: looping on worker->head alone
would spin forever on a placeholder nothing will ever fill. shutdown and
unused are polled across threads, so mark them volatile.
That alone left -keylogfile broken in this build. The sniffer's server
and secret tables are thread local, so every worker has to repeat the
setup main() did for itself, but the workers only ever called load_key()
and so reported every packet as coming from an unregistered server. Carry
the keylog path in SnifferWorker and load the secrets and create the
keylog sniffer server per worker.
Verified with --enable-sniffer CFLAGS=-DTHREADED_SNIFFTEST: the whole
sniffer test suite passes, and the multi-session captures decode the same
application data at 1, 2, 4 and 8 threads as they do without threading.
curve25519 blinding is enabled by default for the C implementation, in
settings.h, and draws from the private key's own RNG on every shared secret.
The sniffer built its key with wc_curve25519_init_ex() followed by
wc_Curve25519PrivateKeyDecode(), and neither sets one, so the shared secret
reached wc_RNG_GenerateBlock() with a NULL RNG and came back BAD_FUNC_ARG
while every argument it was given was valid. SetupKeys() then reported a
server and client key mismatch and no X25519 session could be read at all.
The library's own static ephemeral path already calls
wc_curve25519_set_rng() under the same guard, and the sniffer already calls
wc_ecc_set_rng() for the ECC key a few lines above. Only X25519 was missed.
Regenerate the two X25519 captures. Both were committed as a bare 24 byte
pcap header with no packets in them, so those legs had never tested anything;
they now hold real traffic and fail against the unfixed sniffer.
Drop the has_packets guard added earlier in this branch. It existed to skip
the empty captures, and with every capture holding packets it is dead code.
Removing it is also the better behaviour: snifftest already fails a capture
that yields no plaintext, so an empty file now fails the suite loudly instead
of being skipped, which is what a missing fixture deserves.
Hand the corrected plaintext length to the WOLFSSL_SNIFFER_STORE_DATA_CB
callback. The previous commit fixed the length only for the branch that
copies into the caller's buffer; the callback branch still passed the raw
record size, which spans the explicit IV, the tag or MAC and any padding.
The callback pointer already points at the start of the plaintext, so the
extra bytes run off the end of the decrypt output buffer, which leaves only
a record header of slack. AddressSanitizer reports a heap-buffer-overflow
read of 68 bytes on a TLS 1.2 CBC capture before this change and is clean
after it.
Reject a non-empty encrypt_then_mac extension in the ServerHello. RFC 7366
section 3.1 specifies empty extension_data and TLSX_EncryptThenMac_Parse
enforces it, as do the neighbouring length-constrained cases in the same
switch.
Clear the Encrypt-Then-MAC decision at the start of every ServerHello.
Nothing reset it, so a renegotiation that dropped the extension kept
stripping a MAC that was no longer there, and one that added it started
stripping while the previous cipher was still active.
Reset the decrypted flag at doMessage and gate the padSz subtraction on it.
The flag is set inside the decrypt block and was never cleared, so it
described whichever earlier record in the packet had last been decrypted.
The handshake case already guards the same value with it.
Set the decoded-data flag on the WOLFSSL_ASYNC_CRYPT drain path too. A
record that goes pending is completed inside SnifferAsyncPollQueue rather
than DecodePacket, so an async build could decrypt a capture correctly and
still report that nothing was decrypted. Send that diagnostic to stderr
rather than into the decoded output, and note the exit status in the usage
text so a wrapper is not surprised by it.
Add a TLS 1.2 CBC Encrypt-Then-MAC capture and the sniffer-gen.sh recipe
that produces it, so the fix has a regression test that can be regenerated.
The existing static RSA and IPv6 captures exercise the same path but no
script in the tree can rebuild them. The new capture covers both failure
modes: with an HMAC-SHA1 suite the old code fails the decrypt outright,
and with HMAC-SHA256, whose MAC is a multiple of the block size, it
silently decrypts the wrong byte range and emits 48 bytes where 14 are
correct.
Warn when the Encrypt-Then-MAC gate drops the TLS 1.2 CBC captures, so a
build configuration that loses coverage says so, and regenerate the keylog
reference output with a binary that carries the new feature token.
The sniffer ignored the encrypt_then_mac extension (RFC 7366) in the
ServerHello. For a CBC suite that meant the trailing MAC was handed to the
block decrypt along with the ciphertext, so the length was not a multiple of
the block size and every record failed with BAD_FUNC_ARG. wolfSSL peers
negotiate Encrypt-Then-MAC by default, so no TLS 1.2 CBC capture taken from
one could be decrypted. Record the extension and remove the MAC before
decrypting.
The plaintext length returned to the caller, printed by snifftest as
SSL App Data(packet:length), came from the record size without subtracting
what DecryptMessage() had already worked out in ssl->keys.padSz. A 14 byte
payload was reported as 30 under TLS 1.2 AES-GCM and as 31 under TLS 1.3.
Subtract padSz so the sniffer removes the same bytes the ordinary read path
removes from ssl->curSize.
Both failures were invisible because the tests could not fail. snifftest
overwrote hadBadPacket on every packet instead of accumulating it, so errors
early in a capture were erased by later good packets and the process still
exited 0. The keylog leg read $? after a pipe into tee, which reports tee's
status rather than snifftest's. Accumulate the flag, use PIPESTATUS, and
treat a saved capture that yields no plaintext at all as a failure.
The two static RSA captures decode correctly again and the keylog reference
output is regenerated from the checked-in pcaps, which had drifted by a
packet since the files were last produced on 5.6.3.
The X25519 legs are skipped: both captures are in the tree as a bare 24 byte
pcap header with no packets. sniffer-gen.sh could never produce them because
its configure line for those two builds without ed25519, so the example
client and server fail to load their certificates and tcpdump writes an empty
file. Add the missing option, and pin the group with --disable-mlkem so the
capture negotiates X25519 rather than the X25519MLKEM768 hybrid the static
key does not match. Regenerating them is left out of this change: the sniffer
cannot yet derive the shared secret for an X25519 static ephemeral key, which
is a separate defect.
The negative tests accepted any non-zero client status, and the openssl.test
capability and protocol probes read only a command's output. A run killed by
timeout(1) therefore looked like the certificate rejection, auth failure or
missing feature each site was testing for, and the script still reported
success with the coverage silently dropped.
Check for the timeout statuses (124, and 137 for the SIGKILL used here)
before interpreting a result:
- trusted_peer.test: the three wrong-CA / wrong-peer rejection cases
- tls13.test: cipher mismatch, mutual auth, and the version downgrade cases
- psk.test: the no-peer-cert rejection case
- openssl.test: the seven wolfSSL/OpenSSL certificate capability probes and
the SSLv3, TLS 1.0 and TLS 1.1 connection probes
openssl.test sets IFS=: around its cipher suite loops, so an unquoted
$TIMEOUT_KILL_2M inside them did not split back into separate words and
the shell looked for a command literally named "timeout -s KILL 2m".
Every wolfSSL client run in do_wolfssl_client() then died with "command
not found", which the script reported as a failing cipher suite.
Use an array, whose expansion does not depend on IFS, and keep a
flattened copy for the two eval call sites, where the shell re-parses the
string and IFS does not apply.
Three ways the bounds added here failed to do their job:
- get_first_free_port ended the scan cap with 'exit 1', but every caller
runs it in a command substitution, so only the subshell died. The port
variable came back empty, the next $((port + 1)) evaluated to 1, and the
run limped on to a confusing wait_for_readyFile failure. Forcing the cap
on ocsp-stapling.test: before, the script ran on and hung until an outer
timeout killed it; now it exits 1 at the error. Return instead, and check
the status at all 25 call sites across the six scripts.
- The macOS timeout shim was a shell function. Backgrounding a function
forks a subshell, so $! was the subshell and cleanup killed that while
the server it was meant to stop leaked. Use a prefix variable that
expands to nothing when timeout(1) is absent, keeping $! the real pid.
- timeout -s KILL exits 137, not 124. Sites that read $? and treat any
non-zero as 'feature not compiled in' turned a hang into exit 0, so the
bound made a hang less visible than before. Add timed_out() and check it
before those skip branches; use it for the version probes too, which
matched any status >= 124.
The 'Bad SSL version' probes branch on grep's status, so a client
killed by the 2m timeout read as 'TLS v1.2 supported' and ran the
wrong branch. Take both statuses from PIPESTATUS and fail loudly on
a probe timeout.
- tls13.test: take the client's exit status from PIPESTATUS[0]; $? after
the pipe was tee's status, hiding a client failure or 2m-timeout kill.
- ocsp-responder-openssl-interop.test: only escalate to SIGKILL if the
responder is still running, and make the reap counter local.
A test script that blocks forever burns the CI job's full
timeout-minutes with no logs. e82ecdff93 and 5c5cbd3094 bounded the
waited-on servers; this covers the remaining hang classes in
scripts/*.test:
- Wrap foreground example client/server, openssl s_client, and
openssl ocsp invocations in "timeout -s KILL 2m". A client wedged
before or without a live peer (e.g. blocked in first-seed entropy
gathering, or DTLS with no reset from a dead peer) is not bounded
by its peer's timeout.
- Add the macOS timeout() fallback shim to scripts that now use
timeout.
- Bound the get_first_free_port scan loops (nc -w 1, 100-port cap).
- Add -w 1 to the remaining nc probes and dtls.test UDP pcap markers.
- ocsp-responder-openssl-interop.test: bound the responder reap in
cleanup: give each responder 5 s to exit after SIGTERM, then
SIGKILL before waiting, so a wedged responder cannot hang the EXIT
trap.
- benchmark.test: bound the clients but leave the -i servers
unwrapped: the script ends them with kill -6, which timeout(1)
does not forward, so wrapping would orphan the server.
- trusted_peer/tls13: kill the server with SIGTERM instead of
SIGKILL in cleanup so the signal forwards through the timeout
wrapper to the wrapped server.
SendTls13Certificate keeps its chain walk cursor in the function locals len,
idx, offset and p, but the only state that survives the WANT_WRITE return of a
non-blocking send is ssl->fragOffset, and that is consulted for the leaf
certificate alone. A send that blocked part way through the chain therefore
re-primed the walk on the next call and copied the chain from its first byte
again. The byte count still matched the announced payload size, so the message
stayed well formed on the wire while the tail of the chain was replaced by a
repeat of its head, and the peer rejected it with BUFFER_ERROR.
Rebuild the cursor from ssl->fragOffset when a resume lands inside the chain.
NextCert reads each entry's three byte length prefix and skips it, so passing
over the entries already sent costs one hop per certificate and only happens on
a resume. Guard the extension index bump the same way the send loop does, so
builds without certificate status request keep the leaf extension size.
Track the size of the chain entry being written in its own variable rather than
folding the extension size into len once the entry completes. The send loop
detected completion with offset == len + OPAQUE16_LEN and kept that check
honest by adding extSz[extIdx] - OPAQUE16_LEN to len at the end of an entry, so
until then len held the raw certificate length and the check read as complete
whenever a fragment boundary landed exactly OPAQUE16_LEN bytes into a real
extension. The walk then jumped to the next certificate in the middle of the
current one. entrySz records len + extSz[extIdx] when the entry is picked up,
len keeps the raw certificate length AddCertExt expects, and both the resume
and the ordinary multi fragment path test the same condition.
The stapled chain in scripts/ocsp-stapling_tls13multi.test reproduces the entry
size case with the server records held to 1482 bytes: the boundary falls two
bytes into an 1837 byte OCSP extension and the handshake fails, while 1480,
1481, 1483 and 1484 all pass.
Reaching this needs a certificate message larger than one record, which is why
it stayed dormant with classic certificates. Add SLH-DSA scenarios with
simulated WANT_WRITE, for server and for mutual authentication, to
tests/test-tls13-slhdsa-entity-128s.conf.
The same resume path mishandles the stapled OCSP responses. WriteCSRToBuffer
fills extSz[] only for the entries whose buffer it allocates, so on a resumed
call every entry that still held a buffer, the one being written and all that
follow it, kept the OPAQUE16_LEN default of an empty extension. The message
length, the entry sizes and the extension bytes written for those entries were
all derived from that default. Recover the size from the extension length
already written into the buffer instead.
SetupOcspResp appends a fresh request per certificate on every call, so a
message that resumed often enough exhausted the extension array and the
handshake ended with MAX_CERT_EXTENSIONS_ERR. Look the responses up once, when
the message starts, and reuse them for the rest of it.
A resumed call also reallocates the extension buffers of the entries it has
already sent, and the walk passes over those entries without writing them
again, so free them there. Free the array in wolfSSL_ResourceFree as well:
nothing released it when a connection ended part way through a Certificate
message, which leaked one OCSP response per unsent entry.
Test case 8 of scripts/ocsp-stapling_tls13multi.test covers all three. A
maximum fragment length of 512 bytes splits the stapled message over about
twenty records and the server blocks on every one of them; without these fixes
the handshake fails with MAX_CERT_EXTENSIONS_ERR.
The scripts that wait for a server to publish its ready file declare
counter at file scope and never reset it, so the retry budget is shared
by every server start in the script instead of applying to each one.
Once the early cases have used it up, every later create_port() falls
straight through to "NO ready file ending test", kills a server that was
starting normally, and the client then fails with "port number cannot be
0". Retry loops do not help, since the budget is already spent when they
run.
The failure needs only a build whose server start-up is slow enough to
consume a few tenths of a second each time. It showed up in the FIPS
dev-no-POST kernel-settings-all-pqc-asm job, where the server pays for
the CASTs, the PQC algorithms and the vector-register fallback fuzzer:
psk.test gave up after exactly 20 waits and tls13.test after exactly 51,
both the full script budget rather than a per-case one.
Reset counter where the wait begins, which is what the ocsp-stapling
scripts already do. Reproduced with a wrapper that delays the server by
one second: psk.test then fails on its third case before the change and
passes after it.
wolfSSL removed liboqs: Falcon is now provided natively by wolfCrypt, and
--with-liboqs is a deprecated no-op (configure.ac). A build therefore no
longer links liboqs, so recording it as an SBOM dependency is dead code and
the SBOM integration CI (which asserted a liboqs dep package) failed.
Remove the liboqs dependency throughout:
- scripts/gen-sbom: drop DEP_META['liboqs'] and the --dep-liboqs flag.
- Makefile.am / configure.ac: drop --dep-liboqs "$(ENABLED_LIBOQS)" and the
now-unused AC_SUBST([ENABLED_LIBOQS]).
- .github/workflows/sbom.yml: drop the liboqs install / --with-liboqs steps
and the liboqs dep assertion; keep the native-Falcon build so the
HAVE_FALCON build-property capture is still exercised.
- scripts/test_gen_sbom.py: drop the liboqs-specific tests, guard against
the key reappearing, and use openssl as the example dep elsewhere.
- doc/SBOM.md: drop the --dep-liboqs / liboqs dependency references.
Make the canonical SBOM fragment a true superset of the vendored copies
so it can be re-vendored to every product without regressions.
Products whose feature macros live in config.h (via AC_DEFINE) keep the
existing behaviour: the recipe derives them from a CC -dM -E dump with
AM_CPPFLAGS/AM_CFLAGS/CFLAGS and a force-included SBOM_CONFIG_H.
Products whose feature flags are NOT in config.h (wolfMQTT, wolfTPM,
wolfscep, which record them in a generated options.h) can now set
SBOM_OPTIONS_H to point gen-sbom at that header directly. When unset the
compiler-dump path is used exactly as before.
This folds the options.h capture that had diverged into the wolfMQTT/
wolfTPM/wolfscep copies back into the single source of truth, while
retaining the SBOM_CONFIG_H override, AM_CFLAGS/CFLAGS capture and
$(docdir) install those copies were missing.
Signed-off-by: Sameeh Jubran <sameeh@wolfssl.com>
Use Automake's $(docdir) for sbomdir so a --docdir override is honoured,
match tab/space in the wolfSSL version parse ([[:space:]]), document the
GNU-make requirement and the intentional install/uninstall-sbom
asymmetry, and widen the SBOM workflow pull_request filter to '**' so PRs
onto release/** base branches also run.
Signed-off-by: Sameeh Jubran <sameeh@wolfssl.com>
Add the canonical scripts/sbom.am shared Automake SBOM recipe that
downstream wolfSSL-stack products vendor, and ship it via EXTRA_DIST.
Capture AM_CFLAGS/CFLAGS (not just AM_CPPFLAGS) and make the config
header path overridable (SBOM_CONFIG_H) so products that carry feature
-D flags in AM_CFLAGS or place config.h in a subdirectory record their
real build configuration.
Signed-off-by: Sameeh Jubran <sameeh@wolfssl.com>
- gen-advisory: honour explicit per-version status when defaultStatus is
"affected", so unaffected/fixed releases are no longer marked vulnerable
- gen-advisory: fail loudly when a CVE record has no non-empty English
description (CSAF/CycloneDX note text is required, minLength 1)
- gen-advisory: note that --cve-id fetches from the CVE Services API
- bomsh_verify: scope the object-store shape check to sha1, matching the
sha1 gitoid hashing (drop the unreachable sha256-length branch)
- Makefile.am: fail `make bomsh` early when python3/pyspdxtools are absent;
quote $(ENABLED_LIBZ)/$(ENABLED_LIBOQS); consolidate clean-local so the
omnibor/ and advisories/out/ build dirs are removed on clean
- tests: cover the defaultStatus fix, the _bucket_for unknown-state
hard-fail, and a csaf_validate.mjs runner self-test wired into CI
Signed-off-by: Sameeh Jubran <sameeh@wolfssl.com>
Add an openssl entry to DEP_META (Apache-2.0, OpenSSL 3.x git-tag purl)
and a --dep-openssl flag so OpenSSL-compat products (wolfProvider,
wolfEngine) can record OpenSSL as a dependency component alongside
wolfSSL. Update tests.
Signed-off-by: Sameeh Jubran <sameeh@wolfssl.com>
Recognize the "GPLv3"/"GPLv3+" short form so downstream LICENSING files
map to GPL-3.0-only instead of NOASSERTION. Add --dep-wolfssl, a wolfssl
DEP_META entry, and --name-derived project URLs. Update tests.
Signed-off-by: Sameeh Jubran <sameeh@wolfssl.com>
Add tooling to produce Software Bills of Materials and build provenance
for wolfSSL, supporting EU Cyber Resilience Act (CRA) obligations.
SBOM generation:
- New `make sbom` target producing SPDX 2.3 output with NTIA minimum
elements, urn:uuid document namespaces, and SPDX LicenseRef compliance.
- Reproducible library discovery across autotools and CMake builds, with
liboqs recorded as a linked artefact.
- Standalone `scripts/gen-sbom` for embedded / RTOS / custom-builder
flows that do not use the main build system, plus --srcs-file,
--no-artifact-hash, and hash-source options.
Build provenance (OmniBOR / bomsh):
- End-to-end bomsh tracing of the built binaries with ArtifactID
insertion, snapshotting the traced library before libtool relink and
hashing the bomsh-traced binary.
- `scripts/bomsh_verify.py` to validate provenance against the traced
gitoid.
Security advisories:
- `scripts/gen-advisory` generating CSAF 2.0 and CycloneDX VEX, with a
`make` target, VEX overlay schema/example, and CWE name data.
Docs, tests, and CI:
- doc/SBOM.md and doc/CRA.md, plus README/INSTALL updates.
- Unit and regression tests for gen-sbom and gen-advisory.
- New sbom.yml and advisory.yml workflows: SPDX validation via
pyspdxtools, CSAF validation, bomsh provenance verification, SBOM
artifact archiving, macOS coverage, and actions pinned to SHAs.
Signed-off-by: Sameeh Jubran <sameeh@wolfssl.com>
Scrub the temporary "FN-DSA" name and the "FIPS 206" designation from all
in-tree comments, build text, and message strings, leaving the algorithm
named only as "Falcon". The eventual standardized name is not announced.
The differential known-answer test message ("wolfSSL FN-DSA differential
KAT") is a signed input, so the Falcon-512/1024 public keys and signatures
in wolfcrypt/test/test.c (and the mirrored Falcon-512 vector in
IDE/m33mu-falcon-verify/kat.h) were regenerated with liboqs over the new
message "wolfSSL Falcon differential KAT", preserving the differential
property (liboqs-produced signatures verified by the native verifier).
Verified: testwolfcrypt Falcon test passes; the m33mu verify-only harness
passes (BKPT 0x7f) with the regenerated vector.
Add a complete native Falcon post-quantum lattice signature implementation to
wolfCrypt, replacing the liboqs wrapper. Full key generation, signing and
verification for Falcon-512 (level 1) and Falcon-1024 (level 5).
- Public API wc_falcon_* / falcon_key in falcon.c wraps the native core
(falcon_native_* in wc_falcon.c) plus wc_falcon_{fpr,fft,poly,sampler,
codec,keygen,sign,bigint}.c. No liboqs dependency.
- Portable, constant-time integer-emulated floating-point (fpr) backend is
the default; opt-in per-architecture acceleration:
--enable-falcon-double inline native double
--enable-falcon-asm x86-64 SSE2 out-of-line fpr asm
--enable-falcon-avx2 x86-64 AVX2 (4-wide) FFT
- Division-free (Barrett) integer NTT on the verify path, so no hardware
divide is required on Cortex-M / embedded targets.
- Verify uses a cached twiddle-factor NTT; signing uses the FFT / ffLDL tree
and discrete Gaussian sampler over the abstract fpr seam.
- test.c falcon_test (KAT verify + native keygen/sign/verify roundtrip);
scripts/falcon-interop.c and a CI workflow cross-check native<->liboqs in
both directions.
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.