From 4333f4f34f3080e21f01783e696642714a18940f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Mon, 26 Oct 2026 10:23:08 +0100 Subject: [PATCH 1/2] Fix sniffer AES-CCM decryption in TLS 1.2 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 2ee6a01d91 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. --- .../ci-deps/packages-ubuntu-24.04-minimal.txt | 1 + .github/configs/os-check-linux.json | 2 +- .github/workflows/os-check.yml | 2 +- scripts/include.am | 2 ++ scripts/sniffer-gen.sh | 6 +++- scripts/sniffer-testsuite.test | 32 ++++++++++++++++++ scripts/sniffer-tls12-ccm.out | 4 +++ scripts/sniffer-tls12-ccm.pcap | Bin 0 -> 15448 bytes src/sniffer.c | 28 ++++++++------- sslSniffer/sslSnifferTest/snifftest.c | 3 ++ 10 files changed, 64 insertions(+), 16 deletions(-) create mode 100644 scripts/sniffer-tls12-ccm.out create mode 100644 scripts/sniffer-tls12-ccm.pcap diff --git a/.github/ci-deps/packages-ubuntu-24.04-minimal.txt b/.github/ci-deps/packages-ubuntu-24.04-minimal.txt index 2cb773f2715..8f259694fa7 100644 --- a/.github/ci-deps/packages-ubuntu-24.04-minimal.txt +++ b/.github/ci-deps/packages-ubuntu-24.04-minimal.txt @@ -17,5 +17,6 @@ gcc-11 gcc-12 gcc-9 gcc-multilib +libpcap-dev libtool zlib1g-dev diff --git a/.github/configs/os-check-linux.json b/.github/configs/os-check-linux.json index 90bc8cffd45..898d4eb860f 100644 --- a/.github/configs/os-check-linux.json +++ b/.github/configs/os-check-linux.json @@ -174,7 +174,7 @@ "--enable-cryptocbutils", "CPPFLAGS=-DWC_SHE_SW_DEFAULT"]}, {"name": "sniffer-curves-enckeys", "minutes": 2.2, "configure": ["--enable-sniffer", "--enable-curve25519", "--enable-curve448", - "--enable-enckeys", "CPPFLAGS=-DWOLFSSL_DH_EXTRA"]}, + "--enable-enckeys", "--enable-aesccm", "CPPFLAGS=-DWOLFSSL_DH_EXTRA"]}, {"name": "sniffer-keylog-storedata", "minutes": 2.5, "comment": "The sniffer keylog legs of scripts/sniffer-testsuite.test only run with WOLFSSL_SNIFFER_KEYLOGFILE, and the store data callback path is only compiled with WOLFSSL_SNIFFER_STORE_DATA_CB. Neither is set by any other entry.", "configure": ["--enable-sniffer", "--enable-session-ticket", diff --git a/.github/workflows/os-check.yml b/.github/workflows/os-check.yml index 0061c3b56da..1c132f3a3b2 100644 --- a/.github/workflows/os-check.yml +++ b/.github/workflows/os-check.yml @@ -184,7 +184,7 @@ jobs: if: ${{ env.SEED_DECLINED != 'true' }} uses: ./.github/actions/install-apt-deps with: - packages: autoconf automake libtool build-essential bubblewrap ccache gcc-multilib zlib1g-dev + packages: autoconf automake libtool build-essential bubblewrap ccache gcc-multilib libpcap-dev zlib1g-dev ghcr-debs-tag: ubuntu-24.04-minimal # Ubuntu 24.04 can restrict unprivileged user namespaces via AppArmor, diff --git a/scripts/include.am b/scripts/include.am index e732d228bf6..acbe4646562 100644 --- a/scripts/include.am +++ b/scripts/include.am @@ -128,6 +128,8 @@ dist_noinst_SCRIPTS+= scripts/tsp.test EXTRA_DIST += scripts/sniffer-static-rsa.pcap \ scripts/sniffer-ipv6.pcap \ + scripts/sniffer-tls12-ccm.pcap \ + scripts/sniffer-tls12-ccm.out \ scripts/sniffer-tls12-etm.pcap \ scripts/sniffer-tls12-etm.out \ scripts/sniffer-tls12-etm-keylog.pcap \ diff --git a/scripts/sniffer-gen.sh b/scripts/sniffer-gen.sh index 06819a560cd..928df8545dc 100755 --- a/scripts/sniffer-gen.sh +++ b/scripts/sniffer-gen.sh @@ -103,6 +103,9 @@ run_sequence() { run_test "ECDHE-RSA-AES256-SHA384" "-v 3" "-v 3" elif [ "$1" == "static-rsa" ] || [ "$1" == "ipv6" ]; then # TLS v1.2 static RSA run_test "AES128-SHA" "-v 3" "-v 3" + elif [ "$1" == "tls12-ccm" ]; then # TLS v1.2 static RSA AES-CCM + run_test "AES128-CCM-8" "-v 3" "-v 3" + run_test "AES256-CCM-8" "-v 3" "-v 3" elif [ "$1" == "tls13-hrr" ]; then # TLS v1.3 Hello Retry Request run_test "" "-v 4 -g" "-v 4 -J" else @@ -158,7 +161,7 @@ run_snifftest() { local args=() case "$1" in - tls12-etm) + tls12-etm|tls12-ccm) args=(-key ./certs/server-key.pem) ;; tls12-keylog|tls13-keylog|tls12-etm-keylog) args=(-keylogfile "./scripts/sniffer-${1}.sslkeylog") ;; @@ -192,6 +195,7 @@ run_snifftest() { run_capture "static-rsa" "--enable-enc-then-mac=no" run_capture "ipv6" "--enable-enc-then-mac=no --enable-ipv6" +run_capture "tls12-ccm" "--enable-aesccm" run_capture "tls12" "" run_capture "tls12-keylog" "--enable-enc-then-mac=no --enable-keylog-export CFLAGS='-Wno-cpp -DWOLFSSL_SNIFFER_KEYLOGFILE'" run_capture "tls13-keylog" "--enable-keylog-export CFLAGS='-Wno-cpp -DWOLFSSL_SNIFFER_KEYLOGFILE'" diff --git a/scripts/sniffer-testsuite.test b/scripts/sniffer-testsuite.test index 7cafdc8e9a2..ccd78317834 100755 --- a/scripts/sniffer-testsuite.test +++ b/scripts/sniffer-testsuite.test @@ -59,6 +59,11 @@ has_static_rsa=no if [ $? -eq 0 ]; then has_static_rsa=yes fi +has_aesccm=no +./sslSniffer/sslSnifferTest/snifftest -? 2>&1 | grep -- 'aes_ccm ' +if [ $? -eq 0 ]; then + has_aesccm=yes +fi # sniffer-tls12-etm.pcap negotiated Encrypt-Then-MAC, so a sniffer built # without it cannot decrypt that capture. Every other capture is readable by # any build. @@ -114,6 +119,33 @@ then [ $RESULT -ne 0 ] && echo -e "\nsnifftest static RSA failed\n" && exit 1 fi +# TLS v1.2 Static RSA AES-CCM Test +if test $RESULT -eq 0 && test $has_rsa == yes && test $has_tlsv12 == yes && test $has_static_rsa == yes && test $has_aesccm == yes +then + echo -e "\nStarting snifftest on sniffer-tls12-ccm.pcap...\n" + + TMPFILE=$(mktemp) + RESULT=$? + [ $RESULT -ne 0 ] && echo -e "\nsnifftest TLS v1.2 AES-CCM failed: unable to create tmpfile\n" && exit 1 + + ./sslSniffer/sslSnifferTest/snifftest -expectdata \ + -pcap ./scripts/sniffer-tls12-ccm.pcap \ + -key ./certs/server-key.pem \ + -server 127.0.0.1 -port 11111 | tee $TMPFILE + + RESULT=${PIPESTATUS[0]} + [ $RESULT -ne 0 ] && echo -e "\nsnifftest TLS v1.2 AES-CCM failed: snifftest returned $RESULT\n" && rm $TMPFILE && exit 1 + + SEARCH_STRING="SSL App Data" + grep "$SEARCH_STRING" $TMPFILE | sort \ + | diff - <(grep "$SEARCH_STRING" scripts/sniffer-tls12-ccm.out | sort) + + RESULT=$? + [ $RESULT -ne 0 ] && echo -e "\nsnifftest TLS v1.2 AES-CCM failed: snifftest diff returned $RESULT\n" && rm $TMPFILE && exit 1 + + rm $TMPFILE +fi + # A Certificate message can span many records in one TCP packet. Keep the # later TCP sequence numbers consistent with the original capture. if test $RESULT -eq 0 && test $has_rsa == yes && test $has_tlsv12 == yes && \ diff --git a/scripts/sniffer-tls12-ccm.out b/scripts/sniffer-tls12-ccm.out new file mode 100644 index 00000000000..15f5265cf6d --- /dev/null +++ b/scripts/sniffer-tls12-ccm.out @@ -0,0 +1,4 @@ +SSL App Data(29:14):hello wolfssl! +SSL App Data(31:22):I hear you fa shizzle! +SSL App Data(68:14):hello wolfssl! +SSL App Data(70:22):I hear you fa shizzle! diff --git a/scripts/sniffer-tls12-ccm.pcap b/scripts/sniffer-tls12-ccm.pcap new file mode 100644 index 0000000000000000000000000000000000000000..41150d89b7f6d2acaca8cae512399d715de69bfb GIT binary patch literal 15448 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.additional, 0, AEAD_AUTH_DATA_SZ); - - XMEMCPY(ssl->decrypt.nonce, ssl->keys.aead_dec_imp_IV, AESGCM_IMP_IV_SZ); - XMEMCPY(ssl->decrypt.nonce + AESGCM_IMP_IV_SZ, input, AESGCM_EXP_IV_SZ); - if (sz < AESGCM_EXP_IV_SZ + ssl->specs.aead_mac_size) { ret = BUFFER_ERROR; } if (ret == 0) { + XMEMCPY(ssl->decrypt.nonce, ssl->keys.aead_dec_imp_IV, + AESGCM_IMP_IV_SZ); + XMEMCPY(ssl->decrypt.nonce + AESGCM_IMP_IV_SZ, input, + AESGCM_EXP_IV_SZ); + plainSz = (word16)(sz - AESGCM_EXP_IV_SZ - + ssl->specs.aead_mac_size); + /* The unused tag goes in the tag space after the plaintext. */ ret = aes_auth_fn(ssl->decrypt.aes, plain, input + AESGCM_EXP_IV_SZ, - sz - AESGCM_EXP_IV_SZ - ssl->specs.aead_mac_size, + plainSz, ssl->decrypt.nonce, AESGCM_NONCE_SZ, - ssl->decrypt.additional, AEAD_AUTH_DATA_SZ, + plain + plainSz, ssl->specs.aead_mac_size, NULL, 0); if (ret < 0) { #ifdef WOLFSSL_ASYNC_CRYPT @@ -5410,15 +5413,10 @@ static int DecryptTls(WOLFSSL* ssl, byte* plain, const byte* input, /* free for these happens in FreeCiphers */ if (ssl->specs.bulk_cipher_algorithm == wolfssl_aes_ccm || ssl->specs.bulk_cipher_algorithm == wolfssl_aes_gcm) { - /* make sure auth iv and auth are allocated */ - if (ssl->decrypt.additional == NULL) - ssl->decrypt.additional = (byte*)XMALLOC(AEAD_AUTH_DATA_SZ, - ssl->heap, DYNAMIC_TYPE_AES_BUFFER); if (ssl->decrypt.nonce == NULL) ssl->decrypt.nonce = (byte*)XMALLOC(AESGCM_NONCE_SZ, ssl->heap, DYNAMIC_TYPE_AES_BUFFER); - if (ssl->decrypt.additional == NULL || - ssl->decrypt.nonce == NULL) { + if (ssl->decrypt.nonce == NULL) { return MEMORY_E; } } @@ -5451,6 +5449,10 @@ static int DecryptTls(WOLFSSL* ssl, byte* plain, const byte* input, ssl->specs.bulk_cipher_algorithm == wolfssl_aes_gcm) { if (ssl->decrypt.nonce) ForceZero(ssl->decrypt.nonce, AESGCM_NONCE_SZ); + if (sz >= AESGCM_EXP_IV_SZ + ssl->specs.aead_mac_size) { + ForceZero(plain + sz - AESGCM_EXP_IV_SZ - + ssl->specs.aead_mac_size, ssl->specs.aead_mac_size); + } if (ret < 0) ret = VERIFY_MAC_ERROR; diff --git a/sslSniffer/sslSnifferTest/snifftest.c b/sslSniffer/sslSnifferTest/snifftest.c index b97d9d01a71..6a63a2dbe97 100644 --- a/sslSniffer/sslSnifferTest/snifftest.c +++ b/sslSniffer/sslSnifferTest/snifftest.c @@ -492,6 +492,9 @@ static void show_appinfo(void) #ifdef WOLFSSL_STATIC_DH "dh_static " #endif + #ifdef HAVE_AESCCM + "aes_ccm " + #endif #ifdef WOLFSSL_SNIFFER_KEYLOGFILE "ssl_keylog_file " #endif /* WOLFSSL_SNIFFER_KEYLOGFILE */ From 36bd423939533ecb9c3c55a9416b41ce2d584611 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Mon, 26 Oct 2026 13:59:47 +0100 Subject: [PATCH 2/2] Fix sniffer decryption after an async record 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. --- .github/workflows/async.yml | 10 +++++++- src/sniffer.c | 50 +++++++++++++++++++------------------ 2 files changed, 35 insertions(+), 25 deletions(-) diff --git a/.github/workflows/async.yml b/.github/workflows/async.yml index 69865b7c2fb..efaa7d46310 100644 --- a/.github/workflows/async.yml +++ b/.github/workflows/async.yml @@ -42,7 +42,7 @@ jobs: - name: Install dependencies uses: ./.github/actions/install-apt-deps with: - packages: autoconf automake libtool build-essential bubblewrap + packages: autoconf automake libtool build-essential bubblewrap libpcap-dev ghcr-debs-tag: ubuntu-24.04-minimal # ccache via the cross-platform composite; the script passes the @@ -92,6 +92,14 @@ jobs: "configure": ["--enable-asynccrypt-sw", "--enable-ocspstapling", "--enable-ocspstapling2", "CFLAGS=-pedantic -Wdeclaration-after-statement -Wnull-dereference -DTEST_LIBWOLFSSL_SOURCES_INCLUSION_SEQUENCE"]}, + {"comment": "The API tests do not finish with WC_ASYNC_THRESH_NONE, so only the sniffer suite runs.", + "name": "asynccrypt-sw-sniffer", + "configure": ["--enable-asynccrypt-sw", "--enable-sniffer", + "--enable-aesccm", "--enable-session-ticket", + "CFLAGS=-pedantic -Wdeclaration-after-statement -Wnull-dereference -DTEST_LIBWOLFSSL_SOURCES_INCLUSION_SEQUENCE -DWC_ASYNC_THRESH_NONE -DWOLFSSL_SNIFFER_KEYLOGFILE"], + "check": false, + "run": [["test", "-x", "sslSniffer/sslSnifferTest/snifftest"], + ["../scripts/sniffer-testsuite.test"]]}, {"name": "ocsp-nonblock-certs", "minutes": 1.5, "configure": ["--enable-ocsp", "CFLAGS=-DTEST_NONBLOCK_CERTS -pedantic -Wdeclaration-after-statement -Wnull-dereference -DTEST_LIBWOLFSSL_SOURCES_INCLUSION_SEQUENCE"]} diff --git a/src/sniffer.c b/src/sniffer.c index 6d2e25c3621..61c86b6ad5b 100644 --- a/src/sniffer.c +++ b/src/sniffer.c @@ -5472,6 +5472,28 @@ static int DecryptTls(WOLFSSL* ssl, byte* plain, const byte* input, } #endif /* !WOLFSSL_NO_TLS12 */ +static int DecryptRecord(WOLFSSL* ssl, const byte* input, word32 sz, + byte* output, RecordLayerHeader* rh) +{ +#ifdef WOLFSSL_TLS13 + if (IsAtLeastTLSv1_3(ssl->version)) { + if (sz <= ssl->specs.aead_mac_size) + return BUFFER_ERROR; + return DecryptTls13(ssl, output, input, sz, (byte*)rh, + RECORD_HEADER_SZ); + } +#endif +#ifndef WOLFSSL_NO_TLS12 + XMEMCPY(&ssl->curRL, rh, RECORD_HEADER_SZ); + return DecryptTls(ssl, output, input, sz); +#else + (void)input; + (void)sz; + (void)output; + (void)rh; + return VERSION_ERROR; +#endif +} /* Decrypt input message into output, adjust output steam if needed */ static const byte* DecryptMessage(WOLFSSL* ssl, const byte* input, word32 sz, @@ -5494,35 +5516,15 @@ static const byte* DecryptMessage(WOLFSSL* ssl, const byte* input, word32 sz, } #endif -#ifdef WOLFSSL_TLS13 - if (IsAtLeastTLSv1_3(ssl->version)) { - if (sz <= ssl->specs.aead_mac_size) { - *error = BUFFER_ERROR; - return NULL; - } - ret = DecryptTls13(ssl, output, input, sz, (byte*)rh, RECORD_HEADER_SZ); - } - else -#endif - { -#ifndef WOLFSSL_NO_TLS12 - XMEMCPY(&ssl->curRL, rh, RECORD_HEADER_SZ); - ret = DecryptTls(ssl, output, input, sz - macExtra); -#else - *error = VERSION_ERROR; - return NULL; -#endif - } + ret = DecryptRecord(ssl, input, sz - macExtra, output, rh); #ifdef WOLFSSL_ASYNC_CRYPT /* for async the symmetric operations are blocking */ - if (ret == WC_NO_ERR_TRACE(WC_PENDING_E)) { + while (ret == WC_NO_ERR_TRACE(WC_PENDING_E)) { do { ret = wolfSSL_AsyncPoll(ssl, WOLF_POLL_FLAG_CHECK_HW); } while (ret == 0); - if (ret >= 0) { - /* remove from event queue list */ - ret = wolfSSL_AsyncPop(ssl, NULL); - } + if (ret > 0) + ret = DecryptRecord(ssl, input, sz - macExtra, output, rh); } #endif if (ret != 0) {