diff options
author | Nikos Mavrogiannopoulos <nmav@redhat.com> | 2014-11-03 14:23:48 +0100 |
---|---|---|
committer | Nikos Mavrogiannopoulos <nmav@redhat.com> | 2014-11-03 17:09:01 +0100 |
commit | e93cef18471962b001dac0f792cb569f1a4cde58 (patch) | |
tree | a8e33fc6c302a7126fc177d1f4a8f0e16fabf52b /lib/gnutls_cipher.c | |
parent | e29d027872fb61a6e7117d3b920626bbc638ac64 (diff) | |
download | gnutls-e93cef18471962b001dac0f792cb569f1a4cde58.tar.gz |
Added support for RFC7366 (encrypt then authenticate)
It implements a revised version of RFC7366, to avoid interoperability
issues: http://www.ietf.org/mail-archive/web/tls/current/msg14349.html
This is currently enabled by default, unless %NO_ETM, or %COMPAT
is specified.
Diffstat (limited to 'lib/gnutls_cipher.c')
-rw-r--r-- | lib/gnutls_cipher.c | 294 |
1 files changed, 189 insertions, 105 deletions
diff --git a/lib/gnutls_cipher.c b/lib/gnutls_cipher.c index e9f76cf39e..82abe270e3 100644 --- a/lib/gnutls_cipher.c +++ b/lib/gnutls_cipher.c @@ -206,14 +206,19 @@ calc_enc_length_block(gnutls_session_t session, const version_entry_st * ver, int data_size, int hash_size, uint8_t * pad, - unsigned auth_cipher, uint16_t blocksize) + unsigned auth_cipher, + uint16_t blocksize, + unsigned etm) { /* pad is the LH pad the user wants us to add. Besides * this LH pad, we only add minimal padding */ - unsigned int pre_length = data_size + hash_size + *pad; + unsigned int pre_length = data_size + *pad; unsigned int length, new_pad; + if (etm == 0) + pre_length += hash_size; + new_pad = (uint8_t) (blocksize - (pre_length % blocksize)) + *pad; if (new_pad > 255) @@ -292,7 +297,7 @@ compressed_to_ciphertext(gnutls_session_t session, _gnutls_auth_cipher_tag_len(¶ms->write.cipher_state); int blocksize = _gnutls_cipher_get_block_size(params->cipher); unsigned algo_type = _gnutls_cipher_type(params->cipher); - uint8_t *data_ptr; + uint8_t *data_ptr, *full_cipher_ptr; const version_entry_st *ver = get_version(session); int explicit_iv = _gnutls_version_has_explicit_iv(ver); int auth_cipher = @@ -303,19 +308,12 @@ compressed_to_ciphertext(gnutls_session_t session, if (unlikely(ver == NULL)) return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); - imp_iv_size = _gnutls_cipher_get_implicit_iv_size(params->cipher); - exp_iv_size = _gnutls_cipher_get_explicit_iv_size(params->cipher); _gnutls_hard_log("ENC[%p]: cipher: %s, MAC: %s, Epoch: %u\n", session, _gnutls_cipher_get_name(params->cipher), _gnutls_mac_get_name(params->mac), (unsigned int) params->epoch); - preamble_size = - make_preamble(UINT64DATA - (params->write.sequence_number), - type, compressed->size, ver, preamble); - /* Calculate the encrypted length (padding etc.) */ if (algo_type == CIPHER_BLOCK) { @@ -330,8 +328,11 @@ compressed_to_ciphertext(gnutls_session_t session, length = calc_enc_length_block(session, ver, compressed->size, tag_size, &pad, auth_cipher, - blocksize); + blocksize, params->etm); } else { /* AEAD + STREAM */ + imp_iv_size = _gnutls_cipher_get_implicit_iv_size(params->cipher); + exp_iv_size = _gnutls_cipher_get_explicit_iv_size(params->cipher); + pad = 0; length = calc_enc_length_stream(session, compressed->size, @@ -347,18 +348,22 @@ compressed_to_ciphertext(gnutls_session_t session, return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); data_ptr = cipher_data; + full_cipher_ptr = data_ptr; - if (algo_type == CIPHER_BLOCK && explicit_iv != 0) { - /* copy the random IV. - */ - memcpy(data_ptr, nonce, blocksize); - _gnutls_auth_cipher_setiv(¶ms->write. - cipher_state, data_ptr, - blocksize); + if (algo_type == CIPHER_BLOCK || algo_type == CIPHER_STREAM) { + if (algo_type == CIPHER_BLOCK && explicit_iv != 0) { + /* copy the random IV. + */ + memcpy(data_ptr, nonce, blocksize); + _gnutls_auth_cipher_setiv(¶ms->write. + cipher_state, data_ptr, + blocksize); + + data_ptr += blocksize; + cipher_data += blocksize; + } - data_ptr += blocksize; - cipher_data += blocksize; - } else if (algo_type == CIPHER_AEAD) { + } else { /* AEAD */ /* Values in AEAD are pretty fixed in TLS 1.2 for 128-bit block */ if (params->write.IV.data == NULL @@ -391,6 +396,15 @@ compressed_to_ciphertext(gnutls_session_t session, cipher_data += exp_iv_size; } + if (params->etm && algo_type != CIPHER_AEAD) + ret = length-tag_size; + else + ret = compressed->size; + + preamble_size = + make_preamble(UINT64DATA(params->write.sequence_number), + type, ret, ver, preamble); + /* add the authenticate data */ ret = _gnutls_auth_cipher_add_auth(¶ms->write.cipher_state, @@ -398,6 +412,15 @@ compressed_to_ciphertext(gnutls_session_t session, if (ret < 0) return gnutls_assert_val(ret); + if (params->etm && explicit_iv && algo_type == CIPHER_BLOCK) { + /* In EtM we need to hash the IV as well */ + ret = + _gnutls_auth_cipher_add_auth(¶ms->write.cipher_state, + full_cipher_ptr, blocksize); + if (ret < 0) + return gnutls_assert_val(ret); + } + /* Actual encryption. */ ret = @@ -459,7 +482,7 @@ ciphertext_to_compressed(gnutls_session_t session, { uint8_t tag[MAX_HASH_SIZE]; uint8_t nonce[MAX_CIPHER_BLOCK_SIZE]; - const uint8_t *tag_ptr; + const uint8_t *tag_ptr = NULL; unsigned int pad = 0, i; int length, length_to_decrypt; uint16_t blocksize; @@ -467,12 +490,13 @@ ciphertext_to_compressed(gnutls_session_t session, unsigned int tmp_pad_failed = 0; unsigned int pad_failed = 0; uint8_t preamble[MAX_PREAMBLE_SIZE]; - unsigned int preamble_size; + unsigned int preamble_size = 0; const version_entry_st *ver = get_version(session); unsigned int tag_size = _gnutls_auth_cipher_tag_len(¶ms->read.cipher_state); unsigned int explicit_iv = _gnutls_version_has_explicit_iv(ver); unsigned imp_iv_size, exp_iv_size; + unsigned cipher_type = _gnutls_cipher_type(params->cipher); if (unlikely(ver == NULL)) return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); @@ -481,9 +505,42 @@ ciphertext_to_compressed(gnutls_session_t session, exp_iv_size = _gnutls_cipher_get_explicit_iv_size(params->cipher); blocksize = _gnutls_cipher_get_block_size(params->cipher); + /* if EtM mode and not AEAD */ + if (params->etm !=0 && cipher_type != CIPHER_AEAD) { + if (unlikely(ciphertext->size < tag_size)) + return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH); + + preamble_size = make_preamble(UINT64DATA(*sequence), + type, ciphertext->size-tag_size, + ver, preamble); + + ret = _gnutls_auth_cipher_add_auth(¶ms->read. + cipher_state, preamble, + preamble_size); + if (unlikely(ret < 0)) + return gnutls_assert_val(ret); + + ret = _gnutls_auth_cipher_add_auth(¶ms->read. + cipher_state, + ciphertext->data, + ciphertext->size-tag_size); + if (unlikely(ret < 0)) + return gnutls_assert_val(ret); + + ret = _gnutls_auth_cipher_tag(¶ms->read.cipher_state, tag, tag_size); + if (unlikely(ret < 0)) + return gnutls_assert_val(ret); + + if (unlikely(memcmp(tag, &ciphertext->data[ciphertext->size-tag_size], tag_size) != 0)) { + /* HMAC was not the same. */ + return gnutls_assert_val(GNUTLS_E_DECRYPTION_FAILED); + } + + } + /* actual decryption (inplace) */ - switch (_gnutls_cipher_type(params->cipher)) { + switch (cipher_type) { case CIPHER_AEAD: /* The way AEAD ciphers are defined in RFC5246, it allows * only stream ciphers. @@ -569,16 +626,18 @@ ciphertext_to_compressed(gnutls_session_t session, /* Pass the type, version, length and compressed through * MAC. */ - preamble_size = - make_preamble(UINT64DATA(*sequence), type, - length, ver, preamble); + if (params->etm == 0) { + preamble_size = + make_preamble(UINT64DATA(*sequence), type, + length, ver, preamble); - ret = - _gnutls_auth_cipher_add_auth(¶ms->read. - cipher_state, preamble, - preamble_size); - if (unlikely(ret < 0)) - return gnutls_assert_val(ret); + ret = + _gnutls_auth_cipher_add_auth(¶ms->read. + cipher_state, preamble, + preamble_size); + if (unlikely(ret < 0)) + return gnutls_assert_val(ret); + } if (unlikely ((unsigned) length_to_decrypt > compressed->size)) { @@ -603,13 +662,19 @@ ciphertext_to_compressed(gnutls_session_t session, break; case CIPHER_BLOCK: - if (unlikely - (ciphertext->size < blocksize - || (ciphertext->size % blocksize != 0))) + if (unlikely(ciphertext->size < blocksize)) return gnutls_assert_val (GNUTLS_E_UNEXPECTED_PACKET_LENGTH); + if (params->etm == 0) { + if (unlikely(ciphertext->size % blocksize != 0)) + return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH); + } else { + if (unlikely((ciphertext->size - tag_size) % blocksize != 0)) + return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH); + } + /* ignore the IV in TLS 1.1+ */ if (explicit_iv) { @@ -618,6 +683,7 @@ ciphertext_to_compressed(gnutls_session_t session, ciphertext->data, blocksize); + memcpy(nonce, ciphertext->data, blocksize); ciphertext->size -= blocksize; ciphertext->data += blocksize; } @@ -635,88 +701,106 @@ ciphertext_to_compressed(gnutls_session_t session, return gnutls_assert_val(GNUTLS_E_DECRYPTION_FAILED); - ret = - _gnutls_cipher_decrypt2(¶ms->read.cipher_state. - cipher, ciphertext->data, - ciphertext->size, - compressed->data, - compressed->size); - if (unlikely(ret < 0)) - return gnutls_assert_val(ret); - - pad = compressed->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->id != GNUTLS_SSL3) - for (i = 2; i <= MIN(256, ciphertext->size); i++) { - tmp_pad_failed |= - (compressed-> - data[ciphertext->size - i] != pad); - pad_failed |= - ((i <= (1 + pad)) & (tmp_pad_failed)); - } - - if (unlikely - (pad_failed != 0 - || (1 + pad > ((int) ciphertext->size - tag_size)))) { - /* We do not fail here. We check below for the - * the pad_failed. If zero means success. + if (params->etm == 0) { + ret = + _gnutls_cipher_decrypt2(¶ms->read.cipher_state. + cipher, ciphertext->data, + ciphertext->size, + compressed->data, + compressed->size); + if (unlikely(ret < 0)) + return gnutls_assert_val(ret); + + pad = compressed->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). */ - pad_failed = 1; - pad = 0; - } + if (ver->id != GNUTLS_SSL3) + for (i = 2; i <= MIN(256, ciphertext->size); i++) { + tmp_pad_failed |= + (compressed-> + data[ciphertext->size - i] != pad); + pad_failed |= + ((i <= (1 + pad)) & (tmp_pad_failed)); + } - length = ciphertext->size - tag_size - pad - 1; - tag_ptr = &compressed->data[length]; + if (unlikely + (pad_failed != 0 + || (1 + pad > ((int) ciphertext->size - tag_size)))) { + /* We do not fail here. We check below for the + * the pad_failed. If zero means success. + */ + pad_failed = 1; + pad = 0; + } - /* Pass the type, version, length and compressed through - * MAC. - */ - preamble_size = - make_preamble(UINT64DATA(*sequence), type, - length, ver, preamble); + length = ciphertext->size - tag_size - pad - 1; + tag_ptr = &compressed->data[length]; - ret = - _gnutls_auth_cipher_add_auth(¶ms->read. - cipher_state, preamble, - preamble_size); - if (unlikely(ret < 0)) - return gnutls_assert_val(ret); + /* Pass the type, version, length and compressed through + * MAC. + */ + preamble_size = + make_preamble(UINT64DATA(*sequence), type, + length, ver, preamble); - ret = - _gnutls_auth_cipher_add_auth(¶ms->read. - cipher_state, - compressed->data, length); - if (unlikely(ret < 0)) - return gnutls_assert_val(ret); + ret = + _gnutls_auth_cipher_add_auth(¶ms->read. + cipher_state, preamble, + preamble_size); + if (unlikely(ret < 0)) + return gnutls_assert_val(ret); + ret = + _gnutls_auth_cipher_add_auth(¶ms->read. + cipher_state, + compressed->data, length); + if (unlikely(ret < 0)) + return gnutls_assert_val(ret); + } else { /* EtM */ + ret = + _gnutls_cipher_decrypt2(¶ms->read.cipher_state. + cipher, ciphertext->data, + ciphertext->size - tag_size, + compressed->data, + compressed->size); + if (unlikely(ret < 0)) + return gnutls_assert_val(ret); + + pad = compressed->data[ciphertext->size - tag_size - 1]; /* pad */ + length = ciphertext->size - tag_size - pad - 1; + + if (unlikely(length < 0)) + return gnutls_assert_val(GNUTLS_E_DECRYPTION_FAILED); + } break; default: return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); } - ret = - _gnutls_auth_cipher_tag(¶ms->read.cipher_state, tag, - tag_size); - if (unlikely(ret < 0)) - return gnutls_assert_val(ret); + if (params->etm ==0 && cipher_type != CIPHER_AEAD) { + ret = + _gnutls_auth_cipher_tag(¶ms->read.cipher_state, tag, + tag_size); + if (unlikely(ret < 0)) + return gnutls_assert_val(ret); - /* Here there could be a timing leakage in CBC ciphersuites that - * could be exploited if the cost of a successful memcmp is high. - * A constant time memcmp would help there, but it is not easy to maintain - * against compiler optimizations. Currently we rely on the fact that - * a memcmp comparison is negligible over the crypto operations. - */ - if (unlikely - (memcmp(tag, tag_ptr, tag_size) != 0 || pad_failed != 0)) { - /* HMAC was not the same. */ - dummy_wait(params, compressed, pad_failed, pad, - length + preamble_size); + /* Here there could be a timing leakage in CBC ciphersuites that + * could be exploited if the cost of a successful memcmp is high. + * A constant time memcmp would help there, but it is not easy to maintain + * against compiler optimizations. Currently we rely on the fact that + * a memcmp comparison is negligible over the crypto operations. + */ + if (unlikely + (memcmp(tag, tag_ptr, tag_size) != 0 || pad_failed != 0)) { + /* HMAC was not the same. */ + dummy_wait(params, compressed, pad_failed, pad, + length + preamble_size); - return gnutls_assert_val(GNUTLS_E_DECRYPTION_FAILED); + return gnutls_assert_val(GNUTLS_E_DECRYPTION_FAILED); + } } return length; |