]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[tls] Parse ServerKeyExchange record immediately
authorMichael Brown <mcb30@ipxe.org>
Sun, 19 Jul 2026 14:08:57 +0000 (15:08 +0100)
committerMichael Brown <mcb30@ipxe.org>
Sun, 19 Jul 2026 14:30:10 +0000 (15:30 +0100)
As of commit 433a8f5 ("[tls] Retain a reference in the key schedule to
the bound identity"), the act of binding the server identity is
logically separated from the act of validating the server identity.
We may therefore bind the server identity (by verifying the signature
over the Diffie-Hellman parameters) and agree the ephemeral shared
secret immediately upon receiving the ServerKeyExchange record, rather
than deferring the verification until we have a validated identity.

This provides a closer match to the flow required for TLS version 1.3,
where the ephemeral shared secret is used for all messages after
ServerHello, and so must always be agreed prior to validation.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
src/include/ipxe/tls.h
src/net/tls.c

index d9da163930382fcd0b9f15e1cdd0172557b45ae9..4cd322d6226a2074ad4a8ab4e6fc589e2e95cfdf 100644 (file)
@@ -196,13 +196,23 @@ enum tls_tx_pending {
 struct tls_key_exchange_algorithm {
        /** Algorithm name */
        const char *name;
+       /**
+        * Receive new Server Key Exchange record using ECDHE key exchange
+        *
+        * @v tls               TLS connection
+        * @v data              Server Key Exchange handshake record
+        * @v len               Length of Server Key Exchange handshake record
+        * @ret rc              Return status code
+        */
+       int ( * server ) ( struct tls_connection *tls, const void *data,
+                          size_t len );
        /**
         * Transmit Client Key Exchange record
         *
         * @v tls               TLS connection
         * @ret rc              Return status code
         */
-       int ( * exchange ) ( struct tls_connection *tls );
+       int ( * client ) ( struct tls_connection *tls );
 };
 
 /** A TLS cipher suite */
@@ -499,10 +509,6 @@ struct tls_client {
 struct tls_server {
        /** Random bytes */
        uint8_t random[32];
-       /** Server Key Exchange record (if any) */
-       void *exchange;
-       /** Server Key Exchange record length */
-       size_t exchange_len;
        /** Root of trust */
        struct x509_root *root;
        /** Certificate chain */
index b06d5b64836e62ef2d0270b9a6eb17174107f7b6..106a8d5a1ee096635c5e6872e42f0baa9215c4c5 100644 (file)
@@ -208,6 +208,8 @@ static void tls_regenerate_ephemeral_master ( struct tls_connection *tls );
 static void tls_tx_resume_all ( struct tls_session *session );
 static struct io_buffer * tls_alloc_iob ( struct tls_connection *tls,
                                          size_t len );
+static int tls_send_handshake ( struct tls_connection *tls,
+                               const void *data, size_t len );
 static int tls_send_alert ( struct tls_connection *tls, unsigned int level,
                            unsigned int description );
 static int tls_send_record ( struct tls_connection *tls, unsigned int type,
@@ -342,7 +344,6 @@ static void free_tls ( struct refcnt *refcnt ) {
        tls_clear_cipher ( tls, &tls->rx.cipherspec.active );
        tls_clear_cipher ( tls, &tls->rx.cipherspec.pending );
        tls_clear_digest ( tls );
-       free ( tls->server.exchange );
        list_for_each_entry_safe ( iobuf, tmp, &tls->rx.data, list ) {
                list_del ( &iobuf->list );
                free_iob ( iobuf );
@@ -1438,766 +1439,786 @@ tls_find_param_group ( const void *dh_p, size_t dh_p_len, const void *dh_g,
        return NULL;
 }
 
-/******************************************************************************
- *
- * Record handling
- *
- ******************************************************************************
- */
-
 /**
- * Resume TX state machine
+ * Verify Diffie-Hellman parameter signature
  *
  * @v tls              TLS connection
+ * @v data             Server Key Exchange handshake record
+ * @v len              Length of Server Key Exchange handshake record
+ * @v param_len                Length of Diffie-Hellman parameters
+ * @ret rc             Return status code
  */
-static void tls_tx_resume ( struct tls_connection *tls ) {
-       process_add ( &tls->tx.process );
-}
+static int tls_verify_dh_params ( struct tls_connection *tls, const void *data,
+                                 size_t len, size_t param_len ) {
+       struct tls_cipherspec *cipherspec = &tls->tx.cipherspec.pending;
+       struct tls_signature_hash_algorithm *sig_hash;
+       struct x509_certificate *cert;
+       struct pubkey_algorithm *pubkey;
+       struct digest_algorithm *digest;
+       int use_sig_hash = tls_version ( tls, TLS_VERSION_TLS_1_2 );
+       const struct {
+               uint16_t sig_hash[use_sig_hash];
+               uint16_t signature_len;
+               uint8_t signature[0];
+       } __attribute__ (( packed )) *sig;
+       struct asn1_cursor signature;
+       size_t remaining;
+       int rc;
 
-/**
- * Resume TX state machine for all connections within a session
- *
- * @v session          TLS session
- */
-static void tls_tx_resume_all ( struct tls_session *session ) {
-       struct tls_connection *tls;
+       /* Identify server certificate */
+       cert = x509_first ( tls->server.chain );
+       if ( ! cert ) {
+               DBGC ( tls, "TLS %p has no server certificate\n", tls );
+               return -ENOENT_CERT;
+       }
 
-       list_for_each_entry ( tls, &session->conn, list )
-               tls_tx_resume ( tls );
+       /* Signature follows parameters */
+       assert ( param_len <= len );
+       sig = ( data + param_len );
+       remaining = ( len - param_len );
+
+       /* Parse signature from ServerKeyExchange */
+       if ( ( sizeof ( *sig ) > remaining ) ||
+            ( ntohs ( sig->signature_len ) > ( remaining -
+                                               sizeof ( *sig ) ) ) ) {
+               DBGC ( tls, "TLS %p received underlength ServerKeyExchange\n",
+                      tls );
+               DBGC_HDA ( tls, 0, data, len );
+               return -EINVAL_KEY_EXCHANGE;
+       }
+       signature.data = sig->signature;
+       signature.len = ntohs ( sig->signature_len );
+
+       /* Identify signature and hash algorithm */
+       if ( use_sig_hash ) {
+               sig_hash = tls_find_signature_hash ( sig->sig_hash[0] );
+               if ( ! sig_hash ) {
+                       DBGC ( tls, "TLS %p unsupported signature hash "
+                              "%#04x\n", tls, sig->sig_hash[0] );
+                       return -ENOTSUP_SIG_HASH;
+               }
+               pubkey = sig_hash->pubkey;
+               digest = sig_hash->digest;
+               DBGC ( tls, "TLS %p using signature hash %s-%s\n",
+                      tls, pubkey->name, digest->name );
+               if ( sig_hash->algorithm !=
+                    cert->subject.public_key.algorithm ) {
+                       DBGC ( tls, "TLS %p cannot use %s public key\n", tls,
+                              cert->subject.public_key.algorithm->name );
+                       return -EPERM_KEY_EXCHANGE;
+               }
+       } else {
+               pubkey = cipherspec->suite->pubkey;
+               digest = &md5_sha1_algorithm;
+       }
+
+       /* Verify signature */
+       {
+               uint8_t ctx[digest->ctxsize];
+               uint8_t hash[digest->digestsize];
+
+               /* Calculate digest */
+               digest_init ( digest, ctx );
+               digest_update ( digest, ctx, &tls->client.random,
+                               sizeof ( tls->client.random ) );
+               digest_update ( digest, ctx, tls->server.random,
+                               sizeof ( tls->server.random ) );
+               digest_update ( digest, ctx, data, param_len );
+               digest_final ( digest, ctx, hash );
+
+               /* Verify signature */
+               if ( ( rc = pubkey_verify ( pubkey,
+                                           &cert->subject.public_key.raw,
+                                           digest, hash,
+                                           &signature ) ) != 0 ) {
+                       DBGC ( tls, "TLS %p ServerKeyExchange failed "
+                              "verification\n", tls );
+                       DBGC_HDA ( tls, 0, data, len );
+                       return -EPERM_KEY_EXCHANGE;
+               }
+       }
+
+       /* The verified signature indicates the server's intention to
+        * delegate authority to the shared secret key material.  The
+        * shared secret is therefore bound to the server's identity.
+        */
+       tls_set_binding ( tls, cert );
+
+       return 0;
 }
 
 /**
- * Restart negotiation
+ * Receive new Server Key Exchange record using public key transport
  *
  * @v tls              TLS connection
+ * @v data             Server Key Exchange handshake record
+ * @v len              Length of Server Key Exchange handshake record
+ * @ret rc             Return status code
  */
-static void tls_restart ( struct tls_connection *tls ) {
-
-       /* Sanity check */
-       assert ( ! tls->tx.pending );
-       assert ( ! is_pending ( &tls->client.negotiation ) );
-       assert ( ! is_pending ( &tls->server.negotiation ) );
-       assert ( ! is_pending ( &tls->server.validation ) );
-
-       /* Reset ephemeral master secret */
-       tls_regenerate_ephemeral_master ( tls );
+static int tls_new_server_key_exchange_pubkey ( struct tls_connection *tls,
+                                               const void *data, size_t len ){
 
-       /* (Re)start negotiation */
-       tls->tx.pending = TLS_TX_CLIENT_HELLO;
-       tls_tx_resume ( tls );
-       pending_get ( &tls->client.negotiation );
-       pending_get ( &tls->server.negotiation );
+       /* Should never be received */
+       DBGC ( tls, "TLS %p received unexpected ServerKeyExchange:\n", tls );
+       DBGC_HDA ( tls, 0, data, len );
+       return -EPROTO;
 }
 
 /**
- * Transmit Handshake record
+ * Transmit Client Key Exchange record using public key exchange
  *
  * @v tls              TLS connection
- * @v data             Plaintext record
- * @v len              Length of plaintext record
  * @ret rc             Return status code
  */
-static int tls_send_handshake ( struct tls_connection *tls,
-                               const void *data, size_t len ) {
+static int tls_send_client_key_exchange_pubkey ( struct tls_connection *tls ) {
+       struct tls_cipherspec *cipherspec = &tls->tx.cipherspec.pending;
+       struct tls_key_schedule *key = &tls->key;
+       struct pubkey_algorithm *pubkey = cipherspec->suite->pubkey;
+       struct x509_certificate *cert;
+       struct {
+               uint16_t version;
+               uint8_t random[46];
+       } __attribute__ (( packed )) pre_master_secret;
+       struct asn1_cursor cursor = {
+               .data = &pre_master_secret,
+               .len = sizeof ( pre_master_secret ),
+       };
+       struct asn1_builder builder = { NULL, 0 };
+       int rc;
 
-       /* Send record */
-       return tls_send_plaintext ( tls, TLS_TYPE_HANDSHAKE, data, len );
+       /* Generate pre-master secret */
+       pre_master_secret.version = htons ( TLS_VERSION_MAX );
+       tls_ephemeral_label ( tls, "classic pre-master",
+                             &pre_master_secret.random,
+                             sizeof ( pre_master_secret.random ) );
+       tls_set_kdf_master ( tls, &pre_master_secret,
+                            sizeof ( pre_master_secret ) );
+
+       /* Key derivation function secret has been overwritten with a
+        * value that was not derived from its previous value, and so
+        * is no longer bound to the server's identity.
+        */
+       tls_clear_binding ( tls );
+
+       /* Key schedule now contains shared secret key material */
+       key->keyed = 1;
+
+       /* Identify server certificate */
+       cert = x509_first ( tls->server.chain );
+       if ( ! cert ) {
+               DBGC ( tls, "TLS %p has no server certificate\n", tls );
+               rc = -ENOENT_CERT;
+               goto err_cert;
+       }
+
+       /* Encrypt pre-master secret using server's public key */
+       if ( ( rc = pubkey_encrypt ( pubkey, &cert->subject.public_key.raw,
+                                    &cursor, &builder ) ) != 0 ) {
+               DBGC ( tls, "TLS %p could not encrypt pre-master secret: %s\n",
+                      tls, strerror ( rc ) );
+               goto err_encrypt;
+       }
+
+       /* Construct Client Key Exchange record */
+       {
+               struct {
+                       uint32_t type_length;
+                       uint16_t encrypted_pre_master_secret_len;
+               } __attribute__ (( packed )) header;
+
+               header.type_length =
+                       ( cpu_to_le32 ( TLS_CLIENT_KEY_EXCHANGE ) |
+                         htonl ( builder.len + sizeof ( header ) -
+                                 sizeof ( header.type_length ) ) );
+               header.encrypted_pre_master_secret_len = htons ( builder.len );
+
+               if ( ( rc = asn1_prepend_raw ( &builder, &header,
+                                              sizeof ( header ) ) ) != 0 ) {
+                       DBGC ( tls, "TLS %p could not construct Client Key "
+                              "Exchange: %s\n", tls, strerror ( rc ) );
+                       goto err_prepend;
+               }
+       }
+
+       /* Transmit Client Key Exchange record */
+       if ( ( rc = tls_send_handshake ( tls, builder.data,
+                                        builder.len ) ) != 0 ) {
+               goto err_send;
+       }
+
+       /* Shared secret has now been incorporated into the handshake
+        * digest.  It can be decrypted only with access to the
+        * certificate's private key, and has thereby been bound to
+        * the server's identity.
+        */
+       tls_set_binding ( tls, cert );
+
+ err_send:
+ err_prepend:
+ err_encrypt:
+       free ( builder.data );
+ err_cert:
+       return rc;
 }
 
+/** Public key exchange algorithm */
+struct tls_key_exchange_algorithm tls_pubkey_exchange_algorithm = {
+       .name = "pubkey",
+       .server = tls_new_server_key_exchange_pubkey,
+       .client = tls_send_client_key_exchange_pubkey,
+};
+
 /**
- * Digest or transmit Client Hello record
+ * Receive new Server Key Exchange record using DHE key exchange
  *
  * @v tls              TLS connection
- * @v action           Action to take on Client Hello record
+ * @v data             Server Key Exchange handshake record
+ * @v len              Length of Server Key Exchange handshake record
  * @ret rc             Return status code
  */
-static int tls_client_hello ( struct tls_connection *tls,
-                             int ( * action ) ( struct tls_connection *tls,
-                                                const void *data,
-                                                size_t len ) ) {
-       struct tls_session *session = tls->session;
-       size_t name_len = strlen ( session->name );
-       struct {
-               uint16_t type;
-               uint16_t len;
-               struct {
-                       uint16_t len;
-                       struct {
-                               uint8_t type;
-                               uint16_t len;
-                               uint8_t name[name_len];
-                       } __attribute__ (( packed )) list[1];
-               } __attribute__ (( packed )) data;
-       } __attribute__ (( packed )) *server_name_ext;
-       struct {
-               uint16_t type;
+static int tls_new_server_key_exchange_dhe ( struct tls_connection *tls,
+                                            const void *data, size_t len ) {
+       struct tls_named_group *group;
+       struct exchange_algorithm *exchange;
+       const struct {
                uint16_t len;
-               struct {
-                       uint8_t max;
-               } __attribute__ (( packed )) data;
-       } __attribute__ (( packed )) *max_fragment_length_ext;
-       struct {
-               uint16_t type;
-               uint16_t len;
-               struct {
-                       uint16_t len;
-                       uint16_t code[TLS_NUM_SIG_HASH_ALGORITHMS];
-               } __attribute__ (( packed )) data;
-       } __attribute__ (( packed )) *signature_algorithms_ext;
-       struct {
-               uint16_t type;
-               uint16_t len;
-               struct {
-                       uint8_t len;
-                       uint8_t data[ tls->secure_renegotiation ?
-                                     sizeof ( tls->verify.client ) :0 ];
-               } __attribute__ (( packed )) data;
-       } __attribute__ (( packed )) *renegotiation_info_ext;
-       struct {
-               uint16_t type;
-               uint16_t len;
-               struct {
-                       uint8_t data[session->ticket_len];
-               } __attribute__ (( packed )) data;
-       } __attribute__ (( packed )) *session_ticket_ext;
-       struct {
-               uint16_t type;
-               uint16_t len;
-               struct {
-                       uint16_t len;
-                       uint16_t code[TLS_NUM_NAMED_GROUPS];
-               } __attribute__ (( packed )) data;
-       } __attribute__ (( packed )) *named_group_ext;
-       struct {
-               uint16_t type;
-               uint16_t len;
-       } __attribute__ (( packed )) *extended_master_secret_ext;
-       struct {
-               typeof ( *server_name_ext ) server_name;
-               typeof ( *max_fragment_length_ext ) max_fragment_length;
-               typeof ( *signature_algorithms_ext ) signature_algorithms;
-               typeof ( *renegotiation_info_ext ) renegotiation_info;
-               typeof ( *session_ticket_ext ) session_ticket;
-               typeof ( *extended_master_secret_ext ) extended_master_secret;
-               typeof ( *named_group_ext )
-                       named_group[TLS_NUM_NAMED_GROUPS ? 1 : 0];
-       } __attribute__ (( packed )) *extensions;
-       struct {
-               uint32_t type_length;
-               uint16_t version;
-               uint8_t random[32];
-               uint8_t session_id_len;
-               uint8_t session_id[tls->session_id_len];
-               uint16_t cipher_suite_len;
-               uint16_t cipher_suites[TLS_NUM_CIPHER_SUITES];
-               uint8_t compression_methods_len;
-               uint8_t compression_methods[1];
-               uint16_t extensions_len;
-               typeof ( *extensions ) extensions;
-       } __attribute__ (( packed )) hello;
-       struct tls_cipher_suite *suite;
-       struct tls_signature_hash_algorithm *sighash;
-       struct tls_named_group *group;
+               uint8_t data[0];
+       } __attribute__ (( packed )) *dh_val[3];
+       typeof ( dh_val[0] ) dh_p;
+       typeof ( dh_val[1] ) dh_g;
+       typeof ( dh_val[2] ) dh_ys;
+       const void *param;
+       size_t remaining;
+       size_t frag_len;
+       size_t param_len;
        unsigned int i;
+       int rc;
 
-       /* Construct record */
-       memset ( &hello, 0, sizeof ( hello ) );
-       hello.type_length = ( cpu_to_le32 ( TLS_CLIENT_HELLO ) |
-                             htonl ( sizeof ( hello ) -
-                                     sizeof ( hello.type_length ) ) );
-       hello.version = htons ( TLS_VERSION_MAX );
-       memcpy ( &hello.random, &tls->client.random, sizeof ( hello.random ) );
-       hello.session_id_len = tls->session_id_len;
-       memcpy ( hello.session_id, tls->session_id,
-                sizeof ( hello.session_id ) );
-       hello.cipher_suite_len = htons ( sizeof ( hello.cipher_suites ) );
-       i = 0 ; for_each_table_entry ( suite, TLS_CIPHER_SUITES )
-               hello.cipher_suites[i++] = suite->code;
-       hello.compression_methods_len = sizeof ( hello.compression_methods );
-       hello.extensions_len = htons ( sizeof ( hello.extensions ) );
-       extensions = &hello.extensions;
-
-       /* Construct server name extension */
-       server_name_ext = &extensions->server_name;
-       server_name_ext->type = htons ( TLS_SERVER_NAME );
-       server_name_ext->len = htons ( sizeof ( server_name_ext->data ) );
-       server_name_ext->data.len
-               = htons ( sizeof ( server_name_ext->data.list ) );
-       server_name_ext->data.list[0].type = TLS_SERVER_NAME_HOST_NAME;
-       server_name_ext->data.list[0].len
-               = htons ( sizeof ( server_name_ext->data.list[0].name ) );
-       memcpy ( server_name_ext->data.list[0].name, session->name,
-                sizeof ( server_name_ext->data.list[0].name ) );
-
-       /* Construct maximum fragment length extension */
-       max_fragment_length_ext = &extensions->max_fragment_length;
-       max_fragment_length_ext->type = htons ( TLS_MAX_FRAGMENT_LENGTH );
-       max_fragment_length_ext->len
-               = htons ( sizeof ( max_fragment_length_ext->data ) );
-       max_fragment_length_ext->data.max = TLS_MAX_FRAGMENT_LENGTH_VALUE;
-
-       /* Construct supported signature algorithms extension */
-       signature_algorithms_ext = &extensions->signature_algorithms;
-       signature_algorithms_ext->type = htons ( TLS_SIGNATURE_ALGORITHMS );
-       signature_algorithms_ext->len
-               = htons ( sizeof ( signature_algorithms_ext->data ) );
-       signature_algorithms_ext->data.len
-               = htons ( sizeof ( signature_algorithms_ext->data.code ) );
-       i = 0 ; for_each_table_entry ( sighash, TLS_SIG_HASH_ALGORITHMS )
-               signature_algorithms_ext->data.code[i++] = sighash->code;
-
-       /* Construct renegotiation information extension */
-       renegotiation_info_ext = &extensions->renegotiation_info;
-       renegotiation_info_ext->type = htons ( TLS_RENEGOTIATION_INFO );
-       renegotiation_info_ext->len
-               = htons ( sizeof ( renegotiation_info_ext->data ) );
-       renegotiation_info_ext->data.len
-               = sizeof ( renegotiation_info_ext->data.data );
-       memcpy ( renegotiation_info_ext->data.data, tls->verify.client,
-                sizeof ( renegotiation_info_ext->data.data ) );
-
-       /* Construct session ticket extension */
-       session_ticket_ext = &extensions->session_ticket;
-       session_ticket_ext->type = htons ( TLS_SESSION_TICKET );
-       session_ticket_ext->len
-               = htons ( sizeof ( session_ticket_ext->data ) );
-       memcpy ( session_ticket_ext->data.data, session->ticket,
-                sizeof ( session_ticket_ext->data.data ) );
-
-       /* Construct extended master secret extension */
-       extended_master_secret_ext = &extensions->extended_master_secret;
-       extended_master_secret_ext->type
-               = htons ( TLS_EXTENDED_MASTER_SECRET );
-       extended_master_secret_ext->len = 0;
-
-       /* Construct named groups extension, if applicable */
-       if ( sizeof ( extensions->named_group ) ) {
-               named_group_ext = &extensions->named_group[0];
-               named_group_ext->type = htons ( TLS_NAMED_GROUP );
-               named_group_ext->len
-                       = htons ( sizeof ( named_group_ext->data ) );
-               named_group_ext->data.len
-                       = htons ( sizeof ( named_group_ext->data.code ) );
-               i = 0 ; for_each_table_entry ( group, TLS_NAMED_GROUPS ) {
-                       if ( group->code )
-                               named_group_ext->data.code[i++] = group->code;
+       /* Parse ServerKeyExchange */
+       param = data;
+       remaining = len;
+       for ( i = 0 ; i < ( sizeof ( dh_val ) / sizeof ( dh_val[0] ) ) ; i++ ){
+               dh_val[i] = param;
+               if ( ( sizeof ( *dh_val[i] ) > remaining ) ||
+                    ( ntohs ( dh_val[i]->len ) > ( remaining -
+                                                   sizeof ( *dh_val[i] ) ) )){
+                       DBGC ( tls, "TLS %p received underlength "
+                              "ServerKeyExchange\n", tls );
+                       DBGC_HDA ( tls, 0, data, len );
+                       return -EINVAL_KEY_EXCHANGE;
                }
-               assert ( i == TLS_NUM_NAMED_GROUPS );
+               frag_len = ( sizeof ( *dh_val[i] ) + ntohs ( dh_val[i]->len ));
+               param += frag_len;
+               remaining -= frag_len;
        }
+       param_len = ( len - remaining );
 
-       return action ( tls, &hello, sizeof ( hello ) );
-}
+       /* Identify named group */
+       dh_p = dh_val[0];
+       dh_g = dh_val[1];
+       dh_ys = dh_val[2];
+       group = tls_find_param_group ( dh_p->data, ntohs ( dh_p->len ),
+                                      dh_g->data, ntohs ( dh_g->len ) );
+       if ( ! group ) {
+               DBGC ( tls, "TLS %p unsupported %d-bit group:\n",
+                      tls, ( 8 * ntohs ( dh_p->len ) ) );
+               DBGC_HDA ( tls, 0, data, len );
+               return -ENOTSUP_GROUP;
+       }
+       tls->key.group = group;
+       exchange = group->exchange;
+       DBGC ( tls, "TLS %p using named group %s\n", tls, exchange->name );
 
-/**
- * Transmit Client Hello record
- *
- * @v tls              TLS connection
- * @ret rc             Return status code
- */
-static int tls_send_client_hello ( struct tls_connection *tls ) {
+       /* Generate pre-master secret */
+       if ( ( rc = tls_agree_ephemeral ( tls, dh_ys->data,
+                                         ntohs ( dh_ys->len ), 1 ) ) != 0 ) {
+               return rc;
+       }
 
-       return tls_client_hello ( tls, tls_send_handshake );
+       /* Verify parameter signature */
+       if ( ( rc = tls_verify_dh_params ( tls, data, len, param_len ) ) != 0 )
+               return rc;
+
+       return 0;
 }
 
 /**
- * Transmit Certificate record
+ * Transmit Client Key Exchange record using DHE key exchange
  *
  * @v tls              TLS connection
  * @ret rc             Return status code
  */
-static int tls_send_certificate ( struct tls_connection *tls ) {
-       struct {
-               tls24_t length;
-               uint8_t data[0];
-       } __attribute__ (( packed )) *certificate;
+static int tls_send_client_key_exchange_dhe ( struct tls_connection *tls ) {
+       struct tls_key_schedule *key = &tls->key;
+       struct tls_named_group *group = key->group;
+       struct exchange_algorithm *exchange = group->exchange;
+       size_t pubsize = exchange->pubsize;
        struct {
                uint32_t type_length;
-               tls24_t length;
-               typeof ( *certificate ) certificates[0];
-       } __attribute__ (( packed )) *certificates;
-       struct x509_link *link;
-       struct x509_certificate *cert;
-       struct io_buffer *iobuf;
-       size_t len;
+               uint16_t dh_xs_len;
+               uint8_t dh_xs[pubsize];
+       } __attribute__ (( packed )) *key_xchg;
+       int rc;
 
-       /* Calculate length of client certificates */
-       len = 0;
-       list_for_each_entry ( link, &tls->client.chain->links, list ) {
-               cert = link->cert;
-               len += ( sizeof ( *certificate ) + cert->raw.len );
-               DBGC ( tls, "TLS %p sending client certificate %s\n",
-                      tls, x509_name ( cert ) );
+       /* Allocate space */
+       key_xchg = malloc ( sizeof ( *key_xchg ) );
+       if ( ! key_xchg ) {
+               rc = -ENOMEM;
+               goto err_alloc;
        }
 
-       /* Allocate storage for Certificate record (which may be too
-        * large for the stack).
-        */
-       iobuf = tls_alloc_iob ( tls, ( sizeof ( *certificates ) + len ) );
-       if ( ! iobuf )
-               return -ENOMEM_CERTIFICATE;
+       /* Generate Client Key Exchange record */
+       key_xchg->type_length =
+               ( cpu_to_le32 ( TLS_CLIENT_KEY_EXCHANGE ) |
+                 htonl ( sizeof ( *key_xchg ) -
+                         sizeof ( key_xchg->type_length ) ) );
+       key_xchg->dh_xs_len = htons ( sizeof ( key_xchg->dh_xs ) );
+       if ( ( rc = tls_share_ephemeral ( tls, key_xchg->dh_xs ) ) != 0 )
+               goto err_share;
 
-       /* Populate record */
-       certificates = iob_put ( iobuf, sizeof ( *certificates ) );
-       certificates->type_length =
-               ( cpu_to_le32 ( TLS_CERTIFICATE ) |
-                 htonl ( sizeof ( *certificates ) + len -
-                         sizeof ( certificates->type_length ) ) );
-       tls_set_uint24 ( &certificates->length, len );
-       list_for_each_entry ( link, &tls->client.chain->links, list ) {
-               cert = link->cert;
-               certificate = iob_put ( iobuf, sizeof ( *certificate ) );
-               tls_set_uint24 ( &certificate->length, cert->raw.len );
-               memcpy ( iob_put ( iobuf, cert->raw.len ), cert->raw.data,
-                        cert->raw.len );
+       /* Transmit Client Key Exchange record */
+       if ( ( rc = tls_send_handshake ( tls, key_xchg,
+                                        sizeof ( *key_xchg ) ) ) !=0 ) {
+               goto err_send_handshake;
        }
 
-       /* Transmit record */
-       return tls_send_record ( tls, TLS_TYPE_HANDSHAKE,
-                                iob_disown ( iobuf ) );
+ err_send_handshake:
+ err_share:
+       free ( key_xchg );
+ err_alloc:
+       return rc;
 }
 
-/**
- * Transmit Client Key Exchange record using public key exchange
+/** Ephemeral Diffie-Hellman key exchange algorithm */
+struct tls_key_exchange_algorithm tls_dhe_exchange_algorithm = {
+       .name = "dhe",
+       .server = tls_new_server_key_exchange_dhe,
+       .client = tls_send_client_key_exchange_dhe,
+};
+
+/**
+ * Receive new Server Key Exchange record using ECDHE key exchange
  *
  * @v tls              TLS connection
+ * @v data             Server Key Exchange handshake record
+ * @v len              Length of Server Key Exchange handshake record
  * @ret rc             Return status code
  */
-static int tls_send_client_key_exchange_pubkey ( struct tls_connection *tls ) {
-       struct tls_cipherspec *cipherspec = &tls->tx.cipherspec.pending;
-       struct tls_key_schedule *key = &tls->key;
-       struct pubkey_algorithm *pubkey = cipherspec->suite->pubkey;
-       struct x509_certificate *cert;
-       struct {
-               uint16_t version;
-               uint8_t random[46];
-       } __attribute__ (( packed )) pre_master_secret;
-       struct asn1_cursor cursor = {
-               .data = &pre_master_secret,
-               .len = sizeof ( pre_master_secret ),
-       };
-       struct asn1_builder builder = { NULL, 0 };
+static int tls_new_server_key_exchange_ecdhe ( struct tls_connection *tls,
+                                              const void *data, size_t len ) {
+       struct tls_named_group *group;
+       struct exchange_algorithm *exchange;
+       const struct {
+               uint8_t curve_type;
+               uint16_t named_group;
+               uint8_t public_len;
+               uint8_t public[0];
+       } __attribute__ (( packed )) *ecdh = data;
+       size_t param_len;
        int rc;
 
-       /* Generate pre-master secret */
-       pre_master_secret.version = htons ( TLS_VERSION_MAX );
-       tls_ephemeral_label ( tls, "classic pre-master",
-                             &pre_master_secret.random,
-                             sizeof ( pre_master_secret.random ) );
-       tls_set_kdf_master ( tls, &pre_master_secret,
-                            sizeof ( pre_master_secret ) );
-
-       /* Key derivation function secret has been overwritten with a
-        * value that was not derived from its previous value, and so
-        * is no longer bound to the server's identity.
-        */
-       tls_clear_binding ( tls );
-
-       /* Key schedule now contains shared secret key material */
-       key->keyed = 1;
+       /* Parse ServerKeyExchange record */
+       if ( ( sizeof ( *ecdh ) > len ) ||
+            ( ecdh->public_len > ( len - sizeof ( *ecdh ) ) ) ) {
+               DBGC ( tls, "TLS %p received underlength ServerKeyExchange\n",
+                      tls );
+               DBGC_HDA ( tls, 0, data, len );
+               return -EINVAL_KEY_EXCHANGE;
+       }
+       param_len = ( sizeof ( *ecdh ) + ecdh->public_len );
 
-       /* Identify server certificate */
-       cert = x509_first ( tls->server.chain );
-       if ( ! cert ) {
-               DBGC ( tls, "TLS %p has no server certificate\n", tls );
-               rc = -ENOENT_CERT;
-               goto err_cert;
+       /* Identify named group */
+       if ( ecdh->curve_type != TLS_NAMED_CURVE_TYPE ) {
+               DBGC ( tls, "TLS %p unsupported curve type %d\n",
+                      tls, ecdh->curve_type );
+               DBGC_HDA ( tls, 0, data, len );
+               return -ENOTSUP_GROUP;
+       }
+       group = tls_find_named_group ( ecdh->named_group );
+       if ( ! group ) {
+               DBGC ( tls, "TLS %p unsupported named group %d\n",
+                      tls, ntohs ( ecdh->named_group ) );
+               DBGC_HDA ( tls, 0, data, len );
+               return -ENOTSUP_GROUP;
        }
+       tls->key.group = group;
+       exchange = group->exchange;
+       DBGC ( tls, "TLS %p using named group %s\n", tls, exchange->name );
 
-       /* Encrypt pre-master secret using server's public key */
-       if ( ( rc = pubkey_encrypt ( pubkey, &cert->subject.public_key.raw,
-                                    &cursor, &builder ) ) != 0 ) {
-               DBGC ( tls, "TLS %p could not encrypt pre-master secret: %s\n",
-                      tls, strerror ( rc ) );
-               goto err_encrypt;
+       /* Generate pre-master secret */
+       if ( ( rc = tls_agree_ephemeral ( tls, ecdh->public,
+                                         ecdh->public_len, 0 ) ) != 0 ) {
+               return rc;
        }
 
-       /* Construct Client Key Exchange record */
-       {
-               struct {
-                       uint32_t type_length;
-                       uint16_t encrypted_pre_master_secret_len;
-               } __attribute__ (( packed )) header;
+       /* Verify parameter signature */
+       if ( ( rc = tls_verify_dh_params ( tls, data, len, param_len ) ) != 0 )
+               return rc;
 
-               header.type_length =
-                       ( cpu_to_le32 ( TLS_CLIENT_KEY_EXCHANGE ) |
-                         htonl ( builder.len + sizeof ( header ) -
-                                 sizeof ( header.type_length ) ) );
-               header.encrypted_pre_master_secret_len = htons ( builder.len );
+       return 0;
+}
 
-               if ( ( rc = asn1_prepend_raw ( &builder, &header,
-                                              sizeof ( header ) ) ) != 0 ) {
-                       DBGC ( tls, "TLS %p could not construct Client Key "
-                              "Exchange: %s\n", tls, strerror ( rc ) );
-                       goto err_prepend;
-               }
-       }
+/**
+ * Transmit Client Key Exchange record using ECDHE key exchange
+ *
+ * @v tls              TLS connection
+ * @ret rc             Return status code
+ */
+static int tls_send_client_key_exchange_ecdhe ( struct tls_connection *tls ) {
+       struct tls_key_schedule *key = &tls->key;
+       struct tls_named_group *group = key->group;
+       struct exchange_algorithm *exchange = group->exchange;
+       size_t pubsize = exchange->pubsize;
+       struct {
+               uint32_t type_length;
+               uint8_t public_len;
+               uint8_t public[pubsize];
+       } __attribute__ (( packed )) key_xchg;
+       int rc;
+
+       /* Generate Client Key Exchange record */
+       key_xchg.type_length =
+               ( cpu_to_le32 ( TLS_CLIENT_KEY_EXCHANGE ) |
+                 htonl ( sizeof ( key_xchg ) -
+                         sizeof ( key_xchg.type_length ) ) );
+       key_xchg.public_len = sizeof ( key_xchg.public );
+       if ( ( rc = tls_share_ephemeral ( tls, key_xchg.public ) ) != 0 )
+               return rc;
 
        /* Transmit Client Key Exchange record */
-       if ( ( rc = tls_send_handshake ( tls, builder.data,
-                                        builder.len ) ) != 0 ) {
-               goto err_send;
+       if ( ( rc = tls_send_handshake ( tls, &key_xchg,
+                                        sizeof ( key_xchg ) ) ) !=0 ) {
+               return rc;
        }
 
-       /* Shared secret has now been incorporated into the handshake
-        * digest.  It can be decrypted only with access to the
-        * certificate's private key, and has thereby been bound to
-        * the server's identity.
-        */
-       tls_set_binding ( tls, cert );
+       return 0;
+}
 
-       /* Generate master secret */
-       tls_generate_master_secret ( tls );
+/** Ephemeral Elliptic Curve Diffie-Hellman key exchange algorithm */
+struct tls_key_exchange_algorithm tls_ecdhe_exchange_algorithm = {
+       .name = "ecdhe",
+       .server = tls_new_server_key_exchange_ecdhe,
+       .client = tls_send_client_key_exchange_ecdhe,
+};
 
- err_send:
- err_prepend:
- err_encrypt:
-       free ( builder.data );
- err_cert:
-       return rc;
+/******************************************************************************
+ *
+ * Record handling
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Resume TX state machine
+ *
+ * @v tls              TLS connection
+ */
+static void tls_tx_resume ( struct tls_connection *tls ) {
+       process_add ( &tls->tx.process );
 }
 
-/** Public key exchange algorithm */
-struct tls_key_exchange_algorithm tls_pubkey_exchange_algorithm = {
-       .name = "pubkey",
-       .exchange = tls_send_client_key_exchange_pubkey,
-};
+/**
+ * Resume TX state machine for all connections within a session
+ *
+ * @v session          TLS session
+ */
+static void tls_tx_resume_all ( struct tls_session *session ) {
+       struct tls_connection *tls;
+
+       list_for_each_entry ( tls, &session->conn, list )
+               tls_tx_resume ( tls );
+}
 
 /**
- * Verify Diffie-Hellman parameter signature
+ * Restart negotiation
+ *
+ * @v tls              TLS connection
+ */
+static void tls_restart ( struct tls_connection *tls ) {
+
+       /* Sanity check */
+       assert ( ! tls->tx.pending );
+       assert ( ! is_pending ( &tls->client.negotiation ) );
+       assert ( ! is_pending ( &tls->server.negotiation ) );
+       assert ( ! is_pending ( &tls->server.validation ) );
+
+       /* Reset ephemeral master secret */
+       tls_regenerate_ephemeral_master ( tls );
+
+       /* (Re)start negotiation */
+       tls->tx.pending = TLS_TX_CLIENT_HELLO;
+       tls_tx_resume ( tls );
+       pending_get ( &tls->client.negotiation );
+       pending_get ( &tls->server.negotiation );
+}
+
+/**
+ * Transmit Handshake record
  *
  * @v tls              TLS connection
- * @v param_len                Diffie-Hellman parameter length
+ * @v data             Plaintext record
+ * @v len              Length of plaintext record
  * @ret rc             Return status code
  */
-static int tls_verify_dh_params ( struct tls_connection *tls,
-                                 size_t param_len ) {
-       struct tls_cipherspec *cipherspec = &tls->tx.cipherspec.pending;
-       struct tls_signature_hash_algorithm *sig_hash;
-       struct x509_certificate *cert;
-       struct pubkey_algorithm *pubkey;
-       struct digest_algorithm *digest;
-       int use_sig_hash = tls_version ( tls, TLS_VERSION_TLS_1_2 );
-       const struct {
-               uint16_t sig_hash[use_sig_hash];
-               uint16_t signature_len;
-               uint8_t signature[0];
-       } __attribute__ (( packed )) *sig;
-       struct asn1_cursor signature;
-       const void *data;
-       size_t remaining;
-       int rc;
+static int tls_send_handshake ( struct tls_connection *tls,
+                               const void *data, size_t len ) {
 
-       /* Identify server certificate */
-       cert = x509_first ( tls->server.chain );
-       if ( ! cert ) {
-               DBGC ( tls, "TLS %p has no server certificate\n", tls );
-               return -ENOENT_CERT;
-       }
+       /* Send record */
+       return tls_send_plaintext ( tls, TLS_TYPE_HANDSHAKE, data, len );
+}
 
-       /* Signature follows parameters */
-       assert ( param_len <= tls->server.exchange_len );
-       data = ( tls->server.exchange + param_len );
-       remaining = ( tls->server.exchange_len - param_len );
+/**
+ * Digest or transmit Client Hello record
+ *
+ * @v tls              TLS connection
+ * @v action           Action to take on Client Hello record
+ * @ret rc             Return status code
+ */
+static int tls_client_hello ( struct tls_connection *tls,
+                             int ( * action ) ( struct tls_connection *tls,
+                                                const void *data,
+                                                size_t len ) ) {
+       struct tls_session *session = tls->session;
+       size_t name_len = strlen ( session->name );
+       struct {
+               uint16_t type;
+               uint16_t len;
+               struct {
+                       uint16_t len;
+                       struct {
+                               uint8_t type;
+                               uint16_t len;
+                               uint8_t name[name_len];
+                       } __attribute__ (( packed )) list[1];
+               } __attribute__ (( packed )) data;
+       } __attribute__ (( packed )) *server_name_ext;
+       struct {
+               uint16_t type;
+               uint16_t len;
+               struct {
+                       uint8_t max;
+               } __attribute__ (( packed )) data;
+       } __attribute__ (( packed )) *max_fragment_length_ext;
+       struct {
+               uint16_t type;
+               uint16_t len;
+               struct {
+                       uint16_t len;
+                       uint16_t code[TLS_NUM_SIG_HASH_ALGORITHMS];
+               } __attribute__ (( packed )) data;
+       } __attribute__ (( packed )) *signature_algorithms_ext;
+       struct {
+               uint16_t type;
+               uint16_t len;
+               struct {
+                       uint8_t len;
+                       uint8_t data[ tls->secure_renegotiation ?
+                                     sizeof ( tls->verify.client ) :0 ];
+               } __attribute__ (( packed )) data;
+       } __attribute__ (( packed )) *renegotiation_info_ext;
+       struct {
+               uint16_t type;
+               uint16_t len;
+               struct {
+                       uint8_t data[session->ticket_len];
+               } __attribute__ (( packed )) data;
+       } __attribute__ (( packed )) *session_ticket_ext;
+       struct {
+               uint16_t type;
+               uint16_t len;
+               struct {
+                       uint16_t len;
+                       uint16_t code[TLS_NUM_NAMED_GROUPS];
+               } __attribute__ (( packed )) data;
+       } __attribute__ (( packed )) *named_group_ext;
+       struct {
+               uint16_t type;
+               uint16_t len;
+       } __attribute__ (( packed )) *extended_master_secret_ext;
+       struct {
+               typeof ( *server_name_ext ) server_name;
+               typeof ( *max_fragment_length_ext ) max_fragment_length;
+               typeof ( *signature_algorithms_ext ) signature_algorithms;
+               typeof ( *renegotiation_info_ext ) renegotiation_info;
+               typeof ( *session_ticket_ext ) session_ticket;
+               typeof ( *extended_master_secret_ext ) extended_master_secret;
+               typeof ( *named_group_ext )
+                       named_group[TLS_NUM_NAMED_GROUPS ? 1 : 0];
+       } __attribute__ (( packed )) *extensions;
+       struct {
+               uint32_t type_length;
+               uint16_t version;
+               uint8_t random[32];
+               uint8_t session_id_len;
+               uint8_t session_id[tls->session_id_len];
+               uint16_t cipher_suite_len;
+               uint16_t cipher_suites[TLS_NUM_CIPHER_SUITES];
+               uint8_t compression_methods_len;
+               uint8_t compression_methods[1];
+               uint16_t extensions_len;
+               typeof ( *extensions ) extensions;
+       } __attribute__ (( packed )) hello;
+       struct tls_cipher_suite *suite;
+       struct tls_signature_hash_algorithm *sighash;
+       struct tls_named_group *group;
+       unsigned int i;
 
-       /* Parse signature from ServerKeyExchange */
-       sig = data;
-       if ( ( sizeof ( *sig ) > remaining ) ||
-            ( ntohs ( sig->signature_len ) > ( remaining -
-                                               sizeof ( *sig ) ) ) ) {
-               DBGC ( tls, "TLS %p received underlength ServerKeyExchange\n",
-                      tls );
-               DBGC_HDA ( tls, 0, tls->server.exchange,
-                          tls->server.exchange_len );
-               return -EINVAL_KEY_EXCHANGE;
-       }
-       signature.data = sig->signature;
-       signature.len = ntohs ( sig->signature_len );
+       /* Construct record */
+       memset ( &hello, 0, sizeof ( hello ) );
+       hello.type_length = ( cpu_to_le32 ( TLS_CLIENT_HELLO ) |
+                             htonl ( sizeof ( hello ) -
+                                     sizeof ( hello.type_length ) ) );
+       hello.version = htons ( TLS_VERSION_MAX );
+       memcpy ( &hello.random, &tls->client.random, sizeof ( hello.random ) );
+       hello.session_id_len = tls->session_id_len;
+       memcpy ( hello.session_id, tls->session_id,
+                sizeof ( hello.session_id ) );
+       hello.cipher_suite_len = htons ( sizeof ( hello.cipher_suites ) );
+       i = 0 ; for_each_table_entry ( suite, TLS_CIPHER_SUITES )
+               hello.cipher_suites[i++] = suite->code;
+       hello.compression_methods_len = sizeof ( hello.compression_methods );
+       hello.extensions_len = htons ( sizeof ( hello.extensions ) );
+       extensions = &hello.extensions;
 
-       /* Identify signature and hash algorithm */
-       if ( use_sig_hash ) {
-               sig_hash = tls_find_signature_hash ( sig->sig_hash[0] );
-               if ( ! sig_hash ) {
-                       DBGC ( tls, "TLS %p unsupported signature hash "
-                              "%#04x\n", tls, sig->sig_hash[0] );
-                       return -ENOTSUP_SIG_HASH;
-               }
-               pubkey = sig_hash->pubkey;
-               digest = sig_hash->digest;
-               DBGC ( tls, "TLS %p using signature hash %s-%s\n",
-                      tls, pubkey->name, digest->name );
-               if ( sig_hash->algorithm !=
-                    cert->subject.public_key.algorithm ) {
-                       DBGC ( tls, "TLS %p cannot use %s public key\n", tls,
-                              cert->subject.public_key.algorithm->name );
-                       return -EPERM_KEY_EXCHANGE;
-               }
-       } else {
-               pubkey = cipherspec->suite->pubkey;
-               digest = &md5_sha1_algorithm;
-       }
+       /* Construct server name extension */
+       server_name_ext = &extensions->server_name;
+       server_name_ext->type = htons ( TLS_SERVER_NAME );
+       server_name_ext->len = htons ( sizeof ( server_name_ext->data ) );
+       server_name_ext->data.len
+               = htons ( sizeof ( server_name_ext->data.list ) );
+       server_name_ext->data.list[0].type = TLS_SERVER_NAME_HOST_NAME;
+       server_name_ext->data.list[0].len
+               = htons ( sizeof ( server_name_ext->data.list[0].name ) );
+       memcpy ( server_name_ext->data.list[0].name, session->name,
+                sizeof ( server_name_ext->data.list[0].name ) );
+
+       /* Construct maximum fragment length extension */
+       max_fragment_length_ext = &extensions->max_fragment_length;
+       max_fragment_length_ext->type = htons ( TLS_MAX_FRAGMENT_LENGTH );
+       max_fragment_length_ext->len
+               = htons ( sizeof ( max_fragment_length_ext->data ) );
+       max_fragment_length_ext->data.max = TLS_MAX_FRAGMENT_LENGTH_VALUE;
 
-       /* Verify signature */
-       {
-               uint8_t ctx[digest->ctxsize];
-               uint8_t hash[digest->digestsize];
+       /* Construct supported signature algorithms extension */
+       signature_algorithms_ext = &extensions->signature_algorithms;
+       signature_algorithms_ext->type = htons ( TLS_SIGNATURE_ALGORITHMS );
+       signature_algorithms_ext->len
+               = htons ( sizeof ( signature_algorithms_ext->data ) );
+       signature_algorithms_ext->data.len
+               = htons ( sizeof ( signature_algorithms_ext->data.code ) );
+       i = 0 ; for_each_table_entry ( sighash, TLS_SIG_HASH_ALGORITHMS )
+               signature_algorithms_ext->data.code[i++] = sighash->code;
 
-               /* Calculate digest */
-               digest_init ( digest, ctx );
-               digest_update ( digest, ctx, &tls->client.random,
-                               sizeof ( tls->client.random ) );
-               digest_update ( digest, ctx, tls->server.random,
-                               sizeof ( tls->server.random ) );
-               digest_update ( digest, ctx, tls->server.exchange, param_len );
-               digest_final ( digest, ctx, hash );
+       /* Construct renegotiation information extension */
+       renegotiation_info_ext = &extensions->renegotiation_info;
+       renegotiation_info_ext->type = htons ( TLS_RENEGOTIATION_INFO );
+       renegotiation_info_ext->len
+               = htons ( sizeof ( renegotiation_info_ext->data ) );
+       renegotiation_info_ext->data.len
+               = sizeof ( renegotiation_info_ext->data.data );
+       memcpy ( renegotiation_info_ext->data.data, tls->verify.client,
+                sizeof ( renegotiation_info_ext->data.data ) );
 
-               /* Verify signature */
-               if ( ( rc = pubkey_verify ( pubkey,
-                                           &cert->subject.public_key.raw,
-                                           digest, hash,
-                                           &signature ) ) != 0 ) {
-                       DBGC ( tls, "TLS %p ServerKeyExchange failed "
-                              "verification\n", tls );
-                       DBGC_HDA ( tls, 0, tls->server.exchange,
-                                  tls->server.exchange_len );
-                       return -EPERM_KEY_EXCHANGE;
+       /* Construct session ticket extension */
+       session_ticket_ext = &extensions->session_ticket;
+       session_ticket_ext->type = htons ( TLS_SESSION_TICKET );
+       session_ticket_ext->len
+               = htons ( sizeof ( session_ticket_ext->data ) );
+       memcpy ( session_ticket_ext->data.data, session->ticket,
+                sizeof ( session_ticket_ext->data.data ) );
+
+       /* Construct extended master secret extension */
+       extended_master_secret_ext = &extensions->extended_master_secret;
+       extended_master_secret_ext->type
+               = htons ( TLS_EXTENDED_MASTER_SECRET );
+       extended_master_secret_ext->len = 0;
+
+       /* Construct named groups extension, if applicable */
+       if ( sizeof ( extensions->named_group ) ) {
+               named_group_ext = &extensions->named_group[0];
+               named_group_ext->type = htons ( TLS_NAMED_GROUP );
+               named_group_ext->len
+                       = htons ( sizeof ( named_group_ext->data ) );
+               named_group_ext->data.len
+                       = htons ( sizeof ( named_group_ext->data.code ) );
+               i = 0 ; for_each_table_entry ( group, TLS_NAMED_GROUPS ) {
+                       if ( group->code )
+                               named_group_ext->data.code[i++] = group->code;
                }
+               assert ( i == TLS_NUM_NAMED_GROUPS );
        }
 
-       /* The verified signature indicates the server's intention to
-        * delegate authority to the shared secret key material.  The
-        * shared secret is therefore bound to the server's identity.
-        */
-       tls_set_binding ( tls, cert );
-
-       return 0;
+       return action ( tls, &hello, sizeof ( hello ) );
 }
 
 /**
- * Transmit Client Key Exchange record using DHE key exchange
+ * Transmit Client Hello record
  *
  * @v tls              TLS connection
  * @ret rc             Return status code
  */
-static int tls_send_client_key_exchange_dhe ( struct tls_connection *tls ) {
-       struct tls_named_group *group;
-       struct exchange_algorithm *exchange;
-       const struct {
-               uint16_t len;
-               uint8_t data[0];
-       } __attribute__ (( packed )) *dh_val[3];
-       typeof ( dh_val[0] ) dh_p;
-       typeof ( dh_val[1] ) dh_g;
-       typeof ( dh_val[2] ) dh_ys;
-       const void *data;
-       size_t remaining;
-       size_t frag_len;
-       size_t param_len;
-       unsigned int i;
-       int rc;
-
-       /* Parse ServerKeyExchange */
-       data = tls->server.exchange;
-       remaining = tls->server.exchange_len;
-       for ( i = 0 ; i < ( sizeof ( dh_val ) / sizeof ( dh_val[0] ) ) ; i++ ){
-               dh_val[i] = data;
-               if ( ( sizeof ( *dh_val[i] ) > remaining ) ||
-                    ( ntohs ( dh_val[i]->len ) > ( remaining -
-                                                   sizeof ( *dh_val[i] ) ) )){
-                       DBGC ( tls, "TLS %p received underlength "
-                              "ServerKeyExchange\n", tls );
-                       DBGC_HDA ( tls, 0, tls->server.exchange,
-                                  tls->server.exchange_len );
-                       rc = -EINVAL_KEY_EXCHANGE;
-                       goto err_header;
-               }
-               frag_len = ( sizeof ( *dh_val[i] ) + ntohs ( dh_val[i]->len ));
-               data += frag_len;
-               remaining -= frag_len;
-       }
-       param_len = ( tls->server.exchange_len - remaining );
-
-       /* Identify named group */
-       dh_p = dh_val[0];
-       dh_g = dh_val[1];
-       dh_ys = dh_val[2];
-       group = tls_find_param_group ( dh_p->data, ntohs ( dh_p->len ),
-                                      dh_g->data, ntohs ( dh_g->len ) );
-       if ( ! group ) {
-               DBGC ( tls, "TLS %p unsupported %d-bit group:\n",
-                      tls, ( 8 * ntohs ( dh_p->len ) ) );
-               DBGC_HDA ( tls, 0, tls->server.exchange,
-                          tls->server.exchange_len );
-               rc = -ENOTSUP_GROUP;
-               goto err_group;
-       }
-       tls->key.group = group;
-       exchange = group->exchange;
-       DBGC ( tls, "TLS %p using named group %s\n", tls, exchange->name );
-
-       /* Generate pre-master secret */
-       if ( ( rc = tls_agree_ephemeral ( tls, dh_ys->data,
-                                         ntohs ( dh_ys->len ), 1 ) ) != 0 ) {
-               goto err_agree;
-       }
-
-       /* Verify parameter signature */
-       if ( ( rc = tls_verify_dh_params ( tls, param_len ) ) != 0 )
-               goto err_verify;
-
-       /* Construct and send ClientKeyExchange record */
-       {
-               size_t pubsize = exchange->pubsize;
-               struct {
-                       uint32_t type_length;
-                       uint16_t dh_xs_len;
-                       uint8_t dh_xs[pubsize];
-               } __attribute__ (( packed )) *key_xchg;
-
-               /* Allocate space */
-               key_xchg = malloc ( sizeof ( *key_xchg ) );
-               if ( ! key_xchg ) {
-                       rc = -ENOMEM;
-                       goto err_alloc;
-               }
-
-               /* Generate Client Key Exchange record */
-               key_xchg->type_length =
-                       ( cpu_to_le32 ( TLS_CLIENT_KEY_EXCHANGE ) |
-                         htonl ( sizeof ( *key_xchg ) -
-                                 sizeof ( key_xchg->type_length ) ) );
-               key_xchg->dh_xs_len = htons ( sizeof ( key_xchg->dh_xs ) );
-               if ( ( rc = tls_share_ephemeral ( tls,
-                                                 key_xchg->dh_xs ) ) != 0 ) {
-                       goto err_share;
-               }
-
-               /* Transmit Client Key Exchange record */
-               if ( ( rc = tls_send_handshake ( tls, key_xchg,
-                                                sizeof ( *key_xchg ) ) ) !=0){
-                       goto err_send_handshake;
-               }
-
-               /* Generate master secret */
-               tls_generate_master_secret ( tls );
+static int tls_send_client_hello ( struct tls_connection *tls ) {
 
-       err_send_handshake:
-       err_share:
-               free ( key_xchg );
-       }
- err_alloc:
- err_verify:
- err_agree:
- err_group:
- err_header:
-       return rc;
+       return tls_client_hello ( tls, tls_send_handshake );
 }
 
-/** Ephemeral Diffie-Hellman key exchange algorithm */
-struct tls_key_exchange_algorithm tls_dhe_exchange_algorithm = {
-       .name = "dhe",
-       .exchange = tls_send_client_key_exchange_dhe,
-};
-
 /**
- * Transmit Client Key Exchange record using ECDHE key exchange
+ * Transmit Certificate record
  *
  * @v tls              TLS connection
  * @ret rc             Return status code
  */
-static int tls_send_client_key_exchange_ecdhe ( struct tls_connection *tls ) {
-       struct tls_named_group *group;
-       struct exchange_algorithm *exchange;
-       const struct {
-               uint8_t curve_type;
-               uint16_t named_group;
-               uint8_t public_len;
-               uint8_t public[0];
-       } __attribute__ (( packed )) *ecdh;
-       size_t param_len;
-       int rc;
-
-       /* Parse ServerKeyExchange record */
-       ecdh = tls->server.exchange;
-       if ( ( sizeof ( *ecdh ) > tls->server.exchange_len ) ||
-            ( ecdh->public_len > ( tls->server.exchange_len -
-                                   sizeof ( *ecdh ) ) ) ) {
-               DBGC ( tls, "TLS %p received underlength ServerKeyExchange\n",
-                      tls );
-               DBGC_HDA ( tls, 0, tls->server.exchange,
-                          tls->server.exchange_len );
-               return -EINVAL_KEY_EXCHANGE;
-       }
-       param_len = ( sizeof ( *ecdh ) + ecdh->public_len );
-
-       /* Identify named group */
-       if ( ecdh->curve_type != TLS_NAMED_CURVE_TYPE ) {
-               DBGC ( tls, "TLS %p unsupported curve type %d\n",
-                      tls, ecdh->curve_type );
-               DBGC_HDA ( tls, 0, tls->server.exchange,
-                          tls->server.exchange_len );
-               return -ENOTSUP_GROUP;
-       }
-       group = tls_find_named_group ( ecdh->named_group );
-       if ( ! group ) {
-               DBGC ( tls, "TLS %p unsupported named group %d\n",
-                      tls, ntohs ( ecdh->named_group ) );
-               DBGC_HDA ( tls, 0, tls->server.exchange,
-                          tls->server.exchange_len );
-               return -ENOTSUP_GROUP;
-       }
-       tls->key.group = group;
-       exchange = group->exchange;
-       DBGC ( tls, "TLS %p using named group %s\n", tls, exchange->name );
+static int tls_send_certificate ( struct tls_connection *tls ) {
+       struct {
+               tls24_t length;
+               uint8_t data[0];
+       } __attribute__ (( packed )) *certificate;
+       struct {
+               uint32_t type_length;
+               tls24_t length;
+               typeof ( *certificate ) certificates[0];
+       } __attribute__ (( packed )) *certificates;
+       struct x509_link *link;
+       struct x509_certificate *cert;
+       struct io_buffer *iobuf;
+       size_t len;
 
-       /* Generate pre-master secret */
-       if ( ( rc = tls_agree_ephemeral ( tls, ecdh->public,
-                                         ecdh->public_len, 0 ) ) != 0 ) {
-               return rc;
+       /* Calculate length of client certificates */
+       len = 0;
+       list_for_each_entry ( link, &tls->client.chain->links, list ) {
+               cert = link->cert;
+               len += ( sizeof ( *certificate ) + cert->raw.len );
+               DBGC ( tls, "TLS %p sending client certificate %s\n",
+                      tls, x509_name ( cert ) );
        }
 
-       /* Verify parameter signature */
-       if ( ( rc = tls_verify_dh_params ( tls, param_len ) ) != 0 )
-               return rc;
-
-       /* Construct and send ClientKeyExchange record */
-       {
-               size_t pubsize = exchange->pubsize;
-               struct {
-                       uint32_t type_length;
-                       uint8_t public_len;
-                       uint8_t public[pubsize];
-               } __attribute__ (( packed )) key_xchg;
-
-               /* Generate Client Key Exchange record */
-               key_xchg.type_length =
-                       ( cpu_to_le32 ( TLS_CLIENT_KEY_EXCHANGE ) |
-                         htonl ( sizeof ( key_xchg ) -
-                                 sizeof ( key_xchg.type_length ) ) );
-               key_xchg.public_len = sizeof ( key_xchg.public );
-               if ( ( rc = tls_share_ephemeral ( tls,
-                                                 key_xchg.public ) ) != 0 ) {
-                       return rc;
-               }
-
-               /* Transmit Client Key Exchange record */
-               if ( ( rc = tls_send_handshake ( tls, &key_xchg,
-                                                sizeof ( key_xchg ) ) ) !=0){
-                       return rc;
-               }
+       /* Allocate storage for Certificate record (which may be too
+        * large for the stack).
+        */
+       iobuf = tls_alloc_iob ( tls, ( sizeof ( *certificates ) + len ) );
+       if ( ! iobuf )
+               return -ENOMEM_CERTIFICATE;
 
-               /* Generate master secret */
-               tls_generate_master_secret ( tls );
+       /* Populate record */
+       certificates = iob_put ( iobuf, sizeof ( *certificates ) );
+       certificates->type_length =
+               ( cpu_to_le32 ( TLS_CERTIFICATE ) |
+                 htonl ( sizeof ( *certificates ) + len -
+                         sizeof ( certificates->type_length ) ) );
+       tls_set_uint24 ( &certificates->length, len );
+       list_for_each_entry ( link, &tls->client.chain->links, list ) {
+               cert = link->cert;
+               certificate = iob_put ( iobuf, sizeof ( *certificate ) );
+               tls_set_uint24 ( &certificate->length, cert->raw.len );
+               memcpy ( iob_put ( iobuf, cert->raw.len ), cert->raw.data,
+                        cert->raw.len );
        }
 
-       return 0;
+       /* Transmit record */
+       return tls_send_record ( tls, TLS_TYPE_HANDSHAKE,
+                                iob_disown ( iobuf ) );
 }
 
-/** Ephemeral Elliptic Curve Diffie-Hellman key exchange algorithm */
-struct tls_key_exchange_algorithm tls_ecdhe_exchange_algorithm = {
-       .name = "ecdhe",
-       .exchange = tls_send_client_key_exchange_ecdhe,
-};
-
 /**
  * Transmit Client Key Exchange record
  *
@@ -2210,12 +2231,15 @@ static int tls_send_client_key_exchange ( struct tls_connection *tls ) {
        int rc;
 
        /* Transmit Client Key Exchange record via key exchange algorithm */
-       if ( ( rc = suite->exchange->exchange ( tls ) ) != 0 ) {
+       if ( ( rc = suite->exchange->client ( tls ) ) != 0 ) {
                DBGC ( tls, "TLS %p could not exchange keys: %s\n",
                       tls, strerror ( rc ) );
                return rc;
        }
 
+       /* Generate master secret */
+       tls_generate_master_secret ( tls );
+
        /* Generate keys from master secret */
        if ( ( rc = tls_generate_keys ( tls ) ) != 0 ) {
                DBGC ( tls, "TLS %p could not generate keys: %s\n",
@@ -2914,23 +2938,13 @@ static int tls_new_certificate ( struct tls_connection *tls,
  */
 static int tls_new_server_key_exchange ( struct tls_connection *tls,
                                         const void *data, size_t len ) {
+       struct tls_cipherspec *cipherspec = &tls->tx.cipherspec.pending;
+       struct tls_cipher_suite *suite = cipherspec->suite;
+       int rc;
 
-       /* Free any existing server key exchange record */
-       free ( tls->server.exchange );
-       tls->server.exchange_len = 0;
-
-       /* Allocate copy of server key exchange record */
-       tls->server.exchange = malloc ( len );
-       if ( ! tls->server.exchange )
-               return -ENOMEM;
-
-       /* Store copy of server key exchange record for later
-        * processing.  We cannot verify the signature at this point
-        * since the certificate validation will not yet have
-        * completed.
-        */
-       memcpy ( tls->server.exchange, data, len );
-       tls->server.exchange_len = len;
+       /* Parse via key exchange algorithm */
+       if ( ( rc = suite->exchange->server ( tls, data, len ) ) != 0 )
+               return rc;
 
        return 0;
 }