TLS now working again.
authorMichael Brown <mcb30@etherboot.org>
Mon, 30 Jul 2007 01:48:38 +0000 (02:48 +0100)
committerMichael Brown <mcb30@etherboot.org>
Mon, 30 Jul 2007 01:48:38 +0000 (02:48 +0100)
src/include/gpxe/tls.h
src/net/tcp/http.c
src/net/tls.c [new file with mode: 0644]

index 19ab980..a8cf16e 100644 (file)
 #ifndef _GPXE_TLS_H
 #define _GPXE_TLS_H
 
-#include <errno.h>
+/**
+ * @file
+ *
+ * Transport Layer Security Protocol
+ */
 
-struct stream_application;
+#include <stdint.h>
+#include <gpxe/refcnt.h>
+#include <gpxe/filter.h>
+#include <gpxe/process.h>
+#include <gpxe/crypto.h>
+#include <gpxe/md5.h>
+#include <gpxe/sha1.h>
 
-static inline int add_tls ( struct stream_application *app __unused ) {
-       return -ENOTSUP;
-}
+/** A TLS header */
+struct tls_header {
+       /** Content type
+        *
+        * This is a TLS_TYPE_XXX constant
+        */
+       uint8_t type;
+       /** Protocol version
+        *
+        * This is a TLS_VERSION_XXX constant
+        */
+       uint16_t version;
+       /** Length of payload */
+       uint16_t length;
+} __attribute__ (( packed ));
+
+/** TLS version 1.0 */
+#define TLS_VERSION_TLS_1_0 0x0301
+
+/** TLS version 1.1 */
+#define TLS_VERSION_TLS_1_1 0x0302
+
+/** Change cipher content type */
+#define TLS_TYPE_CHANGE_CIPHER 20
+
+/** Alert content type */
+#define TLS_TYPE_ALERT 21
+
+/** Handshake content type */
+#define TLS_TYPE_HANDSHAKE 22
+
+/** Application data content type */
+#define TLS_TYPE_DATA 23
+
+/* Handshake message types */
+#define TLS_HELLO_REQUEST 0
+#define TLS_CLIENT_HELLO 1
+#define TLS_SERVER_HELLO 2
+#define TLS_CERTIFICATE 11
+#define TLS_SERVER_KEY_EXCHANGE 12
+#define TLS_CERTIFICATE_REQUEST 13
+#define TLS_SERVER_HELLO_DONE 14
+#define TLS_CERTIFICATE_VERIFY 15
+#define TLS_CLIENT_KEY_EXCHANGE 16
+#define TLS_FINISHED 20
+
+/* TLS alert levels */
+#define TLS_ALERT_WARNING 1
+#define TLS_ALERT_FATAL 2
+
+/* TLS cipher specifications */
+#define TLS_RSA_WITH_NULL_MD5 0x0001
+#define TLS_RSA_WITH_NULL_SHA 0x0002
+#define TLS_RSA_WITH_AES_128_CBC_SHA 0x002f
+#define TLS_RSA_WITH_AES_256_CBC_SHA 0x0035
+
+/** TLS RX state machine state */
+enum tls_rx_state {
+       TLS_RX_HEADER = 0,
+       TLS_RX_DATA,
+};
+
+/** TLS TX state machine state */
+enum tls_tx_state {
+       TLS_TX_NONE = 0,
+       TLS_TX_CLIENT_HELLO,
+       TLS_TX_CLIENT_KEY_EXCHANGE,
+       TLS_TX_CHANGE_CIPHER,
+       TLS_TX_FINISHED,
+       TLS_TX_DATA
+};
+
+/** A TLS cipher specification */
+struct tls_cipherspec {
+       /** Public-key encryption algorithm */
+       struct crypto_algorithm *pubkey;
+       /** Bulk encryption cipher algorithm */
+       struct crypto_algorithm *cipher;
+       /** MAC digest algorithm */
+       struct crypto_algorithm *digest;
+       /** Key length */
+       size_t key_len;
+       /** Dynamically-allocated storage */
+       void *dynamic;
+       /** Public key encryption context */
+       void *pubkey_ctx;
+       /** Bulk encryption cipher context */
+       void *cipher_ctx;
+       /** Next bulk encryption cipher context (TX only) */
+       void *cipher_next_ctx;
+       /** MAC secret */
+       void *mac_secret;
+};
+
+/** A TLS session */
+struct tls_session {
+       /** Reference counter */
+       struct refcnt refcnt;
+
+       /** Plaintext stream */
+       struct xfer_filter_half plainstream;
+       /** Ciphertext stream */
+       struct xfer_filter_half cipherstream;
+
+       /** Current TX cipher specification */
+       struct tls_cipherspec tx_cipherspec;
+       /** Next TX cipher specification */
+       struct tls_cipherspec tx_cipherspec_pending;
+       /** Current RX cipher specification */
+       struct tls_cipherspec rx_cipherspec;
+       /** Next RX cipher specification */
+       struct tls_cipherspec rx_cipherspec_pending;
+       /** Premaster secret */
+       uint8_t pre_master_secret[48];
+       /** Master secret */
+       uint8_t master_secret[48];
+       /** Server random bytes */
+       uint8_t server_random[32];
+       /** Client random bytes */
+       uint8_t client_random[32];
+       /** MD5 context for handshake verification */
+       uint8_t handshake_md5_ctx[MD5_CTX_SIZE];
+       /** SHA1 context for handshake verification */
+       uint8_t handshake_sha1_ctx[SHA1_CTX_SIZE];
+
+       /** Hack: server RSA public key */
+       uint8_t *rsa_mod;
+       size_t rsa_mod_len;
+       uint8_t *rsa_pub_exp;
+       size_t rsa_pub_exp_len;
+
+       /** TX sequence number */
+       uint64_t tx_seq;
+       /** TX state */
+       enum tls_tx_state tx_state;
+       /** TX process */
+       struct process process;
+
+       /** RX sequence number */
+       uint64_t rx_seq;
+       /** RX state */
+       enum tls_rx_state rx_state;
+       /** Offset within current RX state */
+       size_t rx_rcvd;
+       /** Current received record header */
+       struct tls_header rx_header;
+       /** Current received raw data buffer */
+       void *rx_data;
+};
+
+extern int add_tls ( struct xfer_interface *xfer,
+                    struct xfer_interface **next );
 
 #endif /* _GPXE_TLS_H */
