QUIC: refactor packet protection cipher initialization

The old key update mechanism was never tested and was probably broken
(using "pp_state->cipher[1 - key_phase]" does not seem correct). To
prepare for the handshake cipher (draft -13), refactor it a bit and
remove the PKN parameter.

Change-Id: I481cc00e2e1d44024a709f8b4115ffe5924988e7
Ping-Bug: 13881
Reviewed-on: https://code.wireshark.org/review/29676
Petri-Dish: Peter Wu <peter@lekensteyn.nl>
Tested-by: Petri Dish Buildbot
Reviewed-by: Alexis La Goutte <alexis.lagoutte@gmail.com>
Reviewed-by: Peter Wu <peter@lekensteyn.nl>
This commit is contained in:
Peter Wu 2018-09-15 17:23:45 +02:00
parent 866ff1ec26
commit 59ac823c3f
1 changed files with 101 additions and 95 deletions

View File

@ -172,7 +172,7 @@ typedef struct quic_cipher {
* Packet protection state for an endpoint.
*/
typedef struct quic_pp_state {
guint8 *secret; /**< client_pp_secret_N / server_pp_secret_N */
guint8 *next_secret; /**< Next application traffic secret. */
quic_cipher cipher[2]; /**< Cipher for KEY_PHASE 0/1 */
guint64 changed_in_pkn; /**< Packet number where key change occurred. */
gboolean key_phase : 1; /**< Current key phase. */
@ -195,9 +195,9 @@ typedef struct quic_info_data {
address server_address;
guint16 server_port;
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. */
int pn_cipher_algo; /**< Cipher algorithm for packet number encryption. */
int cipher_algo; /**< Cipher algorithm for packet number and packet encryption. */
int cipher_mode; /**< Cipher mode for packet encryption. */
quic_cipher client_handshake_cipher;
quic_cipher server_handshake_cipher;
quic_pp_state_t client_pp;
@ -401,6 +401,14 @@ static const value_string quic_error_code_vals[] = {
};
static value_string_ext quic_error_code_vals_ext = VALUE_STRING_EXT_INIT(quic_error_code_vals);
static void
quic_cipher_reset(quic_cipher *cipher)
{
gcry_cipher_close(cipher->pn_cipher);
gcry_cipher_close(cipher->pp_cipher);
memset(cipher, 0, sizeof(*cipher));
}
/* Inspired from ngtcp2 */
static guint64 quic_pkt_adjust_pkt_num(guint64 max_pkt_num, guint64 pkt_num,
size_t n) {
@ -763,16 +771,12 @@ static void
quic_connection_destroy(gpointer data, gpointer user_data _U_)
{
quic_info_data_t *conn = (quic_info_data_t *)data;
gcry_cipher_close(conn->client_handshake_cipher.pn_cipher);
gcry_cipher_close(conn->server_handshake_cipher.pn_cipher);
gcry_cipher_close(conn->client_handshake_cipher.pp_cipher);
gcry_cipher_close(conn->server_handshake_cipher.pp_cipher);
quic_cipher_reset(&conn->client_handshake_cipher);
quic_cipher_reset(&conn->server_handshake_cipher);
for (int i = 0; i < 2; i++) {
gcry_cipher_close(conn->client_pp.cipher[i].pn_cipher);
gcry_cipher_close(conn->server_pp.cipher[i].pn_cipher);
gcry_cipher_close(conn->client_pp.cipher[i].pp_cipher);
gcry_cipher_close(conn->server_pp.cipher[i].pp_cipher);
quic_cipher_reset(&conn->client_pp.cipher[i]);
quic_cipher_reset(&conn->server_pp.cipher[i]);
}
}
/* QUIC Connection tracking. }}} */
@ -1320,9 +1324,8 @@ quic_derive_handshake_secrets(const quic_cid_t *cid,
* See https://tools.ietf.org/html/draft-ietf-quic-tls-12#section-5.6
*/
static gboolean
quic_get_pn_cipher_algo(int cipher_algo, int *pn_cipher_algo, int *pn_cipher_mode)
quic_get_pn_cipher_algo(int cipher_algo, int *pn_cipher_mode)
{
*pn_cipher_algo = cipher_algo;
switch (cipher_algo) {
case GCRY_CIPHER_AES128:
case GCRY_CIPHER_AES256:
@ -1338,6 +1341,42 @@ quic_get_pn_cipher_algo(int cipher_algo, int *pn_cipher_algo, int *pn_cipher_mod
}
}
/*
* (Re)initialize the PNE/PP ciphers using the given cipher algorithm.
* If the optional base secret is given, then its length MUST match the hash
* algorithm output.
*/
static gboolean
quic_cipher_prepare(guint32 version, quic_cipher *cipher, int hash_algo, int cipher_algo, int cipher_mode, guint8 *secret, const char **error)
{
/* Clear previous state (if any). */
quic_cipher_reset(cipher);
int pn_cipher_mode;
if (!quic_get_pn_cipher_algo(cipher_algo, &pn_cipher_mode)) {
*error = "Unsupported cipher algorithm";
return FALSE;
}
if (gcry_cipher_open(&cipher->pn_cipher, cipher_algo, pn_cipher_mode, 0) ||
gcry_cipher_open(&cipher->pp_cipher, cipher_algo, cipher_mode, 0)) {
quic_cipher_reset(cipher);
*error = "Failed to create ciphers";
return FALSE;
}
if (secret) {
guint cipher_keylen = (guint8) gcry_cipher_get_algo_keylen(cipher_algo);
if (!quic_cipher_init(version, cipher, hash_algo, cipher_keylen, secret)) {
quic_cipher_reset(cipher);
*error = "Failed to derive key material for cipher";
return FALSE;
}
}
return TRUE;
}
static gboolean
quic_create_handshake_decoders(const quic_cid_t *cid, const gchar **error, quic_info_data_t *quic_info)
{
@ -1349,32 +1388,12 @@ quic_create_handshake_decoders(const quic_cid_t *cid, const gchar **error, quic_
return FALSE;
}
/* Destroy any previous ciphers in case there exist multiple Initial packets */
gcry_cipher_close(quic_info->client_handshake_cipher.pn_cipher);
gcry_cipher_close(quic_info->server_handshake_cipher.pn_cipher);
gcry_cipher_close(quic_info->client_handshake_cipher.pp_cipher);
gcry_cipher_close(quic_info->server_handshake_cipher.pp_cipher);
memset(&quic_info->client_handshake_cipher, 0, sizeof(quic_cipher));
memset(&quic_info->server_handshake_cipher, 0, sizeof(quic_cipher));
/* packet numbers are protected with AES128-CTR */
if (gcry_cipher_open(&quic_info->client_handshake_cipher.pn_cipher, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_CTR, 0) ||
gcry_cipher_open(&quic_info->server_handshake_cipher.pn_cipher, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_CTR, 0)) {
*error = "Failed to create packet number ciphers";
return FALSE;
}
/* handshake packets are protected with AEAD_AES_128_GCM */
if (gcry_cipher_open(&quic_info->client_handshake_cipher.pp_cipher, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_GCM, 0) ||
gcry_cipher_open(&quic_info->server_handshake_cipher.pp_cipher, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_GCM, 0)) {
*error = "Failed to create ciphers";
return FALSE;
}
guint cipher_keylen = (guint8) gcry_cipher_get_algo_keylen(GCRY_CIPHER_AES128);
if (!quic_cipher_init(version, &quic_info->client_handshake_cipher, GCRY_MD_SHA256, cipher_keylen, client_secret) ||
!quic_cipher_init(version, &quic_info->server_handshake_cipher, GCRY_MD_SHA256, cipher_keylen, server_secret)) {
*error = "Failed to derive key material for cipher";
/* 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,
GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_GCM, client_secret, error) ||
!quic_cipher_prepare(version, &quic_info->server_handshake_cipher, GCRY_MD_SHA256,
GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_GCM, server_secret, error)) {
return FALSE;
}
@ -1430,7 +1449,7 @@ quic_get_pp0_secret(packet_info *pinfo, int hash_algo, quic_pp_state_t *pp_state
if (!tls13_exporter(pinfo, FALSE, label, NULL, 0, hash_len, &pp_secret)) {
return FALSE;
}
pp_state->secret = (guint8 *)wmem_memdup(wmem_file_scope(), pp_secret, hash_len);
pp_state->next_secret = (guint8 *)wmem_memdup(wmem_file_scope(), pp_secret, hash_len);
wmem_free(NULL, pp_secret);
return TRUE;
}
@ -1475,9 +1494,9 @@ static void
quic_update_key(int hash_algo, quic_pp_state_t *pp_state, gboolean from_client)
{
guint hash_len = gcry_md_get_algo_dlen(hash_algo);
qhkdf_expand(hash_algo, pp_state->secret, hash_len,
qhkdf_expand(hash_algo, pp_state->next_secret, hash_len,
from_client ? "client 1rtt" : "server 1rtt",
pp_state->secret, hash_len);
pp_state->next_secret, hash_len);
}
/**
@ -1485,10 +1504,11 @@ quic_update_key(int hash_algo, quic_pp_state_t *pp_state, gboolean from_client)
* See also "PROTECTED PAYLOAD DECRYPTION" comment on top of this file.
*/
static quic_cipher *
quic_get_pp_cipher(packet_info *pinfo, gboolean key_phase, guint64 pkn, quic_info_data_t *quic_info, gboolean from_server)
quic_get_pp_cipher(packet_info *pinfo, gboolean key_phase, quic_info_data_t *quic_info, gboolean from_server)
{
gboolean needs_key_update = FALSE;
guint32 version = quic_info->version;
const char *error = NULL;
gboolean success = FALSE;
/* Keys were previously not available. */
if (quic_info->skip_decryption) {
@ -1500,10 +1520,9 @@ quic_get_pp_cipher(packet_info *pinfo, gboolean key_phase, guint64 pkn, quic_inf
quic_pp_state_t *pp_state = !from_server ? client_pp : server_pp;
/* Try to lookup secrets if not available. */
if (!quic_info->client_pp.secret) {
int cipher_algo, cipher_mode;
if (!quic_info->client_pp.next_secret) {
/* Query TLS for the cipher suite. */
if (!tls_get_cipher_info(pinfo, &cipher_algo, &cipher_mode, &quic_info->hash_algo)) {
if (!tls_get_cipher_info(pinfo, &quic_info->cipher_algo, &quic_info->cipher_mode, &quic_info->hash_algo)) {
/* No previous TLS handshake found or unsupported ciphers, fail. */
quic_info->skip_decryption = TRUE;
return NULL;
@ -1516,59 +1535,50 @@ quic_get_pp_cipher(packet_info *pinfo, gboolean key_phase, guint64 pkn, quic_inf
return NULL;
}
int pn_cipher_algo, pn_cipher_mode;
if (!quic_get_pn_cipher_algo(cipher_algo, &pn_cipher_algo, &pn_cipher_mode) ||
gcry_cipher_open(&client_pp->cipher[0].pn_cipher, pn_cipher_algo, pn_cipher_mode, 0) ||
gcry_cipher_open(&server_pp->cipher[0].pn_cipher, pn_cipher_algo, pn_cipher_mode, 0) ||
gcry_cipher_open(&client_pp->cipher[1].pn_cipher, pn_cipher_algo, pn_cipher_mode, 0) ||
gcry_cipher_open(&server_pp->cipher[1].pn_cipher, pn_cipher_algo, pn_cipher_mode, 0)) {
quic_info->skip_decryption = TRUE;
return NULL;
}
quic_info->pn_cipher_algo = pn_cipher_algo;
/* Create initial cipher handles for KEY_PHASE 0 and 1. */
if (gcry_cipher_open(&client_pp->cipher[0].pp_cipher, cipher_algo, cipher_mode, 0) ||
gcry_cipher_open(&server_pp->cipher[0].pp_cipher, cipher_algo, cipher_mode, 0) ||
gcry_cipher_open(&client_pp->cipher[1].pp_cipher, cipher_algo, cipher_mode, 0) ||
gcry_cipher_open(&server_pp->cipher[1].pp_cipher, cipher_algo, cipher_mode, 0)) {
if (!quic_cipher_prepare(version, &client_pp->cipher[0], quic_info->hash_algo,
quic_info->cipher_algo, quic_info->cipher_mode, client_pp->next_secret, &error) ||
!quic_cipher_prepare(version, &server_pp->cipher[0], quic_info->hash_algo,
quic_info->cipher_algo, quic_info->cipher_mode, server_pp->next_secret, &error)) {
quic_info->skip_decryption = TRUE;
return NULL;
}
quic_info->cipher_keylen = (guint8) gcry_cipher_get_algo_keylen(cipher_algo);
/* Set key for cipher handles KEY_PHASE 0. */
if (!quic_cipher_init(version, &client_pp->cipher[0], quic_info->hash_algo, quic_info->cipher_keylen, client_pp->secret) ||
!quic_cipher_init(version, &server_pp->cipher[0], quic_info->hash_algo, quic_info->cipher_keylen, server_pp->secret)) {
quic_info->skip_decryption = TRUE;
return NULL;
}
pp_state->changed_in_pkn = pkn;
/*
* If the first received packet has KEY_PHASE=1, then the key must be
* updated now.
*/
needs_key_update = key_phase;
quic_update_key(quic_info->hash_algo, pp_state, !from_server);
}
/*
* Check for key phase change. Either it is out-of-order (when packet number
* is lower than the one triggering the most recent key update) or it is
* actually a key update (if the packet number is higher).
* TODO verify decryption before switching keys.
* If the key phase changed, try to decrypt the packet using the new cipher.
* If that fails, then it is either a malicious packet or out-of-order.
* In that case, try the previous cipher (unless it is the very first KP1).
*/
if (key_phase != pp_state->key_phase) {
if (!needs_key_update && pkn < pp_state->changed_in_pkn) {
/* Packet is from before the key phase change, use old cipher. */
return &pp_state->cipher[1 - key_phase];
} else {
/* Key update requested, update key. */
quic_cipher new_cipher;
memset(&new_cipher, 0, sizeof(quic_cipher));
if (!quic_cipher_prepare(version, &new_cipher, quic_info->hash_algo,
quic_info->cipher_algo, quic_info->cipher_mode, server_pp->next_secret, &error)) {
/* This should never be reached, if the parameters were wrong
* before, then it should have set "skip_decryption". */
REPORT_DISSECTOR_BUG("quic_cipher_prepare unexpectedly failed: %s", error);
return NULL;
}
// TODO verify decryption before switching keys.
success = TRUE;
if (success) {
/* Verified the cipher, use it from now on and rotate the key. */
quic_cipher_reset(&pp_state->cipher[key_phase]);
pp_state->cipher[key_phase] = new_cipher;
quic_update_key(quic_info->hash_algo, pp_state, !from_server);
quic_cipher_init(version, &pp_state->cipher[key_phase], quic_info->hash_algo, quic_info->cipher_keylen, pp_state->secret);
pp_state->key_phase = key_phase;
pp_state->changed_in_pkn = pkn;
//pp_state->changed_in_pkn = pkn;
return &pp_state->cipher[key_phase];
} else {
// TODO fallback to previous cipher
return NULL;
}
}
@ -1806,18 +1816,14 @@ dissect_quic_short_header(tvbuff_t *tvb, packet_info *pinfo, proto_tree *quic_tr
#ifdef HAVE_LIBGCRYPT_AEAD
if (!PINFO_FD_VISITED(pinfo) && conn) {
// TODO fix key update. Chicken: need decrypted PKN to detect whether a
// KeyUpdate was legitimate and create new cipher. Egg: need cipher to
// decrypt PKN...
pkn = 0;
cipher = quic_get_pp_cipher(pinfo, key_phase, pkn, conn, from_server);
cipher = quic_get_pp_cipher(pinfo, key_phase, conn, from_server);
}
#endif /* !HAVE_LIBGCRYPT_AEAD */
/* Packet Number */
pkn_len = dissect_quic_packet_number(tvb, pinfo, quic_tree, offset, conn, quic_packet, from_server,
cipher, conn ? conn->pn_cipher_algo : 0, &pkn);
cipher, conn ? conn->cipher_algo : 0, &pkn);
if (pkn_len == 0) {
return offset;
}