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

416 Commits

Author SHA1 Message Date
Tobias Frauenschläger
a796813bd0 Falcon: add a script that checks the fpr emulation for branches
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.
2026-10-14 09:15:37 +02:00
Paul Adelsbach
f5dd686105 CI: fix MacOS port issue in openssl.test 2026-10-05 22:20:56 -07:00
Paul Adelsbach
b34f33a5c8 CI: increase timeouts in workflow scripts 2026-10-05 09:48:59 -07:00
Paul Adelsbach
295044f584 CI: stop wrapped test servers with TERM, not KILL 2026-10-05 09:48:59 -07:00
Tobias Frauenschlaeger
eb16c9225f scripts: teach the OID sum generator about guarded entries
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.
2026-09-29 17:25:51 +02:00
Tobias Frauenschlaeger
01f588a2ff ML-KEM: add key OIDs and ASN.1 key encoding
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.
2026-09-29 17:25:51 +02:00
Tobias Frauenschläger
b166be9a77 Address review comments
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.
2026-09-29 15:58:27 +02:00
Tobias Frauenschläger
753952e0dd Falcon: add a smallest memory signer
--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.
2026-09-29 15:58:27 +02:00
Tobias Frauenschläger
cc49fc0ec9 Make multi-msg-record independent of the peer's session ticket support (#11551)
* 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.
2026-09-28 14:03:15 -07:00
Daniel Pouzzner
d5af5eb34f src/internal.c:
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.
2026-09-20 23:35:04 -05:00
Tobias Frauenschläger
f0b78fb9ba Complete a TLS 1.3 handshake with the ECC PK callbacks
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.
2026-09-08 18:56:18 +02:00
Sean Parkinson
90f723fea4 Merge pull request #11361 from JacobBarthelmeh/extkeyuse
add ssh server extended key usage parsing and missing ssh extended ke…
2026-09-08 09:03:56 +10:00
Tobias Frauenschläger
38fba1147f Send SNI in external.test so the peer returns its own certificate
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.
2026-09-04 15:02:39 +02:00
Sean Parkinson
e36beda7bf Regression testing fixes
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.
2026-09-03 20:59:54 +10:00
JacobBarthelmeh
c3ac2f6da4 add ssh server extended key usage parsing and missing ssh extended key parsing to asn template from asn orig 2026-09-02 23:03:51 -06:00
Tobias Frauenschläger
1a303870c5 Fix threaded snifftest dropping every queued packet
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.
2026-09-01 09:33:17 +02:00
Tobias Frauenschlaeger
d065b14508 Give the sniffer's X25519 static ephemeral key an RNG
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.
2026-09-01 09:33:17 +02:00
Tobias Frauenschlaeger
960551cee0 Address review findings on the sniffer Encrypt-Then-MAC fix
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.
2026-09-01 09:33:17 +02:00
Tobias Frauenschlaeger
6a35183ab6 Fix sniffer Encrypt-Then-MAC support and reported plaintext length
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.
2026-09-01 09:33:17 +02:00
Juliusz Sosinowicz
7968867def Address review: tell a timeout kill apart from an expected failure
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
2026-08-17 03:35:10 +00:00
Juliusz Sosinowicz
3a0da7013e Fix CI: expand the openssl.test timeout prefix under IFS=:
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.
2026-08-14 13:50:06 +00:00
Juliusz Sosinowicz
1fa1228ba1 Address review: make the hang guards actually fire
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.
2026-08-13 15:52:17 +00:00
Juliusz Sosinowicz
44b1341119 Detect timeout separately in version-probe pipes
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.
2026-08-13 15:52:17 +00:00
Juliusz Sosinowicz
d46d95ffe0 Address review feedback
- 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.
2026-08-13 15:52:17 +00:00
Juliusz Sosinowicz
fb3c892931 Bound remaining hangable operations in test scripts
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.
2026-08-13 15:52:17 +00:00
JacobBarthelmeh
8f4fcf5dad Merge pull request #10720 from MarkAtwood/fix/license-metadata-gplv3
fix: update license identifiers and copyright years to GPL-3.0+
2026-08-11 10:55:30 -06:00
David Garske
14a72c96e4 wolfCrypt SRAM PUF: configurable error correction and footprint 2026-08-09 09:04:51 +02:00
Mark Atwood
ec0ec04531 fix: use SPDX GPL-3.0-or-later license identifiers in metadata 2026-08-07 13:34:41 -07:00
David Garske
586f1ed490 Merge pull request #11096 from Frauschi/pqc_only
Support post-quantum-only (ML-KEM + ML-DSA/SLH-DSA) TLS 1.3 builds
2026-08-07 10:08:34 -07:00
Tobias Frauenschläger
16cc1f28bb Resume the TLS 1.3 certificate chain send after a WANT_WRITE
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.
2026-08-07 08:14:25 +02:00
Eric Blankenhorn
42c234ecd2 Parse x500UniqueIdentifier (OID 2.5.4.45) in certificate DN 2026-08-06 11:05:30 -05:00
Tobias Frauenschläger
1f36572941 Reset the ready-file wait counter on each server start
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.
2026-08-06 10:52:58 +02:00
Sean Parkinson
495296a739 Merge pull request #10625 from julek-wolfssl/client-custom-ext
Add SSL_CTX_add_client_custom_ext (OpenSSL-compat client custom extensions)
2026-07-28 09:22:47 +10:00
Mark Atwood
be9963f392 sbom: drop liboqs dependency support (removed from wolfSSL)
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.
2026-07-23 22:32:50 -07:00
Sameeh Jubran
c6a336f75f scripts/sbom.am: add SBOM_OPTIONS_H override to unify the fragment
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>
2026-07-23 21:51:26 -07:00
Sameeh Jubran
136bfa97ab fix spelling
Signed-off-by: Sameeh Jubran <sameeh@wolfssl.com>
2026-07-23 21:51:26 -07:00
Sameeh Jubran
d08282ff9a sbom: use docdir, portable version parse, and widen CI filter
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>
2026-07-23 21:51:26 -07:00
Sameeh Jubran
4c0f6acb66 scripts: add canonical shared sbom.am fragment
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>
2026-07-23 21:51:26 -07:00
Sameeh Jubran
5ab0ad9fab sbom: address advisory/SBOM review findings
- 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>
2026-07-23 21:51:26 -07:00
Sameeh Jubran
2368fd0eac sbom: add --dep-openssl for OpenSSL-compat products
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>
2026-07-23 21:51:26 -07:00
Sameeh Jubran
64b4e441d0 gen-sbom: detect GPLv3 abbreviation and support downstream products
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>
2026-07-23 21:51:26 -07:00
Sameeh Jubran
4ec80d309a feat: SBOM generation and OmniBOR build provenance (CRA compliance)
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>
2026-07-23 21:51:26 -07:00
Daniele Lacamera
effc05a171 Falcon: remove FN-DSA / FIPS 206 references from code comments and text
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.
2026-07-22 09:52:26 +02:00
Daniele Lacamera
20a838aac3 Falcon: native wolfCrypt implementation (no liboqs)
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.
2026-07-22 09:52:26 +02:00
Sean Parkinson
a048395345 Merge pull request #10941 from Frauschi/rfc_compliance
Compliance to new RFCs
2026-07-21 16:19:33 +10:00
Sean Parkinson
78df69d051 FrodoKEM: ASN.1 keys, X.509 certicates
Added support for encoding and decoding keys in ASN.1.
Added support for X.509 certificates and CSRs.
Generated certificates and CSRs. Not fo FrodoKEM-640 as is not in the specs.
2026-07-17 22:38:55 +10:00
Tobias Frauenschläger
34a2a2f389 Parse sniffer keylog file line by line for RFC 9850
The keylog reader used fscanf with three whitespace delimited tokens,
which is not line aware. RFC 9850 Section 1 requires readers to ignore
empty lines and lines whose first character is '#', and Section 2
recommends ignoring lines that do not conform to the format so secrets
can still be recovered from corrupted files. A comment line such as
"# note" was parsed as three fields spanning the line boundary, which
desynchronized the rest of the file.

