From 11c67eeaf4dd0376d84a90590e307d5d2e12f025 Mon Sep 17 00:00:00 2001 From: Matt Caswell Date: Mon, 13 Mar 2017 15:21:15 +0000 Subject: [PATCH] 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 (Merged from https://github.com/openssl/openssl/pull/2895) --- include/openssl/ssl.h | 3 + include/openssl/ssl3.h | 5 +- ssl/ssl_err.c | 4 + ssl/statem/statem_clnt.c | 127 ++++++++++++++++++++--------- ssl/statem/statem_lib.c | 43 +++++++++- ssl/statem/statem_locl.h | 2 +- ssl/statem/statem_srvr.c | 25 +++++- ssl/t1_trce.c | 29 +++++-- util/TLSProxy/HelloRetryRequest.pm | 13 +++ 9 files changed, 200 insertions(+), 51 deletions(-) diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h index e3e85d6165..8003959f7c 100644 --- a/include/openssl/ssl.h +++ b/include/openssl/ssl.h @@ -2135,6 +2135,7 @@ int ERR_load_SSL_strings(void); # define SSL_F_ADD_KEY_SHARE 512 # define SSL_F_BYTES_TO_CIPHER_LIST 519 # define SSL_F_CHECK_SUITEB_CIPHER_LIST 331 +# define SSL_F_CREATE_SYNTHETIC_MESSAGE_HASH 539 # define SSL_F_CT_MOVE_SCTS 345 # define SSL_F_CT_STRICT 349 # define SSL_F_D2I_SSL_SESSION 103 @@ -2175,6 +2176,7 @@ int ERR_load_SSL_strings(void); # define SSL_F_OSSL_STATEM_SERVER_READ_TRANSITION 418 # define SSL_F_PROCESS_KEY_SHARE_EXT 439 # define SSL_F_READ_STATE_MACHINE 352 +# define SSL_F_SET_CLIENT_CIPHERSUITE 540 # define SSL_F_SSL3_CHANGE_CIPHER_STATE 129 # define SSL_F_SSL3_CHECK_CERT_AND_ALGORITHM 130 # define SSL_F_SSL3_CTRL 213 @@ -2464,6 +2466,7 @@ int ERR_load_SSL_strings(void); # define SSL_R_AT_LEAST_TLS_1_0_NEEDED_IN_FIPS_MODE 143 # define SSL_R_AT_LEAST_TLS_1_2_NEEDED_IN_SUITEB_MODE 158 # define SSL_R_BAD_CHANGE_CIPHER_SPEC 103 +# define SSL_R_BAD_CIPHER 186 # define SSL_R_BAD_DATA 390 # define SSL_R_BAD_DATA_RETURNED_BY_CALLBACK 106 # define SSL_R_BAD_DECOMPRESSION 107 diff --git a/include/openssl/ssl3.h b/include/openssl/ssl3.h index 0ecb509a9e..13de6b7b7b 100644 --- a/include/openssl/ssl3.h +++ b/include/openssl/ssl3.h @@ -293,9 +293,10 @@ extern "C" { # define SSL3_MT_CERTIFICATE_STATUS 22 # define SSL3_MT_KEY_UPDATE 24 # ifndef OPENSSL_NO_NEXTPROTONEG -# define SSL3_MT_NEXT_PROTO 67 +# define SSL3_MT_NEXT_PROTO 67 # endif -# define DTLS1_MT_HELLO_VERIFY_REQUEST 3 +# define SSL3_MT_MESSAGE_HASH 254 +# define DTLS1_MT_HELLO_VERIFY_REQUEST 3 /* Dummy message type for handling CCS like a normal handshake message */ # define SSL3_MT_CHANGE_CIPHER_SPEC 0x0101 diff --git a/ssl/ssl_err.c b/ssl/ssl_err.c index c476b1e7e0..f7ee171602 100644 --- a/ssl/ssl_err.c +++ b/ssl/ssl_err.c @@ -23,6 +23,8 @@ static ERR_STRING_DATA SSL_str_functs[] = { {ERR_FUNC(SSL_F_ADD_KEY_SHARE), "add_key_share"}, {ERR_FUNC(SSL_F_BYTES_TO_CIPHER_LIST), "bytes_to_cipher_list"}, {ERR_FUNC(SSL_F_CHECK_SUITEB_CIPHER_LIST), "check_suiteb_cipher_list"}, + {ERR_FUNC(SSL_F_CREATE_SYNTHETIC_MESSAGE_HASH), + "create_synthetic_message_hash"}, {ERR_FUNC(SSL_F_CT_MOVE_SCTS), "ct_move_scts"}, {ERR_FUNC(SSL_F_CT_STRICT), "ct_strict"}, {ERR_FUNC(SSL_F_D2I_SSL_SESSION), "d2i_SSL_SESSION"}, @@ -74,6 +76,7 @@ static ERR_STRING_DATA SSL_str_functs[] = { "ossl_statem_server_read_transition"}, {ERR_FUNC(SSL_F_PROCESS_KEY_SHARE_EXT), "process_key_share_ext"}, {ERR_FUNC(SSL_F_READ_STATE_MACHINE), "read_state_machine"}, + {ERR_FUNC(SSL_F_SET_CLIENT_CIPHERSUITE), "set_client_ciphersuite"}, {ERR_FUNC(SSL_F_SSL3_CHANGE_CIPHER_STATE), "ssl3_change_cipher_state"}, {ERR_FUNC(SSL_F_SSL3_CHECK_CERT_AND_ALGORITHM), "ssl3_check_cert_and_algorithm"}, @@ -481,6 +484,7 @@ static ERR_STRING_DATA SSL_str_reasons[] = { {ERR_REASON(SSL_R_AT_LEAST_TLS_1_2_NEEDED_IN_SUITEB_MODE), "at least (D)TLS 1.2 needed in Suite B mode"}, {ERR_REASON(SSL_R_BAD_CHANGE_CIPHER_SPEC), "bad change cipher spec"}, + {ERR_REASON(SSL_R_BAD_CIPHER), "bad cipher"}, {ERR_REASON(SSL_R_BAD_DATA), "bad data"}, {ERR_REASON(SSL_R_BAD_DATA_RETURNED_BY_CALLBACK), "bad data returned by callback"}, diff --git a/ssl/statem/statem_clnt.c b/ssl/statem/statem_clnt.c index 32b7300e45..d153afe78b 100644 --- a/ssl/statem/statem_clnt.c +++ b/ssl/statem/statem_clnt.c @@ -1243,14 +1243,65 @@ MSG_PROCESS_RETURN dtls_process_hello_verify(SSL *s, PACKET *pkt) 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,53 +1488,17 @@ MSG_PROCESS_RETURN tls_process_server_hello(SSL *s, PACKET *pkt) 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) { @@ -1580,6 +1595,7 @@ static MSG_PROCESS_RETURN tls_process_hello_retry_request(SSL *s, PACKET *pkt) { unsigned int sversion; int errorcode; + const unsigned char *cipherchars; RAW_EXTENSION *extensions = NULL; int al; PACKET extpkt; @@ -1600,6 +1616,17 @@ static MSG_PROCESS_RETURN tls_process_hello_retry_request(SSL *s, PACKET *pkt) 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 @@ static MSG_PROCESS_RETURN tls_process_hello_retry_request(SSL *s, PACKET *pkt) 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 98bd2f714f..04ac795114 100644 --- a/ssl/statem/statem_lib.c +++ b/ssl/statem/statem_lib.c @@ -1149,8 +1149,13 @@ int tls_get_message_body(SSL *s, size_t *len) 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 @@ int check_in_list(SSL *s, unsigned int group_id, const unsigned char *groups, 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 2e9821caf9..1c78a4da70 100644 --- a/ssl/statem/statem_locl.h +++ b/ssl/statem/statem_locl.h @@ -79,7 +79,7 @@ typedef int (*confunc_f) (SSL *s, WPACKET *pkt); 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 608bef2215..08b5f8dcd4 100644 --- a/ssl/statem/statem_srvr.c +++ b/ssl/statem/statem_srvr.c @@ -1975,6 +1975,16 @@ WORK_STATE tls_post_process_client_hello(SSL *s, WORK_STATE wst) 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_encrypted_extensions(SSL *s, WPACKET *pkt) 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 @@ static int tls_construct_hello_retry_request(SSL *s, WPACKET *pkt) 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) diff --git a/ssl/t1_trce.c b/ssl/t1_trce.c index 3968509eb7..06320660f4 100644 --- a/ssl/t1_trce.c +++ b/ssl/t1_trce.c @@ -992,6 +992,29 @@ static int ssl_print_server_hello(BIO *bio, int indent, return 1; } +static int ssl_print_hello_retry_request(BIO *bio, int indent, + const unsigned char *msg, + size_t msglen) +{ + unsigned int cs; + + if (!ssl_print_version(bio, indent, "server_version", &msg, &msglen, NULL)) + return 0; + + cs = (msg[0] << 8) | msg[1]; + BIO_indent(bio, indent, 80); + BIO_printf(bio, "cipher_suite {0x%02X, 0x%02X} %s\n", + msg[0], msg[1], ssl_trace_str(cs, ssl_ciphers_tbl)); + msg += 2; + msglen -= 2; + + if (!ssl_print_extensions(bio, indent, 1, SSL3_MT_HELLO_RETRY_REQUEST, &msg, + &msglen)) + return 0; + + return 1; +} + static int ssl_get_keyex(const char **pname, SSL *ssl) { unsigned long alg_k = ssl->s3->tmp.new_cipher->algorithm_mkey; @@ -1422,11 +1445,7 @@ static int ssl_print_handshake(BIO *bio, SSL *ssl, int server, break; case SSL3_MT_HELLO_RETRY_REQUEST: - if (!ssl_print_version(bio, indent + 2, "server_version", &msg, &msglen, - NULL) - || !ssl_print_extensions(bio, indent + 2, 1, - SSL3_MT_HELLO_RETRY_REQUEST, &msg, - &msglen)) + if (!ssl_print_hello_retry_request(bio, indent + 2, msg, msglen)) return 0; break; diff --git a/util/TLSProxy/HelloRetryRequest.pm b/util/TLSProxy/HelloRetryRequest.pm index a15c05463d..94fe4ee802 100644 --- a/util/TLSProxy/HelloRetryRequest.pm +++ b/util/TLSProxy/HelloRetryRequest.pm @@ -47,6 +47,9 @@ sub parse $server_version = TLSProxy::Record::VERS_TLS_1_3; } + my $ciphersuite = unpack('n', substr($self->data, $ptr)); + $ptr += 2; + my $extensions_len = unpack('n', substr($self->data, $ptr)); if (!defined $extensions_len) { $extensions_len = 0; @@ -75,6 +78,7 @@ sub parse } $self->server_version($server_version); + $self->ciphersuite($ciphersuite); $self->extension_data(\%extensions); print " Extensions Len:".$extensions_len."\n"; @@ -100,6 +104,7 @@ sub set_message_contents } $data = pack('n', $self->server_version); + $data .= pack('n', $self->ciphersuite); $data .= pack('n', length($extensions)); $data .= $extensions; $self->data($data); @@ -114,6 +119,14 @@ sub server_version } return $self->{server_version}; } +sub ciphersuite +{ + my $self = shift; + if (@_) { + $self->{ciphersuite} = shift; + } + return $self->{ciphersuite}; +} sub extension_data { my $self = shift; -- 2.40.0