From e9e4cb4ed045a12c984a367bee0ddcbb3f27ae0d Mon Sep 17 00:00:00 2001 From: mancha Date: Sun, 29 Sep 2013 Subject: CVE-2013-1619 and CVE-2013-2116 [GNUTLS-SA-2013-1,GNUTLS-SA-2013-2] Fix to avoid a timing attack in TLS CBC record parsing (aka Lucky 13). For background, see http://www.isg.rhul.ac.uk/tls/Lucky13.html The fix for CVE-2013-2116 is folded into this patch since it addresses a problem introduced by the fix for CVE-2013-1619. This is a backport adaptation for use with GnuTLS 2.8.4. Relevant upstream commits: -------------------------- https://gitorious.org/gnutls/gnutls/commit/458c67cf98740e https://gitorious.org/gnutls/gnutls/commit/93b7fcfa3297a9 https://gitorious.org/gnutls/gnutls/commit/32a7367601a3fd https://gitorious.org/gnutls/gnutls/commit/63a331df6aa0ec --- gnutls_algorithms.c | 14 +++++ gnutls_algorithms.h | 3 + gnutls_cipher.c | 132 ++++++++++++++++++++++++++++++++-------------------- gnutls_hash_int.h | 21 ++++++++ 4 files changed, 121 insertions(+), 49 deletions(-) --- a/lib/gnutls_cipher.c 2013-09-27 +++ b/lib/gnutls_cipher.c 2013-09-27 @@ -287,6 +287,26 @@ calc_enc_length (gnutls_session_t sessio return length; } +#define PREAMBLE_SIZE 16 +static inline int make_preamble(opaque* uint64_data, opaque type, uint16_t c_length, opaque ver, opaque* preamble) +{ + opaque minor = _gnutls_version_get_minor (ver); + opaque major = _gnutls_version_get_major (ver); + opaque *p = preamble; + + memcpy(p, uint64_data, 8); + p+=8; + *p=type; p++; + if (_gnutls_version_has_variable_padding (ver)) + { /* TLS 1.0 or higher */ + *p = major; p++; + *p = minor; p++; + } + memcpy(p, &c_length, 2); + p+=2; + return p-preamble; +} + /* This is the actual encryption * Encrypts the given compressed datum, and puts the result to cipher_data, * which has cipher_size size. @@ -304,11 +324,11 @@ _gnutls_compressed2ciphertext (gnutls_se int length, ret; digest_hd_st td; uint8_t type = _type; - uint8_t major, minor; + opaque preamble[PREAMBLE_SIZE]; + int preamble_size; int hash_size = _gnutls_hash_get_algo_len (session->security_parameters. write_mac_algorithm); - gnutls_protocol_t ver; int blocksize = _gnutls_cipher_get_block_size (session->security_parameters. write_bulk_cipher_algorithm); @@ -316,40 +336,27 @@ _gnutls_compressed2ciphertext (gnutls_se _gnutls_cipher_is_block (session->security_parameters. write_bulk_cipher_algorithm); opaque *data_ptr; - - - ver = gnutls_protocol_get_version (session); - minor = _gnutls_version_get_minor (ver); - major = _gnutls_version_get_major (ver); - + int ver = gnutls_protocol_get_version (session); /* Initialize MAC */ - ret = mac_init (&td, session->security_parameters.write_mac_algorithm, - session->connection_state.write_mac_secret.data, - session->connection_state.write_mac_secret.size, ver); - - if (ret < 0 - && session->security_parameters.write_mac_algorithm != GNUTLS_MAC_NULL) - { - gnutls_assert (); - return ret; - } c_length = _gnutls_conv_uint16 (compressed.size); if (session->security_parameters.write_mac_algorithm != GNUTLS_MAC_NULL) { /* actually when the algorithm in not the NULL one */ - _gnutls_hmac (&td, - UINT64DATA (session->connection_state. - write_sequence_number), 8); - - _gnutls_hmac (&td, &type, 1); - if (ver >= GNUTLS_TLS1) - { /* TLS 1.0 or higher */ - _gnutls_hmac (&td, &major, 1); - _gnutls_hmac (&td, &minor, 1); - } - _gnutls_hmac (&td, &c_length, 2); + digest_hd_st td; + + ret = mac_init (&td, session->security_parameters.write_mac_algorithm, + session->connection_state.write_mac_secret.data, + session->connection_state.write_mac_secret.size, ver); + + if (ret < 0) + { + gnutls_assert (); + return ret; + } + preamble_size = make_preamble( UINT64DATA (session->connection_state.write_sequence_number), type, c_length, ver, preamble); + _gnutls_hmac (&td, preamble, preamble_size); _gnutls_hmac (&td, compressed.data, compressed.size); mac_deinit (&td, MAC, ver); } @@ -418,6 +425,49 @@ _gnutls_compressed2ciphertext (gnutls_se return length; } +static void dummy_wait(gnutls_session_t session, gnutls_datum_t* plaintext, + unsigned pad_failed, unsigned int pad, unsigned total, int ver) +{ + /* this hack is only needed on CBC ciphers */ + if (_gnutls_cipher_is_block (session->security_parameters.read_bulk_cipher_algorithm) == CIPHER_BLOCK) + { + uint8_t MAC[MAX_HASH_SIZE]; + unsigned len; + digest_hd_st td; + int ret; + + ret = mac_init (&td, session->security_parameters.read_mac_algorithm, + session->connection_state.read_mac_secret.data, + session->connection_state.read_mac_secret.size, ver); + + if (ret < 0) + return; + + /* force an additional hash compression function evaluation to prevent timing + * attacks that distinguish between wrong-mac + correct pad, from wrong-mac + incorrect pad. + */ + if (pad_failed == 0 && pad > 0) + { + len = _gnutls_get_hash_block_len(session->security_parameters.read_mac_algorithm); + if (len > 0) + { + /* This is really specific to the current hash functions. + * It should be removed once a protocol fix is in place. + */ + if ((pad+total) % len > len-9 && total % len <= len-9) + { + if (len < plaintext->size) + _gnutls_hmac (&td, plaintext->data, len); + else + _gnutls_hmac (&td, plaintext->data, plaintext->size); + } + } + } + + mac_deinit (&td, MAC, ver); + } +} + /* Deciphers the ciphertext packet, and puts the result to compress_data, of compress_size. * Returns the actual compressed packet size. */ @@ -429,38 +479,22 @@ _gnutls_ciphertext2compressed (gnutls_se { uint8_t MAC[MAX_HASH_SIZE]; uint16_t c_length; - uint8_t pad; + unsigned int pad = 0; int length; digest_hd_st td; uint16_t blocksize; int ret, i, pad_failed = 0; - uint8_t major, minor; - gnutls_protocol_t ver; + opaque preamble[PREAMBLE_SIZE]; + int preamble_size = 0; + int ver = gnutls_protocol_get_version (session); int hash_size = _gnutls_hash_get_algo_len (session->security_parameters. read_mac_algorithm); - ver = gnutls_protocol_get_version (session); - minor = _gnutls_version_get_minor (ver); - major = _gnutls_version_get_major (ver); - blocksize = _gnutls_cipher_get_block_size (session->security_parameters. read_bulk_cipher_algorithm); - /* initialize MAC - */ - ret = mac_init (&td, session->security_parameters.read_mac_algorithm, - session->connection_state.read_mac_secret.data, - session->connection_state.read_mac_secret.size, ver); - - if (ret < 0 - && session->security_parameters.read_mac_algorithm != GNUTLS_MAC_NULL) - { - gnutls_assert (); - return GNUTLS_E_INTERNAL_ERROR; - } - /* actual decryption (inplace) */ switch (_gnutls_cipher_is_block @@ -508,31 +542,25 @@ _gnutls_ciphertext2compressed (gnutls_se gnutls_assert (); return GNUTLS_E_DECRYPTION_FAILED; } - pad = ciphertext.data[ciphertext.size - 1] + 1; /* pad */ - - if ((int) pad > (int) ciphertext.size - hash_size) - { - gnutls_assert (); - _gnutls_record_log - ("REC[%p]: Short record length %d > %d - %d (under attack?)\n", - session, pad, ciphertext.size, hash_size); - /* We do not fail here. We check below for the - * the pad_failed. If zero means success. - */ - pad_failed = GNUTLS_E_DECRYPTION_FAILED; - } - - length = ciphertext.size - hash_size - pad; - - /* Check the pading bytes (TLS 1.x) + pad = ciphertext.data[ciphertext.size - 1]; /* pad */ + if (pad+1 > ciphertext.size-hash_size) + pad_failed = GNUTLS_E_DECRYPTION_FAILED; + + /* Check the pading bytes (TLS 1.x). + * Note that we access all 256 bytes of ciphertext for padding check + * because there is a timing channel in that memory access (in certain CPUs */ if (ver >= GNUTLS_TLS1 && pad_failed == 0) for (i = 2; i < pad; i++) { - if (ciphertext.data[ciphertext.size - i] != - ciphertext.data[ciphertext.size - 1]) + if (ciphertext.data[ciphertext.size - i] != pad) pad_failed = GNUTLS_E_DECRYPTION_FAILED; } + + if (pad_failed) + pad = 0; + length = ciphertext.size - hash_size - pad - 1; + break; default: gnutls_assert (); @@ -548,17 +576,20 @@ _gnutls_ciphertext2compressed (gnutls_se */ if (session->security_parameters.read_mac_algorithm != GNUTLS_MAC_NULL) { - _gnutls_hmac (&td, - UINT64DATA (session->connection_state. - read_sequence_number), 8); - - _gnutls_hmac (&td, &type, 1); - if (ver >= GNUTLS_TLS1) - { /* TLS 1.x */ - _gnutls_hmac (&td, &major, 1); - _gnutls_hmac (&td, &minor, 1); - } - _gnutls_hmac (&td, &c_length, 2); + digest_hd_st td; + + ret = mac_init (&td, session->security_parameters.read_mac_algorithm, + session->connection_state.read_mac_secret.data, + session->connection_state.read_mac_secret.size, ver); + + if (ret < 0) + { + gnutls_assert (); + return GNUTLS_E_INTERNAL_ERROR; + } + + preamble_size = make_preamble( UINT64DATA (session->connection_state.read_sequence_number), type, c_length, ver, preamble); + _gnutls_hmac (&td, preamble, preamble_size); if (length > 0) _gnutls_hmac (&td, ciphertext.data, length); @@ -566,16 +597,14 @@ _gnutls_ciphertext2compressed (gnutls_se mac_deinit (&td, MAC, ver); } - /* This one was introduced to avoid a timing attack against the TLS - * 1.0 protocol. - */ - if (pad_failed != 0) - return pad_failed; - /* HMAC was not the same. */ - if (memcmp (MAC, &ciphertext.data[length], hash_size) != 0) + if (memcmp (MAC, &ciphertext.data[length], hash_size) != 0 || pad_failed != 0) { + gnutls_datum_t compressed = {compress_data, compress_size}; + /* HMAC was not the same. */ + dummy_wait(session, &compressed, pad_failed, pad, length+preamble_size, ver); + gnutls_assert (); return GNUTLS_E_DECRYPTION_FAILED; } --- a/lib/gnutls_hash_int.h 2013-09-27 +++ b/lib/gnutls_hash_int.h 2013-09-27 @@ -92,4 +92,25 @@ void _gnutls_mac_deinit_ssl3_handshake ( int _gnutls_hash_copy (digest_hd_st* dst_handle, digest_hd_st * src_handle); +/* We shouldn't need to know that, but a work-around in decoding + * TLS record padding requires that. + */ +inline static size_t +_gnutls_get_hash_block_len (gnutls_digest_algorithm_t algo) +{ + switch (algo) + { + case GNUTLS_DIG_MD5: + case GNUTLS_DIG_SHA1: + case GNUTLS_DIG_RMD160: + case GNUTLS_DIG_SHA256: + case GNUTLS_DIG_SHA384: + case GNUTLS_DIG_SHA512: + case GNUTLS_DIG_SHA224: + return 64; + default: + return 0; + } +} + #endif /* GNUTLS_HASH_INT_H */ --- a/lib/gnutls_algorithms.c 2013-09-27 +++ b/lib/gnutls_algorithms.c 2013-09-27 @@ -1185,6 +1185,20 @@ _gnutls_version_is_supported (gnutls_ses return 1; } +/* This function determines if the version specified can have + non-minimal padding. */ +int _gnutls_version_has_variable_padding (gnutls_protocol_t version) +{ + switch(version) { + case GNUTLS_TLS1_0: + case GNUTLS_TLS1_1: + case GNUTLS_TLS1_2: + return 1; + default: + return 0; + } +} + /* Type to KX mappings */ gnutls_kx_algorithm_t _gnutls_map_kx_get_kx (gnutls_credentials_type_t type, int server) --- a/lib/gnutls_algorithms.h 2013-09-27 +++ b/lib/gnutls_algorithms.h 2013-09-27 @@ -41,6 +41,9 @@ int _gnutls_version_get_major (gnutls_pr int _gnutls_version_get_minor (gnutls_protocol_t ver); gnutls_protocol_t _gnutls_version_get (int major, int minor); +/* Functions for feature checks */ +int _gnutls_version_has_variable_padding (gnutls_protocol_t version); + /* Functions for MACs. */ int _gnutls_mac_is_ok (gnutls_mac_algorithm_t algorithm); gnutls_mac_algorithm_t _gnutls_x509_oid2mac_algorithm (const char *oid);