/* * Copyright (C) 2017 Free Software Foundation, Inc. * * Author: Ander Juaristi * * 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 * */ #include "gnutls_int.h" #include "auth/psk.h" #include "secrets.h" #include "tls13/psk_ext_parser.h" #include "tls13/finished.h" #include "auth/psk_passwd.h" #include typedef struct { uint16_t selected_identity; } psk_ext_st; static int compute_binder_key(const mac_entry_st *prf, const uint8_t *key, size_t keylen, void *out) { int ret; char label[] = "ext_binder"; size_t label_len = sizeof(label) - 1; uint8_t tmp_key[MAX_HASH_SIZE]; /* Compute HKDF-Extract(0, psk) */ ret = _tls13_init_secret2(prf, key, keylen, tmp_key); if (ret < 0) return ret; /* Compute Derive-Secret(secret, label, transcript_hash) */ ret = _tls13_derive_secret2(prf, label, label_len, NULL, 0, tmp_key, out); if (ret < 0) return ret; return 0; } static int compute_psk_binder(unsigned entity, const mac_entry_st *prf, unsigned binders_length, unsigned hash_size, int exts_length, int ext_offset, unsigned displacement, const gnutls_datum_t *psk, const gnutls_datum_t *client_hello, void *out) { int ret; unsigned extensions_len_pos; gnutls_buffer_st handshake_buf; uint8_t binder_key[MAX_HASH_SIZE]; _gnutls_buffer_init(&handshake_buf); if (entity == GNUTLS_CLIENT) { if (displacement >= client_hello->size) { ret = GNUTLS_E_INTERNAL_ERROR; goto error; } ret = gnutls_buffer_append_data(&handshake_buf, (const void *) (client_hello->data + displacement), client_hello->size - displacement); if (ret < 0) { gnutls_assert(); goto error; } ext_offset -= displacement; if (ext_offset <= 0) { ret = GNUTLS_E_INTERNAL_ERROR; goto error; } /* This is a ClientHello message */ handshake_buf.data[0] = GNUTLS_HANDSHAKE_CLIENT_HELLO; /* * At this point we have not yet added the binders to the ClientHello, * but we have to overwrite the size field, pretending as if binders * of the correct length were present. */ _gnutls_write_uint24(handshake_buf.length + binders_length - 2, &handshake_buf.data[1]); _gnutls_write_uint16(handshake_buf.length + binders_length - ext_offset, &handshake_buf.data[ext_offset]); extensions_len_pos = handshake_buf.length - exts_length - 2; _gnutls_write_uint16(exts_length + binders_length + 2, &handshake_buf.data[extensions_len_pos]); } else { gnutls_buffer_append_data(&handshake_buf, (const void *) client_hello->data, client_hello->size - binders_length - 3); } ret = compute_binder_key(prf, psk->data, psk->size, binder_key); if (ret < 0) goto error; ret = _gnutls13_compute_finished(prf, binder_key, hash_size, &handshake_buf, out); if (ret < 0) goto error; _gnutls_buffer_clear(&handshake_buf); return 0; error: _gnutls_buffer_clear(&handshake_buf); return gnutls_assert_val(ret); } static int get_credentials(gnutls_session_t session, const gnutls_psk_client_credentials_t cred, gnutls_datum_t *username, gnutls_datum_t *key) { int ret, retval = 0; char *username_str = NULL; if (cred->get_function) { ret = cred->get_function(session, &username_str, key); if (ret < 0) return gnutls_assert_val(ret); username->data = (uint8_t *) username_str; username->size = strlen(username_str); retval = username->size; } else if (cred->username.data != NULL && cred->key.data != NULL) { username->size = cred->username.size; if (username->size > 0) { username->data = gnutls_malloc(username->size); if (!username->data) return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); memcpy(username->data, cred->username.data, username->size); } key->size = cred->key.size; if (key->size > 0) { key->data = gnutls_malloc(key->size); if (!key->data) { _gnutls_free_datum(username); return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); } memcpy(key->data, cred->key.data, key->size); } retval = username->size; } return retval; } static int client_send_params(gnutls_session_t session, gnutls_buffer_t extdata, const gnutls_psk_client_credentials_t cred) { int ret, extdata_len = 0, ext_offset = 0; uint8_t binder_value[MAX_HASH_SIZE]; size_t length, pos = extdata->length; gnutls_datum_t username, key, client_hello; const mac_entry_st *prf = _gnutls_mac_to_entry(cred->tls13_binder_algo); unsigned hash_size = _gnutls_mac_get_algo_len(prf); if (prf == NULL || hash_size == 0 || hash_size > 255) return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); memset(&username, 0, sizeof(gnutls_datum_t)); ret = get_credentials(session, cred, &username, &key); if (ret < 0) return gnutls_assert_val(ret); /* No credentials - this extension is not applicable */ if (ret == 0) { ret = 0; goto cleanup; } ret = _gnutls_buffer_append_prefix(extdata, 16, 0); if (ret < 0) { gnutls_assert_val(ret); goto cleanup; } extdata_len += 2; if (username.size == 0 || username.size > 65536) { ret = gnutls_assert_val(GNUTLS_E_INVALID_PASSWORD); goto cleanup; } if ((ret = _gnutls_buffer_append_data_prefix(extdata, 16, username.data, username.size)) < 0) { gnutls_assert_val(ret); goto cleanup; } /* Now append the ticket age, which is always zero for out-of-band PSKs */ if ((ret = _gnutls_buffer_append_prefix(extdata, 32, 0)) < 0) { gnutls_assert_val(ret); goto cleanup; } /* Total length appended is the length of the data, plus six octets */ length = (username.size + 6); _gnutls_write_uint16(length, &extdata->data[pos]); extdata_len += length; ext_offset = _gnutls_ext_get_extensions_offset(session); /* Add the size of the binder (we only have one) */ length = (hash_size + 1); /* Compute the binders */ client_hello.data = extdata->data; client_hello.size = extdata->length; ret = compute_psk_binder(GNUTLS_CLIENT, prf, length, hash_size, extdata_len, ext_offset, sizeof(mbuffer_st), &key, &client_hello, binder_value); if (ret < 0) { gnutls_assert_val(ret); goto cleanup; } /* Now append the binders */ ret = _gnutls_buffer_append_prefix(extdata, 16, length); if (ret < 0) { gnutls_assert_val(ret); goto cleanup; } extdata_len += 2; _gnutls_buffer_append_prefix(extdata, 8, hash_size); _gnutls_buffer_append_data(extdata, binder_value, hash_size); extdata_len += (hash_size + 1); /* Reference the selected pre-shared key */ session->key.proto.tls13.psk = key.data; session->key.proto.tls13.psk_size = key.size; ret = extdata_len; cleanup: _gnutls_free_datum(&username); return ret; } static int server_send_params(gnutls_session_t session, gnutls_buffer_t extdata) { int ret; if (!(session->internals.hsk_flags & HSK_PSK_SELECTED)) return 0; ret = _gnutls_buffer_append_prefix(extdata, 16, session->key.proto.tls13.psk_index); if (ret < 0) return gnutls_assert_val(ret); return 2; } static int server_recv_params(gnutls_session_t session, const unsigned char *data, long len, const gnutls_psk_server_credentials_t pskcred) { int ret; const mac_entry_st *prf; gnutls_datum_t full_client_hello; uint8_t binder_value[MAX_HASH_SIZE]; int psk_index = -1; gnutls_datum_t binder_recvd = { NULL, 0 }; gnutls_datum_t key; unsigned hash_size; psk_ext_parser_t psk_parser; struct psk_st psk; ret = _gnutls13_psk_ext_parser_init(&psk_parser, data, len); if (ret == 0) { /* No PSKs advertised by client */ return 0; } else if (ret < 0) { return gnutls_assert_val(ret); } if (_gnutls13_psk_ext_parser_next_psk(psk_parser, &psk) >= 0) { /* _gnutls_psk_pwd_find_entry() expects 0-terminated identities */ if (psk.identity.size > 0) { char identity_str[psk.identity.size + 1]; memcpy(identity_str, psk.identity.data, psk.identity.size); identity_str[psk.identity.size] = 0; ret = _gnutls_psk_pwd_find_entry(session, identity_str, &key); if (ret == 0) psk_index = psk.selected_index; } } if (psk_index < 0) return 0; ret = _gnutls13_psk_ext_parser_find_binder(psk_parser, psk_index, &binder_recvd); if (ret < 0) return gnutls_assert_val(ret); if (binder_recvd.size == 0) return gnutls_assert_val(GNUTLS_E_INSUFFICIENT_CREDENTIALS); ret = _gnutls13_psk_ext_parser_deinit(&psk_parser, &data, (size_t *) &len); if (ret < 0) { gnutls_assert(); goto cleanup; } /* Get full ClientHello */ if (!_gnutls_ext_get_full_client_hello(session, &full_client_hello)) { ret = 0; goto cleanup; } /* Compute the binder value for this PSK */ prf = _gnutls_mac_to_entry(pskcred->tls13_binder_algo); hash_size = prf->output_size; compute_psk_binder(GNUTLS_SERVER, prf, hash_size, hash_size, 0, 0, 0, &key, &full_client_hello, binder_value); if (_gnutls_mac_get_algo_len(prf) != binder_recvd.size || safe_memcmp(binder_value, binder_recvd.data, binder_recvd.size)) { ret = gnutls_assert_val(GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER); goto cleanup; } session->internals.hsk_flags |= HSK_PSK_SELECTED; /* Reference the selected pre-shared key */ session->key.proto.tls13.psk = key.data; session->key.proto.tls13.psk_size = key.size; session->key.proto.tls13.psk_index = 0; _gnutls_free_datum(&binder_recvd); return 0; cleanup: _gnutls_free_datum(&binder_recvd); return ret; } static int client_recv_params(gnutls_session_t session, const unsigned char *data, size_t len) { uint16_t selected_identity = _gnutls_read_uint16(data); if (selected_identity == 0) session->internals.hsk_flags |= HSK_PSK_SELECTED; return 0; } /* * Return values for this function: * - 0 : Not applicable. * - >0 : Ok. Return size of extension data. * - GNUTLS_E_INT_RET_0 : Size of extension data is zero. * - <0 : There's been an error. * * In the client, generates the PskIdentity and PskBinderEntry messages. * * PskIdentity identities<7..2^16-1>; * PskBinderEntry binders<33..2^16-1>; * * struct { * opaque identity<1..2^16-1>; * uint32 obfuscated_ticket_age; * } PskIdentity; * * opaque PskBinderEntry<32..255>; * * The server sends the selected identity, which is a zero-based index * of the PSKs offered by the client: * * struct { * uint16 selected_identity; * } PreSharedKeyExtension; */ static int _gnutls_psk_send_params(gnutls_session_t session, gnutls_buffer_t extdata) { gnutls_psk_client_credentials_t cred = NULL; if (session->security_parameters.entity == GNUTLS_CLIENT) { if (session->internals.hsk_flags & HSK_PSK_KE_MODES_SENT) { cred = (gnutls_psk_client_credentials_t) _gnutls_get_cred(session, GNUTLS_CRD_PSK); } /* * If there are no PSK credentials, this extension is not applicable, * so we return zero. */ return (cred ? client_send_params(session, extdata, cred) : 0); } else { if (session->internals.hsk_flags & HSK_PSK_KE_MODES_RECEIVED) return server_send_params(session, extdata); else return 0; } } /* * Return values for this function: * - 0 : Not applicable. * - >0 : Ok. Return size of extension data. * - <0 : There's been an error. */ static int _gnutls_psk_recv_params(gnutls_session_t session, const unsigned char *data, size_t len) { gnutls_psk_server_credentials_t pskcred; if (session->security_parameters.entity == GNUTLS_CLIENT) { if (session->internals.hsk_flags & HSK_PSK_KE_MODES_SENT) return client_recv_params(session, data, len); else return gnutls_assert_val(GNUTLS_E_RECEIVED_ILLEGAL_EXTENSION); } else { if (session->internals.hsk_flags & HSK_PSK_KE_MODES_RECEIVED) { if (session->internals.hsk_flags & HSK_PSK_KE_MODES_INVALID) { /* We received a "psk_ke_modes" extension, but with a value we don't support */ return 0; } pskcred = (gnutls_psk_server_credentials_t) _gnutls_get_cred(session, GNUTLS_CRD_PSK); /* * If there are no PSK credentials, this extension is not applicable, * so we return zero. */ return (pskcred ? server_recv_params(session, data, len, pskcred) : 0); } else { return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET); } } } const hello_ext_entry_st ext_pre_shared_key = { .name = "Pre Shared Key", .tls_id = 41, .gid = GNUTLS_EXTENSION_PRE_SHARED_KEY, .parse_type = GNUTLS_EXT_TLS, .validity = GNUTLS_EXT_FLAG_CLIENT_HELLO | GNUTLS_EXT_FLAG_TLS13_SERVER_HELLO, .send_func = _gnutls_psk_send_params, .recv_func = _gnutls_psk_recv_params };