summaryrefslogtreecommitdiff
path: root/lib/ext/pre_shared_key.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ext/pre_shared_key.c')
-rw-r--r--lib/ext/pre_shared_key.c352
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;
}