aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPeter Wu <peter@lekensteyn.nl>2018-04-17 22:08:55 +0200
committerAnders Broman <a.broman58@gmail.com>2018-04-18 03:48:54 +0000
commit01363266c1e019884036f286b1a27ea20082048b (patch)
tree96e899bd8b41c6fb793e7522e3bd855b41cc9f7f
parent6a45dcd7a2ab695aade66499cdb61406a2196429 (diff)
QUIC: initial draft-10 decryption support
Drop support for draft -08 and draft -09, add support for draft -10 handshake decryption only (requires a new salt as well as a HKDF label change). Fixed a bug in qhkdf_expand (swapped length and "QUIC " label) which affects KeyUpdate (which was initially untested). Bug: 13881 Change-Id: I5f3e2fe71ef0fd929d3271ecea3a8870f90e3934 Reviewed-on: https://code.wireshark.org/review/26992 Petri-Dish: Peter Wu <peter@lekensteyn.nl> Tested-by: Petri Dish Buildbot Reviewed-by: Anders Broman <a.broman58@gmail.com>
-rw-r--r--epan/dissectors/packet-quic.c180
1 files changed, 73 insertions, 107 deletions
diff --git a/epan/dissectors/packet-quic.c b/epan/dissectors/packet-quic.c
index 9e62f06..acf49cd 100644
--- a/epan/dissectors/packet-quic.c
+++ b/epan/dissectors/packet-quic.c
@@ -161,8 +161,8 @@ typedef struct quic_info_data {
gboolean skip_decryption : 1; /**< Set to 1 if no keys are available. */
guint8 cipher_keylen; /**< Cipher key length. */
int hash_algo; /**< Libgcrypt hash algorithm for key derivation. */
- tls13_cipher *client_handshake_cipher;
- tls13_cipher *server_handshake_cipher;
+ tls13_cipher client_handshake_cipher;
+ tls13_cipher server_handshake_cipher;
quic_pp_state_t client_pp;
quic_pp_state_t server_pp;
guint64 max_client_pkn;
@@ -681,12 +681,20 @@ dissect_quic_frame_type(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *quic_
#define QUIC_LONG_HEADER_LENGTH 17
#ifdef HAVE_LIBGCRYPT_AEAD
+static gcry_error_t
+qhkdf_expand(int md, const guint8 *secret, guint secret_len,
+ const char *label, guint8 *out, guint out_len);
+
+static gboolean
+quic_cipher_init_keyiv(tls13_cipher *cipher, int hash_algo, guint8 key_length, guint8 *secret);
+
+
/**
* Given a QUIC message (header + non-empty payload), the actual packet number,
* try to decrypt it using the cipher.
*
* The actual packet number must be constructed according to
- * https://tools.ietf.org/html/draft-ietf-quic-transport-07#section-5.7
+ * https://tools.ietf.org/html/draft-ietf-quic-transport-10#section-5.7
*/
static void
quic_decrypt_message(tls13_cipher *cipher, tvbuff_t *head, guint header_length, guint64 packet_number, quic_decrypt_result_t *result)
@@ -700,6 +708,7 @@ quic_decrypt_message(tls13_cipher *cipher, tvbuff_t *head, guint header_length,
const guchar **error = &result->error;
DISSECTOR_ASSERT(cipher != NULL);
+ DISSECTOR_ASSERT(cipher->hd != NULL);
DISSECTOR_ASSERT(header_length <= sizeof(header));
tvb_memcpy(head, header, 0, header_length);
@@ -751,89 +760,54 @@ quic_decrypt_message(tls13_cipher *cipher, tvbuff_t *head, guint header_length,
/**
* Compute the client and server handshake secrets given Connection ID "cid".
*
- * On success TRUE is returned and the two handshake secrets are returned (these
- * must be freed with wmem_free(NULL, ...)). FALSE is returned on error.
+ * On success TRUE is returned and the two handshake secrets are set.
+ * FALSE is returned on error (see "error" parameter for the reason).
*/
static gboolean
quic_derive_handshake_secrets(guint64 cid,
- guint8 **client_handshake_secret,
- guint8 **server_handshake_secret,
- quic_info_data_t *quic_info,
+ guint8 client_handshake_secret[HASH_SHA2_256_LENGTH],
+ guint8 server_handshake_secret[HASH_SHA2_256_LENGTH],
+ quic_info_data_t *quic_info _U_,
const gchar **error)
{
-
/*
- * https://tools.ietf.org/html/draft-ietf-quic-tls-09#section-5.2.1
- *
- * quic_version_1_salt = afc824ec5fc77eca1e9d36f37fb2d46518c36639
+ * https://tools.ietf.org/html/draft-ietf-quic-tls-10#section-5.2.2
*
- * handshake_secret = HKDF-Extract(quic_version_1_salt,
- * client_connection_id)
+ * handshake_salt = 0x9c108f98520a5c5c32968e950e8a2c5fe06d6c38
+ * handshake_secret =
+ * HKDF-Extract(handshake_salt, client_connection_id)
*
* client_handshake_secret =
- * QHKDF-Expand-Label(handshake_secret,
- * "client hs",
- * "", Hash.length)
+ * QHKDF-Expand(handshake_secret, "client hs", Hash.length)
* server_handshake_secret =
- * QHKDF-Expand-Label(handshake_secret,
- * "server hs",
- * "", Hash.length)
- * Hash for handshake packets is SHA-256 (output size 32).
- *
- * https://tools.ietf.org/html/draft-ietf-quic-tls-09#section-5.2.3
- *
- * HKDF-Expand-Label uses HKDF-Expand [RFC5869] as shown:
- *
- * QHKDF-Expand(Secret, Label, Length) =
- * HKDF-Expand(Secret, QuicHkdfLabel, Length)
+ * QHKDF-Expand(handshake_secret, "server hs", Hash.length)
*
- * Where the info parameter, QuicHkdfLabel, is specified as:
- *
- * struct {
- * uint16 length = Length;
- * opaque label<6..255> = "QUIC " + Label;
- * uint8 hashLength = 0;
- * } QuicHkdfLabel;
+ * Hash for handshake packets is SHA-256 (output size 32).
*/
- static const guint8 quic_version_1_salt[20] = {
- 0xaf, 0xc8, 0x24, 0xec, 0x5f, 0xc7, 0x7e, 0xca, 0x1e, 0x9d,
- 0x36, 0xf3, 0x7f, 0xb2, 0xd4, 0x65, 0x18, 0xc3, 0x66, 0x39
+ static const guint8 handshake_salt[20] = {
+ 0x9c, 0x10, 0x8f, 0x98, 0x52, 0x0a, 0x5c, 0x5c, 0x32, 0x96,
+ 0x8e, 0x95, 0x0e, 0x8a, 0x2c, 0x5f, 0xe0, 0x6d, 0x6c, 0x38
};
- const char *label_prefix = "QUIC ";
gcry_error_t err;
- guint8 secret_bytes[HASH_SHA2_256_LENGTH];
- StringInfo secret = { (guchar *) &secret_bytes, HASH_SHA2_256_LENGTH };
+ guint8 secret[HASH_SHA2_256_LENGTH];
guint8 cid_bytes[8];
- const gchar *client_label = "client hs";
- const gchar *server_label = "server hs";
-
- /* draft-08 don't use the same prefix label and label... */
- if (quic_info->version == 0xFF000008) {
- label_prefix = "tls13 ";
- client_label = "QUIC client handshake secret";
- server_label = "QUIC server handshake secret";
- }
phton64(cid_bytes, cid);
- err = hkdf_extract(GCRY_MD_SHA256, quic_version_1_salt, sizeof(quic_version_1_salt),
- cid_bytes, sizeof(cid_bytes), secret.data);
+ err = hkdf_extract(GCRY_MD_SHA256, handshake_salt, sizeof(handshake_salt),
+ cid_bytes, sizeof(cid_bytes), secret);
if (err) {
*error = wmem_strdup_printf(wmem_packet_scope(), "Failed to extract secrets: %s", gcry_strerror(err));
return FALSE;
}
-
-
- if (!tls13_hkdf_expand_label(GCRY_MD_SHA256, &secret, label_prefix, client_label,
- HASH_SHA2_256_LENGTH, client_handshake_secret)) {
+ if (qhkdf_expand(GCRY_MD_SHA256, secret, sizeof(secret), "client hs",
+ client_handshake_secret, HASH_SHA2_256_LENGTH)) {
*error = "Key expansion (client) failed";
return FALSE;
}
- if (!tls13_hkdf_expand_label(GCRY_MD_SHA256, &secret, label_prefix, server_label,
- HASH_SHA2_256_LENGTH, server_handshake_secret)) {
- wmem_free(NULL, *client_handshake_secret);
- *client_handshake_secret = NULL;
+ if (qhkdf_expand(GCRY_MD_SHA256, secret, sizeof(secret), "server hs",
+ server_handshake_secret, HASH_SHA2_256_LENGTH)) {
*error = "Key expansion (server) failed";
return FALSE;
}
@@ -845,33 +819,32 @@ quic_derive_handshake_secrets(guint64 cid,
static gboolean
quic_create_handshake_decoders(guint64 cid, const gchar **error, quic_info_data_t *quic_info)
{
- tls13_cipher *client_cipher, *server_cipher;
- StringInfo client_secret = { NULL, HASH_SHA2_256_LENGTH };
- StringInfo server_secret = { NULL, HASH_SHA2_256_LENGTH };
- const char *hkdf_label_prefix = "QUIC ";
-
- /* draft-08 uses a different label prefix for HKDF-Expand-Label. */
- if (quic_info->version == 0xFF000008) {
- hkdf_label_prefix = "tls13 ";
- }
+ guint8 client_secret[HASH_SHA2_256_LENGTH];
+ guint8 server_secret[HASH_SHA2_256_LENGTH];
- if (!quic_derive_handshake_secrets(cid, &client_secret.data, &server_secret.data, quic_info, error)) {
+ if (!quic_derive_handshake_secrets(cid, client_secret, server_secret, quic_info, error)) {
return FALSE;
}
- /* handshake packets are protected with AEAD_AES_128_GCM */
- client_cipher = tls13_cipher_create(hkdf_label_prefix, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_GCM, GCRY_MD_SHA256, &client_secret, error);
- server_cipher = tls13_cipher_create(hkdf_label_prefix, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_GCM, GCRY_MD_SHA256, &server_secret, error);
+ /* Destroy any previous ciphers in case there exist multiple Initial packets */
+ gcry_cipher_close(quic_info->client_handshake_cipher.hd);
+ gcry_cipher_close(quic_info->server_handshake_cipher.hd);
+ memset(&quic_info->client_handshake_cipher, 0, sizeof(tls13_cipher));
+ memset(&quic_info->server_handshake_cipher, 0, sizeof(tls13_cipher));
- wmem_free(NULL, client_secret.data);
- wmem_free(NULL, server_secret.data);
-
- if (!client_cipher || !server_cipher) {
+ /* handshake packets are protected with AEAD_AES_128_GCM */
+ if (gcry_cipher_open(&quic_info->client_handshake_cipher.hd, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_GCM, 0) ||
+ gcry_cipher_open(&quic_info->server_handshake_cipher.hd, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_GCM, 0)) {
+ *error = "Failed to create ciphers";
return FALSE;
}
- quic_info->client_handshake_cipher = client_cipher;
- quic_info->server_handshake_cipher = server_cipher;
+ guint cipher_keylen = (guint8) gcry_cipher_get_algo_keylen(GCRY_CIPHER_AES128);
+ if (!quic_cipher_init_keyiv(&quic_info->client_handshake_cipher, GCRY_MD_SHA256, cipher_keylen, client_secret) ||
+ !quic_cipher_init_keyiv(&quic_info->server_handshake_cipher, GCRY_MD_SHA256, cipher_keylen, server_secret)) {
+ *error = "Failed to derive key material for cipher";
+ return FALSE;
+ }
return TRUE;
}
@@ -884,14 +857,13 @@ static gcry_error_t
qhkdf_expand(int md, const guint8 *secret, guint secret_len,
const char *label, guint8 *out, guint out_len)
{
- /* draft-ietf-quic-tls-09
+ /* https://tools.ietf.org/html/draft-ietf-quic-tls-10#section-5.2.1
* QHKDF-Expand(Secret, Label, Length) =
- * HKDF-Expand(Secret, QuicHkdfLabel, Length)
+ * HKDF-Expand(Secret, QhkdfLabel, Length)
* struct {
* uint16 length = Length;
* opaque label<6..255> = "QUIC " + Label;
- * uint8 hashLength = 0; // removed in draft -10
- * } QuicHkdfLabel;
+ * } QhkdfLabel;
*/
gcry_error_t err;
const guint label_length = (guint) strlen(label);
@@ -899,19 +871,16 @@ qhkdf_expand(int md, const guint8 *secret, guint secret_len,
/* Some sanity checks */
DISSECTOR_ASSERT(label_length > 0 && 5 + label_length <= 255);
- /* info = QuicHkdfLabel { length, label, hashLength } */
+ /* info = QhkdfLabel { length, label } */
GByteArray *info = g_byte_array_new();
const guint16 length = g_htons(out_len);
g_byte_array_append(info, (const guint8 *)&length, sizeof(length));
const guint8 label_vector_length = 5 + label_length;
- g_byte_array_append(info, "QUIC ", 5);
g_byte_array_append(info, &label_vector_length, 1);
+ g_byte_array_append(info, "QUIC ", 5);
g_byte_array_append(info, label, label_length);
- const guint8 hash_length = 0;
- g_byte_array_append(info, &hash_length, 1);
-
err = hkdf_expand(md, secret, secret_len, info->data, info->len, out, out_len);
g_byte_array_free(info, TRUE);
return err;
@@ -935,31 +904,25 @@ quic_get_pp0_secret(packet_info *pinfo, int hash_algo, quic_pp_state_t *pp_state
}
/**
- * Expands the packet protection secret and initialize cipher with the new key.
+ * Expands the secret (length MUST be the same as the "hash_algo" digest size)
+ * and initialize cipher with the new key.
*/
static gboolean
quic_cipher_init_keyiv(tls13_cipher *cipher, int hash_algo, guint8 key_length, guint8 *secret)
{
- const char *label_prefix = "QUIC ";
- guchar *write_key = NULL, *write_iv = NULL;
- guint iv_length = TLS13_AEAD_NONCE_LENGTH;
+ guchar write_key[256/8]; /* Maximum key size is for AES256 cipher. */
guint hash_len = gcry_md_get_algo_dlen(hash_algo);
- StringInfo secret_si = { secret, hash_len };
- gboolean success = FALSE;
- if (!tls13_hkdf_expand_label(hash_algo, &secret_si, label_prefix, "key", key_length, &write_key)) {
+ if (key_length > sizeof(write_key)) {
return FALSE;
}
- if (!tls13_hkdf_expand_label(hash_algo, &secret_si, label_prefix, "iv", iv_length, &write_iv)) {
- goto end;
+
+ if (qhkdf_expand(hash_algo, secret, hash_len, "key", write_key, key_length) ||
+ qhkdf_expand(hash_algo, secret, hash_len, "iv", cipher->iv, sizeof(cipher->iv))) {
+ return FALSE;
}
- memcpy(cipher->iv, write_iv, iv_length);
- success = gcry_cipher_setkey(cipher->hd, write_key, key_length) == 0;
-end:
- wmem_free(NULL, write_key);
- wmem_free(NULL, write_iv);
- return success;
+ return gcry_cipher_setkey(cipher->hd, write_key, key_length) == 0;
}
/**
@@ -1078,7 +1041,7 @@ quic_process_payload(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, proto_
* pass and store the result for later use.
*/
if (!PINFO_FD_VISITED(pinfo)) {
- if (!quic_packet->decryption.error && cipher) {
+ if (!quic_packet->decryption.error && cipher && cipher->hd) {
quic_decrypt_message(cipher, tvb, offset, pkn, &quic_packet->decryption);
}
}
@@ -1136,7 +1099,7 @@ dissect_quic_initial(tvbuff_t *tvb, packet_info *pinfo, proto_tree *quic_tree, g
#endif /* !HAVE_LIBGCRYPT_AEAD */
quic_process_payload(tvb, pinfo, quic_tree, ti, offset,
- quic_info, quic_packet, quic_info->client_handshake_cipher, pkn);
+ quic_info, quic_packet, &quic_info->client_handshake_cipher, pkn);
offset += tvb_reported_length_remaining(tvb, offset);
return offset;
@@ -1150,7 +1113,7 @@ dissect_quic_handshake(tvbuff_t *tvb, packet_info *pinfo, proto_tree *quic_tree,
ti = proto_tree_add_item(quic_tree, hf_quic_handshake_payload, tvb, offset, -1, ENC_NA);
- tls13_cipher *cipher = from_server ? quic_info->server_handshake_cipher : quic_info->client_handshake_cipher;
+ tls13_cipher *cipher = from_server ? &quic_info->server_handshake_cipher : &quic_info->client_handshake_cipher;
quic_process_payload(tvb, pinfo, quic_tree, ti, offset,
quic_info, quic_packet, cipher, pkn);
offset += tvb_reported_length_remaining(tvb, offset);
@@ -1168,7 +1131,7 @@ dissect_quic_retry(tvbuff_t *tvb, packet_info *pinfo, proto_tree *quic_tree, gui
/* Retry coming always from server */
quic_process_payload(tvb, pinfo, quic_tree, ti, offset,
- quic_info, quic_packet, quic_info->server_handshake_cipher, pkn);
+ quic_info, quic_packet, &quic_info->server_handshake_cipher, pkn);
offset += tvb_reported_length_remaining(tvb, offset);
@@ -1303,6 +1266,9 @@ quic_info_destroy_cb(wmem_allocator_t *allocator _U_, wmem_cb_event_t event _U_,
{
quic_info_data_t *quic_info = (quic_info_data_t *) user_data;
+ gcry_cipher_close(quic_info->client_handshake_cipher.hd);
+ gcry_cipher_close(quic_info->server_handshake_cipher.hd);
+
gcry_cipher_close(quic_info->client_pp.cipher[0].hd);
gcry_cipher_close(quic_info->client_pp.cipher[1].hd);
gcry_cipher_close(quic_info->server_pp.cipher[0].hd);