From e93cef18471962b001dac0f792cb569f1a4cde58 Mon Sep 17 00:00:00 2001 From: Nikos Mavrogiannopoulos Date: Mon, 3 Nov 2014 14:23:48 +0100 Subject: 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. --- doc/cha-gtls-app.texi | 6 +- lib/ext/Makefile.am | 2 +- lib/ext/etm.c | 137 +++++++++++++++++++ lib/ext/etm.h | 30 ++++ lib/gnutls_buffers.c | 4 + lib/gnutls_cipher.c | 294 ++++++++++++++++++++++++++-------------- lib/gnutls_cipher_int.c | 116 ++++++++++------ lib/gnutls_cipher_int.h | 5 +- lib/gnutls_constate.c | 3 + lib/gnutls_extensions.c | 5 + lib/gnutls_int.h | 6 + lib/gnutls_priority.c | 4 + lib/gnutls_session_pack.c | 5 + lib/includes/gnutls/gnutls.h.in | 1 + lib/libgnutls.map | 1 + lib/priority_options.gperf | 1 + src/common.c | 4 +- 17 files changed, 471 insertions(+), 153 deletions(-) create mode 100644 lib/ext/etm.c create mode 100644 lib/ext/etm.h diff --git a/doc/cha-gtls-app.texi b/doc/cha-gtls-app.texi index f78759ccfe..b5c256b748 100644 --- a/doc/cha-gtls-app.texi +++ b/doc/cha-gtls-app.texi @@ -1106,7 +1106,7 @@ that TLS 1.2 requires extensions to be used, as well as safe renegotiation thus this option must be used with care. @item %NO_TICKETS @tab -will prevent the sending of the TLS session ticket extension. +will prevent the advertizing of the TLS session ticket extension. This is implied by the PFS keyword. @item %SERVER_PRECEDENCE @tab @@ -1139,6 +1139,10 @@ separate records. will disable matching wildcards when comparing hostnames in certificates. +@item %NO_ETM @tab +will disable the encrypt-then-mac TLS extension (RFC7366). This is +implied by the %COMPAT keyword. + @item %DISABLE_SAFE_RENEGOTIATION @tab will completely disable safe renegotiation completely. Do not use unless you know what you are doing. diff --git a/lib/ext/Makefile.am b/lib/ext/Makefile.am index 9b7012bed2..04edf81b91 100644 --- a/lib/ext/Makefile.am +++ b/lib/ext/Makefile.am @@ -40,7 +40,7 @@ libgnutls_ext_la_SOURCES = max_record.c cert_type.c \ session_ticket.h signature.h safe_renegotiation.h \ session_ticket.c srp.c ecc.c ecc.h heartbeat.c heartbeat.h \ status_request.h status_request.c dumbfw.c dumbfw.h \ - ext_master_secret.c ext_master_secret.h + ext_master_secret.c ext_master_secret.h etm.h etm.c if ENABLE_ALPN libgnutls_ext_la_SOURCES += alpn.c alpn.h diff --git a/lib/ext/etm.c b/lib/ext/etm.c new file mode 100644 index 0000000000..2ae9eba7fd --- /dev/null +++ b/lib/ext/etm.c @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2014 Red Hat, Inc. + * + * Author: Nikos Mavrogiannopoulos + * + * This file is part of GnuTLS. + * + * The GnuTLS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see + * + */ + +/* This file contains the code for the Max Record Size TLS extension. + */ + +#include "gnutls_int.h" +#include "gnutls_errors.h" +#include "gnutls_num.h" +#include +#include + +static int _gnutls_ext_etm_recv_params(gnutls_session_t session, + const uint8_t * data, + size_t data_size); +static int _gnutls_ext_etm_send_params(gnutls_session_t session, + gnutls_buffer_st * extdata); + +extension_entry_st ext_mod_etm = { + .name = "ENCRYPT THEN MAC", + .type = GNUTLS_EXTENSION_ETM, + .parse_type = GNUTLS_EXT_MANDATORY, + + .recv_func = _gnutls_ext_etm_recv_params, + .send_func = _gnutls_ext_etm_send_params, + .pack_func = NULL, + .unpack_func = NULL, + .deinit_func = NULL +}; + +/* + * In case of a server: if an EXT_MASTER_SECRET extension type is received then it + * sets a flag into the session security parameters. + * + */ +static int +_gnutls_ext_etm_recv_params(gnutls_session_t session, + const uint8_t * data, size_t _data_size) +{ + ssize_t data_size = _data_size; + + if (data_size != 0) { + return gnutls_assert_val(GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER); + } + + if (session->security_parameters.entity == GNUTLS_SERVER) { + extension_priv_data_t epriv; + + if (session->internals.priorities.no_etm != 0) + return 0; + + epriv.num = 1; + _gnutls_ext_set_session_data(session, + GNUTLS_EXTENSION_ETM, + epriv); + + /* don't decide now, decide on send */ + return 0; + } else { /* client */ + const cipher_entry_st *c; + + c = _gnutls_cipher_suite_get_cipher_algo(session->security_parameters.cipher_suite); + if (c == NULL || c->type == CIPHER_AEAD) + return 0; + + session->security_parameters.etm = 1; + } + + return 0; +} + +/* returns data_size or a negative number on failure + */ +static int +_gnutls_ext_etm_send_params(gnutls_session_t session, + gnutls_buffer_st * extdata) +{ + if (session->internals.priorities.no_etm != 0) + return 0; + + /* this function sends the client extension data */ + if (session->security_parameters.entity == GNUTLS_CLIENT) { + return GNUTLS_E_INT_RET_0; + } else { /* server side */ + const cipher_entry_st *c; + int ret; + extension_priv_data_t epriv; + + c = _gnutls_cipher_suite_get_cipher_algo(session->security_parameters.cipher_suite); + if (c == NULL || c->type == CIPHER_AEAD) + return 0; + + ret = _gnutls_ext_get_session_data(session, + GNUTLS_EXTENSION_ETM, + &epriv); + if (ret < 0 || epriv.num == 0) + return 0; + + session->security_parameters.etm = 1; + return GNUTLS_E_INT_RET_0; + } + + return 0; +} + +/** + * gnutls_session_etm_status: + * @session: is a #gnutls_session_t structure. + * + * Get the status of the encrypt-then-mac extension negotiation. + * This is in accordance to rfc7366 + * + * Returns: Non-zero if the negotiation was successful or zero otherwise. + **/ +unsigned gnutls_session_etm_status(gnutls_session_t session) +{ + return session->security_parameters.etm; +} diff --git a/lib/ext/etm.h b/lib/ext/etm.h new file mode 100644 index 0000000000..23a35a6d9b --- /dev/null +++ b/lib/ext/etm.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2014 Red Hat, Inc. + * + * Author: Nikos Mavrogiannopoulos + * + * This file is part of GnuTLS. + * + * The GnuTLS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see + * + */ + +#ifndef EXT_ETM_H +#define EXT_ETM_H + +#include + +extern extension_entry_st ext_mod_etm; + +#endif diff --git a/lib/gnutls_buffers.c b/lib/gnutls_buffers.c index f99bce325c..569cea778a 100644 --- a/lib/gnutls_buffers.c +++ b/lib/gnutls_buffers.c @@ -142,6 +142,10 @@ _gnutls_record_buffer_get(content_type_t type, (int) bufel->type, _gnutls_packet2str(type), (int) type); + else + _gnutls_debug_log("received unexpected packet: %s(%d)\n", + _gnutls_packet2str(bufel->type), (int)bufel->type); + _mbuffer_head_remove_bytes(&session->internals. record_buffer, msg.size); return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET); 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; diff --git a/lib/gnutls_cipher_int.c b/lib/gnutls_cipher_int.c index 6b7631b807..8a7c6ff552 100644 --- a/lib/gnutls_cipher_int.c +++ b/lib/gnutls_cipher_int.c @@ -140,7 +140,8 @@ int _gnutls_auth_cipher_init(auth_cipher_hd_st * handle, const gnutls_datum_t * iv, const mac_entry_st * me, const gnutls_datum_t * mac_key, - int ssl_hmac, int enc) + unsigned etm, + unsigned ssl_hmac, int enc) { int ret; @@ -150,6 +151,7 @@ int _gnutls_auth_cipher_init(auth_cipher_hd_st * handle, FAIL_IF_LIB_ERROR; memset(handle, 0, sizeof(*handle)); + handle->etm = etm; if (e->id != GNUTLS_CIPHER_NULL) { handle->non_null = 1; @@ -196,22 +198,29 @@ int _gnutls_auth_cipher_init(auth_cipher_hd_st * handle, } +#define MAC(handle, text, textlen) \ + if (handle->ssl_hmac) { \ + ret = \ + _gnutls_hash(&handle->mac.dig, text, textlen); \ + } else { \ + ret = _gnutls_mac(&handle->mac.mac, text, textlen); \ + } \ + if (unlikely(ret < 0)) \ + return gnutls_assert_val(ret) + int _gnutls_auth_cipher_add_auth(auth_cipher_hd_st * handle, const void *text, int textlen) { + int ret; + if (handle->is_mac) { - if (handle->ssl_hmac) - return _gnutls_hash(&handle->mac.dig, text, - textlen); - else - return _gnutls_mac(&handle->mac.mac, text, - textlen); + MAC(handle, text, textlen); } else if (_gnutls_cipher_is_aead(&handle->cipher)) return _gnutls_cipher_auth(&handle->cipher, text, textlen); - else - return 0; + return 0; } + /* The caller must make sure that textlen+pad_size+tag_size is divided by the block size of the cipher */ int _gnutls_auth_cipher_encrypt2_tag(auth_cipher_hd_st * handle, const uint8_t * text, int textlen, @@ -224,20 +233,34 @@ int _gnutls_auth_cipher_encrypt2_tag(auth_cipher_hd_st * handle, _gnutls_cipher_get_block_size(handle->cipher.e); unsigned l; - if (handle->is_mac) { - if (handle->ssl_hmac) + if (handle->is_mac) { /* cipher + mac */ + if (handle->non_null == 0) { /* NULL cipher + MAC */ + MAC(handle, text, textlen); + + if (unlikely(textlen + pad_size + handle->tag_size) > + ciphertextlen) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + if (text != ciphertext) + memcpy(ciphertext, text, textlen); ret = - _gnutls_hash(&handle->mac.dig, text, textlen); - else - ret = _gnutls_mac(&handle->mac.mac, text, textlen); - if (unlikely(ret < 0)) - return gnutls_assert_val(ret); + _gnutls_auth_cipher_tag(handle, + ciphertext + textlen, + handle->tag_size); + if (ret < 0) + return gnutls_assert_val(ret); + + } else { + uint8_t *orig_ciphertext = ciphertext; + + if (handle->etm == 0) { + MAC(handle, text, textlen); + } - if (unlikely(textlen + pad_size + handle->tag_size) > - ciphertextlen) - return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + if (unlikely(textlen + pad_size + handle->tag_size) > + ciphertextlen) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); - if (handle->non_null != 0) { l = (textlen / blocksize) * blocksize; ret = _gnutls_cipher_encrypt2(&handle->cipher, text, @@ -254,13 +277,15 @@ int _gnutls_auth_cipher_encrypt2_tag(auth_cipher_hd_st * handle, if (ciphertext != text && textlen > 0) memcpy(ciphertext, text, textlen); - ret = - _gnutls_auth_cipher_tag(handle, - ciphertext + textlen, - handle->tag_size); - if (ret < 0) - return gnutls_assert_val(ret); - textlen += handle->tag_size; + if (handle->etm == 0) { + ret = + _gnutls_auth_cipher_tag(handle, + ciphertext + textlen, + handle->tag_size); + if (ret < 0) + return gnutls_assert_val(ret); + textlen += handle->tag_size; + } /* TLS 1.0 style padding */ if (pad_size > 0) { @@ -276,17 +301,18 @@ int _gnutls_auth_cipher_encrypt2_tag(auth_cipher_hd_st * handle, ciphertextlen); if (ret < 0) return gnutls_assert_val(ret); - } else { /* null cipher */ - if (text != ciphertext) - memcpy(ciphertext, text, textlen); + if (handle->etm != 0) { + MAC(handle, orig_ciphertext, l); + MAC(handle, ciphertext, textlen); - ret = - _gnutls_auth_cipher_tag(handle, - ciphertext + textlen, - handle->tag_size); - if (ret < 0) - return gnutls_assert_val(ret); + ret = + _gnutls_auth_cipher_tag(handle, + ciphertext + textlen, + handle->tag_size); + if (ret < 0) + return gnutls_assert_val(ret); + } } } else if (_gnutls_cipher_is_aead(&handle->cipher)) { ret = @@ -300,7 +326,7 @@ int _gnutls_auth_cipher_encrypt2_tag(auth_cipher_hd_st * handle, handle->tag_size); if (unlikely(ret < 0)) return gnutls_assert_val(ret); - } else if (handle->non_null == 0 && text != ciphertext) + } else if (handle->non_null == 0 && text != ciphertext) /* NULL cipher - no MAC */ memcpy(ciphertext, text, textlen); return 0; @@ -315,6 +341,13 @@ int _gnutls_auth_cipher_decrypt2(auth_cipher_hd_st * handle, if (unlikely(ciphertextlen > textlen)) return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + if (handle->is_mac && handle->etm != 0) { + /* The MAC is not to be hashed */ + ciphertextlen -= handle->tag_size; + + MAC(handle, ciphertext, ciphertextlen); + } + if (handle->non_null != 0) { ret = _gnutls_cipher_decrypt2(&handle->cipher, ciphertext, @@ -324,16 +357,11 @@ int _gnutls_auth_cipher_decrypt2(auth_cipher_hd_st * handle, } else if (handle->non_null == 0 && text != ciphertext) memcpy(text, ciphertext, ciphertextlen); - if (handle->is_mac) { + if (handle->is_mac && handle->etm == 0) { /* The MAC is not to be hashed */ ciphertextlen -= handle->tag_size; - if (handle->ssl_hmac) - return _gnutls_hash(&handle->mac.dig, text, - ciphertextlen); - else - return _gnutls_mac(&handle->mac.mac, text, - ciphertextlen); + MAC(handle, text, ciphertextlen); } return 0; diff --git a/lib/gnutls_cipher_int.h b/lib/gnutls_cipher_int.h index 9dae9d02a8..89e6f05f51 100644 --- a/lib/gnutls_cipher_int.h +++ b/lib/gnutls_cipher_int.h @@ -134,6 +134,7 @@ typedef struct { unsigned int is_mac:1; unsigned int ssl_hmac:1; unsigned int non_null:1; + unsigned int etm:1; size_t tag_size; } auth_cipher_hd_st; @@ -142,7 +143,9 @@ int _gnutls_auth_cipher_init(auth_cipher_hd_st * handle, const gnutls_datum_t * cipher_key, const gnutls_datum_t * iv, const mac_entry_st * me, - const gnutls_datum_t * mac_key, int ssl_hmac, + const gnutls_datum_t * mac_key, + unsigned etm, + unsigned ssl_hmac, int enc); int _gnutls_auth_cipher_add_auth(auth_cipher_hd_st * handle, diff --git a/lib/gnutls_constate.c b/lib/gnutls_constate.c index 25299982cb..75b71ac538 100644 --- a/lib/gnutls_constate.c +++ b/lib/gnutls_constate.c @@ -207,6 +207,7 @@ _gnutls_init_record_state(record_parameters_st * params, ret = _gnutls_auth_cipher_init(&state->cipher_state, params->cipher, &state->key, iv, params->mac, &state->mac_secret, + params->etm, (ver->id == GNUTLS_SSL3) ? 1 : 0, 1 - read /*1==encrypt */ ); if (ret < 0 && params->cipher->id != GNUTLS_CIPHER_NULL) @@ -353,6 +354,7 @@ int _gnutls_epoch_set_keys(gnutls_session_t session, uint16_t epoch) key_size = _gnutls_cipher_get_key_size(params->cipher); hash_size = _gnutls_mac_get_key_size(params->mac); + params->etm = session->security_parameters.etm; ret = _gnutls_set_keys (session, params, hash_size, IV_size, key_size); @@ -389,6 +391,7 @@ int _gnutls_epoch_set_keys(gnutls_session_t session, uint16_t epoch) dst->compression_method = src->compression_method; \ dst->timestamp = src->timestamp; \ dst->ext_master_secret = src->ext_master_secret; \ + dst->etm = src->etm; \ dst->max_record_recv_size = src->max_record_recv_size; \ dst->max_record_send_size = src->max_record_send_size diff --git a/lib/gnutls_extensions.c b/lib/gnutls_extensions.c index e8cf539f21..3c5d45289a 100644 --- a/lib/gnutls_extensions.c +++ b/lib/gnutls_extensions.c @@ -43,6 +43,7 @@ #include #include #include +#include #include @@ -316,6 +317,10 @@ int _gnutls_ext_init(void) if (ret != GNUTLS_E_SUCCESS) return ret; + ret = _gnutls_ext_register(&ext_mod_etm); + if (ret != GNUTLS_E_SUCCESS) + return ret; + #ifdef ENABLE_OCSP ret = _gnutls_ext_register(&ext_mod_status_request); if (ret != GNUTLS_E_SUCCESS) diff --git a/lib/gnutls_int.h b/lib/gnutls_int.h index 3b9259cf4b..2c2172a2fa 100644 --- a/lib/gnutls_int.h +++ b/lib/gnutls_int.h @@ -285,6 +285,7 @@ typedef enum extensions_t { GNUTLS_EXTENSION_HEARTBEAT = 15, GNUTLS_EXTENSION_ALPN = 16, GNUTLS_EXTENSION_DUMBFW = 21, + GNUTLS_EXTENSION_ETM = 22, GNUTLS_EXTENSION_EXT_MASTER_SECRET = 23, GNUTLS_EXTENSION_SESSION_TICKET = 35, GNUTLS_EXTENSION_SAFE_RENEGOTIATION = 65281 /* aka: 0xff01 */ @@ -577,6 +578,8 @@ typedef struct { * draft-ietf-tls-session-hash-01 */ uint8_t ext_master_secret; + /* encrypt-then-mac -> rfc7366 */ + uint8_t etm; /* Note: if you add anything in Security_Parameters struct, then * also modify CPY_COMMON in gnutls_constate.c, and gnutls_session_pack.c, @@ -615,6 +618,7 @@ struct record_parameters_st { gnutls_compression_method_t compression_algorithm; const cipher_entry_st *cipher; + bool etm; const mac_entry_st *mac; /* for DTLS */ @@ -665,6 +669,7 @@ struct gnutls_priority_st { bool server_precedence; bool allow_wrong_pms; bool no_tickets; + bool no_etm; /* Whether stateless compression will be used */ bool stateless_compression; unsigned int additional_verify_flags; @@ -684,6 +689,7 @@ struct gnutls_priority_st { #define ENABLE_COMPAT(x) \ (x)->allow_large_records = 1; \ + (x)->no_etm = 1; \ (x)->allow_wrong_pms = 1; \ (x)->dumbfw = 1 diff --git a/lib/gnutls_priority.c b/lib/gnutls_priority.c index 1321541369..82abea6e31 100644 --- a/lib/gnutls_priority.c +++ b/lib/gnutls_priority.c @@ -837,6 +837,10 @@ static void enable_no_extensions(gnutls_priority_t c) { c->no_extensions = 1; } +static void enable_no_etm(gnutls_priority_t c) +{ + c->no_etm = 1; +} static void enable_no_tickets(gnutls_priority_t c) { c->no_tickets = 1; diff --git a/lib/gnutls_session_pack.c b/lib/gnutls_session_pack.c index 5d63a1a0a8..ab0f1c39f7 100644 --- a/lib/gnutls_session_pack.c +++ b/lib/gnutls_session_pack.c @@ -770,6 +770,8 @@ pack_security_parameters(gnutls_session_t session, gnutls_buffer_st * ps) session->security_parameters.client_sign_algo); BUFFER_APPEND_NUM(ps, session->security_parameters.ext_master_secret); + BUFFER_APPEND_NUM(ps, + session->security_parameters.etm); _gnutls_write_uint32(ps->length - cur_size, ps->data + size_offset); @@ -860,6 +862,9 @@ unpack_security_parameters(gnutls_session_t session, gnutls_buffer_st * ps) BUFFER_POP_NUM(ps, session->internals.resumed_security_parameters. ext_master_secret); + BUFFER_POP_NUM(ps, + session->internals.resumed_security_parameters. + etm); if (session->internals.resumed_security_parameters. max_record_recv_size == 0 diff --git a/lib/includes/gnutls/gnutls.h.in b/lib/includes/gnutls/gnutls.h.in index 570ade4baf..adcc6ac314 100644 --- a/lib/includes/gnutls/gnutls.h.in +++ b/lib/includes/gnutls/gnutls.h.in @@ -1023,6 +1023,7 @@ int gnutls_heartbeat_allowed(gnutls_session_t session, unsigned int type); /* Safe renegotiation */ int gnutls_safe_renegotiation_status(gnutls_session_t session); unsigned gnutls_session_ext_master_secret_status(gnutls_session_t session); +unsigned gnutls_session_etm_status(gnutls_session_t session); /** * gnutls_supplemental_data_format_type_t: diff --git a/lib/libgnutls.map b/lib/libgnutls.map index 689926f206..3c9d9f2cc5 100644 --- a/lib/libgnutls.map +++ b/lib/libgnutls.map @@ -1049,6 +1049,7 @@ GNUTLS_3_1_0 { gnutls_privkey_import_ext3; gnutls_record_discard_queued; gnutls_session_ext_master_secret_status; + gnutls_session_etm_status; gnutls_priority_string_list; gnutls_aead_cipher_init; gnutls_aead_cipher_decrypt; diff --git a/lib/priority_options.gperf b/lib/priority_options.gperf index 6718e1bf99..1d529813fa 100644 --- a/lib/priority_options.gperf +++ b/lib/priority_options.gperf @@ -10,6 +10,7 @@ COMPAT, enable_compat DUMBFW, enable_dumbfw NO_EXTENSIONS, enable_no_extensions NO_TICKETS, enable_no_tickets +NO_ETM, enable_no_etm STATELESS_COMPRESSION, enable_stateless_compression VERIFY_ALLOW_SIGN_RSA_MD5, enable_verify_allow_rsa_md5 VERIFY_DISABLE_CRL_CHECKS, disable_crl_checks diff --git a/src/common.c b/src/common.c index 9b2d08ba10..dd61d45dd9 100644 --- a/src/common.c +++ b/src/common.c @@ -559,7 +559,9 @@ int print_info(gnutls_session_t session, int verbose, int print_cert) if (gnutls_session_ext_master_secret_status(session)!=0) printf(" extended master secret,"); if (gnutls_safe_renegotiation_status(session)!=0) - printf(" safe renegotiation"); + printf(" safe renegotiation,"); + if (gnutls_session_etm_status(session)!=0) + printf(" EtM,"); printf("\n"); #ifdef ENABLE_DTLS_SRTP -- cgit v1.2.1