index 9c24368..bdd791e 100644 (file)
@@ -468,6 +468,7 @@ static struct xfer_interface_operations http_xfer_operations = {
 static int http_open ( struct xfer_interface *xfer, struct uri *uri ) {
        struct http_request *http;
        struct sockaddr_tcpip server;
+       struct xfer_interface *socket;
        int rc;
 
        /* Sanity checks */
@@ -487,18 +488,16 @@ static int http_open ( struct xfer_interface *xfer, struct uri *uri ) {
        /* Open socket */
        memset ( &server, 0, sizeof ( server ) );
        server.st_port = htons ( uri_port ( http->uri, HTTP_PORT ) );
-       if ( ( rc = xfer_open_named_socket ( &http->socket, SOCK_STREAM,
-                                            ( struct sockaddr * ) &server,
-                                            uri->host, NULL ) ) != 0 )
-               goto err;
-
-#if 0
+       socket = &http->socket;
        if ( strcmp ( http->uri->scheme, "https" ) == 0 ) {
-               st->st_port = htons ( uri_port ( http->uri, HTTPS_PORT ) );
-               if ( ( rc = add_tls ( &http->stream ) ) != 0 )
+               server.st_port = htons ( uri_port ( http->uri, HTTPS_PORT ) );
+               if ( ( rc = add_tls ( socket, &socket ) ) != 0 )
                        goto err;
        }
-#endif
+       if ( ( rc = xfer_open_named_socket ( socket, SOCK_STREAM,
+                                            ( struct sockaddr * ) &server,
+                                            uri->host, NULL ) ) != 0 )
+               goto err;
 
        /* Attach to parent interface, mortalise self, and return */
        xfer_plug_plug ( &http->xfer, xfer );
diff --git a/src/net/tls.c b/src/net/tls.c
new file mode 100644 (file)
index 0000000..1cd995a
--- /dev/null
@@ -0,0 +1,1732 @@
+/*
+ * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/**
+ * @file
+ *
+ * Transport Layer Security Protocol
+ */
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <byteswap.h>
+#include <gpxe/hmac.h>
+#include <gpxe/md5.h>
+#include <gpxe/sha1.h>
+#include <gpxe/aes.h>
+#include <gpxe/rsa.h>
+#include <gpxe/xfer.h>
+#include <gpxe/open.h>
+#include <gpxe/filter.h>
+#include <gpxe/tls.h>
+
+static int tls_send_plaintext ( struct tls_session *tls, unsigned int type,
+                               const void *data, size_t len );
+static void tls_clear_cipher ( struct tls_session *tls,
+                              struct tls_cipherspec *cipherspec );
+
+/**
+ * Free TLS session
+ *
+ * @v refcnt           Reference counter
+ */
+static void free_tls ( struct refcnt *refcnt ) {
+       struct tls_session *tls =
+               container_of ( refcnt, struct tls_session, refcnt );
+
+       /* Free dynamically-allocated resources */
+       tls_clear_cipher ( tls, &tls->tx_cipherspec );
+       tls_clear_cipher ( tls, &tls->tx_cipherspec_pending );
+       tls_clear_cipher ( tls, &tls->rx_cipherspec );
+       tls_clear_cipher ( tls, &tls->rx_cipherspec_pending );
+       free ( tls->rsa_mod );
+       free ( tls->rsa_pub_exp );
+       free ( tls->rx_data );
+
+       /* Free TLS structure itself */
+       free ( tls );   
+}
+
+/**
+ * Finish with TLS session
+ *
+ * @v tls              TLS session
+ * @v rc               Status code
+ */
+static void tls_close ( struct tls_session *tls, int rc ) {
+
+       /* Remove process */
+       process_del ( &tls->process );
+       
+       /* Close ciphertext and plaintext streams */
+       xfer_nullify ( &tls->cipherstream.xfer );
+       xfer_close ( &tls->cipherstream.xfer, rc );
+       xfer_nullify ( &tls->plainstream.xfer );
+       xfer_close ( &tls->plainstream.xfer, rc );
+}
+
+/******************************************************************************
+ *
+ * Random number generation
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Generate random data
+ *
+ * @v data             Buffer to fill
+ * @v len              Length of buffer
+ */
+static void tls_generate_random ( void *data, size_t len ) {
+#warning "Placeholder"
+       memset ( data, 0x01, len );
+}
+
+/**
+ * Update HMAC with a list of ( data, len ) pairs
+ *
+ * @v digest           Hash function to use
+ * @v digest_ctx       Digest context
+ * @v args             ( data, len ) pairs of data, terminated by NULL
+ */
+static void tls_hmac_update_va ( struct crypto_algorithm *digest,
+                                void *digest_ctx, va_list args ) {
+       void *data;
+       size_t len;
+
+       while ( ( data = va_arg ( args, void * ) ) ) {
+               len = va_arg ( args, size_t );
+               hmac_update ( digest, digest_ctx, data, len );
+       }
+}
+
+/**
+ * Generate secure pseudo-random data using a single hash function
+ *
+ * @v tls              TLS session
+ * @v digest           Hash function to use
+ * @v secret           Secret
+ * @v secret_len       Length of secret
+ * @v out              Output buffer
+ * @v out_len          Length of output buffer
+ * @v seeds            ( data, len ) pairs of seed data, terminated by NULL
+ */
+static void tls_p_hash_va ( struct tls_session *tls,
+                           struct crypto_algorithm *digest,
+                           void *secret, size_t secret_len,
+                           void *out, size_t out_len,
+                           va_list seeds ) {
+       uint8_t secret_copy[secret_len];
+       uint8_t digest_ctx[digest->ctxsize];
+       uint8_t digest_ctx_partial[digest->ctxsize];
+       uint8_t a[digest->digestsize];
+       uint8_t out_tmp[digest->digestsize];
+       size_t frag_len = digest->digestsize;
+       va_list tmp;
+
+       /* Copy the secret, in case HMAC modifies it */
+       memcpy ( secret_copy, secret, secret_len );
+       secret = secret_copy;
+       DBGC2 ( tls, "TLS %p %s secret:\n", tls, digest->name );
+       DBGC2_HD ( tls, secret, secret_len );
+
+       /* Calculate A(1) */
+       hmac_init ( digest, digest_ctx, secret, &secret_len );
+       va_copy ( tmp, seeds );
+       tls_hmac_update_va ( digest, digest_ctx, tmp );
+       va_end ( tmp );
+       hmac_final ( digest, digest_ctx, secret, &secret_len, a );
+       DBGC2 ( tls, "TLS %p %s A(1):\n", tls, digest->name );
+       DBGC2_HD ( tls, &a, sizeof ( a ) );
+
+       /* Generate as much data as required */
+       while ( out_len ) {
+               /* Calculate output portion */
+               hmac_init ( digest, digest_ctx, secret, &secret_len );
+               hmac_update ( digest, digest_ctx, a, sizeof ( a ) );
+               memcpy ( digest_ctx_partial, digest_ctx, digest->ctxsize );
+               va_copy ( tmp, seeds );
+               tls_hmac_update_va ( digest, digest_ctx, tmp );
+               va_end ( tmp );
+               hmac_final ( digest, digest_ctx,
+                            secret, &secret_len, out_tmp );
+
+               /* Copy output */
+               if ( frag_len > out_len )
+                       frag_len = out_len;
+               memcpy ( out, out_tmp, frag_len );
+               DBGC2 ( tls, "TLS %p %s output:\n", tls, digest->name );
+               DBGC2_HD ( tls, out, frag_len );
+
+               /* Calculate A(i) */
+               hmac_final ( digest, digest_ctx_partial,
+                            secret, &secret_len, a );
+               DBGC2 ( tls, "TLS %p %s A(n):\n", tls, digest->name );
+               DBGC2_HD ( tls, &a, sizeof ( a ) );
+
+               out += frag_len;
+               out_len -= frag_len;
+       }
+}
+
+/**
+ * Generate secure pseudo-random data
+ *
+ * @v tls              TLS session
+ * @v secret           Secret
+ * @v secret_len       Length of secret
+ * @v out              Output buffer
+ * @v out_len          Length of output buffer
+ * @v ...              ( data, len ) pairs of seed data, terminated by NULL
+ */
+static void tls_prf ( struct tls_session *tls, void *secret, size_t secret_len,
+                     void *out, size_t out_len, ... ) {
+       va_list seeds;
+       va_list tmp;
+       size_t subsecret_len;
+       void *md5_secret;
+       void *sha1_secret;
+       uint8_t out_md5[out_len];
+       uint8_t out_sha1[out_len];
+       unsigned int i;
+
+       va_start ( seeds, out_len );
+
+       /* Split secret into two, with an overlap of up to one byte */
+       subsecret_len = ( ( secret_len + 1 ) / 2 );
+       md5_secret = secret;
+       sha1_secret = ( secret + secret_len - subsecret_len );
+
+       /* Calculate MD5 portion */
+       va_copy ( tmp, seeds );
+       tls_p_hash_va ( tls, &md5_algorithm, md5_secret, subsecret_len,
+                       out_md5, out_len, seeds );
+       va_end ( tmp );
+
+       /* Calculate SHA1 portion */
+       va_copy ( tmp, seeds );
+       tls_p_hash_va ( tls, &sha1_algorithm, sha1_secret, subsecret_len,
+                       out_sha1, out_len, seeds );
+       va_end ( tmp );
+
+       /* XOR the two portions together into the final output buffer */
+       for ( i = 0 ; i < out_len ; i++ ) {
+               *( ( uint8_t * ) out + i ) = ( out_md5[i] ^ out_sha1[i] );
+       }
+
+       va_end ( seeds );
+}
+
+/**
+ * Generate secure pseudo-random data
+ *
+ * @v secret           Secret
+ * @v secret_len       Length of secret
+ * @v out              Output buffer
+ * @v out_len          Length of output buffer
+ * @v label            String literal label
+ * @v ...              ( data, len ) pairs of seed data
+ */
+#define tls_prf_label( tls, secret, secret_len, out, out_len, label, ... ) \
+       tls_prf ( (tls), (secret), (secret_len), (out), (out_len),         \
+                 label, ( sizeof ( label ) - 1 ), __VA_ARGS__, NULL )
+
+/******************************************************************************
+ *
+ * Secret management
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Generate master secret
+ *
+ * @v tls              TLS session
+ *
+ * The pre-master secret and the client and server random values must
+ * already be known.
+ */
+static void tls_generate_master_secret ( struct tls_session *tls ) {
+       DBGC ( tls, "TLS %p pre-master-secret:\n", tls );
+       DBGC_HD ( tls, &tls->pre_master_secret,
+                 sizeof ( tls->pre_master_secret ) );
+       DBGC ( tls, "TLS %p client random bytes:\n", tls );
+       DBGC_HD ( tls, &tls->client_random, sizeof ( tls->server_random ) );
+       DBGC ( tls, "TLS %p server random bytes:\n", tls );
+       DBGC_HD ( tls, &tls->server_random, sizeof ( tls->server_random ) );
+
+       tls_prf_label ( tls, tls->pre_master_secret,
+                       sizeof ( tls->pre_master_secret ),
+                       tls->master_secret, sizeof ( tls->master_secret ),
+                       "master secret",
+                       tls->client_random, sizeof ( tls->client_random ),
+                       tls->server_random, sizeof ( tls->server_random ) );
+
+       DBGC ( tls, "TLS %p generated master secret:\n", tls );
+       DBGC_HD ( tls, &tls->master_secret, sizeof ( tls->master_secret ) );
+}
+
+/**
+ * Generate key material
+ *
+ * @v tls              TLS session
+ *
+ * The master secret must already be known.
+ */
+static int tls_generate_keys ( struct tls_session *tls ) {
+       struct tls_cipherspec *tx_cipherspec = &tls->tx_cipherspec_pending;
+       struct tls_cipherspec *rx_cipherspec = &tls->rx_cipherspec_pending;
+       size_t hash_size = tx_cipherspec->digest->digestsize;
+       size_t key_size = tx_cipherspec->key_len;
+       size_t iv_size = tx_cipherspec->cipher->blocksize;
+       size_t total = ( 2 * ( hash_size + key_size + iv_size ) );
+       uint8_t key_block[total];
+       uint8_t *key;
+       int rc;
+
+       /* Generate key block */
+       tls_prf_label ( tls, tls->master_secret, sizeof ( tls->master_secret ),
+                       key_block, sizeof ( key_block ), "key expansion",
+                       tls->server_random, sizeof ( tls->server_random ),
+                       tls->client_random, sizeof ( tls->client_random ) );
+
+       /* Split key block into portions */
+       key = key_block;
+
+       /* TX MAC secret */
+       memcpy ( tx_cipherspec->mac_secret, key, hash_size );
+       DBGC ( tls, "TLS %p TX MAC secret:\n", tls );
+       DBGC_HD ( tls, key, hash_size );
+       key += hash_size;
+
+       /* RX MAC secret */
+       memcpy ( rx_cipherspec->mac_secret, key, hash_size );
+       DBGC ( tls, "TLS %p RX MAC secret:\n", tls );
+       DBGC_HD ( tls, key, hash_size );
+       key += hash_size;
+
+       /* TX key */
+       if ( ( rc = cipher_setkey ( tx_cipherspec->cipher,
+                                   tx_cipherspec->cipher_ctx,
+                                   key, key_size ) ) != 0 ) {
+               DBGC ( tls, "TLS %p could not set TX key: %s\n",
+                      tls, strerror ( rc ) );
+               return rc;
+       }
+       DBGC ( tls, "TLS %p TX key:\n", tls );
+       DBGC_HD ( tls, key, key_size );
+       key += key_size;
+
+       /* RX key */
+       if ( ( rc = cipher_setkey ( rx_cipherspec->cipher,
+                                   rx_cipherspec->cipher_ctx,
+                                   key, key_size ) ) != 0 ) {
+               DBGC ( tls, "TLS %p could not set TX key: %s\n",
+                      tls, strerror ( rc ) );
+               return rc;
+       }
+
+#warning "AES needs to be fixed to not require this"
+       AES_convert_key ( rx_cipherspec->cipher_ctx );
+
+       DBGC ( tls, "TLS %p RX key:\n", tls );
+       DBGC_HD ( tls, key, key_size );
+       key += key_size;
+
+       /* TX initialisation vector */
+       cipher_setiv ( tx_cipherspec->cipher, tx_cipherspec->cipher_ctx, key );
+       DBGC ( tls, "TLS %p TX IV:\n", tls );
+       DBGC_HD ( tls, key, iv_size );
+       key += iv_size;
+
+       /* RX initialisation vector */
+       cipher_setiv ( rx_cipherspec->cipher, rx_cipherspec->cipher_ctx, key );
+       DBGC ( tls, "TLS %p RX IV:\n", tls );
+       DBGC_HD ( tls, key, iv_size );
+       key += iv_size;
+
+       assert ( ( key_block + total ) == key );
+
+       return 0;
+}
+
+/******************************************************************************
+ *
+ * Cipher suite management
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Clear cipher suite
+ *
+ * @v cipherspec       TLS cipher specification
+ */
+static void tls_clear_cipher ( struct tls_session *tls __unused,
+                              struct tls_cipherspec *cipherspec ) {
+       free ( cipherspec->dynamic );
+       memset ( cipherspec, 0, sizeof ( cipherspec ) );
+       cipherspec->pubkey = &crypto_null;
+       cipherspec->cipher = &crypto_null;
+       cipherspec->digest = &crypto_null;
+}
+
+/**
+ * Set cipher suite
+ *
+ * @v tls              TLS session
+ * @v cipherspec       TLS cipher specification
+ * @v pubkey           Public-key encryption elgorithm
+ * @v cipher           Bulk encryption cipher algorithm
+ * @v digest           MAC digest algorithm
+ * @v key_len          Key length
+ * @ret rc             Return status code
+ */
+static int tls_set_cipher ( struct tls_session *tls,
+                           struct tls_cipherspec *cipherspec,
+                           struct crypto_algorithm *pubkey,
+                           struct crypto_algorithm *cipher,
+                           struct crypto_algorithm *digest,
+                           size_t key_len ) {
+       size_t total;
+       void *dynamic;
+
+       /* Clear out old cipher contents, if any */
+       tls_clear_cipher ( tls, cipherspec );
+       
+       /* Allocate dynamic storage */
+       total = ( pubkey->ctxsize + 2 * cipher->ctxsize + digest->digestsize );
+       dynamic = malloc ( total );
+       if ( ! dynamic ) {
+               DBGC ( tls, "TLS %p could not allocate %zd bytes for crypto "
+                      "context\n", tls, total );
+               return -ENOMEM;
+       }
+       memset ( dynamic, 0, total );
+
+       /* Assign storage */
+       cipherspec->dynamic = dynamic;
+       cipherspec->pubkey_ctx = dynamic;       dynamic += pubkey->ctxsize;
+       cipherspec->cipher_ctx = dynamic;       dynamic += cipher->ctxsize;
+       cipherspec->cipher_next_ctx = dynamic;  dynamic += cipher->ctxsize;
+       cipherspec->mac_secret = dynamic;       dynamic += digest->digestsize;
+       assert ( ( cipherspec->dynamic + total ) == dynamic );
+
+       /* Store parameters */
+       cipherspec->pubkey = pubkey;
+       cipherspec->cipher = cipher;
+       cipherspec->digest = digest;
+       cipherspec->key_len = key_len;
+
+       return 0;
+}
+
+/**
+ * Select next cipher suite
+ *
+ * @v tls              TLS session
+ * @v cipher_suite     Cipher suite specification
+ * @ret rc             Return status code
+ */
+static int tls_select_cipher ( struct tls_session *tls,
+                              unsigned int cipher_suite ) {
+       struct crypto_algorithm *pubkey = &crypto_null;
+       struct crypto_algorithm *cipher = &crypto_null;
+       struct crypto_algorithm *digest = &crypto_null;
+       size_t key_len = 0;
+       int rc;
+
+       switch ( cipher_suite ) {
+       case htons ( TLS_RSA_WITH_AES_128_CBC_SHA ):
+               key_len = ( 128 / 8 );
+               cipher = &aes_algorithm;
+               digest = &sha1_algorithm;
+               break;
+       case htons ( TLS_RSA_WITH_AES_256_CBC_SHA ):
+               key_len = ( 256 / 8 );
+               cipher = &aes_algorithm;
+               digest = &sha1_algorithm;
+               break;
+       default:
+               DBGC ( tls, "TLS %p does not support cipher %04x\n",
+                      tls, ntohs ( cipher_suite ) );
+               return -ENOTSUP;
+       }
+
+       /* Set ciphers */
+       if ( ( rc = tls_set_cipher ( tls, &tls->tx_cipherspec_pending, pubkey,
+                                    cipher, digest, key_len ) ) != 0 )
+               return rc;
+       if ( ( rc = tls_set_cipher ( tls, &tls->rx_cipherspec_pending, pubkey,
+                                    cipher, digest, key_len ) ) != 0 )
+               return rc;
+
+       DBGC ( tls, "TLS %p selected %s-%s-%d-%s\n", tls,
+              pubkey->name, cipher->name, ( key_len * 8 ), digest->name );
+
+       return 0;
+}
+
+/**
+ * Activate next cipher suite
+ *
+ * @v tls              TLS session
+ * @v pending          Pending cipher specification
+ * @v active           Active cipher specification to replace
+ * @ret rc             Return status code
+ */
+static int tls_change_cipher ( struct tls_session *tls,
+                              struct tls_cipherspec *pending,
+                              struct tls_cipherspec *active ) {
+
+#warning "Why is this disabled?"
+#if 0
+       /* Sanity check */
+       if ( ( pending->pubkey == &crypto_null ) ||
+            ( pending->cipher == &crypto_null ) ||
+            ( pending->digest == &crypto_null ) ) {
+               DBGC ( tls, "TLS %p refusing to use null cipher\n", tls );
+               return -ENOTSUP;
+       }
+#endif
+
+       tls_clear_cipher ( tls, active );
+       memswap ( active, pending, sizeof ( *active ) );
+       return 0;
+}
+
+/******************************************************************************
+ *
+ * Handshake verification
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Add handshake record to verification hash
+ *
+ * @v tls              TLS session
+ * @v data             Handshake record
+ * @v len              Length of handshake record
+ */
+static void tls_add_handshake ( struct tls_session *tls,
+                               const void *data, size_t len ) {
+
+       digest_update ( &md5_algorithm, tls->handshake_md5_ctx, data, len );
+       digest_update ( &sha1_algorithm, tls->handshake_sha1_ctx, data, len );
+}
+
+/**
+ * Calculate handshake verification hash
+ *
+ * @v tls              TLS session
+ * @v out              Output buffer
+ *
+ * Calculates the MD5+SHA1 digest over all handshake messages seen so
+ * far.
+ */
+static void tls_verify_handshake ( struct tls_session *tls, void *out ) {
+       struct crypto_algorithm *md5 = &md5_algorithm;
+       struct crypto_algorithm *sha1 = &sha1_algorithm;
+       uint8_t md5_ctx[md5->ctxsize];
+       uint8_t sha1_ctx[sha1->ctxsize];
+       void *md5_digest = out;
+       void *sha1_digest = ( out + md5->digestsize );
+
+       memcpy ( md5_ctx, tls->handshake_md5_ctx, sizeof ( md5_ctx ) );
+       memcpy ( sha1_ctx, tls->handshake_sha1_ctx, sizeof ( sha1_ctx ) );
+       digest_final ( md5, md5_ctx, md5_digest );
+       digest_final ( sha1, sha1_ctx, sha1_digest );
+}
+
+/******************************************************************************
+ *
+ * Record handling
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Transmit Handshake record
+ *
+ * @v tls              TLS session
+ * @v data             Plaintext record
+ * @v len              Length of plaintext record
+ * @ret rc             Return status code
+ */
+static int tls_send_handshake ( struct tls_session *tls,
+                               void *data, size_t len ) {
+
+       /* Add to handshake digest */
+       tls_add_handshake ( tls, data, len );
+
+       /* Send record */
+       return tls_send_plaintext ( tls, TLS_TYPE_HANDSHAKE, data, len );
+}
+
+/**
+ * Transmit Client Hello record
+ *
+ * @v tls              TLS session
+ * @ret rc             Return status code
+ */
+static int tls_send_client_hello ( struct tls_session *tls ) {
+       struct {
+               uint32_t type_length;
+               uint16_t version;
+               uint8_t random[32];
+               uint8_t session_id_len;
+               uint16_t cipher_suite_len;
+               uint16_t cipher_suites[2];
+               uint8_t compression_methods_len;
+               uint8_t compression_methods[1];
+       } __attribute__ (( packed )) hello;
+
+       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_TLS_1_0 );
+       memcpy ( &hello.random, tls->client_random, sizeof ( hello.random ) );
+       hello.cipher_suite_len = htons ( sizeof ( hello.cipher_suites ) );
+       hello.cipher_suites[0] = htons ( TLS_RSA_WITH_AES_128_CBC_SHA );
+       hello.cipher_suites[1] = htons ( TLS_RSA_WITH_AES_256_CBC_SHA );
+       hello.compression_methods_len = sizeof ( hello.compression_methods );
+
+       return tls_send_handshake ( tls, &hello, sizeof ( hello ) );
+}
+
+/**
+ * Transmit Client Key Exchange record
+ *
+ * @v tls              TLS session
+ * @ret rc             Return status code
+ */
+static int tls_send_client_key_exchange ( struct tls_session *tls ) {
+#warning "Hack alert"
+       RSA_CTX *rsa_ctx;
+       RSA_pub_key_new ( &rsa_ctx, tls->rsa_mod, tls->rsa_mod_len,
+                         tls->rsa_pub_exp, tls->rsa_pub_exp_len );
+       struct {
+               uint32_t type_length;
+               uint16_t encrypted_pre_master_secret_len;
+               uint8_t encrypted_pre_master_secret[rsa_ctx->num_octets];
+       } __attribute__ (( packed )) key_xchg;
+
+       memset ( &key_xchg, 0, sizeof ( key_xchg ) );
+       key_xchg.type_length = ( cpu_to_le32 ( TLS_CLIENT_KEY_EXCHANGE ) |
+                                htonl ( sizeof ( key_xchg ) -
+                                        sizeof ( key_xchg.type_length ) ) );
+       key_xchg.encrypted_pre_master_secret_len
+               = htons ( sizeof ( key_xchg.encrypted_pre_master_secret ) );
+
+#warning "Hack alert"
+       DBGC ( tls, "RSA encrypting plaintext, modulus, exponent:\n" );
+       DBGC_HD ( tls, &tls->pre_master_secret,
+                 sizeof ( tls->pre_master_secret ) );
+       DBGC_HD ( tls, tls->rsa_mod, tls->rsa_mod_len );
+       DBGC_HD ( tls, tls->rsa_pub_exp, tls->rsa_pub_exp_len );
+       RSA_encrypt ( rsa_ctx, tls->pre_master_secret,
+                     sizeof ( tls->pre_master_secret ),
+                     key_xchg.encrypted_pre_master_secret, 0 );
+       DBGC ( tls, "RSA encrypt done.  Ciphertext:\n" );
+       DBGC_HD ( tls, &key_xchg.encrypted_pre_master_secret,
+                 sizeof ( key_xchg.encrypted_pre_master_secret ) );
+       RSA_free ( rsa_ctx );
+
+
+       return tls_send_handshake ( tls, &key_xchg, sizeof ( key_xchg ) );
+}
+
+/**
+ * Transmit Change Cipher record
+ *
+ * @v tls              TLS session
+ * @ret rc             Return status code
+ */
+static int tls_send_change_cipher ( struct tls_session *tls ) {
+       static const uint8_t change_cipher[1] = { 1 };
+       return tls_send_plaintext ( tls, TLS_TYPE_CHANGE_CIPHER,
+                                   change_cipher, sizeof ( change_cipher ) );
+}
+
+/**
+ * Transmit Finished record
+ *
+ * @v tls              TLS session
+ * @ret rc             Return status code
+ */
+static int tls_send_finished ( struct tls_session *tls ) {
+       struct {
+               uint32_t type_length;
+               uint8_t verify_data[12];
+       } __attribute__ (( packed )) finished;
+       uint8_t digest[MD5_DIGEST_SIZE + SHA1_DIGEST_SIZE];
+
+       memset ( &finished, 0, sizeof ( finished ) );
+       finished.type_length = ( cpu_to_le32 ( TLS_FINISHED ) |
+                                htonl ( sizeof ( finished ) -
+                                        sizeof ( finished.type_length ) ) );
+       tls_verify_handshake ( tls, digest );
+       tls_prf_label ( tls, tls->master_secret, sizeof ( tls->master_secret ),
+                       finished.verify_data, sizeof ( finished.verify_data ),
+                       "client finished", digest, sizeof ( digest ) );
+
+       return tls_send_handshake ( tls, &finished, sizeof ( finished ) );
+}
+
+/**
+ * Receive new Change Cipher record
+ *
+ * @v tls              TLS session
+ * @v data             Plaintext record
+ * @v len              Length of plaintext record
+ * @ret rc             Return status code
+ */
+static int tls_new_change_cipher ( struct tls_session *tls,
+                                  void *data, size_t len ) {
+       int rc;
+
+       if ( ( len != 1 ) || ( *( ( uint8_t * ) data ) != 1 ) ) {
+               DBGC ( tls, "TLS %p received invalid Change Cipher\n", tls );
+               DBGC_HD ( tls, data, len );
+               return -EINVAL;
+       }
+
+       if ( ( rc = tls_change_cipher ( tls, &tls->rx_cipherspec_pending,
+                                       &tls->rx_cipherspec ) ) != 0 ) {
+               DBGC ( tls, "TLS %p could not activate RX cipher: %s\n",
+                      tls, strerror ( rc ) );
+               return rc;
+       }
+       tls->rx_seq = ~( ( uint64_t ) 0 );
+
+       return 0;
+}
+
+/**
+ * Receive new Alert record
+ *
+ * @v tls              TLS session
+ * @v data             Plaintext record
+ * @v len              Length of plaintext record
+ * @ret rc             Return status code
+ */
+static int tls_new_alert ( struct tls_session *tls, void *data, size_t len ) {
+       struct {
+               uint8_t level;
+               uint8_t description;
+               char next[0];
+       } __attribute__ (( packed )) *alert = data;
+       void *end = alert->next;
+
+       /* Sanity check */
+       if ( end != ( data + len ) ) {
+               DBGC ( tls, "TLS %p received overlength Alert\n", tls );
+               DBGC_HD ( tls, data, len );
+               return -EINVAL;
+       }
+
+       switch ( alert->level ) {
+       case TLS_ALERT_WARNING:
+               DBGC ( tls, "TLS %p received warning alert %d\n",
+                      tls, alert->description );
+               return 0;
+       case TLS_ALERT_FATAL:
+               DBGC ( tls, "TLS %p received fatal alert %d\n",
+                      tls, alert->description );
+               return -EPERM;
+       default:
+               DBGC ( tls, "TLS %p received unknown alert level %d"
+                      "(alert %d)\n", tls, alert->level, alert->description );
+               return -EIO;
+       }
+}
+
+/**
+ * Receive new Server Hello record
+ *
+ * @v tls              TLS session
+ * @v data             Plaintext record
+ * @v len              Length of plaintext record
+ * @ret rc             Return status code
+ */
+static int tls_new_server_hello ( struct tls_session *tls,
+                                 void *data, size_t len ) {
+       struct {
+               uint32_t type_length;
+               uint16_t version;
+               uint8_t random[32];
+               uint8_t session_id_len;
+               char next[0];
+       } __attribute__ (( packed )) *hello_a = data;
+       struct {
+               uint8_t session_id[hello_a->session_id_len];
+               uint16_t cipher_suite;
+               uint8_t compression_method;
+               char next[0];
+       } __attribute__ (( packed )) *hello_b = ( void * ) &hello_a->next;
+       void *end = hello_b->next;
+       int rc;
+
+       /* Sanity check */
+       if ( end != ( data + len ) ) {
+               DBGC ( tls, "TLS %p received overlength Server Hello\n", tls );
+               DBGC_HD ( tls, data, len );
+               return -EINVAL;
+       }
+
+       /* Check protocol version */
+       if ( ntohs ( hello_a->version ) < TLS_VERSION_TLS_1_0 ) {
+               DBGC ( tls, "TLS %p does not support protocol version %d.%d\n",
+                      tls, ( ntohs ( hello_a->version ) >> 8 ),
+                      ( ntohs ( hello_a->version ) & 0xff ) );
+               return -ENOTSUP;
+       }
+
+       /* Copy out server random bytes */
+       memcpy ( tls->server_random, hello_a->random,
+                sizeof ( tls->server_random ) );
+
+       /* Select cipher suite */
+       if ( ( rc = tls_select_cipher ( tls, hello_b->cipher_suite ) ) != 0 )
+               return rc;
+
+       /* Generate secrets */
+       tls_generate_master_secret ( tls );
+       if ( ( rc = tls_generate_keys ( tls ) ) != 0 )
+               return rc;
+
+       return 0;
+}
+
+/**
+ * Receive new Certificate record
+ *
+ * @v tls              TLS session
+ * @v data             Plaintext record
+ * @v len              Length of plaintext record
+ * @ret rc             Return status code
+ */
+static int tls_new_certificate ( struct tls_session *tls,
+                                void *data, size_t len ) {
+       struct {
+               uint32_t type_length;
+               uint8_t length[3];
+               uint8_t first_cert_length[3];
+               uint8_t asn1_start[0];
+       } __attribute__ (( packed )) *certificate = data;
+       uint8_t *cert = certificate->asn1_start;
+       int offset = 0;
+
+       if (asn1_next_obj(cert, &offset, ASN1_SEQUENCE) < 0 ||
+           asn1_next_obj(cert, &offset, ASN1_SEQUENCE) < 0 ||
+            asn1_skip_obj(cert, &offset, ASN1_EXPLICIT_TAG) ||
+            asn1_skip_obj(cert, &offset, ASN1_INTEGER) ||
+            asn1_skip_obj(cert, &offset, ASN1_SEQUENCE) ||
+            asn1_skip_obj(cert, &offset, ASN1_SEQUENCE) ||
+            asn1_skip_obj(cert, &offset, ASN1_SEQUENCE) ||
+            asn1_skip_obj(cert, &offset, ASN1_SEQUENCE) ||
+           asn1_next_obj(cert, &offset, ASN1_SEQUENCE) < 0 ||
+            asn1_skip_obj(cert, &offset, ASN1_SEQUENCE) ||
+            asn1_next_obj(cert, &offset, ASN1_BIT_STRING) < 0) {
+               DBGC ( tls, "TLS %p invalid certificate\n", tls );
+               DBGC_HD ( tls, cert + offset, 64 );
+               return -EPERM;
+       }
+       
+       offset++;
+       
+       if (asn1_next_obj(cert, &offset, ASN1_SEQUENCE) < 0) {
+               DBGC ( tls, "TLS %p invalid certificate\n", tls );
+               DBGC_HD ( tls, cert + offset, 64 );
+               return -EPERM;
+       }
+       
+       tls->rsa_mod_len = asn1_get_int(cert, &offset, &tls->rsa_mod);
+       tls->rsa_pub_exp_len = asn1_get_int(cert, &offset, &tls->rsa_pub_exp);
+       
+       DBGC_HD ( tls, tls->rsa_mod, tls->rsa_mod_len );
+       DBGC_HD ( tls, tls->rsa_pub_exp, tls->rsa_pub_exp_len );
+
+       return 0;
+}
+
+/**
+ * Receive new Server Hello Done record
+ *
+ * @v tls              TLS session
+ * @v data             Plaintext record
+ * @v len              Length of plaintext record
+ * @ret rc             Return status code
+ */
+static int tls_new_server_hello_done ( struct tls_session *tls,
+                                      void *data, size_t len ) {
+       struct {
+               uint32_t type_length;
+               char next[0];
+       } __attribute__ (( packed )) *hello_done = data;
+       void *end = hello_done->next;
+
+       /* Sanity check */
+       if ( end != ( data + len ) ) {
+               DBGC ( tls, "TLS %p received overlength Server Hello Done\n",
+                      tls );
+               DBGC_HD ( tls, data, len );
+               return -EINVAL;
+       }
+
+       /* Check that we are ready to send the Client Key Exchange */
+       if ( tls->tx_state != TLS_TX_NONE ) {
+               DBGC ( tls, "TLS %p received Server Hello Done while in "
+                      "TX state %d\n", tls, tls->tx_state );
+               return -EIO;
+       }
+
+       /* Start sending the Client Key Exchange */
+       tls->tx_state = TLS_TX_CLIENT_KEY_EXCHANGE;
+
+       return 0;
+}
+
+/**
+ * Receive new Finished record
+ *
+ * @v tls              TLS session
+ * @v data             Plaintext record
+ * @v len              Length of plaintext record
+ * @ret rc             Return status code
+ */
+static int tls_new_finished ( struct tls_session *tls,
+                             void *data, size_t len ) {
+
+#warning "Handle this properly"
+       tls->tx_state = TLS_TX_DATA;
+       ( void ) data;
+       ( void ) len;
+       return 0;
+}
+
+/**
+ * Receive new Handshake record
+ *
+ * @v tls              TLS session
+ * @v data             Plaintext record
+ * @v len              Length of plaintext record
+ * @ret rc             Return status code
+ */
+static int tls_new_handshake ( struct tls_session *tls,
+                              void *data, size_t len ) {
+       uint8_t *type = data;
+       int rc;
+
+       switch ( *type ) {
+       case TLS_SERVER_HELLO:
+               rc = tls_new_server_hello ( tls, data, len );
+               break;
+       case TLS_CERTIFICATE:
+               rc = tls_new_certificate ( tls, data, len );
+               break;
+       case TLS_SERVER_HELLO_DONE:
+               rc = tls_new_server_hello_done ( tls, data, len );
+               break;
+       case TLS_FINISHED:
+               rc = tls_new_finished ( tls, data, len );
+               break;
+       default:
+               DBGC ( tls, "TLS %p ignoring handshake type %d\n",
+                      tls, *type );
+               rc = 0;
+               break;
+       }
+
+       /* Add to handshake digest (except for Hello Requests, which
+        * are explicitly excludede).
+        */
+       if ( *type != TLS_HELLO_REQUEST )
+               tls_add_handshake ( tls, data, len );
+
+       return rc;
+}
+
+/**
+ * Receive new record
+ *
+ * @v tls              TLS session
+ * @v type             Record type
+ * @v data             Plaintext record
+ * @v len              Length of plaintext record
+ * @ret rc             Return status code
+ */
+static int tls_new_record ( struct tls_session *tls,
+                           unsigned int type, void *data, size_t len ) {
+
+       switch ( type ) {
+       case TLS_TYPE_CHANGE_CIPHER:
+               return tls_new_change_cipher ( tls, data, len );
+       case TLS_TYPE_ALERT:
+               return tls_new_alert ( tls, data, len );
+       case TLS_TYPE_HANDSHAKE:
+               return tls_new_handshake ( tls, data, len );
+       case TLS_TYPE_DATA:
+               return xfer_deliver_raw ( &tls->plainstream.xfer, data, len );
+       default:
+               /* RFC4346 says that we should just ignore unknown
+                * record types.
+                */
+               DBGC ( tls, "TLS %p ignoring record type %d\n", tls, type );
+               return 0;
+       }
+}
+
+/******************************************************************************
+ *
+ * Record encryption/decryption
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Calculate HMAC
+ *
+ * @v tls              TLS session
+ * @v cipherspec       Cipher specification
+ * @v seq              Sequence number
+ * @v tlshdr           TLS header
+ * @v data             Data
+ * @v len              Length of data
+ * @v mac              HMAC to fill in
+ */
+static void tls_hmac ( struct tls_session *tls __unused,
+                      struct tls_cipherspec *cipherspec,
+                      uint64_t seq, struct tls_header *tlshdr,
+                      const void *data, size_t len, void *hmac ) {
+       struct crypto_algorithm *digest = cipherspec->digest;
+       uint8_t digest_ctx[digest->ctxsize];
+
+       hmac_init ( digest, digest_ctx, cipherspec->mac_secret,
+                   &digest->digestsize );
+       seq = cpu_to_be64 ( seq );
+       hmac_update ( digest, digest_ctx, &seq, sizeof ( seq ) );
+       hmac_update ( digest, digest_ctx, tlshdr, sizeof ( *tlshdr ) );
+       hmac_update ( digest, digest_ctx, data, len );
+       hmac_final ( digest, digest_ctx, cipherspec->mac_secret,
+                    &digest->digestsize, hmac );
+}
+
+/**
+ * Allocate and assemble stream-ciphered record from data and MAC portions
+ *
+ * @v tls              TLS session
+ * @ret data           Data
+ * @ret len            Length of data
+ * @ret digest         MAC digest
+ * @ret plaintext_len  Length of plaintext record
+ * @ret plaintext      Allocated plaintext record
+ */
+static void * tls_assemble_stream ( struct tls_session *tls,
+                                   const void *data, size_t len,
+                                   void *digest, size_t *plaintext_len ) {
+       size_t mac_len = tls->tx_cipherspec.digest->digestsize;
+       void *plaintext;
+       void *content;
+       void *mac;
+
+       /* Calculate stream-ciphered struct length */
+       *plaintext_len = ( len + mac_len );
+
+       /* Allocate stream-ciphered struct */
+       plaintext = malloc ( *plaintext_len );
+       if ( ! plaintext )
+               return NULL;
+       content = plaintext;
+       mac = ( content + len );
+
+       /* Fill in stream-ciphered struct */
+       memcpy ( content, data, len );
+       memcpy ( mac, digest, mac_len );
+
+       return plaintext;
+}
+
+/**
+ * Allocate and assemble block-ciphered record from data and MAC portions
+ *
+ * @v tls              TLS session
+ * @ret data           Data
+ * @ret len            Length of data
+ * @ret digest         MAC digest
+ * @ret plaintext_len  Length of plaintext record
+ * @ret plaintext      Allocated plaintext record
+ */
+static void * tls_assemble_block ( struct tls_session *tls,
+                                  const void *data, size_t len,
+                                  void *digest, size_t *plaintext_len ) {
+       size_t blocksize = tls->tx_cipherspec.cipher->blocksize;
+       size_t iv_len = blocksize;
+       size_t mac_len = tls->tx_cipherspec.digest->digestsize;
+       size_t padding_len;
+       void *plaintext;
+       void *iv;
+       void *content;
+       void *mac;
+       void *padding;
+
+#warning "TLSv1.1 has an explicit IV"
+       iv_len = 0;
+
+       /* Calculate block-ciphered struct length */
+       padding_len = ( ( blocksize - 1 ) & -( iv_len + len + mac_len + 1 ) );
+       *plaintext_len = ( iv_len + len + mac_len + padding_len + 1 );
+
+       /* Allocate block-ciphered struct */
+       plaintext = malloc ( *plaintext_len );
+       if ( ! plaintext )
+               return NULL;
+       iv = plaintext;
+       content = ( iv + iv_len );
+       mac = ( content + len );
+       padding = ( mac + mac_len );
+
+       /* Fill in block-ciphered struct */
+       memset ( iv, 0, iv_len );
+       memcpy ( content, data, len );
+       memcpy ( mac, digest, mac_len );
+       memset ( padding, padding_len, ( padding_len + 1 ) );
+
+       return plaintext;
+}
+
+/**
+ * Send plaintext record
+ *
+ * @v tls              TLS session
+ * @v type             Record type
+ * @v data             Plaintext record
+ * @v len              Length of plaintext record
+ * @ret rc             Return status code
+ */
+static int tls_send_plaintext ( struct tls_session *tls, unsigned int type,
+                               const void *data, size_t len ) {
+       struct tls_header plaintext_tlshdr;
+       struct tls_header *tlshdr;
+       struct tls_cipherspec *cipherspec = &tls->tx_cipherspec;
+       void *plaintext = NULL;
+       size_t plaintext_len;
+       struct io_buffer *ciphertext = NULL;
+       size_t ciphertext_len;
+       size_t mac_len = cipherspec->digest->digestsize;
+       uint8_t mac[mac_len];
+       int rc;
+
+       /* Construct header */
+       plaintext_tlshdr.type = type;
+       plaintext_tlshdr.version = htons ( TLS_VERSION_TLS_1_0 );
+       plaintext_tlshdr.length = htons ( len );
+
+       /* Calculate MAC */
+       tls_hmac ( tls, cipherspec, tls->tx_seq, &plaintext_tlshdr,
+                  data, len, mac );
+
+       /* Allocate and assemble plaintext struct */
+       if ( is_stream_cipher ( cipherspec->cipher ) ) {
+               plaintext = tls_assemble_stream ( tls, data, len, mac,
+                                                 &plaintext_len );
+       } else {
+               plaintext = tls_assemble_block ( tls, data, len, mac,
+                                                &plaintext_len );
+       }
+       if ( ! plaintext ) {
+               DBGC ( tls, "TLS %p could not allocate %zd bytes for "
+                      "plaintext\n", tls, plaintext_len );
+               rc = -ENOMEM;
+               goto done;
+       }
+
+       DBGC2 ( tls, "Sending plaintext data:\n" );
+       DBGC2_HD ( tls, plaintext, plaintext_len );
+
+       /* Allocate ciphertext */
+       ciphertext_len = ( sizeof ( *tlshdr ) + plaintext_len );
+       ciphertext = xfer_alloc_iob ( &tls->cipherstream.xfer,
+                                     ciphertext_len );
+       if ( ! ciphertext ) {
+               DBGC ( tls, "TLS %p could not allocate %zd bytes for "
+                      "ciphertext\n", tls, ciphertext_len );
+               rc = -ENOMEM;
+               goto done;
+       }
+
+       /* Assemble ciphertext */
+       tlshdr = iob_put ( ciphertext, sizeof ( *tlshdr ) );
+       tlshdr->type = type;
+       tlshdr->version = htons ( TLS_VERSION_TLS_1_0 );
+       tlshdr->length = htons ( plaintext_len );
+       memcpy ( cipherspec->cipher_next_ctx, cipherspec->cipher_ctx,
+                cipherspec->cipher->ctxsize );
+       if ( ( rc = cipher_encrypt ( cipherspec->cipher,
+                                    cipherspec->cipher_next_ctx, plaintext,
+                                    iob_put ( ciphertext, plaintext_len ),
+                                    plaintext_len ) ) != 0 ) {
+               DBGC ( tls, "TLS %p could not encrypt: %s\n",
+                      tls, strerror ( rc ) );
+               DBGC_HD ( tls, plaintext, plaintext_len );
+               goto done;
+       }
+
+       /* Free plaintext as soon as possible to conserve memory */
+       free ( plaintext );
+       plaintext = NULL;
+
+       /* Send ciphertext */
+       rc = xfer_deliver_iob ( &tls->cipherstream.xfer, ciphertext );
+       ciphertext = NULL;
+       if ( rc != 0 ) {
+               DBGC ( tls, "TLS %p could not deliver ciphertext: %s\n",
+                      tls, strerror ( rc ) );
+               goto done;
+       }
+
+       /* Update TX state machine to next record */
+       tls->tx_seq += 1;
+       memcpy ( tls->tx_cipherspec.cipher_ctx,
+                tls->tx_cipherspec.cipher_next_ctx,
+                tls->tx_cipherspec.cipher->ctxsize );
+
+ done:
+       free ( plaintext );
+       free_iob ( ciphertext );
+       return rc;
+}
+
+/**
+ * Split stream-ciphered record into data and MAC portions
+ *
+ * @v tls              TLS session
+ * @v plaintext                Plaintext record
+ * @v plaintext_len    Length of record
+ * @ret data           Data
+ * @ret len            Length of data
+ * @ret digest         MAC digest
+ * @ret rc             Return status code
+ */
+static int tls_split_stream ( struct tls_session *tls,
+                             void *plaintext, size_t plaintext_len,
+                             void **data, size_t *len, void **digest ) {
+       void *content;
+       size_t content_len;
+       void *mac;
+       size_t mac_len;
+
+       /* Decompose stream-ciphered data */
+       mac_len = tls->rx_cipherspec.digest->digestsize;
+       if ( plaintext_len < mac_len ) {
+               DBGC ( tls, "TLS %p received underlength record\n", tls );
+               DBGC_HD ( tls, plaintext, plaintext_len );
+               return -EINVAL;
+       }
+       content_len = ( plaintext_len - mac_len );
+       content = plaintext;
+       mac = ( content + content_len );
+
+       /* Fill in return values */
+       *data = content;
+       *len = content_len;
+       *digest = mac;
+
+       return 0;
+}
+
+/**
+ * Split block-ciphered record into data and MAC portions
+ *
+ * @v tls              TLS session
+ * @v plaintext                Plaintext record
+ * @v plaintext_len    Length of record
+ * @ret data           Data
+ * @ret len            Length of data
+ * @ret digest         MAC digest
+ * @ret rc             Return status code
+ */
+static int tls_split_block ( struct tls_session *tls,
+                            void *plaintext, size_t plaintext_len,
+                            void **data, size_t *len,
+                            void **digest ) {
+       void *iv;
+       size_t iv_len;
+       void *content;
+       size_t content_len;
+       void *mac;
+       size_t mac_len;
+       void *padding;
+       size_t padding_len;
+       unsigned int i;
+
+       /* Decompose block-ciphered data */
+       if ( plaintext_len < 1 ) {
+               DBGC ( tls, "TLS %p received underlength record\n", tls );
+               DBGC_HD ( tls, plaintext, plaintext_len );
+               return -EINVAL;
+       }
+       iv_len = tls->rx_cipherspec.cipher->blocksize;
+
+#warning "TLSv1.1 uses an explicit IV"
+       iv_len = 0;
+
+       mac_len = tls->rx_cipherspec.digest->digestsize;
+       padding_len = *( ( uint8_t * ) ( plaintext + plaintext_len - 1 ) );
+       if ( plaintext_len < ( iv_len + mac_len + padding_len + 1 ) ) {
+               DBGC ( tls, "TLS %p received underlength record\n", tls );
+               DBGC_HD ( tls, plaintext, plaintext_len );
+               return -EINVAL;
+       }
+       content_len = ( plaintext_len - iv_len - mac_len - padding_len - 1 );
+       iv = plaintext;
+       content = ( iv + iv_len );
+       mac = ( content + content_len );
+       padding = ( mac + mac_len );
+
+       /* Verify padding bytes */
+       for ( i = 0 ; i < padding_len ; i++ ) {
+               if ( *( ( uint8_t * ) ( padding + i ) ) != padding_len ) {
+                       DBGC ( tls, "TLS %p received bad padding\n", tls );
+                       DBGC_HD ( tls, plaintext, plaintext_len );
+                       return -EINVAL;
+               }
+       }
+
+       /* Fill in return values */
+       *data = content;
+       *len = content_len;
+       *digest = mac;
+
+       return 0;
+}
+
+/**
+ * Receive new ciphertext record
+ *
+ * @v tls              TLS session
+ * @v tlshdr           Record header
+ * @v ciphertext       Ciphertext record
+ * @ret rc             Return status code
+ */
+static int tls_new_ciphertext ( struct tls_session *tls,
+                               struct tls_header *tlshdr, void *ciphertext ) {
+       struct tls_header plaintext_tlshdr;
+       struct tls_cipherspec *cipherspec = &tls->rx_cipherspec;
+       size_t record_len = ntohs ( tlshdr->length );
+       void *plaintext = NULL;
+       void *data;
+       size_t len;
+       void *mac;
+       size_t mac_len = cipherspec->digest->digestsize;
+       uint8_t verify_mac[mac_len];
+       int rc;
+
+       /* Allocate buffer for plaintext */
+       plaintext = malloc ( record_len );
+       if ( ! plaintext ) {
+               DBGC ( tls, "TLS %p could not allocate %zd bytes for "
+                      "decryption buffer\n", tls, record_len );
+               rc = -ENOMEM;
+               goto done;
+       }
+
+       /* Decrypt the record */
+       if ( ( rc = cipher_decrypt ( cipherspec->cipher,
+                                    cipherspec->cipher_ctx, ciphertext,
+                                    plaintext, record_len ) ) != 0 ) {
+               DBGC ( tls, "TLS %p could not decrypt: %s\n",
+                      tls, strerror ( rc ) );
+               DBGC_HD ( tls, ciphertext, record_len );
+               goto done;
+       }
+
+       /* Split record into content and MAC */
+       if ( is_stream_cipher ( cipherspec->cipher ) ) {
+               if ( ( rc = tls_split_stream ( tls, plaintext, record_len,
+                                              &data, &len, &mac ) ) != 0 )
+                       goto done;
+       } else {
+               if ( ( rc = tls_split_block ( tls, plaintext, record_len,
+                                             &data, &len, &mac ) ) != 0 )
+                       goto done;
+       }
+
+       /* Verify MAC */
+       plaintext_tlshdr.type = tlshdr->type;
+       plaintext_tlshdr.version = tlshdr->version;
+       plaintext_tlshdr.length = htons ( len );
+       tls_hmac ( tls, cipherspec, tls->rx_seq, &plaintext_tlshdr,
+                  data, len, verify_mac);
+       if ( memcmp ( mac, verify_mac, mac_len ) != 0 ) {
+               DBGC ( tls, "TLS %p failed MAC verification\n", tls );
+               DBGC_HD ( tls, plaintext, record_len );
+               goto done;
+       }
+
+       DBGC2 ( tls, "Received plaintext data:\n" );
+       DBGC2_HD ( tls, data, len );
+
+       /* Process plaintext record */
+       if ( ( rc = tls_new_record ( tls, tlshdr->type, data, len ) ) != 0 )
+               goto done;
+
+       rc = 0;
+ done:
+       free ( plaintext );
+       return rc;
+}
+
+/******************************************************************************
+ *
+ * Plaintext stream operations
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Close interface
+ *
+ * @v xfer             Plainstream data transfer interface
+ * @v rc               Reason for close
+ */
+static void tls_plainstream_close ( struct xfer_interface *xfer, int rc ) {
+       struct tls_session *tls =
+               container_of ( xfer, struct tls_session, plainstream.xfer );
+
+       tls_close ( tls, rc );
+}
+
+/**
+ * Check flow control window
+ *
+ * @v xfer             Plainstream data transfer interface
+ * @ret len            Length of window
+ */
+static size_t tls_plainstream_window ( struct xfer_interface *xfer ) {
+       struct tls_session *tls =
+               container_of ( xfer, struct tls_session, plainstream.xfer );
+
+       /* Block window unless we are ready to accept data */
+       if ( tls->tx_state != TLS_TX_DATA )
+               return 0;
+
+       return filter_window ( xfer );
+}
+
+/**
+ * Deliver datagram as raw data
+ *
+ * @v xfer             Plainstream data transfer interface
+ * @v data             Data buffer
+ * @v len              Length of data buffer
+ * @ret rc             Return status code
+ */
+static int tls_plainstream_deliver_raw ( struct xfer_interface *xfer,
+                                        const void *data, size_t len ) {
+       struct tls_session *tls =
+               container_of ( xfer, struct tls_session, plainstream.xfer );
+       
+       /* Refuse unless we are ready to accept data */
+       if ( tls->tx_state != TLS_TX_DATA )
+               return -ENOTCONN;
+
+       return tls_send_plaintext ( tls, TLS_TYPE_DATA, data, len );
+}
+
+/** TLS plaintext stream operations */
+static struct xfer_interface_operations tls_plainstream_operations = {
+       .close          = tls_plainstream_close,
+       .vredirect      = ignore_xfer_vredirect,
+       .seek           = filter_seek,
+       .window         = tls_plainstream_window,
+       .alloc_iob      = default_xfer_alloc_iob,
+       .deliver_iob    = xfer_deliver_as_raw,
+       .deliver_raw    = tls_plainstream_deliver_raw,
+};
+
+/******************************************************************************
+ *
+ * Ciphertext stream operations
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Close interface
+ *
+ * @v xfer             Plainstream data transfer interface
+ * @v rc               Reason for close
+ */
+static void tls_cipherstream_close ( struct xfer_interface *xfer, int rc ) {
+       struct tls_session *tls =
+               container_of ( xfer, struct tls_session, cipherstream.xfer );
+
+       tls_close ( tls, rc );
+}
+
+/**
+ * Handle received TLS header
+ *
+ * @v tls              TLS session
+ * @ret rc             Returned status code
+ */
+static int tls_newdata_process_header ( struct tls_session *tls ) {
+       size_t data_len = ntohs ( tls->rx_header.length );
+
+       /* Allocate data buffer now that we know the length */
+       assert ( tls->rx_data == NULL );
+       tls->rx_data = malloc ( data_len );
+       if ( ! tls->rx_data ) {
+               DBGC ( tls, "TLS %p could not allocate %zd bytes "
+                      "for receive buffer\n", tls, data_len );
+               return -ENOMEM;
+       }
+
+       /* Move to data state */
+       tls->rx_state = TLS_RX_DATA;
+
+       return 0;
+}
+
+/**
+ * Handle received TLS data payload
+ *
+ * @v tls              TLS session
+ * @ret rc             Returned status code
+ */
+static int tls_newdata_process_data ( struct tls_session *tls ) {
+       int rc;
+
+       /* Process record */
+       if ( ( rc = tls_new_ciphertext ( tls, &tls->rx_header,
+                                        tls->rx_data ) ) != 0 )
+               return rc;
+
+       /* Increment RX sequence number */
+       tls->rx_seq += 1;
+
+       /* Free data buffer */
+       free ( tls->rx_data );
+       tls->rx_data = NULL;
+
+       /* Return to header state */
+       tls->rx_state = TLS_RX_HEADER;
+
+       return 0;
+}
+
+/**
+ * Receive new ciphertext
+ *
+ * @v app              Stream application
+ * @v data             Data received
+ * @v len              Length of received data
+ * @ret rc             Return status code
+ */
+static int tls_cipherstream_deliver_raw ( struct xfer_interface *xfer,
+                                         const void *data, size_t len ) {
+       struct tls_session *tls = 
+               container_of ( xfer, struct tls_session, cipherstream.xfer );
+       size_t frag_len;
+       void *buf;
+       size_t buf_len;
+       int ( * process ) ( struct tls_session *tls );
+       int rc;
+
+       while ( len ) {
+               /* Select buffer according to current state */
+               switch ( tls->rx_state ) {
+               case TLS_RX_HEADER:
+                       buf = &tls->rx_header;
+                       buf_len = sizeof ( tls->rx_header );
+                       process = tls_newdata_process_header;
+                       break;
+               case TLS_RX_DATA:
+                       buf = tls->rx_data;
+                       buf_len = ntohs ( tls->rx_header.length );
+                       process = tls_newdata_process_data;
+                       break;
+               default:
+                       assert ( 0 );
+                       return -EINVAL;
+               }
+
+               /* Copy data portion to buffer */
+               frag_len = ( buf_len - tls->rx_rcvd );
+               if ( frag_len > len )
+                       frag_len = len;
+               memcpy ( ( buf + tls->rx_rcvd ), data, frag_len );
+               tls->rx_rcvd += frag_len;
+               data += frag_len;
+               len -= frag_len;
+
+               /* Process data if buffer is now full */
+               if ( tls->rx_rcvd == buf_len ) {
+                       if ( ( rc = process ( tls ) ) != 0 ) {
+                               tls_close ( tls, rc );
+                               return rc;
+                       }
+                       tls->rx_rcvd = 0;
+               }
+       }
+
+       return 0;
+}
+
+/** TLS ciphertext stream operations */
+static struct xfer_interface_operations tls_cipherstream_operations = {
+       .close          = tls_cipherstream_close,
+       .vredirect      = xfer_vopen,
+       .seek           = filter_seek,
+       .window         = filter_window,
+       .alloc_iob      = default_xfer_alloc_iob,
+       .deliver_iob    = xfer_deliver_as_raw,
+       .deliver_raw    = tls_cipherstream_deliver_raw,
+};
+
+/******************************************************************************
+ *
+ * Controlling process
+ *
+ ******************************************************************************
+ */
+
+/**
+ * TLS TX state machine
+ *
+ * @v process          TLS process
+ */
+static void tls_step ( struct process *process ) {
+       struct tls_session *tls =
+               container_of ( process, struct tls_session, process );
+       int rc;
+
+       /* Wait for cipherstream to become ready */
+       if ( ! xfer_window ( &tls->cipherstream.xfer ) )
+               return;
+
+       switch ( tls->tx_state ) {
+       case TLS_TX_NONE:
+               /* Nothing to do */
+               break;
+       case TLS_TX_CLIENT_HELLO:
+               /* Send Client Hello */
+               if ( ( rc = tls_send_client_hello ( tls ) ) != 0 ) {
+                       DBGC ( tls, "TLS %p could not send Client Hello: %s\n",
+                              tls, strerror ( rc ) );
+                       goto err;
+               }
+               tls->tx_state = TLS_TX_NONE;
+               break;
+       case TLS_TX_CLIENT_KEY_EXCHANGE:
+               /* Send Client Key Exchange */
+               if ( ( rc = tls_send_client_key_exchange ( tls ) ) != 0 ) {
+                       DBGC ( tls, "TLS %p could send Client Key Exchange: "
+                              "%s\n", tls, strerror ( rc ) );
+                       goto err;
+               }
+               tls->tx_state = TLS_TX_CHANGE_CIPHER;
+               break;
+       case TLS_TX_CHANGE_CIPHER:
+               /* Send Change Cipher, and then change the cipher in use */
+               if ( ( rc = tls_send_change_cipher ( tls ) ) != 0 ) {
+                       DBGC ( tls, "TLS %p could not send Change Cipher: "
+                              "%s\n", tls, strerror ( rc ) );
+                       goto err;
+               }
+               if ( ( rc = tls_change_cipher ( tls,
+                                               &tls->tx_cipherspec_pending,
+                                               &tls->tx_cipherspec )) != 0 ){
+                       DBGC ( tls, "TLS %p could not activate TX cipher: "
+                              "%s\n", tls, strerror ( rc ) );
+                       goto err;
+               }
+               tls->tx_seq = 0;
+               tls->tx_state = TLS_TX_FINISHED;
+               break;
+       case TLS_TX_FINISHED:
+               /* Send Finished */
+               if ( ( rc = tls_send_finished ( tls ) ) != 0 ) {
+                       DBGC ( tls, "TLS %p could not send Finished: %s\n",
+                              tls, strerror ( rc ) );
+                       goto err;
+               }
+               tls->tx_state = TLS_TX_NONE;
+               break;
+       case TLS_TX_DATA:
+               /* Nothing to do */
+               break;
+       default:
+               assert ( 0 );
+       }
+
+       return;
+
+ err:
+       tls_close ( tls, rc );
+}
+
+/******************************************************************************
+ *
+ * Instantiator
+ *
+ ******************************************************************************
+ */
+
+int add_tls ( struct xfer_interface *xfer, struct xfer_interface **next ) {
+       struct tls_session *tls;
+
+       /* Allocate and initialise TLS structure */
+       tls = malloc ( sizeof ( *tls ) );
+       if ( ! tls )
+               return -ENOMEM;
+       memset ( tls, 0, sizeof ( *tls ) );
+       tls->refcnt.free = free_tls;
+       filter_init ( &tls->plainstream, &tls_plainstream_operations,
+                     &tls->cipherstream, &tls_cipherstream_operations,
+                     &tls->refcnt );
+       tls_clear_cipher ( tls, &tls->tx_cipherspec );
+       tls_clear_cipher ( tls, &tls->tx_cipherspec_pending );
+       tls_clear_cipher ( tls, &tls->rx_cipherspec );
+       tls_clear_cipher ( tls, &tls->rx_cipherspec_pending );
+       *( ( uint32_t * ) tls->client_random ) = 0; /* GMT Unix time */
+       tls_generate_random ( ( tls->client_random + 4 ),
+                             ( sizeof ( tls->client_random ) - 4 ) );
+       *( ( uint16_t * ) tls->pre_master_secret )
+               = htons ( TLS_VERSION_TLS_1_0 );
+       tls_generate_random ( ( tls->pre_master_secret + 2 ),
+                             ( sizeof ( tls->pre_master_secret ) - 2 ) );
+       digest_init ( &md5_algorithm, tls->handshake_md5_ctx );
+       digest_init ( &sha1_algorithm, tls->handshake_sha1_ctx );
+       tls->tx_state = TLS_TX_CLIENT_HELLO;
+       process_init ( &tls->process, tls_step, &tls->refcnt );
+
+       /* Attach to parent interface, mortalise self, and return */
+       xfer_plug_plug ( &tls->plainstream.xfer, xfer );
+       *next = &tls->cipherstream.xfer;
+       ref_put ( &tls->refcnt );
+       return 0;
+}
+