Implement Client TLS state machine
authorMatt Caswell <matt@openssl.org>
Mon, 7 Sep 2015 15:36:53 +0000 (16:36 +0100)
committerMatt Caswell <matt@openssl.org>
Fri, 30 Oct 2015 08:32:44 +0000 (08:32 +0000)
This swaps the implementation of the client TLS state machine to use the
new state machine code instead.

Reviewed-by: Tim Hudson <tjh@openssl.org>
Reviewed-by: Richard Levitte <levitte@openssl.org>
ssl/d1_msg.c
ssl/record/rec_layer_d1.c
ssl/record/rec_layer_s3.c
ssl/s3_both.c
ssl/s3_clnt.c
ssl/ssl_locl.h
ssl/statem.c

index 13bda469226e8f2a5fb65dd0f153dcc9326e6b67..e35c9356a4333ff201c8ab19b9b387a8aaeb1a20 100644 (file)
@@ -127,8 +127,7 @@ int dtls1_write_app_data_bytes(SSL *s, int type, const void *buf_, int len)
      */
     if ((SSL_in_init(s) && !s->in_handshake) ||
         (BIO_dgram_is_sctp(SSL_get_wbio(s)) &&
-         (s->state == DTLS1_SCTP_ST_SR_READ_SOCK
-          || s->state == DTLS1_SCTP_ST_CR_READ_SOCK)))
+         statem_in_sctp_read_sock(s)))
 #else
     if (SSL_in_init(s) && !s->in_handshake)
 #endif
