summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNikos Mavrogiannopoulos <nmav@gnutls.org>2013-02-04 03:08:04 +0100
committerNikos Mavrogiannopoulos <nmav@gnutls.org>2013-02-04 09:07:34 +0100
commit8dc2822966f64dd9cf7dde9c7aacd80d49d3ffe5 (patch)
tree585f92e2e3882482af01bf56879c921d6ce64812
parentee6070a4d8b32a63e0d868f5aac20c12144ce468 (diff)
downloadgnutls-8dc2822966f64dd9cf7dde9c7aacd80d49d3ffe5.tar.gz
Fixes to avoid a timing attack in TLS CBC record parsing.
-rw-r--r--lib/gnutls_cipher.c97
-rw-r--r--lib/gnutls_hash_int.h21
2 files changed, 81 insertions, 37 deletions
diff --git a/lib/gnutls_cipher.c b/lib/gnutls_cipher.c
index d25219006b..f5152570b8 100644
--- a/lib/gnutls_cipher.c
+++ b/lib/gnutls_cipher.c
@@ -1,5 +1,6 @@
/*
- * Copyright (C) 2000-2012 Free Software Foundation, Inc.
+ * Copyright (C) 2000-2013 Free Software Foundation, Inc.
+ * Copyright (C) 2013 Nikos Mavrogiannopoulos
*
* Author: Nikos Mavrogiannopoulos
*
@@ -427,6 +428,36 @@ compressed_to_ciphertext (gnutls_session_t session,
return length;
}
+static void dummy_wait(record_parameters_st * params, gnutls_datum_t* plaintext,
+ unsigned pad_failed, unsigned int pad, unsigned total)
+{
+ /* this hack is only needed on CBC ciphers */
+ if (_gnutls_cipher_is_block (params->cipher_algorithm) == CIPHER_BLOCK)
+ {
+ unsigned len;
+
+ /* 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(params->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_auth_cipher_add_auth (&params->read.cipher_state, plaintext->data, len);
+ else
+ _gnutls_auth_cipher_add_auth (&params->read.cipher_state, plaintext->data, plaintext->size);
+ }
+ }
+ }
+ }
+}
/* Deciphers the ciphertext packet, and puts the result to compress_data, of compress_size.
* Returns the actual compressed packet size.
@@ -440,10 +471,12 @@ ciphertext_to_compressed (gnutls_session_t session,
uint64* sequence)
{
uint8_t tag[MAX_HASH_SIZE];
- unsigned int pad, i;
+ unsigned int pad = 0, i;
int length, length_to_decrypt;
uint16_t blocksize;
- int ret, pad_failed = 0;
+ int ret;
+ unsigned int tmp_pad_failed = 0;
+ unsigned int pad_failed = 0;
uint8_t preamble[MAX_PREAMBLE_SIZE];
unsigned int preamble_size;
unsigned int ver = gnutls_protocol_get_version (session);
@@ -452,6 +485,7 @@ ciphertext_to_compressed (gnutls_session_t session,
blocksize = gnutls_cipher_get_block_size (params->cipher_algorithm);
+
/* actual decryption (inplace)
*/
switch (_gnutls_cipher_is_block (params->cipher_algorithm))
@@ -524,7 +558,7 @@ ciphertext_to_compressed (gnutls_session_t session,
ciphertext->data += blocksize;
}
- if (ciphertext->size < tag_size)
+ if (ciphertext->size < tag_size+1)
return gnutls_assert_val(GNUTLS_E_DECRYPTION_FAILED);
/* we don't use the auth_cipher interface here, since
@@ -537,42 +571,30 @@ ciphertext_to_compressed (gnutls_session_t session,
ciphertext->data, ciphertext->size)) < 0)
return gnutls_assert_val(ret);
- pad = ciphertext->data[ciphertext->size - 1] + 1; /* pad */
-
-
- if (pad > (int) ciphertext->size - tag_size)
- {
- gnutls_assert ();
- _gnutls_record_log
- ("REC[%p]: Short record length %d > %d - %d (under attack?)\n",
- session, pad, ciphertext->size, tag_size);
- /* We do not fail here. We check below for the
- * the pad_failed. If zero means success.
- */
- pad_failed = GNUTLS_E_DECRYPTION_FAILED;
- pad %= blocksize;
- }
-
- length = ciphertext->size - tag_size - pad;
+ pad = ciphertext->data[ciphertext->size - 1]; /* pad */
- /* Check the pading bytes (TLS 1.x)
+ /* 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_SSL3)
- for (i = 2; i <= pad; i++)
+ for (i = 2; i <= MIN(256, ciphertext->size); i++)
{
- if (ciphertext->data[ciphertext->size - i] !=
- ciphertext->data[ciphertext->size - 1])
- pad_failed = GNUTLS_E_DECRYPTION_FAILED;
+ tmp_pad_failed |= (ciphertext->data[ciphertext->size - i] != pad);
+ pad_failed |= ((i<= (1+pad)) & (tmp_pad_failed));
}
- if (length < 0)
+ if (pad_failed != 0 || (1+pad > ((int) ciphertext->size - tag_size)))
{
- /* Setting a proper length to prevent timing differences in
- * processing of records with invalid encryption.
+ /* We do not fail here. We check below for the
+ * the pad_failed. If zero means success.
*/
- length = ciphertext->size - tag_size;
+ pad_failed = 1;
+ pad = 0;
}
+ length = ciphertext->size - tag_size - pad - 1;
+
/* Pass the type, version, length and compressed through
* MAC.
*/
@@ -596,15 +618,16 @@ ciphertext_to_compressed (gnutls_session_t session,
if (ret < 0)
return gnutls_assert_val(ret);
- /* This one was introduced to avoid a timing attack against the TLS
- * 1.0 protocol.
- */
- /* HMAC was not the same.
- */
if (memcmp (tag, &ciphertext->data[length], tag_size) != 0 || pad_failed != 0)
- return gnutls_assert_val(GNUTLS_E_DECRYPTION_FAILED);
+ {
+ gnutls_datum compressed = {compress_data, compress_size};
+ /* HMAC was not the same. */
+ dummy_wait(params, &compressed, pad_failed, pad, length+preamble_size);
+
+ return gnutls_assert_val(GNUTLS_E_DECRYPTION_FAILED);
+ }
- /* copy the decrypted stuff to compress_data.
+ /* copy the decrypted stuff to compressed_data.
*/
if (compress_size < length)
return gnutls_assert_val(GNUTLS_E_DECOMPRESSION_FAILED);
diff --git a/lib/gnutls_hash_int.h b/lib/gnutls_hash_int.h
index 5c55490e01..b91671c9a3 100644
--- a/lib/gnutls_hash_int.h
+++ b/lib/gnutls_hash_int.h
@@ -176,4 +176,25 @@ inline static int IS_SHA(gnutls_digest_algorithm_t algo)
return 0;
}
+/* 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 */