HelloRetryRequest updates for draft-19
Draft-19 changes the HRR transcript hash so that the initial ClientHello
is replaced in the transcript with a special synthetic message_hash message
that just contains a hash of ClientHello1 as its message body.
Reviewed-by: Rich Salz <rsalz@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/2895)
diff --git a/ssl/statem/statem_clnt.c b/ssl/statem/statem_clnt.c
index 32b7300..d153afe 100644
--- a/ssl/statem/statem_clnt.c
+++ b/ssl/statem/statem_clnt.c
@@ -1243,14 +1243,65 @@
return MSG_PROCESS_ERROR;
}
-MSG_PROCESS_RETURN tls_process_server_hello(SSL *s, PACKET *pkt)
+static int set_client_ciphersuite(SSL *s, const unsigned char *cipherchars)
{
STACK_OF(SSL_CIPHER) *sk;
const SSL_CIPHER *c;
+ int i;
+
+ c = ssl_get_cipher_by_char(s, cipherchars, 0);
+ if (c == NULL) {
+ /* unknown cipher */
+ SSLerr(SSL_F_SET_CLIENT_CIPHERSUITE, SSL_R_UNKNOWN_CIPHER_RETURNED);
+ return 0;
+ }
+ /*
+ * If it is a disabled cipher we either didn't send it in client hello,
+ * or it's not allowed for the selected protocol. So we return an error.
+ */
+ if (ssl_cipher_disabled(s, c, SSL_SECOP_CIPHER_CHECK)) {
+ SSLerr(SSL_F_SET_CLIENT_CIPHERSUITE, SSL_R_WRONG_CIPHER_RETURNED);
+ return 0;
+ }
+
+ sk = ssl_get_ciphers_by_id(s);
+ i = sk_SSL_CIPHER_find(sk, c);
+ if (i < 0) {
+ /* we did not say we would use this cipher */
+ SSLerr(SSL_F_SET_CLIENT_CIPHERSUITE, SSL_R_WRONG_CIPHER_RETURNED);
+ return 0;
+ }
+
+ if (SSL_IS_TLS13(s) && s->s3->tmp.new_cipher != NULL
+ && s->s3->tmp.new_cipher->id != c->id) {
+ /* ServerHello selected a different ciphersuite to that in the HRR */
+ SSLerr(SSL_F_SET_CLIENT_CIPHERSUITE, SSL_R_WRONG_CIPHER_RETURNED);
+ return 0;
+ }
+
+ /*
+ * Depending on the session caching (internal/external), the cipher
+ * and/or cipher_id values may not be set. Make sure that cipher_id is
+ * set and use it for comparison.
+ */
+ if (s->session->cipher != NULL)
+ s->session->cipher_id = s->session->cipher->id;
+ if (s->hit && (s->session->cipher_id != c->id)) {
+ SSLerr(SSL_F_SET_CLIENT_CIPHERSUITE,
+ SSL_R_OLD_SESSION_CIPHER_NOT_RETURNED);
+ return 0;
+ }
+ s->s3->tmp.new_cipher = c;
+
+ return 1;
+}
+
+MSG_PROCESS_RETURN tls_process_server_hello(SSL *s, PACKET *pkt)
+{
PACKET session_id, extpkt;
size_t session_id_len;
const unsigned char *cipherchars;
- int i, al = SSL_AD_INTERNAL_ERROR;
+ int al = SSL_AD_INTERNAL_ERROR;
unsigned int compression;
unsigned int sversion;
unsigned int context;
@@ -1437,54 +1488,18 @@
SSL_R_SSL_SESSION_VERSION_MISMATCH);
goto f_err;
}
-
- c = ssl_get_cipher_by_char(s, cipherchars, 0);
- if (c == NULL) {
- /* unknown cipher */
- al = SSL_AD_ILLEGAL_PARAMETER;
- SSLerr(SSL_F_TLS_PROCESS_SERVER_HELLO, SSL_R_UNKNOWN_CIPHER_RETURNED);
- goto f_err;
- }
/*
* Now that we know the version, update the check to see if it's an allowed
* version.
*/
s->s3->tmp.min_ver = s->version;
s->s3->tmp.max_ver = s->version;
- /*
- * If it is a disabled cipher we either didn't send it in client hello,
- * or it's not allowed for the selected protocol. So we return an error.
- */
- if (ssl_cipher_disabled(s, c, SSL_SECOP_CIPHER_CHECK)) {
- al = SSL_AD_ILLEGAL_PARAMETER;
- SSLerr(SSL_F_TLS_PROCESS_SERVER_HELLO, SSL_R_WRONG_CIPHER_RETURNED);
- goto f_err;
- }
- sk = ssl_get_ciphers_by_id(s);
- i = sk_SSL_CIPHER_find(sk, c);
- if (i < 0) {
- /* we did not say we would use this cipher */
+ if (!set_client_ciphersuite(s, cipherchars)) {
al = SSL_AD_ILLEGAL_PARAMETER;
- SSLerr(SSL_F_TLS_PROCESS_SERVER_HELLO, SSL_R_WRONG_CIPHER_RETURNED);
goto f_err;
}
- /*
- * Depending on the session caching (internal/external), the cipher
- * and/or cipher_id values may not be set. Make sure that cipher_id is
- * set and use it for comparison.
- */
- if (s->session->cipher)
- s->session->cipher_id = s->session->cipher->id;
- if (s->hit && (s->session->cipher_id != c->id)) {
- al = SSL_AD_ILLEGAL_PARAMETER;
- SSLerr(SSL_F_TLS_PROCESS_SERVER_HELLO,
- SSL_R_OLD_SESSION_CIPHER_NOT_RETURNED);
- goto f_err;
- }
- s->s3->tmp.new_cipher = c;
-
#ifdef OPENSSL_NO_COMP
if (compression != 0) {
al = SSL_AD_ILLEGAL_PARAMETER;
@@ -1580,6 +1595,7 @@
{
unsigned int sversion;
int errorcode;
+ const unsigned char *cipherchars;
RAW_EXTENSION *extensions = NULL;
int al;
PACKET extpkt;
@@ -1600,6 +1616,17 @@
goto f_err;
}
+ if (!PACKET_get_bytes(pkt, &cipherchars, TLS_CIPHER_LEN)) {
+ SSLerr(SSL_F_TLS_PROCESS_HELLO_RETRY_REQUEST, SSL_R_LENGTH_MISMATCH);
+ al = SSL_AD_DECODE_ERROR;
+ goto f_err;
+ }
+
+ if (!set_client_ciphersuite(s, cipherchars)) {
+ al = SSL_AD_ILLEGAL_PARAMETER;
+ goto f_err;
+ }
+
if (!PACKET_as_length_prefixed_2(pkt, &extpkt)) {
al = SSL_AD_DECODE_ERROR;
SSLerr(SSL_F_TLS_PROCESS_HELLO_RETRY_REQUEST, SSL_R_BAD_LENGTH);
@@ -1614,6 +1641,28 @@
OPENSSL_free(extensions);
+ /*
+ * Re-initialise the Transcript Hash. We're going to prepopulate it with
+ * a synthetic message_hash in place of ClientHello1.
+ */
+ if (!create_synthetic_message_hash(s)) {
+ al = SSL_AD_INTERNAL_ERROR;
+ goto f_err;
+ }
+
+ /*
+ * Add this message to the Transcript Hash. Normally this is done
+ * automatically prior to the message processing stage. However due to the
+ * need to create the synthetic message hash, we defer that step until now
+ * for HRR messages.
+ */
+ if (!ssl3_finish_mac(s, (unsigned char *)s->init_buf->data,
+ s->init_num + SSL3_HM_HEADER_LENGTH)) {
+ al = SSL_AD_INTERNAL_ERROR;
+ SSLerr(SSL_F_TLS_PROCESS_HELLO_RETRY_REQUEST, ERR_R_INTERNAL_ERROR);
+ goto f_err;
+ }
+
return MSG_PROCESS_FINISHED_READING;
f_err:
ssl3_send_alert(s, SSL3_AL_FATAL, al);
diff --git a/ssl/statem/statem_lib.c b/ssl/statem/statem_lib.c
index 98bd2f7..04ac795 100644
--- a/ssl/statem/statem_lib.c
+++ b/ssl/statem/statem_lib.c
@@ -1149,8 +1149,13 @@
s->msg_callback(0, SSL2_VERSION, 0, s->init_buf->data,
(size_t)s->init_num, s, s->msg_callback_arg);
} else {
- if (!ssl3_finish_mac(s, (unsigned char *)s->init_buf->data,
- s->init_num + SSL3_HM_HEADER_LENGTH)) {
+ /*
+ * We defer feeding in the HRR until later. We'll do it as part of
+ * processing the message
+ */
+ if (s->s3->tmp.message_type != SSL3_MT_HELLO_RETRY_REQUEST
+ && !ssl3_finish_mac(s, (unsigned char *)s->init_buf->data,
+ s->init_num + SSL3_HM_HEADER_LENGTH)) {
SSLerr(SSL_F_TLS_GET_MESSAGE_BODY, ERR_R_EVP_LIB);
ssl3_send_alert(s, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
*len = 0;
@@ -1870,3 +1875,37 @@
return i < num_groups;
}
#endif
+
+/* Replace ClientHello1 in the transcript hash with a synthetic message */
+int create_synthetic_message_hash(SSL *s)
+{
+ unsigned char hashval[EVP_MAX_MD_SIZE];
+ size_t hashlen = 0;
+ unsigned char msghdr[SSL3_HM_HEADER_LENGTH] = {
+ SSL3_MT_MESSAGE_HASH,
+ 0,
+ 0,
+ 0
+ };
+
+ /* Get the hash of the initial ClientHello */
+ if (!ssl3_digest_cached_records(s, 0)
+ || !ssl_handshake_hash(s, hashval, sizeof(hashval), &hashlen)) {
+ SSLerr(SSL_F_CREATE_SYNTHETIC_MESSAGE_HASH, ERR_R_INTERNAL_ERROR);
+ return 0;
+ }
+
+ /* Reinitialise the transcript hash */
+ if (!ssl3_init_finished_mac(s))
+ return 0;
+
+ /* Inject the synthetic message_hash message */
+ msghdr[SSL3_HM_HEADER_LENGTH - 1] = hashlen;
+ if (!ssl3_finish_mac(s, msghdr, SSL3_HM_HEADER_LENGTH)
+ || !ssl3_finish_mac(s, hashval, hashlen)) {
+ SSLerr(SSL_F_CREATE_SYNTHETIC_MESSAGE_HASH, ERR_R_INTERNAL_ERROR);
+ return 0;
+ }
+
+ return 1;
+}
diff --git a/ssl/statem/statem_locl.h b/ssl/statem/statem_locl.h
index 2e9821c..1c78a4d 100644
--- a/ssl/statem/statem_locl.h
+++ b/ssl/statem/statem_locl.h
@@ -79,7 +79,7 @@
int check_in_list(SSL *s, unsigned int group_id, const unsigned char *groups,
size_t num_groups, int checkallow);
-
+int create_synthetic_message_hash(SSL *s);
/*
* TLS/DTLS client state machine functions
*/
diff --git a/ssl/statem/statem_srvr.c b/ssl/statem/statem_srvr.c
index 608bef2..08b5f8d 100644
--- a/ssl/statem/statem_srvr.c
+++ b/ssl/statem/statem_srvr.c
@@ -1975,6 +1975,16 @@
SSL_R_NO_SHARED_CIPHER);
goto f_err;
}
+ if (SSL_IS_TLS13(s) && s->s3->tmp.new_cipher != NULL
+ && s->s3->tmp.new_cipher->id != cipher->id) {
+ /*
+ * A previous HRR picked a different ciphersuite to the one we
+ * just selected. Something must have changed.
+ */
+ al = SSL_AD_ILLEGAL_PARAMETER;
+ SSLerr(SSL_F_TLS_POST_PROCESS_CLIENT_HELLO, SSL_R_BAD_CIPHER);
+ goto f_err;
+ }
s->s3->tmp.new_cipher = cipher;
if (!tls_choose_sigalg(s, &al))
goto f_err;
@@ -3662,17 +3672,18 @@
static int tls_construct_hello_retry_request(SSL *s, WPACKET *pkt)
{
int al = SSL_AD_INTERNAL_ERROR;
+ size_t len = 0;
/*
* TODO(TLS1.3): Remove the DRAFT version before release
* (should be s->version)
*/
if (!WPACKET_put_bytes_u16(pkt, TLS1_3_VERSION_DRAFT)
+ || !s->method->put_cipher_by_char(s->s3->tmp.new_cipher, pkt, &len)
|| !tls_construct_extensions(s, pkt, EXT_TLS1_3_HELLO_RETRY_REQUEST,
NULL, 0, &al)) {
SSLerr(SSL_F_TLS_CONSTRUCT_HELLO_RETRY_REQUEST, ERR_R_INTERNAL_ERROR);
- ssl3_send_alert(s, SSL3_AL_FATAL, al);
- return 0;
+ goto err;
}
/* Ditch the session. We'll create a new one next time around */
@@ -3680,7 +3691,17 @@
s->session = NULL;
s->hit = 0;
+ /*
+ * Re-initialise the Transcript Hash. We're going to prepopulate it with
+ * a synthetic message_hash in place of ClientHello1.
+ */
+ if (!create_synthetic_message_hash(s))
+ goto err;
+
return 1;
+ err:
+ ssl3_send_alert(s, SSL3_AL_FATAL, al);
+ return 0;
}
MSG_PROCESS_RETURN tls_process_end_of_early_data(SSL *s, PACKET *pkt)