diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 16:29:52 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 16:29:52 +0000 |
commit | ca67b09c015d4af3ae3cce12aa72e60941dbb8b5 (patch) | |
tree | b7316d7b06c373e08dabb79a2c866c568e08f49e /debian/grub-extras/disabled/gpxe/src/net/tls.c | |
parent | Adding upstream version 2.06. (diff) | |
download | grub2-ca67b09c015d4af3ae3cce12aa72e60941dbb8b5.tar.xz grub2-ca67b09c015d4af3ae3cce12aa72e60941dbb8b5.zip |
Adding debian version 2.06-13+deb12u1.debian/2.06-13+deb12u1debian
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'debian/grub-extras/disabled/gpxe/src/net/tls.c')
-rw-r--r-- | debian/grub-extras/disabled/gpxe/src/net/tls.c | 1759 |
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; +} + |