Skip to content

Fix sniffer AES-CCM and async decryption in TLS 1.2 - #11906

Open
Frauschi wants to merge 2 commits into
wolfSSL:masterfrom
Frauschi:fix-sniffer-aesccm-tag
Open

Frauschi wants to merge 2 commits into
wolfSSL:masterfrom
Frauschi:fix-sniffer-aesccm-tag

Conversation

@Frauschi

Copy link
Copy Markdown
Member

Description

Fixes two TLS 1.2 decrypt bugs in the sniffer, and makes the Linux GitHub CI sniffer entries actually decode captures. Found through the Jenkins nightly-sniffer-test-v2 job, whose AES-CCM-8 captures fail on master.

  • AES-CCM: DecryptDo() passed the 13-byte decrypt.additional buffer as the tag output of the encrypt call it decrypts with. wc_AesCcmCheckTagSize() rejects 13, so every TLS 1.2 AES-CCM record failed (broken since 2ee6a01). The tag now goes into the output buffer after the plaintext with the suite's tag size, and is wiped with the nonce.
  • Short AEAD records: the explicit IV is copied into the nonce only after the record length check, so a record shorter than 8 bytes is no longer read past its end.
  • Async: after an async record, DecryptMessage() popped the event itself and left decrypt.state at END, so the next record failed with VERIFY_MAC_ERROR. It now calls the decrypt function again after the poll (new DecryptRecord() helper), as the TLS layer does.
  • CI: no Linux GitHub job installed libpcap, so snifftest was never built and the os-check sniffer entries skipped every capture. os-check and async now install libpcap-dev, which is also added to the ubuntu-24.04-minimal bundle list.

Testing

  • New scripts/sniffer-tls12-ccm.pcap (static RSA, AES128/256-CCM-8, from the new tls12-ccm mode in sniffer-gen.sh). Its sniffer-testsuite leg compares the decrypted app data with sniffer-tls12-ccm.out, as the sniffer does not check the tag, and is gated on the new aes_ccm entry in snifftest's banner.
  • --enable-aesccm added to the sniffer-curves-enckeys os-check entry.
  • New asynccrypt-sw-sniffer entry in async.yml with WC_ASYNC_THRESH_NONE (capture records go async) and WOLFSSL_SNIFFER_KEYLOGFILE (runs the TLS 1.2 AES-GCM keylog capture). It runs only the sniffer suite and fails if snifftest was not built.
  • The five Linux os-check sniffer entries now decode captures in CI for the first time.

DecryptDo() decrypts AEAD records with the encrypt function so the tag is
not checked, and passed the 13 byte decrypt.additional buffer as the tag
output. GCM accepts a 13 byte tag, but wc_AesCcmCheckTagSize() rejects it,
so every TLS 1.2 AES-CCM record failed with a decrypt error. This has been
broken since 2ee6a01 swapped aead_mac_size for AEAD_AUTH_DATA_SZ to stop
a 16 byte GCM tag overflowing that buffer.

Write the tag into the output buffer after the plaintext, using the suite's
tag size. The caller already counts that space as tag (padSz), and it stays
valid until an async operation completes, which a stack buffer would not.
Wipe it together with the nonce once the record is done. Also copy the
explicit IV into the nonce only after checking the record is long enough,
so a record shorter than 8 bytes is no longer read past its end.

Add a static RSA AES-CCM-8 capture to sniffer-gen.sh and the sniffer test
suite. The sniffer does not check the tag, so the test compares the
plaintext against known good output. Have snifftest report aes_ccm support,
and enable AES-CCM in the sniffer-curves-enckeys os-check entry.

The Linux os-check job never installed libpcap, so snifftest was not built
and none of its sniffer entries ran a capture. Install libpcap-dev there and
add it to the ubuntu-24.04-minimal bundle list.
When a record decrypt returns WC_PENDING_E, DecryptMessage() polls and pops
the event itself instead of calling the decrypt function again. That left
ssl->decrypt.state at CIPHER_STATE_END, so for the next record DecryptTls()
found no pending event, skipped DecryptDo() and returned VERIFY_MAC_ERROR.
With the default WC_ASYNC_THRESH_AES_GCM of 128 bytes, any record that size
or larger broke the one after it. The end state's work, wiping the nonce
and the unused AEAD tag and mapping a failure to VERIFY_MAC_ERROR, never
ran for the async record either.

Once the poll completes, call the decrypt function again, as the TLS layer
does, so it pops the event and, for TLS 1.2, its end state does that work.
The version dispatch moves into DecryptRecord() for that.

