summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNikos Mavrogiannopoulos <nmav@redhat.com>2014-11-03 14:23:48 +0100
committerNikos Mavrogiannopoulos <nmav@redhat.com>2014-11-03 17:09:01 +0100
commite93cef18471962b001dac0f792cb569f1a4cde58 (patch)
treea8e33fc6c302a7126fc177d1f4a8f0e16fabf52b
parente29d027872fb61a6e7117d3b920626bbc638ac64 (diff)
downloadgnutls-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.
-rw-r--r--doc/cha-gtls-app.texi6
-rw-r--r--lib/ext/Makefile.am2
-rw-r--r--lib/ext/etm.c137
-rw-r--r--lib/ext/etm.h30
-rw-r--r--lib/gnutls_buffers.c4
-rw-r--r--lib/gnutls_cipher.c294
-rw-r--r--lib/gnutls_cipher_int.c116
-rw-r--r--lib/gnutls_cipher_int.h5
-rw-r--r--lib/gnutls_constate.c3
-rw-r--r--lib/gnutls_extensions.c5
-rw-r--r--lib/gnutls_int.h6
-rw-r--r--lib/gnutls_priority.c4
-rw-r--r--lib/gnutls_session_pack.c5
-rw-r--r--lib/includes/gnutls/gnutls.h.in1
-rw-r--r--lib/libgnutls.map1
-rw-r--r--lib/priority_options.gperf1
-rw-r--r--src/common.c4
17 files changed, 471 insertions, 153 deletions
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 <http://www.gnu.org/licenses/>
+ *
+ */
+
+/* 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 <gnutls_extensions.h>
+#include <ext/etm.h>
+
+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 <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifndef EXT_ETM_H
+#define EXT_ETM_H
+
+#include <gnutls_extensions.h>
+
+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(&params->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(&params->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(&params->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(&params->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(&params->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(&params->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(&params->read.
+ cipher_state, preamble,
+ preamble_size);
+ if (unlikely(ret < 0))
+ return gnutls_assert_val(ret);
+
+ ret = _gnutls_auth_cipher_add_auth(&params->read.
+ cipher_state,
+ ciphertext->data,
+ ciphertext->size-tag_size);
+ if (unlikely(ret < 0))
+ return gnutls_assert_val(ret);
+
+ ret = _gnutls_auth_cipher_tag(&params->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(&params->read.
- cipher_state, preamble,
- preamble_size);
- if (unlikely(ret < 0))
- return gnutls_assert_val(ret);
+ ret =
+ _gnutls_auth_cipher_add_auth(&params->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(&params->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(&params->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(&params->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(&params->read.
- cipher_state,
- compressed->data, length);
- if (unlikely(ret < 0))
- return gnutls_assert_val(ret);
+ ret =
+ _gnutls_auth_cipher_add_auth(&params->read.
+ cipher_state, preamble,
+ preamble_size);
+ if (unlikely(ret < 0))
+ return gnutls_assert_val(ret);
+ ret =
+ _gnutls_auth_cipher_add_auth(&params->read.
+ cipher_state,
+ compressed->data, length);
+ if (unlikely(ret < 0))
+ return gnutls_assert_val(ret);
+ } else { /* EtM */
+ ret =
+ _gnutls_cipher_decrypt2(&params->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(&params->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(&params->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 <ext/srtp.h>
#include <ext/alpn.h>
#include <ext/dumbfw.h>
+#include <ext/etm.h>
#include <gnutls_num.h>
@@ -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