From d781d247d1ef9331983f456d616659108c857d0d Mon Sep 17 00:00:00 2001 From: Matt Caswell Date: Tue, 21 Feb 2017 17:14:42 +0000 Subject: [PATCH] Provide an SSL_read_early() function for reading early data Reviewed-by: Rich Salz (Merged from https://github.com/openssl/openssl/pull/2737) --- include/openssl/ssl.h | 10 +++++- ssl/record/rec_layer_s3.c | 8 +++++ ssl/record/ssl3_record.c | 17 ++++++++-- ssl/ssl_err.c | 3 ++ ssl/ssl_lib.c | 69 +++++++++++++++++++++++++++++++++++++++ ssl/ssl_locl.h | 8 ++++- ssl/statem/statem.c | 3 +- ssl/statem/statem_clnt.c | 6 ---- ssl/statem/statem_lib.c | 5 +++ ssl/statem/statem_srvr.c | 12 +++++-- util/libssl.num | 1 + 11 files changed, 127 insertions(+), 15 deletions(-) diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h index 40965e6450..597f77380d 100644 --- a/include/openssl/ssl.h +++ b/include/openssl/ssl.h @@ -1611,6 +1611,12 @@ __owur int SSL_accept(SSL *ssl); __owur int SSL_connect(SSL *ssl); __owur int SSL_read(SSL *ssl, void *buf, int num); __owur int SSL_read_ex(SSL *ssl, void *buf, size_t num, size_t *readbytes); + +# define SSL_READ_EARLY_ERROR 0 +# define SSL_READ_EARLY_SUCCESS 1 +# define SSL_READ_EARLY_FINISH 2 + +__owur int SSL_read_early(SSL *s, void *buf, size_t num, size_t *readbytes); __owur int SSL_peek(SSL *ssl, void *buf, int num); __owur int SSL_peek_ex(SSL *ssl, void *buf, size_t num, size_t *readbytes); __owur int SSL_write(SSL *ssl, const void *buf, int num); @@ -2255,6 +2261,7 @@ int ERR_load_SSL_strings(void); # define SSL_F_SSL_PEEK_EX 432 # define SSL_F_SSL_PEEK_INTERNAL 522 # define SSL_F_SSL_READ 223 +# define SSL_F_SSL_READ_EARLY 529 # define SSL_F_SSL_READ_EX 434 # define SSL_F_SSL_READ_INTERNAL 523 # define SSL_F_SSL_RENEGOTIATE 516 @@ -2330,7 +2337,7 @@ int ERR_load_SSL_strings(void); # define SSL_F_TLS_CONSTRUCT_CLIENT_VERIFY 489 # define SSL_F_TLS_CONSTRUCT_CTOS_ALPN 466 # define SSL_F_TLS_CONSTRUCT_CTOS_CERTIFICATE 355 -# define SSL_F_TLS_CONSTRUCT_CTOS_EARLY_DATA 521 +# define SSL_F_TLS_CONSTRUCT_CTOS_EARLY_DATA 530 # define SSL_F_TLS_CONSTRUCT_CTOS_EC_PT_FORMATS 467 # define SSL_F_TLS_CONSTRUCT_CTOS_EMS 468 # define SSL_F_TLS_CONSTRUCT_CTOS_ETM 469 @@ -2669,6 +2676,7 @@ int ERR_load_SSL_strings(void); # define SSL_R_UNABLE_TO_FIND_PUBLIC_KEY_PARAMETERS 239 # define SSL_R_UNABLE_TO_LOAD_SSL3_MD5_ROUTINES 242 # define SSL_R_UNABLE_TO_LOAD_SSL3_SHA1_ROUTINES 243 +# define SSL_R_UNEXPECTED_END_OF_EARLY_DATA 178 # define SSL_R_UNEXPECTED_MESSAGE 244 # define SSL_R_UNEXPECTED_RECORD 245 # define SSL_R_UNINITIALIZED 276 diff --git a/ssl/record/rec_layer_s3.c b/ssl/record/rec_layer_s3.c index d7b98e055b..6fa272c77a 100644 --- a/ssl/record/rec_layer_s3.c +++ b/ssl/record/rec_layer_s3.c @@ -1437,6 +1437,14 @@ int ssl3_read_bytes(SSL *s, int type, int *recvd_type, unsigned char *buf, al = SSL_AD_HANDSHAKE_FAILURE; SSLerr(SSL_F_SSL3_READ_BYTES, SSL_R_NO_RENEGOTIATION); goto f_err; + } else if (alert_descr == SSL_AD_END_OF_EARLY_DATA) { + if (!ssl_end_of_early_data_seen(s)) { + al = SSL_AD_UNEXPECTED_MESSAGE; + SSLerr(SSL_F_SSL3_READ_BYTES, + SSL_R_UNEXPECTED_END_OF_EARLY_DATA); + goto f_err; + } + return 0; } } else if (alert_level == SSL3_AL_FATAL) { char tmp[16]; diff --git a/ssl/record/ssl3_record.c b/ssl/record/ssl3_record.c index 4a1c0413a1..94c221f558 100644 --- a/ssl/record/ssl3_record.c +++ b/ssl/record/ssl3_record.c @@ -419,15 +419,15 @@ int ssl3_get_record(SSL *s) /*- * enc_err is: - * 0: (in non-constant time) if the record is publically invalid. + * 0: (in non-constant time) if the record is publicly invalid. * 1: if the padding is valid * -1: if the padding is invalid */ if (enc_err == 0) { if (num_recs == 1 && ossl_statem_skip_early_data(s)) { /* - * We assume this is unreadable early_data - we treat it like an - * empty record + * Valid early_data that we cannot decrypt might fail here as + * publicly invalid. We treat it like an empty record. */ thisrr = &rr[0]; thisrr->length = 0; @@ -507,6 +507,17 @@ int ssl3_get_record(SSL *s) } if (enc_err < 0) { + if (num_recs == 1 && ossl_statem_skip_early_data(s)) { + /* + * We assume this is unreadable early_data - we treat it like an + * empty record + */ + thisrr = &rr[0]; + thisrr->length = 0; + thisrr->read = 1; + RECORD_LAYER_set_numrpipes(&s->rlayer, 1); + return 1; + } /* * A separate 'decryption_failed' alert was introduced with TLS 1.0, * SSL 3.0 only has 'bad_record_mac'. But unless a decryption diff --git a/ssl/ssl_err.c b/ssl/ssl_err.c index c6cc375643..0620966172 100644 --- a/ssl/ssl_err.c +++ b/ssl/ssl_err.c @@ -205,6 +205,7 @@ static ERR_STRING_DATA SSL_str_functs[] = { {ERR_FUNC(SSL_F_SSL_PEEK_EX), "SSL_peek_ex"}, {ERR_FUNC(SSL_F_SSL_PEEK_INTERNAL), "ssl_peek_internal"}, {ERR_FUNC(SSL_F_SSL_READ), "SSL_read"}, + {ERR_FUNC(SSL_F_SSL_READ_EARLY), "SSL_read_early"}, {ERR_FUNC(SSL_F_SSL_READ_EX), "SSL_read_ex"}, {ERR_FUNC(SSL_F_SSL_READ_INTERNAL), "ssl_read_internal"}, {ERR_FUNC(SSL_F_SSL_RENEGOTIATE), "SSL_renegotiate"}, @@ -793,6 +794,8 @@ static ERR_STRING_DATA SSL_str_reasons[] = { "unable to load ssl3 md5 routines"}, {ERR_REASON(SSL_R_UNABLE_TO_LOAD_SSL3_SHA1_ROUTINES), "unable to load ssl3 sha1 routines"}, + {ERR_REASON(SSL_R_UNEXPECTED_END_OF_EARLY_DATA), + "unexpected end of early data"}, {ERR_REASON(SSL_R_UNEXPECTED_MESSAGE), "unexpected message"}, {ERR_REASON(SSL_R_UNEXPECTED_RECORD), "unexpected record"}, {ERR_REASON(SSL_R_UNINITIALIZED), "uninitialized"}, diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c index 8e28786447..e3e7853d60 100644 --- a/ssl/ssl_lib.c +++ b/ssl/ssl_lib.c @@ -1594,6 +1594,75 @@ int SSL_read_ex(SSL *s, void *buf, size_t num, size_t *readbytes) return ret; } +int SSL_read_early(SSL *s, void *buf, size_t num, size_t *readbytes) +{ + int ret; + + if (!s->server) { + SSLerr(SSL_F_SSL_READ_EARLY, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED); + return SSL_READ_EARLY_ERROR; + } + + /* + * TODO(TLS1.3): Somehow we need to check that we're not receiving too much + * data + */ + + switch (s->early_data_state) { + case SSL_EARLY_DATA_NONE: + if (!SSL_in_before(s)) { + SSLerr(SSL_F_SSL_READ_EARLY, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED); + return SSL_READ_EARLY_ERROR; + } + /* fall through */ + + case SSL_EARLY_DATA_ACCEPT_RETRY: + s->early_data_state = SSL_EARLY_DATA_ACCEPTING; + ret = SSL_accept(s); + if (ret <= 0) { + /* NBIO or error */ + s->early_data_state = SSL_EARLY_DATA_ACCEPT_RETRY; + return SSL_READ_EARLY_ERROR; + } + /* fall through */ + + case SSL_EARLY_DATA_READ_RETRY: + if (s->ext.early_data == SSL_EARLY_DATA_ACCEPTED) { + s->early_data_state = SSL_EARLY_DATA_READING; + ret = SSL_read_ex(s, buf, num, readbytes); + /* + * Record layer will call ssl_end_of_early_data_seen() if we see + * that alert - which updates the early_data_state to + * SSL_EARLY_DATA_FINISHED_READING + */ + if (ret > 0 || (ret <= 0 && s->early_data_state + != SSL_EARLY_DATA_FINISHED_READING)) { + s->early_data_state = SSL_EARLY_DATA_READ_RETRY; + return ret > 0 ? SSL_READ_EARLY_SUCCESS : SSL_READ_EARLY_ERROR; + } + } else { + s->early_data_state = SSL_EARLY_DATA_FINISHED_READING; + } + *readbytes = 0; + ossl_statem_set_in_init(s, 1); + return SSL_READ_EARLY_FINISH; + + default: + SSLerr(SSL_F_SSL_READ_EARLY, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED); + return SSL_READ_EARLY_ERROR; + } +} + +int ssl_end_of_early_data_seen(SSL *s) +{ + if (s->early_data_state == SSL_EARLY_DATA_READING) { + s->early_data_state = SSL_EARLY_DATA_FINISHED_READING; + return 1; + } + + return 0; +} + static int ssl_peek_internal(SSL *s, void *buf, size_t num, size_t *readbytes) { if (s->handshake_func == NULL) { diff --git a/ssl/ssl_locl.h b/ssl/ssl_locl.h index 7c8e3fad21..db1d7cfad3 100644 --- a/ssl/ssl_locl.h +++ b/ssl/ssl_locl.h @@ -618,7 +618,12 @@ typedef enum { SSL_EARLY_DATA_CONNECTING, SSL_EARLY_DATA_WRITE_RETRY, SSL_EARLY_DATA_WRITING, - SSL_EARLY_DATA_FINISHED_WRITING + SSL_EARLY_DATA_FINISHED_WRITING, + SSL_EARLY_DATA_ACCEPT_RETRY, + SSL_EARLY_DATA_ACCEPTING, + SSL_EARLY_DATA_READ_RETRY, + SSL_EARLY_DATA_READING, + SSL_EARLY_DATA_FINISHED_READING } SSL_EARLY_DATA_STATE; #define MAX_COMPRESSIONS_SIZE 255 @@ -1987,6 +1992,7 @@ static ossl_inline int ssl_has_cert(const SSL *s, int idx) # ifndef OPENSSL_UNIT_TEST +int ssl_end_of_early_data_seen(SSL *s); __owur int ssl_read_internal(SSL *s, void *buf, size_t num, size_t *readbytes); __owur int ssl_write_internal(SSL *s, const void *buf, size_t num, size_t *written); void ssl_clear_cipher_ctx(SSL *s); diff --git a/ssl/statem/statem.c b/ssl/statem/statem.c index 9ec8e85426..26c9273210 100644 --- a/ssl/statem/statem.c +++ b/ssl/statem/statem.c @@ -326,7 +326,8 @@ static int state_machine(SSL *s, int server) } if ((SSL_IS_FIRST_HANDSHAKE(s) - && s->early_data_state != SSL_EARLY_DATA_FINISHED_WRITING) + && s->early_data_state != SSL_EARLY_DATA_FINISHED_WRITING + && s->early_data_state != SSL_EARLY_DATA_FINISHED_READING) || s->renegotiate) { if (!tls_setup_handshake(s)) { ossl_statem_set_error(s); diff --git a/ssl/statem/statem_clnt.c b/ssl/statem/statem_clnt.c index 390e475fa4..e70ed10932 100644 --- a/ssl/statem/statem_clnt.c +++ b/ssl/statem/statem_clnt.c @@ -1229,12 +1229,6 @@ MSG_PROCESS_RETURN tls_process_server_hello(SSL *s, PACKET *pkt) SSL_COMP *comp; #endif - /* - * This is a real handshake so make sure we clean it up at the end. We set - * this here so that we are after any early_data - */ - s->statem.cleanuphand = 1; - if (!PACKET_get_net_2(pkt, &sversion)) { al = SSL_AD_DECODE_ERROR; SSLerr(SSL_F_TLS_PROCESS_SERVER_HELLO, SSL_R_LENGTH_MISMATCH); diff --git a/ssl/statem/statem_lib.c b/ssl/statem/statem_lib.c index ed1ecce160..dec8cb3e38 100644 --- a/ssl/statem/statem_lib.c +++ b/ssl/statem/statem_lib.c @@ -654,6 +654,10 @@ MSG_PROCESS_RETURN tls_process_finished(SSL *s, PACKET *pkt) int al = SSL_AD_INTERNAL_ERROR; size_t md_len; + + /* This is a real handshake so make sure we clean it up at the end */ + s->statem.cleanuphand = 1; + /* If this occurs, we have missed a message */ if (!SSL_IS_TLS13(s) && !s->s3->change_cipher_spec) { al = SSL_AD_UNEXPECTED_MESSAGE; @@ -944,6 +948,7 @@ WORK_STATE tls_finish_handshake(SSL *s, WORK_STATE wst, int clearbufs) s->d1->next_handshake_write_seq = 0; dtls1_clear_received_buffer(s); } + s->early_data_state = SSL_EARLY_DATA_NONE; } /* diff --git a/ssl/statem/statem_srvr.c b/ssl/statem/statem_srvr.c index 39e0f59833..3d2d39b33d 100644 --- a/ssl/statem/statem_srvr.c +++ b/ssl/statem/statem_srvr.c @@ -406,6 +406,10 @@ static WRITE_TRAN ossl_statem_server13_write_transition(SSL *s) return WRITE_TRAN_ERROR; case TLS_ST_OK: + if (s->early_data_state == SSL_EARLY_DATA_FINISHED_READING) { + st->hand_state = TLS_ST_SW_FINISHED; + return WRITE_TRAN_FINISHED; + } if (s->key_update != SSL_KEY_UPDATE_NONE) { st->hand_state = TLS_ST_SW_KEY_UPDATE; return WRITE_TRAN_CONTINUE; @@ -450,6 +454,11 @@ static WRITE_TRAN ossl_statem_server13_write_transition(SSL *s) return WRITE_TRAN_CONTINUE; case TLS_ST_SW_FINISHED: + if (s->early_data_state == SSL_EARLY_DATA_ACCEPTING) { + st->hand_state = TLS_ST_OK; + ossl_statem_set_in_init(s, 0); + return WRITE_TRAN_CONTINUE; + } return WRITE_TRAN_FINISHED; case TLS_ST_SR_FINISHED: @@ -1234,9 +1243,6 @@ MSG_PROCESS_RETURN tls_process_client_hello(SSL *s, PACKET *pkt) s->new_session = 1; } - /* This is a real handshake so make sure we clean it up at the end */ - s->statem.cleanuphand = 1; - /* * First, parse the raw ClientHello data into the CLIENTHELLO_MSG structure. */ diff --git a/util/libssl.num b/util/libssl.num index 52ae727b37..8d1f0b88b7 100644 --- a/util/libssl.num +++ b/util/libssl.num @@ -430,3 +430,4 @@ SSL_get_max_early_data 430 1_1_1 EXIST::FUNCTION: SSL_CTX_get_max_early_data 431 1_1_1 EXIST::FUNCTION: SSL_write_early 432 1_1_1 EXIST::FUNCTION: SSL_write_early_finish 433 1_1_1 EXIST::FUNCTION: +SSL_read_early 434 1_1_1 EXIST::FUNCTION: -- 2.25.1