Read one line at a time with fgets, skip empty and comment lines, and
parse each line with sscanf, skipping any line that does not yield the
three expected fields instead of aborting. Clear the field buffers each
iteration so a short field cannot pick up stale bytes from a previous
line, validate that the fixed length client random field is the exact
expected length, and leave the secret length variable because it depends
on the negotiated hash. Zero the new line buffer on every return path,
matching the existing ForceZero handling of the secret buffers.

Add a comment line, a blank line, and a malformed line to the TLS 1.3
keylog test data so the sniffer keylog test exercises the skip paths.
2026-07-17 11:04:24 +02:00
Juliusz Sosinowicz
18aef5a43b google.test: skip when the TCP connection cannot be opened
PRB nodes intermittently fail google.test with 'tcp connect failed:
Connection timed out' while www.google.com still answers ping and
www.wolfssl.com:443 (external.test) connects fine: Google drops or
throttles TCP connections from busy CI egress IPs, so the existing
ping reachability guard does not catch it.

Failing to even open the TCP connection exercises no wolfSSL code, so
treat it like the unreachable-server case and skip (77) instead of
failing. TLS-level failures still fail the test.
2026-07-08 10:31:32 -07:00
Tobias Frauenschläger
673d8d00bb Merge pull request #10778 from SparkiDev/time_stamp_protocol
Time-Stamp Protocol (RFC 3161)
2026-07-08 17:43:38 +02:00
Sean Parkinson
ae023a5643 Time-Stamp Protocol (RFC 3161)
Implementation in wolfCrypt
OpenSSL compatibility layer in wolfSSL
Added tests, certificates, examples.
2026-07-08 09:33:47 +10:00