summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Wu <peter@lekensteyn.nl>2016-09-20 21:26:43 +0200
committerPeter Wu <peter@lekensteyn.nl>2017-01-29 01:52:58 +0100
commit380e87ebf19c2ecbcd6dfcc977d70b5971559446 (patch)
treeb422874df0bac8a342f7f9d75b0234e53de174b6
parentec9ce3fdad014274ce00de1768f9e11395a77e37 (diff)
downloadwireshark-ssl-aead-fixes.tar.gz
[WIP] ssl: add AEAD auth tag validation [rv2]ssl-aead-fixes
TODO - pull AEAD init/check outside function (for re-use with TLS1.3?) - fix sequence number for nonce, need to check what is wrong - more testing (+ extend test suite)? CCM seems to work. GCM auth check seems still broken. - test with older libgcrypt versions (currently tested with 1.7.6). Change-Id: I94dd2fd70e1281d85c954abfe523f7483d9ac68b
-rw-r--r--epan/dissectors/packet-dtls.c9
-rw-r--r--epan/dissectors/packet-ssl-utils.c156
-rw-r--r--epan/dissectors/packet-ssl-utils.h18
-rw-r--r--epan/dissectors/packet-ssl.c16
4 files changed, 141 insertions, 58 deletions
diff --git a/epan/dissectors/packet-dtls.c b/epan/dissectors/packet-dtls.c
index 0ac6a94340..dd8905c968 100644
--- a/epan/dissectors/packet-dtls.c
+++ b/epan/dissectors/packet-dtls.c
@@ -568,8 +568,8 @@ dtls_is_null_cipher(guint cipher )
}
static gboolean
-decrypt_dtls_record(tvbuff_t *tvb, packet_info *pinfo, guint32 offset,
- guint32 record_length, guint8 content_type, SslDecryptSession* ssl,
+decrypt_dtls_record(tvbuff_t *tvb, packet_info *pinfo, guint32 offset, SslDecryptSession *ssl,
+ guint8 content_type, guint16 record_version, guint16 record_length,
gboolean allow_fragments)
{
gboolean success;
@@ -618,7 +618,8 @@ decrypt_dtls_record(tvbuff_t *tvb, packet_info *pinfo, guint32 offset,
ssl_debug_printf("decrypt_dtls_record: no decoder available\n");
return FALSE;
}
- success = ssl_decrypt_record(ssl, decoder, content_type, tvb_get_ptr(tvb, offset, record_length), record_length,
+ success = ssl_decrypt_record(ssl, decoder, content_type, record_version,
+ tvb_get_ptr(tvb, offset, record_length), record_length,
&dtls_compressed_data, &dtls_decrypted_data, &dtls_decrypted_data_avail) == 0;
}
else if (dtls_is_null_cipher(ssl->session.cipher)) {
@@ -794,7 +795,7 @@ dissect_dtls_record(tvbuff_t *tvb, packet_info *pinfo,
/* try to decrypt record on the first pass, if possible. Store decrypted
* record for later usage (without having to decrypt again). */
if (ssl) {
- decrypt_dtls_record(tvb, pinfo, offset, record_length, content_type, ssl,
+ decrypt_dtls_record(tvb, pinfo, offset, ssl, content_type, version, record_length,
content_type == SSL_ID_APP_DATA || content_type == SSL_ID_HANDSHAKE);
}
decrypted = ssl_get_record_info(tvb, proto_dtls, pinfo, tvb_raw_offset(tvb)+offset, &record);
diff --git a/epan/dissectors/packet-ssl-utils.c b/epan/dissectors/packet-ssl-utils.c
index 76ff8f0865..76dd8d481a 100644
--- a/epan/dissectors/packet-ssl-utils.c
+++ b/epan/dissectors/packet-ssl-utils.c
@@ -57,6 +57,11 @@
#if defined(HAVE_LIBGNUTLS) && defined(HAVE_LIBGCRYPT)
#include <gnutls/abstract.h>
#endif
+#if GCRYPT_VERSION_NUMBER >= 0x010600 /* 1.6.0 */
+/* Whether to provide support for authentication in addition to decryption. */
+#define HAVE_LIBGCRYPT_AEAD
+#define HAVE_LIBGCRYPT_CCM
+#endif
/* Lookup tables {{{ */
const value_string ssl_version_short_names[] = {
@@ -1842,7 +1847,19 @@ static gint
ssl_cipher_init(gcry_cipher_hd_t *cipher, gint algo, guchar* sk,
guchar* iv, gint mode)
{
- gint gcry_modes[]={GCRY_CIPHER_MODE_STREAM,GCRY_CIPHER_MODE_CBC,GCRY_CIPHER_MODE_CTR,GCRY_CIPHER_MODE_CTR,GCRY_CIPHER_MODE_CTR};
+ gint gcry_modes[] = {
+ GCRY_CIPHER_MODE_STREAM,
+ GCRY_CIPHER_MODE_CBC,
+#ifdef HAVE_LIBGCRYPT_AEAD
+ GCRY_CIPHER_MODE_GCM,
+ GCRY_CIPHER_MODE_CCM,
+ GCRY_CIPHER_MODE_CCM
+#else
+ GCRY_CIPHER_MODE_CTR,
+ GCRY_CIPHER_MODE_CTR,
+ GCRY_CIPHER_MODE_CTR,
+#endif
+ };
gint err;
if (algo == -1) {
/* NULL mode */
@@ -2686,12 +2703,12 @@ ssl_change_cipher(SslDecryptSession *ssl_session, gboolean server)
}
int
-ssl_decrypt_record(SslDecryptSession*ssl, SslDecoder* decoder, gint ct,
- const guchar* in, guint inl, StringInfo* comp_str _U_, StringInfo* out, guint* outl)
+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)
{
ssl_debug_printf("ssl_decrypt_record: impossible without gnutls. ssl %p"
- "decoder %p ct %d, in %p inl %d out %p outl %p\n", ssl, decoder, ct,
- in, inl, out, outl);
+ "decoder %p ct %d, version %d in %p inl %d out %p outl %p\n", ssl, decoder, ct,
+ record_version, in, inl, out, outl);
return 0;
}
/* }}} */
@@ -2847,7 +2864,7 @@ ssl_create_decoder(const SslCipherSuite *cipher_suite, gint cipher_algo,
// decoders since "decryption" is easy for such ciphers.
dec->mac_key.data = dec->_mac_key_or_write_iv;
ssl_data_set(&dec->mac_key, mk, ssl_cipher_suite_dig(cipher_suite)->len);
- } else if (mode == MODE_GCM || mode == MODE_CCM || mode == MODE_CCM_8) {
+ } else if (ssl_is_aead_cipher(mode)) {
/* So far all AEAD ciphers derive the 4-byte implicit nonce part from the write IV */
dec->write_iv.data = dec->_mac_key_or_write_iv;
ssl_data_set(&dec->write_iv, iv, 4);
@@ -3153,7 +3170,7 @@ ssl_generate_keyring_material(SslDecryptSession*ssl_session)
if (cipher_suite->mode == MODE_CBC) {
write_iv_len = (guint)gcry_cipher_get_algo_blklen(cipher_algo);
- } else if (cipher_suite->mode == MODE_GCM || cipher_suite->mode == MODE_CCM || cipher_suite->mode == MODE_CCM_8) {
+ } else if (ssl_is_aead_cipher(cipher_suite->mode)) {
/* account for a four-byte salt for client and server side (from
* client_write_IV and server_write_IV), see GCMNonce (RFC 5288) */
write_iv_len = 4;
@@ -3562,11 +3579,13 @@ dtls_check_mac(SslDecoder*decoder, gint ct,int ver, guint8* data,
/* Assume that we are called only for a non-NULL decoder which also means that
* we have a non-NULL decoder->cipher_suite. */
int
-ssl_decrypt_record(SslDecryptSession*ssl,SslDecoder* decoder, gint ct,
- const guchar* in, guint inl, StringInfo* comp_str, StringInfo* out_str, guint* outl)
+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)
{
guint pad, worklen, uncomplen;
guint8 *mac;
+ guint auth_tag_len = 0;
+ gcry_error_t err;
ssl_debug_printf("ssl_decrypt_record ciphertext len %d\n", inl);
ssl_print_data("Ciphertext",in, inl);
@@ -3618,9 +3637,7 @@ ssl_decrypt_record(SslDecryptSession*ssl,SslDecoder* decoder, gint ct,
}
/* Nonce for GenericAEADCipher */
- if (decoder->cipher_suite->mode == MODE_GCM ||
- decoder->cipher_suite->mode == MODE_CCM ||
- decoder->cipher_suite->mode == MODE_CCM_8) {
+ if (ssl_is_aead_cipher(decoder->cipher_suite->mode)) {
/* 4 bytes write_iv, 8 bytes explicit_nonce, 4 bytes counter */
guchar gcm_nonce[16] = { 0 };
@@ -3630,65 +3647,98 @@ ssl_decrypt_record(SslDecryptSession*ssl,SslDecoder* decoder, gint ct,
return -1;
}
+ if (decoder->write_iv.data_len != 4) {
+ ssl_debug_printf("Unexpected implicit nonce length %d!\n", decoder->write_iv.data_len);
+ return -1;
+ }
+
+ /* Implicit (4) and explicit (8) part of nonce. */
+ memcpy(gcm_nonce, decoder->write_iv.data, decoder->write_iv.data_len); /* salt */
+ memcpy(gcm_nonce + decoder->write_iv.data_len, in, SSL_EX_NONCE_LEN_GCM);
+
if (decoder->cipher_suite->mode == MODE_GCM) {
- memcpy(gcm_nonce, decoder->write_iv.data, decoder->write_iv.data_len); /* salt */
- memcpy(gcm_nonce + decoder->write_iv.data_len, in, SSL_EX_NONCE_LEN_GCM);
/* NIST SP 800-38D, sect. 7.2 says that the 32-bit counter part starts
* at 1, and gets incremented before passing to the block cipher. */
gcm_nonce[4 + SSL_EX_NONCE_LEN_GCM + 3] = 2;
+ auth_tag_len = 16;
} else { /* MODE_CCM and MODE_CCM_8 */
/* The nonce for CCM and GCM are the same, but the nonce is used as input
* in the CCM algorithm described in RFC 3610. The nonce generated here is
* the one from RFC 3610 sect 2.3. Encryption. */
+#ifndef HAVE_LIBGCRYPT_AEAD
+ memmove(gcm_nonce + 1, gcm_nonce, 4 + SSL_EX_NONCE_LEN_GCM);
/* Flags: (L-1) ; L = 16 - 1 - nonceSize */
gcm_nonce[0] = 3 - 1;
-
/* struct { opaque salt[4]; opaque nonce_explicit[8] } CCMNonce (RFC 6655) */
- memcpy(gcm_nonce + 1, decoder->write_iv.data, decoder->write_iv.data_len); /* salt */
- memcpy(gcm_nonce + 1 + decoder->write_iv.data_len, in, SSL_EX_NONCE_LEN_GCM);
gcm_nonce[4 + SSL_EX_NONCE_LEN_GCM + 3] = 1;
+#endif
+ auth_tag_len = decoder->cipher_suite->mode == MODE_CCM_8 ? 8 : 16;
+ }
+
+ if (inl < SSL_EX_NONCE_LEN_GCM + auth_tag_len) {
+ ssl_debug_printf("%s failed: missing tag of length %d, available %d\n", G_STRFUNC, auth_tag_len, inl);
+ return -1;
+ }
+ /* Decrypted fragment without explicit nonce and trailing auth tag. */
+ worklen = inl - SSL_EX_NONCE_LEN_GCM - auth_tag_len;
+
+#ifdef HAVE_LIBGCRYPT_AEAD
+ gcry_cipher_reset(decoder->evp);
+ ssl_print_data("nonce", gcm_nonce, 12);
+ err = gcry_cipher_setiv(decoder->evp, gcm_nonce, 12);
+ if (err) {
+ ssl_debug_printf("%s failed to set nonce: %s\n", G_STRFUNC, gcry_strerror(err));
+ return -1;
+ }
+
+ if (decoder->cipher_suite->mode == MODE_CCM || decoder->cipher_suite->mode == MODE_CCM_8) {
+ guint64 lengths[3] = {
+ worklen, /* size of plaintext */
+ 13, /* size of additional authenticated data */
+ auth_tag_len
+ };
+ gcry_cipher_ctl(decoder->evp, GCRYCTL_SET_CCM_LENGTHS, lengths, sizeof(lengths));
}
- pad = gcry_cipher_setctr (decoder->evp, gcm_nonce, sizeof (gcm_nonce));
- if (pad != 0) {
+ guchar aad[13];
+ // XXX sequence number is sometimes one too small, why?
+ phton64(aad, decoder->seq); /* record sequence number */
+ aad[8] = ct; /* TLSCompressed.type */
+ phton16(aad + 9, record_version); /* TLSCompressed.version */
+ phton16(aad + 11, worklen); /* TLSCompressed.length */
+ ssl_print_data("AAD", aad, sizeof(aad));
+ err = gcry_cipher_authenticate(decoder->evp, aad, sizeof(aad));
+ if (err) {
+ ssl_debug_printf("%s failed to set AAD: %s\n", G_STRFUNC, gcry_strerror(err));
+ return -1;
+ }
+#else
+ (void)record_version;
+ err = gcry_cipher_setctr(decoder->evp, gcm_nonce, sizeof(gcm_nonce));
+ if (err != 0) {
ssl_debug_printf("ssl_decrypt_record failed: failed to set CTR: %s %s\n",
- gcry_strsource (pad), gcry_strerror (pad));
+ gcry_strsource (err), gcry_strerror (err));
return -1;
}
+#endif
inl -= SSL_EX_NONCE_LEN_GCM;
in += SSL_EX_NONCE_LEN_GCM;
+ } else {
+ /* For other ciphers (like CBC), assume plaintext to have the same
+ * length as its input. Padding will be removed later. */
+ worklen = inl;
}
/* First decrypt*/
- if ((pad = ssl_cipher_decrypt(&decoder->evp, out_str->data, out_str->data_len, in, inl))!= 0) {
+ memset(out_str->data, 0xcc, out_str->data_len); /// XXX remove me
+ if ((pad = ssl_cipher_decrypt(&decoder->evp, out_str->data, out_str->data_len, in, worklen)) != 0) {
ssl_debug_printf("ssl_decrypt_record failed: ssl_cipher_decrypt: %s %s\n", gcry_strsource (pad),
gcry_strerror (pad));
return -1;
}
- ssl_print_data("Plaintext", out_str->data, inl);
- worklen=inl;
+ ssl_print_data("Plaintext", out_str->data, worklen);
- /* RFC 5116 sect 5.1/5.3: AES128/256 GCM/CCM uses 16 bytes for auth tag
- * RFC 6655 sect 6.1: AEAD_AES_128_CCM uses 16 bytes for auth tag */
- if (decoder->cipher_suite->mode == MODE_GCM ||
- decoder->cipher_suite->mode == MODE_CCM) {
- if (worklen < 16) {
- ssl_debug_printf("ssl_decrypt_record failed: missing tag, work %d\n", worklen);
- return -1;
- }
- /* XXX - validate auth tag */
- worklen -= 16;
- }
- /* RFC 6655 sect 6.1: AEAD_AES_128_CCM_8 uses 8 bytes for auth tag */
- if (decoder->cipher_suite->mode == MODE_CCM_8) {
- if (worklen < 8) {
- ssl_debug_printf("ssl_decrypt_record failed: missing tag, work %d\n", worklen);
- return -1;
- }
- /* XXX - validate auth tag */
- worklen -= 8;
- }
/* strip padding for GenericBlockCipher */
if (decoder->cipher_suite->mode == MODE_CBC) {
@@ -3716,8 +3766,24 @@ ssl_decrypt_record(SslDecryptSession*ssl,SslDecoder* decoder, gint ct,
}
worklen-=ssl_cipher_suite_dig(decoder->cipher_suite)->len;
mac = out_str->data + worklen;
- } else /* if (decoder->cipher_suite->mode == MODE_GCM) */ {
- /* GenericAEADCipher has no MAC */
+ } else /* if (ssl_is_aead_cipher(decoder->cipher_suite->mode)) */ {
+ /* GenericAEADCipher has no MAC, check auth tag for authenticity. */
+#ifdef HAVE_LIBGCRYPT_AEAD
+ guchar auth_tag[16];
+ err = gcry_cipher_gettag(decoder->evp, auth_tag, auth_tag_len);
+ if (err) {
+ ssl_debug_printf("%s failed: cannot obtain tag: %s %s\n", G_STRFUNC,
+ gcry_strsource(err), gcry_strerror(err));
+ } else if (!memcmp(auth_tag, in + worklen, auth_tag_len)) {
+ ssl_debug_printf("%s auth tag OK\n", G_STRFUNC);
+ } else {
+ ssl_debug_printf("%s auth tag mismatch\n", G_STRFUNC);
+ ssl_print_data("auth_tag(expect)", auth_tag, auth_tag_len);
+ ssl_print_data("auth_tag(actual)", in + worklen, auth_tag_len);
+ }
+#else
+ ssl_debug_printf("Libgcrypt is older than 1.6, unable to verify authenticity!\n");
+#endif
goto skip_mac;
}
diff --git a/epan/dissectors/packet-ssl-utils.h b/epan/dissectors/packet-ssl-utils.h
index 8760103922..50240b5351 100644
--- a/epan/dissectors/packet-ssl-utils.h
+++ b/epan/dissectors/packet-ssl-utils.h
@@ -268,6 +268,19 @@ typedef enum {
MODE_CCM_8 /* AEAD_AES_{128,256}_CCM with 8 byte auth tag */
} ssl_cipher_mode_t;
+static inline gboolean
+ssl_is_aead_cipher(ssl_cipher_mode_t mode)
+{
+ switch (mode) {
+ case MODE_GCM:
+ case MODE_CCM:
+ case MODE_CCM_8:
+ return TRUE;
+ default:
+ return FALSE;
+ }
+}
+
/* Explicit nonce length */
#define SSL_EX_NONCE_LEN_GCM 8 /* RFC 5288 - section 3 */
@@ -544,6 +557,7 @@ ssl_change_cipher(SslDecryptSession *ssl_session, gboolean server);
@param ssl ssl_session the store all the session data
@param decoder the stream decoder to be used
@param ct the content type of this ssl record
+ @param record_version the version as contained in the record
@param in a pointer to the ssl record to be decrypted
@param inl the record length
@param comp_str a pointer to the store the compression data
@@ -551,8 +565,8 @@ ssl_change_cipher(SslDecryptSession *ssl_session, gboolean server);
@param outl the decrypted data len
@return 0 on success */
extern gint
-ssl_decrypt_record(SslDecryptSession* ssl,SslDecoder* decoder, gint ct,
- const guchar* in, guint inl, StringInfo* comp_str, StringInfo* out_str, guint* outl);
+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);
/* Common part bitween SSL and DTLS dissectors */
diff --git a/epan/dissectors/packet-ssl.c b/epan/dissectors/packet-ssl.c
index ee016e389f..2a47cf7e03 100644
--- a/epan/dissectors/packet-ssl.c
+++ b/epan/dissectors/packet-ssl.c
@@ -884,8 +884,8 @@ dissect_ssl(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_)
* length "ssl_decrypted_data_avail".
*/
static gboolean
-decrypt_ssl3_record(tvbuff_t *tvb, packet_info *pinfo, guint32 offset,
- guint32 record_length, guint8 content_type, SslDecryptSession *ssl,
+decrypt_ssl3_record(tvbuff_t *tvb, packet_info *pinfo, guint32 offset, SslDecryptSession *ssl,
+ guint8 content_type, guint16 record_version, guint16 record_length,
gboolean allow_fragments)
{
gboolean success;
@@ -923,9 +923,9 @@ decrypt_ssl3_record(tvbuff_t *tvb, packet_info *pinfo, guint32 offset,
/* run decryption and add decrypted payload to protocol data, if decryption
* is successful*/
ssl_decrypted_data_avail = ssl_decrypted_data.data_len;
- success = ssl_decrypt_record(ssl, decoder,
- content_type, tvb_get_ptr(tvb, offset, record_length),
- record_length, &ssl_compressed_data, &ssl_decrypted_data, &ssl_decrypted_data_avail) == 0;
+ success = ssl_decrypt_record(ssl, decoder, content_type, record_version,
+ tvb_get_ptr(tvb, offset, record_length), record_length,
+ &ssl_compressed_data, &ssl_decrypted_data, &ssl_decrypted_data_avail) == 0;
/* */
if (!success) {
/* save data to update IV if valid session key is obtained later */
@@ -1526,7 +1526,7 @@ dissect_ssl3_record(tvbuff_t *tvb, packet_info *pinfo,
* } TLSPlaintext;
*/
guint32 record_length;
- guint16 version;
+ guint16 record_version, version;
guint8 content_type;
guint8 next_byte;
proto_tree *ti;
@@ -1588,6 +1588,7 @@ dissect_ssl3_record(tvbuff_t *tvb, packet_info *pinfo,
*/
content_type = tvb_get_guint8(tvb, offset);
version = tvb_get_ntohs(tvb, offset + 1);
+ record_version = version;
record_length = tvb_get_ntohs(tvb, offset + 3);
if (ssl_is_valid_content_type(content_type)) {
@@ -1698,7 +1699,8 @@ dissect_ssl3_record(tvbuff_t *tvb, packet_info *pinfo,
* used as 'key' to identify this record in the packet (we can have multiple
* handshake records in the same frame). */
if (ssl) {
- decrypt_ssl3_record(tvb, pinfo, offset, record_length, content_type, ssl,
+ decrypt_ssl3_record(tvb, pinfo, offset, ssl,
+ content_type, record_version, record_length,
content_type == SSL_ID_APP_DATA ||
content_type == SSL_ID_HANDSHAKE);
}