diff --git a/doc/editcap.pod b/doc/editcap.pod index 5c6c7ac8e0..3cb705ccb4 100644 --- a/doc/editcap.pod +++ b/doc/editcap.pod @@ -345,7 +345,8 @@ additional configuration in protocol preferences. The file format is described by Esecrets typeE which can be one of: -I TLS Key Log as described at L +I TLS Key Log as described at L +I WireGuard Key Log, see L This option may be specified multiple times. The available options for Esecrets typeE can be listed with B<--inject-secrets help>. diff --git a/docbook/release-notes.adoc b/docbook/release-notes.adoc index a72a796fc5..870899835b 100644 --- a/docbook/release-notes.adoc +++ b/docbook/release-notes.adoc @@ -39,6 +39,8 @@ since version 3.0.0: * Brotli decompression support in HTTP/HTTP2 (requires the brotli library). * The build system now checks for a SpeexDSP system library installation. The bundled Speex resampler code is still provided as a fallback. +* WireGuard decryption can now be enabled through keys embedded in a pcapng in + addition to the existing key log preference (wsbuglink:15571[]). // === Removed Features and Support diff --git a/editcap.c b/editcap.c index eb8c20bd9c..948bb4ac7d 100644 --- a/editcap.c +++ b/editcap.c @@ -180,7 +180,8 @@ static const struct { const char *str; guint32 id; } secrets_types[] = { - { "tls", SECRETS_TYPE_TLS }, + { "tls", SECRETS_TYPE_TLS }, + { "wg", SECRETS_TYPE_WIREGUARD }, }; static int find_dct2000_real_data(guint8 *buf); diff --git a/epan/dissectors/file-pcapng.c b/epan/dissectors/file-pcapng.c index c5a2e608fe..64700940e1 100644 --- a/epan/dissectors/file-pcapng.c +++ b/epan/dissectors/file-pcapng.c @@ -537,7 +537,8 @@ static const value_string flags_reception_type_vals[] = { }; static const value_string dsb_secrets_types_vals[] = { - { SECRETS_TYPE_TLS, "TLS Key Log" }, + { SECRETS_TYPE_TLS, "TLS Key Log" }, + { SECRETS_TYPE_WIREGUARD, "WireGuard Key Log" }, { 0, NULL } }; diff --git a/epan/dissectors/packet-wireguard.c b/epan/dissectors/packet-wireguard.c index 6d60573cd6..bc236c2e9a 100644 --- a/epan/dissectors/packet-wireguard.c +++ b/epan/dissectors/packet-wireguard.c @@ -28,6 +28,8 @@ #include #include #include +#include +#include #if GCRYPT_VERSION_NUMBER >= 0x010800 /* 1.8.0 */ /* Decryption requires Curve25519, ChaCha20-Poly1305 (1.7) and Blake2s (1.8). */ @@ -649,6 +651,8 @@ wg_keylog_reset(void) } } +static void wg_keylog_process_lines(const void *data, guint datalen); + static void wg_keylog_read(void) { @@ -695,43 +699,67 @@ wg_keylog_read(void) break; } - gsize bytes_read = strlen(buf); - /* fgets includes the \n at the end of the line. */ - if (bytes_read > 0 && buf[bytes_read - 1] == '\n') { - buf[bytes_read - 1] = 0; - bytes_read--; + wg_keylog_process_lines((const guint8 *)buf, (guint)strlen(buf)); + } +} + +static void +wg_keylog_process_lines(const void *data, guint datalen) +{ + const char *next_line = (const char *)data; + const char *line_end = next_line + datalen; + while (next_line && next_line < line_end) { + /* Note: line is NOT nul-terminated. */ + const char *line = next_line; + next_line = (const char *)memchr(line, '\n', line_end - line); + gssize linelen; + + if (next_line) { + linelen = next_line - line; + next_line++; /* drop LF */ + } else { + linelen = (gssize)(line_end - line); } - if (bytes_read > 0 && buf[bytes_read - 1] == '\r') { - buf[bytes_read - 1] = 0; - bytes_read--; + if (linelen > 0 && line[linelen - 1] == '\r') { + linelen--; /* drop CR */ } - g_debug("Read key log line: %s", buf); + g_debug("Read WG key log line: %.*s", (int)linelen, line); /* Strip leading spaces. */ - char *p = buf; - while (*p == ' ') { + const char *p = line; + while (p < line_end && *p == ' ') { ++p; } - const char *key_type = p; - const char *key_value = NULL; - p = strchr(p, '='); - if (p && key_type != p) { - key_value = p + 1; - /* Strip '=' and spaces before it (after key type). */ - do { - *p = '\0'; - --p; - } while (*p == ' '); - /* Strip spaces after '=' (before key value) */ - while (*key_value == ' ') { - ++key_value; + char key_type[sizeof("LOCAL_EPHEMERAL_PRIVATE_KEY")]; + char key_value[45] = { 0 }; + const char *p0 = p; + p = (const char *)memchr(p0, '=', line_end - p); + if (p && p0 != p) { + /* Extract "key-type" from "key-type = key-value" */ + gsize key_type_len = p - p0; + while (key_type_len && p0[key_type_len - 1] == ' ') { + --key_type_len; + } + if (key_type_len && key_type_len < sizeof(key_type)) { + memcpy(key_type, p0, key_type_len); + key_type[key_type_len] = '\0'; + + /* Skip '=' and any spaces. */ + p = p + 1; + while (p < line_end && *p == ' ') { + ++p; + } + gsize key_value_len = (line + linelen) - p; + if (key_value_len && key_value_len < sizeof(key_value)) { + memcpy(key_value, p, key_value_len); + } } } wg_qqword key; - if (!key_value || !decode_base64_key(&key, key_value)) { - g_debug("Unrecognized key log line: %s", buf); + if (!key_value[0] || !decode_base64_key(&key, key_value)) { + g_debug("Unrecognized key log line: %.*s", (int)linelen, line); continue; } @@ -750,7 +778,7 @@ wg_keylog_read(void) g_debug("Ignored PSK as no new ephemeral key was found"); } } else { - g_debug("Unrecognized key log line: %s", buf); + g_debug("Unrecognized key log line: %.*s", (int)linelen, line); } } } @@ -1857,6 +1885,8 @@ proto_register_wg(void) g_warning("%s: decryption will not be possible due to lack of algorithms support", G_STRFUNC); } + secrets_register_type(SECRETS_TYPE_WIREGUARD, wg_keylog_process_lines); + wg_ephemeral_keys = wmem_map_new_autoreset(wmem_epan_scope(), wmem_file_scope(), g_int_hash, wg_pubkey_equal); #endif /* WG_DECRYPTION_SUPPORTED */ diff --git a/test/captures/wireguard-ping-tcp-dsb.pcapng b/test/captures/wireguard-ping-tcp-dsb.pcapng new file mode 100644 index 0000000000..d15790f395 Binary files /dev/null and b/test/captures/wireguard-ping-tcp-dsb.pcapng differ diff --git a/test/suite_decryption.py b/test/suite_decryption.py index 68f189588a..06cca6367e 100644 --- a/test/suite_decryption.py +++ b/test/suite_decryption.py @@ -723,6 +723,31 @@ class case_decrypt_wireguard(subprocesstest.SubprocessTestCase): self.assertIn('17\t\t\t\t\t\t443', lines) self.assertIn('18\t\t\t\t\t\t49472', lines) + def test_decrypt_wg_full_initiator_dsb(self, run_wireguard_test): + """ + Similar to test_decrypt_full_initiator, but using decryption keys + embedded in the pcapng file. The embedded secrets do not contain leading + spaces nor spaces around the '=' character. + """ + lines = run_wireguard_test(self, [ + '-Tfields', + '-e', 'frame.number', + '-e', 'wg.ephemeral.known_privkey', + '-e', 'wg.static', + '-e', 'wg.timestamp.nanoseconds', + '-e', 'wg.handshake_ok', + '-e', 'icmp.type', + '-e', 'tcp.dstport', + ], pcap_file='wireguard-ping-tcp-dsb.pcapng') + self.assertIn('1\t1\t%s\t%s\t\t\t' % (self.key_Spub_i, '356537872'), lines) + self.assertIn('2\t0\t\t\t1\t\t', lines) + self.assertIn('3\t\t\t\t\t8\t', lines) + self.assertIn('4\t\t\t\t\t0\t', lines) + self.assertIn('13\t1\t%s\t%s\t\t\t' % (self.key_Spub_i, '490514356'), lines) + self.assertIn('14\t0\t\t\t1\t\t', lines) + self.assertIn('17\t\t\t\t\t\t443', lines) + self.assertIn('18\t\t\t\t\t\t49472', lines) + def test_decrypt_full_responder(self, run_wireguard_test): """Check for full handshake decryption using responder secrets.""" lines = run_wireguard_test(self, [ diff --git a/wiretap/secrets-types.h b/wiretap/secrets-types.h index 513b901286..5b1b774f90 100644 --- a/wiretap/secrets-types.h +++ b/wiretap/secrets-types.h @@ -15,5 +15,6 @@ * Type describing the format of the opaque secrets value in a pcapng DSB. */ #define SECRETS_TYPE_TLS 0x544c534b /* TLS Key Log */ +#define SECRETS_TYPE_WIREGUARD 0x57474b4c /* WireGuard Key Log */ #endif /* __SECRETS_TYPES_H__ */