diff options
author | Daiki Ueno <dueno@redhat.com> | 2018-10-12 11:45:59 +0200 |
---|---|---|
committer | Daiki Ueno <dueno@redhat.com> | 2018-11-11 07:03:43 +0100 |
commit | 957f7537604b21653c0d456e55fabed600052508 (patch) | |
tree | 0e818d620669b1efe8f024eb1e4caf3f1d4217bd /lib | |
parent | f39af59c4e7f7062b548c6c97e785bb6b6284371 (diff) | |
download | gnutls-957f7537604b21653c0d456e55fabed600052508.tar.gz |
handshake: handle early data
This plumbers early data handling in the handshake processes, which
consists of:
- traffic key updates taking into account of client_early_traffic_secret
- early data buffering in both server and client
- the EndOfEarlyData message handling
- making use of max_early_data_size extension in NewSessionTicket
Signed-off-by: Daiki Ueno <dueno@redhat.com>
Diffstat (limited to 'lib')
-rw-r--r-- | lib/Makefile.am | 1 | ||||
-rw-r--r-- | lib/constate.c | 79 | ||||
-rw-r--r-- | lib/ext/early_data.c | 24 | ||||
-rw-r--r-- | lib/gnutls_int.h | 18 | ||||
-rw-r--r-- | lib/handshake-tls13.c | 165 | ||||
-rw-r--r-- | lib/handshake.c | 97 | ||||
-rw-r--r-- | lib/includes/gnutls/gnutls.h.in | 8 | ||||
-rw-r--r-- | lib/record.c | 114 | ||||
-rw-r--r-- | lib/state.c | 7 | ||||
-rw-r--r-- | lib/tls13/early_data.c | 101 | ||||
-rw-r--r-- | lib/tls13/early_data.h | 25 | ||||
-rw-r--r-- | lib/tls13/session_ticket.c | 41 |
12 files changed, 572 insertions, 108 deletions
diff --git a/lib/Makefile.am b/lib/Makefile.am index e101ec68f2..6167c4e861 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -97,6 +97,7 @@ COBJECTS += tls13/encrypted_extensions.c tls13/encrypted_extensions.h \ tls13/hello_retry.c tls13/hello_retry.h \ tls13/session_ticket.c tls13/session_ticket.h \ tls13/certificate.c tls13/certificate.h \ + tls13/early_data.c tls13/early_data.h \ tls13/post_handshake.c \ tls13/psk_ext_parser.c tls13/psk_ext_parser.h diff --git a/lib/constate.c b/lib/constate.c index d9ec5c08ef..11fedab533 100644 --- a/lib/constate.c +++ b/lib/constate.c @@ -322,6 +322,61 @@ _tls13_update_keys(gnutls_session_t session, hs_stage_t stage, } static int +_tls13_set_early_keys(gnutls_session_t session, + record_parameters_st * params, + unsigned iv_size, unsigned key_size) +{ + uint8_t key_block[MAX_CIPHER_KEY_SIZE]; + uint8_t iv_block[MAX_CIPHER_IV_SIZE]; + char buf[65]; + record_state_st *early_state; + int ret; + + if (session->security_parameters.entity == GNUTLS_CLIENT && + !(session->internals.hsk_flags & HSK_TLS13_TICKET_SENT)) { + return GNUTLS_E_INVALID_REQUEST; + } + + ret = _tls13_expand_secret(session, "key", 3, NULL, 0, session->key.proto.tls13.e_ckey, key_size, key_block); + if (ret < 0) + return gnutls_assert_val(ret); + + ret = _tls13_expand_secret(session, "iv", 2, NULL, 0, session->key.proto.tls13.e_ckey, iv_size, iv_block); + if (ret < 0) + return gnutls_assert_val(ret); + + if (session->security_parameters.entity == GNUTLS_CLIENT) { + early_state = ¶ms->write; + } else { + early_state = ¶ms->read; + } + + early_state->mac_key_size = 0; + + assert(key_size <= sizeof(early_state->key)); + memcpy(early_state->key, key_block, key_size); + early_state->key_size = key_size; + + _gnutls_hard_log("INT: EARLY KEY [%d]: %s\n", + key_size, + _gnutls_bin2hex(key_block, key_size, + buf, sizeof(buf), NULL)); + + if (iv_size > 0) { + assert(iv_size <= sizeof(early_state->iv)); + memcpy(early_state->iv, iv_block, iv_size); + early_state->iv_size = iv_size; + + _gnutls_hard_log("INT: EARLY IV [%d]: %s\n", + iv_size, + _gnutls_bin2hex(iv_block, iv_size, + buf, sizeof(buf), NULL)); + } + + return 0; +} + +static int _tls13_set_keys(gnutls_session_t session, hs_stage_t stage, record_parameters_st * params, unsigned iv_size, unsigned key_size) @@ -342,7 +397,11 @@ _tls13_set_keys(gnutls_session_t session, hs_stage_t stage, return _tls13_update_keys(session, stage, params, iv_size, key_size); - if (stage == STAGE_HS) { + else if (stage == STAGE_EARLY) + return _tls13_set_early_keys(session, + params, iv_size, key_size); + + else if (stage == STAGE_HS) { label = HANDSHAKE_CLIENT_TRAFFIC_LABEL; label_size = sizeof(HANDSHAKE_CLIENT_TRAFFIC_LABEL)-1; hsk_len = session->internals.handshake_hash_buffer.length; @@ -621,13 +680,19 @@ int _gnutls_epoch_set_keys(gnutls_session_t session, uint16_t epoch, hs_stage_t if (ret < 0) return gnutls_assert_val(ret); - ret = _tls13_init_record_state(params->cipher->id, ¶ms->read); - if (ret < 0) - return gnutls_assert_val(ret); + if (stage != STAGE_EARLY || + session->security_parameters.entity == GNUTLS_SERVER) { + ret = _tls13_init_record_state(params->cipher->id, ¶ms->read); + if (ret < 0) + return gnutls_assert_val(ret); + } - ret = _tls13_init_record_state(params->cipher->id, ¶ms->write); - if (ret < 0) - return gnutls_assert_val(ret); + if (stage != STAGE_EARLY || + session->security_parameters.entity == GNUTLS_CLIENT) { + ret = _tls13_init_record_state(params->cipher->id, ¶ms->write); + if (ret < 0) + return gnutls_assert_val(ret); + } } else { ret = _gnutls_set_keys (session, params, hash_size, IV_size, key_size); diff --git a/lib/ext/early_data.c b/lib/ext/early_data.c index daa0d8fcb7..213e17d5c4 100644 --- a/lib/ext/early_data.c +++ b/lib/ext/early_data.c @@ -39,7 +39,7 @@ const hello_ext_entry_st ext_mod_early_data = { .name = "Early Data", .tls_id = 42, .gid = GNUTLS_EXTENSION_EARLY_DATA, - .validity = GNUTLS_EXT_FLAG_TLS | GNUTLS_EXT_FLAG_CLIENT_HELLO, + .validity = GNUTLS_EXT_FLAG_TLS | GNUTLS_EXT_FLAG_CLIENT_HELLO | GNUTLS_EXT_FLAG_EE, .parse_type = GNUTLS_EXT_MANDATORY, /* force parsing prior to EXT_TLS extensions */ .recv_func = early_data_recv_params, .send_func = early_data_send_params, @@ -54,10 +54,20 @@ early_data_recv_params(gnutls_session_t session, const uint8_t * data, size_t _data_size) { const version_entry_st *vers = get_version(session); + if (!vers || !vers->tls13_sem) return gnutls_assert_val(0); - if (session->security_parameters.entity == GNUTLS_SERVER) + + if (session->security_parameters.entity == GNUTLS_SERVER) { + if ((session->internals.flags & GNUTLS_ENABLE_EARLY_DATA) && + /* Refuse early data when this is a second CH after HRR */ + !(session->internals.hsk_flags & HSK_HRR_SENT)) + session->internals.hsk_flags |= HSK_EARLY_DATA_ACCEPTED; session->internals.hsk_flags |= HSK_EARLY_DATA_IN_FLIGHT; + } else { + if (_gnutls_ext_get_msg(session) == GNUTLS_EXT_FLAG_EE) + session->internals.hsk_flags |= HSK_EARLY_DATA_ACCEPTED; + } return 0; } @@ -68,6 +78,16 @@ static int early_data_send_params(gnutls_session_t session, gnutls_buffer_st * extdata) { + if (session->security_parameters.entity == GNUTLS_SERVER) { + if (session->internals.hsk_flags & HSK_EARLY_DATA_ACCEPTED) + return GNUTLS_E_INT_RET_0; + } else { + if (session->internals.early_data_presend_buffer.length > 0) { + session->internals.hsk_flags |= HSK_EARLY_DATA_IN_FLIGHT; + return GNUTLS_E_INT_RET_0; + } + } + return 0; } diff --git a/lib/gnutls_int.h b/lib/gnutls_int.h index 6fc3672f34..7a3ecee958 100644 --- a/lib/gnutls_int.h +++ b/lib/gnutls_int.h @@ -169,7 +169,8 @@ typedef enum hs_stage_t { STAGE_HS, STAGE_APP, STAGE_UPD_OURS, - STAGE_UPD_PEERS + STAGE_UPD_PEERS, + STAGE_EARLY } hs_stage_t; typedef enum record_send_state_t { @@ -272,7 +273,7 @@ typedef enum handshake_state_t { STATE0 = 0, STATE1, STATE2, STATE90=90, STATE91, STATE92, STATE93, STATE94, STATE99=99, STATE100=100, STATE101, STATE102, STATE103, STATE104, STATE105, STATE106, STATE107, STATE108, STATE109, STATE110, - STATE111, STATE112, STATE113, STATE114, + STATE111, STATE112, STATE113, STATE114, STATE115, STATE150 /* key update */ } handshake_state_t; @@ -538,6 +539,7 @@ struct gnutls_key_st { * early_secret, client_early_traffic_secret, ... */ uint8_t temp_secret[MAX_HASH_SIZE]; unsigned temp_secret_size; /* depends on negotiated PRF size */ + uint8_t e_ckey[MAX_HASH_SIZE]; /* client_early_traffic_secret */ uint8_t hs_ckey[MAX_HASH_SIZE]; /* client_hs_traffic_secret */ uint8_t hs_skey[MAX_HASH_SIZE]; /* server_hs_traffic_secret */ uint8_t ap_ckey[MAX_HASH_SIZE]; /* client_ap_traffic_secret */ @@ -1160,6 +1162,9 @@ typedef struct { * send. */ + mbuffer_head_st early_data_recv_buffer; + gnutls_buffer_st early_data_presend_buffer; + record_send_state_t rsend_state; /* buffer used temporarily during key update */ gnutls_buffer_st record_key_update_buffer; @@ -1342,8 +1347,13 @@ typedef struct { */ #define HSK_TICKET_RECEIVED (1<<20) /* client: a session ticket was received */ #define HSK_EARLY_START_USED (1<<21) -#define HSK_EARLY_DATA_IN_FLIGHT (1<<22) /* server: early_data extension was seen in ClientHello */ -#define HSK_RECORD_SIZE_LIMIT_NEGOTIATED (1<<23) +#define HSK_EARLY_DATA_IN_FLIGHT (1<<22) /* client: sent early_data extension in ClientHello + * server: early_data extension was seen in ClientHello + */ +#define HSK_EARLY_DATA_ACCEPTED (1<<23) /* client: early_data extension was seen in EncryptedExtensions + * server: intend to process early data + */ +#define HSK_RECORD_SIZE_LIMIT_NEGOTIATED (1<<24) /* The hsk_flags are for use within the ongoing handshake; * they are reset to zero prior to handshake start by gnutls_handshake. */ diff --git a/lib/handshake-tls13.c b/lib/handshake-tls13.c index 5a1cb0eeda..fedef6cbbc 100644 --- a/lib/handshake-tls13.c +++ b/lib/handshake-tls13.c @@ -52,12 +52,13 @@ #include "tls13/certificate_request.h" #include "tls13/certificate_verify.h" #include "tls13/certificate.h" +#include "tls13/early_data.h" #include "tls13/finished.h" #include "tls13/key_update.h" #include "ext/pre_shared_key.h" static int generate_rms_keys(gnutls_session_t session); -static int generate_and_set_hs_traffic_keys(gnutls_session_t session); +static int generate_hs_traffic_keys(gnutls_session_t session); static int generate_ap_traffic_keys(gnutls_session_t session); #define SAVE_TRANSCRIPT \ @@ -90,59 +91,99 @@ int _gnutls13_handshake_client(gnutls_session_t session) #endif FALLTHROUGH; case STATE101: - ret = - generate_and_set_hs_traffic_keys(session); - STATE = STATE101; - IMED_RET_FATAL("generate session keys", ret, 0); + /* Note that we check IN_FLIGHT, not ACCEPTED + * here. This is because the client sends early data + * speculatively. */ + if (session->internals.hsk_flags & HSK_EARLY_DATA_IN_FLIGHT) { + ret = _tls13_write_connection_state_init(session, STAGE_EARLY); + if (ret == 0) { + _gnutls_epoch_bump(session); + ret = _gnutls_epoch_dup(session, EPOCH_WRITE_CURRENT); + } + STATE = STATE101; + IMED_RET_FATAL("set early traffic keys", ret, 0); + } FALLTHROUGH; case STATE102: - ret = _gnutls13_recv_encrypted_extensions(session); + ret = _gnutls13_send_early_data(session); STATE = STATE102; - IMED_RET("recv encrypted extensions", ret, 0); + IMED_RET("send early data", ret, 0); FALLTHROUGH; case STATE103: - ret = _gnutls13_recv_certificate_request(session); STATE = STATE103; - IMED_RET("recv certificate request", ret, 0); + ret = generate_hs_traffic_keys(session); + /* Note that we check IN_FLIGHT, not ACCEPTED + * here. This is because the client sends early data + * speculatively. */ + IMED_RET_FATAL("generate hs traffic keys", ret, 0); + if (session->internals.hsk_flags & HSK_EARLY_DATA_IN_FLIGHT) + ret = _tls13_read_connection_state_init(session, STAGE_HS); + else + ret = _tls13_connection_state_init(session, STAGE_HS); + IMED_RET_FATAL("set hs traffic keys", ret, 0); FALLTHROUGH; case STATE104: - ret = _gnutls13_recv_certificate(session); + ret = _gnutls13_recv_encrypted_extensions(session); STATE = STATE104; - IMED_RET("recv certificate", ret, 0); + IMED_RET("recv encrypted extensions", ret, 0); FALLTHROUGH; case STATE105: - ret = _gnutls13_recv_certificate_verify(session); + ret = _gnutls13_recv_certificate_request(session); STATE = STATE105; - IMED_RET("recv server certificate verify", ret, 0); + IMED_RET("recv certificate request", ret, 0); FALLTHROUGH; case STATE106: - ret = _gnutls_run_verify_callback(session, GNUTLS_CLIENT); + ret = _gnutls13_recv_certificate(session); STATE = STATE106; - if (ret < 0) - return gnutls_assert_val(ret); + IMED_RET("recv certificate", ret, 0); FALLTHROUGH; case STATE107: - ret = _gnutls13_recv_finished(session); + ret = _gnutls13_recv_certificate_verify(session); STATE = STATE107; - IMED_RET("recv finished", ret, 0); + IMED_RET("recv server certificate verify", ret, 0); FALLTHROUGH; case STATE108: - ret = _gnutls13_send_certificate(session, AGAIN(STATE108)); + ret = _gnutls_run_verify_callback(session, GNUTLS_CLIENT); STATE = STATE108; - IMED_RET("send certificate", ret, 0); + if (ret < 0) + return gnutls_assert_val(ret); FALLTHROUGH; case STATE109: - ret = _gnutls13_send_certificate_verify(session, AGAIN(STATE109)); + ret = _gnutls13_recv_finished(session); STATE = STATE109; - IMED_RET("send certificate verify", ret, 0); + IMED_RET("recv finished", ret, 0); FALLTHROUGH; case STATE110: - ret = _gnutls13_send_finished(session, AGAIN(STATE110)); + ret = _gnutls13_send_end_of_early_data(session, AGAIN(STATE110)); STATE = STATE110; - IMED_RET("send finished", ret, 0); + IMED_RET("send end of early data", ret, 0); + + /* Note that we check IN_FLIGHT, not ACCEPTED + * here. This is because the client sends early data + * speculatively. */ + if (session->internals.hsk_flags & HSK_EARLY_DATA_IN_FLIGHT) { + session->internals.hsk_flags &= ~HSK_EARLY_DATA_IN_FLIGHT; + ret = _tls13_write_connection_state_init(session, STAGE_HS); + IMED_RET_FATAL("set hs traffic key after sending early data", ret, 0); + } FALLTHROUGH; case STATE111: + ret = _gnutls13_send_certificate(session, AGAIN(STATE111)); STATE = STATE111; + IMED_RET("send certificate", ret, 0); + FALLTHROUGH; + case STATE112: + ret = _gnutls13_send_certificate_verify(session, AGAIN(STATE112)); + STATE = STATE112; + IMED_RET("send certificate verify", ret, 0); + FALLTHROUGH; + case STATE113: + ret = _gnutls13_send_finished(session, AGAIN(STATE113)); + STATE = STATE113; + IMED_RET("send finished", ret, 0); + FALLTHROUGH; + case STATE114: + STATE = STATE114; ret = generate_ap_traffic_keys(session); @@ -262,7 +303,7 @@ static int generate_ap_traffic_keys(gnutls_session_t session) return 0; } -static int generate_and_set_hs_traffic_keys(gnutls_session_t session) +static int generate_hs_traffic_keys(gnutls_session_t session) { int ret; unsigned null_key = 0; @@ -317,12 +358,6 @@ static int generate_and_set_hs_traffic_keys(gnutls_session_t session) } } - ret = _tls13_connection_state_init(session, STAGE_HS); - if (ret < 0) { - gnutls_assert(); - return ret; - } - return 0; } @@ -388,10 +423,26 @@ int _gnutls13_handshake_server(gnutls_session_t session) #endif FALLTHROUGH; case STATE101: - ret = - generate_and_set_hs_traffic_keys(session); STATE = STATE101; - IMED_RET_FATAL("generate session keys", ret, 0); + if (session->internals.hsk_flags & HSK_EARLY_DATA_ACCEPTED) { + ret = _tls13_read_connection_state_init(session, STAGE_EARLY); + if (ret == 0) { + _gnutls_epoch_bump(session); + ret = _gnutls_epoch_dup(session, EPOCH_READ_CURRENT); + } + IMED_RET_FATAL("set early traffic keys", ret, 0); + + ret = generate_hs_traffic_keys(session); + IMED_RET_FATAL("generate hs traffic keys", ret, 0); + + ret = _tls13_write_connection_state_init(session, STAGE_HS); + } else { + ret = generate_hs_traffic_keys(session); + IMED_RET_FATAL("generate hs traffic keys", ret, 0); + + ret = _tls13_connection_state_init(session, STAGE_HS); + } + IMED_RET_FATAL("set hs traffic keys", ret, 0); FALLTHROUGH; case STATE102: ret = _gnutls13_send_encrypted_extensions(session, AGAIN(STATE102)); @@ -419,6 +470,16 @@ int _gnutls13_handshake_server(gnutls_session_t session) IMED_RET("send finished", ret, 0); FALLTHROUGH; case STATE107: + ret = _gnutls13_recv_end_of_early_data(session); + STATE = STATE107; + IMED_RET("recv end of early data", ret, 0); + + if (session->internals.hsk_flags & HSK_EARLY_DATA_ACCEPTED) { + ret = _tls13_read_connection_state_init(session, STAGE_HS); + IMED_RET_FATAL("set hs traffic key after receiving early data", ret, 0); + } + FALLTHROUGH; + case STATE108: /* At this point our sending keys should be the app keys * see 4.4.4 at draft-ietf-tls-tls13-28 */ ret = @@ -428,7 +489,7 @@ int _gnutls13_handshake_server(gnutls_session_t session) /* If the session is unauthenticated, try to optimize the handshake by * sending the session ticket early. */ if (!(session->internals.hsk_flags & (HSK_CRT_REQ_SENT|HSK_PSK_SELECTED))) { - STATE = STATE107; + STATE = STATE108; ret = generate_non_auth_rms_keys(session); IMED_RET_FATAL("generate rms keys", ret, 0); @@ -443,15 +504,15 @@ int _gnutls13_handshake_server(gnutls_session_t session) _gnutls_handshake_log("HSK[%p]: switching early to application traffic keys\n", session); FALLTHROUGH; - case STATE108: + case STATE109: if (session->internals.resumed != RESUME_FALSE) _gnutls_set_resumed_parameters(session); if (session->internals.hsk_flags & HSK_EARLY_START_USED) { ret = _gnutls13_send_session_ticket(session, TICKETS_TO_SEND, - AGAIN(STATE108)); + AGAIN(STATE109)); - STATE = STATE108; + STATE = STATE109; IMED_RET("send session ticket", ret, 0); /* complete this phase of the handshake. We @@ -459,7 +520,7 @@ int _gnutls13_handshake_server(gnutls_session_t session) */ if (session->internals.flags & GNUTLS_ENABLE_EARLY_START) { - STATE = STATE112; /* finished */ + STATE = STATE113; /* finished */ gnutls_assert(); session->internals.recv_state = RECV_STATE_EARLY_START; @@ -467,31 +528,31 @@ int _gnutls13_handshake_server(gnutls_session_t session) } } FALLTHROUGH; - case STATE109: + case STATE110: ret = _gnutls13_recv_certificate(session); - STATE = STATE109; + STATE = STATE110; IMED_RET("recv certificate", ret, 0); FALLTHROUGH; - case STATE110: + case STATE111: ret = _gnutls13_recv_certificate_verify(session); - STATE = STATE110; + STATE = STATE111; IMED_RET("recv certificate verify", ret, 0); FALLTHROUGH; - case STATE111: + case STATE112: ret = _gnutls_run_verify_callback(session, GNUTLS_CLIENT); - STATE = STATE111; + STATE = STATE112; if (ret < 0) return gnutls_assert_val(ret); FALLTHROUGH; - case STATE112: /* can enter from STATE108 */ + case STATE113: /* can enter from STATE109 */ ret = _gnutls13_recv_finished(session); - STATE = STATE112; + STATE = STATE113; IMED_RET("recv finished", ret, 0); FALLTHROUGH; - case STATE113: + case STATE114: /* If we did request a client certificate, then we can * only send the tickets here */ - STATE = STATE113; + STATE = STATE114; if (!(session->internals.hsk_flags & HSK_EARLY_START_USED)) { ret = generate_rms_keys(session); @@ -502,11 +563,11 @@ int _gnutls13_handshake_server(gnutls_session_t session) IMED_RET_FATAL("set read app keys", ret, 0); FALLTHROUGH; - case STATE114: + case STATE115: if (!(session->internals.hsk_flags & (HSK_TLS13_TICKET_SENT|HSK_EARLY_START_USED))) { ret = _gnutls13_send_session_ticket(session, TICKETS_TO_SEND, - AGAIN(STATE114)); - STATE = STATE114; + AGAIN(STATE115)); + STATE = STATE115; IMED_RET("send session ticket", ret, 0); } diff --git a/lib/handshake.c b/lib/handshake.c index f0ed91f976..5080756c28 100644 --- a/lib/handshake.c +++ b/lib/handshake.c @@ -1620,6 +1620,7 @@ _gnutls_recv_handshake(gnutls_session_t session, case GNUTLS_HANDSHAKE_CERTIFICATE_VERIFY: case GNUTLS_HANDSHAKE_SUPPLEMENTAL: case GNUTLS_HANDSHAKE_NEW_SESSION_TICKET: + case GNUTLS_HANDSHAKE_END_OF_EARLY_DATA: ret = hsk.data.length; break; default: @@ -1783,6 +1784,26 @@ no_resume: } +static int generate_early_traffic_secret(gnutls_session_t session, + const mac_entry_st *prf) +{ + int ret; + + ret = _tls13_derive_secret2(prf, EARLY_TRAFFIC_LABEL, sizeof(EARLY_TRAFFIC_LABEL)-1, + session->internals.handshake_hash_buffer.data, + session->internals.handshake_hash_buffer_client_hello_len, + session->key.proto.tls13.temp_secret, + session->key.proto.tls13.e_ckey); + if (ret < 0) + return gnutls_assert_val(ret); + + _gnutls_nss_keylog_write(session, "CLIENT_EARLY_TRAFFIC_SECRET", + session->key.proto.tls13.e_ckey, + prf->output_size); + + return 0; +} + /* This function reads and parses the server hello handshake message. * This function also restores resumed parameters if we are resuming a * session. @@ -1971,7 +1992,8 @@ read_server_hello(gnutls_session_t session, return gnutls_assert_val(ret); /* Calculate TLS 1.3 Early Secret */ - if (vers->tls13_sem) { + if (vers->tls13_sem && + !(session->internals.hsk_flags & HSK_EARLY_DATA_IN_FLIGHT)) { if (session->internals.hsk_flags & HSK_PSK_SELECTED) { psk = session->key.binders[0].psk.data; psk_size = session->key.binders[0].psk.size; @@ -1983,7 +2005,7 @@ read_server_hello(gnutls_session_t session, ret = _tls13_init_secret(session, psk, psk_size); if (ret < 0) { gnutls_assert(); - goto cleanup; + return ret; } } @@ -2019,6 +2041,56 @@ append_null_comp(gnutls_session_t session, return ret; } +/* Calculate TLS 1.3 Early Secret and client_early_traffic_secret, + * assuming that the PSK we offer will be accepted by the server */ +static int +generate_early_traffic_secret_from_ticket(gnutls_session_t session) +{ + int ret = 0; + const uint8_t *psk; + size_t psk_size; + const mac_entry_st *prf; + + if (!(session->internals.hsk_flags & HSK_TLS13_TICKET_SENT)) { + ret = GNUTLS_E_INVALID_REQUEST; + gnutls_assert(); + goto cleanup; + } + + psk = session->key.binders[0].psk.data; + psk_size = session->key.binders[0].psk.size; + prf = session->key.binders[0].prf; + + if (psk_size == 0) { + ret = GNUTLS_E_INVALID_REQUEST; + gnutls_assert(); + goto cleanup; + } + + ret = _tls13_init_secret2(prf, psk, psk_size, + session->key.proto.tls13.temp_secret); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + session->key.proto.tls13.temp_secret_size = prf->output_size; + + ret = generate_early_traffic_secret(session, prf); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + return ret; + + cleanup: + /* If any of the above calculation fails, we are not going to + * send early data. */ + session->internals.hsk_flags &= ~HSK_EARLY_DATA_IN_FLIGHT; + + return ret; +} + /* This function sends the client hello handshake message. */ static int send_client_hello(gnutls_session_t session, int again) @@ -2230,9 +2302,10 @@ static int send_client_hello(gnutls_session_t session, int again) bufel = _gnutls_buffer_to_mbuffer(&extdata); } - return - _gnutls_send_handshake(session, bufel, - GNUTLS_HANDSHAKE_CLIENT_HELLO); + ret = _gnutls_send_handshake(session, bufel, + GNUTLS_HANDSHAKE_CLIENT_HELLO); + + return ret; cleanup: _gnutls_buffer_clear(&extdata); @@ -2252,6 +2325,7 @@ int _gnutls_send_server_hello(gnutls_session_t session, int again) unsigned extflag = 0; const uint8_t *psk = NULL; size_t psk_size = 0; + const mac_entry_st *prf = session->security_parameters.prf; gnutls_ext_parse_type_t etype; _gnutls_buffer_init(&buf); @@ -2266,6 +2340,7 @@ int _gnutls_send_server_hello(gnutls_session_t session, int again) if (session->internals.hsk_flags & HSK_PSK_SELECTED) { psk = session->key.binders[0].psk.data; psk_size = session->key.binders[0].psk.size; + prf = session->key.binders[0].prf; } ret = _tls13_init_secret(session, psk, psk_size); @@ -2274,6 +2349,12 @@ int _gnutls_send_server_hello(gnutls_session_t session, int again) goto fail; } + ret = generate_early_traffic_secret(session, prf); + if (ret < 0) { + gnutls_assert(); + goto fail; + } + vbytes[0] = 0x03; /* TLS1.2 */ vbytes[1] = 0x03; extflag |= GNUTLS_EXT_FLAG_TLS13_SERVER_HELLO; @@ -2714,6 +2795,8 @@ int gnutls_handshake(gnutls_session_t session) _gnutls_handshake_internal_state_clear(session); + _gnutls_buffer_clear(&session->internals.record_presend_buffer); + _gnutls_epoch_bump(session); } @@ -2841,6 +2924,10 @@ static int handshake_client(gnutls_session_t session) ret = send_client_hello(session, AGAIN(STATE1)); STATE = STATE1; IMED_RET("send hello", ret, 1); + if (session->internals.hsk_flags & HSK_EARLY_DATA_IN_FLIGHT) { + ret = generate_early_traffic_secret_from_ticket(session); + IMED_RET_FATAL("generate early traffic keys from ticket", ret, 0); + } FALLTHROUGH; case STATE2: if (IS_DTLS(session)) { diff --git a/lib/includes/gnutls/gnutls.h.in b/lib/includes/gnutls/gnutls.h.in index 5dcbc1c986..b838ee55dd 100644 --- a/lib/includes/gnutls/gnutls.h.in +++ b/lib/includes/gnutls/gnutls.h.in @@ -399,6 +399,7 @@ typedef enum { * finish; similarly to false start the handshake will be completed once data are received by the * client, while the server is able to transmit sooner. This is not enabled by default as it could * break certain existing server assumptions and use-cases. Since 3.6.4. + * @GNUTLS_ENABLE_EARLY_DATA: Under TLS1.3 allow the server to receive early data sent as part of the initial ClientHello (0-RTT). This is not enabled by default as early data has weaker security properties than other data. Since 3.6.5. * @GNUTLS_FORCE_CLIENT_CERT: When in client side and only a single cert is specified, send that certificate irrespective of the issuers expected by the server. Since 3.5.0. * @GNUTLS_NO_TICKETS: Flag to indicate that the session should not use resumption with session tickets. * @GNUTLS_KEY_SHARE_TOP3: Generate key shares for the top-3 different groups which are enabled. @@ -456,7 +457,8 @@ typedef enum { GNUTLS_SAFE_PADDING_CHECK = (1<<16), GNUTLS_ENABLE_EARLY_START = (1<<17), GNUTLS_ENABLE_CERT_TYPE_NEG = (1<<18), - GNUTLS_AUTO_REAUTH = (1<<19) + GNUTLS_AUTO_REAUTH = (1<<19), + GNUTLS_ENABLE_EARLY_DATA = (1<<20) } gnutls_init_flags_t; /* compatibility defines (previous versions of gnutls @@ -1502,6 +1504,7 @@ unsigned gnutls_session_etm_status(gnutls_session_t session); * @GNUTLS_SFLAGS_SESSION_TICKET: A session ticket has been received by the server. * @GNUTLS_SFLAGS_POST_HANDSHAKE_AUTH: Indicates client capability for post-handshake auth; set only on server side. * @GNUTLS_SFLAGS_EARLY_START: The TLS1.3 server session returned early. + * @GNUTLS_SFLAGS_EARLY_DATA: The TLS1.3 early data has been received by the server. * * Enumeration of different session parameters. */ @@ -1515,7 +1518,8 @@ typedef enum { GNUTLS_SFLAGS_RFC7919 = 1<<6, GNUTLS_SFLAGS_SESSION_TICKET = 1<<7, GNUTLS_SFLAGS_POST_HANDSHAKE_AUTH = 1<<8, - GNUTLS_SFLAGS_EARLY_START = 1<<9 + GNUTLS_SFLAGS_EARLY_START = 1<<9, + GNUTLS_SFLAGS_EARLY_DATA = 1<<10 } gnutls_session_flags_t; unsigned gnutls_session_get_flags(gnutls_session_t session); diff --git a/lib/record.c b/lib/record.c index 9372e8ae03..7dc4f23348 100644 --- a/lib/record.c +++ b/lib/record.c @@ -1370,42 +1370,89 @@ _gnutls_recv_in_buffers(gnutls_session_t session, content_type_t type, _mbuffer_head_remove_bytes(&session->internals.record_recv_buffer, record.header_size + record.length); - /* FIXME: as 0-RTT is not implemented yet, when early data is - * indicated, skip decryption failure up to - * max_early_data_size. Otherwise, if the record is properly - * decrypted, treat it as the start of client's second flight. - * - * This implements the first way suggested in 4.2.10 of - * draft-ietf-tls-tls13-28. - */ - if (unlikely(session->internals.hsk_flags & HSK_EARLY_DATA_IN_FLIGHT)) { - if (record.type == GNUTLS_APPLICATION_DATA && - (ret < 0 || - /* early data must always be encrypted, treat it - * as decryption failure if otherwise */ - record_params->cipher->id == GNUTLS_CIPHER_NULL)) { - if (record.length > - session->security_parameters.max_early_data_size - - session->internals.early_data_received) { + if (session->security_parameters.entity == GNUTLS_SERVER && + session->internals.hsk_flags & HSK_EARLY_DATA_IN_FLIGHT) { + if (session->internals.hsk_flags & HSK_EARLY_DATA_ACCEPTED) { + if (ret < 0 || + /* early data must always be encrypted, treat it + * as decryption failure if otherwise */ + record_params->cipher->id == GNUTLS_CIPHER_NULL) { _gnutls_record_log - ("REC[%p]: max_early_data_size exceeded\n", - session); - ret = GNUTLS_E_UNEXPECTED_PACKET; + ("REC[%p]: failed to decrypt early data, in epoch %d\n", + session, + record_params->epoch); + ret = GNUTLS_E_DECRYPTION_FAILED; goto sanity_check_error; - } + } else if (record.type == GNUTLS_APPLICATION_DATA) { + size_t decrypted_length = + _mbuffer_get_udata_size(decrypted); + _gnutls_record_log + ("REC[%p]: decrypted early data with length: %d, in epoch %d\n", + session, + (int) decrypted_length, + record_params->epoch); + if (decrypted_length > + session->security_parameters.max_early_data_size - + session->internals.early_data_received) { + _gnutls_record_log + ("REC[%p]: max_early_data_size exceeded\n", + session); + ret = GNUTLS_E_UNEXPECTED_PACKET; + goto sanity_check_error; + } + + _mbuffer_enqueue(&session->internals.early_data_recv_buffer, decrypted); + session->internals.early_data_received += + decrypted_length; + + /* Increase sequence number. We do both for TLS and DTLS, since in + * DTLS we also rely on that number (roughly) since it may get reported + * to application via gnutls_record_get_state(). + */ + if (sequence_increment(session, &record_state->sequence_number) != 0) { + session_invalidate(session); + gnutls_assert(); + ret = GNUTLS_E_RECORD_LIMIT_REACHED; + goto sanity_check_error; + } - _gnutls_record_log("REC[%p]: Discarded early data[%u] due to invalid decryption, length: %u\n", - session, - (unsigned int) - _gnutls_uint64touint32(packet_sequence), - (unsigned int) - record.length); - session->internals.early_data_received += record.length; - /* silently discard received data */ - _mbuffer_xfree(&decrypted); - return gnutls_assert_val(GNUTLS_E_AGAIN); + /* decrypted is now accounted */ + return GNUTLS_E_AGAIN; + } } else { - session->internals.hsk_flags &= ~HSK_EARLY_DATA_IN_FLIGHT; + /* We do not accept early data: skip decryption + * failure up to max_early_data_size. Otherwise, + * if the record is properly decrypted, treat it as + * the start of client's second flight. + */ + if (record.type == GNUTLS_APPLICATION_DATA && + (ret < 0 || + /* early data must always be encrypted, treat it + * as decryption failure if otherwise */ + record_params->cipher->id == GNUTLS_CIPHER_NULL)) { + if (record.length > + session->security_parameters.max_early_data_size - + session->internals.early_data_received) { + _gnutls_record_log + ("REC[%p]: max_early_data_size exceeded\n", + session); + ret = GNUTLS_E_UNEXPECTED_PACKET; + goto sanity_check_error; + } + + _gnutls_record_log("REC[%p]: Discarded early data[%u] due to invalid decryption, length: %u\n", + session, + (unsigned int) + _gnutls_uint64touint32(packet_sequence), + (unsigned int) + record.length); + session->internals.early_data_received += record.length; + /* silently discard received data */ + _mbuffer_xfree(&decrypted); + return gnutls_assert_val(GNUTLS_E_AGAIN); + } else { + session->internals.hsk_flags &= ~HSK_EARLY_DATA_IN_FLIGHT; + } } } @@ -1926,7 +1973,8 @@ gnutls_record_send2(gnutls_session_t session, const void *data, * data. We allow sending however, if we are in false start handshake * state. */ if (session->internals.recv_state != RECV_STATE_FALSE_START && - session->internals.recv_state != RECV_STATE_EARLY_START) + session->internals.recv_state != RECV_STATE_EARLY_START && + !(session->internals.hsk_flags & HSK_EARLY_DATA_IN_FLIGHT)) return gnutls_assert_val(GNUTLS_E_UNAVAILABLE_DURING_HANDSHAKE); } diff --git a/lib/state.c b/lib/state.c index 303a3ad2f8..01288ad474 100644 --- a/lib/state.c +++ b/lib/state.c @@ -485,6 +485,8 @@ int gnutls_init(gnutls_session_t * session, unsigned int flags) _mbuffer_head_init(&(*session)->internals.record_buffer); _mbuffer_head_init(&(*session)->internals.record_send_buffer); _mbuffer_head_init(&(*session)->internals.record_recv_buffer); + _mbuffer_head_init(&(*session)->internals.early_data_recv_buffer); + _gnutls_buffer_init(&(*session)->internals.early_data_presend_buffer); _mbuffer_head_init(&(*session)->internals.handshake_send_buffer); _gnutls_handshake_recv_buffer_init(*session); @@ -620,6 +622,9 @@ void gnutls_deinit(gnutls_session_t session) _mbuffer_head_clear(&session->internals.record_recv_buffer); _mbuffer_head_clear(&session->internals.record_send_buffer); + _mbuffer_head_clear(&session->internals.early_data_recv_buffer); + _gnutls_buffer_clear(&session->internals.early_data_presend_buffer); + _gnutls_free_datum(&session->internals.resumption_data); _gnutls_free_datum(&session->internals.dtls.dcookie); @@ -1542,6 +1547,8 @@ unsigned gnutls_session_get_flags(gnutls_session_t session) flags |= GNUTLS_SFLAGS_SESSION_TICKET; if (session->security_parameters.post_handshake_auth) flags |= GNUTLS_SFLAGS_POST_HANDSHAKE_AUTH; + if (session->internals.hsk_flags & HSK_EARLY_DATA_ACCEPTED) + flags |= GNUTLS_SFLAGS_EARLY_DATA; return flags; } diff --git a/lib/tls13/early_data.c b/lib/tls13/early_data.c new file mode 100644 index 0000000000..dd977fc410 --- /dev/null +++ b/lib/tls13/early_data.c @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2017 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/> + * + */ + +#include "gnutls_int.h" +#include "handshake.h" +#include "tls13/early_data.h" + +int _gnutls13_send_early_data(gnutls_session_t session) +{ + int ret; + + if (!(session->security_parameters.entity == GNUTLS_CLIENT && + session->internals.hsk_flags & HSK_EARLY_DATA_IN_FLIGHT)) + return 0; + + while (session->internals.early_data_presend_buffer.length > 0) { + ret = + gnutls_record_send(session, + session->internals. + early_data_presend_buffer.data, + session->internals. + early_data_presend_buffer. + length); + if (ret < 0) + return gnutls_assert_val(ret); + + session->internals.early_data_presend_buffer.data += ret; + session->internals.early_data_presend_buffer.length -= ret; + } + + + return 0; +} + +int _gnutls13_send_end_of_early_data(gnutls_session_t session, unsigned again) +{ + int ret; + mbuffer_st *bufel = NULL; + gnutls_buffer_st buf; + + if (!(session->security_parameters.entity == GNUTLS_CLIENT && + session->internals.hsk_flags & HSK_EARLY_DATA_ACCEPTED)) + return 0; + + if (again == 0) { + ret = _gnutls_buffer_init_handshake_mbuffer(&buf); + if (ret < 0) + return gnutls_assert_val(ret); + + bufel = _gnutls_buffer_to_mbuffer(&buf); + } + + return _gnutls_send_handshake(session, bufel, GNUTLS_HANDSHAKE_END_OF_EARLY_DATA); +} + +int _gnutls13_recv_end_of_early_data(gnutls_session_t session) +{ + int ret; + gnutls_buffer_st buf; + + if (!(session->security_parameters.entity == GNUTLS_SERVER && + session->internals.hsk_flags & HSK_EARLY_DATA_ACCEPTED)) + return 0; + + ret = _gnutls_recv_handshake(session, GNUTLS_HANDSHAKE_END_OF_EARLY_DATA, 0, &buf); + if (ret < 0) + return gnutls_assert_val(ret); + + if (buf.length != 0) { + gnutls_assert(); + ret = GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER; + goto cleanup; + } + + session->internals.hsk_flags &= ~HSK_EARLY_DATA_IN_FLIGHT; + + ret = 0; +cleanup: + + _gnutls_buffer_clear(&buf); + return ret; +} diff --git a/lib/tls13/early_data.h b/lib/tls13/early_data.h new file mode 100644 index 0000000000..ddbd983293 --- /dev/null +++ b/lib/tls13/early_data.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2017 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/> + * + */ + +int _gnutls13_send_end_of_early_data(gnutls_session_t session, unsigned again); +int _gnutls13_recv_end_of_early_data(gnutls_session_t session); +int _gnutls13_send_early_data(gnutls_session_t session); diff --git a/lib/tls13/session_ticket.c b/lib/tls13/session_ticket.c index ad04a60919..b34de41029 100644 --- a/lib/tls13/session_ticket.c +++ b/lib/tls13/session_ticket.c @@ -229,6 +229,23 @@ generate_session_ticket(gnutls_session_t session, tls13_ticket_st *ticket) return 0; } +static int append_nst_extension(void *ctx, gnutls_buffer_st *buf) +{ + gnutls_session_t session = ctx; + int ret; + + if (!(session->internals.flags & GNUTLS_ENABLE_EARLY_DATA)) + return 0; + + ret = _gnutls_buffer_append_prefix(buf, 32, + session->security_parameters. + max_early_data_size); + if (ret < 0) + gnutls_assert(); + + return ret; +} + int _gnutls13_send_session_ticket(gnutls_session_t session, unsigned nr, unsigned again) { int ret = 0; @@ -253,6 +270,8 @@ int _gnutls13_send_session_ticket(gnutls_session_t session, unsigned nr, unsigne if (again == 0) { for (i=0;i<nr;i++) { + unsigned init_pos; + memset(&ticket, 0, sizeof(tls13_ticket_st)); bufel = NULL; @@ -296,13 +315,28 @@ int _gnutls13_send_session_ticket(gnutls_session_t session, unsigned nr, unsigne goto cleanup; } - ret = _gnutls_buffer_append_prefix(&buf, 16, 0); + _gnutls_free_datum(&ticket.ticket); + + /* append extensions */ + ret = _gnutls_extv_append_init(&buf); if (ret < 0) { gnutls_assert(); goto cleanup; } + init_pos = ret; - _gnutls_free_datum(&ticket.ticket); + ret = _gnutls_extv_append(&buf, ext_mod_early_data.tls_id, session, + (extv_append_func)append_nst_extension); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + ret = _gnutls_extv_append_final(&buf, init_pos); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } bufel = _gnutls_buffer_to_mbuffer(&buf); @@ -337,7 +371,8 @@ static int parse_nst_extension(void *ctx, unsigned tls_id, const unsigned char * if (data_size < 4) return gnutls_assert_val(GNUTLS_E_TLS_PACKET_DECODING_ERROR); size = _gnutls_read_uint32(data); - session->security_parameters.max_early_data_size = size; + if (size < session->security_parameters.max_early_data_size) + session->security_parameters.max_early_data_size = size; } return 0; } |