aboutsummaryrefslogtreecommitdiffstats
path: root/epan
diff options
context:
space:
mode:
authorPeter Wu <peter@lekensteyn.nl>2018-09-16 01:07:21 +0200
committerAlexis La Goutte <alexis.lagoutte@gmail.com>2018-09-17 08:14:32 +0000
commit2fd42045f5afb556a03d8a1090f3278c77798766 (patch)
tree6607c73ce45fcb1d4221c81c8e393256135e7ca1 /epan
parent9de95b83f87e49a5d99085df1e8aa262f4fa2af1 (diff)
QUIC: implement decryption using new traffic secrets (draft -13)
QUIC draft -12 and before used the TLS Exporter to derive the protected payload secrets. Starting with draft -13, the handshake and 1-RTT protected payloads use keys derived during the TLS 1.3 handshake (but with the "quic " label for HKDF-Expand-Label instead of "tls13 "). That unfortunately means that previous CLIENT_HANDSHAKE_TRAFFIC_SECRET, SERVER_TRAFFIC_SECRET_0, etc. are unusable. As a quick workaround, extend the key log format with new labels similar to the old one (but with "QUIC_" prepended to it). To match draft -13, rename the original "handshake cipher/secret" to "initial cipher/secret" and add a new "handshake cipher". Potential limitation: if the client/server addresses/ports change since the Initial Packet, then a new TLS session is created in the TLS dissector. Attempting to retrieve secrets after the change will fail since the Client Random is empty and the secret cannot be linked. Another more common limitation: (Certificate) handshake messages that span multiple CRYPTO frames are not correctly recognized. Change-Id: I2932c3cc851fae51e8becf859db53ccc5f4beeda Ping-Bug: 13881 Reviewed-on: https://code.wireshark.org/review/29677 Petri-Dish: Peter Wu <peter@lekensteyn.nl> Tested-by: Petri Dish Buildbot Reviewed-by: Alexis La Goutte <alexis.lagoutte@gmail.com>
Diffstat (limited to 'epan')
-rw-r--r--epan/dissectors/packet-quic.c122
-rw-r--r--epan/dissectors/packet-ssl-utils.c27
-rw-r--r--epan/dissectors/packet-ssl-utils.h7
-rw-r--r--epan/dissectors/packet-ssl.c78
-rw-r--r--epan/dissectors/packet-ssl.h3
5 files changed, 208 insertions, 29 deletions
diff --git a/epan/dissectors/packet-quic.c b/epan/dissectors/packet-quic.c
index 38f2a4641f..3eef02f93a 100644
--- a/epan/dissectors/packet-quic.c
+++ b/epan/dissectors/packet-quic.c
@@ -128,8 +128,7 @@ static dissector_handle_t tls13_handshake_handle;
/*
* PROTECTED PAYLOAD DECRYPTION (done in first pass)
*
- * Long packet types always use a single cipher (client_handshake_cipher or
- * server_handshake_cipher).
+ * Long packet types always use a single cipher depending on packet type.
* Short packet types always use 1-RTT secrets for packet protection (pp).
* TODO 0-RTT decryption requires another (client) cipher.
*
@@ -198,6 +197,8 @@ typedef struct quic_info_data {
int hash_algo; /**< Libgcrypt hash algorithm for key derivation. */
int cipher_algo; /**< Cipher algorithm for packet number and packet encryption. */
int cipher_mode; /**< Cipher mode for packet encryption. */
+ quic_cipher client_initial_cipher;
+ quic_cipher server_initial_cipher;
quic_cipher client_handshake_cipher;
quic_cipher server_handshake_cipher;
quic_pp_state_t client_pp;
@@ -771,6 +772,8 @@ static void
quic_connection_destroy(gpointer data, gpointer user_data _U_)
{
quic_info_data_t *conn = (quic_info_data_t *)data;
+ quic_cipher_reset(&conn->client_initial_cipher);
+ quic_cipher_reset(&conn->server_initial_cipher);
quic_cipher_reset(&conn->client_handshake_cipher);
quic_cipher_reset(&conn->server_handshake_cipher);
@@ -1239,17 +1242,17 @@ quic_hkdf_expand_label(int hash_algo, guint8 *secret, guint secret_len, const ch
}
/**
- * Compute the client and server handshake secrets given Connection ID "cid".
+ * Compute the client and server initial secrets given Connection ID "cid".
*
- * On success TRUE is returned and the two handshake secrets are set.
+ * On success TRUE is returned and the two initial secrets are set.
* FALSE is returned on error (see "error" parameter for the reason).
*/
static gboolean
-quic_derive_handshake_secrets(const quic_cid_t *cid,
- guint8 client_handshake_secret[HASH_SHA2_256_LENGTH],
- guint8 server_handshake_secret[HASH_SHA2_256_LENGTH],
- quic_info_data_t *quic_info,
- const gchar **error)
+quic_derive_initial_secrets(const quic_cid_t *cid,
+ guint8 client_initial_secret[HASH_SHA2_256_LENGTH],
+ guint8 server_initial_secret[HASH_SHA2_256_LENGTH],
+ quic_info_data_t *quic_info,
+ const gchar **error)
{
/*
* https://tools.ietf.org/html/draft-ietf-quic-tls-10#section-5.2.2
@@ -1291,25 +1294,25 @@ quic_derive_handshake_secrets(const quic_cid_t *cid,
if (is_quic_draft_max(quic_info->version, 12)) {
if (qhkdf_expand(GCRY_MD_SHA256, secret, sizeof(secret), "client hs",
- client_handshake_secret, HASH_SHA2_256_LENGTH)) {
+ client_initial_secret, HASH_SHA2_256_LENGTH)) {
*error = "Key expansion (client) failed";
return FALSE;
}
if (qhkdf_expand(GCRY_MD_SHA256, secret, sizeof(secret), "server hs",
- server_handshake_secret, HASH_SHA2_256_LENGTH)) {
+ server_initial_secret, HASH_SHA2_256_LENGTH)) {
*error = "Key expansion (server) failed";
return FALSE;
}
} else {
if (!quic_hkdf_expand_label(GCRY_MD_SHA256, secret, sizeof(secret), "client in",
- client_handshake_secret, HASH_SHA2_256_LENGTH)) {
+ client_initial_secret, HASH_SHA2_256_LENGTH)) {
*error = "Key expansion (client) failed";
return FALSE;
}
if (!quic_hkdf_expand_label(GCRY_MD_SHA256, secret, sizeof(secret), "server in",
- server_handshake_secret, HASH_SHA2_256_LENGTH)) {
+ server_initial_secret, HASH_SHA2_256_LENGTH)) {
*error = "Key expansion (server) failed";
return FALSE;
}
@@ -1378,21 +1381,21 @@ quic_cipher_prepare(guint32 version, quic_cipher *cipher, int hash_algo, int cip
}
static gboolean
-quic_create_handshake_decoders(const quic_cid_t *cid, const gchar **error, quic_info_data_t *quic_info)
+quic_create_initial_decoders(const quic_cid_t *cid, const gchar **error, quic_info_data_t *quic_info)
{
guint8 client_secret[HASH_SHA2_256_LENGTH];
guint8 server_secret[HASH_SHA2_256_LENGTH];
guint32 version = quic_info->version;
- if (!quic_derive_handshake_secrets(cid, client_secret, server_secret, quic_info, error)) {
+ if (!quic_derive_initial_secrets(cid, client_secret, server_secret, quic_info, error)) {
return FALSE;
}
/* Packet numbers are protected with AES128-CTR,
* initial packets are protected with AEAD_AES_128_GCM. */
- if (!quic_cipher_prepare(version, &quic_info->client_handshake_cipher, GCRY_MD_SHA256,
+ if (!quic_cipher_prepare(version, &quic_info->client_initial_cipher, GCRY_MD_SHA256,
GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_GCM, client_secret, error) ||
- !quic_cipher_prepare(version, &quic_info->server_handshake_cipher, GCRY_MD_SHA256,
+ !quic_cipher_prepare(version, &quic_info->server_initial_cipher, GCRY_MD_SHA256,
GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_GCM, server_secret, error)) {
return FALSE;
}
@@ -1400,6 +1403,33 @@ quic_create_handshake_decoders(const quic_cid_t *cid, const gchar **error, quic_
return TRUE;
}
+static gboolean
+quic_create_decoders(packet_info *pinfo, guint32 version, quic_info_data_t *quic_info, quic_cipher *cipher,
+ gboolean from_server, TLSRecordType type, const char **error)
+{
+ if (!quic_info->hash_algo) {
+ if (!tls_get_cipher_info(pinfo, &quic_info->cipher_algo, &quic_info->cipher_mode, &quic_info->hash_algo)) {
+ *error = "Unable to retrieve cipher information";
+ return FALSE;
+ }
+ }
+
+ guint hash_len = gcry_md_get_algo_dlen(quic_info->hash_algo);
+ char *secret = (char *)wmem_alloc0(wmem_packet_scope(), hash_len);
+
+ if (!tls13_get_quic_secret(pinfo, from_server, type, hash_len, secret)) {
+ *error = "Unable to retrieve secret";
+ return FALSE;
+ }
+
+ if (!quic_cipher_prepare(version, cipher, quic_info->hash_algo,
+ quic_info->cipher_algo, quic_info->cipher_mode, secret, error)) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
/**
* Computes QHKDF-Expand(Secret, Label, Length).
* Caller must ensure that "out" is large enough for "out_len".
@@ -1455,6 +1485,21 @@ quic_get_pp0_secret(packet_info *pinfo, int hash_algo, quic_pp_state_t *pp_state
}
/**
+ * Tries to obtain the QUIC application traffic secrets.
+ */
+static gboolean
+quic_get_traffic_secret(packet_info *pinfo, int hash_algo, quic_pp_state_t *pp_state, gboolean from_client)
+{
+ guint hash_len = gcry_md_get_algo_dlen(hash_algo);
+ char *secret = (char *)wmem_alloc0(wmem_packet_scope(), hash_len);
+ if (!tls13_get_quic_secret(pinfo, !from_client, TLS_SECRET_APP, hash_len, secret)) {
+ return FALSE;
+ }
+ pp_state->next_secret = (guint8 *)wmem_memdup(wmem_file_scope(), secret, hash_len);
+ return TRUE;
+}
+
+/**
* Expands the secret (length MUST be the same as the "hash_algo" digest size)
* and initialize cipher with the new key.
*/
@@ -1529,10 +1574,18 @@ quic_get_pp_cipher(packet_info *pinfo, gboolean key_phase, quic_info_data_t *qui
}
/* Retrieve secrets for both the client and server. */
- if (!quic_get_pp0_secret(pinfo, quic_info->hash_algo, client_pp, TRUE) ||
- !quic_get_pp0_secret(pinfo, quic_info->hash_algo, server_pp, FALSE)) {
- quic_info->skip_decryption = TRUE;
- return NULL;
+ if (is_quic_draft_max(version, 12)) {
+ if (!quic_get_pp0_secret(pinfo, quic_info->hash_algo, client_pp, TRUE) ||
+ !quic_get_pp0_secret(pinfo, quic_info->hash_algo, server_pp, FALSE)) {
+ quic_info->skip_decryption = TRUE;
+ return NULL;
+ }
+ } else {
+ if (!quic_get_traffic_secret(pinfo, quic_info->hash_algo, client_pp, TRUE) ||
+ !quic_get_traffic_secret(pinfo, quic_info->hash_algo, server_pp, FALSE)) {
+ quic_info->skip_decryption = TRUE;
+ return NULL;
+ }
}
/* Create initial cipher handles for KEY_PHASE 0 and 1. */
@@ -1750,20 +1803,31 @@ dissect_quic_long_header(tvbuff_t *tvb, packet_info *pinfo, proto_tree *quic_tre
offset += len_payload_length;
#ifdef HAVE_LIBGCRYPT_AEAD
+ if (conn) {
+ if (long_packet_type == QUIC_LPT_INITIAL || is_quic_draft_max(version, 12)) {
+ cipher = !from_server ? &conn->client_initial_cipher : &conn->server_initial_cipher;
+ } else if (long_packet_type == QUIC_LPT_HANDSHAKE) {
+ cipher = !from_server ? &conn->client_handshake_cipher : &conn->server_handshake_cipher;
+ }
+ }
/* Build handshake cipher now for PKN (and handshake) decryption. */
- if (!PINFO_FD_VISITED(pinfo) && (long_packet_type == QUIC_LPT_INITIAL && !from_server) &&
- conn && !memcmp(&dcid, &conn->client_dcid_initial, sizeof(quic_cid_t))) {
+ if (!PINFO_FD_VISITED(pinfo) && conn) {
const gchar *error = NULL;
- /* Create new decryption context based on the Client Connection
- * ID from the *very first* Client Initial packet. */
- if (!quic_create_handshake_decoders(&dcid, &error, conn)) {
+ if (long_packet_type == QUIC_LPT_INITIAL && !from_server &&
+ !memcmp(&dcid, &conn->client_dcid_initial, sizeof(quic_cid_t))) {
+ /* Create new decryption context based on the Client Connection
+ * ID from the *very first* Client Initial packet. */
+ quic_create_initial_decoders(&dcid, &error, conn);
+ } else if (long_packet_type == QUIC_LPT_HANDSHAKE && !is_quic_draft_max(version, 12)) {
+ if (!cipher->pn_cipher) {
+ quic_create_decoders(pinfo, version, conn, cipher, from_server, TLS_SECRET_HANDSHAKE, &error);
+ }
+ }
+ if (error) {
expert_add_info_format(pinfo, quic_tree, &ei_quic_decryption_failed, "Failed to create decryption context: %s", error);
quic_packet->decryption.error = wmem_strdup(wmem_file_scope(), error);
}
}
- if (conn) {
- cipher = !from_server ? &conn->client_handshake_cipher : &conn->server_handshake_cipher;
- }
#endif /* !HAVE_LIBGCRYPT_AEAD */
pkn_len = dissect_quic_packet_number(tvb, pinfo, quic_tree, offset, conn, quic_packet, from_server,
diff --git a/epan/dissectors/packet-ssl-utils.c b/epan/dissectors/packet-ssl-utils.c
index 3cce6609e7..1c177d4b5b 100644
--- a/epan/dissectors/packet-ssl-utils.c
+++ b/epan/dissectors/packet-ssl-utils.c
@@ -4663,6 +4663,13 @@ ssl_common_init(ssl_master_key_map_t *mk_map,
mk_map->tls13_exporter = g_hash_table_new(ssl_hash, ssl_equal);
ssl_data_alloc(decrypted_data, 32);
ssl_data_alloc(compressed_data, 32);
+
+ /* QUIC keys. */
+ mk_map->quic_client_early = g_hash_table_new(ssl_hash, ssl_equal);
+ mk_map->quic_client_handshake = g_hash_table_new(ssl_hash, ssl_equal);
+ mk_map->quic_server_handshake = g_hash_table_new(ssl_hash, ssl_equal);
+ mk_map->quic_client_appdata = g_hash_table_new(ssl_hash, ssl_equal);
+ mk_map->quic_server_appdata = g_hash_table_new(ssl_hash, ssl_equal);
}
void
@@ -4685,6 +4692,13 @@ ssl_common_cleanup(ssl_master_key_map_t *mk_map, FILE **ssl_keylog_file,
g_free(decrypted_data->data);
g_free(compressed_data->data);
+ /* QUIC keys */
+ g_hash_table_destroy(mk_map->quic_client_early);
+ g_hash_table_destroy(mk_map->quic_client_handshake);
+ g_hash_table_destroy(mk_map->quic_server_handshake);
+ g_hash_table_destroy(mk_map->quic_client_appdata);
+ g_hash_table_destroy(mk_map->quic_server_appdata);
+
/* close the previous keylog file now that the cache are cleared, this
* allows the cache to be filled with the full keylog file contents. */
if (*ssl_keylog_file) {
@@ -5108,6 +5122,13 @@ ssl_compile_keyfile_regex(void)
"|SERVER_TRAFFIC_SECRET_0 (?<server_appdata>" OCTET "{32})"
"|EARLY_EXPORTER_SECRET (?<early_exporter>" OCTET "{32})"
"|EXPORTER_SECRET (?<exporter>" OCTET "{32})"
+ /* QUIC (draft >= -13) Client Random to Derived Secrets mapping.
+ * EXPERIMENTAL, subject to change based on QUIC changes! */
+ "|QUIC_CLIENT_EARLY_TRAFFIC_SECRET (?<quic_client_early>" OCTET "{32})"
+ "|QUIC_CLIENT_HANDSHAKE_TRAFFIC_SECRET (?<quic_client_handshake>" OCTET "{32})"
+ "|QUIC_SERVER_HANDSHAKE_TRAFFIC_SECRET (?<quic_server_handshake>" OCTET "{32})"
+ "|QUIC_CLIENT_TRAFFIC_SECRET_0 (?<quic_client_appdata>" OCTET "{32})"
+ "|QUIC_SERVER_TRAFFIC_SECRET_0 (?<quic_server_appdata>" OCTET "{32})"
") (?<derived_secret>" OCTET "+)";
#undef OCTET
static GRegex *regex = NULL;
@@ -5172,6 +5193,12 @@ ssl_load_keyfile(const gchar *ssl_keylog_filename, FILE **keylog_file,
{ "server_appdata", mk_map->tls13_server_appdata },
{ "early_exporter", mk_map->tls13_early_exporter },
{ "exporter", mk_map->tls13_exporter },
+ /* QUIC map from Client Random to derived secret. */
+ { "quic_client_early", mk_map->quic_client_early },
+ { "quic_client_handshake", mk_map->quic_client_handshake },
+ { "quic_server_handshake", mk_map->quic_server_handshake },
+ { "quic_client_appdata", mk_map->quic_client_appdata },
+ { "quic_server_appdata", mk_map->quic_server_appdata },
};
/* no need to try if no key log file is configured. */
if (!ssl_keylog_filename || !*ssl_keylog_filename) {
diff --git a/epan/dissectors/packet-ssl-utils.h b/epan/dissectors/packet-ssl-utils.h
index 23638ff014..3d18c3b73e 100644
--- a/epan/dissectors/packet-ssl-utils.h
+++ b/epan/dissectors/packet-ssl-utils.h
@@ -466,6 +466,13 @@ typedef struct {
GHashTable *tls13_server_appdata;
GHashTable *tls13_early_exporter;
GHashTable *tls13_exporter;
+
+ /* For QUIC: maps Client Random to derived secret. */
+ GHashTable *quic_client_early;
+ GHashTable *quic_client_handshake;
+ GHashTable *quic_server_handshake;
+ GHashTable *quic_client_appdata;
+ GHashTable *quic_server_appdata;
} ssl_master_key_map_t;
gint ssl_get_keyex_alg(gint cipher);
diff --git a/epan/dissectors/packet-ssl.c b/epan/dissectors/packet-ssl.c
index 912d83b40f..d9acf6315d 100644
--- a/epan/dissectors/packet-ssl.c
+++ b/epan/dissectors/packet-ssl.c
@@ -3430,6 +3430,84 @@ tls_get_cipher_info(packet_info *pinfo, int *cipher_algo, int *cipher_mode, int
return TRUE;
}
+/**
+ * Load the QUIC traffic secret from the keylog file.
+ * Returns TRUE and the secret into 'secret' if a secret was found of length
+ * 'secret_size' and FALSE otherwise.
+ */
+gboolean
+tls13_get_quic_secret(packet_info *pinfo, gboolean is_from_server, int type, guint secret_len, guint8 *secret_out)
+{
+ GHashTable *key_map;
+ const char *label;
+ conversation_t *conv = find_conversation_pinfo(pinfo, 0);
+ if (!conv) {
+ return FALSE;
+ }
+
+ SslDecryptSession *ssl = (SslDecryptSession *)conversation_get_proto_data(conv, proto_tls);
+ if (ssl == NULL) {
+ return FALSE;
+ }
+
+ gboolean is_quic = !!(ssl->state & SSL_QUIC_RECORD_LAYER);
+ ssl_debug_printf("%s frame %d is_quic=%d\n", G_STRFUNC, pinfo->num, is_quic);
+ if (!is_quic) {
+ return FALSE;
+ }
+
+ if (ssl->client_random.data_len == 0) {
+ /* May happen if Hello message is missing and Finished is found. */
+ ssl_debug_printf("%s missing Client Random\n", G_STRFUNC);
+ return FALSE;
+ }
+
+ // Not strictly necessary as QUIC CRYPTO frames have just been processed
+ // which also calls ssl_load_keyfile for key transitions.
+ ssl_load_keyfile(ssl_options.keylog_filename, &ssl_keylog_file, &ssl_master_key_map);
+
+ switch ((TLSRecordType)type) {
+ case TLS_SECRET_0RTT_APP:
+ DISSECTOR_ASSERT(!is_from_server);
+ label = "CLIENT_EARLY_TRAFFIC_SECRET";
+ key_map = ssl_master_key_map.quic_client_early;
+ break;
+ case TLS_SECRET_HANDSHAKE:
+ if (is_from_server) {
+ label = "SERVER_HANDSHAKE_TRAFFIC_SECRET";
+ key_map = ssl_master_key_map.quic_server_handshake;
+ } else {
+ label = "CLIENT_HANDSHAKE_TRAFFIC_SECRET";
+ key_map = ssl_master_key_map.quic_client_handshake;
+ }
+ break;
+ case TLS_SECRET_APP:
+ if (is_from_server) {
+ label = "SERVER_TRAFFIC_SECRET_0";
+ key_map = ssl_master_key_map.quic_server_appdata;
+ } else {
+ label = "CLIENT_TRAFFIC_SECRET_0";
+ key_map = ssl_master_key_map.quic_client_appdata;
+ }
+ break;
+ default:
+ g_assert_not_reached();
+ }
+
+ StringInfo *secret = (StringInfo *)g_hash_table_lookup(key_map, &ssl->client_random);
+ if (!secret || secret->data_len != secret_len) {
+ ssl_debug_printf("%s Cannot QUIC_%s of size %d, found bad size %d!\n",
+ G_STRFUNC, label, secret_len, secret ? secret->data_len : 0);
+ return FALSE;
+ }
+
+ ssl_debug_printf("%s Retrieved QUIC traffic secret.\n", G_STRFUNC);
+ ssl_print_string("Client Random", &ssl->client_random);
+ ssl_print_string(label, secret);
+ memcpy(secret_out, secret->data, secret_len);
+ return TRUE;
+}
+
/* TLS Exporters {{{ */
#if GCRYPT_VERSION_NUMBER >= 0x010600 /* 1.6.0 */
/**
diff --git a/epan/dissectors/packet-ssl.h b/epan/dissectors/packet-ssl.h
index 02334cff55..534682ce66 100644
--- a/epan/dissectors/packet-ssl.h
+++ b/epan/dissectors/packet-ssl.h
@@ -43,4 +43,7 @@ tls13_exporter(packet_info *pinfo, gboolean is_early,
const char *label, guint8 *context,
guint context_length, guint key_length, guchar **out);
+gboolean
+tls13_get_quic_secret(packet_info *pinfo, gboolean is_from_server, int type, guint secret_len, guint8 *secret_out);
+
#endif /* __PACKET_SSL_H__ */