diff options
author | Nikos Mavrogiannopoulos <nmav@redhat.com> | 2017-11-02 15:19:10 +0100 |
---|---|---|
committer | Nikos Mavrogiannopoulos <nmav@redhat.com> | 2018-02-19 15:29:36 +0100 |
commit | c0be323b29c0c31b7bc307f03df7db14816b0c48 (patch) | |
tree | 92469b39093ec6ab17f7f8c6c1227158e11f0e49 | |
parent | 9ec15a01ee5982706adc92a938304eeb8a3de60a (diff) | |
download | gnutls-c0be323b29c0c31b7bc307f03df7db14816b0c48.tar.gz |
handshake: added support for post-handshake authentication
That is:
* introduced a gnutls_init() flag for clients to enable post-handshake
authentication
* introduced gnutls_reauth() function, to be called by servers to request
authentication, and by clients to perform authentication
Resolves #562
Signed-off-by: Nikos Mavrogiannopoulos <nmav@redhat.com>
-rw-r--r-- | lib/Makefile.am | 3 | ||||
-rw-r--r-- | lib/errors.c | 2 | ||||
-rw-r--r-- | lib/ext/post_handshake.c | 13 | ||||
-rw-r--r-- | lib/gnutls_int.h | 18 | ||||
-rw-r--r-- | lib/handshake-tls13.c | 37 | ||||
-rw-r--r-- | lib/handshake.c | 14 | ||||
-rw-r--r-- | lib/includes/gnutls/gnutls.h.in | 9 | ||||
-rw-r--r-- | lib/libgnutls.map | 1 | ||||
-rw-r--r-- | lib/record.c | 1 | ||||
-rw-r--r-- | lib/state.c | 5 | ||||
-rw-r--r-- | lib/tls13/certificate.c | 57 | ||||
-rw-r--r-- | lib/tls13/certificate_request.c | 98 | ||||
-rw-r--r-- | lib/tls13/certificate_request.h | 2 | ||||
-rw-r--r-- | lib/tls13/post_handshake.c | 239 |
14 files changed, 448 insertions, 51 deletions
diff --git a/lib/Makefile.am b/lib/Makefile.am index 52686bf25d..3b4ae8bda9 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -96,7 +96,8 @@ COBJECTS += tls13/encrypted_extensions.c tls13/encrypted_extensions.h \ tls13/key_update.c tls13/key_update.h \ tls13/hello_retry.c tls13/hello_retry.h \ tls13/session_ticket.c tls13/session_ticket.h \ - tls13/certificate.c tls13/certificate.h + tls13/certificate.c tls13/certificate.h \ + tls13/post_handshake.c if ENABLE_PKCS11 COBJECTS += pkcs11.c pkcs11x.c pkcs11_privkey.c pkcs11_write.c pkcs11_secret.c \ diff --git a/lib/errors.c b/lib/errors.c index ea444c3e12..77cba34fc7 100644 --- a/lib/errors.c +++ b/lib/errors.c @@ -440,6 +440,8 @@ static const gnutls_error_entry non_fatal_error_entries[] = { ERROR_ENTRY(N_("Function was interrupted."), GNUTLS_E_INTERRUPTED), ERROR_ENTRY(N_("Rehandshake was requested by the peer."), GNUTLS_E_REHANDSHAKE), + ERROR_ENTRY(N_("Re-authentication was requested by the peer."), + GNUTLS_E_REAUTH_REQUEST), /* Only non fatal (for handshake) errors here */ {NULL, NULL, 0} }; diff --git a/lib/ext/post_handshake.c b/lib/ext/post_handshake.c index fa1e870fde..9aa67e2aed 100644 --- a/lib/ext/post_handshake.c +++ b/lib/ext/post_handshake.c @@ -62,7 +62,8 @@ _gnutls_post_handshake_recv_params(gnutls_session_t session, if (unlikely(vers == NULL)) return 0; - if (vers->post_handshake_auth) + if ((session->internals.flags & GNUTLS_POST_HANDSHAKE_AUTH) && + vers->post_handshake_auth) session->security_parameters.post_handshake_auth = 1; } @@ -75,14 +76,11 @@ static int _gnutls_post_handshake_send_params(gnutls_session_t session, gnutls_buffer_st * extdata) { - /* we don't support post-handshake authentication yet */ - return 0; -#if 0 - gnutls_certificate_credentials_t cred; const version_entry_st *max; - if (session->security_parameters.entity != GNUTLS_CLIENT) { + if (session->security_parameters.entity != GNUTLS_CLIENT || + !(session->internals.flags & GNUTLS_POST_HANDSHAKE_AUTH)) { /* not sent on server side */ return 0; } @@ -100,7 +98,4 @@ _gnutls_post_handshake_send_params(gnutls_session_t session, return GNUTLS_E_INT_RET_0; else return 0; -#endif } - - diff --git a/lib/gnutls_int.h b/lib/gnutls_int.h index d3862d5a4f..4a02ddbae1 100644 --- a/lib/gnutls_int.h +++ b/lib/gnutls_int.h @@ -263,7 +263,13 @@ typedef enum bye_state_t { BYE_STATE0 = 0, BYE_STATE1, BYE_STATE2 } bye_state_t; +typedef enum reauth_state_t { + REAUTH_STATE0 = 0, REAUTH_STATE1, REAUTH_STATE2, REAUTH_STATE3, + REAUTH_STATE4, REAUTH_STATE5 +} reauth_state_t; + #define BYE_STATE session->internals.bye_state +#define REAUTH_STATE session->internals.reauth_state typedef enum heartbeat_state_t { SHB_SEND1 = 0, @@ -921,6 +927,8 @@ typedef struct { bool resumable; /* TRUE or FALSE - if we can resume that session */ bool ticket_sent; /* whether a session ticket was sent */ bye_state_t bye_state; /* used by gnutls_bye() */ + reauth_state_t reauth_state; /* used by gnutls_reauth() */ + handshake_state_t handshake_final_state; handshake_state_t handshake_state; /* holds * a number which indicates where @@ -997,6 +1005,9 @@ typedef struct { * function. */ + /* buffer used temporarily during TLS1.3 reauthentication */ + gnutls_buffer_st reauth_buffer; + time_t expire_time; /* after expire_time seconds this session will expire */ const struct mod_auth_st_int *auth_struct; /* used in handshake packets and KX algorithms */ @@ -1119,6 +1130,13 @@ typedef struct { unsigned int handshake_timeout_ms; /* timeout in milliseconds */ unsigned int record_timeout_ms; /* timeout in milliseconds */ + /* saved context of post handshake certificate request. In + * client side is what we received in server's certificate request; + * in server side is what we sent to client. */ + gnutls_datum_t post_handshake_cr_context; + /* it is a copy of the handshake hash buffer if post handshake is used */ + gnutls_buffer_st post_handshake_hash_buffer; + #define HSK_CRT_VRFY_EXPECTED 1 #define HSK_CRT_SENT (1<<1) #define HSK_CRT_ASKED (1<<2) diff --git a/lib/handshake-tls13.c b/lib/handshake-tls13.c index d7e6d168d1..03b08285da 100644 --- a/lib/handshake-tls13.c +++ b/lib/handshake-tls13.c @@ -59,6 +59,16 @@ static int generate_hs_traffic_keys(gnutls_session_t session); static int generate_ap_traffic_keys(gnutls_session_t session); +#define SAVE_TRANSCRIPT \ + if (session->internals.flags & GNUTLS_POST_HANDSHAKE_AUTH) { \ + /* If post-handshake auth is in use we need a copy of the original \ + * handshake transcript */ \ + memcpy( &session->internals.post_handshake_hash_buffer, \ + &session->internals.handshake_hash_buffer, \ + sizeof(session->internals.handshake_hash_buffer)); \ + _gnutls_buffer_init(&session->internals.handshake_hash_buffer); \ + } + /* * _gnutls13_handshake_client * This function performs the client side of the handshake of the TLS/SSL protocol. @@ -136,6 +146,8 @@ int _gnutls13_handshake_client(gnutls_session_t session) session->internals.recv_state = RECV_STATE_0; session->internals.initial_negotiation_completed = 1; + SAVE_TRANSCRIPT; + return 0; } @@ -311,9 +323,16 @@ int _gnutls13_handshake_server(gnutls_session_t session) session->internals.recv_state = RECV_STATE_0; session->internals.initial_negotiation_completed = 1; + SAVE_TRANSCRIPT; + return 0; } +/* Processes handshake messages received asynchronously after initial handshake. + * + * It is called once per message, with a read-only buffer in @buf, + * and should return success, or a fatal error code. + */ int _gnutls13_recv_async_handshake(gnutls_session_t session, gnutls_buffer_st *buf) { @@ -341,6 +360,24 @@ _gnutls13_recv_async_handshake(gnutls_session_t session, gnutls_buffer_st *buf) return gnutls_assert_val(ret); switch(type) { + case GNUTLS_HANDSHAKE_CERTIFICATE_REQUEST: + if (!(session->security_parameters.entity == GNUTLS_CLIENT) || + !(session->internals.flags & GNUTLS_POST_HANDSHAKE_AUTH)) { + return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET); + } + + _gnutls_buffer_reset(&session->internals.reauth_buffer); + + /* include the handshake headers in reauth buffer */ + ret = _gnutls_buffer_append_data(&session->internals.reauth_buffer, + buf->data-4, buf->length+4); + if (ret < 0) + return gnutls_assert_val(ret); + + /* Application is expected to handle re-authentication + * explicitly. */ + return GNUTLS_E_REAUTH_REQUEST; + case GNUTLS_HANDSHAKE_KEY_UPDATE: ret = _gnutls13_recv_key_update(session, buf); if (ret < 0) diff --git a/lib/handshake.c b/lib/handshake.c index afd2d97045..f7c6853416 100644 --- a/lib/handshake.c +++ b/lib/handshake.c @@ -1088,6 +1088,7 @@ _gnutls_send_handshake(gnutls_session_t session, mbuffer_st * bufel, uint8_t *data; uint32_t datasize, i_datasize; int pos = 0; + const version_entry_st *vers = get_version(session); if (bufel == NULL) { /* we are resuming a previously interrupted @@ -1164,6 +1165,13 @@ _gnutls_send_handshake(gnutls_session_t session, mbuffer_st * bufel, return ret; } + if (vers && vers->tls13_sem && + session->internals.initial_negotiation_completed) { + /* we are under TLS1.3 in a re-authentication phase. + * we don't attempt to cache any messages */ + goto force_send; + } + /* The messages which are followed by another are not sent by default * but are cached instead */ switch (type) { @@ -1185,11 +1193,13 @@ _gnutls_send_handshake(gnutls_session_t session, mbuffer_st * bufel, break; default: /* send cached messages */ - ret = _gnutls_handshake_io_write_flush(session); - break; + goto force_send; } return ret; + + force_send: + return _gnutls_handshake_io_write_flush(session); } #define CHECK_SIZE(ll) \ diff --git a/lib/includes/gnutls/gnutls.h.in b/lib/includes/gnutls/gnutls.h.in index ea231550b0..cddc3d4348 100644 --- a/lib/includes/gnutls/gnutls.h.in +++ b/lib/includes/gnutls/gnutls.h.in @@ -372,6 +372,9 @@ typedef enum { * result to more roundtrips needed to discover the server's choice. * @GNUTLS_NO_AUTO_REKEY: Disable auto-rekeying under TLS1.3. If this option is not specified * gnutls will force a rekey after 2^24 records have been sent. + * @GNUTLS_POST_HANDSHAKE_AUTH: Enable post handshake authentication for server and client. When set and + * a server requests authentication after handshake %GNUTLS_E_REAUTH_REQUEST will be returned + * by gnutls_record_recv(). A client should then call gnutls_reauth() to re-authenticate. * * Enumeration of different flags for gnutls_init() function. All the flags * can be combined except @GNUTLS_SERVER and @GNUTLS_CLIENT which are mutually @@ -396,6 +399,7 @@ typedef enum { GNUTLS_KEY_SHARE_TOP = (1<<11), GNUTLS_KEY_SHARE_TOP2 = (1<<12), GNUTLS_KEY_SHARE_TOP3 = (1<<13), + GNUTLS_POST_HANDSHAKE_AUTH = (1<<14), GNUTLS_NO_AUTO_REKEY = (1<<15) } gnutls_init_flags_t; @@ -1004,6 +1008,8 @@ int gnutls_bye(gnutls_session_t session, gnutls_close_request_t how); int gnutls_handshake(gnutls_session_t session); +int gnutls_reauth(gnutls_session_t session, unsigned int flags); + #define GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT ((unsigned int)-1) #define GNUTLS_INDEFINITE_TIMEOUT ((unsigned int)-2) void gnutls_handshake_set_timeout(gnutls_session_t session, @@ -3011,7 +3017,8 @@ void gnutls_fips140_set_mode(gnutls_fips_mode_t mode, unsigned flags); #define GNUTLS_E_PK_INVALID_PUBKEY_PARAMS -420 #define GNUTLS_E_PK_NO_VALIDATION_PARAMS -421 -#define GNUTLS_E_NO_COMMON_KEY_SHARE -420 +#define GNUTLS_E_NO_COMMON_KEY_SHARE -423 +#define GNUTLS_E_REAUTH_REQUEST -424 #define GNUTLS_E_UNIMPLEMENTED_FEATURE -1250 diff --git a/lib/libgnutls.map b/lib/libgnutls.map index 8e5444745a..0641a09bbb 100644 --- a/lib/libgnutls.map +++ b/lib/libgnutls.map @@ -1209,6 +1209,7 @@ GNUTLS_3_6_xx global: gnutls_session_key_update; gnutls_ext_get_current_msg; + gnutls_reauth; } GNUTLS_3_6_2; GNUTLS_FIPS140_3_4 { diff --git a/lib/record.c b/lib/record.c index cee139d80c..b3ae667681 100644 --- a/lib/record.c +++ b/lib/record.c @@ -940,7 +940,6 @@ record_add_to_buffers(gnutls_session_t session, } else { ret = GNUTLS_E_AGAIN; } - goto cleanup; } diff --git a/lib/state.c b/lib/state.c index c90adbb551..889b3190c0 100644 --- a/lib/state.c +++ b/lib/state.c @@ -291,10 +291,12 @@ int gnutls_init(gnutls_session_t * session, unsigned int flags) /* Initialize buffers */ _gnutls_buffer_init(&(*session)->internals.handshake_hash_buffer); + _gnutls_buffer_init(&(*session)->internals.post_handshake_hash_buffer); _gnutls_buffer_init(&(*session)->internals.hb_remote_data); _gnutls_buffer_init(&(*session)->internals.hb_local_data); _gnutls_buffer_init(&(*session)->internals.record_presend_buffer); _gnutls_buffer_init(&(*session)->internals.record_key_update_buffer); + _gnutls_buffer_init(&(*session)->internals.reauth_buffer); _mbuffer_head_init(&(*session)->internals.record_buffer); _mbuffer_head_init(&(*session)->internals.record_send_buffer); @@ -410,10 +412,12 @@ void gnutls_deinit(gnutls_session_t session) } _gnutls_buffer_clear(&session->internals.handshake_hash_buffer); + _gnutls_buffer_clear(&session->internals.post_handshake_hash_buffer); _gnutls_buffer_clear(&session->internals.hb_remote_data); _gnutls_buffer_clear(&session->internals.hb_local_data); _gnutls_buffer_clear(&session->internals.record_presend_buffer); _gnutls_buffer_clear(&session->internals.record_key_update_buffer); + _gnutls_buffer_clear(&session->internals.reauth_buffer); _mbuffer_head_clear(&session->internals.record_buffer); _mbuffer_head_clear(&session->internals.record_recv_buffer); @@ -423,6 +427,7 @@ void gnutls_deinit(gnutls_session_t session) _gnutls_free_datum(&session->internals.dtls.dcookie); gnutls_free(session->internals.rexts); + gnutls_free(session->internals.post_handshake_cr_context.data); gnutls_free(session->internals.rsup); diff --git a/lib/tls13/certificate.c b/lib/tls13/certificate.c index c4aadedc20..147100be88 100644 --- a/lib/tls13/certificate.c +++ b/lib/tls13/certificate.c @@ -58,16 +58,36 @@ int _gnutls13_recv_certificate(gnutls_session_t session) return 0; } - if (buf.data[0] != 0) { - /* The context field must be empty during handshake */ - gnutls_assert(); - ret = GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER; - goto cleanup; - } + if (session->internals.initial_negotiation_completed && + session->internals.post_handshake_cr_context.size > 0) { + gnutls_datum_t context; - /* buf.length is positive */ - buf.data++; - buf.length--; + /* verify whether the context matches */ + ret = _gnutls_buffer_pop_datum_prefix8(&buf, &context); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + if (context.size != session->internals.post_handshake_cr_context.size || + memcmp(context.data, session->internals.post_handshake_cr_context.data, + context.size) != 0) { + ret = GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER; + gnutls_assert(); + goto cleanup; + } + } else { + if (buf.data[0] != 0) { + /* The context field must be empty during handshake */ + gnutls_assert(); + ret = GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER; + goto cleanup; + } + + /* buf.length is positive */ + buf.data++; + buf.length--; + } _gnutls_handshake_log("HSK[%p]: parsing certificate message\n", session); @@ -114,10 +134,21 @@ int _gnutls13_send_certificate(gnutls_session_t session, unsigned again) if (ret < 0) return gnutls_assert_val(ret); - ret = _gnutls_buffer_append_prefix(&buf, 8, 0); - if (ret < 0) { - gnutls_assert(); - goto cleanup; + if (session->security_parameters.entity == GNUTLS_CLIENT) { + ret = _gnutls_buffer_append_data_prefix(&buf, 8, + session->internals.post_handshake_cr_context.data, + session->internals.post_handshake_cr_context.size); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + } else { + ret = _gnutls_buffer_append_prefix(&buf, 8, 0); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } } /* mark total size */ diff --git a/lib/tls13/certificate_request.c b/lib/tls13/certificate_request.c index 3e5b831a4f..42ba3c4055 100644 --- a/lib/tls13/certificate_request.c +++ b/lib/tls13/certificate_request.c @@ -112,42 +112,45 @@ int parse_cert_extension(void *_ctx, uint16_t tls_id, const uint8_t *data, int d return 0; } -int _gnutls13_recv_certificate_request(gnutls_session_t session) +int _gnutls13_recv_certificate_request_int(gnutls_session_t session, gnutls_buffer_st *buf) { int ret; - gnutls_buffer_st buf; crt_req_ctx_st ctx; - if (unlikely(session->security_parameters.entity != GNUTLS_CLIENT)) - return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + _gnutls_handshake_log("HSK[%p]: parsing certificate request\n", session); - ret = _gnutls_recv_handshake(session, GNUTLS_HANDSHAKE_CERTIFICATE_REQUEST, 1, &buf); - if (ret < 0) - return gnutls_assert_val(ret); + /* if initial negotiation is complete, this is a post-handshake auth */ + if (!session->internals.initial_negotiation_completed || + session->security_parameters.entity == GNUTLS_SERVER) { + if (buf->data[0] != 0) { + /* The context field must be empty during handshake */ + ret = GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER; + gnutls_assert(); + goto cleanup; + } - /* if not received */ - if (buf.length == 0) { - _gnutls_buffer_clear(&buf); - return 0; - } + /* buf->length is positive */ + buf->data++; + buf->length--; + } else { + gnutls_datum_t context; - _gnutls_handshake_log("HSK[%p]: parsing certificate request\n", session); + ret = _gnutls_buffer_pop_datum_prefix8(buf, &context); + if (ret < 0) + return gnutls_assert_val(ret); - if (buf.data[0] != 0) { - /* The context field must be empty during handshake */ - ret = GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER; - gnutls_assert(); - goto cleanup; + gnutls_free(session->internals.post_handshake_cr_context.data); + session->internals.post_handshake_cr_context.data = NULL; + ret = _gnutls_set_datum(&session->internals.post_handshake_cr_context, + context.data, context.size); + if (ret < 0) + return gnutls_assert_val(ret); } - /* buf.length is positive */ - buf.data++; - buf.length--; - memset(&ctx, 0, sizeof(ctx)); ctx.session = session; - ret = _gnutls_extv_parse(&ctx, parse_cert_extension, buf.data, buf.length); + ret = _gnutls_extv_parse(&ctx, parse_cert_extension, buf->data, buf->length); if (ret < 0) { gnutls_assert(); goto cleanup; @@ -167,6 +170,29 @@ int _gnutls13_recv_certificate_request(gnutls_session_t session) ret = 0; cleanup: + return ret; +} + +int _gnutls13_recv_certificate_request(gnutls_session_t session) +{ + int ret; + gnutls_buffer_st buf; + + if (unlikely(session->security_parameters.entity != GNUTLS_CLIENT)) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + ret = _gnutls_recv_handshake(session, GNUTLS_HANDSHAKE_CERTIFICATE_REQUEST, 1, &buf); + if (ret < 0) + return gnutls_assert_val(ret); + + /* if not received */ + if (buf.length == 0) { + _gnutls_buffer_clear(&buf); + return 0; + } + + ret = _gnutls13_recv_certificate_request_int(session, &buf); + _gnutls_buffer_clear(&buf); return ret; } @@ -209,6 +235,8 @@ int _gnutls13_send_certificate_request(gnutls_session_t session, unsigned again) unsigned init_pos; if (again == 0) { + unsigned char rnd[12]; + if (session->internals.send_cert_req == 0) return 0; @@ -221,7 +249,29 @@ int _gnutls13_send_certificate_request(gnutls_session_t session, unsigned again) if (ret < 0) return gnutls_assert_val(ret); - ret = _gnutls_buffer_append_prefix(&buf, 8, 0); + if (session->internals.initial_negotiation_completed) { /* reauth */ + ret = gnutls_rnd(GNUTLS_RND_NONCE, rnd, sizeof(rnd)); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + gnutls_free(session->internals.post_handshake_cr_context.data); + session->internals.post_handshake_cr_context.data = NULL; + ret = _gnutls_set_datum(&session->internals.post_handshake_cr_context, + rnd, sizeof(rnd)); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + ret = _gnutls_buffer_append_data_prefix(&buf, 8, + session->internals.post_handshake_cr_context.data, + session->internals.post_handshake_cr_context.size); + } else { + ret = _gnutls_buffer_append_prefix(&buf, 8, 0); + } + if (ret < 0) { gnutls_assert(); goto cleanup; diff --git a/lib/tls13/certificate_request.h b/lib/tls13/certificate_request.h index 78b4b4eb41..d00e890e87 100644 --- a/lib/tls13/certificate_request.h +++ b/lib/tls13/certificate_request.h @@ -21,4 +21,6 @@ */ int _gnutls13_recv_certificate_request(gnutls_session_t session); +int _gnutls13_recv_certificate_request_int(gnutls_session_t session, gnutls_buffer_st *buf); + int _gnutls13_send_certificate_request(gnutls_session_t session, unsigned again); diff --git a/lib/tls13/post_handshake.c b/lib/tls13/post_handshake.c new file mode 100644 index 0000000000..39ae680ab9 --- /dev/null +++ b/lib/tls13/post_handshake.c @@ -0,0 +1,239 @@ +/* + * Copyright (C) 2017 Red Hat, Inc. + * + * Author: Nikos Mavrogiannopoulos + * + * This file is part of GnuTLS. + * + * The GnuTLS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +/* Functions that relate to the TLS handshake procedure. + */ + +#include "gnutls_int.h" +#include "errors.h" +#include "dh.h" +#include "debug.h" +#include "algorithms.h" +#include "cipher.h" +#include "buffers.h" +#include "mbuffers.h" +#include "kx.h" +#include "handshake.h" +#include "num.h" +#include "hash_int.h" +#include "db.h" +#include "hello_ext.h" +#include "supplemental.h" +#include "auth.h" +#include "sslv2_compat.h" +#include <auth/cert.h> +#include "constate.h" +#include <record.h> +#include <state.h> +#include <random.h> +#include <dtls.h> +#include "tls13/certificate_request.h" +#include "tls13/certificate_verify.h" +#include "tls13/certificate.h" +#include "tls13/finished.h" + +#undef AGAIN +#define AGAIN(x) ((x)==(REAUTH_STATE)) + +/* + * _gnutls13_reauth_client + * This function performs the client side of the post-handshake authentication + */ +static +int _gnutls13_reauth_client(gnutls_session_t session) +{ + int ret = 0; + size_t tmp; + + if (!session->internals.initial_negotiation_completed) + return gnutls_assert_val(GNUTLS_E_UNAVAILABLE_DURING_HANDSHAKE); + + if (!(session->internals.flags & GNUTLS_POST_HANDSHAKE_AUTH)) + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + + if (session->internals.reauth_buffer.length == 0) + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + + switch (REAUTH_STATE) { + case REAUTH_STATE0: + + /* restore handshake transcript */ + _gnutls_buffer_reset(&session->internals.handshake_hash_buffer); + ret = gnutls_buffer_append_data(&session->internals.handshake_hash_buffer, + session->internals.post_handshake_hash_buffer.data, + session->internals.post_handshake_hash_buffer.length); + if (ret < 0) + return gnutls_assert_val(ret); + + /* append the previously received certificate request message, to the + * transcript. */ + ret = gnutls_buffer_append_data(&session->internals.handshake_hash_buffer, + session->internals.reauth_buffer.data, + session->internals.reauth_buffer.length); + if (ret < 0) + return gnutls_assert_val(ret); + + session->internals.handshake_hash_buffer_prev_len = session->internals.handshake_hash_buffer.length; + + /* skip the reauth buffer handshake message headers */ + ret = _gnutls_buffer_pop_prefix32(&session->internals.reauth_buffer, &tmp, 0); + if (ret < 0) + return gnutls_assert_val(ret); + + /* fall through */ + case REAUTH_STATE1: + ret = _gnutls13_recv_certificate_request_int(session, + &session->internals.reauth_buffer); + REAUTH_STATE = REAUTH_STATE1; + IMED_RET("recv certificate request", ret, 0); + /* fall through */ + case REAUTH_STATE2: + ret = _gnutls13_send_certificate(session, AGAIN(REAUTH_STATE2)); + REAUTH_STATE = REAUTH_STATE2; + IMED_RET("send certificate", ret, 0); + /* fall through */ + case REAUTH_STATE3: + ret = _gnutls13_send_certificate_verify(session, AGAIN(REAUTH_STATE3)); + REAUTH_STATE = REAUTH_STATE3; + IMED_RET("send certificate verify", ret, 0); + /* fall through */ + case REAUTH_STATE4: + ret = _gnutls13_send_finished(session, AGAIN(REAUTH_STATE4)); + REAUTH_STATE = REAUTH_STATE4; + IMED_RET("send finished", ret, 0); + break; + default: + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + } + + _gnutls_handshake_hash_buffers_clear(session); + _gnutls_buffer_reset(&session->internals.reauth_buffer); + REAUTH_STATE = REAUTH_STATE0; + + return 0; +} + +/* + * _gnutls13_reauth_server + * This function does the server stuff of the post-handshake authentication. + */ +static +int _gnutls13_reauth_server(gnutls_session_t session) +{ + int ret = 0; + + if (session->security_parameters.post_handshake_auth == 0 || + (!(session->internals.flags & GNUTLS_POST_HANDSHAKE_AUTH))) + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + + if (session->internals.send_cert_req == 0) + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + + switch (REAUTH_STATE) { + case REAUTH_STATE0: + /* restore handshake transcript */ + _gnutls_buffer_reset(&session->internals.handshake_hash_buffer); + ret = gnutls_buffer_append_data(&session->internals.handshake_hash_buffer, + session->internals.post_handshake_hash_buffer.data, + session->internals.post_handshake_hash_buffer.length); + if (ret < 0) + return gnutls_assert_val(ret); + + session->internals.handshake_hash_buffer_prev_len = session->internals.handshake_hash_buffer.length; + + /* fall through */ + case REAUTH_STATE1: + ret = _gnutls13_send_certificate_request(session, AGAIN(REAUTH_STATE1)); + REAUTH_STATE = REAUTH_STATE1; + IMED_RET("send certificate request", ret, 0); + /* fall through */ + case REAUTH_STATE2: + /* here we should tolerate application data */ + ret = _gnutls13_recv_certificate(session); + REAUTH_STATE = REAUTH_STATE2; + IMED_RET("recv certificate", ret, 0); + /* fall through */ + case REAUTH_STATE3: + ret = _gnutls13_recv_certificate_verify(session); + REAUTH_STATE = REAUTH_STATE3; + IMED_RET("recv certificate verify", ret, 0); + /* fall through */ + case REAUTH_STATE4: + ret = _gnutls_run_verify_callback(session, GNUTLS_CLIENT); + REAUTH_STATE = REAUTH_STATE4; + if (ret < 0) + return gnutls_assert_val(ret); + /* fall through */ + case REAUTH_STATE5: + ret = _gnutls13_recv_finished(session); + REAUTH_STATE = REAUTH_STATE5; + IMED_RET("recv finished", ret, 0); + break; + default: + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + } + + _gnutls_handshake_hash_buffers_clear(session); + REAUTH_STATE = REAUTH_STATE0; + + return 0; +} + +/** + * gnutls_reauth: + * @session: is a #gnutls_session_t type. + * @flags: must be zero + * + * This function performs the post-handshake authentication + * for TLS 1.3. + * + * The non-fatal errors expected by this function are: + * %GNUTLS_E_INTERRUPTED, %GNUTLS_E_AGAIN, as well as + * %GNUTLS_E_GOT_APPLICATION_DATA when called on server side. + * + * The former two interrupt the authentication procedure due to the transport + * layer being interrupted, and the latter because there were pending data prior + * to peer initiating the re-authentication. + * + * When this function is called under TLS1.2 or earlier or the peer didn't + * advertise post-handshake auth, it always fails with + * %GNUTLS_E_INVALID_REQUEST. The verification of the received peers certificate + * is delegated to the session or credentials verification callbacks. + * + * Prior to calling this function in server side, the function + * gnutls_certificate_server_set_request() must be called setting expectations + * for the received certificate (request or require). + * + * Returns: %GNUTLS_E_SUCCESS on a successful authentication, otherwise a negative error code. + **/ +int gnutls_reauth(gnutls_session_t session, unsigned int flags) +{ + const version_entry_st *vers = get_version(session); + + if (unlikely(!vers->tls13_sem)) + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + + if (session->security_parameters.entity == GNUTLS_SERVER) + return _gnutls13_reauth_server(session); + else + return _gnutls13_reauth_client(session); +} |