diff options
Diffstat (limited to 'lib/ext/pre_shared_key.c')
-rw-r--r-- | lib/ext/pre_shared_key.c | 352 |
1 files changed, 325 insertions, 27 deletions
diff --git a/lib/ext/pre_shared_key.c b/lib/ext/pre_shared_key.c index 44797bb5ad..f632f972bc 100644 --- a/lib/ext/pre_shared_key.c +++ b/lib/ext/pre_shared_key.c @@ -35,6 +35,14 @@ #include <ext/pre_shared_key.h> #include <assert.h> +inline static bool +have_psk_credentials(const gnutls_psk_client_credentials_t cred, + gnutls_session_t session) +{ + return (cred->get_function || cred->username.data) && + session->internals.priorities->have_psk; +} + static int compute_psk_from_ticket(const tls13_ticket_st *ticket, gnutls_datum_t *key) { @@ -61,16 +69,45 @@ static int compute_psk_from_ticket(const tls13_ticket_st *ticket, return ret; } +enum binder_type { + BINDER_EXT, + BINDER_RES, + BINDER_IMP +}; + +static const char *get_binder_label(enum binder_type type, size_t *size) +{ + static const char ext_label[] = EXT_BINDER_LABEL; + static const char res_label[] = RES_BINDER_LABEL; + static const char imp_label[] = IMP_BINDER_LABEL; + const char *label; + + switch (type) { + case BINDER_EXT: + label = ext_label; + *size = sizeof(ext_label) - 1; + break; + case BINDER_RES: + label = res_label; + *size = sizeof(res_label) - 1; + break; + case BINDER_IMP: + label = imp_label; + *size = sizeof(imp_label) - 1; + break; + default: + assert(0); + } + + return label; +} + static int compute_binder_key(const mac_entry_st *prf, const uint8_t *key, - size_t keylen, bool resuming, void *out) + size_t keylen, enum binder_type type, void *out) { int ret; - const char ext_label[] = EXT_BINDER_LABEL; - const size_t ext_label_len = sizeof(ext_label) - 1; - const char res_label[] = RES_BINDER_LABEL; - const size_t res_label_len = sizeof(res_label) - 1; - const char *label = resuming ? res_label : ext_label; - size_t label_len = resuming ? res_label_len : ext_label_len; + size_t label_len; + const char *label = get_binder_label(type, &label_len); uint8_t tmp_key[MAX_HASH_SIZE]; /* Compute HKDF-Extract(0, psk) */ @@ -90,8 +127,8 @@ static int compute_binder_key(const mac_entry_st *prf, const uint8_t *key, static int compute_psk_binder(gnutls_session_t session, const mac_entry_st *prf, unsigned binders_length, int exts_length, int ext_offset, const gnutls_datum_t *psk, - const gnutls_datum_t *client_hello, bool resuming, - void *out) + const gnutls_datum_t *client_hello, + enum binder_type type, void *out) { int ret; unsigned client_hello_pos, extensions_len_pos; @@ -176,8 +213,7 @@ static int compute_psk_binder(gnutls_session_t session, const mac_entry_st *prf, } } - ret = compute_binder_key(prf, psk->data, psk->size, resuming, - binder_key); + ret = compute_binder_key(prf, psk->data, psk->size, type, binder_key); if (ret < 0) { gnutls_assert(); goto error; @@ -264,6 +300,172 @@ int _gnutls_generate_early_secrets_for_psk(gnutls_session_t session) return 0; } +/** + * gnutls_psk_format_imported_identity: + * @identity: external identity + * @context: optional contextual information + * @version: protocol version to which the PSK is imported + * @hash: hash algorithm used for KDF + * @imported_identity: where the imported identity is stored + * + * This formats an external PSK identity @identity into an imported + * form, described in RFC 9258 as ImportedIdentity. + * + * Upon success, the data field of @imported_identity is allocated + * using gnutls_malloc() and the caller must free the memory after + * use. + * + * Returns: %GNUTLS_E_SUCCESS (0) on success, otherwise a negative error code. + * Since: 3.8.1 + */ +int gnutls_psk_format_imported_identity(const gnutls_datum_t *identity, + const gnutls_datum_t *context, + gnutls_protocol_t version, + gnutls_digest_algorithm_t hash, + gnutls_datum_t *imported_identity) +{ + gnutls_buffer_st buf; + const version_entry_st *ver = version_to_entry(version); + const mac_entry_st *prf = hash_to_entry(hash); + uint16_t target_protocol; + uint16_t target_kdf; + int ret; + + _gnutls_buffer_init(&buf); + + /* external_identity */ + ret = _gnutls_buffer_append_data_prefix(&buf, 16, identity->data, + identity->size); + if (ret < 0) { + goto error; + } + + /* context */ + ret = _gnutls_buffer_append_data_prefix(&buf, 16, context->data, + context->size); + if (ret < 0) { + goto error; + } + + /* target_protocol */ + target_protocol = ver->major << 8 | ver->minor; + ret = _gnutls_buffer_append_prefix(&buf, 16, target_protocol); + if (ret < 0) { + goto error; + } + + /* target_kdf */ + switch (prf->id) { + case GNUTLS_MAC_SHA256: + target_kdf = 0x0001; + break; + case GNUTLS_MAC_SHA384: + target_kdf = 0x0002; + break; + default: + ret = gnutls_assert_val(GNUTLS_E_UNKNOWN_HASH_ALGORITHM); + goto error; + } + ret = _gnutls_buffer_append_prefix(&buf, 16, target_kdf); + if (ret < 0) { + goto error; + } + + ret = _gnutls_buffer_to_datum(&buf, imported_identity, 0); + if (ret < 0) { + goto error; + } + return 0; + +error: + _gnutls_buffer_clear(&buf); + return ret; +} + +static int derive_ipsk(const mac_entry_st *prf, + const gnutls_datum_t *imported_identity, + const gnutls_datum_t *epsk, uint8_t ipsk[MAX_HASH_SIZE]) +{ + uint8_t epskx[MAX_HASH_SIZE]; + uint8_t hashed_identity[MAX_HASH_SIZE]; + int ret; + + /* epskx = HKDF-Extract(0, epsk) */ + ret = _tls13_init_secret2(prf, epsk->data, epsk->size, epskx); + if (ret < 0) { + return ret; + } + ret = gnutls_hash_fast((gnutls_digest_algorithm_t)prf->id, + imported_identity->data, imported_identity->size, + hashed_identity); + if (ret < 0) { + return ret; + } + /* ipskx = HKDF-Expand-Label(epskx, "derived psk", Hash(ImportedIdentity), L) */ + return _tls13_expand_secret2(prf, DERIVED_PSK_LABEL, + sizeof(DERIVED_PSK_LABEL) - 1, + hashed_identity, prf->output_size, epskx, + prf->output_size, ipsk); +} + +/* This does the opposite of gnutls_psk_format_imported_identity. + * Note that this does not allocate memory, and the data field of + * identity and context must not be freed. + */ +static int parse_imported_identity(const gnutls_datum_t *imported_identity, + gnutls_datum_t *identity, + gnutls_datum_t *context, + gnutls_protocol_t *version, + gnutls_digest_algorithm_t *hash) +{ + uint16_t target_protocol; + uint16_t target_kdf; + gnutls_buffer_st buf; + size_t size; + int ret; + + _gnutls_ro_buffer_from_datum(&buf, (gnutls_datum_t *)imported_identity); + + /* external_identity */ + ret = _gnutls_buffer_pop_datum_prefix16(&buf, identity); + if (ret < 0) { + return ret; + } + + /* context */ + ret = _gnutls_buffer_pop_datum_prefix16(&buf, context); + if (ret < 0) { + return ret; + } + + /* target_protocol */ + ret = _gnutls_buffer_pop_prefix16(&buf, &size, 0); + if (ret < 0) { + return ret; + } + target_protocol = size; + *version = _gnutls_version_get((target_protocol >> 8) & 0xFF, + target_protocol & 0xFF); + + /* target_kdf */ + ret = _gnutls_buffer_pop_prefix16(&buf, &size, 0); + if (ret < 0) { + return ret; + } + target_kdf = size; + switch (target_kdf) { + case 0x0001: + *hash = GNUTLS_DIG_SHA256; + break; + case 0x0002: + *hash = GNUTLS_DIG_SHA384; + break; + default: + return gnutls_assert_val(GNUTLS_E_UNKNOWN_HASH_ALGORITHM); + } + return 0; +} + static int client_send_params(gnutls_session_t session, gnutls_buffer_t extdata, const gnutls_psk_client_credentials_t cred) { @@ -282,11 +484,12 @@ static int client_send_params(gnutls_session_t session, gnutls_buffer_t extdata, psk_auth_info_t info = NULL; unsigned psk_id_len = 0; unsigned binders_len, binders_pos; + bool imported = false; tls13_ticket_st *ticket = &session->internals.tls13_ticket; if (((session->internals.flags & GNUTLS_NO_TICKETS) || session->internals.tls13_ticket.ticket.data == NULL) && - (!cred || !_gnutls_have_psk_credentials(cred, session))) { + (!cred || !have_psk_credentials(cred, session))) { return 0; } @@ -353,8 +556,9 @@ static int client_send_params(gnutls_session_t session, gnutls_buffer_t extdata, } ignore_ticket: - if (cred && _gnutls_have_psk_credentials(cred, session)) { + if (cred && have_psk_credentials(cred, session)) { gnutls_datum_t tkey; + gnutls_psk_key_flags flags; if (cred->binder_algo == NULL) { gnutls_assert(); @@ -366,7 +570,7 @@ ignore_ticket: prf_psk = cred->binder_algo; ret = _gnutls_find_psk_key(session, cred, &username, &tkey, - &free_username); + &flags, &free_username); if (ret < 0) { gnutls_assert(); goto cleanup; @@ -390,6 +594,50 @@ ignore_ticket: user_key.size = tkey.size; } + if (flags & GNUTLS_PSK_KEY_EXT) { + uint8_t ipsk[MAX_HASH_SIZE]; + gnutls_datum_t imported_identity = { NULL, 0 }; + gnutls_datum_t context = { NULL, 0 }; + gnutls_protocol_t version; + gnutls_digest_algorithm_t hash; + const version_entry_st *vers; + + ret = parse_imported_identity(&username, + &imported_identity, + &context, &version, + &hash); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + vers = version_to_entry(version); + if (unlikely(!vers || !vers->tls13_sem)) { + gnutls_assert(); + goto cleanup; + } + if (hash != MAC_TO_DIG(prf_psk->id)) { + gnutls_assert(); + goto cleanup; + } + + ret = derive_ipsk(prf_psk, &username, &user_key, ipsk); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + _gnutls_free_datum(&user_key); + ret = _gnutls_set_datum(&user_key, ipsk, + prf_psk->output_size); + zeroize_key(ipsk, sizeof(ipsk)); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + imported = true; + } + ret = _gnutls_auth_info_init(session, GNUTLS_CRD_PSK, sizeof(psk_auth_info_st), 1); if (ret < 0) { @@ -457,7 +705,8 @@ ignore_ticket: ret = compute_psk_binder(session, prf_res, binders_len, binders_pos, ext_offset, &rkey, - &client_hello, 1, binder_value); + &client_hello, BINDER_RES, + binder_value); if (ret < 0) { gnutls_assert(); goto cleanup; @@ -498,7 +747,9 @@ ignore_ticket: ret = compute_psk_binder(session, prf_psk, binders_len, binders_pos, ext_offset, &user_key, - &client_hello, 0, binder_value); + &client_hello, + imported ? BINDER_IMP : BINDER_EXT, + binder_value); if (ret < 0) { gnutls_assert(); goto cleanup; @@ -574,7 +825,7 @@ static int server_recv_params(gnutls_session_t session, /* These values should be set properly when session ticket is accepted. */ uint32_t ticket_age = UINT32_MAX; struct timespec ticket_creation_time = { 0, 0 }; - bool resuming; + enum binder_type binder_type; bool refuse_early_data = false; ret = _gnutls13_psk_ext_parser_init(&psk_parser, data, len); @@ -625,22 +876,69 @@ static int server_recv_params(gnutls_session_t session, tls13_ticket_deinit(&ticket_data); - resuming = 1; + binder_type = BINDER_RES; break; } else if (pskcred && psk.ob_ticket_age == 0 && psk.identity.size > 0 && psk.identity.size <= MAX_USERNAME_SIZE) { + gnutls_psk_key_flags flags; + uint8_t ipsk[MAX_HASH_SIZE]; + prf = pskcred->binder_algo; /* this fails only on configuration errors; as such we always * return its error code in that case */ ret = _gnutls_psk_pwd_find_entry( session, (char *)psk.identity.data, - psk.identity.size, &key); - if (ret < 0) + psk.identity.size, &key, &flags); + if (ret < 0) { return gnutls_assert_val(ret); + } + + if (flags & GNUTLS_PSK_KEY_EXT) { + gnutls_datum_t imported_identity = { NULL, 0 }; + gnutls_datum_t context = { NULL, 0 }; + gnutls_protocol_t version; + gnutls_digest_algorithm_t hash; + const version_entry_st *vers; - resuming = 0; + ret = parse_imported_identity( + &psk.identity, &imported_identity, + &context, &version, &hash); + if (ret < 0) { + gnutls_assert(); + goto fail; + } + + vers = version_to_entry(version); + if (unlikely(!vers || !vers->tls13_sem)) { + gnutls_assert(); + goto fail; + } + if (hash != MAC_TO_DIG(prf->id)) { + gnutls_assert(); + goto fail; + } + + ret = derive_ipsk(prf, &psk.identity, &key, + ipsk); + _gnutls_free_temp_key_datum(&key); + if (ret < 0) { + gnutls_assert(); + goto fail; + } + ret = _gnutls_set_datum(&key, ipsk, + prf->output_size); + zeroize_key(ipsk, sizeof(ipsk)); + if (ret < 0) { + gnutls_assert(); + goto fail; + } + + binder_type = BINDER_IMP; + } else { + binder_type = BINDER_EXT; + } break; } } @@ -667,7 +965,7 @@ static int server_recv_params(gnutls_session_t session, /* Compute the binder value for this PSK */ ret = compute_psk_binder(session, prf, psk_parser.binders_len + 2, 0, 0, - &key, &full_client_hello, resuming, + &key, &full_client_hello, binder_type, binder_value); if (ret < 0) { gnutls_assert(); @@ -691,7 +989,7 @@ static int server_recv_params(gnutls_session_t session, /* save the username in psk_auth_info to make it available * using gnutls_psk_server_get_username() */ - if (!resuming) { + if (binder_type != BINDER_RES) { assert(psk.identity.size <= MAX_USERNAME_SIZE); ret = _gnutls_auth_info_init(session, GNUTLS_CRD_PSK, @@ -760,10 +1058,12 @@ static int server_recv_params(gnutls_session_t session, /* Reference the selected pre-shared key */ session->key.binders[0].psk.data = key.data; session->key.binders[0].psk.size = key.size; + key.data = NULL; + key.size = 0; session->key.binders[0].idx = psk_index; session->key.binders[0].prf = prf; - session->key.binders[0].resumption = resuming; + session->key.binders[0].resumption = binder_type == BINDER_RES; ret = _gnutls_generate_early_secrets_for_psk(session); if (ret < 0) { @@ -771,10 +1071,8 @@ static int server_recv_params(gnutls_session_t session, goto fail; } - return 0; - fail: - gnutls_free(key.data); + _gnutls_free_datum(&key); return ret; } |