index d7d0093aec785b583d0f4ebfc887c71b3c7c5647..d91de4d4a0d90a15698f110a5d140d1270b604f4 100644 (file)
@@ -440,9 +440,8 @@ int dtls1_read_bytes(SSL *s, int type, int *recvd_type, unsigned char *buf,
      * SCTP.
      */
     if ((!s->in_handshake && SSL_in_init(s)) ||
-        (BIO_dgram_is_sctp(SSL_get_rbio(s)) &&
-         (s->state == DTLS1_SCTP_ST_SR_READ_SOCK
-          || s->state == DTLS1_SCTP_ST_CR_READ_SOCK)
+        (BIO_dgram_is_sctp(SSL_get_rbio(s))
+         && statem_in_sctp_read_sock(s)
          && s->s3->in_read_app_data != 2))
 #else
     if (!s->in_handshake && SSL_in_init(s))
@@ -586,8 +585,7 @@ int dtls1_read_bytes(SSL *s, int type, int *recvd_type, unsigned char *buf,
          */
         if (BIO_dgram_is_sctp(SSL_get_rbio(s)) &&
             SSL3_RECORD_get_type(rr) == SSL3_RT_APPLICATION_DATA &&
-            (s->state == DTLS1_SCTP_ST_SR_READ_SOCK
-             || s->state == DTLS1_SCTP_ST_CR_READ_SOCK)) {
+            statem_in_sctp_read_sock(s)) {
             s->rwstate = SSL_READING;
             BIO_clear_retry_flags(SSL_get_rbio(s));
             BIO_set_retry_read(SSL_get_rbio(s));
index f5dd27aa7c79cd8603e797bdd2818a3beb458870..78e355a5d99d869d383c1331357acb2e8f463d5e 100644 (file)
@@ -1450,16 +1450,7 @@ int ssl3_read_bytes(SSL *s, int type, int *recvd_type, unsigned char *buf,
          * application data at this point (session renegotiation not yet
          * started), we will indulge it.
          */
-        if (s->s3->in_read_app_data &&
-            (s->s3->total_renegotiations != 0) &&
-            (((s->state & SSL_ST_CONNECT) &&
-              (s->state >= SSL3_ST_CW_CLNT_HELLO_A) &&
-              (s->state <= SSL3_ST_CR_SRVR_HELLO_A)
-             ) || ((s->state & SSL_ST_ACCEPT) &&
-                   (s->state <= SSL3_ST_SW_HELLO_REQ_A) &&
-                   (s->state >= SSL3_ST_SR_CLNT_HELLO_A)
-             )
-            )) {
+        if (statem_app_data_allowed(s)) {
             s->s3->in_read_app_data = 2;
             return (-1);
         } else {
index e9ab4a6392a5e2638ec75dbc3b15dca6ec964ca3..1829feb243fda22ca2cf13375fb8bc3c8701795b 100644 (file)
@@ -446,6 +446,70 @@ unsigned long ssl3_output_cert_chain(SSL *s, CERT_PKEY *cpk)
     return l + SSL_HM_HEADER_LENGTH(s);
 }
 
+enum WORK_STATE tls_finish_handshake(SSL *s, enum WORK_STATE wst)
+{
+    void (*cb) (const SSL *ssl, int type, int val) = NULL;
+
+#ifndef OPENSSL_NO_SCTP
+    if (SSL_IS_DTLS(s) && BIO_dgram_is_sctp(SSL_get_wbio(s))) {
+        enum WORK_STATE ret;
+        ret = dtls_wait_for_dry(s);
+        if (ret != WORK_FINISHED_CONTINUE)
+            return ret;
+    }
+#endif
+
+    /* clean a few things up */
+    ssl3_cleanup_key_block(s);
+    BUF_MEM_free(s->init_buf);
+    s->init_buf = NULL;
+
+    ssl_free_wbio_buffer(s);
+
+    s->init_num = 0;
+
+    if (!s->server || s->renegotiate == 2) {
+        /* skipped if we just sent a HelloRequest */
+        s->renegotiate = 0;
+        s->new_session = 0;
+
+        if (s->server) {
+            s->renegotiate = 0;
+            s->new_session = 0;
+
+            ssl_update_cache(s, SSL_SESS_CACHE_SERVER);
+
+            s->ctx->stats.sess_accept_good++;
+            s->handshake_func = ssl3_accept;
+        } else {
+            ssl_update_cache(s, SSL_SESS_CACHE_CLIENT);
+            if (s->hit)
+                s->ctx->stats.sess_hit++;
+
+            s->handshake_func = ssl3_connect;
+            s->ctx->stats.sess_connect_good++;
+        }
+
+        if (s->info_callback != NULL)
+            cb = s->info_callback;
+        else if (s->ctx->info_callback != NULL)
+            cb = s->ctx->info_callback;
+
+        if (cb != NULL)
+            cb(s, SSL_CB_HANDSHAKE_DONE, 1);
+
+        if (SSL_IS_DTLS(s)) {
+            /* done with handshaking */
+            s->d1->handshake_read_seq = 0;
+            s->d1->handshake_write_seq = 0;
+            s->d1->next_handshake_write_seq = 0;
+        }
+    }
+
+    return WORK_FINISHED_STOP;
+}
+
+
 /*
  * Obtain handshake message of message type 'mt' (any if mt == -1), maximum
  * acceptable body length 'max'. The first four bytes (msg_type and length)
index b967b97995c7ba3ee4c2a2d93ba6a588d14aa5b1..5cf47d21ff7c59662144c30f8fcbb2e2e484f19b 100644 (file)
 
 static int ssl_set_version(SSL *s);
 static int ca_dn_cmp(const X509_NAME *const *a, const X509_NAME *const *b);
+#if 0
+/*
+ * Temporarily disabled during development of new state machine code.
+ * TODO: Clean me up
+ */
 static int ssl3_check_change(SSL *s);
+#endif
 static int ssl_cipher_list_to_bytes(SSL *s, STACK_OF(SSL_CIPHER) *sk,
                                     unsigned char *p);
 
 
+#if 0
+/*
+ * Temporarily disabled during development of new state machine code.
+ * TODO: Clean me up
+ */
 int ssl3_connect(SSL *s)
 {
     BUF_MEM *buf = NULL;
@@ -631,6 +642,7 @@ int ssl3_connect(SSL *s)
     return (ret);
 }
 
+#endif /* End temp disabled ssl3_connect */
 /*
  * Work out what version we should be using for the initial ClientHello if
  * the version is currently set to (D)TLS_ANY_VERSION.
@@ -1285,6 +1297,32 @@ enum MSG_PROCESS_RETURN tls_process_server_hello(SSL *s, unsigned long n)
         goto f_err;
     }
 
+#ifndef OPENSSL_NO_SCTP
+    if (SSL_IS_DTLS(s) && s->hit) {
+        unsigned char sctpauthkey[64];
+        char labelbuffer[sizeof(DTLS1_SCTP_AUTH_LABEL)];
+
+        /*
+         * Add new shared key for SCTP-Auth, will be ignored if
+         * no SCTP used.
+         */
+        snprintf((char *)labelbuffer,
+                 sizeof(DTLS1_SCTP_AUTH_LABEL),
+                 DTLS1_SCTP_AUTH_LABEL);
+
+        if (SSL_export_keying_material(s, sctpauthkey,
+                                   sizeof(sctpauthkey),
+                                   labelbuffer,
+                                   sizeof(labelbuffer), NULL, 0,
+                                   0) <= 0)
+            goto err;
+
+        BIO_ctrl(SSL_get_wbio(s),
+                 BIO_CTRL_DGRAM_SCTP_ADD_AUTH_KEY,
+                 sizeof(sctpauthkey), sctpauthkey);
+    }
+#endif
+
     return MSG_PROCESS_CONTINUE_READING;
  f_err:
     ssl3_send_alert(s, SSL3_AL_FATAL, al);
@@ -3489,6 +3527,11 @@ int ssl3_check_cert_and_algorithm(SSL *s)
  * pre-shared secret, we have a "ticket" and the next server message
  * is CCS; and 0 otherwise. It returns -1 upon an error.
  */
+ #if 0
+ /*
+  * TODO: No longer required. Temporarily kept during state machine development
+  * To be deleted by later commits
+  */
 static int ssl3_check_change(SSL *s)
 {
     int ok = 0;
@@ -3518,6 +3561,7 @@ static int ssl3_check_change(SSL *s)
 
     return 0;
 }
+#endif
 
 #ifndef OPENSSL_NO_NEXTPROTONEG
 int ssl3_send_next_proto(SSL *s)
index e2e9ddf0259c49a0d36c331e6e5e95cf98bf393d..7cedb123aaff3f7432d70bbcc5ffa61c167924ab 100644 (file)
@@ -844,6 +844,9 @@ struct statem_st {
     enum HANDSHAKE_STATE hand_state;
     int read_state_first_init;
     int use_timer;
+#ifndef OPENSSL_NO_SCTP
+    int in_sctp_read_sock;
+#endif
 };
 typedef struct statem_st STATEM;
 
@@ -2071,6 +2074,7 @@ __owur int tls_get_message_header(SSL *s, int *mt);
 __owur int tls_get_message_body(SSL *s, unsigned long *len);
 __owur int ssl3_send_finished(SSL *s, int a, int b, const char *sender, int slen);
 __owur int tls_construct_finished(SSL *s, const char *sender, int slen);
+__owur enum WORK_STATE tls_finish_handshake(SSL *s, enum WORK_STATE wst);
 __owur int ssl3_num_ciphers(void);
 __owur const SSL_CIPHER *ssl3_get_cipher(unsigned int u);
 int ssl3_renegotiate(SSL *ssl);
@@ -2092,7 +2096,11 @@ __owur int ssl3_connect(SSL *s);
 void statem_clear(SSL *s);
 void statem_set_renegotiate(SSL *s);
 void statem_set_error(SSL *s);
-__owur int statem_client_app_data_allowed(SSL *s);
+__owur int statem_app_data_allowed(SSL *s);
+#ifndef OPENSSL_NO_SCTP
+void statem_set_sctp_read_sock(SSL *s, int read_sock);
+__owur int statem_in_sctp_read_sock(SSL *s);
+#endif
 __owur int ssl3_read(SSL *s, void *buf, int len);
 __owur int ssl3_peek(SSL *s, void *buf, int len);
 __owur int ssl3_write(SSL *s, const void *buf, int len);
index ef0799e4078f3cb9a747d806240f2c215bc71181..2ebe2a644badec86b6b84d4ae1a40b22cb8ba9ef 100644 (file)
@@ -103,11 +103,22 @@ enum SUB_STATE_RETURN {
     SUB_STATE_END_HANDSHAKE
 };
 
-int state_machine(SSL *s, int server);
+static int state_machine(SSL *s, int server);
 static void init_read_state_machine(SSL *s);
 static enum SUB_STATE_RETURN read_state_machine(SSL *s);
 static void init_write_state_machine(SSL *s);
 static enum SUB_STATE_RETURN write_state_machine(SSL *s);
+static inline int cert_req_allowed(SSL *s);
+static inline int key_exchange_skip_allowed(SSL *s);
+static int client_read_transition(SSL *s, int mt);
+static enum WRITE_TRAN client_write_transition(SSL *s);
+static enum WORK_STATE client_pre_work(SSL *s, enum WORK_STATE wst);
+static enum WORK_STATE client_post_work(SSL *s, enum WORK_STATE wst);
+static int client_construct_message(SSL *s);
+static unsigned long client_max_message_size(SSL *s);
+static enum MSG_PROCESS_RETURN client_process_message(SSL *s,
+                                                      unsigned long len);
+static enum WORK_STATE client_post_process_message(SSL *s, enum WORK_STATE wst);
 
 /*
  * Clear the state machine state and reset back to MSG_FLOW_UNINITED
@@ -136,6 +147,10 @@ void statem_set_error(SSL *s)
     s->state = SSL_ST_ERR;
 }
 
+int ssl3_connect(SSL *s) {
+    return state_machine(s, 0);
+}
+
 /*
  * The main message flow state machine. We start in the MSG_FLOW_UNINITED or
  * MSG_FLOW_RENEGOTIATE state and finish in MSG_FLOW_FINISHED. Valid states and
@@ -164,7 +179,7 @@ void statem_set_error(SSL *s)
  *   1: Success
  * <=0: NBIO or error
  */
-int state_machine(SSL *s, int server) {
+static int state_machine(SSL *s, int server) {
     BUF_MEM *buf = NULL;
     unsigned long Time = (unsigned long)time(NULL);
     void (*cb) (const SSL *ssl, int type, int val) = NULL;
@@ -433,11 +448,10 @@ static enum SUB_STATE_RETURN read_state_machine(SSL *s) {
         post_process_message = NULL;
         max_message_size = NULL;
     } else {
-        /* TODO: Fill these in later when we've implemented them */
-        transition = NULL;
-        process_message = NULL;
-        post_process_message = NULL;
-        max_message_size = NULL;
+        transition = client_read_transition;
+        process_message = client_process_message;
+        max_message_size = client_max_message_size;
+        post_process_message = client_post_process_message;
     }
 
     if (st->read_state_first_init) {
@@ -623,11 +637,10 @@ static enum SUB_STATE_RETURN write_state_machine(SSL *s)
         post_work = NULL;
         construct_message = NULL;
     } else {
-        /* TODO: Fill these in later when we've implemented them */
-        transition = NULL;
-        pre_work = NULL;
-        post_work = NULL;
-        construct_message = NULL;
+        transition = client_write_transition;
+        pre_work = client_pre_work;
+        post_work = client_post_work;
+        construct_message = client_construct_message;
     }
 
     while(1) {
@@ -704,6 +717,20 @@ static enum SUB_STATE_RETURN write_state_machine(SSL *s)
     }
 }
 
+/*
+ * Flush the write BIO
+ */
+static int statem_flush(SSL *s)
+{
+    s->rwstate = SSL_WRITING;
+    if (BIO_flush(s->wbio) <= 0) {
+        return 0;
+    }
+    s->rwstate = SSL_NOTHING;
+
+    return 1;
+}
+
 /*
  * Called by the record layer to determine whether application data is
  * allowed to be sent in the current handshake state or not.
@@ -712,14 +739,637 @@ static enum SUB_STATE_RETURN write_state_machine(SSL *s)
  *   1: Yes (application data allowed)
  *   0: No (application data not allowed)
  */
-int statem_client_app_data_allowed(SSL *s)
+int statem_app_data_allowed(SSL *s)
 {
     STATEM *st = &s->statem;
 
-    if(st->hand_state != TLS_ST_BEFORE &&
-            st->hand_state != TLS_ST_OK &&
-            st->hand_state != TLS_ST_CW_CLNT_HELLO)
+    if (!s->s3->in_read_app_data || (s->s3->total_renegotiations == 0))
+        return 0;
+
+    if (!s->server) {
+        if (st->state == MSG_FLOW_UNINITED || st->state == MSG_FLOW_RENEGOTIATE)
+            return 0;
+
+        if(st->hand_state == TLS_ST_CW_CLNT_HELLO)
+            return 1;
+
+        return 0;
+    }
+
+    /*
+     * This is the old check for code still using the old state machine. This
+     * will be removed by later commits
+     */
+    if (((s->state & SSL_ST_CONNECT) && SSL_IS_DTLS(s) &&
+          (s->state >= SSL3_ST_CW_CLNT_HELLO_A) &&
+          (s->state <= SSL3_ST_CR_SRVR_HELLO_A)) ||
+         ((s->state & SSL_ST_ACCEPT) &&
+               (s->state <= SSL3_ST_SW_HELLO_REQ_A) &&
+               (s->state >= SSL3_ST_SR_CLNT_HELLO_A))
+        )
+        return 1;
+
+    return 0;
+}
+
+
+#ifndef OPENSSL_NO_SCTP
+/*
+ * Set flag used by SCTP to determine whether we are in the read sock state
+ */
+void statem_set_sctp_read_sock(SSL *s, int read_sock)
+{
+    s->statem.in_sctp_read_sock = read_sock;
+}
+
+/*
+ * Called by the record layer to determine whether we are in the read sock
+ * state or not.
+ *
+ * Return values are:
+ *   1: Yes (we are in the read sock state)
+ *   0: No (we are not in the read sock state)
+ */
+int statem_in_sctp_read_sock(SSL *s)
+{
+    return s->statem.in_sctp_read_sock;
+}
+#endif
+
+/*
+ * Is a CertificateRequest message allowed at the moment or not?
+ *
+ *  Return values are:
+ *  1: Yes
+ *  0: No
+ */
+static inline int cert_req_allowed(SSL *s)
+{
+    /* TLS does not like anon-DH with client cert */
+    if (s->version > SSL3_VERSION
+            && (s->s3->tmp.new_cipher->algorithm_auth & SSL_aNULL))
+        return 0;
+
+    return 1;
+}
+
+/*
+ * Are we allowed to skip the ServerKeyExchange message?
+ *
+ *  Return values are:
+ *  1: Yes
+ *  0: No
+ */
+static inline int key_exchange_skip_allowed(SSL *s)
+{
+    long alg_k = s->s3->tmp.new_cipher->algorithm_mkey;
+
+    /*
+     * Can't skip server key exchange if this is an ephemeral
+     * ciphersuite.
+     */
+    if (alg_k & (SSL_kDHE | SSL_kECDHE)) {
         return 0;
+    }
 
     return 1;
 }
+
+/*
+ * client_read_transition() encapsulates the logic for the allowed handshake
+ * state transitions when the client is reading messages from the server. The
+ * message type that the server has sent is provided in |mt|. The current state
+ * is in |s->statem.hand_state|.
+ *
+ *  Return values are:
+ *  1: Success (transition allowed)
+ *  0: Error (transition not allowed)
+ */
+static int client_read_transition(SSL *s, int mt)
+{
+    STATEM *st = &s->statem;
+
+    switch(st->hand_state) {
+    case TLS_ST_CW_CLNT_HELLO:
+        if (mt == SSL3_MT_SERVER_HELLO) {
+            st->hand_state = TLS_ST_CR_SRVR_HELLO;
+            return 1;
+        }
+
+        if (SSL_IS_DTLS(s)) {
+            if (mt == DTLS1_MT_HELLO_VERIFY_REQUEST) {
+                st->hand_state = DTLS_ST_CR_HELLO_VERIFY_REQUEST;
+                return 1;
+            }
+        }
+        break;
+
+    case TLS_ST_CR_SRVR_HELLO:
+        if (s->hit) {
+            if (s->tlsext_ticket_expected) {
+                if (mt == SSL3_MT_NEWSESSION_TICKET) {
+                    st->hand_state = TLS_ST_CR_SESSION_TICKET;
+                    return 1;
+                }
+            } else if (mt == SSL3_MT_CHANGE_CIPHER_SPEC) {
+                st->hand_state = TLS_ST_CR_CHANGE;
+                return 1;
+            }
+        } else {
+            if (!(s->s3->tmp.new_cipher->algorithm_auth
+                        & (SSL_aNULL | SSL_aSRP | SSL_aPSK))) {
+                if (mt == SSL3_MT_CERTIFICATE) {
+                    st->hand_state = TLS_ST_CR_CERT;
+                    return 1;
+                }
+            } else {
+                if (mt == SSL3_MT_SERVER_KEY_EXCHANGE) {
+                    st->hand_state = TLS_ST_CR_KEY_EXCH;
+                    return 1;
+                } else if (key_exchange_skip_allowed(s)) {
+                    if (mt == SSL3_MT_CERTIFICATE_REQUEST
+                            && cert_req_allowed(s)) {
+                        st->hand_state = TLS_ST_CR_CERT_REQ;
+                        return 1;
+                    } else if (mt == SSL3_MT_SERVER_DONE) {
+                        st->hand_state = TLS_ST_CR_SRVR_DONE;
+                        return 1;
+                    }
+                }
+            }
+        }
+        break;
+
+    case TLS_ST_CR_CERT:
+        if (s->tlsext_status_expected) {
+            if (mt == SSL3_MT_CERTIFICATE_STATUS) {
+                st->hand_state = TLS_ST_CR_CERT_STATUS;
+                return 1;
+            }
+        } else {
+            if (mt == SSL3_MT_SERVER_KEY_EXCHANGE) {
+                st->hand_state = TLS_ST_CR_KEY_EXCH;
+                return 1;
+            } else if (key_exchange_skip_allowed(s)) {
+                if (mt == SSL3_MT_CERTIFICATE_REQUEST && cert_req_allowed(s)) {
+                    st->hand_state = TLS_ST_CR_CERT_REQ;
+                    return 1;
+                } else if (mt == SSL3_MT_SERVER_DONE) {
+                    st->hand_state = TLS_ST_CR_SRVR_DONE;
+                    return 1;
+                }
+            }
+        }
+        break;
+
+    case TLS_ST_CR_CERT_STATUS:
+        if (mt == SSL3_MT_SERVER_KEY_EXCHANGE) {
+            st->hand_state = TLS_ST_CR_KEY_EXCH;
+            return 1;
+        } else if (key_exchange_skip_allowed(s)) {
+            if (mt == SSL3_MT_CERTIFICATE_REQUEST && cert_req_allowed(s)) {
+                st->hand_state = TLS_ST_CR_CERT_REQ;
+                return 1;
+            } else if (mt == SSL3_MT_SERVER_DONE) {
+                st->hand_state = TLS_ST_CR_SRVR_DONE;
+                return 1;
+            }
+        }
+        break;
+
+    case TLS_ST_CR_KEY_EXCH:
+        if (mt == SSL3_MT_CERTIFICATE_REQUEST && cert_req_allowed(s)) {
+            st->hand_state = TLS_ST_CR_CERT_REQ;
+            return 1;
+        } else if (mt == SSL3_MT_SERVER_DONE) {
+            st->hand_state = TLS_ST_CR_SRVR_DONE;
+            return 1;
+        }
+        break;
+
+    case TLS_ST_CR_CERT_REQ:
+        if (mt == SSL3_MT_SERVER_DONE) {
+            st->hand_state = TLS_ST_CR_SRVR_DONE;
+            return 1;
+        }
+        break;
+
+    case TLS_ST_CW_FINISHED:
+        if (mt == SSL3_MT_NEWSESSION_TICKET && s->tlsext_ticket_expected) {
+            st->hand_state = TLS_ST_CR_SESSION_TICKET;
+            return 1;
+        } else if (mt == SSL3_MT_CHANGE_CIPHER_SPEC) {
+            st->hand_state = TLS_ST_CR_CHANGE;
+            return 1;
+        }
+        break;
+
+    case TLS_ST_CR_SESSION_TICKET:
+        if (mt == SSL3_MT_CHANGE_CIPHER_SPEC) {
+            st->hand_state = TLS_ST_CR_CHANGE;
+            return 1;
+        }
+        break;
+
+    case TLS_ST_CR_CHANGE:
+        if (mt == SSL3_MT_FINISHED) {
+            st->hand_state = TLS_ST_CR_FINISHED;
+            return 1;
+        }
+        break;
+
+    default:
+        break;
+    }
+
+    /* No valid transition found */
+    return 0;
+}
+
+/*
+ * client_write_transition() works out what handshake state to move to next
+ * when the client is writing messages to be sent to the server.
+ */
+static enum WRITE_TRAN client_write_transition(SSL *s)
+{
+    STATEM *st = &s->statem;
+
+    switch(st->hand_state) {
+        case TLS_ST_OK:
+            /* Renegotiation - fall through */
+        case TLS_ST_BEFORE:
+            st->hand_state = TLS_ST_CW_CLNT_HELLO;
+            return WRITE_TRAN_CONTINUE;
+
+        case TLS_ST_CW_CLNT_HELLO:
+            /*
+             * No transition at the end of writing because we don't know what
+             * we will be sent
+             */
+            return WRITE_TRAN_FINISHED;
+
+        case TLS_ST_CR_SRVR_DONE:
+            if (s->s3->tmp.cert_req)
+                st->hand_state = TLS_ST_CW_CERT;
+            else
+                st->hand_state = TLS_ST_CW_KEY_EXCH;
+            return WRITE_TRAN_CONTINUE;
+
+        case TLS_ST_CW_CERT:
+            st->hand_state = TLS_ST_CW_KEY_EXCH;
+            return WRITE_TRAN_CONTINUE;
+
+        case TLS_ST_CW_KEY_EXCH:
+            /*
+             * For TLS, cert_req is set to 2, so a cert chain of nothing is
+             * sent, but no verify packet is sent
+             */
+            /*
+             * XXX: For now, we do not support client authentication in ECDH
+             * cipher suites with ECDH (rather than ECDSA) certificates. We
+             * need to skip the certificate verify message when client's
+             * ECDH public key is sent inside the client certificate.
+             */
+            if (s->s3->tmp.cert_req == 1) {
+                st->hand_state = TLS_ST_CW_CERT_VRFY;
+            } else {
+                st->hand_state = TLS_ST_CW_CHANGE;
+            }
+            if (s->s3->flags & TLS1_FLAGS_SKIP_CERT_VERIFY) {
+                st->hand_state = TLS_ST_CW_CHANGE;
+            }
+            return WRITE_TRAN_CONTINUE;
+
+        case TLS_ST_CW_CERT_VRFY:
+            st->hand_state = TLS_ST_CW_CHANGE;
+            return WRITE_TRAN_CONTINUE;
+
+        case TLS_ST_CW_CHANGE:
+#if defined(OPENSSL_NO_NEXTPROTONEG)
+            st->hand_state = TLS_ST_CW_FINISHED;
+#else
+            if (s->s3->next_proto_neg_seen)
+                st->hand_state = TLS_ST_CW_NEXT_PROTO;
+            else
+                st->hand_state = TLS_ST_CW_FINISHED;
+#endif
+            return WRITE_TRAN_CONTINUE;
+
+#if !defined(OPENSSL_NO_NEXTPROTONEG)
+        case TLS_ST_CW_NEXT_PROTO:
+            st->hand_state = TLS_ST_CW_FINISHED;
+            return WRITE_TRAN_CONTINUE;
+#endif
+
+        case TLS_ST_CW_FINISHED:
+            if (s->hit) {
+                st->hand_state = TLS_ST_OK;
+                /* TODO: This needs removing */
+                s->state = SSL_ST_OK;
+                return WRITE_TRAN_CONTINUE;
+            } else {
+                return WRITE_TRAN_FINISHED;
+            }
+
+        case TLS_ST_CR_FINISHED:
+            if (s->hit) {
+                st->hand_state = TLS_ST_CW_CHANGE;
+                return WRITE_TRAN_CONTINUE;
+            } else {
+                st->hand_state = TLS_ST_OK;
+                /* TODO: This needs removing */
+                s->state = SSL_ST_OK;
+                return WRITE_TRAN_CONTINUE;
+            }
+
+        default:
+            /* Shouldn't happen */
+            return WRITE_TRAN_ERROR;
+    }
+}
+
+/*
+ * Perform any pre work that needs to be done prior to sending a message from
+ * the client to the server.
+ */
+static enum WORK_STATE client_pre_work(SSL *s, enum WORK_STATE wst)
+{
+    STATEM *st = &s->statem;
+
+    switch(st->hand_state) {
+    case TLS_ST_CW_CLNT_HELLO:
+        s->shutdown = 0;
+        break;
+
+    case TLS_ST_CW_CERT:
+        return tls_prepare_client_certificate(s, wst);
+
+    case TLS_ST_OK:
+        return tls_finish_handshake(s, wst);
+
+    default:
+        /* No pre work to be done */
+        break;
+    }
+
+    return WORK_FINISHED_CONTINUE;
+}
+
+/*
+ * Perform any work that needs to be done after sending a message from the
+ * client to the server.
+ */
+static enum WORK_STATE client_post_work(SSL *s, enum WORK_STATE wst)
+{
+    STATEM *st = &s->statem;
+
+    s->init_num = 0;
+
+    switch(st->hand_state) {
+    case TLS_ST_CW_CLNT_HELLO:
+        /* turn on buffering for the next lot of output */
+        if (s->bbio != s->wbio)
+            s->wbio = BIO_push(s->bbio, s->wbio);
+        break;
+
+    case TLS_ST_CW_KEY_EXCH:
+        if (tls_client_key_exchange_post_work(s) == 0)
+            return WORK_ERROR;
+        break;
+
+    case TLS_ST_CW_CHANGE:
+        s->session->cipher = s->s3->tmp.new_cipher;
+#ifdef OPENSSL_NO_COMP
+        s->session->compress_meth = 0;
+#else
+        if (s->s3->tmp.new_compression == NULL)
+            s->session->compress_meth = 0;
+        else
+            s->session->compress_meth = s->s3->tmp.new_compression->id;
+#endif
+        if (!s->method->ssl3_enc->setup_key_block(s))
+            return WORK_ERROR;
+
+        if (!s->method->ssl3_enc->change_cipher_state(s,
+                                                      SSL3_CHANGE_CIPHER_CLIENT_WRITE))
+            return WORK_ERROR;
+
+        if (SSL_IS_DTLS(s)) {
+#ifndef OPENSSL_NO_SCTP
+            if (s->hit) {
+                /*
+                 * Change to new shared key of SCTP-Auth, will be ignored if
+                 * no SCTP used.
+                 */
+                BIO_ctrl(SSL_get_wbio(s), BIO_CTRL_DGRAM_SCTP_NEXT_AUTH_KEY,
+                         0, NULL);
+            }
+#endif
+
+            dtls1_reset_seq_numbers(s, SSL3_CC_WRITE);
+        }
+        break;
+
+    case TLS_ST_CW_FINISHED:
+#ifndef OPENSSL_NO_SCTP
+        if (SSL_IS_DTLS(s) && s->hit == 0) {
+            /*
+             * Change to new shared key of SCTP-Auth, will be ignored if
+             * no SCTP used.
+             */
+            BIO_ctrl(SSL_get_wbio(s), BIO_CTRL_DGRAM_SCTP_NEXT_AUTH_KEY,
+                     0, NULL);
+        }
+#endif
+        if (statem_flush(s) != 1)
+            return WORK_MORE_B;
+
+        if (s->hit && tls_finish_handshake(s, WORK_MORE_A) != 1)
+                return WORK_ERROR;
+        break;
+
+    default:
+        /* No post work to be done */
+        break;
+    }
+
+    return WORK_FINISHED_CONTINUE;
+}
+
+/*
+ * Construct a message to be sent from the client to the server.
+ *
+ * Valid return values are:
+ *   1: Success
+ *   0: Error
+ */
+static int client_construct_message(SSL *s)
+{
+    STATEM *st = &s->statem;
+
+    switch(st->hand_state) {
+    case TLS_ST_CW_CLNT_HELLO:
+        return tls_construct_client_hello(s);
+
+    case TLS_ST_CW_CERT:
+        return tls_construct_client_certificate(s);
+
+    case TLS_ST_CW_KEY_EXCH:
+        return tls_construct_client_key_exchange(s);
+
+    case TLS_ST_CW_CERT_VRFY:
+        return tls_construct_client_verify(s);
+
+    case TLS_ST_CW_CHANGE:
+        return tls_construct_change_cipher_spec(s);
+
+#if !defined(OPENSSL_NO_NEXTPROTONEG)
+    case TLS_ST_CW_NEXT_PROTO:
+        return tls_construct_next_proto(s);
+#endif
+    case TLS_ST_CW_FINISHED:
+        return tls_construct_finished(s,
+                                      s->method->
+                                      ssl3_enc->client_finished_label,
+                                      s->method->
+                                      ssl3_enc->client_finished_label_len);
+
+    default:
+        /* Shouldn't happen */
+        break;
+    }
+
+    return 0;
+}
+
+/* The spec allows for a longer length than this, but we limit it */
+#define SERVER_HELLO_MAX_LENGTH         20000
+#define SERVER_KEY_EXCH_MAX_LENGTH      102400
+#define SERVER_HELLO_DONE_MAX_LENGTH    0
+#define CCS_MAX_LENGTH                  1
+/* Max should actually be 36 but we are generous */
+#define FINISHED_MAX_LENGTH             64
+
+/*
+ * Returns the maximum allowed length for the current message that we are
+ * reading. Excludes the message header.
+ */
+static unsigned long client_max_message_size(SSL *s)
+{
+    STATEM *st = &s->statem;
+
+    switch(st->hand_state) {
+        case TLS_ST_CR_SRVR_HELLO:
+            return SERVER_HELLO_MAX_LENGTH;
+
+        case TLS_ST_CR_CERT:
+            return s->max_cert_list;
+
+        case TLS_ST_CR_CERT_STATUS:
+            return SSL3_RT_MAX_PLAIN_LENGTH;
+
+        case TLS_ST_CR_KEY_EXCH:
+            return SERVER_KEY_EXCH_MAX_LENGTH;
+
+        case TLS_ST_CR_CERT_REQ:
+            return SSL3_RT_MAX_PLAIN_LENGTH;
+
+        case TLS_ST_CR_SRVR_DONE:
+            return SERVER_HELLO_DONE_MAX_LENGTH;
+
+        case TLS_ST_CR_CHANGE:
+            return CCS_MAX_LENGTH;
+
+        case TLS_ST_CR_SESSION_TICKET:
+            return SSL3_RT_MAX_PLAIN_LENGTH;
+
+        case TLS_ST_CR_FINISHED:
+            return FINISHED_MAX_LENGTH;
+
+        default:
+            /* Shouldn't happen */
+            break;
+    }
+
+    return 0;
+}
+
+/*
+ * Process a message that the client has been received from the server.
+ */
+static enum MSG_PROCESS_RETURN client_process_message(SSL *s, unsigned long len)
+{
+    STATEM *st = &s->statem;
+
+    switch(st->hand_state) {
+        case TLS_ST_CR_SRVR_HELLO:
+            return tls_process_server_hello(s, len);
+
+        case TLS_ST_CR_CERT:
+            return tls_process_server_certificate(s, len);
+
+        case TLS_ST_CR_CERT_STATUS:
+            return tls_process_cert_status(s, len);
+
+        case TLS_ST_CR_KEY_EXCH:
+            return tls_process_key_exchange(s, len);
+
+        case TLS_ST_CR_CERT_REQ:
+            return tls_process_certificate_request(s, len);
+
+        case TLS_ST_CR_SRVR_DONE:
+            return tls_process_server_done(s, len);
+
+        case TLS_ST_CR_CHANGE:
+            return tls_process_change_cipher_spec(s, len);
+
+        case TLS_ST_CR_SESSION_TICKET:
+            return tls_process_new_session_ticket(s, len);
+
+        case TLS_ST_CR_FINISHED:
+            return tls_process_finished(s, len);
+
+        default:
+            /* Shouldn't happen */
+            break;
+    }
+
+    return MSG_PROCESS_ERROR;
+}
+
+/*
+ * Perform any further processing required following the receipt of a message
+ * from the server
+ */
+static enum WORK_STATE client_post_process_message(SSL *s, enum WORK_STATE wst)
+{
+    STATEM *st = &s->statem;
+
+    switch(st->hand_state) {
+#ifndef OPENSSL_NO_SCTP
+    case TLS_ST_CR_SRVR_DONE:
+        /* We only get here if we are using SCTP and we are renegotiating */
+        if (BIO_dgram_sctp_msg_waiting(SSL_get_rbio(s))) {
+            s->s3->in_read_app_data = 2;
+            s->rwstate = SSL_READING;
+            BIO_clear_retry_flags(SSL_get_rbio(s));
+            BIO_set_retry_read(SSL_get_rbio(s));
+            statem_set_sctp_read_sock(s, 1);
+            return WORK_MORE_A;
+        }
+        statem_set_sctp_read_sock(s, 0);
+        return WORK_FINISHED_STOP;
+#endif
+
+    case TLS_ST_CR_FINISHED:
+        if (!s->hit)
+            return tls_finish_handshake(s, wst);
+        else
+            return WORK_FINISHED_STOP;
+    default:
+        break;
+    }
+
+    /* Shouldn't happen */
+    return WORK_ERROR;
+}