2 * Routines for WireGuard dissection
3 * Copyright 2018, Peter Wu <peter@lekensteyn.nl>
5 * Wireshark - Network traffic analyzer
6 * By Gerald Combs <gerald@wireshark.org>
7 * Copyright 1998 Gerald Combs
9 * SPDX-License-Identifier: GPL-2.0-or-later
13 * Protocol details: https://www.wireguard.com/protocol/
20 /* Start with G_MESSAGES_DEBUG=packet-wireguard to see messages. */
21 #define G_LOG_DOMAIN "packet-wireguard"
23 #include <epan/packet.h>
24 #include <epan/expert.h>
25 #include <epan/prefs.h>
26 #include <epan/proto_data.h>
28 #include <wsutil/file_util.h>
29 #include <wsutil/ws_printf.h> /* ws_g_warning */
30 #include <wsutil/wsgcrypt.h>
31 #include <wsutil/curve25519.h>
33 #if GCRYPT_VERSION_NUMBER >= 0x010800 /* 1.8.0 */
34 /* Decryption requires Curve25519, ChaCha20-Poly1305 (1.7) and Blake2s (1.8). */
35 #define WG_DECRYPTION_SUPPORTED
38 void proto_reg_handoff_wg(void);
39 void proto_register_wg(void);
41 static int proto_wg = -1;
42 static int hf_wg_type = -1;
43 static int hf_wg_reserved = -1;
44 static int hf_wg_sender = -1;
45 static int hf_wg_ephemeral = -1;
46 static int hf_wg_encrypted_static = -1;
47 static int hf_wg_static = -1;
48 static int hf_wg_encrypted_timestamp = -1;
49 static int hf_wg_timestamp_tai64_label = -1;
50 static int hf_wg_timestamp_nanoseconds = -1;
51 static int hf_wg_timestamp_value = -1;
52 static int hf_wg_mac1 = -1;
53 static int hf_wg_mac2 = -1;
54 static int hf_wg_receiver = -1;
55 static int hf_wg_encrypted_empty = -1;
56 static int hf_wg_handshake_ok = -1;
57 static int hf_wg_nonce = -1;
58 static int hf_wg_encrypted_cookie = -1;
59 static int hf_wg_counter = -1;
60 static int hf_wg_encrypted_packet = -1;
61 static int hf_wg_stream = -1;
62 static int hf_wg_response_in = -1;
63 static int hf_wg_response_to = -1;
64 static int hf_wg_receiver_pubkey = -1;
65 static int hf_wg_receiver_pubkey_known_privkey = -1;
66 static int hf_wg_ephemeral_known_privkey = -1;
67 static int hf_wg_static_known_pubkey = -1;
68 static int hf_wg_static_known_privkey = -1;
70 static gint ett_wg = -1;
71 static gint ett_timestamp = -1;
72 static gint ett_key_info = -1;
74 static expert_field ei_wg_bad_packet_length = EI_INIT;
75 static expert_field ei_wg_keepalive = EI_INIT;
76 static expert_field ei_wg_decryption_error = EI_INIT;
78 #ifdef WG_DECRYPTION_SUPPORTED
79 static gboolean pref_dissect_packet = TRUE;
80 static const char *pref_keylog_file;
82 static dissector_handle_t ip_handle;
83 #endif /* WG_DECRYPTION_SUPPORTED */
86 // Length of AEAD authentication tag
87 #define AUTH_TAG_LENGTH 16
90 WG_TYPE_HANDSHAKE_INITIATION = 1,
91 WG_TYPE_HANDSHAKE_RESPONSE = 2,
92 WG_TYPE_COOKIE_REPLY = 3,
93 WG_TYPE_TRANSPORT_DATA = 4
96 static const value_string wg_type_names[] = {
97 { 0x01, "Handshake Initiation" },
98 { 0x02, "Handshake Response" },
99 { 0x03, "Cookie Reply" },
100 { 0x04, "Transport Data" },
104 #ifdef WG_DECRYPTION_SUPPORTED
105 /* Decryption types. {{{ */
107 * Most operations operate on 32 byte units (keys and hash output).
110 #define WG_KEY_LEN 32
111 guchar data[WG_KEY_LEN];
115 * Static key with the MAC1 key pre-computed and an optional private key.
117 typedef struct wg_skey {
120 wg_qqword priv_key; /* Optional, set to all zeroes if missing. */
126 typedef struct wg_ekey {
128 wg_qqword priv_key; /* Optional, set to all zeroes if missing. */
132 * Set of (long-term) static keys (for guessing the peer based on MAC1).
133 * Maps the public key to the "wg_skey_t" structure.
134 * Keys are populated from the UAT and key log file.
136 static GHashTable *wg_static_keys;
139 * Set of ephemeral keys (for decryption). Maps the public key to the
140 * "wg_ekey_t" structure. The private key MUST be available.
141 * Keys are populated from the key log file and wmem_file_scope allocated.
143 static wmem_map_t *wg_ephemeral_keys;
146 * Key log file handle. Opened on demand (when keys are actually looked up),
147 * closed when the capture file closes.
149 static FILE *wg_keylog_file;
151 /* UAT adapter for populating wg_static_keys. */
152 enum { WG_KEY_UAT_PUBLIC, WG_KEY_UAT_PRIVATE };
153 static const value_string wg_key_uat_type_vals[] = {
154 { WG_KEY_UAT_PUBLIC, "Public" },
155 { WG_KEY_UAT_PRIVATE, "Private" },
160 guint key_type; /* See "wg_key_uat_type_vals". */
162 } wg_key_uat_record_t;
164 static wg_key_uat_record_t *wg_key_records;
165 static guint num_wg_key_records;
168 * Input keying material for key derivation/decryption during the handshake.
169 * For the Initiation message, Spub_r and either Spriv_r or Epriv_i must be set.
170 * For the Response message, Epriv_r + Spriv_r or Epriv_r + Epub_i.
172 * The static and ephemeral keys are reset upon UAT changes or are invalidated
173 * when the capture file closes.
176 const wg_skey_t *initiator_skey; /* Spub_i based on Initiation.static (decrypted, null if decryption failed) */
177 const wg_skey_t *responder_skey; /* Spub_r based on Initiation.MAC1 (+Spriv_r if available) */
178 guint8 timestamp[12]; /* Initiation.timestamp (decrypted) */
179 gboolean timestamp_ok : 1; /* Whether the timestamp was successfully decrypted */
180 gboolean empty_ok : 1; /* Whether the empty field was successfully decrypted */
182 /* The following fields are only valid on the initial pass. */
183 const wg_ekey_t *initiator_ekey; /* Epub_i matching Initiation.Ephemeral (+Epriv_i if available) */
184 const wg_ekey_t *responder_ekey; /* Epub_r matching Response.Ephemeral (+Epriv_r if available) */
185 wg_qqword handshake_hash; /* Handshake hash H_i */
186 wg_qqword chaining_key; /* Chaining key C_i */
188 /* Transport ciphers. */
189 gcry_cipher_hd_t initiator_recv_cipher;
190 gcry_cipher_hd_t responder_recv_cipher;
191 } wg_handshake_state_t;
193 /** Hash(CONSTRUCTION), initialized by wg_decrypt_init. */
194 static wg_qqword hash_of_construction;
195 /** Hash(Hash(CONSTRUCTION) || IDENTIFIER), initialized by wg_decrypt_init. */
196 static wg_qqword hash_of_c_identifier;
197 /* Decryption types. }}} */
198 #endif /* WG_DECRYPTION_SUPPORTED */
201 * Information required to process and link messages as required on the first
202 * sequential pass. After that it can be erased.
205 address initiator_address;
206 address responder_address;
207 guint16 initiator_port;
208 guint16 responder_port;
212 * A "session" between two peer is identified by a "sender" id as independently
213 * chosen by each side. In case both peer IDs collide, the source IP and UDP
214 * port number could be used to distinguish sessions. As IDs can be recycled
215 * over time, lookups should use the most recent initiation (or response).
217 * XXX record timestamps (time since last message, for validating timers).
220 guint32 stream; /* Session identifier (akin to udp.stream). */
221 guint32 initiator_frame;
222 guint32 response_frame; /* Responder or Cookie Reply message. */
223 wg_initial_info_t initial; /* Valid only on the first pass. */
224 #ifdef WG_DECRYPTION_SUPPORTED
225 wg_handshake_state_t *hs; /* Handshake state to enable decryption. */
226 #endif /* WG_DECRYPTION_SUPPORTED */
229 /* Per-packet state. */
231 wg_session_t *session;
232 gboolean receiver_is_initiator; /* Whether this transport data packet is sent to an Initiator. */
235 /* Map from Sender/Receiver IDs to a list of session information. */
236 static wmem_map_t *sessions;
237 static guint32 wg_session_count;
240 #ifdef WG_DECRYPTION_SUPPORTED
241 /* Key conversion routines. {{{ */
242 /* Import external random data as private key. */
244 set_private_key(wg_qqword *privkey, const wg_qqword *inkey)
246 // The 254th bit of a Curve25519 secret will always be set in calculations,
247 // use this property to recognize whether a private key is set.
249 privkey->data[31] |= 64;
252 /* Whether a private key is initialized (see set_private_key). */
253 static inline gboolean
254 has_private_key(const wg_qqword *secret)
256 return !!(secret->data[31] & 64);
260 * Compute the Curve25519 public key from a private key.
263 priv_to_pub(wg_qqword *pub, const wg_qqword *priv)
265 int r = crypto_scalarmult_curve25519_base(pub->data, priv->data);
266 /* The computation should always be possible. */
267 DISSECTOR_ASSERT(r == 0);
271 dh_x25519(wg_qqword *shared_secret, const wg_qqword *priv, const wg_qqword *pub)
274 * If the point ("pub") is of small order, of if the result is all zeros, -1
275 * could be returned with Sodium. We are just interpreting the trace, so
276 * just ignore the condition for now.
278 (void)crypto_scalarmult_curve25519(shared_secret->data, priv->data, pub->data);
282 * Returns the string representation (base64) of a public key.
283 * The returned value is allocated with wmem_packet_scope.
286 pubkey_to_string(const wg_qqword *pubkey)
288 gchar *str = g_base64_encode(pubkey->data, WG_KEY_LEN);
289 gchar *ret = wmem_strdup(wmem_packet_scope(), str);
295 decode_base64_key(wg_qqword *out, const char *str)
300 if (strlen(str) + 1 != sizeof(tmp)) {
303 memcpy(tmp, str, sizeof(tmp));
304 g_base64_decode_inplace(tmp, &out_len);
305 if (out_len != WG_KEY_LEN) {
308 memcpy(out->data, tmp, WG_KEY_LEN);
311 /* Key conversion routines. }}} */
314 wg_pubkey_equal(gconstpointer v1, gconstpointer v2)
316 const wg_qqword *pubkey1 = (const wg_qqword *)v1;
317 const wg_qqword *pubkey2 = (const wg_qqword *)v2;
318 return !memcmp(pubkey1->data, pubkey2->data, WG_KEY_LEN);
322 /* Protocol-specific crypto routines. {{{ */
324 * Computes MAC1. Caller must ensure that GCRY_MD_BLAKE2S_256 is available.
327 wg_mac1_key(const wg_qqword *static_public, wg_qqword *mac_key_out)
330 if (gcry_md_open(&hd, GCRY_MD_BLAKE2S_256, 0) == 0) {
331 const char wg_label_mac1[] = "mac1----";
332 gcry_md_write(hd, wg_label_mac1, strlen(wg_label_mac1));
333 gcry_md_write(hd, static_public->data, sizeof(wg_qqword));
334 memcpy(mac_key_out->data, gcry_md_read(hd, 0), sizeof(wg_qqword));
338 // caller should have checked this.
339 DISSECTOR_ASSERT_NOT_REACHED();
343 * Verify that MAC(mac_key, data) matches "mac_output".
346 wg_mac_verify(const wg_qqword *mac_key,
347 const guchar *data, guint data_len, const guint8 mac_output[16])
351 if (gcry_md_open(&hd, GCRY_MD_BLAKE2S_128, 0) == 0) {
353 // not documented by Libgcrypt, but required for keyed blake2s
354 r = gcry_md_setkey(hd, mac_key->data, WG_KEY_LEN);
355 DISSECTOR_ASSERT(r == 0);
356 gcry_md_write(hd, data, data_len);
357 ok = memcmp(mac_output, gcry_md_read(hd, 0), 16) == 0;
360 // caller should have checked this.
361 DISSECTOR_ASSERT_NOT_REACHED();
367 * Update the new chained hash value: h = Hash(h || data).
370 wg_mix_hash(wg_qqword *h, const void *data, size_t data_len)
373 if (gcry_md_open(&hd, GCRY_MD_BLAKE2S_256, 0)) {
374 DISSECTOR_ASSERT_NOT_REACHED();
376 gcry_md_write(hd, h->data, sizeof(wg_qqword));
377 gcry_md_write(hd, data, data_len);
378 memcpy(h, gcry_md_read(hd, 0), sizeof(wg_qqword));
383 * Computes KDF_n(key, input) where n is the number of derived keys.
386 wg_kdf(const wg_qqword *key, const guint8 *input, guint input_len, guint n, wg_qqword *out)
388 guint8 prk[32]; /* Blake2s_256 hash output. */
390 err = hkdf_extract(GCRY_MD_BLAKE2S_256, key->data, sizeof(wg_qqword), input, input_len, prk);
391 DISSECTOR_ASSERT(err == 0);
392 err = hkdf_expand(GCRY_MD_BLAKE2S_256, prk, sizeof(prk), NULL, 0, out->data, 32 * n);
393 DISSECTOR_ASSERT(err == 0);
397 * Must be called before attempting decryption.
400 wg_decrypt_init(void)
402 if (gcry_md_test_algo(GCRY_MD_BLAKE2S_128) != 0 ||
403 gcry_md_test_algo(GCRY_MD_BLAKE2S_256) != 0 ||
404 gcry_cipher_test_algo(GCRY_CIPHER_CHACHA20) != 0) {
407 static const char construction[] = "Noise_IKpsk2_25519_ChaChaPoly_BLAKE2s";
408 gcry_md_hash_buffer(GCRY_MD_BLAKE2S_256, hash_of_construction.data, construction, strlen(construction));
410 static const char wg_identifier[] = "WireGuard v1 zx2c4 Jason@zx2c4.com";
411 memcpy(&hash_of_c_identifier, hash_of_construction.data, sizeof(wg_qqword));
412 wg_mix_hash(&hash_of_c_identifier, wg_identifier, strlen(wg_identifier));
416 static gcry_cipher_hd_t
417 wg_create_cipher(const wg_qqword *key)
420 if (gcry_cipher_open(&hd, GCRY_CIPHER_CHACHA20, GCRY_CIPHER_MODE_POLY1305, 0)) {
424 if (gcry_cipher_setkey(hd, key->data, sizeof(*key))) {
425 gcry_cipher_close(hd);
432 wg_handshake_state_destroy_cb(wmem_allocator_t *allocator _U_, wmem_cb_event_t event _U_, void *user_data)
434 wg_handshake_state_t *hs = (wg_handshake_state_t *)user_data;
436 if (hs->initiator_recv_cipher) {
437 gcry_cipher_close(hs->initiator_recv_cipher);
438 hs->initiator_recv_cipher = NULL;
440 if (hs->responder_recv_cipher) {
441 gcry_cipher_close(hs->responder_recv_cipher);
442 hs->responder_recv_cipher = NULL;
448 * Decrypt ciphertext using the ChaCha20-Poly1305 cipher. The auth tag must be
449 * included with the ciphertext.
452 wg_aead_decrypt(gcry_cipher_hd_t hd, guint64 counter, const guchar *ctext, guint ctext_len, const guchar *aad, guint aad_len, guchar *out, guint out_len)
454 DISSECTOR_ASSERT(ctext_len >= AUTH_TAG_LENGTH);
455 ctext_len -= AUTH_TAG_LENGTH;
456 const guchar *auth_tag = ctext + ctext_len;
458 counter = GUINT64_TO_LE(counter);
459 guchar nonce[12] = { 0 };
460 memcpy(nonce + 4, &counter, 8);
462 return gcry_cipher_setiv(hd, nonce, sizeof(nonce)) == 0 &&
463 gcry_cipher_authenticate(hd, aad, aad_len) == 0 &&
464 gcry_cipher_decrypt(hd, out, out_len, ctext, ctext_len) == 0 &&
465 gcry_cipher_checktag(hd, auth_tag, AUTH_TAG_LENGTH) == 0;
469 * Decrypt ciphertext using the ChaCha20-Poly1305 cipher. The auth tag must be
470 * included with the ciphertext.
473 aead_decrypt(const wg_qqword *key, guint64 counter, const guchar *ctext, guint ctext_len, const guchar *aad, guint aad_len, guchar *out, guint out_len)
475 DISSECTOR_ASSERT(ctext_len >= AUTH_TAG_LENGTH);
477 gcry_cipher_hd_t hd = wg_create_cipher(key);
478 DISSECTOR_ASSERT(hd);
479 gboolean ok = wg_aead_decrypt(hd, counter, ctext, ctext_len, aad, aad_len, out, out_len);
480 gcry_cipher_close(hd);
483 /* Protocol-specific crypto routines. }}} */
486 * Add a static public or private key to "wg_static_keys".
489 wg_add_static_key(const wg_qqword *tmp_key, gboolean is_private)
491 wg_skey_t *key = g_new0(wg_skey_t, 1);
493 set_private_key(&key->priv_key, tmp_key);
494 priv_to_pub(&key->pub_key, tmp_key);
496 key->pub_key = *tmp_key;
499 // If a previous pubkey exists, skip adding the new key. Do add the
500 // secret if it has become known in meantime.
501 wg_skey_t *oldkey = (wg_skey_t *)g_hash_table_lookup(wg_static_keys, &key->pub_key);
503 if (!has_private_key(&oldkey->priv_key) && is_private) {
504 oldkey->priv_key = key->priv_key;
510 // New key, precompute the MAC1 label.
511 wg_mac1_key(&key->pub_key, &key->mac1_key);
513 g_hash_table_insert(wg_static_keys, &key->pub_key, key);
517 * Stores the given ephemeral private key.
520 wg_add_ephemeral_privkey(const wg_qqword *priv_key)
523 priv_to_pub(&pub_key, priv_key);
524 wg_ekey_t *key = (wg_ekey_t *)wmem_map_lookup(wg_ephemeral_keys, &pub_key);
526 key = wmem_new0(wmem_file_scope(), wg_ekey_t);
527 key->pub_key = pub_key;
528 set_private_key(&key->priv_key, priv_key);
529 wmem_map_insert(wg_ephemeral_keys, &key->pub_key, key);
534 /* UAT and key configuration. {{{ */
535 /* XXX this is copied verbatim from packet-ssl-utils.c - create new common API
536 * for retrieval of runtime secrets? */
538 file_needs_reopen(FILE *fp, const char *filename)
540 ws_statb64 open_stat, current_stat;
542 /* consider a file deleted when stat fails for either file,
543 * or when the residing device / inode has changed. */
544 if (0 != ws_fstat64(ws_fileno(fp), &open_stat))
546 if (0 != ws_stat64(filename, ¤t_stat))
549 /* Note: on Windows, ino may be 0. Existing files cannot be deleted on
550 * Windows, but hopefully the size is a good indicator when a file got
551 * removed and recreated */
552 return open_stat.st_dev != current_stat.st_dev ||
553 open_stat.st_ino != current_stat.st_ino ||
554 open_stat.st_size > current_stat.st_size;
558 wg_keylog_reset(void)
560 if (wg_keylog_file) {
561 fclose(wg_keylog_file);
562 wg_keylog_file = NULL;
569 if (!pref_keylog_file || !*pref_keylog_file) {
573 // Reopen file if it got deleted.
574 if (wg_keylog_file && file_needs_reopen(wg_keylog_file, pref_keylog_file)) {
575 g_debug("Key log file got changed or deleted, trying to re-open.");
579 if (!wg_keylog_file) {
580 wg_keylog_file = ws_fopen(pref_keylog_file, "r");
581 if (!wg_keylog_file) {
582 g_debug("Failed to open key log file %s: %s", pref_keylog_file, g_strerror(errno));
585 g_debug("Opened key log file %s", pref_keylog_file);
588 /* File format: each line follows the format "<type>=<key>" (leading spaces
589 * and spaces around '=' as produced by extract-handshakes.sh are ignored).
590 * For available <type>s, see below. <key> is the base64-encoded key (44
594 * LOCAL_STATIC_PRIVATE_KEY = AKeZaHwBxjiKLFnkY2unvEdOTtg4AL+M9dQXfopFVFk=
595 * REMOTE_STATIC_PUBLIC_KEY = YDCttCs9e1J52/g9vEnwJJa+2x6RqaayAYMpSVQfGEY=
596 * LOCAL_EPHEMERAL_PRIVATE_KEY = sLGLJSOQfyz7JNJ5ZDzFf3Uz1rkiCMMjbWerNYcPFFU=
597 * PRESHARED_KEY = AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
602 if (!fgets(buf, sizeof(buf), wg_keylog_file)) {
603 if (feof(wg_keylog_file)) {
604 clearerr(wg_keylog_file);
605 } else if (ferror(wg_keylog_file)) {
606 g_debug("Error while reading %s, closing it.", pref_keylog_file);
612 gsize bytes_read = strlen(buf);
613 /* fgets includes the \n at the end of the line. */
614 if (bytes_read > 0 && buf[bytes_read - 1] == '\n') {
615 buf[bytes_read - 1] = 0;
618 if (bytes_read > 0 && buf[bytes_read - 1] == '\r') {
619 buf[bytes_read - 1] = 0;
623 g_debug("Read key log line: %s", buf);
625 /* Strip leading spaces. */
630 const char *key_type = p;
631 const char *key_value = NULL;
633 if (p && key_type != p) {
635 /* Strip '=' and spaces before it (after key type). */
640 /* Strip spaces after '=' (before key value) */
641 while (*key_value == ' ') {
647 if (!key_value || !decode_base64_key(&key, key_value)) {
648 g_debug("Unrecognized key log line: %s", buf);
652 if (!strcmp(key_type, "LOCAL_STATIC_PRIVATE_KEY")) {
653 wg_add_static_key(&key, TRUE);
654 } else if (!strcmp(key_type, "REMOTE_STATIC_PUBLIC_KEY")) {
655 wg_add_static_key(&key, FALSE);
656 } else if (!strcmp(key_type, "LOCAL_EPHEMERAL_PRIVATE_KEY")) {
657 wg_add_ephemeral_privkey(&key);
658 } else if (!strcmp(key_type, "PRESHARED_KEY")) {
661 g_debug("Unrecognized key log line: %s", buf);
667 wg_key_uat_record_update_cb(void *r, char **error)
669 wg_key_uat_record_t *rec = (wg_key_uat_record_t *)r;
672 /* Check for valid base64-encoding. */
673 if (!decode_base64_key(&key, rec->key)) {
674 *error = g_strdup("Invalid key");
682 wg_key_uat_apply(void)
684 if (!wg_static_keys) {
685 // The first field of "wg_skey_t" is the pubkey (and the table key),
686 // its initial four bytes should be good enough as key hash.
687 wg_static_keys = g_hash_table_new_full(g_int_hash, wg_pubkey_equal, NULL, g_free);
689 g_hash_table_remove_all(wg_static_keys);
692 // As static keys from the key log file also end up in "wg_static_keys",
693 // reset the file pointer such that it will be fully read later.
696 /* Convert base64-encoded strings to wg_skey_t and derive pubkey. */
697 for (guint i = 0; i < num_wg_key_records; i++) {
698 wg_key_uat_record_t *rec = &wg_key_records[i];
699 wg_qqword tmp_key; /* Either public or private, not sure yet. */
701 /* Populate public (and private) keys. */
702 gboolean decoded = decode_base64_key(&tmp_key, rec->key);
703 DISSECTOR_ASSERT(decoded);
704 wg_add_static_key(&tmp_key, rec->key_type == WG_KEY_UAT_PRIVATE);
709 wg_key_uat_reset(void)
711 /* Erase keys when the UAT is unloaded. */
712 g_hash_table_destroy(wg_static_keys);
713 wg_static_keys = NULL;
716 UAT_VS_DEF(wg_key_uat, key_type, wg_key_uat_record_t, guint, WG_KEY_UAT_PUBLIC, "Public")
717 UAT_CSTRING_CB_DEF(wg_key_uat, key, wg_key_uat_record_t)
718 /* UAT and key configuration. }}} */
721 * Tries to decrypt the initiation message.
722 * Assumes responder_skey and initiator_ekey to be set.
725 wg_process_initiation(tvbuff_t *tvb, wg_handshake_state_t *hs)
727 DISSECTOR_ASSERT(hs->responder_skey);
728 DISSECTOR_ASSERT(hs->initiator_ekey);
729 DISSECTOR_ASSERT(hs->initiator_skey == NULL);
731 wg_qqword decrypted_static = {{ 0 }};
732 const gboolean has_Spriv_r = has_private_key(&hs->responder_skey->priv_key);
733 const gboolean has_Epriv_i = has_private_key(&hs->initiator_ekey->priv_key);
735 // Either Spriv_r or Epriv_i + Spriv_i are needed. If the first two are not
736 // available, fail early. Spriv_i will be looked up later.
737 if (!has_Spriv_r && !has_Epriv_i) {
741 const wg_qqword *ephemeral = (const wg_qqword *)tvb_get_ptr(tvb, 8, WG_KEY_LEN);
742 #define WG_ENCRYPTED_STATIC_LENGTH (32 + AUTH_TAG_LENGTH)
743 const guint8 *encrypted_static = (const guint8 *)tvb_get_ptr(tvb, 40, WG_ENCRYPTED_STATIC_LENGTH);
744 #define WG_ENCRYPTED_TIMESTAMP_LENGTH (12 + AUTH_TAG_LENGTH)
745 const guint8 *encrypted_timestamp = (const guint8 *)tvb_get_ptr(tvb, 88, WG_ENCRYPTED_TIMESTAMP_LENGTH);
747 wg_qqword c_and_k[2], h;
748 wg_qqword *c = &c_and_k[0], *k = &c_and_k[1];
749 // c = Hash(CONSTRUCTION)
750 memcpy(c->data, hash_of_construction.data, sizeof(wg_qqword));
751 // h = Hash(c || IDENTIFIER)
752 memcpy(h.data, hash_of_c_identifier.data, sizeof(wg_qqword));
753 // h = Hash(h || Spub_r)
754 wg_mix_hash(&h, hs->responder_skey->pub_key.data, sizeof(wg_qqword));
755 // c = KDF1(c, msg.ephemeral)
756 wg_kdf(c, ephemeral->data, WG_KEY_LEN, 1, c);
757 // h = Hash(h || msg.ephemeral)
758 wg_mix_hash(&h, ephemeral, WG_KEY_LEN);
759 // dh1 = DH(Spriv_r, msg.ephemeral) if kType = R
760 // dh1 = DH(Epriv_i, Spub_r) if kType = I
761 wg_qqword dh1 = {{ 0 }};
763 dh_x25519(&dh1, &hs->responder_skey->priv_key, ephemeral);
765 dh_x25519(&dh1, &hs->initiator_ekey->priv_key, &hs->responder_skey->pub_key);
767 // (c, k) = KDF2(c, dh1)
768 wg_kdf(c, dh1.data, sizeof(dh1), 2, c_and_k);
769 // Spub_i = AEAD-Decrypt(k, 0, msg.static, h)
770 if (!aead_decrypt(k, 0, encrypted_static, WG_ENCRYPTED_STATIC_LENGTH, h.data, sizeof(wg_qqword), decrypted_static.data, sizeof(decrypted_static))) {
773 // Save static public key to the context and lookup private key if possible.
774 wg_skey_t *skey_i = (wg_skey_t *)g_hash_table_lookup(wg_static_keys, &decrypted_static);
776 skey_i = wmem_new0(wmem_file_scope(), wg_skey_t);
777 skey_i->pub_key = decrypted_static;
779 hs->initiator_skey = skey_i;
780 // If Spriv_r is not available, then Epriv_i + Spriv_i must be available.
781 if (!has_Spriv_r && !has_private_key(&hs->initiator_skey->priv_key)) {
785 // h = Hash(h || msg.static)
786 wg_mix_hash(&h, encrypted_static, WG_ENCRYPTED_STATIC_LENGTH);
787 // dh2 = DH(Spriv_r, Spub_i) if kType = R
788 // dh2 = DH(Spriv_i, Spub_r) if kType = I
789 wg_qqword dh2 = {{ 0 }};
791 dh_x25519(&dh2, &hs->responder_skey->priv_key, &hs->initiator_skey->pub_key);
793 dh_x25519(&dh2, &hs->initiator_skey->priv_key, &hs->responder_skey->pub_key);
795 // (c, k) = KDF2(c, dh2)
796 wg_kdf(c, dh2.data, sizeof(wg_qqword), 2, c_and_k);
797 // timestamp = AEAD-Decrypt(k, 0, msg.timestamp, h)
798 if (!aead_decrypt(k, 0, encrypted_timestamp, WG_ENCRYPTED_TIMESTAMP_LENGTH, h.data, sizeof(wg_qqword), hs->timestamp, sizeof(hs->timestamp))) {
801 hs->timestamp_ok = TRUE;
802 // h = Hash(h || msg.timestamp)
803 wg_mix_hash(&h, encrypted_timestamp, WG_ENCRYPTED_TIMESTAMP_LENGTH);
805 // save (h, k) context for responder message processing
806 hs->handshake_hash = h;
807 hs->chaining_key = *c;
811 wg_process_response(tvbuff_t *tvb, wg_handshake_state_t *hs)
813 DISSECTOR_ASSERT(hs->initiator_ekey);
814 DISSECTOR_ASSERT(hs->initiator_skey);
815 DISSECTOR_ASSERT(hs->responder_ekey);
816 DISSECTOR_ASSERT(hs->responder_skey);
817 // XXX when multiple responses are linkable to a single handshake state,
818 // they should probably fork into a new state or be discarded when equal.
819 if (hs->initiator_recv_cipher || hs->responder_recv_cipher) {
820 ws_g_warning("%s FIXME multiple responses linked to a single session", G_STRFUNC);
823 DISSECTOR_ASSERT(!hs->initiator_recv_cipher);
824 DISSECTOR_ASSERT(!hs->responder_recv_cipher);
826 const gboolean has_Epriv_i = has_private_key(&hs->initiator_ekey->priv_key);
827 const gboolean has_Spriv_i = has_private_key(&hs->initiator_skey->priv_key);
828 const gboolean has_Epriv_r = has_private_key(&hs->responder_ekey->priv_key);
830 // Either Epriv_i + Spriv_i or Epriv_r + Epub_i + Spub_i are required.
831 if (!(has_Epriv_i && has_Spriv_i) && !has_Epriv_r) {
835 const wg_qqword *ephemeral = (const wg_qqword *)tvb_get_ptr(tvb, 12, WG_KEY_LEN);
836 const guint8 *encrypted_empty = (const guint8 *)tvb_get_ptr(tvb, 44, AUTH_TAG_LENGTH);
839 wg_qqword *c = &ctk[0], *t = &ctk[1], *k = &ctk[2];
840 h = hs->handshake_hash;
841 *c = hs->chaining_key;
843 // c = KDF1(c, msg.ephemeral)
844 wg_kdf(c, ephemeral->data, WG_KEY_LEN, 1, c);
845 // h = Hash(h || msg.ephemeral)
846 wg_mix_hash(&h, ephemeral, WG_KEY_LEN);
847 // dh1 = DH(Epriv_i, msg.ephemeral) if kType == I
848 // dh1 = DH(Epriv_r, Epub_i) if kType == R
850 if (has_Epriv_i && has_Spriv_i) {
851 dh_x25519(&dh1, &hs->initiator_ekey->priv_key, ephemeral);
853 dh_x25519(&dh1, &hs->responder_ekey->priv_key, &hs->initiator_ekey->pub_key);
856 wg_kdf(c, dh1.data, sizeof(dh1), 1, c);
857 // dh2 = DH(Spriv_i, msg.ephemeral) if kType == I
858 // dh2 = DH(Epriv_r, Spub_i) if kType == R
860 if (has_Epriv_i && has_Spriv_i) {
861 dh_x25519(&dh2, &hs->initiator_skey->priv_key, ephemeral);
863 dh_x25519(&dh2, &hs->responder_ekey->priv_key, &hs->initiator_skey->pub_key);
866 wg_kdf(c, dh2.data, sizeof(dh2), 1, c);
867 // c, t, k = KDF3(c, PSK)
868 // TODO apply PSK from keylog file
869 wg_qqword psk = {{ 0 }};
870 wg_kdf(c, psk.data, WG_KEY_LEN, 3, ctk);
872 wg_mix_hash(&h, t, sizeof(wg_qqword));
873 // empty = AEAD-Decrypt(k, 0, msg.empty, h)
874 if (!aead_decrypt(k, 0, encrypted_empty, AUTH_TAG_LENGTH, h.data, sizeof(wg_qqword), NULL, 0)) {
878 // h = Hash(h || msg.empty)
879 wg_mix_hash(&h, encrypted_empty, AUTH_TAG_LENGTH);
881 // Calculate transport keys and create ciphers.
882 // (Tsend_i = Trecv_r, Trecv_i = Tsend_r) = KDF2(C, "")
883 wg_qqword transport_keys[2];
884 wg_kdf(c, NULL, 0, 2, transport_keys);
886 hs->initiator_recv_cipher = wg_create_cipher(&transport_keys[1]);
887 hs->responder_recv_cipher = wg_create_cipher(&transport_keys[0]);
889 #endif /* WG_DECRYPTION_SUPPORTED */
893 wg_sessions_insert(guint32 id, wg_session_t *session)
895 wmem_list_t *list = (wmem_list_t *)wmem_map_lookup(sessions, GUINT_TO_POINTER(id));
897 list = wmem_list_new(wmem_file_scope());
898 wmem_map_insert(sessions, GUINT_TO_POINTER(id), list);
900 wmem_list_append(list, session);
903 static wg_session_t *
906 wg_session_t *session = wmem_new0(wmem_file_scope(), wg_session_t);
907 session->stream = wg_session_count++;
911 /* Updates the peer address based on the source address. */
913 wg_session_update_address(wg_session_t *session, packet_info *pinfo, gboolean sender_is_initiator)
915 DISSECTOR_ASSERT(!PINFO_FD_VISITED(pinfo));
917 if (sender_is_initiator) {
918 copy_address_wmem(wmem_file_scope(), &session->initial.initiator_address, &pinfo->src);
919 session->initial.initiator_port = (guint16)pinfo->srcport;
921 copy_address_wmem(wmem_file_scope(), &session->initial.responder_address, &pinfo->src);
922 session->initial.responder_port = (guint16)pinfo->srcport;
926 /* Finds an initiation message based on the given Receiver ID that was not
927 * previously associated with a responder message. Returns the session if a
928 * matching initation message can be found or NULL otherwise.
930 static wg_session_t *
931 wg_sessions_lookup_initiation(packet_info *pinfo, guint32 receiver_id)
933 DISSECTOR_ASSERT(!PINFO_FD_VISITED(pinfo));
935 /* Look for the initiation message matching this Receiver ID. */
936 wmem_list_t *list = (wmem_list_t *)wmem_map_lookup(sessions, GUINT_TO_POINTER(receiver_id));
941 /* Walk backwards to find the most recent message first. All packets are
942 * guaranteed to arrive before this frame because this is the first pass. */
943 for (wmem_list_frame_t *item = wmem_list_tail(list); item; item = wmem_list_frame_prev(item)) {
944 wg_session_t *session = (wg_session_t *)wmem_list_frame_data(item);
945 if (session->initial.initiator_port != pinfo->destport ||
946 !addresses_equal(&session->initial.initiator_address, &pinfo->dst)) {
947 /* Responder messages are expected to be sent to the initiator. */
950 if (session->response_frame && session->response_frame != pinfo->num) {
951 /* This session was linked elsewhere. */
955 /* This assumes no malicious messages and no contrived sequences:
956 * Any initiator or responder message is not duplicated nor are these
957 * mutated. If this must be detected, the caller could decrypt or check
958 * mac1 to distinguish valid messages.
966 /* Finds a session with a completed handshake that matches the Receiver ID. */
967 static wg_session_t *
968 wg_sessions_lookup(packet_info *pinfo, guint32 receiver_id, gboolean *receiver_is_initiator)
970 DISSECTOR_ASSERT(!PINFO_FD_VISITED(pinfo));
972 wmem_list_t *list = (wmem_list_t *)wmem_map_lookup(sessions, GUINT_TO_POINTER(receiver_id));
977 /* Walk backwards to find the most recent message first. */
978 for (wmem_list_frame_t *item = wmem_list_tail(list); item; item = wmem_list_frame_prev(item)) {
979 wg_session_t *session = (wg_session_t *)wmem_list_frame_data(item);
980 if (!session->response_frame) {
981 /* Ignore sessions that are not fully established. */
984 if (session->initial.initiator_port == pinfo->destport &&
985 addresses_equal(&session->initial.initiator_address, &pinfo->dst)) {
986 *receiver_is_initiator = TRUE;
987 } else if (session->initial.responder_port == pinfo->destport &&
988 addresses_equal(&session->initial.responder_address, &pinfo->dst)) {
989 *receiver_is_initiator = FALSE;
991 /* Both peers do not match the destination, ignore. */
1000 #ifdef WG_DECRYPTION_SUPPORTED
1002 * Finds the static public key for the receiver of this message based on the
1004 * TODO on PINFO_FD_VISITED, reuse previously discovered keys from session?
1006 static const wg_skey_t *
1007 wg_mac1_key_probe(tvbuff_t *tvb, gboolean is_initiation)
1009 const int mac1_offset = is_initiation ? 116 : 60;
1011 // Shortcut: skip MAC1 validation if no pubkeys are configured.
1012 if (g_hash_table_size(wg_static_keys) == 0) {
1016 const guint8 *mac1_msgdata = tvb_get_ptr(tvb, 0, mac1_offset);
1017 const guint8 *mac1_output = tvb_get_ptr(tvb, mac1_offset, 16);
1018 // Find public key that matches the 16-byte MAC1 field.
1019 GHashTableIter iter;
1021 g_hash_table_iter_init(&iter, wg_static_keys);
1022 while (g_hash_table_iter_next(&iter, NULL, &value)) {
1023 const wg_skey_t *skey = (wg_skey_t *)value;
1024 if (wg_mac_verify(&skey->mac1_key, mac1_msgdata, (guint)mac1_offset, mac1_output)) {
1033 * Builds the handshake decryption state when sufficient keying material is
1034 * available from the initiation message.
1036 static wg_handshake_state_t *
1037 wg_prepare_handshake_keys(const wg_skey_t *skey_r, tvbuff_t *tvb)
1039 wg_handshake_state_t *hs;
1040 gboolean has_r_keys = skey_r && has_private_key(&skey_r->priv_key);
1041 wg_ekey_t *ekey_i = (wg_ekey_t *)wmem_map_lookup(wg_ephemeral_keys, tvb_get_ptr(tvb, 8, WG_KEY_LEN));
1043 // If neither private keys are available, do not create a session.
1044 if (!has_r_keys && !ekey_i) {
1048 // Even if Spriv_r is available, store Epub_i for Response decryption.
1050 ekey_i = wmem_new0(wmem_file_scope(), wg_ekey_t);
1051 tvb_memcpy(tvb, ekey_i->pub_key.data, 8, WG_KEY_LEN);
1054 hs = wmem_new0(wmem_file_scope(), wg_handshake_state_t);
1055 hs->responder_skey = skey_r;
1056 hs->initiator_ekey = ekey_i;
1057 wmem_register_callback(wmem_file_scope(), wg_handshake_state_destroy_cb, hs);
1062 * Processes a Response message, storing additional keys in the state.
1065 wg_prepare_handshake_responder_keys(wg_handshake_state_t *hs, tvbuff_t *tvb)
1067 wg_ekey_t *ekey_r = (wg_ekey_t *)wmem_map_lookup(wg_ephemeral_keys, tvb_get_ptr(tvb, 12, WG_KEY_LEN));
1069 // Response decryption needs Epriv_r (or Epub_r + additional secrets).
1071 ekey_r = wmem_new0(wmem_file_scope(), wg_ekey_t);
1072 tvb_memcpy(tvb, ekey_r->pub_key.data, 12, WG_KEY_LEN);
1075 hs->responder_ekey = ekey_r;
1078 /* Converts a TAI64 label to the seconds since the Unix epoch.
1079 * See https://cr.yp.to/libtai/tai64.html */
1080 static gboolean tai64n_to_unix(guint64 tai64_label, guint32 nanoseconds, nstime_t *nstime)
1082 const guint64 pow2_62 = 1ULL << 62;
1083 if (tai64_label < pow2_62 || tai64_label >= (1ULL << 63) || nanoseconds > 999999999) {
1084 // Seconds before 1970 and values larger than 2^63 (reserved) cannot
1085 // be represented. Nanoseconds must also be valid.
1089 // TODO this can result in loss of precision
1090 nstime->secs = (time_t)(tai64_label - pow2_62);
1091 nstime->nsecs = (int)nanoseconds;
1096 wg_dissect_key_extra(proto_tree *tree, tvbuff_t *tvb, const wg_qqword *pubkey, gboolean is_ephemeral)
1098 guint32 has_private = FALSE;
1102 wg_ekey_t *ekey = (wg_ekey_t *)wmem_map_lookup(wg_ephemeral_keys, pubkey->data);
1103 has_private = ekey && has_private_key(&ekey->priv_key);
1105 wg_skey_t *skey = (wg_skey_t *)g_hash_table_lookup(wg_static_keys, pubkey->data);
1106 has_private = skey && has_private_key(&skey->priv_key);
1107 ti = proto_tree_add_boolean(tree, hf_wg_static_known_pubkey, tvb, 0, 0, !!skey);
1108 PROTO_ITEM_SET_GENERATED(ti);
1111 int hf_known_privkey = is_ephemeral ? hf_wg_ephemeral_known_privkey : hf_wg_static_known_privkey;
1112 ti = proto_tree_add_boolean(tree, hf_known_privkey, tvb, 0, 0, has_private);
1113 PROTO_ITEM_SET_GENERATED(ti);
1115 #endif /* WG_DECRYPTION_SUPPORTED */
1119 wg_dissect_pubkey(proto_tree *tree, tvbuff_t *tvb, int offset, gboolean is_ephemeral)
1121 const guint8 *pubkey = tvb_get_ptr(tvb, offset, 32);
1122 gchar *str = g_base64_encode(pubkey, 32);
1123 gchar *key_str = wmem_strdup(wmem_packet_scope(), str);
1126 int hf_id = is_ephemeral ? hf_wg_ephemeral : hf_wg_static;
1127 #ifdef WG_DECRYPTION_SUPPORTED
1128 proto_item *ti = proto_tree_add_string(tree, hf_id, tvb, offset, 32, key_str);
1129 proto_tree *key_tree = proto_item_add_subtree(ti, ett_key_info);
1130 wg_dissect_key_extra(key_tree, tvb, (const wg_qqword *)pubkey, is_ephemeral);
1132 proto_tree_add_string(tree, hf_id, tvb, offset, 32, key_str);
1136 #ifdef WG_DECRYPTION_SUPPORTED
1138 wg_dissect_decrypted_static(tvbuff_t *tvb, packet_info *pinfo, proto_tree *wg_tree, wg_handshake_state_t *hs)
1142 if (!hs || !hs->initiator_skey) {
1146 new_tvb = tvb_new_child_real_data(tvb, hs->initiator_skey->pub_key.data, WG_KEY_LEN, WG_KEY_LEN);
1147 add_new_data_source(pinfo, new_tvb, "Decrypted Static");
1148 wg_dissect_pubkey(wg_tree, new_tvb, 0, FALSE);
1152 wg_dissect_decrypted_timestamp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, wg_handshake_state_t *hs)
1154 guint64 tai64_label;
1155 guint32 nanoseconds;
1160 if (!hs || !hs->timestamp_ok) {
1164 new_tvb = tvb_new_child_real_data(tvb, hs->timestamp, sizeof(hs->timestamp), sizeof(hs->timestamp));
1165 add_new_data_source(pinfo, new_tvb, "Decrypted Timestamp");
1167 tai64_label = tvb_get_guint64(new_tvb, 0, ENC_BIG_ENDIAN);
1168 nanoseconds = tvb_get_guint32(new_tvb, 8, ENC_BIG_ENDIAN);
1169 if (tai64n_to_unix(tai64_label, nanoseconds, &nstime)) {
1170 ti = proto_tree_add_time(tree, hf_wg_timestamp_value, new_tvb, 0, 12, &nstime);
1171 tree = proto_item_add_subtree(ti, ett_timestamp);
1173 proto_tree_add_item(tree, hf_wg_timestamp_tai64_label, new_tvb, 0, 8, ENC_BIG_ENDIAN);
1174 proto_tree_add_item(tree, hf_wg_timestamp_nanoseconds, new_tvb, 8, 4, ENC_BIG_ENDIAN);
1178 wg_dissect_decrypted_packet(tvbuff_t *tvb, packet_info *pinfo, proto_tree *wg_tree, wg_packet_info_t *wg_pinfo, guint64 counter, gint plain_length)
1180 wg_handshake_state_t *hs = wg_pinfo->session->hs;
1181 gcry_cipher_hd_t cipher = wg_pinfo->receiver_is_initiator ? hs->initiator_recv_cipher : hs->responder_recv_cipher;
1186 DISSECTOR_ASSERT(plain_length >= 0);
1187 const gint ctext_len = plain_length + AUTH_TAG_LENGTH;
1188 const guchar *ctext = tvb_get_ptr(tvb, 16, ctext_len);
1189 guchar *plain = (guchar *)wmem_alloc0(pinfo->pool, (guint)plain_length);
1190 if (!wg_aead_decrypt(cipher, counter, ctext, (guint)ctext_len, NULL, 0, plain, (guint)plain_length)) {
1191 proto_tree_add_expert(wg_tree, pinfo, &ei_wg_decryption_error, tvb, 16, ctext_len);
1194 if (plain_length == 0) {
1198 tvbuff_t *new_tvb = tvb_new_child_real_data(tvb, plain, (guint)plain_length, plain_length);
1199 add_new_data_source(pinfo, new_tvb, "Decrypted Packet");
1201 proto_tree *tree = proto_item_get_parent(wg_tree);
1202 if (!pref_dissect_packet) {
1203 // (IP packet not shown, preference "Dissect transport data" is disabled)
1204 call_data_dissector(new_tvb, pinfo, tree);
1206 call_dissector(ip_handle, new_tvb, pinfo, tree);
1211 wg_dissect_mac1_pubkey(proto_tree *tree, tvbuff_t *tvb, const wg_skey_t *skey)
1219 ti = proto_tree_add_string(tree, hf_wg_receiver_pubkey, tvb, 0, 0, pubkey_to_string(&skey->pub_key));
1220 PROTO_ITEM_SET_GENERATED(ti);
1221 proto_tree *key_tree = proto_item_add_subtree(ti, ett_key_info);
1222 ti = proto_tree_add_boolean(key_tree, hf_wg_receiver_pubkey_known_privkey, tvb, 0, 0, !!has_private_key(&skey->priv_key));
1223 PROTO_ITEM_SET_GENERATED(ti);
1225 #endif /* WG_DECRYPTION_SUPPORTED */
1228 wg_dissect_handshake_initiation(tvbuff_t *tvb, packet_info *pinfo, proto_tree *wg_tree, wg_packet_info_t *wg_pinfo)
1233 #ifdef WG_DECRYPTION_SUPPORTED
1235 const wg_skey_t *skey_r = wg_mac1_key_probe(tvb, TRUE);
1236 wg_handshake_state_t *hs = NULL;
1238 if (!PINFO_FD_VISITED(pinfo)) {
1240 hs = wg_prepare_handshake_keys(skey_r, tvb);
1242 wg_process_initiation(tvb, hs);
1246 hs = wg_pinfo->session->hs;
1248 #endif /* WG_DECRYPTION_SUPPORTED */
1250 proto_tree_add_item_ret_uint(wg_tree, hf_wg_sender, tvb, 4, 4, ENC_LITTLE_ENDIAN, &sender_id);
1251 col_append_fstr(pinfo->cinfo, COL_INFO, ", sender=0x%08X", sender_id);
1252 wg_dissect_pubkey(wg_tree, tvb, 8, TRUE);
1253 proto_tree_add_item(wg_tree, hf_wg_encrypted_static, tvb, 40, 32 + AUTH_TAG_LENGTH, ENC_NA);
1254 #ifdef WG_DECRYPTION_SUPPORTED
1255 wg_dissect_decrypted_static(tvb, pinfo, wg_tree, hs);
1256 #endif /* WG_DECRYPTION_SUPPORTED */
1257 proto_tree_add_item(wg_tree, hf_wg_encrypted_timestamp, tvb, 88, 12 + AUTH_TAG_LENGTH, ENC_NA);
1258 #ifdef WG_DECRYPTION_SUPPORTED
1259 wg_dissect_decrypted_timestamp(tvb, pinfo, wg_tree, hs);
1260 #endif /* WG_DECRYPTION_SUPPORTED */
1261 proto_tree_add_item(wg_tree, hf_wg_mac1, tvb, 116, 16, ENC_NA);
1262 #ifdef WG_DECRYPTION_SUPPORTED
1263 wg_dissect_mac1_pubkey(wg_tree, tvb, skey_r);
1264 #endif /* WG_DECRYPTION_SUPPORTED */
1265 proto_tree_add_item(wg_tree, hf_wg_mac2, tvb, 132, 16, ENC_NA);
1267 if (!PINFO_FD_VISITED(pinfo)) {
1268 /* XXX should an initiation message with the same contents (except MAC2) be
1269 * considered part of the same "session"? */
1270 wg_session_t *session = wg_session_new();
1271 session->initiator_frame = pinfo->num;
1272 wg_session_update_address(session, pinfo, TRUE);
1273 #ifdef WG_DECRYPTION_SUPPORTED
1275 #endif /* WG_DECRYPTION_SUPPORTED */
1276 wg_sessions_insert(sender_id, session);
1277 wg_pinfo->session = session;
1279 wg_session_t *session = wg_pinfo->session;
1281 ti = proto_tree_add_uint(wg_tree, hf_wg_stream, tvb, 0, 0, session->stream);
1282 PROTO_ITEM_SET_GENERATED(ti);
1284 if (session && session->response_frame) {
1285 ti = proto_tree_add_uint(wg_tree, hf_wg_response_in, tvb, 0, 0, session->response_frame);
1286 PROTO_ITEM_SET_GENERATED(ti);
1293 wg_dissect_handshake_response(tvbuff_t *tvb, packet_info *pinfo, proto_tree *wg_tree, wg_packet_info_t *wg_pinfo)
1295 guint32 sender_id, receiver_id;
1297 wg_session_t *session;
1299 #ifdef WG_DECRYPTION_SUPPORTED
1301 const wg_skey_t *skey_i = wg_mac1_key_probe(tvb, FALSE);
1302 #endif /* WG_DECRYPTION_SUPPORTED */
1304 proto_tree_add_item_ret_uint(wg_tree, hf_wg_sender, tvb, 4, 4, ENC_LITTLE_ENDIAN, &sender_id);
1305 col_append_fstr(pinfo->cinfo, COL_INFO, ", sender=0x%08X", sender_id);
1306 proto_tree_add_item_ret_uint(wg_tree, hf_wg_receiver, tvb, 8, 4, ENC_LITTLE_ENDIAN, &receiver_id);
1307 col_append_fstr(pinfo->cinfo, COL_INFO, ", receiver=0x%08X", receiver_id);
1309 if (!PINFO_FD_VISITED(pinfo)) {
1310 session = wg_sessions_lookup_initiation(pinfo, receiver_id);
1311 #ifdef WG_DECRYPTION_SUPPORTED
1312 if (session && session->hs) {
1313 wg_prepare_handshake_responder_keys(session->hs, tvb);
1314 wg_process_response(tvb, session->hs);
1316 #endif /* WG_DECRYPTION_SUPPORTED */
1318 session = wg_pinfo->session;
1321 wg_dissect_pubkey(wg_tree, tvb, 12, TRUE);
1322 proto_tree_add_item(wg_tree, hf_wg_encrypted_empty, tvb, 44, 16, ENC_NA);
1323 #ifdef WG_DECRYPTION_SUPPORTED
1324 if (session && session->hs) {
1325 ti = proto_tree_add_boolean(wg_tree, hf_wg_handshake_ok, tvb, 0, 0, !!session->hs->empty_ok);
1326 PROTO_ITEM_SET_GENERATED(ti);
1328 #endif /* WG_DECRYPTION_SUPPORTED */
1329 proto_tree_add_item(wg_tree, hf_wg_mac1, tvb, 60, 16, ENC_NA);
1330 #ifdef WG_DECRYPTION_SUPPORTED
1331 wg_dissect_mac1_pubkey(wg_tree, tvb, skey_i);
1332 #endif /* WG_DECRYPTION_SUPPORTED */
1333 proto_tree_add_item(wg_tree, hf_wg_mac2, tvb, 76, 16, ENC_NA);
1335 if (!PINFO_FD_VISITED(pinfo)) {
1336 /* XXX should probably check whether decryption succeeds before linking
1337 * and somehow mark that this response is related but not correct. */
1339 session->response_frame = pinfo->num;
1340 wg_session_update_address(session, pinfo, FALSE);
1341 wg_sessions_insert(sender_id, session);
1342 wg_pinfo->session = session;
1346 ti = proto_tree_add_uint(wg_tree, hf_wg_stream, tvb, 0, 0, session->stream);
1347 PROTO_ITEM_SET_GENERATED(ti);
1348 ti = proto_tree_add_uint(wg_tree, hf_wg_response_to, tvb, 0, 0, session->initiator_frame);
1349 PROTO_ITEM_SET_GENERATED(ti);
1356 wg_dissect_handshake_cookie(tvbuff_t *tvb, packet_info *pinfo, proto_tree *wg_tree, wg_packet_info_t *wg_pinfo)
1358 guint32 receiver_id;
1361 proto_tree_add_item_ret_uint(wg_tree, hf_wg_receiver, tvb, 4, 4, ENC_LITTLE_ENDIAN, &receiver_id);
1362 col_append_fstr(pinfo->cinfo, COL_INFO, ", receiver=0x%08X", receiver_id);
1363 proto_tree_add_item(wg_tree, hf_wg_nonce, tvb, 8, 24, ENC_NA);
1364 proto_tree_add_item(wg_tree, hf_wg_encrypted_cookie, tvb, 32, 16 + AUTH_TAG_LENGTH, ENC_NA);
1366 wg_session_t *session;
1367 if (!PINFO_FD_VISITED(pinfo)) {
1368 /* Check for Cookie Reply from Responder to Initiator. */
1369 session = wg_sessions_lookup_initiation(pinfo, receiver_id);
1371 session->response_frame = pinfo->num;
1372 wg_session_update_address(session, pinfo, FALSE);
1373 wg_pinfo->session = session;
1375 /* XXX check for cookie reply from Initiator to Responder */
1377 session = wg_pinfo->session;
1380 ti = proto_tree_add_uint(wg_tree, hf_wg_stream, tvb, 0, 0, session->stream);
1381 PROTO_ITEM_SET_GENERATED(ti);
1382 /* XXX check for cookie reply from Initiator to Responder */
1383 ti = proto_tree_add_uint(wg_tree, hf_wg_response_to, tvb, 0, 0, session->initiator_frame);
1384 PROTO_ITEM_SET_GENERATED(ti);
1391 wg_dissect_data(tvbuff_t *tvb, packet_info *pinfo, proto_tree *wg_tree, wg_packet_info_t *wg_pinfo)
1393 guint32 receiver_id;
1397 proto_tree_add_item_ret_uint(wg_tree, hf_wg_receiver, tvb, 4, 4, ENC_LITTLE_ENDIAN, &receiver_id);
1398 col_append_fstr(pinfo->cinfo, COL_INFO, ", receiver=0x%08X", receiver_id);
1399 proto_tree_add_item_ret_uint64(wg_tree, hf_wg_counter, tvb, 8, 8, ENC_LITTLE_ENDIAN, &counter);
1400 col_append_fstr(pinfo->cinfo, COL_INFO, ", counter=%" G_GUINT64_FORMAT, counter);
1402 gint packet_length = tvb_captured_length_remaining(tvb, 16);
1403 if (packet_length < AUTH_TAG_LENGTH) {
1404 proto_tree_add_expert(wg_tree, pinfo, &ei_wg_bad_packet_length, tvb, 16, packet_length);
1405 return 16 + packet_length;
1406 } else if (packet_length != AUTH_TAG_LENGTH) {
1407 /* Keepalive messages are already marked, no need to append data length. */
1408 col_append_fstr(pinfo->cinfo, COL_INFO, ", datalen=%d", packet_length - AUTH_TAG_LENGTH);
1410 ti = proto_tree_add_item(wg_tree, hf_wg_encrypted_packet, tvb, 16, packet_length, ENC_NA);
1412 if (packet_length == AUTH_TAG_LENGTH) {
1413 expert_add_info(pinfo, ti, &ei_wg_keepalive);
1416 wg_session_t *session;
1417 if (!PINFO_FD_VISITED(pinfo)) {
1418 gboolean receiver_is_initiator;
1419 session = wg_sessions_lookup(pinfo, receiver_id, &receiver_is_initiator);
1421 wg_session_update_address(session, pinfo, !receiver_is_initiator);
1422 wg_pinfo->session = session;
1423 wg_pinfo->receiver_is_initiator = receiver_is_initiator;
1426 session = wg_pinfo->session;
1429 ti = proto_tree_add_uint(wg_tree, hf_wg_stream, tvb, 0, 0, session->stream);
1430 PROTO_ITEM_SET_GENERATED(ti);
1433 #ifdef WG_DECRYPTION_SUPPORTED
1434 if (session && session->hs) {
1435 wg_dissect_decrypted_packet(tvb, pinfo, wg_tree, wg_pinfo, counter, packet_length - AUTH_TAG_LENGTH);
1437 #endif /* WG_DECRYPTION_SUPPORTED */
1439 return 16 + packet_length;
1443 dissect_wg(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_)
1446 proto_tree *wg_tree;
1447 guint32 message_type;
1448 const char *message_type_str;
1449 wg_packet_info_t *wg_pinfo;
1451 /* Heuristics check: check for reserved bits (zeros) and message type. */
1452 if (tvb_reported_length(tvb) < 4 || tvb_get_ntoh24(tvb, 1) != 0)
1455 message_type = tvb_get_guint8(tvb, 0);
1456 message_type_str = try_val_to_str(message_type, wg_type_names);
1457 if (!message_type_str)
1460 /* Special case: zero-length data message is a Keepalive message. */
1461 if (message_type == WG_TYPE_TRANSPORT_DATA && tvb_reported_length(tvb) == 32) {
1462 message_type_str = "Keepalive";
1465 col_set_str(pinfo->cinfo, COL_PROTOCOL, "WireGuard");
1466 col_set_str(pinfo->cinfo, COL_INFO, message_type_str);
1468 ti = proto_tree_add_item(tree, proto_wg, tvb, 0, -1, ENC_NA);
1469 wg_tree = proto_item_add_subtree(ti, ett_wg);
1471 proto_tree_add_item(wg_tree, hf_wg_type, tvb, 0, 1, ENC_NA);
1472 proto_tree_add_item(wg_tree, hf_wg_reserved, tvb, 1, 3, ENC_NA);
1474 if (!PINFO_FD_VISITED(pinfo)) {
1475 wg_pinfo = wmem_new0(wmem_file_scope(), wg_packet_info_t);
1476 p_add_proto_data(wmem_file_scope(), pinfo, proto_wg, 0, wg_pinfo);
1478 wg_pinfo = (wg_packet_info_t *)p_get_proto_data(wmem_file_scope(), pinfo, proto_wg, 0);
1481 switch ((wg_message_type)message_type) {
1482 case WG_TYPE_HANDSHAKE_INITIATION:
1483 return wg_dissect_handshake_initiation(tvb, pinfo, wg_tree, wg_pinfo);
1484 case WG_TYPE_HANDSHAKE_RESPONSE:
1485 return wg_dissect_handshake_response(tvb, pinfo, wg_tree, wg_pinfo);
1486 case WG_TYPE_COOKIE_REPLY:
1487 return wg_dissect_handshake_cookie(tvb, pinfo, wg_tree, wg_pinfo);
1488 case WG_TYPE_TRANSPORT_DATA:
1489 return wg_dissect_data(tvb, pinfo, wg_tree, wg_pinfo);
1492 DISSECTOR_ASSERT_NOT_REACHED();
1498 wg_session_count = 0;
1502 proto_register_wg(void)
1504 #ifdef WG_DECRYPTION_SUPPORTED
1505 module_t *wg_module;
1506 #endif /* WG_DECRYPTION_SUPPORTED */
1507 expert_module_t *expert_wg;
1509 static hf_register_info hf[] = {
1510 /* Initiation message */
1512 { "Type", "wg.type",
1513 FT_UINT8, BASE_DEC, VALS(wg_type_names), 0x0,
1517 { "Reserved", "wg.reserved",
1518 FT_NONE, BASE_NONE, NULL, 0x0,
1522 { "Sender", "wg.sender",
1523 FT_UINT32, BASE_HEX, NULL, 0x0,
1524 "Identifier as chosen by the sender", HFILL }
1527 { "Ephemeral", "wg.ephemeral",
1528 FT_STRING, BASE_NONE, NULL, 0x0,
1529 "Ephemeral public key of sender", HFILL }
1531 { &hf_wg_encrypted_static,
1532 { "Encrypted Static", "wg.encrypted_static",
1533 FT_NONE, BASE_NONE, NULL, 0x0,
1534 "Encrypted long-term static public key of sender", HFILL }
1537 { "Static Public Key", "wg.static",
1538 FT_STRING, BASE_NONE, NULL, 0x0,
1539 "Long-term static public key of sender", HFILL }
1541 { &hf_wg_encrypted_timestamp,
1542 { "Encrypted Timestamp", "wg.encrypted_timestamp",
1543 FT_NONE, BASE_NONE, NULL, 0x0,
1546 { &hf_wg_timestamp_tai64_label,
1547 { "TAI64 Label", "wg.timestamp.tai64_label",
1548 FT_UINT64, BASE_DEC, NULL, 0x0,
1551 { &hf_wg_timestamp_nanoseconds,
1552 { "Nanoseconds", "wg.timestamp.nanoseconds",
1553 FT_UINT32, BASE_DEC, NULL, 0x0,
1556 { &hf_wg_timestamp_value,
1557 { "Timestamp", "wg.timestamp.value",
1558 FT_ABSOLUTE_TIME, ABSOLUTE_TIME_UTC, NULL, 0x0,
1562 { "mac1", "wg.mac1",
1563 FT_BYTES, BASE_NONE, NULL, 0x0,
1567 { "mac2", "wg.mac2",
1568 FT_BYTES, BASE_NONE, NULL, 0x0,
1572 /* Response message */
1574 { "Receiver", "wg.receiver",
1575 FT_UINT32, BASE_HEX, NULL, 0x0,
1576 "Identifier as chosen by receiver", HFILL }
1578 { &hf_wg_encrypted_empty,
1579 { "Encrypted Empty", "wg.encrypted_empty",
1580 FT_NONE, BASE_NONE, NULL, 0x0,
1581 "Authenticated encryption of an empty string", HFILL }
1583 { &hf_wg_handshake_ok,
1584 { "Handshake decryption successful", "wg.handshake_ok",
1585 FT_BOOLEAN, BASE_NONE, NULL, 0x0,
1586 "Whether decryption keys were successfully derived", HFILL }
1589 /* Cookie message */
1591 { "Nonce", "wg.nonce",
1592 FT_BYTES, BASE_NONE, NULL, 0x0,
1595 { &hf_wg_encrypted_cookie,
1596 { "Encrypted Cookie", "wg.encrypted_cookie",
1597 FT_BYTES, BASE_NONE, NULL, 0x0,
1600 /* TODO decrypted cookie field. */
1604 { "Counter", "wg.counter",
1605 FT_UINT64, BASE_DEC, NULL, 0x0,
1608 { &hf_wg_encrypted_packet,
1609 { "Encrypted Packet", "wg.encrypted_packet",
1610 FT_NONE, BASE_NONE, NULL, 0x0,
1614 /* Association tracking. */
1616 { "Stream index", "wg.stream",
1617 FT_UINT32, BASE_DEC, NULL, 0x0,
1618 "Identifies a session in this capture file", HFILL }
1620 { &hf_wg_response_in,
1621 { "Response in Frame", "wg.response_in",
1622 FT_FRAMENUM, BASE_NONE, FRAMENUM_TYPE(FT_FRAMENUM_RESPONSE), 0x0,
1623 "The response to this initiation message is in this frame", HFILL }
1625 { &hf_wg_response_to,
1626 { "Response to Frame", "wg.response_to",
1627 FT_FRAMENUM, BASE_NONE, FRAMENUM_TYPE(FT_FRAMENUM_REQUEST), 0x0,
1628 "This is a response to the initiation message in this frame", HFILL }
1631 /* Additional fields. */
1632 { &hf_wg_receiver_pubkey,
1633 { "Receiver Static Public Key", "wg.receiver_pubkey",
1634 FT_STRING, BASE_NONE, NULL, 0x0,
1635 "Public key of the receiver (matched based on MAC1)", HFILL }
1637 { &hf_wg_receiver_pubkey_known_privkey,
1638 { "Has Private Key", "wg.receiver_pubkey.known_privkey",
1639 FT_BOOLEAN, BASE_NONE, NULL, 0x0,
1640 "Whether the corresponding private key is known (configured via prefs)", HFILL }
1642 { &hf_wg_ephemeral_known_privkey,
1643 { "Has Private Key", "wg.ephemeral.known_privkey",
1644 FT_BOOLEAN, BASE_NONE, NULL, 0x0,
1645 "Whether the corresponding private key is known (configured via prefs)", HFILL }
1647 { &hf_wg_static_known_pubkey,
1648 { "Known Public Key", "wg.static.known_pubkey",
1649 FT_BOOLEAN, BASE_NONE, NULL, 0x0,
1650 "Whether this public key is known (configured via prefs)", HFILL }
1652 { &hf_wg_static_known_privkey,
1653 { "Has Private Key", "wg.static.known_privkey",
1654 FT_BOOLEAN, BASE_NONE, NULL, 0x0,
1655 "Whether the corresponding private key is known (configured via prefs)", HFILL }
1659 static gint *ett[] = {
1665 static ei_register_info ei[] = {
1666 { &ei_wg_bad_packet_length,
1667 { "wg.bad_packet_length", PI_MALFORMED, PI_ERROR,
1668 "Packet length is too small", EXPFILL }
1671 { "wg.keepalive", PI_SEQUENCE, PI_CHAT,
1672 "This is a Keepalive message", EXPFILL }
1674 { &ei_wg_decryption_error,
1675 { "wg.decryption_error", PI_DECRYPTION, PI_WARN,
1676 "Packet data decryption failed", EXPFILL }
1680 #ifdef WG_DECRYPTION_SUPPORTED
1681 /* UAT for header fields */
1682 static uat_field_t wg_key_uat_fields[] = {
1683 UAT_FLD_VS(wg_key_uat, key_type, "Key type", wg_key_uat_type_vals, "Public or Private"),
1684 UAT_FLD_CSTRING(wg_key_uat, key, "Key", "Base64-encoded key"),
1687 #endif /* WG_DECRYPTION_SUPPORTED */
1689 proto_wg = proto_register_protocol("WireGuard Protocol", "WireGuard", "wg");
1691 proto_register_field_array(proto_wg, hf, array_length(hf));
1692 proto_register_subtree_array(ett, array_length(ett));
1694 expert_wg = expert_register_protocol(proto_wg);
1695 expert_register_field_array(expert_wg, ei, array_length(ei));
1697 register_dissector("wg", dissect_wg, proto_wg);
1699 #ifdef WG_DECRYPTION_SUPPORTED
1700 wg_module = prefs_register_protocol(proto_wg, NULL);
1702 uat_t *wg_keys_uat = uat_new("WireGuard static keys",
1703 sizeof(wg_key_uat_record_t),
1704 "wg_keys", /* filename */
1705 TRUE, /* from_profile */
1706 &wg_key_records, /* data_ptr */
1707 &num_wg_key_records, /* numitems_ptr */
1708 UAT_AFFECTS_DISSECTION, /* affects dissection of packets, but not set of named fields */
1709 NULL, /* Help section (currently a wiki page) */
1711 wg_key_uat_record_update_cb, /* update_cb */
1713 wg_key_uat_apply, /* post_update_cb */
1714 wg_key_uat_reset, /* reset_cb */
1717 prefs_register_uat_preference(wg_module, "keys",
1718 "WireGuard static keys",
1719 "A table of long-term static keys to enable WireGuard peer identification or partial decryption",
1722 prefs_register_bool_preference(wg_module, "dissect_packet",
1723 "Dissect transport data",
1724 "Whether the IP dissector should dissect decrypted transport data.",
1725 &pref_dissect_packet);
1727 prefs_register_filename_preference(wg_module, "keylog_file", "Key log filename",
1728 "The path to the file which contains a list of secrets in the following format:\n"
1729 "\"<key-type> = <base64-encoded-key>\" (without quotes, leading spaces and spaces around '=' are ignored).\n"
1730 "<key-type> is one of: LOCAL_STATIC_PRIVATE_KEY, REMOTE_STATIC_PUBLIC_KEY, "
1731 "LOCAL_EPHEMERAL_PRIVATE_KEY or PRESHARED_KEY.",
1732 &pref_keylog_file, FALSE);
1734 if (!wg_decrypt_init()) {
1735 ws_g_warning("%s: decryption will not be possible due to lack of algorithms support", G_STRFUNC);
1738 wg_ephemeral_keys = wmem_map_new_autoreset(wmem_epan_scope(), wmem_file_scope(), g_int_hash, wg_pubkey_equal);
1739 #endif /* WG_DECRYPTION_SUPPORTED */
1741 register_init_routine(wg_init);
1742 #ifdef WG_DECRYPTION_SUPPORTED
1743 register_cleanup_routine(wg_keylog_reset);
1744 #endif /* WG_DECRYPTION_SUPPORTED */
1745 sessions = wmem_map_new_autoreset(wmem_epan_scope(), wmem_file_scope(), g_direct_hash, g_direct_equal);
1749 proto_reg_handoff_wg(void)
1751 heur_dissector_add("udp", dissect_wg, "WireGuard", "wg", proto_wg, HEURISTIC_ENABLE);
1753 #ifdef WG_DECRYPTION_SUPPORTED
1754 ip_handle = find_dissector("ip");
1755 #endif /* WG_DECRYPTION_SUPPORTED */
1759 * Editor modelines - https://www.wireshark.org/tools/modelines.html
1764 * indent-tabs-mode: nil
1767 * vi: set shiftwidth=4 tabstop=8 expandtab:
1768 * :indentSize=4:tabSize=8:noTabs=true: