Add support for embedding WireGuard keys in a pcapng file

pcapng spec update is here: https://github.com/pcapng/pcapng/pull/62

Bug: 15571
Change-Id: I2f1921b1da70ac0bab8c38dd5138a9dfe7843fea
Reviewed-on: https://code.wireshark.org/review/33300
Petri-Dish: Peter Wu <peter@lekensteyn.nl>
Tested-by: Petri Dish Buildbot
Reviewed-by: Anders Broman <a.broman58@gmail.com>
This commit is contained in:
Peter Wu 2019-05-21 18:06:49 +01:00 committed by Anders Broman
parent 662ad82d60
commit 94b211977a
8 changed files with 91 additions and 30 deletions

View File

@ -345,7 +345,8 @@ additional configuration in protocol preferences.
The file format is described by E<lt>secrets typeE<gt> which can be one of:
I<tls> TLS Key Log as described at L<https://developer.mozilla.org/NSS_Key_Log_Format>
I<tls> TLS Key Log as described at L<https://developer.mozilla.org/NSS_Key_Log_Format>
I<wg> WireGuard Key Log, see L<https://wiki.wireshark.org/WireGuard#Key_Log_Format>
This option may be specified multiple times. The available options for
E<lt>secrets typeE<gt> can be listed with B<--inject-secrets help>.

View File

@ -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

View File

@ -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);

View File

@ -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 }
};

View File

@ -28,6 +28,8 @@
#include <wsutil/file_util.h>
#include <wsutil/wsgcrypt.h>
#include <wsutil/curve25519.h>
#include <epan/secrets.h>
#include <wiretap/secrets-types.h>
#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 */

Binary file not shown.

View File

@ -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, [

View File

@ -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__ */