Browse Source

Merge pull request #6717 from bigbrett/sniffer-keylogfile

sniffer keylog file support
JacobBarthelmeh 8 months ago
parent
commit
3033371abc

+ 12 - 0
configure.ac

@@ -1324,6 +1324,17 @@ then
   AM_CFLAGS="$AM_CFLAGS -DWOLFSSL_DTLS_MTU"
 fi
 
+# KeyLog file export
+AC_ARG_ENABLE([keylog-export],
+              [AS_HELP_STRING([--enable-keylog-export],[Enable (DANGEROUS INSECURE) exporting TLS secrets to an NSS keylog file (default: disabled)])],
+    [ ENABLED_KEYLOG_EXPORT=$enableval ],
+    [ ENABLED_KEYLOG_EXPORT=no ]
+    )
+if test "$ENABLED_KEYLOG_EXPORT" = "yes"
+then
+  AM_CFLAGS="$AM_CFLAGS -DSHOW_SECRETS -DHAVE_SECRET_CALLBACK -DWOLFSSL_SSLKEYLOGFILE"
+fi
+
 # TLS v1.3 Draft 18 (Note: only final TLS v1.3 supported, here for backwards build compatibility)
 AC_ARG_ENABLE([tls13-draft18],
     [AS_HELP_STRING([--enable-tls13-draft18],[Enable wolfSSL TLS v1.3 Draft 18 (default: disabled)])],
@@ -9449,6 +9460,7 @@ echo "   * PSA:                        $ENABLED_PSA"
 echo "   * System CA certs:            $ENABLED_SYS_CA_CERTS"
 echo "   * ERR Queues per Thread:      $ENABLED_ERRORQUEUEPERTHREAD"
 echo "   * rwlock:                     $ENABLED_RWLOCK"
+echo "   * keylog export:              $ENABLED_KEYLOG_EXPORT"
 echo ""
 echo "---"
 

+ 21 - 3
scripts/sniffer-gen.sh

@@ -46,7 +46,7 @@ run_sequence() {
         run_test "TLS13-AES128-GCM-SHA256" "-v 4" "-v 4"
         run_test "TLS13-AES256-GCM-SHA384" "-v 4" "-v 4"
         run_test "TLS13-CHACHA20-POLY1305-SHA256" "-v 4" "-v 4"
-    elif [ "$1" == "tls12" ]; then # TLS v1.2
+    elif [ "$1" == "tls12" ] || [ "$1" == "tls12-keylog" ]; then # TLS v1.2
         run_test "ECDHE-ECDSA-AES128-GCM-SHA256" "-v 3 -A ./certs/ca-ecc-cert.pem -k ./certs/ecc-key.pem -c ./certs/intermediate/server-chain-ecc.pem -V" "-v 3 -A ./certs/ca-ecc-cert.pem -k ./certs/ecc-client-key.pem -c ./certs/intermediate/client-chain-ecc.pem -C"
         run_test "ECDHE-ECDSA-AES256-GCM-SHA384" "-v 3 -A ./certs/ca-ecc-cert.pem -k ./certs/ecc-key.pem -c ./certs/intermediate/server-chain-ecc.pem -V" "-v 3 -A ./certs/ca-ecc-cert.pem -k ./certs/ecc-client-key.pem -c ./certs/intermediate/client-chain-ecc.pem -C"
     elif [ "$1" == "tls13-dh-resume" ] || [ "$1" == "tls13-ecc-resume" ]; then # TLS v1.3 Resumption
@@ -69,19 +69,37 @@ run_sequence() {
     fi
 }
 
-run_capture(){
+
+run_capture() {
+    local config_flags=()
     echo -e "\nconfiguring and building wolfssl ($1)..."
-    ./configure --enable-sniffer $2 1>/dev/null || exit $?
+
+    # Add default flags
+    config_flags+=(--enable-sniffer)
+
+    # If additional arguments are provided, add them to the array
+    if [ -n "$2" ]; then
+        # Convert string into an array, respecting quoted strings as a single element
+        eval "config_flags+=($2)"
+    fi
+
+    ./configure "${config_flags[@]}" 1>/dev/null || exit $?
     make 1>/dev/null || exit $?
+
     echo "starting capture"
     tcpdump -i lo -n port 11111 -w ./scripts/sniffer-${1}.pcap -U &
     tcpdump_pid=$!
     run_sequence $1
     sleep 1
     kill -15 $tcpdump_pid; tcpdump_pid=0
+
+    if [ "$1" == "tls12-keylog" ]; then
+        cp ./sslkeylog.log ./scripts/sniffer-${1}.sslkeylog
+    fi
 }
 
 run_capture "tls12"               ""
+run_capture "tls12-keylog"        "--enable-enc-then-mac=no --enable-keylog-export CFLAGS='-Wno-cpp -DWOLFSSL_SNIFFER_KEYLOGFILE'"
 run_capture "tls13-ecc"           ""
 run_capture "tls13-ecc-resume"    "--enable-session-ticket"
 run_capture "tls13-dh"            "--disable-ecc"

+ 51 - 11
scripts/sniffer-testsuite.test

@@ -59,6 +59,12 @@ has_static_rsa=no
 if [ $? -eq 0 ]; then
     has_static_rsa=yes
 fi
+# ./configure --enable-sniffer CFLAGS="-DWOLFSSL_SNIFFER_KEYLOGFILE"
+has_keylog=no
+./sslSniffer/sslSnifferTest/snifftest -? 2>&1 | grep -- 'ssl_keylog_file'
+if [ $? -eq 0 ]; then
+    has_keylog=yes
+fi
 
 
 RESULT=0
@@ -66,8 +72,8 @@ RESULT=0
 # TLS v1.2 Static RSA Test
 if test $RESULT -eq 0 && test $has_rsa == yes && test $has_tlsv12 == yes && test $has_static_rsa == yes
 then
-    echo -e "\nStaring snifftest on testsuite.pcap...\n"
-    ./sslSniffer/sslSnifferTest/snifftest ./scripts/sniffer-static-rsa.pcap ./certs/server-key.pem 127.0.0.1 11111
+    echo -e "\nStaring snifftest on sniffer-static-rsa.pcap...\n"
+    ./sslSniffer/sslSnifferTest/snifftest -pcap ./scripts/sniffer-static-rsa.pcap -key ./certs/server-key.pem -server 127.0.0.1 -port 11111
 
     RESULT=$?
     [ $RESULT -ne 0 ] && echo -e "\nsnifftest static RSA failed\n" && exit 1
@@ -77,16 +83,45 @@ fi
 if test $RESULT -eq 0 && test $has_rsa == yes && test $has_tlsv12 == yes && test $has_static_rsa == yes
 then
     echo -e "\nStaring snifftest on sniffer-ipv6.pcap...\n"
-    ./sslSniffer/sslSnifferTest/snifftest ./scripts/sniffer-ipv6.pcap ./certs/server-key.pem ::1 11111
+    ./sslSniffer/sslSnifferTest/snifftest -pcap ./scripts/sniffer-ipv6.pcap -key ./certs/server-key.pem -server ::1 -port 11111
 
     RESULT=$?
     [ $RESULT -ne 0 ] && echo -e "\nsnifftest (ipv6) failed\n" && exit 1
 fi
 
+#  TLS v1.2 sniffer keylog file test: runs sniffer on pcap and associated keylog file and compares decrypted traffic with known good output.
+#  To regenerate the known good output, run `scripts/sniffer-gen.sh` to regenerate the pcap and keylog file, then run the sniffer on it
+#  with the same arguments as in the test below, but redirect output to `./scripts/sniffer-tls12-keylog.out`.
+if test $RESULT -eq 0 && test $has_tlsv13 == yes && test $has_keylog == yes
+then
+    echo -e "\nStaring snifftest on sniffer-tls12-keylog.pcap...\n"
+
+    TMPFILE=$(mktemp)
+    RESULT=$?
+    [ $RESULT -ne 0 ] && echo -e "\nsnifftest keylog test failed: unable to create tmpfile\n" && rm $TMPFILE && exit 1
+
+    ./sslSniffer/sslSnifferTest/snifftest \
+        -pcap scripts/sniffer-tls12-keylog.pcap \
+        -keylogfile scripts/sniffer-tls12-keylog.sslkeylog \
+        -server 127.0.0.1 -port 11111 > $TMPFILE
+
+    RESULT=$?
+    [ $RESULT -ne 0 ] && echo -e "\nsnifftest keylog test failed: snifftest returned $RESULT\n" && rm $TMPFILE && exit 1
+
+    # sed '1d' strips out first line, which contains wolfSSL version
+    sed '1d' $TMPFILE | diff - <(sed '1d' scripts/sniffer-tls12-keylog.out)
+
+    RESULT=$?
+    [ $RESULT -ne 0 ] && echo -e "\nsnifftest keylog test failed: snifftest diff returned $RESULT\n" && rm $TMPFILE && exit 1
+
+    rm $TMPFILE
+fi
+
 # TLS v1.3 sniffer test ECC
 if test $RESULT -eq 0 && test $has_tlsv13 == yes && test $has_ecc == yes
 then
-    ./sslSniffer/sslSnifferTest/snifftest ./scripts/sniffer-tls13-ecc.pcap ./certs/statickeys/ecc-secp256r1.pem 127.0.0.1 11111
+    echo -e "\nStaring snifftest on sniffer-tls13-ecc.pcap...\n"
+    ./sslSniffer/sslSnifferTest/snifftest -pcap ./scripts/sniffer-tls13-ecc.pcap -key ./certs/statickeys/ecc-secp256r1.pem -server 127.0.0.1 -port 11111
 
     RESULT=$?
     [ $RESULT -ne 0 ] && echo -e "\nsnifftest TLS v1.3 ECC failed\n" && exit 1
@@ -95,7 +130,8 @@ fi
 # TLS v1.3 sniffer test DH
 if test $RESULT -eq 0 && test $has_tlsv13 == yes && test $has_dh == yes
 then
-    ./sslSniffer/sslSnifferTest/snifftest ./scripts/sniffer-tls13-dh.pcap ./certs/statickeys/dh-ffdhe2048.pem 127.0.0.1 11111
+    echo -e "\nStaring snifftest on sniffer-tls13-dh.pcap...\n"
+    ./sslSniffer/sslSnifferTest/snifftest -pcap ./scripts/sniffer-tls13-dh.pcap -key ./certs/statickeys/dh-ffdhe2048.pem -server 127.0.0.1 -port 11111
 
     RESULT=$?
     [ $RESULT -ne 0 ] && echo -e "\nsnifftest TLS v1.3 DH failed\n" && exit 1
@@ -104,7 +140,8 @@ fi
 # TLS v1.3 sniffer test X25519
 if test $RESULT -eq 0 && test $has_tlsv13 == yes && test $has_x25519 == yes
 then
-    ./sslSniffer/sslSnifferTest/snifftest ./scripts/sniffer-tls13-x25519.pcap ./certs/statickeys/x25519.pem 127.0.0.1 11111
+    echo -e "\nStaring snifftest on sniffer-tls13-x25519.pcap...\n"
+    ./sslSniffer/sslSnifferTest/snifftest -pcap ./scripts/sniffer-tls13-x25519.pcap -key ./certs/statickeys/x25519.pem -server 127.0.0.1 -port 11111
 
     RESULT=$?
     [ $RESULT -ne 0 ] && echo -e "\nsnifftest TLS v1.3 X25519 failed\n" && exit 1
@@ -113,7 +150,8 @@ fi
 # TLS v1.3 sniffer test ECC resumption
 if test $RESULT -eq 0 && test $has_tlsv13 == yes && test $has_ecc == yes && test $session_ticket == yes
 then
-    ./sslSniffer/sslSnifferTest/snifftest ./scripts/sniffer-tls13-ecc-resume.pcap ./certs/statickeys/ecc-secp256r1.pem 127.0.0.1 11111
+    echo -e "\nStaring snifftest on sniffer-tls13-ecc-resume.pcap...\n"
+    ./sslSniffer/sslSnifferTest/snifftest -pcap ./scripts/sniffer-tls13-ecc-resume.pcap -key ./certs/statickeys/ecc-secp256r1.pem -server 127.0.0.1 -port 11111
 
     RESULT=$?
     [ $RESULT -ne 0 ] && echo -e "\nsnifftest TLS v1.3 ECC failed\n" && exit 1
@@ -122,7 +160,8 @@ fi
 # TLS v1.3 sniffer test DH
 if test $RESULT -eq 0 && test $has_tlsv13 == yes && test $has_dh == yes && test $session_ticket == yes
 then
-    ./sslSniffer/sslSnifferTest/snifftest ./scripts/sniffer-tls13-dh-resume.pcap ./certs/statickeys/dh-ffdhe2048.pem 127.0.0.1 11111
+    echo -e "\nStaring snifftest on sniffer-tls13-dh-resume.pcap...\n"
+    ./sslSniffer/sslSnifferTest/snifftest -pcap ./scripts/sniffer-tls13-dh-resume.pcap -key ./certs/statickeys/dh-ffdhe2048.pem -server 127.0.0.1 -port 11111
 
     RESULT=$?
     [ $RESULT -ne 0 ] && echo -e "\nsnifftest TLS v1.3 DH failed\n" && exit 1
@@ -131,7 +170,8 @@ fi
 # TLS v1.3 sniffer test X25519
 if test $RESULT -eq 0 && test $has_tlsv13 == yes && test $has_x25519 == yes && test $session_ticket == yes
 then
-    ./sslSniffer/sslSnifferTest/snifftest ./scripts/sniffer-tls13-x25519-resume.pcap ./certs/statickeys/x25519.pem 127.0.0.1 11111
+    echo -e "\nStaring snifftest on sniffer-tls13-x25519-resume.pcap...\n"
+    ./sslSniffer/sslSnifferTest/snifftest -pcap ./scripts/sniffer-tls13-x25519-resume.pcap -key ./certs/statickeys/x25519.pem -server 127.0.0.1 -port 11111
 
     RESULT=$?
     [ $RESULT -ne 0 ] && echo -e "\nsnifftest TLS v1.3 X25519 failed\n" && exit 1
@@ -140,12 +180,12 @@ fi
 # TLS v1.3 sniffer test hello_retry_request (HRR) with ECDHE
 if test $RESULT -eq 0 && test $has_tlsv13 == yes && test $has_ecc == yes
 then
-    ./sslSniffer/sslSnifferTest/snifftest ./scripts/sniffer-tls13-hrr.pcap ./certs/statickeys/ecc-secp256r1.pem 127.0.0.1 11111
+    echo -e "\nStaring snifftest on sniffer-tls13-hrr.pcap...\n"
+    ./sslSniffer/sslSnifferTest/snifftest -pcap ./scripts/sniffer-tls13-hrr.pcap -key ./certs/statickeys/ecc-secp256r1.pem -server 127.0.0.1 -port 11111
 
     RESULT=$?
     [ $RESULT -ne 0 ] && echo -e "\nsnifftest TLS v1.3 HRR failed\n" && exit 1
 fi
 
 echo -e "\nSuccess!\n"
-
 exit 0

+ 7 - 0
scripts/sniffer-tls12-keylog.out

@@ -0,0 +1,7 @@
+snifftest 5.6.3
+sniffer features: key_callback tls_v13 tls_v12 static_ephemeral sni extended_master rsa dh ecc rsa_static dh_static ssl_keylog_file 
+
+SSL App Data(26:14):hello wolfssl!
+SSL App Data(27:22):I hear you fa shizzle!
+SSL App Data(57:14):hello wolfssl!
+SSL App Data(58:22):I hear you fa shizzle!

BIN
scripts/sniffer-tls12-keylog.pcap


+ 12 - 0
scripts/sniffer-tls12-keylog.sslkeylog

@@ -0,0 +1,12 @@
+CLIENT_RANDOM 3827fef5d4172f3753d81661dbc228b41adcb2357e04e493f8d9d4d4a85777d3 5240740265eaa6a8622805728bf53fd88b546b1523e4b9c3d4b6573471bc081ce9f074520df99873c0c447d3a37ebdc6
+CLIENT_RANDOM 3827fef5d4172f3753d81661dbc228b41adcb2357e04e493f8d9d4d4a85777d3 5240740265eaa6a8622805728bf53fd88b546b1523e4b9c3d4b6573471bc081ce9f074520df99873c0c447d3a37ebdc6
+CLIENT_RANDOM 8d793a1160661700dc686746be0e77a01dcf94472971bfbb517c6d7d179b7bcd ac612c7b9292ad6bc5304176b9dcde81ee488b6adb63bb6917cbf38a0775e9e334766839e091506972450e77ba6ce977
+CLIENT_RANDOM 8d793a1160661700dc686746be0e77a01dcf94472971bfbb517c6d7d179b7bcd ac612c7b9292ad6bc5304176b9dcde81ee488b6adb63bb6917cbf38a0775e9e334766839e091506972450e77ba6ce977
+CLIENT_RANDOM 4a1d3695145e5136a2914756962f848f033b62d3a9b714f7e659ae3f133d2527 118442e0edd05696d1566eb73693a9a1316d24ac62e024f92e685c540eaec31a463e19091d45b63cfc8539d3bd11915b
+CLIENT_RANDOM 4a1d3695145e5136a2914756962f848f033b62d3a9b714f7e659ae3f133d2527 118442e0edd05696d1566eb73693a9a1316d24ac62e024f92e685c540eaec31a463e19091d45b63cfc8539d3bd11915b
+CLIENT_RANDOM 307abe19ea84d9b45621df5b89fee8d2f9ac66eb4303cf9303cf6e957ad1d75d dfb9bb0d29579a0b2f35be65982954f33268c30ea8709985a45c95633c1c6e94cbfdebe625bda975572921b4462d5153
+CLIENT_RANDOM 307abe19ea84d9b45621df5b89fee8d2f9ac66eb4303cf9303cf6e957ad1d75d dfb9bb0d29579a0b2f35be65982954f33268c30ea8709985a45c95633c1c6e94cbfdebe625bda975572921b4462d5153
+CLIENT_RANDOM 41ad4bceb3b900ffbc77f9b0c67d69a62f2b1d490f91b2af496cf6e78371900d 9752ea66a193ac04e4a20aca3c7160faa2637efb927d00c2a2d90b77e2e7875a760ee76f9ce509e549f8303625a2fd59
+CLIENT_RANDOM 41ad4bceb3b900ffbc77f9b0c67d69a62f2b1d490f91b2af496cf6e78371900d 9752ea66a193ac04e4a20aca3c7160faa2637efb927d00c2a2d90b77e2e7875a760ee76f9ce509e549f8303625a2fd59
+CLIENT_RANDOM 596ffcdec477ac0b24e0958ecd7c1fc7cc5b37337bac90803b864e3edbad8780 2f86705d0c4fb7e92c7cb1ef2f104955724d5a0b5abd18478d39c1dd96222b4462e4382982bec26e9a231ec970c2d509
+CLIENT_RANDOM 596ffcdec477ac0b24e0958ecd7c1fc7cc5b37337bac90803b864e3edbad8780 2f86705d0c4fb7e92c7cb1ef2f104955724d5a0b5abd18478d39c1dd96222b4462e4382982bec26e9a231ec970c2d509

+ 2 - 2
src/internal.c

@@ -7502,7 +7502,7 @@ int InitSSL(WOLFSSL* ssl, WOLFSSL_CTX* ctx, int writeDup)
 #endif
 
 #if defined(HAVE_SECRET_CALLBACK) && defined(SHOW_SECRETS) && \
-    defined(WOLFSSL_SSLKEYLOGFILE)
+    defined(WOLFSSL_SSLKEYLOGFILE) && defined(WOLFSSL_TLS13)
     (void)wolfSSL_set_tls13_secret_cb(ssl, tls13ShowSecrets, NULL);
 #endif
 
@@ -17651,7 +17651,7 @@ int ChachaAEADEncrypt(WOLFSSL* ssl, byte* out, const byte* input,
  *
  * Return 0 on success negative values in error case
  */
-static int ChachaAEADDecrypt(WOLFSSL* ssl, byte* plain, const byte* input,
+int ChachaAEADDecrypt(WOLFSSL* ssl, byte* plain, const byte* input,
                            word16 sz)
 {
     byte add[AEAD_AUTH_DATA_SZ];

+ 453 - 20
src/sniffer.c

@@ -373,6 +373,9 @@ static const char* const msgTable[] =
     "Setting up keys",
     "Unsupported TLS Version",
     "Server Client Key Mismatch",
+
+    /* 99 */
+    "Invalid or missing keylog file",
 };
 
 
@@ -436,6 +439,11 @@ typedef struct SnifferServer {
     NamedKey*      namedKeys;                    /* mapping of names and keys */
     wolfSSL_Mutex  namedKeysMutex;               /* mutex for namedKey list */
 #endif
+#if defined(WOLFSSL_SNIFFER_KEYLOGFILE)
+    byte           useKeyLogFile; /* True if session secrets are coming from a
+                                     keylog file */
+#endif /* WOLFSSL_SNIFFER_KEYLOGFILE */
+
     struct SnifferServer* next;                  /* for list */
 } SnifferServer;
 
@@ -652,6 +660,22 @@ static void UpdateMissedDataSessions(void)
     static WOLFSSL_GLOBAL int CryptoDeviceId = INVALID_DEVID;
 #endif
 
+#if defined(WOLFSSL_SNIFFER_KEYLOGFILE)
+static int addSecretNode(unsigned char* clientRandom,
+                         unsigned char* masterSecret,
+                         char* error);
+static void hexToBin(const char* hex, unsigned char* bin, int binLength);
+static int parseKeyLogFile(const char* fileName, char* error);
+static unsigned char* findMasterSecret(unsigned char* clientRandom);
+static void freeSecretList(void);
+static int snifferSecretCb(unsigned char* client_random,
+                           unsigned char* output_secret);
+static void setSnifferSecretCb(SnifferSession* session);
+static int addKeyLogSnifferServerHelper(const char* address,
+                                        int port,
+                                        char* error);
+#endif /* WOLFSSL_SNIFFER_KEYLOGFILE */
+
 
 /* Initialize overall Sniffer */
 void ssl_InitSniffer_ex(int devId)
@@ -867,8 +891,16 @@ void ssl_FreeSniffer(void)
     }
     ServerList = NULL;
 
+
+
     UNLOCK_SESSION();
     UNLOCK_SERVER_LIST();
+
+#if defined(WOLFSSL_SNIFFER_KEYLOGFILE)
+    freeSecretList();
+#endif /* WOLFSSL_SNIFFER_KEYLOGFILE */
+
+
 #ifndef WOLFSSL_SNIFFER_NO_RECOVERY
     wc_FreeMutex(&RecoveryMutex);
 #endif
@@ -1162,8 +1194,14 @@ static void TraceSetServer(const char* srv, int port, const char* keyFile)
 {
     if (TraceOn) {
         XFPRINTF(TraceFile, "\tTrying to install a new Sniffer Server with\n");
-        XFPRINTF(TraceFile, "\tserver: %s, port: %d, keyFile: %s\n", srv, port,
-                                                                    keyFile);
+        if (keyFile != NULL) {
+            XFPRINTF(TraceFile, "\tserver: %s, port: %d, keyFile: %s\n",
+                     srv, port, keyFile);
+        }
+        else {
+            XFPRINTF(TraceFile, "\tserver: %s, port: %d\n",
+                     srv, port);
+        }
     }
 }
 
@@ -1732,6 +1770,7 @@ static int CreateWatchSnifferServer(char* error)
 
 #endif
 
+
 /* Caller locks ServerListMutex */
 static int SetNamedPrivateKey(const char* name, const char* address, int port,
     const char* keyFile, int keySz, int typeKey, const char* password,
@@ -1780,10 +1819,11 @@ static int SetNamedPrivateKey(const char* name, const char* address, int port,
     if (serverIp.ip4 == XINADDR_NONE) {
     #ifdef FUSION_RTOS
         if (XINET_PTON(AF_INET6, address, serverIp.ip6,
-                       sizeof(serverIp.ip4)) == 1) {
+                       sizeof(serverIp.ip4)) == 1)
     #else
-        if (XINET_PTON(AF_INET6, address, serverIp.ip6) == 1) {
+        if (XINET_PTON(AF_INET6, address, serverIp.ip6) == 1)
     #endif
+        {
             serverIp.version = IPV6;
         }
     }
@@ -2463,6 +2503,17 @@ static int SetupKeys(const byte* input, int* sslBytes, SnifferSession* session,
     }
 #endif
 
+    #if defined(WOLFSSL_SNIFFER_KEYLOGFILE)
+    if (session->context->useKeyLogFile) {
+        ret = 0;
+        XMEMSET(args, 0, sizeof(SetupKeysArgs));
+
+        /* We want to skip all the key setup and go right to master secret generation, which is
+         * where we inject the master secret obtained from the keylog file */
+        ssl->options.asyncState = TLS_ASYNC_FINALIZE;
+    }
+    #endif
+
     switch (ssl->options.asyncState) {
     case TLS_ASYNC_BEGIN:
     {
@@ -3084,12 +3135,17 @@ static int SetupKeys(const byte* input, int* sslBytes, SnifferSession* session,
 
     case TLS_ASYNC_FINALIZE:
     {
-        /* store for client side as well */
-        XMEMCPY(session->sslClient->arrays->preMasterSecret,
-            session->sslServer->arrays->preMasterSecret,
-            session->sslServer->arrays->preMasterSz);
-        session->sslClient->arrays->preMasterSz =
-            session->sslServer->arrays->preMasterSz;
+    #if defined(WOLFSSL_SNIFFER_KEYLOGFILE)
+        if (!session->context->useKeyLogFile)
+    #endif /* !WOLFSSL_SNIFFER_KEYLOGFILE */
+        {
+            /* store for client side as well */
+            XMEMCPY(session->sslClient->arrays->preMasterSecret,
+                    session->sslServer->arrays->preMasterSecret,
+                    session->sslServer->arrays->preMasterSz);
+            session->sslClient->arrays->preMasterSz =
+                session->sslServer->arrays->preMasterSz;
+        }
 
     #ifdef SHOW_SECRETS
         PrintSecret("pre master secret",
@@ -4556,14 +4612,21 @@ static int DoHandShake(const byte* input, int* sslBytes,
             Trace(GOT_CERT_REQ_STR);
             break;
         case server_key_exchange:
-#ifdef WOLFSSL_SNIFFER_STATS
-            INC_STAT(SnifferStats.sslEphemeralMisses);
-#endif
             Trace(GOT_SERVER_KEY_EX_STR);
-            /* can't know temp key passively */
-            SetError(BAD_CIPHER_SPEC_STR, error, session, FATAL_ERROR_STATE);
-            session->verboseErr = 1;
-            ret = -1;
+
+#if defined(WOLFSSL_SNIFFER_KEYLOGFILE)
+            if (!session->context->useKeyLogFile)
+#endif /* WOLFSSL_SNIFFER_KEYLOGFILE */
+            {
+                /* can't know temp key passively */
+                SetError(BAD_CIPHER_SPEC_STR, error, session, FATAL_ERROR_STATE);
+                session->verboseErr = 1;
+                ret = -1;
+
+#if defined(WOLFSSL_SNIFFER_STATS)
+                INC_STAT(SnifferStats.sslEphemeralMisses);
+#endif /* WOLFSSL_SNIFFER_STATS */
+            }
             break;
         case encrypted_extensions:
             Trace(GOT_ENC_EXT_STR);
@@ -4720,6 +4783,8 @@ static int DecryptDo(WOLFSSL* ssl, byte* plain, const byte* input,
         case wolfssl_aes_gcm:
         case wolfssl_aes_ccm: /* GCM AEAD macros use same size as CCM */
         {
+            /* For ciphers that use AEAD use the encrypt routine to
+             * bypass the auth tag checking */
             wc_AesAuthEncryptFunc aes_auth_fn;
 
         #ifdef WOLFSSL_ASYNC_CRYPT
@@ -4749,7 +4814,7 @@ static int DecryptDo(WOLFSSL* ssl, byte* plain, const byte* input,
                         input + AESGCM_EXP_IV_SZ,
                           sz - AESGCM_EXP_IV_SZ - ssl->specs.aead_mac_size,
                         ssl->decrypt.nonce, AESGCM_NONCE_SZ,
-                        ssl->decrypt.additional, ssl->specs.aead_mac_size,
+                        ssl->decrypt.additional, AEAD_AUTH_DATA_SZ,
                         NULL, 0)) < 0) {
             #ifdef WOLFSSL_ASYNC_CRYPT
                 if (ret == WC_PENDING_E) {
@@ -4782,7 +4847,7 @@ static int DecryptDo(WOLFSSL* ssl, byte* plain, const byte* input,
     #if defined(HAVE_CHACHA) && defined(HAVE_POLY1305) && \
         !defined(NO_CHAPOL_AEAD)
         case wolfssl_chacha:
-            ret = ChachaAEADEncrypt(ssl, plain, input, sz);
+            ret = ChachaAEADDecrypt(ssl, plain, input, sz);
             break;
     #endif
 
@@ -5122,6 +5187,13 @@ static SnifferSession* CreateSession(IpInfo* ipInfo, TcpInfo* tcpInfo,
     /* put server back into server mode */
     session->sslServer->options.side = WOLFSSL_SERVER_END;
 
+#if defined(WOLFSSL_SNIFFER_KEYLOGFILE)
+    if (session->context->useKeyLogFile) {
+        setSnifferSecretCb(session);
+    }
+#endif /* WOLFSSL_SNIFFER_KEYLOGFILE */
+
+
     row = SessionHash(ipInfo, tcpInfo);
 
     /* add it to the session table */
@@ -6492,10 +6564,10 @@ static int RemoveFatalSession(IpInfo* ipInfo, TcpInfo* tcpInfo,
                               SnifferSession* session, char* error)
 {
     if (session && session->flags.fatalError == FATAL_ERROR_STATE) {
-        RemoveSession(session, ipInfo, tcpInfo, 0);
         if (!session->verboseErr) {
             SetError(FATAL_ERROR_STR, error, NULL, 0);
         }
+        RemoveSession(session, ipInfo, tcpInfo, 0);
         return 1;
     }
     return 0;
@@ -7132,6 +7204,367 @@ int ssl_PollSniffer(WOLF_EVENT** events, int maxEvents, WOLF_EVENT_FLAG flags,
 }
 #endif
 
+
+#if defined(WOLFSSL_SNIFFER_KEYLOGFILE)
+
+#define CLIENT_RANDOM_LABEL_LENGTH 13
+#define CLIENT_RANDOM_LENGTH 32
+#define MASTER_SECRET_LENGTH 48
+#define CLIENT_RANDOM_BITS ((CLIENT_RANDOM_LENGTH) * 8)
+
+
+typedef struct SecretNode {
+    unsigned char clientRandom[CLIENT_RANDOM_LENGTH];
+    unsigned char masterSecret[MASTER_SECRET_LENGTH];
+    struct SecretNode* next;
+} SecretNode;
+
+
+/* Default to the same size hash table as the session table,
+ * but allow user to override */
+#ifndef WOLFSSL_SNIFFER_KEYLOGFILE_HASH_TABLE_SIZE
+#define WOLFSSL_SNIFFER_KEYLOGFILE_HASH_TABLE_SIZE HASH_SIZE
+#endif
+
+static THREAD_LS_T WOLFSSL_GLOBAL
+SecretNode*
+secretHashTable[WOLFSSL_SNIFFER_KEYLOGFILE_HASH_TABLE_SIZE] = {NULL};
+#ifndef HAVE_C___ATOMIC
+static WOLFSSL_GLOBAL wolfSSL_Mutex secretListMutex;
+#endif
+
+
+static unsigned int secretHashFunction(unsigned char* clientRandom);
+
+#ifdef HAVE_C___ATOMIC
+    #define LOCK_SECRET_LIST() WC_DO_NOTHING
+    #define UNLOCK_SECRET_LIST() WC_DO_NOTHING
+#else
+    #define LOCK_SECRET_LIST() wc_LockMutex(&secretListMutex)
+    #define UNLOCK_SECRET_LIST() wc_UnLockMutex(&secretListMutex)
+#endif
+
+
+/*
+ * Basic polynomial hash function that maps a 32-byte client random value to an
+ * array index
+ */
+static unsigned int secretHashFunction(unsigned char* clientRandom)
+{
+    int i = 0;
+    unsigned int hash = 0;
+
+    for (i = 0; i < CLIENT_RANDOM_LENGTH; i++) {
+        hash = (hash * CLIENT_RANDOM_BITS + clientRandom[i])
+            % WOLFSSL_SNIFFER_KEYLOGFILE_HASH_TABLE_SIZE;
+    }
+
+    return hash;
+}
+
+
+static int addSecretNode(unsigned char* clientRandom,
+                         unsigned char* masterSecret,
+                         char* error)
+{
+    unsigned int index        = 0;
+    SecretNode* newSecretNode = NULL;
+
+    newSecretNode = (SecretNode*)XMALLOC(sizeof(SecretNode),
+                                         NULL,
+                                         DYNAMIC_TYPE_SNIFFER_KEYLOG_NODE);
+    if (newSecretNode == NULL) {
+        SetError(MEMORY_STR, error, NULL, 0);
+        return WOLFSSL_SNIFFER_ERROR;
+    }
+
+    XMEMCPY(newSecretNode->clientRandom, clientRandom, CLIENT_RANDOM_LENGTH);
+    XMEMCPY(newSecretNode->masterSecret, masterSecret, MASTER_SECRET_LENGTH);
+
+    LOCK_SECRET_LIST();
+
+    index = secretHashFunction(clientRandom);
+    newSecretNode->next = NULL;
+
+    if (secretHashTable[index] == NULL) {
+        secretHashTable[index] = newSecretNode;
+    }
+    else {
+        SecretNode* current = secretHashTable[index];
+        while (current != NULL) {
+            if (memcmp(current->clientRandom,
+                       clientRandom,
+                       CLIENT_RANDOM_LENGTH) == 0) {
+                /* No need for a new node, since it already exists */
+                fprintf(stderr, "Found duplicate client random value in "
+                                "keylog file. Rejecting.\n");
+                XFREE(newSecretNode, NULL, DYNAMIC_TYPE_SNIFFER_KEYLOG_NODE);
+                break;
+            }
+            if (current->next == NULL) {
+                current->next = newSecretNode;
+                break;
+            }
+            current = current->next;
+        }
+    }
+
+    UNLOCK_SECRET_LIST();
+
+    return 0;
+}
+
+
+/*
+ * Looks up a master secret for a given client random from the keylog file
+ */
+static unsigned char* findMasterSecret(unsigned char* clientRandom)
+{
+    unsigned char* secret = NULL;
+    SecretNode* node = NULL;
+    unsigned int index = 0;
+
+    LOCK_SECRET_LIST();
+
+    index = secretHashFunction(clientRandom);
+    node  = secretHashTable[index];
+
+    while (node != NULL) {
+        if (XMEMCMP(node->clientRandom,
+                    clientRandom, CLIENT_RANDOM_LENGTH) == 0) {
+            secret = node->masterSecret;
+            break;
+        }
+        node = node->next;
+    }
+
+    UNLOCK_SECRET_LIST();
+
+    return secret;
+}
+
+
+static void hexToBin(const char* hex, unsigned char* bin, int binLength)
+{
+    int i = 0;
+    for (i = 0; i < binLength; i++) {
+        sscanf(hex + 2 * i, "%02hhx", &bin[i]);
+    }
+}
+
+
+static int parseKeyLogFile(const char* fileName, char* error)
+{
+    const char CLIENT_RANDOM_LABEL_STR[] = "CLIENT_RANDOM";
+    unsigned char clientRandom[CLIENT_RANDOM_LENGTH];
+    unsigned char masterSecret[MASTER_SECRET_LENGTH];
+    FILE* file = NULL;
+    int ret = 0;
+    /* +1 for null terminator */
+    char clientRandomLabel[CLIENT_RANDOM_LABEL_LENGTH + 1] = {0};
+    /* 2 chars for Hexadecimal representation, plus null terminator */
+    char clientRandomHex[2 * CLIENT_RANDOM_LENGTH + 1] = {0};
+    char masterSecretHex[2 * MASTER_SECRET_LENGTH + 1] = {0};
+
+
+    file = fopen(fileName, "r");
+    if (file == NULL) {
+        fprintf(stderr, "Could not open keylog file: %s\n", fileName);
+        SetError(KEYLOG_FILE_INVALID, error, NULL, 0);
+        return WOLFSSL_SNIFFER_ERROR;
+    }
+
+    while (fscanf(file, "%13s %64s %96s",
+                  clientRandomLabel, clientRandomHex, masterSecretHex) == 3) {
+        if (XSTRCMP(clientRandomLabel, CLIENT_RANDOM_LABEL_STR) == 0) {
+            hexToBin(clientRandomHex, clientRandom, CLIENT_RANDOM_LENGTH);
+            hexToBin(masterSecretHex, masterSecret, MASTER_SECRET_LENGTH);
+            ret = addSecretNode(clientRandom, masterSecret, error);
+            if (ret != 0) {
+                fclose(file);
+                return ret;
+            }
+        }
+    }
+    fclose(file);
+
+    return 0;
+}
+
+
+static void freeSecretList(void)
+{
+    int i = 0;
+
+    LOCK_SECRET_LIST();
+
+    for (i=0; i<WOLFSSL_SNIFFER_KEYLOGFILE_HASH_TABLE_SIZE; i++)
+    {
+        SecretNode* current = secretHashTable[i];
+        SecretNode * next = NULL;
+
+        while (current != NULL) {
+            next = current->next;
+            XFREE(current, NULL, DYNAMIC_TYPE_SNIFFER_KEYLOG_NODE);
+            current = next;
+        }
+    }
+
+    UNLOCK_SECRET_LIST();
+}
+
+
+/*
+ * Looks up secret based on client random and copies it to output_secret
+ */
+static int snifferSecretCb(unsigned char* client_random,
+                           unsigned char* output_secret)
+{
+    unsigned char* secret = NULL;
+
+    if (client_random == NULL || output_secret == NULL) {
+        return WOLFSSL_SNIFFER_FATAL_ERROR;
+    }
+
+    /* get secret from secret table based on client random */
+    secret = findMasterSecret(client_random);
+    if (secret != NULL) {
+        XMEMCPY(output_secret, secret, MASTER_SECRET_LENGTH);
+        return 0;
+    }
+
+    /* didn't find the secret */
+    return WOLFSSL_SNIFFER_ERROR;
+}
+
+
+static void setSnifferSecretCb(SnifferSession* session)
+{
+    session->context->useKeyLogFile = 1;
+    session->sslServer->snifferSecretCb = snifferSecretCb;
+    session->sslClient->snifferSecretCb = snifferSecretCb;
+}
+
+
+/*
+ * Helper function that creates a sniffer server object that can decrypt using
+ * a keylog file, and adds it to the server list
+ *
+ * NOTE: the caller is responsible for locking and unlocking the server list
+ */
+static int addKeyLogSnifferServerHelper(const char* address,
+                                        int port,
+                                        char* error)
+{
+    IpAddrInfo     serverIp = {0};
+    SnifferServer *sniffer = NULL;
+
+    TraceHeader();
+    TraceSetServer(address, port, NULL);
+
+    serverIp.version = IPV4;
+    serverIp.ip4 = XINET_ADDR(address);
+    if (serverIp.ip4 == XINADDR_NONE) {
+    #ifdef FUSION_RTOS
+        if (XINET_PTON(AF_INET6, address, serverIp.ip6,
+                       sizeof(serverIp.ip4)) == 1)
+    #else
+        if (XINET_PTON(AF_INET6, address, serverIp.ip6) == 1)
+    #endif
+        {
+            serverIp.version = IPV6;
+        }
+    }
+
+    sniffer = ServerList;
+    while (sniffer != NULL &&
+            (!MatchAddr(sniffer->server, serverIp) || sniffer->port != port)) {
+        sniffer = sniffer->next;
+    }
+
+    if (sniffer == NULL) {
+        sniffer = (SnifferServer*)XMALLOC(sizeof(SnifferServer),
+                NULL, DYNAMIC_TYPE_SNIFFER_SERVER);
+        if (sniffer == NULL) {
+            SetError(MEMORY_STR, error, NULL, 0);
+            return WOLFSSL_SNIFFER_ERROR;
+        }
+        InitSnifferServer(sniffer);
+
+        XSTRNCPY(sniffer->address, address, MAX_SERVER_ADDRESS-1);
+        sniffer->address[MAX_SERVER_ADDRESS-1] = '\0';
+        sniffer->server = serverIp;
+        sniffer->port = port;
+
+        sniffer->ctx = wolfSSL_CTX_new(wolfSSLv23_client_method());
+        if (!sniffer->ctx) {
+            SetError(MEMORY_STR, error, NULL, 0);
+            FreeSnifferServer(sniffer);
+            return WOLFSSL_SNIFFER_ERROR;
+        }
+    #if defined(WOLF_CRYPTO_CB) || defined(WOLFSSL_ASYNC_CRYPT)
+        if (CryptoDeviceId != INVALID_DEVID)
+            wolfSSL_CTX_SetDevId(sniffer->ctx, CryptoDeviceId);
+    #endif
+
+        sniffer->next = ServerList;
+        ServerList = sniffer;
+    }
+    else {
+        printf("SESSION ALREADY EXISTS\n");
+    }
+
+    /* Tag the new or existing server as requiring keylog support to
+     * decrypt, otherwise it won't be useable */
+    sniffer->useKeyLogFile = 1;
+
+    return 0;
+}
+
+/*
+ * Creates a sniffer server that is able to decrypt using secrets from a
+ * keylog file, and adds it to the server list
+ *
+ * If a server at the address and port already exists, it will be marked
+ * for keylog file decryption
+ */
+int ssl_CreateKeyLogSnifferServer(const char* address, int port, char* error)
+{
+    int ret = 0;
+
+    if (address == NULL) {
+        SetError(KEYLOG_FILE_INVALID, error, NULL, 0);
+        return WOLFSSL_SNIFFER_ERROR;
+    }
+
+    LOCK_SERVER_LIST();
+
+    ret = addKeyLogSnifferServerHelper(address, port, error);
+
+    UNLOCK_SERVER_LIST();
+
+    return ret;
+}
+
+
+/*
+ * Loads secrets to decrypt TLS traffic from a keylog file. Only sniffer
+ * servers registered with ssl_createKeyLogSnifferServer() will be able to
+ * decrypt using these secrets
+ */
+int ssl_LoadSecretsFromKeyLogFile(const char* keylogfile, char* error)
+{
+    if (keylogfile == NULL) {
+        SetError(KEYLOG_FILE_INVALID, error, NULL, 0);
+        return WOLFSSL_SNIFFER_ERROR;
+    }
+
+    return parseKeyLogFile(keylogfile, error);
+}
+
+#endif /* WOLFSSL_SNIFFER_KEYLOGFILE */
+
+
 #undef ERROR_OUT
 
 #endif /* WOLFSSL_SNIFFER */

+ 13 - 0
src/tls.c

@@ -516,6 +516,19 @@ int MakeTlsMasterSecret(WOLFSSL* ssl)
 {
     int ret;
 
+#if defined(WOLFSSL_SNIFFER) && defined(WOLFSSL_SNIFFER_KEYLOGFILE)
+    /* If this is called from a sniffer session with keylog file support, obtain
+     * the master secret from the callback */
+    if (ssl->snifferSecretCb != NULL) {
+        ret = ssl->snifferSecretCb(ssl->arrays->clientRandom, ssl->arrays->masterSecret);
+        if (ret != 0) {
+            return ret;
+        }
+        ret = DeriveTlsKeys(ssl);
+        return ret;
+    }
+#endif /* WOLFSSL_SNIFFER && WOLFSSL_SNIFFER_KEYLOGFILE */
+
 #ifdef HAVE_EXTENDED_MASTER
     if (ssl->options.haveEMS) {
         word32 hashSz = HSHASH_SZ;

+ 6 - 0
src/tls13.c

@@ -13729,6 +13729,12 @@ int tls13ShowSecrets(WOLFSSL* ssl, int id, const unsigned char* secret,
             str = "SERVER_TRAFFIC_SECRET_0"; break;
         case EXPORTER_SECRET:
             str = "EXPORTER_SECRET"; break;
+        default:
+#ifdef WOLFSSL_SSLKEYLOGFILE_OUTPUT
+            XFCLOSE(fp);
+#endif
+            return BAD_FUNC_ARG;
+            break;
     }
 
     fprintf(fp, "%s ", str);

+ 67 - 20
sslSniffer/README.md

@@ -39,13 +39,18 @@ The STARTTLS option allows the sniffer to receive and ignore plaintext before re
 
 `./configure --enable-sniffer CPPFLAGS=-DSTARTTLS_ALLOWED`
 
+The SSL KeyLog file option enables the sniffer to decrypt TLS traffic using the master secret obtained from a [NSS keylog file](https://web.archive.org/web/20220531072242/https://firefox-source-docs.mozilla.org/security/nss/legacy/key_log_format/index.html). This allows the sniffer to decrypt all TLS traffic, even for TLS connections using ephemeral cipher suites. Currently, sniffer keylog file support is limited to TLSv1.2 traffic. WolfSSL can be configured to export a keylog file using the `-DSHOW_SECRETS -DHAVE_SECRET_CALLBACK -DWOLFSSL_SSLKEYLOGFILE` macros, independently from the sniffer feature (NOTE: never do this in a production environment, as it is inherently insecure). To enable sniffer support for keylog files,
+use the following configure command line and build as before:
+
+`./configure --enable-sniffer CPPFLAGS=-DWOLFSSL_SNIFFER_KEYLOGFILE`
+
 All options may be enabled with the following configure command line:
 
 ```sh
 ./configure --enable-sniffer \
     CPPFLAGS="-DWOLFSSL_SNIFFER_STATS -DWOLFSSL_SNIFFER_WATCH \
     -DWOLFSSL_SNIFFER_STORE_DATA_CB -DWOLFSSL_SNIFFER_CHAIN_INPUT \
-    -DSTARTTLS_ALLOWED"
+    -DSTARTTLS_ALLOWED -DWOLFSSL_SNIFFER_KEYLOGFILE"
 ```
 
 To add some other cipher support to the sniffer, you can add options like:
@@ -88,7 +93,11 @@ To build with OCTEON III support for a Linux host:
 
 ## Command Line Options
 
-The wolfSSL sniffer includes a test application `snifftest` in the `sslSniffer/sslSnifferTest/` directory. The command line application has several options that can be passed in at runtime to change the default behavior of the application. To execute a “live” sniff just run the application without any parameters and then pick an interface to sniff on followed by the port.
+The wolfSSL sniffer includes a test application `snifftest` in the `sslSniffer/sslSnifferTest/` directory. The command line application has two sniffing modes: "live" mode and "offline" mode. In "live" mode, the application will prompt the user for network information and other parameters and then actively sniff real network traffic on an interface. In "offline" mode, the user provides the application with a pcap file and other network information via command line arguments, and the sniffer
+will then decrypt the relevant TLS traffic captured in the pcap file.
+
+### Live Sniff Mode
+To execute a “live” sniff just run the application without any parameters and then pick an interface to sniff on followed by the port.
 
 An example startup may look like this:
 
@@ -116,41 +125,50 @@ The above example sniffs on the localhost interface (lo0) with the default wolfS
 
 Trace output will be written to a file named `tracefile.txt`.
 
-To decode a previously saved pcap file you will need to enter a few parameters.
+### Offline Sniff Mode
+
+Offline mode allows traffic to be decoded from a previously saved pcap file. To run the sniffer in offline mode, you will need to provide the application with some command line arguments, some of which are mandatory and some of which are optional
 
-The following table lists the accepted inputs in saved file mode.
+The following table lists the accepted inputs in offline mode.
 
 Synopsis:
 
-`snifftest  dumpFile pemKey [server] [port] [password] [threads]`
+`snifftest -pcap pcap_arg -key key_arg [-password password_arg] [-server server_arg] [-port port_arg] [-keylogfile keylogfile_arg] [-threads threads_arg]`
 
 `snifftest` Options Summary:
 
 ```
-Option      Description                                 Default Value
-dumpFile    A previously saved pcap file                NA
-pemKey      The server’s private key in PEM format      NA
-server      The server’s IP address (v4 or v6)          127.0.0.1
-port        The server port to sniff                    443
-password    Private Key Password if required            NA
-threads     The number of threads to run with           5
+Option           Description                                 Default Value   Mandatory
+pcap_arg         A previously saved pcap file                NA              Y
+key_arg          The server’s private key in PEM format      NA              Y
+password_arg     Private Key Password if required            NA              N
+server_arg       The server’s IP address (v4 or v6)          127.0.0.1       N
+port_arg         The server port to sniff                    443             N
+threads          The number of threads to run with           5               N
+keylogfile_arg   Keylog file containing decryption secrets   NA              N
 ```
 
 To decode a pcap file named test.pcap with a server key file called myKey.pem that was generated on the localhost with a server at port 443 just use:
 
-`./snifftest test.pcap myKey.pem`
+`./snifftest -pcap test.pcap -key myKey.pem`
 
 If the server was on 10.0.1.2 and on port 12345 you could instead use:
 
-`./snifftest test.pcap myKey.pem 10.0.1.2 12345`
+`./snifftest -pcap test.pcap -key myKey.pem -server 10.0.1.2 -port 12345`
 
 If the server was on localhost using IPv6 and on port 12345 you could instead use:
 
-`./snifftest test.pcap myKey.pem ::1 12345`
+`./snifftest -pcap test.pcap -key myKey.pem -server ::1 -port 12345`
 
 If you wanted to use 15 threads to decode `test.pcap` and your key does not require a password, you could use a dummy password and run:
 
-`./snifftest test.pcap myKey.pem 10.0.1.2 12345 pass 15`
+`./snifftest -pcap test.pcap -key myKey.pem -server 10.0.1.2 -port 12345 -password pass -threads 15`
+
+If the server exported its secrets in a [NSS keylog file](https://web.archive.org/web/20220531072242/https://firefox-source-docs.mozilla.org/security/nss/legacy/key_log_format/index.html)
+named "sslkeylog.log", you could decrypt the traffic using:
+
+`./snifftest -pcap test.pcap -key myKey.pem -server 10.0.1.2 -port 12345 -keylogfile /path/to/sslkeylog.log`
+
 
 ## API Usage
 
@@ -164,7 +182,7 @@ Use the include `#include <wolfssl/sniffer.h>`.
 void ssl_InitSniffer(void);
 ```
 
-Initializes the wolfSSL sniffer for use and should be called once per application.  
+Initializes the wolfSSL sniffer for use and should be called once per application.
 
 ### ssl_FreeSniffer
 
@@ -285,8 +303,8 @@ Return Values:
 ### ssl_SetEphemeralKey
 
 ```c
-int ssl_SetEphemeralKey(const char* address, int port, 
-                        const char* keyFile, int typeKey, 
+int ssl_SetEphemeralKey(const char* address, int port,
+                        const char* keyFile, int typeKey,
                         const char* password, char* error)
 ```
 Creates a sniffer session based on the `serverAddress` and `port` inputs using ECC or DH static ephemeral key.
@@ -300,6 +318,35 @@ Return Values:
 * 0 on success
 * -1 if a problem occurred, the string error will hold a message describing the problem
 
+### ssl_LoadSecretsFromKeyLogFile
+
+```c
+int ssl_LoadSecretsFromKeyLogFile(const char* keylogfile, char* error)
+```
+
+Loads secrets to decrypt TLS traffic from a keylog file. Only sniffer servers registered with `ssl_createKeyLogSnifferServer()` will be able to decrypt using these secrets
+
+This function requires that sniffer keylog file support (`WOLFSSL_SNIFFER_KEYLOGFILE`) is enabled in the build. Keylog file sniffing is only supported for TLS 1.2 traffic.
+
+Return Values:
+* 0 on success
+* -1 if a problem occurred, the string error will hold a message describing the problem
+
+### ssl_CreateKeyLogSnifferServer
+
+```c
+int ssl_CreateKeyLogSnifferServer(const char* address, int port, char* error)
+```
+
+Creates a sniffer session based on `serverAddress` and `port`, and uses secrets obtained from a keylog file to decrypt traffic. Keylog files should be loaded using `ssl_LoadSecretsFromKeyLogFile()`.
+
+This function requires that sniffer keylog file support (`WOLFSSL_SNIFFER_KEYLOGFILE`) is enabled in the build. Keylog file sniffing is only supported for TLS 1.2 traffic.
+
+Return Values:
+* 0 on success
+* -1 if a problem occurred, the string error will hold a message describing the problem
+
+
 ### ssl_DecodePacket
 
 ```c
@@ -525,7 +572,7 @@ Return Values:
 ### ssl_SetWatchKey_buffer
 
 ```c
-int ssl_SetWatchKey_buffer(void* vSniffer, const unsigned char* key, 
+int ssl_SetWatchKey_buffer(void* vSniffer, const unsigned char* key,
     unsigned int keySz, int keyType, char* error);
 ```
 

+ 105 - 44
sslSniffer/sslSnifferTest/snifftest.c

@@ -144,6 +144,8 @@ enum {
     #endif
 #endif
 
+#define DEFAULT_SERVER_IP   "127.0.0.1"
+#define DEFAULT_SERVER_PORT (443)
 
 #ifdef WOLFSSL_SNIFFER_WATCH
 static const byte rsaHash[] = {
@@ -470,20 +472,12 @@ static void show_appinfo(void)
     #ifdef WOLFSSL_STATIC_DH
         "dh_static "
     #endif
+    #ifdef WOLFSSL_SNIFFER_KEYLOGFILE
+        "ssl_keylog_file "
+    #endif /* WOLFSSL_SNIFFER_KEYLOGFILE */
     "\n\n"
     );
 }
-static void show_usage(void)
-{
-    printf("usage:\n");
-    printf("\t./snifftest\n");
-    printf("\t\tprompts for options\n");
-#ifdef THREADED_SNIFFTEST
-    printf("\t./snifftest dump pemKey [server] [port] [password] [threads]\n");
-#else
-    printf("\t./snifftest dump pemKey [server] [port] [password]\n");
-#endif
-}
 
 typedef struct SnifferPacket {
     byte* packet;
@@ -955,7 +949,6 @@ int main(int argc, char** argv)
     int          ret = 0;
     int          hadBadPacket = 0;
     int          inum = 0;
-    int          port = 0;
     int          saveFile = 0;
     int          i = 0, defDev = 0;
     int          packetNumber = 0;
@@ -963,9 +956,13 @@ int main(int argc, char** argv)
     char         err[PCAP_ERRBUF_SIZE];
     char         filter[32];
     const char  *keyFilesSrc = NULL;
+#ifdef WOLFSSL_SNIFFER_KEYLOGFILE
+    const char  *sslKeyLogFile = NULL;
+#endif /* WOLFSSL_SNIFFER_KEYLOGFILE */
     char         keyFilesBuf[MAX_FILENAME_SZ];
     char         keyFilesUser[MAX_FILENAME_SZ];
-    const char  *server = NULL;
+    const char  *server = DEFAULT_SERVER_IP;
+    int          port   = DEFAULT_SERVER_PORT;
     const char  *sniName = NULL;
     const char  *passwd = NULL;
     pcap_if_t   *d;
@@ -977,18 +974,13 @@ int main(int argc, char** argv)
     workerThreadCount = 1;
 #else
     workerThreadCount = 5;
-    if (argc >= 7)
-        workerThreadCount = XATOI(argv[6]);
 #endif
-    SnifferWorker workers[workerThreadCount];
-    int           used[workerThreadCount];
 #endif
 
     show_appinfo();
 
     signal(SIGINT, sig_handler);
 
-
 #ifndef THREADED_SNIFFTEST
     #ifndef _WIN32
     ssl_InitSniffer();   /* dll load on Windows */
@@ -1140,51 +1132,117 @@ int main(int argc, char** argv)
             }
         }
     }
-    else if (argc >= 3) {
-        saveFile = 1;
-        pcap = pcap_open_offline(argv[1], err);
-        if (pcap == NULL) {
-            printf("pcap_open_offline failed %s\n", err);
-            ret = -1;
+    else {
+        char *pcapFile = NULL;
+
+        for (i = 1; i < argc; i++) {
+            if (strcmp(argv[i], "-pcap") == 0 && i + 1 < argc) {
+                pcapFile = argv[++i];
+            }
+            else if (strcmp(argv[i], "-key") == 0 && i + 1 < argc) {
+                keyFilesSrc = argv[++i];
+            }
+            else if (strcmp(argv[i], "-server") == 0 && i + 1 < argc) {
+                server = argv[++i];
+            }
+            else if (strcmp(argv[i], "-port") == 0 && i + 1 < argc) {
+                port = XATOI(argv[++i]);
+            }
+            else if (strcmp(argv[i], "-password") == 0 && i + 1 < argc) {
+                passwd = argv[++i];
+            }
+#if defined(WOLFSSL_SNIFFER_KEYLOGFILE)
+            else if (strcmp(argv[i], "-keylogfile") == 0 && i + 1 < argc) {
+                sslKeyLogFile = argv[++i];
+            }
+#endif /* WOLFSSL_SNIFFER_KEYLOGFILE */
+#if defined(THREADED_SNIFFTEST)
+            else if (strcmp(argv[i], "-threads") == 0 && i + 1 < argc) {
+                workerThreadCount = XATOI(argv[++i]);
+            }
+#endif /* THREADED_SNIFFTEST */
+            else {
+                fprintf(stderr, "Invalid option or missing argument: %s\n", argv[i]);
+                fprintf(stderr, "Usage: %s -pcap pcap_arg -key key_arg"
+                        " [-password password_arg] [-server server_arg] [-port port_arg]"
+#if defined(WOLFSSL_SNIFFER_KEYLOGFILE)
+                        " [-keylogfile keylogfile_arg]"
+#endif /* WOLFSSL_SNIFFER_KEYLOGFILE */
+#if defined(THREADED_SNIFFTEST)
+                        " [-threads threads_arg]"
+#endif /* THREADED_SNIFFTEST */
+                        "\n", argv[0]);
+                exit(EXIT_FAILURE);
+            }
         }
-        else {
-            /* defaults for server and port */
-            port = 443;
-            server = "127.0.0.1";
-            keyFilesSrc = argv[2];
 
-            if (argc >= 4)
-                server = argv[3];
+        if (!pcapFile) {
+            fprintf(stderr, "Error: -pcap option is required.\n");
+            exit(EXIT_FAILURE);
+        }
 
-            if (argc >= 5)
-                port = XATOI(argv[4]);
+#if defined(WOLFSSL_SNIFFER_KEYLOGFILE)
+        /* If we offer keylog support, then user must provide EITHER a pubkey
+         * OR a keylog file but NOT both */
+        if ((!keyFilesSrc && !sslKeyLogFile) || (keyFilesSrc && sslKeyLogFile)) {
+            fprintf(stderr, "Error: either -key OR -keylogfile option required but NOT both.\n");
+            exit(EXIT_FAILURE);
+        }
+#else
+        if (!keyFilesSrc) {
+            fprintf(stderr, "Error: -key option is required.\n");
+            exit(EXIT_FAILURE);
+        }
+#endif
 
-            if (argc >= 6)
-                passwd = argv[5];
+        saveFile = 1;
+        pcap = pcap_open_offline(pcapFile , err);
+        if (pcap == NULL) {
+            fprintf(stderr, "pcap_open_offline failed %s\n", err);
+            err_sys(err);
+        }
+        else {
+#if defined(WOLFSSL_SNIFFER_KEYLOGFILE)
+            if (sslKeyLogFile != NULL) {
+                ret = ssl_LoadSecretsFromKeyLogFile(sslKeyLogFile, err);
+                if (ret != 0) {
+                    fprintf(stderr, "ERROR=%d, unable to load secrets from keylog file\n",ret);
+                    err_sys(err);
+                }
 
-            ret = load_key(NULL, server, port, keyFilesSrc, passwd, err);
-            if (ret != 0) {
-                exit(EXIT_FAILURE);
+                ret = ssl_CreateKeyLogSnifferServer(server, port, err);
+                if (ret != 0) {
+                    fprintf(stderr, "ERROR=%d, unable to create keylog sniffer server\n",ret);
+                    err_sys(err);
+                }
+            }
+            else
+#endif /* WOLFSSL_SNIFFER_KEYLOGFILE */
+            {
+                ret = load_key(NULL, server, port, keyFilesSrc, passwd, err);
+                if (ret != 0) {
+                    fprintf(stderr, "Failed to load key\n");
+                    err_sys(err);
+                }
             }
 
+
             /* Only let through TCP/IP packets */
             ret = pcap_compile(pcap, &pcap_fp, "(ip6 or ip) and tcp", 0, 0);
             if (ret != 0) {
-                printf("pcap_compile failed %s\n", pcap_geterr(pcap));
+                fprintf(stderr, "pcap_compile failed %s\n", pcap_geterr(pcap));
                 exit(EXIT_FAILURE);
             }
 
             ret = pcap_setfilter(pcap, &pcap_fp);
             if (ret != 0) {
-                printf("pcap_setfilter failed %s\n", pcap_geterr(pcap));
+                fprintf(stderr, "pcap_setfilter failed %s\n", pcap_geterr(pcap));
                 exit(EXIT_FAILURE);
             }
+
+
         }
     }
-    else {
-        show_usage();
-        exit(EXIT_FAILURE);
-    }
 
     if (ret != 0)
         err_sys(err);
@@ -1193,6 +1251,9 @@ 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));
 

+ 11 - 0
wolfssl/internal.h

@@ -279,6 +279,10 @@
 
 #include <wolfssl/wolfcrypt/hpke.h>
 
+#if defined(WOLFSSL_SNIFFER) && defined(WOLFSSL_SNIFFER_KEYLOGFILE)
+#include <wolfssl/sniffer.h>
+#endif /* WOLFSSL_SNIFFER && WOLFSSL_SNIFFER_KEYLOGFILE */
+
 #ifdef __cplusplus
     extern "C" {
 #endif
@@ -2204,6 +2208,8 @@ WOLFSSL_LOCAL int ALPN_Select(WOLFSSL* ssl);
 
 WOLFSSL_LOCAL int ChachaAEADEncrypt(WOLFSSL* ssl, byte* out, const byte* input,
                               word16 sz); /* needed by sniffer */
+WOLFSSL_LOCAL int ChachaAEADDecrypt(WOLFSSL* ssl, byte* plain, const byte* input,
+                              word16 sz); /* needed by sniffer */
 
 #ifdef WOLFSSL_TLS13
 WOLFSSL_LOCAL int  DecryptTls13(WOLFSSL* ssl, byte* output, const byte* input,
@@ -5839,6 +5845,11 @@ struct WOLFSSL {
 #if defined(WOLFSSL_TLS13) && defined(HAVE_ECH)
     WOLFSSL_EchConfig* echConfigs;
 #endif
+
+#if defined(WOLFSSL_SNIFFER) && defined(WOLFSSL_SNIFFER_KEYLOGFILE)
+    SSLSnifferSecretCb snifferSecretCb;
+#endif /* WOLFSSL_SNIFFER && WOLFSSL_SNIFFER_KEYLOGFILE */
+
 };
 
 /*

+ 15 - 0
wolfssl/sniffer.h

@@ -313,6 +313,21 @@ SSL_SNIFFER_API int ssl_PollSniffer(WOLF_EVENT** events, int maxEvents,
 
 #endif /* WOLFSSL_ASYNC_CRYPT */
 
+#ifdef WOLFSSL_SNIFFER_KEYLOGFILE
+
+WOLFSSL_API
+SSL_SNIFFER_API int ssl_CreateKeyLogSnifferServer(const char* address,
+                                                  int port,
+                                                  char* error);
+
+WOLFSSL_API
+SSL_SNIFFER_API int ssl_LoadSecretsFromKeyLogFile(const char* keylogfile,
+                                                  char* error);
+
+typedef int (*SSLSnifferSecretCb)(unsigned char* client_random,
+                                  unsigned char* output_secret);
+
+#endif /* WOLFSSL_SNIFFER_KEYLOGFILE */
 
 
 #ifdef __cplusplus

+ 2 - 0
wolfssl/sniffer_error.h

@@ -142,6 +142,8 @@
 #define SNIFFER_KEY_SETUP_STR 96
 #define UNSUPPORTED_TLS_VER_STR 97
 #define KEY_MISMATCH_STR 98
+
+#define KEYLOG_FILE_INVALID 99
 /* !!!! also add to msgTable in sniffer.c and .rc file !!!! */
 
 

+ 5 - 3
wolfssl/sniffer_error.rc

@@ -1,5 +1,5 @@
 
-STRINGTABLE 
+STRINGTABLE
 {
     1, "Out of Memory"
     2, "New SSL Sniffer Server Registered"
@@ -60,7 +60,7 @@ STRINGTABLE
     48, "Wrong Protocol type"
     49, "Packet Short for header processing"
     50, "Got Unknown Record Type"
-    
+
     51, "Can't Open Trace File"
     52, "Session in Fatal Error State"
     53, "Partial SSL record received"
@@ -72,7 +72,7 @@ STRINGTABLE
     58, "Received an Overlap Duplicate Packet"
     59, "Received an Overlap Reassembly Begin Duplicate Packet"
     60, "Received an Overlap Reassembly End Duplicate Packet"
-    
+
     61, "Missed the Client Hello Entirely"
     62, "Got Hello Request msg"
     63, "Got Session Ticket msg"
@@ -118,4 +118,6 @@ STRINGTABLE
     96, "Setting up keys"
     97, "Unsupported TLS Version"
     98, "Server Client Key Mismatch"
+
+    99, "Invalid or missing keylog file"
 }

+ 2 - 1
wolfssl/wolfcrypt/types.h

@@ -1023,7 +1023,8 @@ typedef struct w64wrapper {
         DYNAMIC_TYPE_SNIFFER_PB_BUFFER  = 1003,
         DYNAMIC_TYPE_SNIFFER_TICKET_ID  = 1004,
         DYNAMIC_TYPE_SNIFFER_NAMED_KEY  = 1005,
-        DYNAMIC_TYPE_SNIFFER_KEY        = 1006
+        DYNAMIC_TYPE_SNIFFER_KEY        = 1006,
+        DYNAMIC_TYPE_SNIFFER_KEYLOG_NODE = 1007
     };
 
     /* max error buffer string size */