forked from osmocom/wireshark
TLS13,QUIC: prepare for QUIC decryption
Add interface to expand the QUIC cleartext secrets (quic_derive_cleartext_secrets), an interface to create the cleartext ciphers (quic_create_cleartext_decoders), an interface to decrypt messages using this cipher (quic_decrypt_message). Change-Id: Id546150be2964959388b7ef69984b891521e5caa Reviewed-on: https://code.wireshark.org/review/24435 Petri-Dish: Peter Wu <peter@lekensteyn.nl> Petri-Dish: Alexis La Goutte <alexis.lagoutte@gmail.com> Reviewed-by: Alexis La Goutte <alexis.lagoutte@gmail.com>
This commit is contained in:
parent
5a3addd8eb
commit
56f1feb678
|
@ -32,7 +32,12 @@
|
|||
#include <epan/expert.h>
|
||||
#include "packet-ssl-utils.h"
|
||||
#include <epan/prefs.h>
|
||||
#include <wsutil/pint.h>
|
||||
|
||||
#if GCRYPT_VERSION_NUMBER >= 0x010600 /* 1.6.0 */
|
||||
/* Whether to provide support for authentication in addition to decryption. */
|
||||
#define HAVE_LIBGCRYPT_AEAD
|
||||
#endif
|
||||
|
||||
/* Prototypes */
|
||||
void proto_reg_handoff_quic(void);
|
||||
|
@ -746,6 +751,175 @@ dissect_quic_frame_type(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *quic_
|
|||
return offset;
|
||||
}
|
||||
|
||||
/* TLS 1.3 draft used by the draft-ietf-quic-tls-07 */
|
||||
#define QUIC_TLS13_VERSION 21
|
||||
#define QUIC_LONG_HEADER_LENGTH 17
|
||||
|
||||
#ifdef HAVE_LIBGCRYPT_AEAD
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* If decryption succeeds, the decrypted buffer is added as data source and
|
||||
* returned. Otherwise NULL is returned and an error message is set.
|
||||
*/
|
||||
static tvbuff_t *
|
||||
quic_decrypt_message(tls13_cipher *cipher, tvbuff_t *head, packet_info *pinfo, guint header_length, guint64 packet_number, const gchar **error)
|
||||
{
|
||||
gcry_error_t err;
|
||||
guint8 header[QUIC_LONG_HEADER_LENGTH];
|
||||
guint8 nonce[TLS13_AEAD_NONCE_LENGTH];
|
||||
guint8 *buffer;
|
||||
guint8 *atag[16];
|
||||
guint buffer_length;
|
||||
tvbuff_t *decrypted;
|
||||
|
||||
DISSECTOR_ASSERT(cipher != NULL);
|
||||
DISSECTOR_ASSERT(header_length <= sizeof(header));
|
||||
tvb_memcpy(head, header, 0, header_length);
|
||||
|
||||
/* Input is "header || ciphertext (buffer) || auth tag (16 bytes)" */
|
||||
buffer_length = tvb_captured_length_remaining(head, header_length + 16);
|
||||
if (buffer_length == 0) {
|
||||
*error = "Decryption not possible, ciphertext is too short";
|
||||
return NULL;
|
||||
}
|
||||
buffer = (guint8 *)tvb_memdup(pinfo->pool, head, header_length, buffer_length);
|
||||
tvb_memcpy(head, atag, header_length + buffer_length, 16);
|
||||
|
||||
memcpy(nonce, cipher->iv, TLS13_AEAD_NONCE_LENGTH);
|
||||
/* Packet number is left-padded with zeroes and XORed with write_iv */
|
||||
phton64(nonce + sizeof(nonce) - 8, pntoh64(nonce + sizeof(nonce) - 8) ^ packet_number);
|
||||
|
||||
gcry_cipher_reset(cipher->hd);
|
||||
err = gcry_cipher_setiv(cipher->hd, nonce, TLS13_AEAD_NONCE_LENGTH);
|
||||
if (err) {
|
||||
*error = wmem_strdup_printf(wmem_packet_scope(), "Decryption (setiv) failed: %s", gcry_strerror(err));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* associated data (A) is the contents of QUIC header */
|
||||
err = gcry_cipher_authenticate(cipher->hd, header, header_length);
|
||||
if (err) {
|
||||
*error = wmem_strdup_printf(wmem_packet_scope(), "Decryption (authenticate) failed: %s", gcry_strerror(err));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Output ciphertext (C) */
|
||||
err = gcry_cipher_decrypt(cipher->hd, buffer, buffer_length, NULL, 0);
|
||||
if (err) {
|
||||
*error = wmem_strdup_printf(wmem_packet_scope(), "Decryption (decrypt) failed: %s", gcry_strerror(err));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
err = gcry_cipher_checktag(cipher->hd, atag, 16);
|
||||
if (err) {
|
||||
*error = wmem_strdup_printf(wmem_packet_scope(), "Decryption (checktag) failed: %s", gcry_strerror(err));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
decrypted = tvb_new_child_real_data(head, buffer, buffer_length, buffer_length);
|
||||
add_new_data_source(pinfo, decrypted, "Decrypted QUIC");
|
||||
|
||||
*error = NULL;
|
||||
return decrypted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the client and server cleartext secrets given Connection ID "cid".
|
||||
*
|
||||
* On success TRUE is returned and the two cleartext secrets are returned (these
|
||||
* must be freed with wmem_free(NULL, ...)). FALSE is returned on error.
|
||||
*/
|
||||
static gboolean
|
||||
quic_derive_cleartext_secrets(guint64 cid,
|
||||
guint8 **client_cleartext_secret,
|
||||
guint8 **server_cleartext_secret,
|
||||
const gchar **error)
|
||||
{
|
||||
/*
|
||||
* https://tools.ietf.org/html/draft-ietf-quic-tls-07#section-5.2.1
|
||||
*
|
||||
* quic_version_1_salt = afc824ec5fc77eca1e9d36f37fb2d46518c36639
|
||||
*
|
||||
* cleartext_secret = HKDF-Extract(quic_version_1_salt,
|
||||
* client_connection_id)
|
||||
*
|
||||
* client_cleartext_secret =
|
||||
* HKDF-Expand-Label(cleartext_secret,
|
||||
* "QUIC client cleartext Secret",
|
||||
* "", Hash.length)
|
||||
* server_cleartext_secret =
|
||||
* HKDF-Expand-Label(cleartext_secret,
|
||||
* "QUIC server cleartext Secret",
|
||||
* "", Hash.length)
|
||||
* Hash for cleartext 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
|
||||
};
|
||||
guint tls13_draft_version = QUIC_TLS13_VERSION;
|
||||
gcry_error_t err;
|
||||
guint8 secret_bytes[HASH_SHA2_256_LENGTH];
|
||||
StringInfo secret = { (guchar *) &secret_bytes, HASH_SHA2_256_LENGTH };
|
||||
guint8 cid_bytes[8];
|
||||
|
||||
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);
|
||||
if (err) {
|
||||
*error = wmem_strdup_printf(wmem_packet_scope(), "Failed to extract secrets: %s", gcry_strerror(err));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!tls13_hkdf_expand_label(tls13_draft_version, GCRY_MD_SHA256, &secret, "QUIC client cleartext Secret",
|
||||
"", HASH_SHA2_256_LENGTH, client_cleartext_secret)) {
|
||||
*error = "Key expansion (client) failed";
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!tls13_hkdf_expand_label(tls13_draft_version, GCRY_MD_SHA256, &secret, "QUIC server cleartext Secret",
|
||||
"", HASH_SHA2_256_LENGTH, server_cleartext_secret)) {
|
||||
wmem_free(NULL, client_cleartext_secret);
|
||||
*client_cleartext_secret = NULL;
|
||||
*error = "Key expansion (server) failed";
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
*error = NULL;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
quic_create_cleartext_decoders(guint64 cid, const gchar **error)
|
||||
{
|
||||
tls13_cipher *client_cipher, *server_cipher;
|
||||
StringInfo client_secret = { NULL, HASH_SHA2_256_LENGTH };
|
||||
StringInfo server_secret = { NULL, HASH_SHA2_256_LENGTH };
|
||||
|
||||
/* TODO extract from packet/conversation */
|
||||
if (!quic_derive_cleartext_secrets(cid, &client_secret.data, &server_secret.data, error)) {
|
||||
/* TODO handle error (expert info) */
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Cleartext packets are protected with AEAD_AES_128_GCM */
|
||||
client_cipher = tls13_cipher_create(QUIC_TLS13_VERSION, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_GCM, GCRY_MD_SHA256, &client_secret, error);
|
||||
server_cipher = tls13_cipher_create(QUIC_TLS13_VERSION, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_GCM, GCRY_MD_SHA256, &server_secret, error);
|
||||
if (!client_cipher || !server_cipher) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* TODO store ciphers in conversation, handle errors (expert info) */
|
||||
return TRUE;
|
||||
}
|
||||
#endif /* HAVE_LIBGCRYPT_AEAD */
|
||||
|
||||
|
||||
static int
|
||||
dissect_quic_long_header(tvbuff_t *tvb, packet_info *pinfo, proto_tree *quic_tree, guint offset, quic_info_data_t *quic_info){
|
||||
guint32 long_packet_type, pkn;
|
||||
|
|
|
@ -2091,6 +2091,7 @@ ssl_cipher_cleanup(gcry_cipher_hd_t *cipher)
|
|||
gcry_cipher_close(*cipher);
|
||||
*cipher = NULL;
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
/* Digests, Ciphers and Cipher Suites registry {{{ */
|
||||
static const SslDigestAlgo digests[]={
|
||||
|
@ -3001,6 +3002,65 @@ ssl_decoder_destroy_cb(wmem_allocator_t *allocator _U_, wmem_cb_event_t event _U
|
|||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
tls13_cipher_destroy_cb(wmem_allocator_t *allocator _U_, wmem_cb_event_t event _U_, void *user_data)
|
||||
{
|
||||
tls13_cipher *cipher = (tls13_cipher *) user_data;
|
||||
|
||||
gcry_cipher_close(cipher->hd);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
tls13_cipher *
|
||||
tls13_cipher_create(guint8 tls13_draft_version, int cipher_algo, int cipher_mode, int hash_algo, StringInfo *secret, const gchar **error)
|
||||
{
|
||||
tls13_cipher *cipher = NULL;
|
||||
guchar *write_key = NULL, *write_iv = NULL;
|
||||
guint key_length, iv_length;
|
||||
gcry_cipher_hd_t hd = NULL;
|
||||
gcry_error_t err;
|
||||
|
||||
/*
|
||||
* Calculate traffic keys based on
|
||||
* https://tools.ietf.org/html/draft-ietf-tls-tls13-21#section-7
|
||||
*/
|
||||
key_length = (guint) gcry_cipher_get_algo_keylen(cipher_algo);
|
||||
iv_length = TLS13_AEAD_NONCE_LENGTH;
|
||||
|
||||
if (!tls13_hkdf_expand_label(tls13_draft_version, hash_algo, secret, "key", "", key_length, &write_key)) {
|
||||
*error = "Key expansion (key) failed";
|
||||
return NULL;
|
||||
}
|
||||
if (!tls13_hkdf_expand_label(tls13_draft_version, hash_algo, secret, "iv", "", iv_length, &write_iv)) {
|
||||
*error = "Key expansion (IV) failed";
|
||||
goto end;
|
||||
}
|
||||
|
||||
err = gcry_cipher_open(&hd, cipher_algo, cipher_mode, 0);
|
||||
if (err) {
|
||||
*error = wmem_strdup_printf(wmem_packet_scope(), "Decryption (initialization) failed: %s", gcry_strerror(err));
|
||||
goto end;
|
||||
}
|
||||
err = gcry_cipher_setkey(hd, write_key, key_length);
|
||||
if (err) {
|
||||
*error = wmem_strdup_printf(wmem_packet_scope(), "Decryption (setkey) failed: %s", gcry_strerror(err));
|
||||
gcry_cipher_close(hd);
|
||||
goto end;
|
||||
}
|
||||
|
||||
cipher = wmem_new(wmem_file_scope(), tls13_cipher);
|
||||
wmem_register_callback(wmem_file_scope(), tls13_cipher_destroy_cb, cipher);
|
||||
cipher->hd = hd;
|
||||
memcpy(cipher->iv, write_iv, iv_length);
|
||||
*error = NULL;
|
||||
|
||||
end:
|
||||
wmem_free(NULL, write_key);
|
||||
wmem_free(NULL, write_iv);
|
||||
return cipher;
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
/* (Pre-)master secrets calculations {{{ */
|
||||
|
@ -4209,6 +4269,7 @@ skip_mac:
|
|||
|
||||
#if defined(HAVE_LIBGNUTLS)
|
||||
|
||||
/* RSA private key file processing {{{ */
|
||||
static void
|
||||
ssl_find_private_key_by_pubkey(SslDecryptSession *ssl, GHashTable *key_hash,
|
||||
gnutls_datum_t *subjectPublicKeyInfo)
|
||||
|
@ -4256,7 +4317,6 @@ end:
|
|||
/* RSA private key file processing }}} */
|
||||
#endif /* ! defined(HAVE_LIBGNUTLS) */
|
||||
|
||||
|
||||
/*--- Start of dissector-related code below ---*/
|
||||
|
||||
/* get ssl data for this session. if no ssl data is found allocate a new one*/
|
||||
|
|
|
@ -313,6 +313,7 @@ typedef enum {
|
|||
/* Explicit and implicit nonce length (RFC 5116 - Section 3.2.1) */
|
||||
#define IMPLICIT_NONCE_LEN 4
|
||||
#define EXPLICIT_NONCE_LEN 8
|
||||
#define TLS13_AEAD_NONCE_LENGTH 12
|
||||
|
||||
/* TLS 1.3 Record type for selecting the appropriate secret. */
|
||||
typedef enum {
|
||||
|
@ -358,6 +359,15 @@ typedef struct _SslDecoder {
|
|||
StringInfo app_traffic_secret; /**< TLS 1.3 application traffic secret (if applicable), wmem file scope. */
|
||||
} SslDecoder;
|
||||
|
||||
/*
|
||||
* TLS 1.3 Cipher context. Simpler than SslDecoder since no compression is
|
||||
* required and all keys are calculated internally.
|
||||
*/
|
||||
typedef struct {
|
||||
gcry_cipher_hd_t hd;
|
||||
guint8 iv[TLS13_AEAD_NONCE_LENGTH];
|
||||
} tls13_cipher;
|
||||
|
||||
#define KEX_DHE_DSS 0x10
|
||||
#define KEX_DHE_PSK 0x11
|
||||
#define KEX_DHE_RSA 0x12
|
||||
|
@ -622,6 +632,25 @@ extern gint
|
|||
ssl_decrypt_record(SslDecryptSession *ssl, SslDecoder *decoder, guint8 ct, guint16 record_version,
|
||||
const guchar *in, guint16 inl, StringInfo *comp_str, StringInfo *out_str, guint *outl);
|
||||
|
||||
/**
|
||||
* Given a cipher algorithm and its mode, a hash algorithm and the secret (with
|
||||
* the same length as the hash algorithm), try to build a cipher. The algorithms
|
||||
* and mode are Libgcrypt identifiers.
|
||||
*/
|
||||
tls13_cipher *
|
||||
tls13_cipher_create(guint8 tls13_draft_version, int cipher_algo, int cipher_mode, int hash_algo, StringInfo *secret, const gchar **error);
|
||||
|
||||
/*
|
||||
* Calculate HKDF-Extract(salt, IKM) -> PRK according to RFC 5869.
|
||||
* Caller must ensure that 'prk' is large enough to store the digest.
|
||||
*/
|
||||
static inline gcry_error_t
|
||||
hkdf_extract(int algo, const guint8 *salt, size_t salt_len, const guint8 *ikm, size_t ikm_len, guint8 *prk)
|
||||
{
|
||||
/* PRK = HMAC-Hash(salt, IKM) where salt is key, and IKM is input. */
|
||||
return ws_hmac_buffer(algo, prk, ikm, ikm_len, salt, salt_len);
|
||||
}
|
||||
|
||||
|
||||
/* Common part bitween SSL and DTLS dissectors */
|
||||
/* Hash Functions for RSA private keys table */
|
||||
|
|
Loading…
Reference in New Issue