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 08f78b993bd..9ef747e6e72 100644 --- a/.github/configs/os-check-linux.json +++ b/.github/configs/os-check-linux.json @@ -176,7 +176,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/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/.github/workflows/os-check.yml b/.github/workflows/os-check.yml index e4d1eded6d2..4d20a39f235 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 00000000000..41150d89b7f Binary files /dev/null and b/scripts/sniffer-tls12-ccm.pcap differ diff --git a/src/sniffer.c b/src/sniffer.c index 385ca277a95..61c86b6ad5b 100644 --- a/src/sniffer.c +++ b/src/sniffer.c @@ -5277,6 +5277,7 @@ static int DecryptDo(WOLFSSL* ssl, byte* plain, const byte* input, /* For ciphers that use AEAD use the encrypt routine to * bypass the auth tag checking */ wc_AesAuthEncryptFunc aes_auth_fn; + word16 plainSz; #ifdef WOLFSSL_ASYNC_CRYPT /* initialize event */ @@ -5295,22 +5296,24 @@ static int DecryptDo(WOLFSSL* ssl, byte* plain, const byte* input, aes_auth_fn = wc_AesCcmEncrypt; #endif - XMEMSET(ssl->decrypt.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; @@ -5470,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, @@ -5492,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) { diff --git a/sslSniffer/sslSnifferTest/snifftest.c b/sslSniffer/sslSnifferTest/snifftest.c index b97d9d01a71..b99e0939487 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 */ @@ -1060,6 +1063,8 @@ int main(int argc, char** argv) #ifdef THREADED_SNIFFTEST int workerThreadCount; const char* keyLogFileArg = NULL; + SnifferWorker* workers = NULL; + int* used = NULL; #endif #ifdef DEBUG_WOLFSSL @@ -1367,10 +1372,14 @@ int main(int argc, char** argv) frame = NULL_IF_FRAME_LEN; #ifdef THREADED_SNIFFTEST - SnifferWorker workers[workerThreadCount]; - int used[workerThreadCount]; - XMEMSET(used, 0, sizeof(used)); - XMEMSET(&workers, 0, sizeof(workers)); + workers = (SnifferWorker*)XMALLOC(sizeof(SnifferWorker) * workerThreadCount, + NULL, DYNAMIC_TYPE_TMP_BUFFER); + used = (int*)XMALLOC(sizeof(int) * workerThreadCount, NULL, + DYNAMIC_TYPE_TMP_BUFFER); + if (workers == NULL || used == NULL) + err_sys("Unable to allocate sniffer workers"); + XMEMSET(used, 0, sizeof(int) * workerThreadCount); + XMEMSET(workers, 0, sizeof(SnifferWorker) * workerThreadCount); for (i=0; i