]> git.ipfire.org Git - thirdparty/gnutls.git/commitdiff
handshake: added support for post-handshake authentication
authorNikos Mavrogiannopoulos <nmav@redhat.com>
Thu, 2 Nov 2017 14:19:10 +0000 (15:19 +0100)
committerNikos Mavrogiannopoulos <nmav@redhat.com>
Mon, 19 Feb 2018 14:29:36 +0000 (15:29 +0100)
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>
14 files changed:
lib/Makefile.am
lib/errors.c
lib/ext/post_handshake.c
lib/gnutls_int.h
lib/handshake-tls13.c
lib/handshake.c
lib/includes/gnutls/gnutls.h.in
lib/libgnutls.map
lib/record.c
lib/state.c
lib/tls13/certificate.c
lib/tls13/certificate_request.c
lib/tls13/certificate_request.h
lib/tls13/post_handshake.c [new file with mode: 0644]

index 52686bf25d1209aa2f271501c4d9d68a7a579e8b..3b4ae8bda9e3afec4cad191d1eefb5c80ad394b6 100644 (file)
@@ -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 \
index ea444c3e12fb1f6e4dc157277a6e1df9bce2bbca..77cba34fc7a184a5cb9f93898636bf6154693abf 100644 (file)
@@ -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}
 };
index fa1e870fde6036173d3da1fc8f8ea94a557eea4d..9aa67e2aede02d44a4fa0e889ba8c18e33523025 100644 (file)
@@ -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
 }
-
-
index d3862d5a4f287bcf897661b56d756254b8492eb3..4a02ddbae1c97993626e4d901f2745e55d4b6ad3 100644 (file)
@@ -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)
index d7e6d168d130d40e46feaff582b8391ae29949b6..03b08285da6c3c62c160aab86a7946241a2e8946 100644 (file)
 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)
index afd2d9704598760b57f7f4860255967e422eb5cd..f7c68534168db16dfdeb313ebbe09297fb4cbeaf 100644 (file)
@@ -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) \
index ea231550b0e3adf53a5404fa3f4d7b37451ea31d..cddc3d434859795553993532eef78b3429ccd5ce 100644 (file)
@@ -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
 
index 8e5444745a67abd44abfff20a8d4de0d55c6a8f8..0641a09bbb41361f3a0f5858bd94c7449db4ec82 100644 (file)
@@ -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 {
index cee139d80c4869361b70057852c9b2b98d824075..b3ae667681a39a14c8fb9355c60fcca0632cad91 100644 (file)
@@ -940,7 +940,6 @@ record_add_to_buffers(gnutls_session_t session,
                                } else {
                                        ret = GNUTLS_E_AGAIN;
                                }
-
                                goto cleanup;
                        }
 
index c90adbb551a2746019d0b35d2656eb221a63799e..889b3190c0c9886845e0f0632936d7b0c8f86b72 100644 (file)
@@ -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);
 
index c4aadedc206f75bc443f997067507bebee4b8111..147100be88a70f013cf204225e292a6ffd6651b6 100644 (file)
@@ -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 */
index 3e5b831a4fd45bdcde7c2589c418ec07b6bb5f9a..42ba3c40559766d6989281cef265bdf15c9a5be1 100644 (file)
@@ -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;
index 78b4b4eb4127c60c216af9e09f0156bcbe7aecda..d00e890e8742e373e69be06f46d916083c2b9703 100644 (file)
@@ -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 (file)
index 0000000..39ae680
--- /dev/null
@@ -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);
+}