Add an async sniffer entry to the async workflow, with WC_ASYNC_THRESH_NONE
so the short records in the captures go async, and with
WOLFSSL_SNIFFER_KEYLOGFILE so the TLS 1.2 AES-GCM keylog capture runs. It
is the only TLS 1.2 AEAD capture that goes async, as AES-CCM has no async
path. The entry runs only the sniffer suite, since the API tests' 10-round
memio handshakes do not complete when every AES operation is async. Install
libpcap-dev in that job as well. Without it snifftest is not built and the
suite skips every capture, so the entry also checks that snifftest exists.
@Frauschi Frauschi self-assigned this Oct 27, 2026
Copilot AI balanced review requested due to automatic review settings October 27, 2026 11:57

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Cryptographic buffer and asynchronous state changes need human confirmation of synchronous and asynchronous capture-test results.

No open issues 🎉

What changed in this PR

Fixes TLS 1.2 AES-CCM and asynchronous record decryption in the wolfSSL sniffer, and enables Linux CI jobs to exercise capture decoding.

Changes:

  • Corrects AEAD tag-buffer handling, checks record lengths before copying explicit IVs, and resumes decryption after asynchronous completion.
  • Adds AES-CCM capture generation and expected-output regression checks.
  • Installs libpcap in Linux CI and adds dedicated asynchronous sniffer coverage.
File Description
sslSniffer/​sslSnifferTest/​snifftest.c Reports AES-CCM support for test gating.
src/​sniffer.c Fixes AEAD buffer handling and asynchronous decryption completion.
scripts/​sniffer-tls12-ccm.out Adds expected decrypted application data.
scripts/​sniffer-testsuite.test Adds capability-gated AES-CCM output checks.
scripts/​sniffer-gen.sh Adds TLS 1.2 AES-CCM capture generation.
scripts/​include.am Includes CCM regression fixtures in distributions.
.github/​workflows/​os-check.yml Installs libpcap for sniffer tests.
.github/​workflows/​async.yml Installs libpcap and adds an asynchronous sniffer test entry.
.github/​configs/​os-check-linux.json Enables AES-CCM in a sniffer configuration.
.github/​ci-deps/​packages-ubuntu-24.04-minimal.txt Adds libpcap to the dependency bundle.

Review effort: Balanced

@Frauschi Frauschi assigned wolfSSL-Bot and unassigned Frauschi Oct 27, 2026
@Frauschi

Copy link
Copy Markdown
Member Author

@wolfSSL-Fenrir-bot review balanced

@Frauschi

Copy link
Copy Markdown
Member Author

@claude review

@wolfSSL-Fenrir-bot wolfSSL-Fenrir-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fenrir Automated Review — PR #11906

Scan targets checked: wolfssl-src, wolfssl-bugs
Not reviewed (no scan target covers them): .github/ci-deps/packages-ubuntu-24.04-minimal.txt, .github/configs/os-check-linux.json, .github/workflows/async.yml, .github/workflows/os-check.yml, scripts/include.am, scripts/sniffer-gen.sh, scripts/sniffer-testsuite.test, scripts/sniffer-tls12-ccm.out, scripts/sniffer-tls12-ccm.pcap, sslSniffer/sslSnifferTest/snifftest.c
Coverage: 1 of 1 in-scope changed file(s) opened by the reviewer

Fenrir result: Approved ✅

No new issues found in the changed files.

Advisory only — this automated result does not count as a GitHub approval.

Review tier: Balanced

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reviewed this PR and didn't find any bugs. It changes the sniffer's TLS record decryption and the async decrypt state handling, so a human should still look at it.

What was reviewed:

  • DecryptDo: the computed tag is written to plain + plainSz. That stays inside the sz - 8 bytes of output, because the length check now runs before the explicit IV is copied into the nonce.
  • DecryptTls: the tag area is only ForceZero'd in CIPHER_STATE_END, so an operation that returns WC_PENDING_E doesn't have its nonce or tag wiped while it is still in flight.
  • DecryptMessage: the new poll and re-call loop through DecryptRecord replaces the single wolfSSL_AsyncPop. DecryptRecord handles unused parameters under WOLFSSL_NO_TLS12.
Extended reasoning...

The change touches src/sniffer.c's AEAD (AES-GCM/CCM) decrypt path and its async-crypt retry loop, plus test captures, the snifftest banner and CI workflow and config updates. It is crypto-adjacent code that handles decrypted record buffers, nonce and tag zeroing, and the async state machine. That makes it security-sensitive even though the sniffer intentionally skips tag verification. No bugs were found, but the crypto surface and the async control-flow change are why this defers to a human instead of approving.

@philljj philljj self-assigned this Oct 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants