summaryrefslogtreecommitdiffstats
path: root/debian/grub-extras/disabled/gpxe/src/net/tls.c
diff options
context:
space:
mode:
Diffstat (limited to 'debian/grub-extras/disabled/gpxe/src/net/tls.c')
-rw-r--r--debian/grub-extras/disabled/gpxe/src/net/tls.c1759
1 files changed, 1759 insertions, 0 deletions
diff --git a/debian/grub-extras/disabled/gpxe/src/net/tls.c b/debian/grub-extras/disabled/gpxe/src/net/tls.c
new file mode 100644
index 0000000..a5b126e
--- /dev/null
+++ b/debian/grub-extras/disabled/gpxe/src/net/tls.c
@@ -0,0 +1,1759 @@
+/*
+ * 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_LICENCE ( GPL2_OR_LATER );
+
+/**
+ * @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/asn1.h>
+#include <gpxe/x509.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 );
+
+/******************************************************************************
+ *
+ * Utility functions
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Extract 24-bit field value
+ *
+ * @v field24 24-bit field
+ * @ret value Field value
+ *
+ * TLS uses 24-bit integers in several places, which are awkward to
+ * parse in C.
+ */
+static unsigned long tls_uint24 ( uint8_t field24[3] ) {
+ return ( ( field24[0] << 16 ) + ( field24[1] << 8 ) + field24[2] );
+}
+
+/******************************************************************************
+ *
+ * Cleanup functions
+ *
+ ******************************************************************************
+ */
+
+/**
+ * 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 );
+ x509_free_rsa_public_key ( &tls->rsa );
+ 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 ) {
+ /* FIXME: Some real random data source would be nice... */
+ 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 digest_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 digest_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->client_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;
+ }
+ 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 = &pubkey_null;
+ cipherspec->cipher = &cipher_null;
+ cipherspec->digest = &digest_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 pubkey_algorithm *pubkey,
+ struct cipher_algorithm *cipher,
+ struct digest_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 pubkey_algorithm *pubkey = &pubkey_null;
+ struct cipher_algorithm *cipher = &cipher_null;
+ struct digest_algorithm *digest = &digest_null;
+ unsigned int key_len = 0;
+ int rc;
+
+ switch ( cipher_suite ) {
+ case htons ( TLS_RSA_WITH_AES_128_CBC_SHA ):
+ key_len = ( 128 / 8 );
+ cipher = &aes_cbc_algorithm;
+ digest = &sha1_algorithm;
+ break;
+ case htons ( TLS_RSA_WITH_AES_256_CBC_SHA ):
+ key_len = ( 256 / 8 );
+ cipher = &aes_cbc_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 ) {
+
+ /* Sanity check */
+ if ( /* FIXME (when pubkey is not hard-coded to RSA):
+ * ( pending->pubkey == &pubkey_null ) || */
+ ( pending->cipher == &cipher_null ) ||
+ ( pending->digest == &digest_null ) ) {
+ DBGC ( tls, "TLS %p refusing to use null cipher\n", tls );
+ return -ENOTSUP;
+ }
+
+ 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 digest_algorithm *md5 = &md5_algorithm;
+ struct digest_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 ) {
+ /* FIXME: Hack alert */
+ RSA_CTX *rsa_ctx;
+ RSA_pub_key_new ( &rsa_ctx, tls->rsa.modulus, tls->rsa.modulus_len,
+ tls->rsa.exponent, tls->rsa.exponent_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 ) );
+
+ /* FIXME: 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.modulus, tls->rsa.modulus_len );
+ DBGC_HD ( tls, tls->rsa.exponent, tls->rsa.exponent_len );
+ RSA_encrypt ( rsa_ctx, ( const uint8_t * ) &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 handshake record
+ *
+ * @v tls TLS session
+ * @v data Plaintext handshake record
+ * @v len Length of plaintext handshake record
+ * @ret rc Return status code
+ */
+static int tls_new_server_hello ( struct tls_session *tls,
+ void *data, size_t len ) {
+ struct {
+ 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 handshake record
+ *
+ * @v tls TLS session
+ * @v data Plaintext handshake record
+ * @v len Length of plaintext handshake record
+ * @ret rc Return status code
+ */
+static int tls_new_certificate ( struct tls_session *tls,
+ void *data, size_t len ) {
+ struct {
+ uint8_t length[3];
+ uint8_t certificates[0];
+ } __attribute__ (( packed )) *certificate = data;
+ struct {
+ uint8_t length[3];
+ uint8_t certificate[0];
+ } __attribute__ (( packed )) *element =
+ ( ( void * ) certificate->certificates );
+ size_t elements_len = tls_uint24 ( certificate->length );
+ void *end = ( certificate->certificates + elements_len );
+ struct asn1_cursor cursor;
+ int rc;
+
+ /* Sanity check */
+ if ( end != ( data + len ) ) {
+ DBGC ( tls, "TLS %p received overlength Server Certificate\n",
+ tls );
+ DBGC_HD ( tls, data, len );
+ return -EINVAL;
+ }
+
+ /* Traverse certificate chain */
+ do {
+ cursor.data = element->certificate;
+ cursor.len = tls_uint24 ( element->length );
+ if ( ( cursor.data + cursor.len ) > end ) {
+ DBGC ( tls, "TLS %p received corrupt Server "
+ "Certificate\n", tls );
+ DBGC_HD ( tls, data, len );
+ return -EINVAL;
+ }
+
+ // HACK
+ if ( ( rc = x509_rsa_public_key ( &cursor,
+ &tls->rsa ) ) != 0 ) {
+ DBGC ( tls, "TLS %p cannot determine RSA public key: "
+ "%s\n", tls, strerror ( rc ) );
+ return rc;
+ }
+ return 0;
+
+ element = ( cursor.data + cursor.len );
+ } while ( element != end );
+
+ return -EINVAL;
+}
+
+/**
+ * Receive new Server Hello Done handshake record
+ *
+ * @v tls TLS session
+ * @v data Plaintext handshake record
+ * @v len Length of plaintext handshake record
+ * @ret rc Return status code
+ */
+static int tls_new_server_hello_done ( struct tls_session *tls,
+ void *data, size_t len ) {
+ struct {
+ 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 handshake record
+ *
+ * @v tls TLS session
+ * @v data Plaintext handshake record
+ * @v len Length of plaintext handshake record
+ * @ret rc Return status code
+ */
+static int tls_new_finished ( struct tls_session *tls,
+ void *data, size_t len ) {
+
+ /* FIXME: 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 ) {
+ struct {
+ uint8_t type;
+ uint8_t length[3];
+ uint8_t payload[0];
+ } __attribute__ (( packed )) *handshake = data;
+ void *payload = &handshake->payload;
+ size_t payload_len = tls_uint24 ( handshake->length );
+ void *end = ( payload + payload_len );
+ int rc;
+
+ /* Sanity check */
+ if ( end != ( data + len ) ) {
+ DBGC ( tls, "TLS %p received overlength Handshake\n", tls );
+ DBGC_HD ( tls, data, len );
+ return -EINVAL;
+ }
+
+ switch ( handshake->type ) {
+ case TLS_SERVER_HELLO:
+ rc = tls_new_server_hello ( tls, payload, payload_len );
+ break;
+ case TLS_CERTIFICATE:
+ rc = tls_new_certificate ( tls, payload, payload_len );
+ break;
+ case TLS_SERVER_HELLO_DONE:
+ rc = tls_new_server_hello_done ( tls, payload, payload_len );
+ break;
+ case TLS_FINISHED:
+ rc = tls_new_finished ( tls, payload, payload_len );
+ break;
+ default:
+ DBGC ( tls, "TLS %p ignoring handshake type %d\n",
+ tls, handshake->type );
+ rc = 0;
+ break;
+ }
+
+ /* Add to handshake digest (except for Hello Requests, which
+ * are explicitly excluded).
+ */
+ if ( handshake->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 digest_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 * __malloc 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;
+
+ /* FIXME: 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 );
+ cipher_encrypt ( cipherspec->cipher, cipherspec->cipher_next_ctx,
+ plaintext, iob_put ( ciphertext, plaintext_len ),
+ plaintext_len );
+
+ /* 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;
+
+ /* FIXME: 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 */
+ cipher_decrypt ( cipherspec->cipher, cipherspec->cipher_ctx,
+ ciphertext, plaintext, record_len );
+
+ /* 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,
+ .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_vreopen,
+ .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 );
+ tls->client_random.gmt_unix_time = 0;
+ tls_generate_random ( &tls->client_random.random,
+ ( sizeof ( tls->client_random.random ) ) );
+ tls->pre_master_secret.version = htons ( TLS_VERSION_TLS_1_0 );
+ tls_generate_random ( &tls->pre_master_secret.random,
+ ( sizeof ( tls->pre_master_secret.random ) ) );
+ 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;
+}
+