diff -up gnutls-1.4.1/lib/gnutls_cipher.c.lucky13 gnutls-1.4.1/lib/gnutls_cipher.c --- gnutls-1.4.1/lib/gnutls_cipher.c.lucky13 2012-06-06 14:25:44.000000000 +0200 +++ gnutls-1.4.1/lib/gnutls_cipher.c 2013-02-21 19:39:52.828556530 +0100 @@ -403,6 +403,48 @@ _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; + mac_hd_t td; + + td = mac_init (session->security_parameters.read_mac_algorithm, + session->connection_state.read_mac_secret.data, + session->connection_state.read_mac_secret.size, ver); + + if (td == GNUTLS_MAC_FAILED) + 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. */ @@ -414,12 +456,12 @@ _gnutls_ciphertext2compressed (gnutls_se { uint8_t MAC[MAX_HASH_SIZE]; uint16_t c_length; - uint8_t pad; + unsigned int pad = 0; int length; - mac_hd_t td; uint16_t blocksize; int ret, i, pad_failed = 0; uint8_t major, minor; + int preamble_size = 11; gnutls_protocol_t ver; int hash_size = _gnutls_hash_get_algo_len (session->security_parameters. @@ -429,21 +471,12 @@ _gnutls_ciphertext2compressed (gnutls_se minor = _gnutls_version_get_minor (ver); major = _gnutls_version_get_major (ver); + if (ver >= GNUTLS_TLS1) + preamble_size +=2; + blocksize = _gnutls_cipher_get_block_size (session->security_parameters. read_bulk_cipher_algorithm); - /* initialize MAC - */ - td = mac_init (session->security_parameters.read_mac_algorithm, - session->connection_state.read_mac_secret.data, - session->connection_state.read_mac_secret.size, ver); - - if (td == GNUTLS_MAC_FAILED - && session->security_parameters.read_mac_algorithm != GNUTLS_MAC_NULL) - { - gnutls_assert (); - return GNUTLS_E_INTERNAL_ERROR; - } /* actual decryption (inplace) */ @@ -488,35 +521,27 @@ _gnutls_ciphertext2compressed (gnutls_se } if (ciphertext.size < hash_size) - { - 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[%x]: 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) + { + gnutls_assert (); + return GNUTLS_E_DECRYPTION_FAILED; + } + pad = ciphertext.data[ciphertext.size - 1]; /* pad */ + + /* 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++) + 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 (); @@ -530,8 +555,20 @@ _gnutls_ciphertext2compressed (gnutls_se /* Pass the type, version, length and compressed through * MAC. */ - if (td != GNUTLS_MAC_FAILED) + if (session->security_parameters.read_mac_algorithm != GNUTLS_MAC_NULL) { + mac_hd_t td; + + td = mac_init (session->security_parameters.read_mac_algorithm, + session->connection_state.read_mac_secret.data, + session->connection_state.read_mac_secret.size, ver); + + if (td == GNUTLS_MAC_FAILED) + { + gnutls_assert (); + return GNUTLS_E_INTERNAL_ERROR; + } + _gnutls_hmac (td, UINT64DATA (session->connection_state. read_sequence_number), 8); @@ -550,16 +587,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; } diff -up gnutls-1.4.1/lib/gnutls_hash_int.h.lucky13 gnutls-1.4.1/lib/gnutls_hash_int.h --- gnutls-1.4.1/lib/gnutls_hash_int.h.lucky13 2006-03-08 11:44:59.000000000 +0100 +++ gnutls-1.4.1/lib/gnutls_hash_int.h 2013-02-21 19:43:13.578986259 +0100 @@ -69,4 +69,21 @@ void _gnutls_mac_deinit_ssl3_handshake ( GNUTLS_HASH_HANDLE _gnutls_hash_copy (GNUTLS_HASH_HANDLE 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: + return 64; + default: + return 0; + } +} + #endif /* GNUTLS_HASH_INT_H */