diff options
42 files changed, 2015 insertions, 185 deletions
diff --git a/doc/cha-gtls-app.texi b/doc/cha-gtls-app.texi index 6575120756..655046c917 100644 --- a/doc/cha-gtls-app.texi +++ b/doc/cha-gtls-app.texi @@ -1268,7 +1268,9 @@ GROUP-SECP256R1, GROUP-SECP384R1, GROUP-SECP521R1, GROUP-X25519, GROUP-FFDHE2048, GROUP-FFDHE3072, GROUP-FFDHE4096 and GROUP-FFDHE8192. Groups include both elliptic curve groups, e.g., SECP256R1, as well as finite field groups such as FFDHE2048. Catch all which enables all groups -from NORMAL priority is GROUP-ALL. +from NORMAL priority is GROUP-ALL. The helper keywords GROUP-DH-ALL and +GROUP-EC-ALL are also available, restricting the groups to finite fields +(DH) and elliptic curves. @item Certificate type @tab The only option currently is CTYPE-X509. Catch all is CTYPE-ALL. diff --git a/lib/Makefile.am b/lib/Makefile.am index ea03f39724..64c7110d85 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -97,7 +97,8 @@ 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/post_handshake.c + tls13/post_handshake.c \ + tls13/psk_ext_parser.c tls13/psk_ext_parser.h if ENABLE_PKCS11 COBJECTS += pkcs11.c pkcs11x.c pkcs11_privkey.c pkcs11_write.c pkcs11_secret.c \ diff --git a/lib/algorithms.h b/lib/algorithms.h index 495b20d439..468a0c8e96 100644 --- a/lib/algorithms.h +++ b/lib/algorithms.h @@ -444,6 +444,15 @@ static inline int _gnutls_kx_is_ecc(gnutls_kx_algorithm_t kx) return 0; } +static inline int _gnutls_kx_is_psk(gnutls_kx_algorithm_t kx) +{ + if (kx == GNUTLS_KX_PSK || kx == GNUTLS_KX_DHE_PSK || + kx == GNUTLS_KX_ECDHE_PSK || kx == GNUTLS_KX_RSA_PSK) + return 1; + + return 0; +} + static inline int _gnutls_kx_is_dhe(gnutls_kx_algorithm_t kx) { if (kx == GNUTLS_KX_DHE_RSA || kx == GNUTLS_KX_DHE_DSS || diff --git a/lib/algorithms/ciphersuites.c b/lib/algorithms/ciphersuites.c index 063363b5bf..ef31a05afd 100644 --- a/lib/algorithms/ciphersuites.c +++ b/lib/algorithms/ciphersuites.c @@ -1482,6 +1482,13 @@ _gnutls_figure_common_ciphersuite(gnutls_session_t session, if (!kx_is_ok(session, kx, cred_type, &sgroup)) continue; + /* if we have selected PSK, we need a ciphersuites which matches + * the selected binder */ + if (session->internals.hsk_flags & HSK_PSK_SELECTED) { + if (session->key.proto.tls13.binder_prf->id != session->internals.priorities->cs.entry[j]->prf) + continue; + } + if (cred_type == GNUTLS_CRD_CERTIFICATE) { ret = _gnutls_server_select_cert(session, peer_clist->entry[i]); if (ret < 0) { @@ -1520,6 +1527,13 @@ _gnutls_figure_common_ciphersuite(gnutls_session_t session, if (!kx_is_ok(session, kx, cred_type, &sgroup)) break; + /* if we have selected PSK, we need a ciphersuites which matches + * the selected binder */ + if (session->internals.hsk_flags & HSK_PSK_SELECTED) { + if (session->key.proto.tls13.binder_prf->id != session->internals.priorities->cs.entry[j]->prf) + break; + } + if (cred_type == GNUTLS_CRD_CERTIFICATE) { ret = _gnutls_server_select_cert(session, peer_clist->entry[i]); if (ret < 0) { diff --git a/lib/auth/psk.c b/lib/auth/psk.c index e9bb701f33..6968bb8057 100644 --- a/lib/auth/psk.c +++ b/lib/auth/psk.c @@ -26,7 +26,6 @@ #include "errors.h" #include "auth.h" -#include "auth.h" #include "debug.h" #include "num.h" #include <auth/psk.h> @@ -111,40 +110,6 @@ _gnutls_set_psk_session_key(gnutls_session_t session, return ret; } -/* returns the username and they key for the PSK session. - * Free is non (0) if they have to be freed. - */ -int _gnutls_find_psk_key(gnutls_session_t session, - gnutls_psk_client_credentials_t cred, - gnutls_datum_t * username, gnutls_datum_t * key, - int *free) -{ - char *user_p; - int ret; - - *free = 0; - - if (cred->username.data != NULL && cred->key.data != NULL) { - username->data = cred->username.data; - username->size = cred->username.size; - key->data = cred->key.data; - key->size = cred->key.size; - } else if (cred->get_function != NULL) { - ret = cred->get_function(session, &user_p, key); - if (ret) - return gnutls_assert_val(ret); - - username->data = (uint8_t *) user_p; - username->size = strlen(user_p); - - *free = 1; - } else - return - gnutls_assert_val(GNUTLS_E_INSUFFICIENT_CREDENTIALS); - - return 0; -} - /* Generates the PSK client key exchange * diff --git a/lib/auth/psk.h b/lib/auth/psk.h index 8cde1f7159..3d204bc8c8 100644 --- a/lib/auth/psk.h +++ b/lib/auth/psk.h @@ -30,6 +30,8 @@ typedef struct gnutls_psk_client_credentials_st { gnutls_datum_t username; gnutls_datum_t key; gnutls_psk_client_credentials_function *get_function; + /* TLS 1.3 - The HMAC algorithm to use to compute the binder values */ + const mac_entry_st *binder_algo; } psk_client_credentials_st; typedef struct gnutls_psk_server_credentials_st { @@ -50,6 +52,8 @@ typedef struct gnutls_psk_server_credentials_st { /* Identity hint. */ char *hint; + /* TLS 1.3 - HMAC algorithm for the binder values */ + const mac_entry_st *binder_algo; } psk_server_cred_st; /* these structures should not use allocated data */ @@ -59,11 +63,10 @@ typedef struct psk_auth_info_st { char hint[MAX_USERNAME_SIZE + 1]; } *psk_auth_info_t; +typedef struct psk_auth_info_st psk_auth_info_st; #ifdef ENABLE_PSK -typedef struct psk_auth_info_st psk_auth_info_st; - int _gnutls_set_psk_session_key(gnutls_session_t session, gnutls_datum_t * key, gnutls_datum_t * psk2); @@ -71,10 +74,6 @@ int _gnutls_gen_psk_server_kx(gnutls_session_t session, gnutls_buffer_st * data); int _gnutls_gen_psk_client_kx(gnutls_session_t, gnutls_buffer_st *); -int _gnutls_find_psk_key(gnutls_session_t session, - gnutls_psk_client_credentials_t cred, - gnutls_datum_t * username, gnutls_datum_t * key, - int *free); #else #define _gnutls_set_psk_session_key(x,y,z) GNUTLS_E_UNIMPLEMENTED_FEATURE diff --git a/lib/auth/psk_passwd.c b/lib/auth/psk_passwd.c index 1e1898cb8d..dfaac2bea2 100644 --- a/lib/auth/psk_passwd.c +++ b/lib/auth/psk_passwd.c @@ -24,8 +24,6 @@ #include "gnutls_int.h" -#ifdef ENABLE_PSK - #include "x509_b64.h" #include "errors.h" #include <auth/psk_passwd.h> @@ -202,5 +200,36 @@ cleanup: } +/* returns the username and they key for the PSK session. + * Free is non (0) if they have to be freed. + */ +int _gnutls_find_psk_key(gnutls_session_t session, + gnutls_psk_client_credentials_t cred, + gnutls_datum_t * username, gnutls_datum_t * key, + int *free) +{ + char *user_p; + int ret; + + *free = 0; -#endif /* ENABLE PSK */ + if (cred->username.data != NULL && cred->key.data != NULL) { + username->data = cred->username.data; + username->size = cred->username.size; + key->data = cred->key.data; + key->size = cred->key.size; + } else if (cred->get_function != NULL) { + ret = cred->get_function(session, &user_p, key); + if (ret) + return gnutls_assert_val(ret); + + username->data = (uint8_t *) user_p; + username->size = strlen(user_p); + + *free = 1; + } else + return + gnutls_assert_val(GNUTLS_E_INSUFFICIENT_CREDENTIALS); + + return 0; +} diff --git a/lib/auth/psk_passwd.h b/lib/auth/psk_passwd.h index f09df621d5..8fe7ae4b02 100644 --- a/lib/auth/psk_passwd.h +++ b/lib/auth/psk_passwd.h @@ -20,10 +20,11 @@ * */ -#ifdef ENABLE_PSK - /* this is locally allocated. It should be freed using the provided function */ int _gnutls_psk_pwd_find_entry(gnutls_session_t, char *username, gnutls_datum_t * key); -#endif /* ENABLE_SRP */ +int _gnutls_find_psk_key(gnutls_session_t session, + gnutls_psk_client_credentials_t cred, + gnutls_datum_t * username, gnutls_datum_t * key, + int *free); diff --git a/lib/errors.c b/lib/errors.c index 16ade63749..1e9a64922b 100644 --- a/lib/errors.c +++ b/lib/errors.c @@ -134,7 +134,7 @@ static const gnutls_error_entry error_entries[] = { GNUTLS_E_UNIMPLEMENTED_FEATURE), ERROR_ENTRY(N_("Insufficient credentials for that request."), GNUTLS_E_INSUFFICIENT_CREDENTIALS), - ERROR_ENTRY(N_("Error in password file."), GNUTLS_E_SRP_PWD_ERROR), + ERROR_ENTRY(N_("Error in password/key file."), GNUTLS_E_SRP_PWD_ERROR), ERROR_ENTRY(N_("Wrong padding in PKCS1 packet."), GNUTLS_E_PKCS1_WRONG_PAD), ERROR_ENTRY(N_("The session or certificate has expired."), @@ -148,7 +148,7 @@ static const gnutls_error_entry error_entries[] = { GNUTLS_E_BASE64_UNEXPECTED_HEADER_ERROR), ERROR_ENTRY(N_("Base64 encoding error."), GNUTLS_E_BASE64_ENCODING_ERROR), - ERROR_ENTRY(N_("Parsing error in password file."), + ERROR_ENTRY(N_("Parsing error in password/key file."), GNUTLS_E_SRP_PWD_PARSING_ERROR), ERROR_ENTRY(N_("The requested data were not available."), GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE), @@ -260,7 +260,7 @@ static const gnutls_error_entry error_entries[] = { ERROR_ENTRY(N_("The SRP username supplied is illegal."), GNUTLS_E_ILLEGAL_SRP_USERNAME), - ERROR_ENTRY(N_("The SRP username supplied is unknown."), + ERROR_ENTRY(N_("The username supplied is unknown."), GNUTLS_E_UNKNOWN_SRP_USERNAME), ERROR_ENTRY(N_("The OpenPGP fingerprint is not supported."), diff --git a/lib/ext/Makefile.am b/lib/ext/Makefile.am index 63d94760bb..89d2389be9 100644 --- a/lib/ext/Makefile.am +++ b/lib/ext/Makefile.am @@ -43,7 +43,8 @@ libgnutls_ext_la_SOURCES = max_record.c \ ext_master_secret.c ext_master_secret.h etm.h etm.c \ supported_versions.c supported_versions.h \ post_handshake.c post_handshake.h key_share.c key_share.h \ - cookie.c cookie.h + cookie.c cookie.h \ + psk_ke_modes.c psk_ke_modes.h pre_shared_key.c pre_shared_key.h if ENABLE_ALPN libgnutls_ext_la_SOURCES += alpn.c alpn.h diff --git a/lib/ext/dumbfw.c b/lib/ext/dumbfw.c index 7faff65693..4e56192337 100644 --- a/lib/ext/dumbfw.c +++ b/lib/ext/dumbfw.c @@ -1,5 +1,6 @@ /* - * Copyright (C) 2013 Nikos Mavrogiannopoulos + * Copyright (C) 2013-2018 Nikos Mavrogiannopoulos + * Copyright (C) 2018 Red Hat, Inc. * * This file is part of GnuTLS. * @@ -57,15 +58,16 @@ _gnutls_dumbfw_send_params(gnutls_session_t session, int total_size = 0, ret; uint8_t pad[257]; unsigned pad_size; + ssize_t len = extdata->length - sizeof(mbuffer_st); if (session->security_parameters.entity == GNUTLS_SERVER || session->internals.dumbfw == 0 || IS_DTLS(session) != 0 || - (extdata->length < 256 || extdata->length >= 512)) { + (len < 256 || len >= 512)) { return 0; } else { /* 256 <= extdata->length < 512 */ - pad_size = 512 - extdata->length; + pad_size = 512 - len; memset(pad, 0, pad_size); ret = diff --git a/lib/ext/key_share.c b/lib/ext/key_share.c index f9403df838..871ff08ceb 100644 --- a/lib/ext/key_share.c +++ b/lib/ext/key_share.c @@ -506,6 +506,13 @@ key_share_recv_params(gnutls_session_t session, if (data_size != size) return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH); + /* if we do PSK without DH ignore that share */ + if ((session->internals.hsk_flags & HSK_PSK_SELECTED) && + (session->internals.hsk_flags & HSK_PSK_KE_MODE_PSK)) { + reset_cand_groups(session); + return 0; + } + while(data_size > 0) { DECR_LEN(data_size, 2); gid = _gnutls_read_uint16(data); @@ -554,8 +561,9 @@ key_share_recv_params(gnutls_session_t session, * In cases (2,3) the error is translated to illegal * parameter alert. */ - if (used_share == 0) + if (used_share == 0) { return gnutls_assert_val(GNUTLS_E_NO_COMMON_KEY_SHARE); + } } else { /* Client */ ver = get_version(session); @@ -611,6 +619,7 @@ key_share_recv_params(gnutls_session_t session, } _gnutls_session_group_set(session, group); + session->internals.hsk_flags |= HSK_KEY_SHARE_RECEIVED; ret = client_use_key_share(session, group, data, size); if (ret < 0) @@ -718,6 +727,11 @@ key_share_send_params(gnutls_session_t session, if (ret < 0) return gnutls_assert_val(ret); } else { + /* if we are negotiating PSK without DH, do not send a key share */ + if ((session->internals.hsk_flags & HSK_PSK_SELECTED) && + (session->internals.hsk_flags & HSK_PSK_KE_MODE_PSK)) + return gnutls_assert_val(0); + group = get_group(session); if (unlikely(group == NULL)) return gnutls_assert_val(GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER); @@ -726,8 +740,9 @@ key_share_send_params(gnutls_session_t session, if (ret < 0) return gnutls_assert_val(ret); } + + session->internals.hsk_flags |= HSK_KEY_SHARE_SENT; } return 0; } - diff --git a/lib/ext/pre_shared_key.c b/lib/ext/pre_shared_key.c new file mode 100644 index 0000000000..d4ea982cbb --- /dev/null +++ b/lib/ext/pre_shared_key.c @@ -0,0 +1,478 @@ +/* + * Copyright (C) 2017-2018 Free Software Foundation, Inc. + * Copyright (C) 2018 Red Hat, Inc. + * + * Author: Ander Juaristi, 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 "auth/psk.h" +#include "secrets.h" +#include "tls13/psk_ext_parser.h" +#include "tls13/finished.h" +#include "auth/psk_passwd.h" +#include <ext/pre_shared_key.h> +#include <assert.h> + +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, + 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) { + ret = gnutls_buffer_append_data(&handshake_buf, + (const void *) client_hello->data, + client_hello->size); + if (ret < 0) { + gnutls_assert(); + 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) { + gnutls_assert(); + goto error; + } + + ret = _gnutls13_compute_finished(prf, + binder_key, hash_size, + &handshake_buf, + out); + if (ret < 0) { + gnutls_assert(); + goto error; + } + + ret = 0; +error: + _gnutls_buffer_clear(&handshake_buf); + return ret; +} + +static int +client_send_params(gnutls_session_t session, + gnutls_buffer_t extdata, + const gnutls_psk_client_credentials_t cred) +{ + int ret, ext_offset = 0; + uint8_t binder_value[MAX_HASH_SIZE]; + size_t length, pos; + gnutls_datum_t username = {NULL, 0}, key = {NULL, 0}, client_hello; + const mac_entry_st *prf = cred->binder_algo; + unsigned hash_size = _gnutls_mac_get_algo_len(prf); + int free_data; + + if (prf == NULL || hash_size == 0 || hash_size > 255) + return gnutls_assert_val(GNUTLS_E_INSUFFICIENT_CREDENTIALS); + + /* Credentials but no username set - this extension is not applicable */ + if (!_gnutls_have_psk_credentials(cred)) + return 0; + + ret = _gnutls_find_psk_key(session, cred, &username, &key, &free_data); + if (ret < 0) + return gnutls_assert_val(ret); + + if (username.size == 0 || username.size > UINT16_MAX) { + ret = gnutls_assert_val(GNUTLS_E_INVALID_PASSWORD); + goto cleanup; + } + + /* placeholder to be filled later */ + pos = extdata->length; + ret = _gnutls_buffer_append_prefix(extdata, 16, 0); + if (ret < 0) { + gnutls_assert_val(ret); + goto cleanup; + } + + if ((ret = _gnutls_buffer_append_data_prefix(extdata, 16, + username.data, username.size)) < 0) { + gnutls_assert(); + 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(); + 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]); + + ext_offset = _gnutls_ext_get_extensions_offset(session); + + /* Compute the binders. extdata->data points to the start + * of this client hello. */ + assert(extdata->length >= sizeof(mbuffer_st)); + assert(ext_offset >= (ssize_t)sizeof(mbuffer_st)); + ext_offset -= sizeof(mbuffer_st); + client_hello.data = extdata->data+sizeof(mbuffer_st); + client_hello.size = extdata->length-sizeof(mbuffer_st); + + ret = compute_psk_binder(GNUTLS_CLIENT, prf, + hash_size+1, hash_size, extdata->length-pos, + ext_offset, &key, &client_hello, + binder_value); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + /* Associate the selected pre-shared key with the session */ + session->key.psk.data = key.data; + session->key.psk.size = key.size; + session->key.psk_needs_free = free_data; + key.data = NULL; + session->key.proto.tls13.binder_prf = prf; + + /* Now append the binders */ + ret = _gnutls_buffer_append_prefix(extdata, 16, hash_size+1); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + /* Add the size of the binder (we only have one) */ + ret = _gnutls_buffer_append_data_prefix(extdata, 8, binder_value, hash_size); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + ret = 0; + +cleanup: + if (free_data) { + _gnutls_free_datum(&username); + _gnutls_free_temp_key_datum(&key); + } + 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, size_t 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_st psk_parser; + struct psk_st psk; + psk_auth_info_t info; + + ret = _gnutls13_psk_ext_parser_init(&psk_parser, data, len); + if (ret < 0) { + if (ret == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) /* No PSKs advertised by client */ + return 0; + return gnutls_assert_val(ret); + } + + while ((ret = _gnutls13_psk_ext_parser_next_psk(&psk_parser, &psk)) >= 0) { + if (psk.ob_ticket_age == 0) { + /* _gnutls_psk_pwd_find_entry() expects 0-terminated identities */ + if (psk.identity.size > 0 && psk.identity.size <= MAX_USERNAME_SIZE) { + 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 = ret; + } + } + } + + 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); + + /* Get full ClientHello */ + if (!_gnutls_ext_get_full_client_hello(session, &full_client_hello)) + return 0; + + /* Compute the binder value for this PSK */ + prf = pskcred->binder_algo; + hash_size = prf->output_size; + ret = compute_psk_binder(GNUTLS_SERVER, prf, hash_size, hash_size, 0, 0, + &key, &full_client_hello, + binder_value); + if (ret < 0) + return gnutls_assert_val(ret); + + if (_gnutls_mac_get_algo_len(prf) != binder_recvd.size || + safe_memcmp(binder_value, binder_recvd.data, binder_recvd.size)) { + gnutls_free(key.data); + return gnutls_assert_val(GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER); + } + + if (session->internals.hsk_flags & HSK_PSK_KE_MODE_DHE_PSK) + _gnutls_handshake_log("EXT[%p]: Selected DHE-PSK mode\n", session); + else { + reset_cand_groups(session); + _gnutls_handshake_log("EXT[%p]: Selected PSK mode\n", session); + } + + /* save the username in psk_auth_info to make it available + * using gnutls_psk_server_get_username() */ + if (psk.ob_ticket_age == 0) { + if (psk.identity.size >= sizeof(info->username)) + return gnutls_assert_val(GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER); + + ret = _gnutls_auth_info_set(session, GNUTLS_CRD_PSK, sizeof(psk_auth_info_st), 1); + if (ret < 0) + return gnutls_assert_val(ret); + + info = _gnutls_get_auth_info(session, GNUTLS_CRD_PSK); + assert(info != NULL); + + memcpy(info->username, psk.identity.data, psk.identity.size); + info->username[psk.identity.size] = 0; + } + + session->internals.hsk_flags |= HSK_PSK_SELECTED; + + /* Reference the selected pre-shared key */ + session->key.psk.data = key.data; + session->key.psk.size = key.size; + session->key.psk_needs_free = 1; + + session->key.proto.tls13.psk_index = psk_index; + session->key.proto.tls13.binder_prf = prf; + + 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; + const version_entry_st *vers; + + if (session->security_parameters.entity == GNUTLS_CLIENT) { + vers = _gnutls_version_max(session); + + if (!vers || !vers->tls13_sem) + return 0; + + 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. */ + if (cred == NULL || !session->internals.priorities->have_psk) + return 0; + + return client_send_params(session, extdata, cred); + } else { + return 0; + } + } else { + vers = get_version(session); + + if (!vers || !vers->tls13_sem) + return 0; + + cred = (gnutls_psk_client_credentials_t) + _gnutls_get_cred(session, GNUTLS_CRD_PSK); + if (cred == NULL || !session->internals.priorities->have_psk) + return 0; + + 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; + const version_entry_st *vers = get_version(session); + + if (!vers || !vers->tls13_sem) + return 0; + + if (session->security_parameters.entity == GNUTLS_CLIENT) { + if (session->internals.hsk_flags & HSK_PSK_KE_MODES_SENT) { + uint16_t selected_identity = _gnutls_read_uint16(data); + + if (selected_identity == 0) { + _gnutls_handshake_log("EXT[%p]: Selected PSK mode\n", session); + session->internals.hsk_flags |= HSK_PSK_SELECTED; + } + return 0; + } 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_MODE_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. */ + if (pskcred == NULL) + return 0; + + return server_recv_params(session, data, len, pskcred); + } else { + return gnutls_assert_val(GNUTLS_E_RECEIVED_ILLEGAL_EXTENSION); + } + } +} + +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 +}; diff --git a/lib/ext/pre_shared_key.h b/lib/ext/pre_shared_key.h new file mode 100644 index 0000000000..25dd159f6e --- /dev/null +++ b/lib/ext/pre_shared_key.h @@ -0,0 +1,18 @@ +#ifndef EXT_PRE_SHARED_KEY_H +#define EXT_PRE_SHARED_KEY_H + +#include "auth/psk.h" +#include <hello_ext.h> + +extern const hello_ext_entry_st ext_pre_shared_key; + +inline static +unsigned _gnutls_have_psk_credentials(const gnutls_psk_client_credentials_t cred) +{ + if (cred->get_function || cred->username.data) + return 1; + else + return 0; +} + +#endif diff --git a/lib/ext/psk_ke_modes.c b/lib/ext/psk_ke_modes.c new file mode 100644 index 0000000000..c6aef3bda8 --- /dev/null +++ b/lib/ext/psk_ke_modes.c @@ -0,0 +1,180 @@ +/* + * 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 <http://www.gnu.org/licenses/> + * + */ + +#include "gnutls_int.h" +#include "ext/psk_ke_modes.h" +#include "ext/pre_shared_key.h" +#include <assert.h> + +#define PSK_KE 0 +#define PSK_DHE_KE 1 + +/* + * We only support ECDHE-authenticated PSKs. + * The client just sends a "psk_key_exchange_modes" extension + * with the value one. + */ +static int +psk_ke_modes_send_params(gnutls_session_t session, + gnutls_buffer_t extdata) +{ + int ret; + gnutls_psk_client_credentials_t cred; + const version_entry_st *vers; + uint8_t data[2]; + unsigned pos, i; + unsigned have_dhpsk = 0; + unsigned have_psk = 0; + + /* Server doesn't send psk_key_exchange_modes */ + if (session->security_parameters.entity == GNUTLS_SERVER || + !session->internals.priorities->have_psk) + return 0; + + cred = (gnutls_psk_client_credentials_t) + _gnutls_get_cred(session, GNUTLS_CRD_PSK); + if (cred == NULL || _gnutls_have_psk_credentials(cred) == 0) + return 0; + + vers = _gnutls_version_max(session); + if (!vers || !vers->tls13_sem) + return 0; + + pos = 0; + for (i=0;i<session->internals.priorities->_kx.algorithms;i++) { + if (session->internals.priorities->_kx.priority[i] == GNUTLS_KX_PSK && !have_psk) { + assert(pos <= 1); + data[pos++] = PSK_KE; + session->internals.hsk_flags |= HSK_PSK_KE_MODE_PSK; + have_psk = 1; + } else if ((session->internals.priorities->_kx.priority[i] == GNUTLS_KX_DHE_PSK || + session->internals.priorities->_kx.priority[i] == GNUTLS_KX_ECDHE_PSK) && !have_dhpsk) { + assert(pos <= 1); + data[pos++] = PSK_DHE_KE; + session->internals.hsk_flags |= HSK_PSK_KE_MODE_DHE_PSK; + have_dhpsk = 1; + } + + if (have_psk && have_dhpsk) + break; + } + + ret = _gnutls_buffer_append_data_prefix(extdata, 8, data, pos); + if (ret < 0) + return gnutls_assert_val(ret); + + session->internals.hsk_flags |= HSK_PSK_KE_MODES_SENT; + + return 0; +} + +#define MAX_POS INT_MAX + +/* + * Since we only support ECDHE-authenticated PSKs, the server + * just verifies that a "psk_key_exchange_modes" extension was received, + * and that it contains the value one. + */ +static int +psk_ke_modes_recv_params(gnutls_session_t session, + const unsigned char *data, size_t _len) +{ + uint8_t ke_modes_len; + ssize_t len = _len; + const version_entry_st *vers = get_version(session); + gnutls_psk_server_credentials_t cred; + int dhpsk_pos = MAX_POS; + int psk_pos = MAX_POS; + int cli_psk_pos = MAX_POS; + int cli_dhpsk_pos = MAX_POS; + unsigned i; + + /* Server doesn't send psk_key_exchange_modes */ + if (session->security_parameters.entity == GNUTLS_CLIENT) + return gnutls_assert_val(GNUTLS_E_RECEIVED_ILLEGAL_EXTENSION); + + if (!vers || !vers->tls13_sem) + return 0; + + cred = (gnutls_psk_server_credentials_t)_gnutls_get_cred(session, GNUTLS_CRD_PSK); + if (cred == NULL) + return 0; + + DECR_LEN(len, 1); + ke_modes_len = *(data++); + + for (i=0;i<session->internals.priorities->_kx.algorithms;i++) { + if (session->internals.priorities->_kx.priority[i] == GNUTLS_KX_PSK && psk_pos == MAX_POS) { + psk_pos = i; + } else if ((session->internals.priorities->_kx.priority[i] == GNUTLS_KX_DHE_PSK || + session->internals.priorities->_kx.priority[i] == GNUTLS_KX_ECDHE_PSK) && + dhpsk_pos == MAX_POS) { + dhpsk_pos = i; + } + + if (dhpsk_pos != MAX_POS && psk_pos != MAX_POS) + break; + } + + if (session->internals.priorities->groups.size == 0 && psk_pos == MAX_POS) + return gnutls_assert_val(0); + + for (i=0;i<ke_modes_len;i++) { + if (data[i] == PSK_DHE_KE) + cli_dhpsk_pos = i; + if (data[i] == PSK_KE) + cli_psk_pos = i; + + if (cli_psk_pos != MAX_POS && cli_dhpsk_pos != MAX_POS) + break; + } + + if (session->internals.priorities->server_precedence) { + if (dhpsk_pos != MAX_POS && cli_dhpsk_pos != MAX_POS && dhpsk_pos < psk_pos) + session->internals.hsk_flags |= HSK_PSK_KE_MODE_DHE_PSK; + else if (psk_pos != MAX_POS && cli_psk_pos != MAX_POS && psk_pos < dhpsk_pos) + session->internals.hsk_flags |= HSK_PSK_KE_MODE_PSK; + } else { + if (dhpsk_pos != MAX_POS && cli_dhpsk_pos != MAX_POS && cli_dhpsk_pos < cli_psk_pos) + session->internals.hsk_flags |= HSK_PSK_KE_MODE_DHE_PSK; + else if (psk_pos != MAX_POS && cli_psk_pos != MAX_POS && cli_psk_pos < cli_dhpsk_pos) + session->internals.hsk_flags |= HSK_PSK_KE_MODE_PSK; + } + + if ((session->internals.hsk_flags & HSK_PSK_KE_MODE_PSK) || + (session->internals.hsk_flags & HSK_PSK_KE_MODE_DHE_PSK)) { + return 0; + } else { + session->internals.hsk_flags |= HSK_PSK_KE_MODE_INVALID; + return 0; + } +} + +const hello_ext_entry_st ext_psk_ke_modes = { + .name = "PSK Key Exchange Modes", + .tls_id = 45, + .gid = GNUTLS_EXTENSION_PSK_KE_MODES, + .parse_type = GNUTLS_EXT_TLS, + .validity = GNUTLS_EXT_FLAG_CLIENT_HELLO | GNUTLS_EXT_FLAG_TLS13_SERVER_HELLO, + .send_func = psk_ke_modes_send_params, + .recv_func = psk_ke_modes_recv_params +}; diff --git a/lib/ext/psk_ke_modes.h b/lib/ext/psk_ke_modes.h new file mode 100644 index 0000000000..bd06139ff5 --- /dev/null +++ b/lib/ext/psk_ke_modes.h @@ -0,0 +1,8 @@ +#ifndef EXT_PSK_KE_MODES_H +#define EXT_PSK_KE_MODES_H + +#include <hello_ext.h> + +extern const hello_ext_entry_st ext_psk_ke_modes; + +#endif diff --git a/lib/gnutls_int.h b/lib/gnutls_int.h index e926b3d0fe..cae9d7aec7 100644 --- a/lib/gnutls_int.h +++ b/lib/gnutls_int.h @@ -325,7 +325,13 @@ typedef enum extensions_t { GNUTLS_EXTENSION_SAFE_RENEGOTIATION, GNUTLS_EXTENSION_SERVER_NAME, GNUTLS_EXTENSION_COOKIE, - GNUTLS_EXTENSION_DUMBFW, /* this must always be the last */ + GNUTLS_EXTENSION_PSK_KE_MODES, + /* + * pre_shared_key and dumbfw must always be the last extensions, + * in that order + */ + GNUTLS_EXTENSION_PRE_SHARED_KEY, + GNUTLS_EXTENSION_DUMBFW, GNUTLS_EXTENSION_MAX /* not real extension - used for iterators */ } extensions_t; @@ -449,7 +455,7 @@ typedef struct mbuffer_head_st { typedef struct auth_cred_st { gnutls_credentials_type_t algorithm; - /* the type of credentials depends on algorithm + /* the type of credentials depends on algorithm */ void *credentials; struct auth_cred_st *next; @@ -463,9 +469,21 @@ struct gnutls_key_st { gnutls_pk_params_st dh_params; } kshare; - /* The union contents depend on the negotiated protocol */ + /* The union contents depend on the negotiated protocol. + * It should not contain any values which are allocated + * prior to protocol negotiation, as it would be impossible + * to deinitialize. + */ union { struct { + /* + * 0-based index of the selected PSK. + * This only applies if the HSK_PSK_SELECTED flag is set in internals.hsk_flags, + * which signals a PSK has indeed been selected. + */ + unsigned psk_index; + const struct mac_entry_st *binder_prf; + /* the current (depending on state) secret, can be * early_secret, client_early_traffic_secret, ... */ uint8_t temp_secret[MAX_HASH_SIZE]; @@ -507,6 +525,10 @@ struct gnutls_key_st { } tls12; /* from ssl3.0 to tls12 */ } proto; + /* Pre-shared key in use (if any); temporary storage */ + gnutls_datum_t psk; + unsigned psk_needs_free; + /* TLS pre-master key; applies to 1.2 and 1.3 */ gnutls_datum_t key; @@ -849,6 +871,7 @@ struct gnutls_priority_st { bool allow_server_key_usage_violation; /* for test suite purposes only */ bool no_tickets; bool have_cbc; + bool have_psk; unsigned int additional_verify_flags; /* TLS_FALLBACK_SCSV */ @@ -1181,6 +1204,9 @@ typedef struct { /* it is a copy of the handshake hash buffer if post handshake is used */ gnutls_buffer_st post_handshake_hash_buffer; +/* When either of PSK or DHE-PSK is received */ +#define HSK_PSK_KE_MODES_RECEIVED (HSK_PSK_KE_MODE_PSK|HSK_PSK_KE_MODE_DHE_PSK|HSK_PSK_KE_MODE_INVALID) + #define HSK_CRT_VRFY_EXPECTED 1 #define HSK_CRT_SENT (1<<1) #define HSK_CRT_ASKED (1<<2) @@ -1192,10 +1218,25 @@ typedef struct { #define HSK_FALSE_START_USED (1<<8) /* TLS1.2 only */ #define HSK_HAVE_FFDHE (1<<9) /* whether the peer has advertized at least an FFDHE group */ #define HSK_USED_FFDHE (1<<10) /* whether ffdhe was actually negotiated and used */ +#define HSK_PSK_KE_MODES_SENT (1<<11) +#define HSK_PSK_KE_MODE_PSK (1<<12) /* client: whether PSK without DH is allowed, + * server: whether PSK without DH is selected. */ +#define HSK_PSK_KE_MODE_INVALID (1<<13) /* server: no compatible PSK modes were seen */ +#define HSK_PSK_KE_MODE_DHE_PSK (1<<14) /* server: whether PSK with DH is selected + * client: whether PSK with DH is allowed + */ +#define HSK_PSK_SELECTED (1<<15) +#define HSK_KEY_SHARE_SENT (1<<16) /* server: key share was sent to client */ +#define HSK_KEY_SHARE_RECEIVED (1<<17) /* client: key share was received */ + /* The hsk_flags are for use within the ongoing handshake; * they are reset to zero prior to handshake start by gnutls_handshake. */ unsigned hsk_flags; time_t last_key_update; + /* Read-only pointer to the full ClientHello message */ + gnutls_buffer_st full_client_hello; + /* The offset at which extensions start in the ClientHello buffer */ + int extensions_offset; gnutls_buffer_st hb_local_data; gnutls_buffer_st hb_remote_data; @@ -1289,6 +1330,10 @@ typedef struct { /* Maximum number of epochs we keep around. */ #define MAX_EPOCH_INDEX 4 +#define reset_cand_groups(session) \ + session->internals.cand_ec_group = session->internals.cand_dh_group = \ + session->internals.cand_group = NULL + struct gnutls_session_int { security_parameters_st security_parameters; record_parameters_st *record_parameters[MAX_EPOCH_INDEX]; diff --git a/lib/handshake-tls13.c b/lib/handshake-tls13.c index edb6e80574..de14cf106e 100644 --- a/lib/handshake-tls13.c +++ b/lib/handshake-tls13.c @@ -204,14 +204,46 @@ static int generate_ap_traffic_keys(gnutls_session_t session) static int generate_hs_traffic_keys(gnutls_session_t session) { int ret; + unsigned null_key = 0; - if (unlikely(session->key.key.size == 0 || session->key.proto.tls13.temp_secret_size == 0)) + if (unlikely(session->key.proto.tls13.temp_secret_size == 0)) return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); - ret = _tls13_update_secret(session, session->key.key.data, session->key.key.size); - if (ret < 0) { - gnutls_assert(); - return ret; + if ((session->security_parameters.entity == GNUTLS_CLIENT && + !(session->internals.hsk_flags & HSK_KEY_SHARE_RECEIVED)) || + (session->security_parameters.entity == GNUTLS_SERVER && + !(session->internals.hsk_flags & HSK_KEY_SHARE_SENT))) { + + if ((session->internals.hsk_flags & HSK_PSK_SELECTED) && + (session->internals.hsk_flags & HSK_PSK_KE_MODE_PSK)) { + null_key = 1; + } + } + + if (null_key) { + uint8_t digest[MAX_HASH_SIZE]; + unsigned digest_size; + + if (unlikely(session->security_parameters.prf == NULL)) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + digest_size = session->security_parameters.prf->output_size; + memset(digest, 0, digest_size); + + ret = _tls13_update_secret(session, digest, digest_size); + if (ret < 0) { + gnutls_assert(); + return ret; + } + } else { + if (unlikely(session->key.key.size == 0)) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + ret = _tls13_update_secret(session, session->key.key.data, session->key.key.size); + if (ret < 0) { + gnutls_assert(); + return ret; + } } ret = _tls13_connection_state_init(session, STAGE_HS); diff --git a/lib/handshake.c b/lib/handshake.c index 955fd5dd08..5d2bf9b852 100644 --- a/lib/handshake.c +++ b/lib/handshake.c @@ -83,6 +83,7 @@ handshake_hash_buffer_reset(gnutls_session_t session) session->internals.handshake_hash_buffer_server_finished_len = 0; session->internals.handshake_hash_buffer_prev_len = 0; session->internals.handshake_hash_buffer.length = 0; + session->internals.full_client_hello.length = 0; return; } @@ -108,6 +109,7 @@ void _gnutls_handshake_hash_buffers_clear(gnutls_session_t session) { handshake_hash_buffer_reset(session); _gnutls_buffer_clear(&session->internals.handshake_hash_buffer); + _gnutls_buffer_clear(&session->internals.full_client_hello); } /* Replace handshake message buffer, with the special synthetic message @@ -1409,8 +1411,15 @@ _gnutls_recv_handshake(gnutls_session_t session, hsk.data.length); else #endif + { + /* Reference the full ClientHello in case an extension needs it */ + ret = _gnutls_ext_set_full_client_hello(session, &hsk); + if (ret < 0) + return gnutls_assert_val(ret); + ret = read_client_hello(session, hsk.data.data, hsk.data.length); + } if (ret < 0) { gnutls_assert(); @@ -1581,6 +1590,15 @@ set_client_ciphersuite(gnutls_session_t session, uint8_t suite[2]) gnutls_assert(); return GNUTLS_E_INTERNAL_ERROR; } + } else { + if (session->internals.hsk_flags & HSK_PSK_SELECTED) { + if (session->key.proto.tls13.binder_prf->id != selected->prf) { + _gnutls_handshake_log + ("HSK[%p]: PRF of ciphersuite differs with the PSK identity (cs: %s, id: %s)\n", + session, selected->name, session->key.proto.tls13.binder_prf->name); + gnutls_assert(); + } + } } return 0; @@ -1659,6 +1677,8 @@ read_server_hello(gnutls_session_t session, int len = datalen; unsigned ext_parse_flag = 0; const version_entry_st *vers, *saved_vers; + const uint8_t *psk = NULL; + size_t psk_size = 0; if (datalen < GNUTLS_RANDOM_SIZE+2) { gnutls_assert(); @@ -1832,6 +1852,28 @@ read_server_hello(gnutls_session_t session, if (ret < 0) return gnutls_assert_val(ret); + if (vers->tls13_sem) { + /* TLS 1.3 Early Secret */ + if (session->internals.hsk_flags & HSK_PSK_SELECTED) { + psk = session->key.psk.data; + psk_size = session->key.psk.size; + } + + ret = _tls13_init_secret(session, psk, psk_size); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + ret = _tls13_derive_secret(session, DERIVED_LABEL, sizeof(DERIVED_LABEL)-1, + NULL, 0, session->key.proto.tls13.temp_secret, + session->key.proto.tls13.temp_secret); + if (ret < 0) + gnutls_assert(); + } + +cleanup: + return ret; } @@ -2081,6 +2123,8 @@ int _gnutls_send_server_hello(gnutls_session_t session, int again) const version_entry_st *vers; uint8_t vbytes[2]; unsigned extflag = 0; + const uint8_t *psk = NULL; + size_t psk_size = 0; _gnutls_buffer_init(&buf); @@ -2091,9 +2135,16 @@ int _gnutls_send_server_hello(gnutls_session_t session, int again) if (vers->tls13_sem) { /* TLS 1.3 Early Secret */ - ret = _tls13_init_secret(session, NULL, 0); - if (ret < 0) - return gnutls_assert_val(ret); + if (session->internals.hsk_flags & HSK_PSK_SELECTED) { + psk = session->key.psk.data; + psk_size = session->key.psk.size; + } + + ret = _tls13_init_secret(session, psk, psk_size); + if (ret < 0) { + gnutls_assert(); + goto fail; + } vbytes[0] = 0x03; /* TLS1.2 */ vbytes[1] = 0x03; @@ -2105,8 +2156,10 @@ int _gnutls_send_server_hello(gnutls_session_t session, int again) } ret = _gnutls_buffer_init_handshake_mbuffer(&buf); - if (ret < 0) - return gnutls_assert_val(ret); + if (ret < 0) { + gnutls_assert(); + goto fail; + } ret = _gnutls_buffer_append_data(&buf, vbytes, 2); if (ret < 0) { @@ -2180,7 +2233,7 @@ int _gnutls_send_server_hello(gnutls_session_t session, int again) _gnutls_send_handshake(session, bufel, GNUTLS_HANDSHAKE_SERVER_HELLO); - fail: +fail: _gnutls_buffer_clear(&buf); return ret; } @@ -2580,6 +2633,9 @@ int _gnutls_run_verify_callback(gnutls_session_t session, unsigned int side) gnutls_certificate_credentials_t cred; int ret, type; + if (session->internals.hsk_flags & HSK_PSK_SELECTED) + return 0; + cred = (gnutls_certificate_credentials_t) _gnutls_get_cred(session, GNUTLS_CRD_CERTIFICATE); diff --git a/lib/hello_ext.c b/lib/hello_ext.c index 7fe8e1056c..57583231a3 100644 --- a/lib/hello_ext.c +++ b/lib/hello_ext.c @@ -1,6 +1,6 @@ /* - * Copyright (C) 2001-2016 Free Software Foundation, Inc. - * Copyright (C) 2015-2017 Red Hat, Inc. + * Copyright (C) 2001-2018 Free Software Foundation, Inc. + * Copyright (C) 2015-2018 Red Hat, Inc. * * Author: Nikos Mavrogiannopoulos, Simon Josefsson * @@ -46,6 +46,8 @@ #include <ext/alpn.h> #include <ext/dumbfw.h> #include <ext/key_share.h> +#include <ext/pre_shared_key.h> +#include <ext/psk_ke_modes.h> #include <ext/etm.h> #include <ext/cookie.h> #include "extv.h" @@ -87,6 +89,8 @@ static hello_ext_entry_st const *extfunc[MAX_EXT_TYPES+1] = { #ifdef ENABLE_ALPN [GNUTLS_EXTENSION_ALPN] = &ext_mod_alpn, #endif + [GNUTLS_EXTENSION_PSK_KE_MODES] = &ext_psk_ke_modes, + [GNUTLS_EXTENSION_PRE_SHARED_KEY] = &ext_pre_shared_key, /* This must be the last extension registered. */ [GNUTLS_EXTENSION_DUMBFW] = &ext_mod_dumbfw, @@ -335,9 +339,9 @@ int hello_ext_send(void *_ctx, gnutls_buffer_st *buf) int _gnutls_gen_hello_extensions(gnutls_session_t session, - gnutls_buffer_st * buf, - gnutls_ext_flags_t msg, - gnutls_ext_parse_type_t parse_type) + gnutls_buffer_st * buf, + gnutls_ext_flags_t msg, + gnutls_ext_parse_type_t parse_type) { int pos, ret; size_t i; @@ -352,6 +356,7 @@ _gnutls_gen_hello_extensions(gnutls_session_t session, return gnutls_assert_val(ret); pos = ret; + _gnutls_ext_set_extensions_offset(session, pos); for (i=0; i < session->internals.rexts_size; i++) { ctx.ext = &session->internals.rexts[i]; @@ -481,6 +486,38 @@ int _gnutls_hello_ext_pack(gnutls_session_t session, gnutls_buffer_st *packed) return 0; } +int _gnutls_ext_set_full_client_hello(gnutls_session_t session, + handshake_buffer_st *recv_buf) +{ + int ret; + gnutls_buffer_st *buf = &session->internals.full_client_hello; + + _gnutls_buffer_clear(buf); + + if ((ret = _gnutls_buffer_append_prefix(buf, 8, recv_buf->htype)) < 0) + return gnutls_assert_val(ret); + if ((ret = _gnutls_buffer_append_prefix(buf, 24, recv_buf->data.length)) < 0) + return gnutls_assert_val(ret); + if ((ret = _gnutls_buffer_append_data(buf, recv_buf->data.data, recv_buf->data.length)) < 0) + return gnutls_assert_val(ret); + + return 0; +} + +unsigned _gnutls_ext_get_full_client_hello(gnutls_session_t session, + gnutls_datum_t *d) +{ + gnutls_buffer_st *buf = &session->internals.full_client_hello; + + if (!buf->length) + return 0; + + d->data = buf->data; + d->size = buf->length; + + return 1; +} + static void _gnutls_ext_set_resumed_session_data(gnutls_session_t session, extensions_t id, diff --git a/lib/hello_ext.h b/lib/hello_ext.h index 53e1d5eede..f0fcf056c7 100644 --- a/lib/hello_ext.h +++ b/lib/hello_ext.h @@ -63,6 +63,22 @@ inline static void _gnutls_ext_set_msg(gnutls_session_t session, gnutls_ext_flag session->internals.ext_msg = msg; } +inline static void _gnutls_ext_set_extensions_offset(gnutls_session_t session, + int offset) +{ + session->internals.extensions_offset = offset; +} + +inline static int _gnutls_ext_get_extensions_offset(gnutls_session_t session) +{ + return session->internals.extensions_offset; +} + +int _gnutls_ext_set_full_client_hello(gnutls_session_t session, + handshake_buffer_st *recv_buf); +unsigned _gnutls_ext_get_full_client_hello(gnutls_session_t session, + gnutls_datum_t *datum); + /* for session packing */ int _gnutls_hello_ext_pack(gnutls_session_t session, gnutls_buffer_st * packed); int _gnutls_hello_ext_unpack(gnutls_session_t session, diff --git a/lib/includes/gnutls/gnutls.h.in b/lib/includes/gnutls/gnutls.h.in index 8a00423336..cf7c61866c 100644 --- a/lib/includes/gnutls/gnutls.h.in +++ b/lib/includes/gnutls/gnutls.h.in @@ -2867,7 +2867,8 @@ void gnutls_fips140_set_mode(gnutls_fips_mode_t mode, unsigned flags); #define GNUTLS_E_AGAIN -28 #define GNUTLS_E_EXPIRED -29 #define GNUTLS_E_DB_ERROR -30 -#define GNUTLS_E_SRP_PWD_ERROR -31 +#define GNUTLS_E_SRP_PWD_ERROR GNUTLS_E_KEYFILE_ERROR +#define GNUTLS_E_KEYFILE_ERROR -31 #define GNUTLS_E_INSUFFICIENT_CREDENTIALS -32 #define GNUTLS_E_INSUFICIENT_CREDENTIALS GNUTLS_E_INSUFFICIENT_CREDENTIALS /* for backwards compatibility only */ #define GNUTLS_E_INSUFFICIENT_CRED GNUTLS_E_INSUFFICIENT_CREDENTIALS @@ -2916,7 +2917,8 @@ void gnutls_fips140_set_mode(gnutls_fips_mode_t mode, unsigned flags); #define GNUTLS_E_PK_SIG_VERIFY_FAILED -89 #define GNUTLS_E_ILLEGAL_SRP_USERNAME -90 -#define GNUTLS_E_SRP_PWD_PARSING_ERROR -91 +#define GNUTLS_E_SRP_PWD_PARSING_ERROR GNUTLS_E_KEYFILE_PARSING_ERROR +#define GNUTLS_E_KEYFILE_PARSING_ERROR -91 #define GNUTLS_E_NO_TEMPORARY_DH_PARAMS -93 /* For certificate and key stuff diff --git a/lib/libgnutls.map b/lib/libgnutls.map index 07aaf714cc..45aa9b9431 100644 --- a/lib/libgnutls.map +++ b/lib/libgnutls.map @@ -1218,6 +1218,7 @@ GNUTLS_3_6_3 } GNUTLS_3_6_2; GNUTLS_FIPS140_3_4 { + global: gnutls_cipher_self_test; gnutls_pk_self_test; gnutls_mac_self_test; @@ -1256,6 +1257,11 @@ GNUTLS_PRIVATE_3_4 { _gnutls_digest_exists; _gnutls_log; + # PSK extension parser unit test + _gnutls13_psk_ext_parser_init; + _gnutls13_psk_ext_parser_next_psk; + _gnutls13_psk_ext_parser_find_binder; + # Internal symbols needed by gnutls-cli-debug: _gnutls_rsa_pms_set_version; _gnutls_record_set_default_version; diff --git a/lib/priority.c b/lib/priority.c index 65b3dd3d93..fef7d5f9ba 100644 --- a/lib/priority.c +++ b/lib/priority.c @@ -105,6 +105,22 @@ static void _clear_given_priorities(priority_st * st, const int *list) } } +static const int _supported_groups_dh[] = { + GNUTLS_GROUP_FFDHE2048, + GNUTLS_GROUP_FFDHE3072, + GNUTLS_GROUP_FFDHE4096, + GNUTLS_GROUP_FFDHE8192, + 0 +}; + +static const int _supported_groups_ecdh[] = { + GNUTLS_GROUP_SECP256R1, + GNUTLS_GROUP_SECP384R1, + GNUTLS_GROUP_SECP521R1, + GNUTLS_GROUP_X25519, /* draft-ietf-tls-rfc4492bis */ + 0 +}; + static const int _supported_groups_normal[] = { GNUTLS_GROUP_SECP256R1, GNUTLS_GROUP_SECP384R1, @@ -1177,6 +1193,7 @@ static int set_ciphersuite_list(gnutls_priority_t priority_cache) const version_entry_st *tlsmin = NULL; const version_entry_st *dtlsmin = NULL; unsigned have_tls13 = 0; + unsigned have_psk = 0; priority_cache->cs.size = 0; priority_cache->sigalg.size = 0; @@ -1213,9 +1230,18 @@ static int set_ciphersuite_list(gnutls_priority_t priority_cache) if ((!tlsmax || !tlsmin) && (!dtlsmax || !dtlsmin)) return gnutls_assert_val(GNUTLS_E_NO_PRIORITIES_WERE_SET); + for (i = 0; i < priority_cache->_kx.algorithms; i++) { + if (_gnutls_kx_is_psk(priority_cache->_kx.priority[i])) { + have_psk = 1; + break; + } + } + + priority_cache->have_psk = have_psk; + /* if we are have TLS1.3+ do not enable any key exchange algorithms, * the protocol doesn't require any. */ - if (tlsmin && tlsmin->tls13_sem) { + if (tlsmin && tlsmin->tls13_sem && !have_psk) { if (!dtlsmin || (dtlsmin && dtlsmin->tls13_sem)) priority_cache->_kx.algorithms = 0; } @@ -1316,7 +1342,7 @@ static int set_ciphersuite_list(gnutls_priority_t priority_cache) return gnutls_assert_val(GNUTLS_E_NO_PRIORITIES_WERE_SET); /* when TLS 1.3 is available we must have groups set */ - if (tlsmax && tlsmax->id >= GNUTLS_TLS1_3 && priority_cache->groups.size == 0) + if (!have_psk && tlsmax && tlsmax->id >= GNUTLS_TLS1_3 && priority_cache->groups.size == 0) return gnutls_assert_val(GNUTLS_E_NO_PRIORITIES_WERE_SET); return 0; @@ -1575,6 +1601,18 @@ gnutls_priority_init(gnutls_priority_t * priority_cache, bulk_fn(&(*priority_cache)-> _supported_ecc, supported_groups_normal); + } else if (strncasecmp + (&broken_list[i][1], "GROUP-DH-ALL", + 12) == 0) { + bulk_given_fn(&(*priority_cache)-> + _supported_ecc, + _supported_groups_dh); + } else if (strncasecmp + (&broken_list[i][1], "GROUP-EC-ALL", + 12) == 0) { + bulk_given_fn(&(*priority_cache)-> + _supported_ecc, + _supported_groups_ecdh); } else { if ((algo = gnutls_group_get_id @@ -67,6 +67,8 @@ gnutls_psk_allocate_client_credentials(gnutls_psk_client_credentials_t * if (*sc == NULL) return GNUTLS_E_MEMORY_ERROR; + /* TLS 1.3 - Default binder HMAC algorithm is SHA-256 */ + (*sc)->binder_algo = _gnutls_mac_to_entry(GNUTLS_MAC_SHA256); return 0; } @@ -182,6 +184,8 @@ gnutls_psk_allocate_server_credentials(gnutls_psk_server_credentials_t * if (*sc == NULL) return GNUTLS_E_MEMORY_ERROR; + /* TLS 1.3 - Default binder HMAC algorithm is SHA-256 */ + (*sc)->binder_algo = _gnutls_mac_to_entry(GNUTLS_MAC_SHA256); return 0; } @@ -343,7 +347,10 @@ const char *gnutls_psk_server_get_username(gnutls_session_t session) * username to use. This should only be called in case of PSK * authentication and in case of a client. * - * Returns: the identity hint of the peer, or %NULL in case of an error. + * Note: there is no hint in TLS 1.3, so this function will return %NULL + * if TLS 1.3 has been negotiated. + * + * Returns: the identity hint of the peer, or %NULL in case of an error or if TLS 1.3 is being used. * * Since: 2.4.0 **/ diff --git a/lib/secrets.c b/lib/secrets.c index 73402f9e60..fed5198ae6 100644 --- a/lib/secrets.c +++ b/lib/secrets.c @@ -32,24 +32,36 @@ /* HKDF-Extract(0,0) or HKDF-Extract(0, PSK) */ int _tls13_init_secret(gnutls_session_t session, const uint8_t *psk, size_t psk_size) { + session->key.proto.tls13.temp_secret_size = session->security_parameters.prf->output_size; + + return _tls13_init_secret2(session->security_parameters.prf, + psk, psk_size, + session->key.proto.tls13.temp_secret); +} + +int _tls13_init_secret2(const mac_entry_st *prf, + const uint8_t *psk, size_t psk_size, + void *out) +{ char buf[128]; - session->key.proto.tls13.temp_secret_size = session->security_parameters.prf->output_size; + if (unlikely(prf == NULL)) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); /* when no PSK, use the zero-value */ if (psk == NULL) { - psk_size = session->key.proto.tls13.temp_secret_size; + psk_size = prf->output_size; if (unlikely(psk_size >= sizeof(buf))) return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); memset(buf, 0, psk_size); - psk = (uint8_t*)buf; + psk = (uint8_t*) buf; } - return gnutls_hmac_fast(session->security_parameters.prf->id, + return gnutls_hmac_fast(prf->id, "", 0, psk, psk_size, - session->key.proto.tls13.temp_secret); + out); } /* HKDF-Extract(Prev-Secret, key) */ @@ -62,7 +74,7 @@ int _tls13_update_secret(gnutls_session_t session, const uint8_t *key, size_t ke } /* Derive-Secret(Secret, Label, Messages) */ -int _tls13_derive_secret(gnutls_session_t session, +int _tls13_derive_secret2(const mac_entry_st *prf, const char *label, unsigned label_size, const uint8_t *tbh, size_t tbh_size, const uint8_t secret[MAX_HASH_SIZE], @@ -70,21 +82,39 @@ int _tls13_derive_secret(gnutls_session_t session, { uint8_t digest[MAX_HASH_SIZE]; int ret; - unsigned digest_size = session->security_parameters.prf->output_size; + unsigned digest_size; + if (unlikely(prf == NULL)) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); if (unlikely(label_size >= sizeof(digest))) return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); - ret = gnutls_hash_fast((gnutls_digest_algorithm_t)session->security_parameters.prf->id, + digest_size = prf->output_size; + ret = gnutls_hash_fast((gnutls_digest_algorithm_t) prf->id, tbh, tbh_size, digest); if (ret < 0) return gnutls_assert_val(ret); - return _tls13_expand_secret(session, label, label_size, digest, digest_size, secret, digest_size, out); + return _tls13_expand_secret2(prf, label, label_size, digest, digest_size, secret, digest_size, out); +} + +/* Derive-Secret(Secret, Label, Messages) */ +int _tls13_derive_secret(gnutls_session_t session, + const char *label, unsigned label_size, + const uint8_t *tbh, size_t tbh_size, + const uint8_t secret[MAX_HASH_SIZE], + void *out) +{ + if (unlikely(session->security_parameters.prf == NULL)) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + return _tls13_derive_secret2(session->security_parameters.prf, label, label_size, tbh, tbh_size, + secret, + out); } /* HKDF-Expand-Label(Secret, Label, HashValue, Length) */ -int _tls13_expand_secret(gnutls_session_t session, +int _tls13_expand_secret2(const mac_entry_st *prf, const char *label, unsigned label_size, const uint8_t *msg, size_t msg_size, const uint8_t secret[MAX_HASH_SIZE], @@ -119,7 +149,7 @@ int _tls13_expand_secret(gnutls_session_t session, goto cleanup; } - switch(session->security_parameters.prf->id) { + switch (prf->id) { case GNUTLS_MAC_SHA256:{ struct hmac_sha256_ctx ctx; @@ -160,3 +190,20 @@ int _tls13_expand_secret(gnutls_session_t session, _gnutls_buffer_clear(&str); return ret; } + +int _tls13_expand_secret(gnutls_session_t session, + const char *label, unsigned label_size, + const uint8_t *msg, size_t msg_size, + const uint8_t secret[MAX_CIPHER_KEY_SIZE], + unsigned out_size, + void *out) +{ + if (unlikely(session->security_parameters.prf == NULL)) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + return _tls13_expand_secret2(session->security_parameters.prf, + label, label_size, + msg, msg_size, secret, + out_size, out); +} + diff --git a/lib/secrets.h b/lib/secrets.h index 0dcdcf7c9c..92255ceaa6 100644 --- a/lib/secrets.h +++ b/lib/secrets.h @@ -22,14 +22,25 @@ #ifndef SECRETS_H #define SECRETS_H +#include "gnutls_int.h" int _tls13_init_secret(gnutls_session_t session, const uint8_t *psk, size_t psk_size); int _tls13_update_secret(gnutls_session_t session, const uint8_t *key, size_t key_size); + +int _tls13_init_secret2(const mac_entry_st *prf, + const uint8_t *psk, size_t psk_size, + void *out); + int _tls13_derive_secret(gnutls_session_t session, const char *label, unsigned label_size, const uint8_t *msg, size_t msg_size, const uint8_t secret[MAX_HASH_SIZE], void *out /* of enough length to hold PRF MAC */); +int _tls13_derive_secret2(const mac_entry_st *prf, + const char *label, unsigned label_size, + const uint8_t *tbh, size_t tbh_size, + const uint8_t secret[MAX_CIPHER_KEY_SIZE], + void *out); int _tls13_expand_secret(gnutls_session_t session, const char *label, unsigned label_size, @@ -37,5 +48,10 @@ int _tls13_expand_secret(gnutls_session_t session, const uint8_t secret[MAX_HASH_SIZE], unsigned out_size, void *out); +int _tls13_expand_secret2(const mac_entry_st *prf, + const char *label, unsigned label_size, + const uint8_t *msg, size_t msg_size, + const uint8_t secret[MAX_CIPHER_KEY_SIZE], + unsigned out_size, void *out); #endif diff --git a/lib/session.c b/lib/session.c index 6c2671d70e..3e29c15292 100644 --- a/lib/session.c +++ b/lib/session.c @@ -306,7 +306,18 @@ char *gnutls_session_get_desc(gnutls_session_t session) sign_str = gnutls_sign_get_name(sign_algo); if (kx == 0 && ver->tls13_sem) { /* TLS 1.3 */ - if (group && sign_str) { + if (session->internals.hsk_flags & HSK_PSK_SELECTED) { + if (group) { + if (group->pk == GNUTLS_PK_DH) + snprintf(kx_name, sizeof(kx_name), "(DHE-PSK-%s)", + group_name); + else + snprintf(kx_name, sizeof(kx_name), "(ECDHE-PSK-%s)", + group_name); + } else { + snprintf(kx_name, sizeof(kx_name), "(PSK)"); + } + } else if (group && sign_str) { if (group->curve) snprintf(kx_name, sizeof(kx_name), "(ECDHE-%s)-(%s)", group_name, sign_str); @@ -346,7 +357,7 @@ char *gnutls_session_get_desc(gnutls_session_t session) type = gnutls_certificate_type_get(session); - if (type == GNUTLS_CRT_X509) + if (type == GNUTLS_CRT_X509 || type == GNUTLS_CRT_UNKNOWN) snprintf(proto_name, sizeof(proto_name), "%s", gnutls_protocol_get_name(get_num_version (session))); diff --git a/lib/state.c b/lib/state.c index 708f7649c7..1062c446bf 100644 --- a/lib/state.c +++ b/lib/state.c @@ -119,11 +119,22 @@ gnutls_kx_algorithm_t gnutls_kx_get(gnutls_session_t session) const version_entry_st *ver = get_version(session); const gnutls_group_entry_st *group = get_group(session); - if (ver->tls13_sem && group) { - if (group->curve) - return GNUTLS_KX_ECDHE_RSA; - else - return GNUTLS_KX_DHE_RSA; + if (ver->tls13_sem) { + if (session->internals.hsk_flags & HSK_PSK_SELECTED) { + if (group) { + if (group->pk == GNUTLS_PK_DH) + return GNUTLS_KX_DHE_PSK; + else + return GNUTLS_KX_ECDHE_PSK; + } else { + return GNUTLS_KX_PSK; + } + } else if (group) { + if (group->pk == GNUTLS_PK_DH) + return GNUTLS_KX_DHE_RSA; + else + return GNUTLS_KX_ECDHE_RSA; + } } } @@ -207,6 +218,8 @@ static void deinit_keys(gnutls_session_t session) sizeof(session->key.proto.tls13.hs_skey)); } + if (session->key.psk_needs_free) + _gnutls_free_temp_key_datum(&session->key.psk); _gnutls_free_temp_key_datum(&session->key.key); } @@ -279,7 +292,7 @@ void _gnutls_handshake_internal_state_clear(gnutls_session_t session) int gnutls_init(gnutls_session_t * session, unsigned int flags) { int ret; - + FAIL_IF_LIB_ERROR; *session = gnutls_calloc(1, sizeof(struct gnutls_session_int)); diff --git a/lib/tls13/certificate.c b/lib/tls13/certificate.c index ad05f372c5..d4abd58702 100644 --- a/lib/tls13/certificate.c +++ b/lib/tls13/certificate.c @@ -38,6 +38,9 @@ int _gnutls13_recv_certificate(gnutls_session_t session) gnutls_buffer_st buf; unsigned optional = 0; + if (session->internals.hsk_flags & HSK_PSK_SELECTED) + return 0; + if (session->security_parameters.entity == GNUTLS_SERVER) { /* if we didn't request a certificate, there will not be any */ if (session->internals.send_cert_req == 0) @@ -194,6 +197,9 @@ int _gnutls13_send_certificate(gnutls_session_t session, unsigned again) gnutls_certificate_credentials_t cred; if (again == 0) { + if (session->internals.hsk_flags & HSK_PSK_SELECTED) + return 0; + cred = (gnutls_certificate_credentials_t) _gnutls_get_cred(session, GNUTLS_CRD_CERTIFICATE); if (cred == NULL) { diff --git a/lib/tls13/certificate_request.c b/lib/tls13/certificate_request.c index 4e7c104afb..293cc38dcf 100644 --- a/lib/tls13/certificate_request.c +++ b/lib/tls13/certificate_request.c @@ -187,6 +187,9 @@ int _gnutls13_recv_certificate_request(gnutls_session_t session) int ret; gnutls_buffer_st buf; + if (session->internals.hsk_flags & HSK_PSK_SELECTED) + return 0; + if (unlikely(session->security_parameters.entity != GNUTLS_CLIENT)) return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); @@ -246,6 +249,9 @@ int _gnutls13_send_certificate_request(gnutls_session_t session, unsigned again) if (again == 0) { unsigned char rnd[12]; + if (session->internals.hsk_flags & HSK_PSK_SELECTED) + return 0; + if (session->internals.send_cert_req == 0) return 0; diff --git a/lib/tls13/certificate_verify.c b/lib/tls13/certificate_verify.c index 995e0c6058..33318ca1cf 100644 --- a/lib/tls13/certificate_verify.c +++ b/lib/tls13/certificate_verify.c @@ -142,6 +142,9 @@ int _gnutls13_send_certificate_verify(gnutls_session_t session, unsigned again) const gnutls_sign_entry_st *se; if (again == 0) { + if (session->internals.hsk_flags & HSK_PSK_SELECTED) + return 0; + ret = _gnutls_get_selected_cert(session, &apr_cert_list, &apr_cert_list_length, &apr_pkey); if (ret < 0) diff --git a/lib/tls13/finished.c b/lib/tls13/finished.c index 9286f328f6..c28d24a19d 100644 --- a/lib/tls13/finished.c +++ b/lib/tls13/finished.c @@ -28,40 +28,63 @@ #include "mbuffers.h" #include "secrets.h" -int _gnutls13_recv_finished(gnutls_session_t session) +int _gnutls13_compute_finished(const mac_entry_st *prf, + const uint8_t *base_key, + unsigned hash_size, + gnutls_buffer_st *handshake_hash_buffer, + void *out) { int ret; - gnutls_buffer_st buf; uint8_t fkey[MAX_HASH_SIZE]; uint8_t ts_hash[MAX_HASH_SIZE]; - uint8_t verifier[MAX_HASH_SIZE]; - const uint8_t *base_key; - unsigned hash_size = session->security_parameters.prf->output_size; - if (session->security_parameters.entity == GNUTLS_CLIENT) - base_key = session->key.proto.tls13.hs_skey; - else - base_key = session->key.proto.tls13.hs_ckey; - - ret = _tls13_expand_secret(session, "finished", 8, NULL, 0, base_key, + ret = _tls13_expand_secret2(prf, + "finished", 8, + NULL, 0, + base_key, hash_size, fkey); if (ret < 0) return gnutls_assert_val(ret); - ret = gnutls_hash_fast(session->security_parameters.prf->id, - session->internals.handshake_hash_buffer.data, - /* we haven't yet processed the finished message */ - session->internals.handshake_hash_buffer.length, + ret = gnutls_hash_fast(prf->id, + handshake_hash_buffer->data, + handshake_hash_buffer->length, ts_hash); - if (ret < 0) { - gnutls_assert(); - goto cleanup; - } + if (ret < 0) + return gnutls_assert_val(ret); - ret = gnutls_hmac_fast(session->security_parameters.prf->id, + ret = gnutls_hmac_fast(prf->id, fkey, hash_size, ts_hash, hash_size, - verifier); + out); + if (ret < 0) + return gnutls_assert_val(ret); + + return 0; +} + +int _gnutls13_recv_finished(gnutls_session_t session) +{ + int ret; + gnutls_buffer_st buf; + uint8_t verifier[MAX_HASH_SIZE]; + const uint8_t *base_key; + unsigned hash_size; + + if (unlikely(session->security_parameters.prf == NULL)) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + hash_size = session->security_parameters.prf->output_size; + + if (session->security_parameters.entity == GNUTLS_CLIENT) + base_key = session->key.proto.tls13.hs_skey; + else + base_key = session->key.proto.tls13.hs_ckey; + + ret = _gnutls13_compute_finished(session->security_parameters.prf, + base_key, hash_size, + &session->internals.handshake_hash_buffer, + verifier); if (ret < 0) { gnutls_assert(); goto cleanup; @@ -96,37 +119,26 @@ cleanup: int _gnutls13_send_finished(gnutls_session_t session, unsigned again) { int ret; - uint8_t fkey[MAX_HASH_SIZE]; - uint8_t ts_hash[MAX_HASH_SIZE]; uint8_t verifier[MAX_HASH_SIZE]; mbuffer_st *bufel = NULL; const uint8_t *base_key; - unsigned hash_size = session->security_parameters.prf->output_size; + unsigned hash_size; if (again == 0) { + if (unlikely(session->security_parameters.prf == NULL)) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + hash_size = session->security_parameters.prf->output_size; + if (session->security_parameters.entity == GNUTLS_CLIENT) base_key = session->key.proto.tls13.hs_ckey; else base_key = session->key.proto.tls13.hs_skey; - ret = _tls13_expand_secret(session, "finished", 8, NULL, 0, base_key, - hash_size, fkey); - if (ret < 0) - return gnutls_assert_val(ret); - - ret = gnutls_hash_fast(session->security_parameters.prf->id, - session->internals.handshake_hash_buffer.data, - session->internals.handshake_hash_buffer.length, - ts_hash); - if (ret < 0) { - gnutls_assert(); - goto cleanup; - } - - ret = gnutls_hmac_fast(session->security_parameters.prf->id, - fkey, hash_size, - ts_hash, hash_size, - verifier); + ret = _gnutls13_compute_finished(session->security_parameters.prf, + base_key, hash_size, + &session->internals.handshake_hash_buffer, + verifier); if (ret < 0) { gnutls_assert(); goto cleanup; diff --git a/lib/tls13/finished.h b/lib/tls13/finished.h index fc49c2e63f..2e732e7493 100644 --- a/lib/tls13/finished.h +++ b/lib/tls13/finished.h @@ -20,5 +20,10 @@ * */ +int _gnutls13_compute_finished(const mac_entry_st *prf, + const uint8_t *base_key, + unsigned hash_size, + gnutls_buffer_st *handshake_hash_buffer, + void *out); int _gnutls13_recv_finished(gnutls_session_t session); int _gnutls13_send_finished(gnutls_session_t session, unsigned again); diff --git a/lib/tls13/psk_ext_parser.c b/lib/tls13/psk_ext_parser.c new file mode 100644 index 0000000000..9f4087773d --- /dev/null +++ b/lib/tls13/psk_ext_parser.c @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2017-2018 Free Software Foundation, Inc. + * Copyright (C) 2018 Red Hat, Inc. + * + * Author: Ander Juaristi, 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 "tls13/psk_ext_parser.h" + +/* Returns GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE when no identities + * are present, or >= 0, on success. + */ +int _gnutls13_psk_ext_parser_init(psk_ext_parser_st *p, + const unsigned char *data, size_t _len) +{ + uint16_t identities_len; + ssize_t len = _len; + + if (!p || !data || !len) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + memset(p, 0, sizeof(*p)); + + DECR_LEN(len, 2); + identities_len = _gnutls_read_uint16(data); + data += 2; + + if (identities_len == 0) + return gnutls_assert_val(GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE); + + p->id_len = identities_len; + p->data = (unsigned char *) data; + p->len = len; + + DECR_LEN(len, p->id_len); + data += p->id_len; + + DECR_LEN(len, 2); + p->binder_len = _gnutls_read_uint16(data); + + p->binder_data = p->data + p->id_len + 2; + DECR_LEN(len, p->binder_len); + + return 0; +} + +int _gnutls13_psk_ext_parser_next_psk(psk_ext_parser_st *p, struct psk_st *psk) +{ + if (p->id_read >= p->id_len) + return GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE; + + /* Read a PskIdentity structure */ + DECR_LEN(p->len, 2); + psk->identity.size = _gnutls_read_uint16(p->data); + if (psk->identity.size == 0) + return GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE; + + p->data += 2; + p->id_read += 2; + + psk->identity.data = (void*)p->data; + + DECR_LEN(p->len, psk->identity.size); + p->data += psk->identity.size; + p->id_read += psk->identity.size; + + DECR_LEN(p->len, 4); + psk->ob_ticket_age = _gnutls_read_uint32(p->data); + + p->data += 4; + p->id_read += 4; + + return p->next_index++; +} + +/* Output is a pointer to data, which shouldn't be de-allocated. */ +int _gnutls13_psk_ext_parser_find_binder(psk_ext_parser_st *p, int psk_index, + gnutls_datum_t *binder_out) +{ + uint8_t binder_len; + int cur_index = 0, binder_found = 0; + ssize_t len; + const uint8_t *data; + ssize_t read_data = 0; + + if (p == NULL || psk_index < 0 || binder_out == NULL) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + if (p->id_len == 0) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + len = p->binder_len; + data = p->binder_data; + + if (len == 0) + return gnutls_assert_val(GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE); + + /* Start traversing the binders */ + while (!binder_found && len > 0) { + DECR_LEN(len, 1); + binder_len = *(data); + + if (binder_len == 0) + return gnutls_assert_val(GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER); + + data++; + read_data++; + + if (cur_index == psk_index) { + /* We found the binder with the supplied index */ + DECR_LEN(len, binder_len); + binder_out->data = (void*)data; + binder_out->size = binder_len; + + data += binder_len; + read_data += binder_len; + + binder_found = 1; + } else { + /* Not our binder - continue to the next one */ + DECR_LEN(len, binder_len); + data += binder_len; + read_data += binder_len; + + cur_index++; + } + } + + if (binder_found) + return 0; + else + return gnutls_assert_val(GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE); +} diff --git a/lib/tls13/psk_ext_parser.h b/lib/tls13/psk_ext_parser.h new file mode 100644 index 0000000000..2c6ab603ae --- /dev/null +++ b/lib/tls13/psk_ext_parser.h @@ -0,0 +1,52 @@ +/* + * 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 <http://www.gnu.org/licenses/> + * + */ + +#ifndef PSK_PARSER_H +#define PSK_PARSER_H +#include <gnutls/gnutls.h> + +struct psk_ext_parser_st { + const unsigned char *data; + ssize_t len; + size_t id_len; + size_t id_read; + int next_index; + + const unsigned char *binder_data; + ssize_t binder_len; +}; + +typedef struct psk_ext_parser_st psk_ext_parser_st; + +struct psk_st { + /* constant values */ + gnutls_datum_t identity; + uint32_t ob_ticket_age; +}; + +int _gnutls13_psk_ext_parser_init(psk_ext_parser_st *p, + const unsigned char *data, size_t len); +int _gnutls13_psk_ext_parser_next_psk(psk_ext_parser_st *p, struct psk_st *psk); +int _gnutls13_psk_ext_parser_find_binder(psk_ext_parser_st *p, int psk_index, + gnutls_datum_t *binder_out); + +#endif diff --git a/tests/Makefile.am b/tests/Makefile.am index 5abd976ff8..2536d3b8f1 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -108,6 +108,8 @@ ctests = tls13/supported_versions tls13/tls12-no-tls13-exts \ tls12-rollback-detection tls11-rollback-detection \ tls12-check-rollback-val tls11-check-rollback-val tls13/hello_random_value +ctests += tls13/psk-ext + ctests += tls13/key_update ctests += tls13/key_limits @@ -120,6 +122,8 @@ ctests += tls13/change_cipher_spec ctests += tls13-cipher-neg +ctests += tls13/no-psk-exts + ctests += mini-record-2 simple gnutls_hmac_fast set_pkcs12_cred cert certuniqueid tls-neg-ext-key \ mpi certificate_set_x509_crl dn parse_ca x509-dn x509-dn-decode record-sizes \ hostname-check cve-2008-4989 pkcs12_s2k chainverify record-sizes-range \ diff --git a/tests/fastopen.sh b/tests/fastopen.sh index 5d58b35028..87f3b24bba 100755 --- a/tests/fastopen.sh +++ b/tests/fastopen.sh @@ -48,13 +48,20 @@ SERV="${SERV} -q" echo "Checking Fast open" +KEY1=${srcdir}/../doc/credentials/x509/key-rsa.pem +CERT1=${srcdir}/../doc/credentials/x509/cert-rsa.pem +CA1=${srcdir}/../doc/credentials/x509/ca.pem + eval "${GETPORT}" -launch_server $$ --echo --priority "NORMAL:+ANON-ECDH" +launch_server $$ --echo --x509keyfile ${KEY1} --x509certfile ${CERT1} PID=$! wait_server ${PID} -${VALGRIND} "${CLI}" -p "${PORT}" 127.0.0.1 --fastopen --rehandshake --priority "NORMAL:+ANON-ECDH:+ANON-DH" </dev/null >/dev/null || \ - fail ${PID} "1. rehandshake should have succeeded!" +${VALGRIND} "${CLI}" -p "${PORT}" localhost --fastopen --priority "NORMAL:-VERS-ALL:+VERS-TLS1.2" --x509cafile ${CA1} </dev/null || \ + fail ${PID} "1. TLS1.2 handshake should have succeeded!" + +${VALGRIND} "${CLI}" -p "${PORT}" localhost --fastopen --x509cafile ${CA1} </dev/null || \ + fail ${PID} "2. handshake should have succeeded!" kill ${PID} diff --git a/tests/psk-file.c b/tests/psk-file.c index ee6f8c533e..a6df3f0467 100644 --- a/tests/psk-file.c +++ b/tests/psk-file.c @@ -65,14 +65,13 @@ static void tls_log_func(int level, const char *str) #define MAX_BUF 1024 #define MSG "Hello TLS" -static void client(int sd, const char *prio, const char *user, unsigned expect_fail) +static void client(int sd, const char *prio, const char *user, const gnutls_datum_t *key, + unsigned expect_hint, int expect_fail, int exp_kx) { - int ret, ii; + int ret, ii, kx; gnutls_session_t session; char buffer[MAX_BUF + 1]; gnutls_psk_client_credentials_t pskcred; - /* Need to enable anonymous KX specifically. */ - const gnutls_datum_t key = { (void *) "9e32cf7786321a828ef7668f09fb35db", 32 }; const char *hint; global_init(); @@ -83,7 +82,7 @@ static void client(int sd, const char *prio, const char *user, unsigned expect_f side = "client"; gnutls_psk_allocate_client_credentials(&pskcred); - gnutls_psk_set_client_credentials(pskcred, user, &key, + gnutls_psk_set_client_credentials(pskcred, user, key, GNUTLS_PSK_KEY_HEX); /* Initialize TLS session @@ -106,7 +105,10 @@ static void client(int sd, const char *prio, const char *user, unsigned expect_f if (ret < 0) { if (!expect_fail) fail("client: Handshake failed\n"); - gnutls_perror(ret); + if (ret != expect_fail) { + fail("expected cli error %d (%s), got %d (%s)\n", expect_fail, gnutls_strerror(expect_fail), + ret, gnutls_strerror(ret)); + } goto end; } else { if (debug) @@ -114,10 +116,12 @@ static void client(int sd, const char *prio, const char *user, unsigned expect_f } /* check the hint */ - hint = gnutls_psk_client_get_hint(session); - if (hint == NULL || strcmp(hint, "hint") != 0) { - fail("client: hint is not the expected: %s\n", gnutls_psk_client_get_hint(session)); - goto end; + if (expect_hint) { + hint = gnutls_psk_client_get_hint(session); + if (hint == NULL || strcmp(hint, "hint") != 0) { + fail("client: hint is not the expected: %s\n", gnutls_psk_client_get_hint(session)); + goto end; + } } gnutls_record_send(session, MSG, strlen(MSG)); @@ -133,6 +137,8 @@ static void client(int sd, const char *prio, const char *user, unsigned expect_f goto end; } + kx = gnutls_kx_get(session); + if (debug) { printf("- Received %d bytes: ", ret); for (ii = 0; ii < ret; ii++) { @@ -143,6 +149,15 @@ static void client(int sd, const char *prio, const char *user, unsigned expect_f gnutls_bye(session, GNUTLS_SHUT_RDWR); + if (expect_fail) + fail("client: expected failure but connection succeeded!\n"); + + if (exp_kx && kx != exp_kx) { + fail("client: expected key exchange %s, but got %s\n", + gnutls_kx_get_name(exp_kx), + gnutls_kx_get_name(kx)); + } + end: close(sd); @@ -159,13 +174,15 @@ static void client(int sd, const char *prio, const char *user, unsigned expect_f #define MAX_BUF 1024 -static void server(int sd, const char *prio, const char *user, unsigned expect_fail) +static void server(int sd, const char *prio, const char *user, int expect_fail, int exp_kx) { gnutls_psk_server_credentials_t server_pskcred; - int ret; + int ret, kx; gnutls_session_t session; + const char *pskid; char buffer[MAX_BUF + 1]; char *psk_file = getenv("PSK_FILE"); + char *desc; /* this must be called once in the program */ @@ -197,17 +214,22 @@ static void server(int sd, const char *prio, const char *user, unsigned expect_f gnutls_transport_set_int(session, sd); ret = gnutls_handshake(session); if (ret < 0) { - close(sd); - gnutls_deinit(session); - gnutls_psk_free_server_credentials(server_pskcred); + gnutls_alert_send_appropriate(session, ret); if (expect_fail) { - success("server: Handshake has failed - expected (%s)\n\n", - gnutls_strerror(ret)); + if (ret != expect_fail) { + fail("expected error %d (%s), got %d (%s)\n", expect_fail, + gnutls_strerror(expect_fail), + ret, gnutls_strerror(ret)); + } + + if (debug) + success("server: Handshake has failed - expected (%s)\n\n", + gnutls_strerror(ret)); } else { fail("server: Handshake has failed (%s)\n\n", gnutls_strerror(ret)); } - return; + goto end; } if (debug) success("server: Handshake was completed\n"); @@ -235,10 +257,32 @@ static void server(int sd, const char *prio, const char *user, unsigned expect_f strlen(buffer)); } } + + kx = gnutls_kx_get(session); + + desc = gnutls_session_get_desc(session); + success(" - connected with: %s\n", desc); + gnutls_free(desc); /* do not wait for the peer to close the connection. */ gnutls_bye(session, GNUTLS_SHUT_WR); + if (expect_fail) + fail("server: expected failure but connection succeeded!\n"); + + pskid = gnutls_psk_server_get_username(session); + if (pskid == NULL || strcmp(pskid, user) != 0) { + fail("server: username (%s), does not match expected (%s)\n", + pskid, user); + } + + if (exp_kx && kx != exp_kx) { + fail("server: expected key exchange %s, but got %s\n", + gnutls_kx_get_name(exp_kx), + gnutls_kx_get_name(kx)); + } + + end: close(sd); gnutls_deinit(session); @@ -251,13 +295,18 @@ static void server(int sd, const char *prio, const char *user, unsigned expect_f } static -void run_test(const char *prio, const char *user, unsigned expect_fail) +void run_test2(const char *prio, const char *sprio, const char *user, const gnutls_datum_t *key, + unsigned expect_hint, int exp_kx, int expect_fail_cli, int expect_fail_serv) { pid_t child; int err; int sockets[2]; - success("trying %s / user:%s\n", prio, user); + if (expect_fail_serv || expect_fail_cli) { + success("ntest %s (user:%s)\n", prio, user); + } else { + success("test %s (user:%s)\n", prio, user); + } err = socketpair(AF_UNIX, SOCK_STREAM, 0, sockets); if (err == -1) { @@ -277,22 +326,72 @@ void run_test(const char *prio, const char *user, unsigned expect_fail) close(sockets[1]); int status; /* parent */ - server(sockets[0], prio, user, expect_fail); + server(sockets[0], sprio?sprio:prio, user, expect_fail_serv, exp_kx); wait(&status); check_wait_status(status); } else { close(sockets[0]); - client(sockets[1], prio, user, expect_fail); + client(sockets[1], prio, user, key, expect_hint, expect_fail_cli, exp_kx); exit(0); } } +static +void run_test_ok(const char *prio, const char *user, const gnutls_datum_t *key, unsigned expect_hint, int expect_fail) +{ + return run_test2(prio, NULL, user, key, expect_hint, GNUTLS_KX_PSK, expect_fail, expect_fail); +} + +static +void run_ectest_ok(const char *prio, const char *user, const gnutls_datum_t *key, unsigned expect_hint, int expect_fail) +{ + return run_test2(prio, NULL, user, key, expect_hint, GNUTLS_KX_ECDHE_PSK, expect_fail, expect_fail); +} + +static +void run_dhtest_ok(const char *prio, const char *user, const gnutls_datum_t *key, unsigned expect_hint, int expect_fail) +{ + return run_test2(prio, NULL, user, key, expect_hint, GNUTLS_KX_DHE_PSK, expect_fail, expect_fail); +} + void doit(void) { - run_test("NORMAL:-VERS-ALL:+VERS-TLS1.2:-KX-ALL:+PSK", "jas", 0); - run_test("NORMAL:-KX-ALL:+PSK", "jas", 0); - run_test("NORMAL:-VERS-ALL:+VERS-TLS1.2:-KX-ALL:+PSK", "non-hex", 1); - run_test("NORMAL:-KX-ALL:+PSK", "non-hex", 1); + const gnutls_datum_t key = { (void *) "9e32cf7786321a828ef7668f09fb35db", 32 }; + const gnutls_datum_t wrong_key = { (void *) "9e31cf7786321a828ef7668f09fb35db", 32 }; + + run_test_ok("NORMAL:-VERS-ALL:+VERS-TLS1.2:-KX-ALL:+PSK", "jas", &key, 1, 0); + run_dhtest_ok("NORMAL:-VERS-ALL:+VERS-TLS1.2:-KX-ALL:+DHE-PSK", "jas", &key, 1, 0); + run_ectest_ok("NORMAL:-VERS-ALL:+VERS-TLS1.2:-KX-ALL:+ECDHE-PSK", "jas", &key, 1, 0); + run_test2("NORMAL:-VERS-ALL:+VERS-TLS1.2:-KX-ALL:+PSK", NULL, "unknown", &key, 1, 0, GNUTLS_E_FATAL_ALERT_RECEIVED, GNUTLS_E_DECRYPTION_FAILED); + run_test2("NORMAL:-VERS-ALL:+VERS-TLS1.2:-KX-ALL:+PSK", NULL, "jas", &wrong_key, 1, 0, GNUTLS_E_FATAL_ALERT_RECEIVED, GNUTLS_E_DECRYPTION_FAILED); + run_test2("NORMAL:-VERS-ALL:+VERS-TLS1.2:-KX-ALL:+PSK", NULL, "non-hex", &key, 1, 0, GNUTLS_E_FATAL_ALERT_RECEIVED, GNUTLS_E_KEYFILE_ERROR); + + run_test_ok("NORMAL:-KX-ALL:+PSK", "jas", &key, 1, 0); + run_test2("NORMAL:+PSK", NULL, "unknown", &key, 1, 0, GNUTLS_E_FATAL_ALERT_RECEIVED, GNUTLS_E_DECRYPTION_FAILED); + run_test2("NORMAL:+PSK", NULL, "jas", &wrong_key, 1, 0, GNUTLS_E_FATAL_ALERT_RECEIVED, GNUTLS_E_DECRYPTION_FAILED); + run_test2("NORMAL:-KX-ALL:+PSK", NULL, "non-hex", &key, 1, 0, GNUTLS_E_FATAL_ALERT_RECEIVED, GNUTLS_E_KEYFILE_ERROR); + + run_dhtest_ok("NORMAL:-VERS-ALL:+VERS-TLS1.3:+DHE-PSK:-GROUP-EC-ALL", "jas", &key, 0, 0); + run_test_ok("NORMAL:-VERS-ALL:+VERS-TLS1.3:+PSK", "jas", &key, 0, 0); + + /* test priorities of DHE-PSK and PSK */ + run_ectest_ok("NORMAL:-VERS-ALL:+VERS-TLS1.3:-CIPHER-ALL:+AES-128-GCM:+DHE-PSK:+PSK:-GROUP-DH-ALL", "jas", &key, 0, 0); + run_test_ok("NORMAL:-VERS-ALL:+VERS-TLS1.3:-CIPHER-ALL:+AES-128-GCM:+PSK:+DHE-PSK:-GROUP-DH-ALL", "jas", &key, 0, 0); + run_test2("NORMAL:-VERS-ALL:+VERS-TLS1.3:-CIPHER-ALL:+AES-128-GCM:+DHE-PSK:+PSK:-GROUP-DH-ALL", + "NORMAL:-VERS-ALL:+VERS-TLS1.3:-CIPHER-ALL:+AES-128-GCM:+PSK:+DHE-PSK:%SERVER_PRECEDENCE:-GROUP-DH-ALL", + "jas", &key, 0, GNUTLS_KX_PSK, 0, 0); + /* try with PRF that doesn't match binder (SHA256) */ + run_test2("NORMAL:-VERS-ALL:+VERS-TLS1.3:-CIPHER-ALL:+AES-256-GCM:+PSK:+DHE-PSK", NULL, "jas", &key, 0, 0, GNUTLS_E_FATAL_ALERT_RECEIVED, GNUTLS_E_INSUFFICIENT_SECURITY); + /* try with no groups and PSK */ + run_test_ok("NORMAL:-VERS-ALL:+VERS-TLS1.3:+PSK:-GROUP-ALL", "jas", &key, 0, 0); + /* try without any groups but DHE-PSK */ + run_test2("NORMAL:-VERS-ALL:+VERS-TLS1.3:+DHE-PSK:-GROUP-ALL", "NORMAL:-VERS-ALL:+VERS-TLS1.3:+DHE-PSK:+PSK", "jas", &key, 0, 0, GNUTLS_E_FATAL_ALERT_RECEIVED, GNUTLS_E_NO_COMMON_KEY_SHARE); + run_test2("NORMAL:-VERS-ALL:+VERS-TLS1.3:+DHE-PSK:-GROUP-ALL", "NORMAL:-VERS-ALL:+VERS-TLS1.3:+DHE-PSK:+PSK:-GROUP-ALL", "jas", &key, 0, 0, GNUTLS_E_FATAL_ALERT_RECEIVED, GNUTLS_E_NO_COMMON_KEY_SHARE); + + /* if user invalid we continue without PSK */ + run_test2("NORMAL:-VERS-ALL:+VERS-TLS1.3:+PSK:+DHE-PSK", NULL, "non-hex", &key, 0, 0, GNUTLS_E_FATAL_ALERT_RECEIVED, GNUTLS_E_INSUFFICIENT_CREDENTIALS); + run_test2("NORMAL:-VERS-ALL:+VERS-TLS1.3:+PSK:+DHE-PSK", NULL, "unknown", &key, 0, 0, GNUTLS_E_FATAL_ALERT_RECEIVED, GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER); + run_test2("NORMAL:-VERS-ALL:+VERS-TLS1.3:+PSK:+DHE-PSK", NULL, "jas", &wrong_key, 0, 0, GNUTLS_E_FATAL_ALERT_RECEIVED, GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER); } #endif /* _WIN32 */ diff --git a/tests/pskself.c b/tests/pskself.c index 293cb59561..9580abe86d 100644 --- a/tests/pskself.c +++ b/tests/pskself.c @@ -64,7 +64,7 @@ static void tls_log_func(int level, const char *str) #define MAX_BUF 1024 #define MSG "Hello TLS" -static void client(int sd, const char *prio) +static void client(int sd, const char *prio, unsigned exp_hint) { int ret, ii; gnutls_session_t session; @@ -112,10 +112,12 @@ static void client(int sd, const char *prio) } /* check the hint */ - hint = gnutls_psk_client_get_hint(session); - if (hint == NULL || strcmp(hint, "hint") != 0) { - fail("client: hint is not the expected: %s\n", gnutls_psk_client_get_hint(session)); - goto end; + if (exp_hint) { + hint = gnutls_psk_client_get_hint(session); + if (hint == NULL || strcmp(hint, "hint") != 0) { + fail("client: hint is not the expected: %s\n", gnutls_psk_client_get_hint(session)); + goto end; + } } gnutls_record_send(session, MSG, strlen(MSG)); @@ -274,12 +276,14 @@ char buffer[MAX_BUF + 1]; } static -void run_test(const char *prio) +void run_test(const char *prio, unsigned exp_hint) { pid_t child; int err; int sockets[2]; + success("trying with %s\n", prio); + err = socketpair(AF_UNIX, SOCK_STREAM, 0, sockets); if (err == -1) { perror("socketpair"); @@ -303,7 +307,7 @@ void run_test(const char *prio) check_wait_status(status); } else { close(sockets[0]); - client(sockets[1], prio); + client(sockets[1], prio, exp_hint); exit(0); } } @@ -312,13 +316,19 @@ void doit(void) { generate_dh_params(); - run_test("NORMAL:-VERS-ALL:+VERS-TLS1.2:-KX-ALL:+PSK"); - run_test("NORMAL:-VERS-ALL:+VERS-TLS1.2:-KX-ALL:+ECDHE-PSK"); - run_test("NORMAL:-VERS-ALL:+VERS-TLS1.2:-KX-ALL:+DHE-PSK"); + run_test("NORMAL:-VERS-ALL:+VERS-TLS1.2:-KX-ALL:+PSK", 1); + run_test("NORMAL:-VERS-ALL:+VERS-TLS1.2:-KX-ALL:+ECDHE-PSK", 1); + run_test("NORMAL:-VERS-ALL:+VERS-TLS1.2:-KX-ALL:+DHE-PSK", 1); + + run_test("NORMAL:-VERS-ALL:+VERS-TLS1.3:+PSK", 0); + run_test("NORMAL:-VERS-ALL:+VERS-TLS1.3:-GROUP-ALL:+GROUP-FFDHE2048:+DHE-PSK", 0); + run_test("NORMAL:-VERS-ALL:+VERS-TLS1.3:-GROUP-ALL:+GROUP-SECP256R1:+ECDHE-PSK", 0); + /* the follow should work once we support PSK without DH */ + run_test("NORMAL:-VERS-ALL:+VERS-TLS1.3:-GROUP-ALL:+PSK", 0); - run_test("NORMAL:-KX-ALL:+PSK"); - run_test("NORMAL:-KX-ALL:+ECDHE-PSK"); - run_test("NORMAL:-KX-ALL:+DHE-PSK"); + run_test("NORMAL:-KX-ALL:+PSK", 1); + run_test("NORMAL:-KX-ALL:+ECDHE-PSK", 1); + run_test("NORMAL:-KX-ALL:+DHE-PSK", 1); gnutls_dh_params_deinit(dh_params); } diff --git a/tests/tls13/no-psk-exts.c b/tests/tls13/no-psk-exts.c new file mode 100644 index 0000000000..b70c667822 --- /dev/null +++ b/tests/tls13/no-psk-exts.c @@ -0,0 +1,258 @@ +/* + * Copyright (C) 2017-2018 Red Hat, Inc. + * + * Author: Nikos Mavrogiannopoulos + * + * This file is part of GnuTLS. + * + * GnuTLS is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuTLS 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 + * 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/> + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> + +#if defined(_WIN32) + +int main() +{ + exit(77); +} + +#else + +#include <string.h> +#include <sys/types.h> +#include <netinet/in.h> +#include <sys/socket.h> +#include <sys/wait.h> +#include <arpa/inet.h> +#include <unistd.h> +#include <gnutls/gnutls.h> +#include <gnutls/dtls.h> +#include <signal.h> + +#include "cert-common.h" +#include "tls13/ext-parse.h" +#include "utils.h" + +/* This program tests whether a connection without the PSK priority + * options, will contain PSK extensions */ + +static void server_log_func(int level, const char *str) +{ + fprintf(stderr, "server|<%d>| %s", level, str); +} + +static void client_log_func(int level, const char *str) +{ + fprintf(stderr, "client|<%d>| %s", level, str); +} + +#define MAX_BUF 1024 + +static void client(int fd) +{ + int ret; + gnutls_certificate_credentials_t x509_cred; + gnutls_psk_client_credentials_t psk_cred; + gnutls_session_t session; + + global_init(); + + if (debug) { + gnutls_global_set_log_function(client_log_func); + gnutls_global_set_log_level(7); + } + + gnutls_certificate_allocate_credentials(&x509_cred); + gnutls_psk_allocate_client_credentials(&psk_cred); + + /* Initialize TLS session + */ + gnutls_init(&session, GNUTLS_CLIENT); + + gnutls_handshake_set_timeout(session, 20 * 1000); + + ret = gnutls_priority_set_direct(session, "NORMAL:-VERS-ALL:+VERS-TLS1.3:+VERS-TLS1.2:+VERS-TLS1.0", NULL); + if (ret < 0) + fail("cannot set TLS 1.3 priorities\n"); + + /* put the anonymous credentials to the current session + */ + gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, x509_cred); + gnutls_credentials_set(session, GNUTLS_CRD_PSK, psk_cred); + + gnutls_transport_set_int(session, fd); + + /* Perform the TLS handshake + */ + do { + ret = gnutls_handshake(session); + } + while (ret < 0 && gnutls_error_is_fatal(ret) == 0); + + /* try if gnutls_reauth() would fail as expected */ + ret = gnutls_reauth(session, 0); + if (ret != GNUTLS_E_INVALID_REQUEST) + fail("server: gnutls_reauth did not fail as expected: %s", gnutls_strerror(ret)); + + close(fd); + + gnutls_deinit(session); + + gnutls_certificate_free_credentials(x509_cred); + gnutls_psk_free_client_credentials(psk_cred); + + gnutls_global_deinit(); +} + +static unsigned server_hello_ok = 0; + +#define TLS_EXT_PSK 41 +#define TLS_EXT_PSK_KE 45 + +static int hellos_callback(gnutls_session_t session, unsigned int htype, + unsigned post, unsigned int incoming, const gnutls_datum_t *msg) +{ + if (htype == GNUTLS_HANDSHAKE_SERVER_HELLO && post == GNUTLS_HOOK_POST) { + if (find_server_extension(msg, TLS_EXT_PSK_KE, NULL, NULL)) { + fail("PSK KE extension seen on server (illegal)!\n"); + } + if (find_server_extension(msg, TLS_EXT_PSK, NULL, NULL)) { + fail("PSK extension seen on server (illegal)!\n"); + } + server_hello_ok = 1; + + return GNUTLS_E_INTERRUPTED; + } + + if (htype != GNUTLS_HANDSHAKE_CLIENT_HELLO || post != GNUTLS_HOOK_PRE) + return 0; + + if (find_client_extension(msg, TLS_EXT_PSK, NULL, NULL)) + fail("PSK extension seen in client hello with no PSK!\n"); + + if (find_client_extension(msg, TLS_EXT_PSK_KE, NULL, NULL)) + fail("PSK extension seen in client hello with no PSK!\n"); + + return 0; +} + +static void server(int fd) +{ + int ret; + char buffer[MAX_BUF + 1]; + gnutls_session_t session; + gnutls_certificate_credentials_t x509_cred; + + /* this must be called once in the program + */ + global_init(); + memset(buffer, 0, sizeof(buffer)); + + if (debug) { + gnutls_global_set_log_function(server_log_func); + gnutls_global_set_log_level(4711); + } + + gnutls_certificate_allocate_credentials(&x509_cred); + gnutls_certificate_set_x509_key_mem(x509_cred, &server_cert, + &server_key, + GNUTLS_X509_FMT_PEM); + + gnutls_init(&session, GNUTLS_SERVER); + + gnutls_handshake_set_timeout(session, 20 * 1000); + gnutls_handshake_set_hook_function(session, GNUTLS_HANDSHAKE_ANY, + GNUTLS_HOOK_BOTH, + hellos_callback); + + /* avoid calling all the priority functions, since the defaults + * are adequate. + */ + gnutls_priority_set_direct(session, "NORMAL:+VERS-TLS1.3", NULL); + + gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, x509_cred); + + gnutls_transport_set_int(session, fd); + + do { + ret = gnutls_handshake(session); + if (ret == GNUTLS_E_INTERRUPTED) { /* expected */ + break; + } + } while (ret < 0 && gnutls_error_is_fatal(ret) == 0); + + + if (server_hello_ok == 0) { + fail("server: did not verify the server hello contents\n"); + } + + close(fd); + gnutls_deinit(session); + + gnutls_certificate_free_credentials(x509_cred); + + gnutls_global_deinit(); + + if (debug) + success("server: client/server hello were verified\n"); +} + +static void ch_handler(int sig) +{ + int status; + wait(&status); + check_wait_status(status); + return; +} + +void doit(void) +{ + int fd[2]; + int ret; + pid_t child; + + signal(SIGCHLD, ch_handler); + + ret = socketpair(AF_UNIX, SOCK_STREAM, 0, fd); + if (ret < 0) { + perror("socketpair"); + exit(1); + } + + child = fork(); + if (child < 0) { + perror("fork"); + fail("fork"); + exit(1); + } + + if (child) { + /* parent */ + close(fd[1]); + server(fd[0]); + kill(child, SIGTERM); + } else { + close(fd[0]); + client(fd[1]); + exit(0); + } +} + +#endif /* _WIN32 */ diff --git a/tests/tls13/psk-ext.c b/tests/tls13/psk-ext.c new file mode 100644 index 0000000000..3d5c5956ed --- /dev/null +++ b/tests/tls13/psk-ext.c @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2016, 2017 Red Hat, Inc. + * + * Author: Nikos Mavrogiannopoulos + * + * This file is part of GnuTLS. + * + * GnuTLS is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuTLS 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 + * 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/> + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> + +#include <string.h> +#include <gnutls/gnutls.h> +#include <stdint.h> +#include "../lib/tls13/psk_ext_parser.h" + +#include "utils.h" + +/* Tests the PSK-extension decoding part */ + +static void decode(const char *test_name, const gnutls_datum_t *raw, const gnutls_datum_t *id, + const gnutls_datum_t *b, unsigned idx, int res) +{ + int ret; + psk_ext_parser_st p; + struct psk_st psk; + gnutls_datum_t binder; + unsigned found = 0; + + ret = _gnutls13_psk_ext_parser_init(&p, raw->data, raw->size); + if (ret < 0) { + if (res == ret) /* expected */ + return; + fail("%s: _gnutls13_psk_ext_parser_init: %d/%s\n", test_name, ret, gnutls_strerror(ret)); + exit(1); + } + + while ((ret = _gnutls13_psk_ext_parser_next_psk(&p, &psk)) >= 0) { + if (ret == (int)idx) { + if (psk.identity.size == id->size && memcmp(psk.identity.data, id->data, id->size) == 0) { + if (debug) + success("%s: found id\n", test_name); + found = 1; + break; + } else { + fail("%s: did not found identity on index %d\n", test_name, idx); + } + } + } + + if (found == 0) + fail("%s: did not found identity!\n", test_name); + + ret = _gnutls13_psk_ext_parser_find_binder(&p, idx, &binder); + if (ret < 0) { + fail("%s: could not extract binder: %s\n", test_name, gnutls_strerror(ret)); + } + + if (binder.size != b->size || memcmp(binder.data, b->data, b->size) != 0) { + hexprint(binder.data, binder.size); + fail("%s: did not match binder on index %d\n", test_name, idx); + } + + return; +} + +struct decode_tests_st { + const char *name; + gnutls_datum_t psk; + unsigned idx; /* the ID index */ + gnutls_datum_t id; + gnutls_datum_t binder; + int res; +}; + +struct decode_tests_st decode_tests[] = { + { + .name = "single PSK", + .psk = { (unsigned char*)"\x00\x0a\x00\x04\x6e\x6d\x61\x76\x00\x00\x00\x00\x00\x21\x20\xc4\xda\xe5\x7e\x05\x59\xf7\xae\x9b\xba\x90\xd2\x6e\x12\x68\xf6\xc1\xc7\xb9\x7e\xdc\xed\x9e\x67\x4e\xa5\x91\x2d\x7c\xb4\xf0\xab", 47}, + .id = { (unsigned char*)"nmav", 4 }, + .binder = { (unsigned char*)"\xc4\xda\xe5\x7e\x05\x59\xf7\xae\x9b\xba\x90\xd2\x6e\x12\x68\xf6\xc1\xc7\xb9\x7e\xdc\xed\x9e\x67\x4e\xa5\x91\x2d\x7c\xb4\xf0\xab", 32 }, + .idx = 0, + .res = 0 + }, + { + .name = "multiple psks id0", + .psk = { (unsigned char*)"\x00\x20\x00\x04\x70\x73\x6b\x31\x00\x00\x00\x00" + "\x00\x06\x70\x73\x6b\x69\x64\x00\x00\x00\x00\x00" + "\x00\x04\x74\x65\x73\x74\x00\x00\x00\x00\x00\x63" + "\x20\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\x01\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x20\x71\x83\x89\x3d\xcc" + "\x46\xad\x83\x18\x98\x59\x46\x0b\xb2\x51\x24\x53" + "\x41\xb4\x35\x04\x22\x90\x02\xac\x5e\xc1\xe7\xbc" + "\xca\x52\x16", 135}, + .id = { (unsigned char*)"psk1", 4 }, + .binder = { (unsigned char*)"\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 32}, + .idx = 0, + .res = 0 + }, + { + .name = "multiple psks id1", + .psk = { (unsigned char*)"\x00\x20\x00\x04\x70\x73\x6b\x31\x00\x00\x00\x00" + "\x00\x06\x70\x73\x6b\x69\x64\x00\x00\x00\x00\x00" + "\x00\x04\x74\x65\x73\x74\x00\x00\x00\x00\x00\x63" + "\x20\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\x01\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x20\x71\x83\x89\x3d\xcc" + "\x46\xad\x83\x18\x98\x59\x46\x0b\xb2\x51\x24\x53" + "\x41\xb4\x35\x04\x22\x90\x02\xac\x5e\xc1\xe7\xbc" + "\xca\x52\x16", 135}, + .id = { (unsigned char*)"pskid", 6 }, + .binder = { (unsigned char*)"\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 32}, + .idx = 1, + .res = 0 + }, + { + .name = "multiple psks id2", + .psk = { (unsigned char*)"\x00\x20\x00\x04\x70\x73\x6b\x31\x00\x00\x00\x00" + "\x00\x06\x70\x73\x6b\x69\x64\x00\x00\x00\x00\x00" + "\x00\x04\x74\x65\x73\x74\x00\x00\x00\x00\x00\x63" + "\x20\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\x01\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x20\x71\x83\x89\x3d\xcc" + "\x46\xad\x83\x18\x98\x59\x46\x0b\xb2\x51\x24\x53" + "\x41\xb4\x35\x04\x22\x90\x02\xac\x5e\xc1\xe7\xbc" + "\xca\x52\x16", 135}, + .id = { (unsigned char*)"test", 4 }, + .binder = { (unsigned char*)"\x71\x83\x89\x3d\xcc\x46\xad\x83\x18\x98\x59\x46\x0b\xb2\x51\x24\x53\x41\xb4\x35\x04\x22\x90\x02\xac\x5e\xc1\xe7\xbc\xca\x52\x16", 32}, + .idx = 2, + .res = 0 + } +}; + +void doit(void) +{ + unsigned i; + + for (i=0;i<sizeof(decode_tests)/sizeof(decode_tests[0]);i++) { + decode(decode_tests[i].name, &decode_tests[i].psk, &decode_tests[i].id, + &decode_tests[i].binder, decode_tests[i].idx, decode_tests[i].res); + } +} + |