diff options
author | 2015-08-28 09:58:54 +0800 | |
---|---|---|
committer | 2015-09-01 12:44:00 +0800 | |
commit | e44e3482bdb4d0ebde2d8b41830ac2cdb07948fb (patch) | |
tree | 66b09f592c55df2878107a468a91d21506104d3f /qemu/roms/ipxe/src/net/tls.c | |
parent | 9ca8dbcc65cfc63d6f5ef3312a33184e1d726e00 (diff) |
Add qemu 2.4.0
Change-Id: Ic99cbad4b61f8b127b7dc74d04576c0bcbaaf4f5
Signed-off-by: Yang Zhang <yang.z.zhang@intel.com>
Diffstat (limited to 'qemu/roms/ipxe/src/net/tls.c')
-rw-r--r-- | qemu/roms/ipxe/src/net/tls.c | 2639 |
1 files changed, 2639 insertions, 0 deletions
diff --git a/qemu/roms/ipxe/src/net/tls.c b/qemu/roms/ipxe/src/net/tls.c new file mode 100644 index 000000000..30ccc932e --- /dev/null +++ b/qemu/roms/ipxe/src/net/tls.c @@ -0,0 +1,2639 @@ +/* + * 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., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +/** + * @file + * + * Transport Layer Security Protocol + */ + +#include <stdint.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <time.h> +#include <errno.h> +#include <byteswap.h> +#include <ipxe/pending.h> +#include <ipxe/hmac.h> +#include <ipxe/md5.h> +#include <ipxe/sha1.h> +#include <ipxe/sha256.h> +#include <ipxe/aes.h> +#include <ipxe/rsa.h> +#include <ipxe/iobuf.h> +#include <ipxe/xfer.h> +#include <ipxe/open.h> +#include <ipxe/x509.h> +#include <ipxe/privkey.h> +#include <ipxe/certstore.h> +#include <ipxe/rbg.h> +#include <ipxe/validator.h> +#include <ipxe/tls.h> + +/* Disambiguate the various error causes */ +#define EINVAL_CHANGE_CIPHER __einfo_error ( EINFO_EINVAL_CHANGE_CIPHER ) +#define EINFO_EINVAL_CHANGE_CIPHER \ + __einfo_uniqify ( EINFO_EINVAL, 0x01, \ + "Invalid Change Cipher record" ) +#define EINVAL_ALERT __einfo_error ( EINFO_EINVAL_ALERT ) +#define EINFO_EINVAL_ALERT \ + __einfo_uniqify ( EINFO_EINVAL, 0x02, \ + "Invalid Alert record" ) +#define EINVAL_HELLO __einfo_error ( EINFO_EINVAL_HELLO ) +#define EINFO_EINVAL_HELLO \ + __einfo_uniqify ( EINFO_EINVAL, 0x03, \ + "Invalid Server Hello record" ) +#define EINVAL_CERTIFICATE __einfo_error ( EINFO_EINVAL_CERTIFICATE ) +#define EINFO_EINVAL_CERTIFICATE \ + __einfo_uniqify ( EINFO_EINVAL, 0x04, \ + "Invalid Certificate" ) +#define EINVAL_CERTIFICATES __einfo_error ( EINFO_EINVAL_CERTIFICATES ) +#define EINFO_EINVAL_CERTIFICATES \ + __einfo_uniqify ( EINFO_EINVAL, 0x05, \ + "Invalid Server Certificate record" ) +#define EINVAL_HELLO_DONE __einfo_error ( EINFO_EINVAL_HELLO_DONE ) +#define EINFO_EINVAL_HELLO_DONE \ + __einfo_uniqify ( EINFO_EINVAL, 0x06, \ + "Invalid Server Hello Done record" ) +#define EINVAL_FINISHED __einfo_error ( EINFO_EINVAL_FINISHED ) +#define EINFO_EINVAL_FINISHED \ + __einfo_uniqify ( EINFO_EINVAL, 0x07, \ + "Invalid Server Finished record" ) +#define EINVAL_HANDSHAKE __einfo_error ( EINFO_EINVAL_HANDSHAKE ) +#define EINFO_EINVAL_HANDSHAKE \ + __einfo_uniqify ( EINFO_EINVAL, 0x08, \ + "Invalid Handshake record" ) +#define EINVAL_STREAM __einfo_error ( EINFO_EINVAL_STREAM ) +#define EINFO_EINVAL_STREAM \ + __einfo_uniqify ( EINFO_EINVAL, 0x09, \ + "Invalid stream-ciphered record" ) +#define EINVAL_BLOCK __einfo_error ( EINFO_EINVAL_BLOCK ) +#define EINFO_EINVAL_BLOCK \ + __einfo_uniqify ( EINFO_EINVAL, 0x0a, \ + "Invalid block-ciphered record" ) +#define EINVAL_PADDING __einfo_error ( EINFO_EINVAL_PADDING ) +#define EINFO_EINVAL_PADDING \ + __einfo_uniqify ( EINFO_EINVAL, 0x0b, \ + "Invalid block padding" ) +#define EINVAL_RX_STATE __einfo_error ( EINFO_EINVAL_RX_STATE ) +#define EINFO_EINVAL_RX_STATE \ + __einfo_uniqify ( EINFO_EINVAL, 0x0c, \ + "Invalid receive state" ) +#define EINVAL_MAC __einfo_error ( EINFO_EINVAL_MAC ) +#define EINFO_EINVAL_MAC \ + __einfo_uniqify ( EINFO_EINVAL, 0x0d, \ + "Invalid MAC" ) +#define EIO_ALERT __einfo_error ( EINFO_EIO_ALERT ) +#define EINFO_EIO_ALERT \ + __einfo_uniqify ( EINFO_EINVAL, 0x01, \ + "Unknown alert level" ) +#define ENOMEM_CONTEXT __einfo_error ( EINFO_ENOMEM_CONTEXT ) +#define EINFO_ENOMEM_CONTEXT \ + __einfo_uniqify ( EINFO_ENOMEM, 0x01, \ + "Not enough space for crypto context" ) +#define ENOMEM_CERTIFICATE __einfo_error ( EINFO_ENOMEM_CERTIFICATE ) +#define EINFO_ENOMEM_CERTIFICATE \ + __einfo_uniqify ( EINFO_ENOMEM, 0x02, \ + "Not enough space for certificate" ) +#define ENOMEM_CHAIN __einfo_error ( EINFO_ENOMEM_CHAIN ) +#define EINFO_ENOMEM_CHAIN \ + __einfo_uniqify ( EINFO_ENOMEM, 0x03, \ + "Not enough space for certificate chain" ) +#define ENOMEM_TX_PLAINTEXT __einfo_error ( EINFO_ENOMEM_TX_PLAINTEXT ) +#define EINFO_ENOMEM_TX_PLAINTEXT \ + __einfo_uniqify ( EINFO_ENOMEM, 0x04, \ + "Not enough space for transmitted plaintext" ) +#define ENOMEM_TX_CIPHERTEXT __einfo_error ( EINFO_ENOMEM_TX_CIPHERTEXT ) +#define EINFO_ENOMEM_TX_CIPHERTEXT \ + __einfo_uniqify ( EINFO_ENOMEM, 0x05, \ + "Not enough space for transmitted ciphertext" ) +#define ENOMEM_RX_DATA __einfo_error ( EINFO_ENOMEM_RX_DATA ) +#define EINFO_ENOMEM_RX_DATA \ + __einfo_uniqify ( EINFO_ENOMEM, 0x07, \ + "Not enough space for received data" ) +#define ENOMEM_RX_CONCAT __einfo_error ( EINFO_ENOMEM_RX_CONCAT ) +#define EINFO_ENOMEM_RX_CONCAT \ + __einfo_uniqify ( EINFO_ENOMEM, 0x08, \ + "Not enough space to concatenate received data" ) +#define ENOTSUP_CIPHER __einfo_error ( EINFO_ENOTSUP_CIPHER ) +#define EINFO_ENOTSUP_CIPHER \ + __einfo_uniqify ( EINFO_ENOTSUP, 0x01, \ + "Unsupported cipher" ) +#define ENOTSUP_NULL __einfo_error ( EINFO_ENOTSUP_NULL ) +#define EINFO_ENOTSUP_NULL \ + __einfo_uniqify ( EINFO_ENOTSUP, 0x02, \ + "Refusing to use null cipher" ) +#define ENOTSUP_SIG_HASH __einfo_error ( EINFO_ENOTSUP_SIG_HASH ) +#define EINFO_ENOTSUP_SIG_HASH \ + __einfo_uniqify ( EINFO_ENOTSUP, 0x03, \ + "Unsupported signature and hash algorithm" ) +#define ENOTSUP_VERSION __einfo_error ( EINFO_ENOTSUP_VERSION ) +#define EINFO_ENOTSUP_VERSION \ + __einfo_uniqify ( EINFO_ENOTSUP, 0x04, \ + "Unsupported protocol version" ) +#define EPERM_ALERT __einfo_error ( EINFO_EPERM_ALERT ) +#define EINFO_EPERM_ALERT \ + __einfo_uniqify ( EINFO_EPERM, 0x01, \ + "Received fatal alert" ) +#define EPERM_VERIFY __einfo_error ( EINFO_EPERM_VERIFY ) +#define EINFO_EPERM_VERIFY \ + __einfo_uniqify ( EINFO_EPERM, 0x02, \ + "Handshake verification failed" ) +#define EPERM_CLIENT_CERT __einfo_error ( EINFO_EPERM_CLIENT_CERT ) +#define EINFO_EPERM_CLIENT_CERT \ + __einfo_uniqify ( EINFO_EPERM, 0x03, \ + "No suitable client certificate available" ) +#define EPROTO_VERSION __einfo_error ( EINFO_EPROTO_VERSION ) +#define EINFO_EPROTO_VERSION \ + __einfo_uniqify ( EINFO_EPROTO, 0x01, \ + "Illegal protocol version upgrade" ) + +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 inline __attribute__ (( always_inline )) unsigned long +tls_uint24 ( const uint8_t field24[3] ) { + const uint32_t *field32 __attribute__ (( may_alias )) = + ( ( const void * ) field24 ); + return ( be32_to_cpu ( *field32 ) >> 8 ); +} + +/** + * Set 24-bit field value + * + * @v field24 24-bit field + * @v value Field value + * + * The field must be pre-zeroed. + */ +static void tls_set_uint24 ( uint8_t field24[3], unsigned long value ) { + uint32_t *field32 __attribute__ (( may_alias )) = + ( ( void * ) field24 ); + *field32 |= cpu_to_be32 ( value << 8 ); +} + +/** + * Determine if TLS session is ready for application data + * + * @v tls TLS session + * @ret is_ready TLS session is ready + */ +static int tls_ready ( struct tls_session *tls ) { + return ( ( ! is_pending ( &tls->client_negotiation ) ) && + ( ! is_pending ( &tls->server_negotiation ) ) ); +} + +/****************************************************************************** + * + * Hybrid MD5+SHA1 hash as used by TLSv1.1 and earlier + * + ****************************************************************************** + */ + +/** + * Initialise MD5+SHA1 algorithm + * + * @v ctx MD5+SHA1 context + */ +static void md5_sha1_init ( void *ctx ) { + struct md5_sha1_context *context = ctx; + + digest_init ( &md5_algorithm, context->md5 ); + digest_init ( &sha1_algorithm, context->sha1 ); +} + +/** + * Accumulate data with MD5+SHA1 algorithm + * + * @v ctx MD5+SHA1 context + * @v data Data + * @v len Length of data + */ +static void md5_sha1_update ( void *ctx, const void *data, size_t len ) { + struct md5_sha1_context *context = ctx; + + digest_update ( &md5_algorithm, context->md5, data, len ); + digest_update ( &sha1_algorithm, context->sha1, data, len ); +} + +/** + * Generate MD5+SHA1 digest + * + * @v ctx MD5+SHA1 context + * @v out Output buffer + */ +static void md5_sha1_final ( void *ctx, void *out ) { + struct md5_sha1_context *context = ctx; + struct md5_sha1_digest *digest = out; + + digest_final ( &md5_algorithm, context->md5, digest->md5 ); + digest_final ( &sha1_algorithm, context->sha1, digest->sha1 ); +} + +/** Hybrid MD5+SHA1 digest algorithm */ +static struct digest_algorithm md5_sha1_algorithm = { + .name = "md5+sha1", + .ctxsize = sizeof ( struct md5_sha1_context ), + .blocksize = 0, /* Not applicable */ + .digestsize = sizeof ( struct md5_sha1_digest ), + .init = md5_sha1_init, + .update = md5_sha1_update, + .final = md5_sha1_final, +}; + +/** RSA digestInfo prefix for MD5+SHA1 algorithm */ +struct rsa_digestinfo_prefix rsa_md5_sha1_prefix __rsa_digestinfo_prefix = { + .digest = &md5_sha1_algorithm, + .data = NULL, /* MD5+SHA1 signatures have no digestInfo */ + .len = 0, +}; + +/****************************************************************************** + * + * 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 ); + struct io_buffer *iobuf; + struct io_buffer *tmp; + + /* 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 ); + list_for_each_entry_safe ( iobuf, tmp, &tls->rx_data, list ) { + list_del ( &iobuf->list ); + free_iob ( iobuf ); + } + x509_put ( tls->cert ); + x509_chain_put ( tls->chain ); + + /* 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 pending operations, if applicable */ + pending_put ( &tls->client_negotiation ); + pending_put ( &tls->server_negotiation ); + + /* Remove process */ + process_del ( &tls->process ); + + /* Close all interfaces */ + intf_shutdown ( &tls->cipherstream, rc ); + intf_shutdown ( &tls->plainstream, rc ); + intf_shutdown ( &tls->validator, rc ); +} + +/****************************************************************************** + * + * Random number generation + * + ****************************************************************************** + */ + +/** + * Generate random data + * + * @v tls TLS session + * @v data Buffer to fill + * @v len Length of buffer + * @ret rc Return status code + */ +static int tls_generate_random ( struct tls_session *tls, + void *data, size_t len ) { + int rc; + + /* Generate random bits with no additional input and without + * prediction resistance + */ + if ( ( rc = rbg_generate ( NULL, 0, 0, data, len ) ) != 0 ) { + DBGC ( tls, "TLS %p could not generate random data: %s\n", + tls, strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** + * 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 buf[out_len]; + unsigned int i; + + va_start ( seeds, out_len ); + + if ( tls->version >= TLS_VERSION_TLS_1_2 ) { + /* Use P_SHA256 for TLSv1.2 and later */ + tls_p_hash_va ( tls, &sha256_algorithm, secret, secret_len, + out, out_len, seeds ); + } else { + /* Use combination of P_MD5 and P_SHA-1 for TLSv1.1 + * and earlier + */ + + /* 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, out_len, seeds ); + va_end ( tmp ); + + /* Calculate SHA1 portion */ + va_copy ( tmp, seeds ); + tls_p_hash_va ( tls, &sha1_algorithm, sha1_secret, + subsecret_len, buf, 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 ) ^= buf[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->suite->digest->digestsize; + size_t key_size = tx_cipherspec->suite->key_len; + size_t iv_size = tx_cipherspec->suite->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->suite->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->suite->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->suite->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->suite->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 + * + ****************************************************************************** + */ + +/** Null cipher suite */ +struct tls_cipher_suite tls_cipher_suite_null = { + .pubkey = &pubkey_null, + .cipher = &cipher_null, + .digest = &digest_null, +}; + +/** Supported cipher suites, in order of preference */ +struct tls_cipher_suite tls_cipher_suites[] = { + { + .code = htons ( TLS_RSA_WITH_AES_256_CBC_SHA256 ), + .key_len = ( 256 / 8 ), + .pubkey = &rsa_algorithm, + .cipher = &aes_cbc_algorithm, + .digest = &sha256_algorithm, + }, + { + .code = htons ( TLS_RSA_WITH_AES_128_CBC_SHA256 ), + .key_len = ( 128 / 8 ), + .pubkey = &rsa_algorithm, + .cipher = &aes_cbc_algorithm, + .digest = &sha256_algorithm, + }, + { + .code = htons ( TLS_RSA_WITH_AES_256_CBC_SHA ), + .key_len = ( 256 / 8 ), + .pubkey = &rsa_algorithm, + .cipher = &aes_cbc_algorithm, + .digest = &sha1_algorithm, + }, + { + .code = htons ( TLS_RSA_WITH_AES_128_CBC_SHA ), + .key_len = ( 128 / 8 ), + .pubkey = &rsa_algorithm, + .cipher = &aes_cbc_algorithm, + .digest = &sha1_algorithm, + }, +}; + +/** Number of supported cipher suites */ +#define TLS_NUM_CIPHER_SUITES \ + ( sizeof ( tls_cipher_suites ) / sizeof ( tls_cipher_suites[0] ) ) + +/** + * Identify cipher suite + * + * @v cipher_suite Cipher suite specification + * @ret suite Cipher suite, or NULL + */ +static struct tls_cipher_suite * +tls_find_cipher_suite ( unsigned int cipher_suite ) { + struct tls_cipher_suite *suite; + unsigned int i; + + /* Identify cipher suite */ + for ( i = 0 ; i < TLS_NUM_CIPHER_SUITES ; i++ ) { + suite = &tls_cipher_suites[i]; + if ( suite->code == cipher_suite ) + return suite; + } + + return NULL; +} + +/** + * Clear cipher suite + * + * @v cipherspec TLS cipher specification + */ +static void tls_clear_cipher ( struct tls_session *tls __unused, + struct tls_cipherspec *cipherspec ) { + + if ( cipherspec->suite ) { + pubkey_final ( cipherspec->suite->pubkey, + cipherspec->pubkey_ctx ); + } + free ( cipherspec->dynamic ); + memset ( cipherspec, 0, sizeof ( *cipherspec ) ); + cipherspec->suite = &tls_cipher_suite_null; +} + +/** + * Set cipher suite + * + * @v tls TLS session + * @v cipherspec TLS cipher specification + * @v suite Cipher suite + * @ret rc Return status code + */ +static int tls_set_cipher ( struct tls_session *tls, + struct tls_cipherspec *cipherspec, + struct tls_cipher_suite *suite ) { + struct pubkey_algorithm *pubkey = suite->pubkey; + struct cipher_algorithm *cipher = suite->cipher; + struct digest_algorithm *digest = suite->digest; + 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 = zalloc ( total ); + if ( ! dynamic ) { + DBGC ( tls, "TLS %p could not allocate %zd bytes for crypto " + "context\n", tls, total ); + return -ENOMEM_CONTEXT; + } + + /* 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->suite = suite; + + 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 tls_cipher_suite *suite; + int rc; + + /* Identify cipher suite */ + suite = tls_find_cipher_suite ( cipher_suite ); + if ( ! suite ) { + DBGC ( tls, "TLS %p does not support cipher %04x\n", + tls, ntohs ( cipher_suite ) ); + return -ENOTSUP_CIPHER; + } + + /* Set ciphers */ + if ( ( rc = tls_set_cipher ( tls, &tls->tx_cipherspec_pending, + suite ) ) != 0 ) + return rc; + if ( ( rc = tls_set_cipher ( tls, &tls->rx_cipherspec_pending, + suite ) ) != 0 ) + return rc; + + DBGC ( tls, "TLS %p selected %s-%s-%d-%s\n", tls, suite->pubkey->name, + suite->cipher->name, ( suite->key_len * 8 ), + suite->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 ( pending->suite == &tls_cipher_suite_null ) { + DBGC ( tls, "TLS %p refusing to use null cipher\n", tls ); + return -ENOTSUP_NULL; + } + + tls_clear_cipher ( tls, active ); + memswap ( active, pending, sizeof ( *active ) ); + return 0; +} + +/****************************************************************************** + * + * Signature and hash algorithms + * + ****************************************************************************** + */ + +/** Supported signature and hash algorithms + * + * Note that the default (TLSv1.1 and earlier) algorithm using + * MD5+SHA1 is never explicitly specified. + */ +struct tls_signature_hash_algorithm tls_signature_hash_algorithms[] = { + { + .code = { + .signature = TLS_RSA_ALGORITHM, + .hash = TLS_SHA256_ALGORITHM, + }, + .pubkey = &rsa_algorithm, + .digest = &sha256_algorithm, + }, +}; + +/** Number of supported signature and hash algorithms */ +#define TLS_NUM_SIG_HASH_ALGORITHMS \ + ( sizeof ( tls_signature_hash_algorithms ) / \ + sizeof ( tls_signature_hash_algorithms[0] ) ) + +/** + * Find TLS signature and hash algorithm + * + * @v pubkey Public-key algorithm + * @v digest Digest algorithm + * @ret sig_hash Signature and hash algorithm, or NULL + */ +static struct tls_signature_hash_algorithm * +tls_signature_hash_algorithm ( struct pubkey_algorithm *pubkey, + struct digest_algorithm *digest ) { + struct tls_signature_hash_algorithm *sig_hash; + unsigned int i; + + /* Identify signature and hash algorithm */ + for ( i = 0 ; i < TLS_NUM_SIG_HASH_ALGORITHMS ; i++ ) { + sig_hash = &tls_signature_hash_algorithms[i]; + if ( ( sig_hash->pubkey == pubkey ) && + ( sig_hash->digest == digest ) ) { + return sig_hash; + } + } + + return NULL; +} + +/****************************************************************************** + * + * 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_sha1_algorithm, tls->handshake_md5_sha1_ctx, + data, len ); + digest_update ( &sha256_algorithm, tls->handshake_sha256_ctx, + data, len ); +} + +/** + * Calculate handshake verification hash + * + * @v tls TLS session + * @v out Output buffer + * + * Calculates the MD5+SHA1 or SHA256 digest over all handshake + * messages seen so far. + */ +static void tls_verify_handshake ( struct tls_session *tls, void *out ) { + struct digest_algorithm *digest = tls->handshake_digest; + uint8_t ctx[ digest->ctxsize ]; + + memcpy ( ctx, tls->handshake_ctx, sizeof ( ctx ) ); + digest_final ( digest, ctx, out ); +} + +/****************************************************************************** + * + * Record handling + * + ****************************************************************************** + */ + +/** + * Resume TX state machine + * + * @v tls TLS session + */ +static void tls_tx_resume ( struct tls_session *tls ) { + process_add ( &tls->process ); +} + +/** + * 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[TLS_NUM_CIPHER_SUITES]; + uint8_t compression_methods_len; + uint8_t compression_methods[1]; + uint16_t extensions_len; + struct { + uint16_t server_name_type; + uint16_t server_name_len; + struct { + uint16_t len; + struct { + uint8_t type; + uint16_t len; + uint8_t name[ strlen ( tls->name ) ]; + } __attribute__ (( packed )) list[1]; + } __attribute__ (( packed )) server_name; + uint16_t max_fragment_length_type; + uint16_t max_fragment_length_len; + struct { + uint8_t max; + } __attribute__ (( packed )) max_fragment_length; + } __attribute__ (( packed )) extensions; + } __attribute__ (( packed )) hello; + unsigned int i; + + 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 ); + memcpy ( &hello.random, &tls->client_random, sizeof ( hello.random ) ); + hello.cipher_suite_len = htons ( sizeof ( hello.cipher_suites ) ); + for ( i = 0 ; i < TLS_NUM_CIPHER_SUITES ; i++ ) + hello.cipher_suites[i] = tls_cipher_suites[i].code; + hello.compression_methods_len = sizeof ( hello.compression_methods ); + hello.extensions_len = htons ( sizeof ( hello.extensions ) ); + hello.extensions.server_name_type = htons ( TLS_SERVER_NAME ); + hello.extensions.server_name_len + = htons ( sizeof ( hello.extensions.server_name ) ); + hello.extensions.server_name.len + = htons ( sizeof ( hello.extensions.server_name.list ) ); + hello.extensions.server_name.list[0].type = TLS_SERVER_NAME_HOST_NAME; + hello.extensions.server_name.list[0].len + = htons ( sizeof ( hello.extensions.server_name.list[0].name )); + memcpy ( hello.extensions.server_name.list[0].name, tls->name, + sizeof ( hello.extensions.server_name.list[0].name ) ); + hello.extensions.max_fragment_length_type + = htons ( TLS_MAX_FRAGMENT_LENGTH ); + hello.extensions.max_fragment_length_len + = htons ( sizeof ( hello.extensions.max_fragment_length ) ); + hello.extensions.max_fragment_length.max + = TLS_MAX_FRAGMENT_LENGTH_4096; + + return tls_send_handshake ( tls, &hello, sizeof ( hello ) ); +} + +/** + * Transmit Certificate record + * + * @v tls TLS session + * @ret rc Return status code + */ +static int tls_send_certificate ( struct tls_session *tls ) { + struct { + uint32_t type_length; + uint8_t length[3]; + struct { + uint8_t length[3]; + uint8_t data[ tls->cert->raw.len ]; + } __attribute__ (( packed )) certificates[1]; + } __attribute__ (( packed )) *certificate; + int rc; + + /* Allocate storage for Certificate record (which may be too + * large for the stack). + */ + certificate = zalloc ( sizeof ( *certificate ) ); + if ( ! certificate ) + return -ENOMEM_CERTIFICATE; + + /* Populate record */ + certificate->type_length = + ( cpu_to_le32 ( TLS_CERTIFICATE ) | + htonl ( sizeof ( *certificate ) - + sizeof ( certificate->type_length ) ) ); + tls_set_uint24 ( certificate->length, + sizeof ( certificate->certificates ) ); + tls_set_uint24 ( certificate->certificates[0].length, + sizeof ( certificate->certificates[0].data ) ); + memcpy ( certificate->certificates[0].data, + tls->cert->raw.data, + sizeof ( certificate->certificates[0].data ) ); + + /* Transmit record */ + rc = tls_send_handshake ( tls, certificate, sizeof ( *certificate ) ); + + /* Free record */ + free ( certificate ); + + return rc; +} + +/** + * 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 ) { + struct tls_cipherspec *cipherspec = &tls->tx_cipherspec_pending; + struct pubkey_algorithm *pubkey = cipherspec->suite->pubkey; + size_t max_len = pubkey_max_len ( pubkey, cipherspec->pubkey_ctx ); + struct { + uint32_t type_length; + uint16_t encrypted_pre_master_secret_len; + uint8_t encrypted_pre_master_secret[max_len]; + } __attribute__ (( packed )) key_xchg; + size_t unused; + int len; + int rc; + + /* Encrypt pre-master secret using server's public key */ + memset ( &key_xchg, 0, sizeof ( key_xchg ) ); + len = pubkey_encrypt ( pubkey, cipherspec->pubkey_ctx, + &tls->pre_master_secret, + sizeof ( tls->pre_master_secret ), + key_xchg.encrypted_pre_master_secret ); + if ( len < 0 ) { + rc = len; + DBGC ( tls, "TLS %p could not encrypt pre-master secret: %s\n", + tls, strerror ( rc ) ); + return rc; + } + unused = ( max_len - len ); + key_xchg.type_length = + ( cpu_to_le32 ( TLS_CLIENT_KEY_EXCHANGE ) | + htonl ( sizeof ( key_xchg ) - + sizeof ( key_xchg.type_length ) - unused ) ); + key_xchg.encrypted_pre_master_secret_len = + htons ( sizeof ( key_xchg.encrypted_pre_master_secret ) - + unused ); + + return tls_send_handshake ( tls, &key_xchg, + ( sizeof ( key_xchg ) - unused ) ); +} + +/** + * Transmit Certificate Verify record + * + * @v tls TLS session + * @ret rc Return status code + */ +static int tls_send_certificate_verify ( struct tls_session *tls ) { + struct digest_algorithm *digest = tls->handshake_digest; + struct x509_certificate *cert = tls->cert; + struct pubkey_algorithm *pubkey = cert->signature_algorithm->pubkey; + uint8_t digest_out[ digest->digestsize ]; + uint8_t ctx[ pubkey->ctxsize ]; + struct tls_signature_hash_algorithm *sig_hash = NULL; + int rc; + + /* Generate digest to be signed */ + tls_verify_handshake ( tls, digest_out ); + + /* Initialise public-key algorithm */ + if ( ( rc = pubkey_init ( pubkey, ctx, private_key.data, + private_key.len ) ) != 0 ) { + DBGC ( tls, "TLS %p could not initialise %s client private " + "key: %s\n", tls, pubkey->name, strerror ( rc ) ); + goto err_pubkey_init; + } + + /* TLSv1.2 and later use explicit algorithm identifiers */ + if ( tls->version >= TLS_VERSION_TLS_1_2 ) { + sig_hash = tls_signature_hash_algorithm ( pubkey, digest ); + if ( ! sig_hash ) { + DBGC ( tls, "TLS %p could not identify (%s,%s) " + "signature and hash algorithm\n", tls, + pubkey->name, digest->name ); + rc = -ENOTSUP_SIG_HASH; + goto err_sig_hash; + } + } + + /* Generate and transmit record */ + { + size_t max_len = pubkey_max_len ( pubkey, ctx ); + int use_sig_hash = ( ( sig_hash == NULL ) ? 0 : 1 ); + struct { + uint32_t type_length; + struct tls_signature_hash_id sig_hash[use_sig_hash]; + uint16_t signature_len; + uint8_t signature[max_len]; + } __attribute__ (( packed )) certificate_verify; + size_t unused; + int len; + + /* Sign digest */ + len = pubkey_sign ( pubkey, ctx, digest, digest_out, + certificate_verify.signature ); + if ( len < 0 ) { + rc = len; + DBGC ( tls, "TLS %p could not sign %s digest using %s " + "client private key: %s\n", tls, digest->name, + pubkey->name, strerror ( rc ) ); + goto err_pubkey_sign; + } + unused = ( max_len - len ); + + /* Construct Certificate Verify record */ + certificate_verify.type_length = + ( cpu_to_le32 ( TLS_CERTIFICATE_VERIFY ) | + htonl ( sizeof ( certificate_verify ) - + sizeof ( certificate_verify.type_length ) - + unused ) ); + if ( use_sig_hash ) { + memcpy ( &certificate_verify.sig_hash[0], + &sig_hash->code, + sizeof ( certificate_verify.sig_hash[0] ) ); + } + certificate_verify.signature_len = + htons ( sizeof ( certificate_verify.signature ) - + unused ); + + /* Transmit record */ + rc = tls_send_handshake ( tls, &certificate_verify, + ( sizeof ( certificate_verify ) - unused ) ); + } + + err_pubkey_sign: + err_sig_hash: + pubkey_final ( pubkey, ctx ); + err_pubkey_init: + return rc; +} + +/** + * 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 digest_algorithm *digest = tls->handshake_digest; + struct { + uint32_t type_length; + uint8_t verify_data[12]; + } __attribute__ (( packed )) finished; + uint8_t digest_out[ digest->digestsize ]; + int rc; + + /* Construct record */ + 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_out ); + tls_prf_label ( tls, &tls->master_secret, sizeof ( tls->master_secret ), + finished.verify_data, sizeof ( finished.verify_data ), + "client finished", digest_out, sizeof ( digest_out ) ); + + /* Transmit record */ + if ( ( rc = tls_send_handshake ( tls, &finished, + sizeof ( finished ) ) ) != 0 ) + return rc; + + /* Mark client as finished */ + pending_put ( &tls->client_negotiation ); + + return 0; +} + +/** + * 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, + const 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_CHANGE_CIPHER; + } + + 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, const void *data, + size_t len ) { + const struct { + uint8_t level; + uint8_t description; + char next[0]; + } __attribute__ (( packed )) *alert = data; + const 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_ALERT; + } + + 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_ALERT; + default: + DBGC ( tls, "TLS %p received unknown alert level %d" + "(alert %d)\n", tls, alert->level, alert->description ); + return -EIO_ALERT; + } +} + +/** + * 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, + const void *data, size_t len ) { + const struct { + uint16_t version; + uint8_t random[32]; + uint8_t session_id_len; + char next[0]; + } __attribute__ (( packed )) *hello_a = data; + const 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; + const void *end = hello_b->next; + uint16_t version; + int rc; + + /* Sanity check */ + if ( end > ( data + len ) ) { + DBGC ( tls, "TLS %p received underlength Server Hello\n", tls ); + DBGC_HD ( tls, data, len ); + return -EINVAL_HELLO; + } + + /* Check and store protocol version */ + version = ntohs ( hello_a->version ); + if ( version < TLS_VERSION_TLS_1_0 ) { + DBGC ( tls, "TLS %p does not support protocol version %d.%d\n", + tls, ( version >> 8 ), ( version & 0xff ) ); + return -ENOTSUP_VERSION; + } + if ( version > tls->version ) { + DBGC ( tls, "TLS %p server attempted to illegally upgrade to " + "protocol version %d.%d\n", + tls, ( version >> 8 ), ( version & 0xff ) ); + return -EPROTO_VERSION; + } + tls->version = version; + DBGC ( tls, "TLS %p using protocol version %d.%d\n", + tls, ( version >> 8 ), ( version & 0xff ) ); + + /* Use MD5+SHA1 digest algorithm for handshake verification + * for versions earlier than TLSv1.2. + */ + if ( tls->version < TLS_VERSION_TLS_1_2 ) { + tls->handshake_digest = &md5_sha1_algorithm; + tls->handshake_ctx = tls->handshake_md5_sha1_ctx; + } + + /* 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; +} + +/** + * Parse certificate chain + * + * @v tls TLS session + * @v data Certificate chain + * @v len Length of certificate chain + * @ret rc Return status code + */ +static int tls_parse_chain ( struct tls_session *tls, + const void *data, size_t len ) { + const void *end = ( data + len ); + const struct { + uint8_t length[3]; + uint8_t data[0]; + } __attribute__ (( packed )) *certificate; + size_t certificate_len; + struct x509_certificate *cert; + const void *next; + int rc; + + /* Free any existing certificate chain */ + x509_chain_put ( tls->chain ); + tls->chain = NULL; + + /* Create certificate chain */ + tls->chain = x509_alloc_chain(); + if ( ! tls->chain ) { + rc = -ENOMEM_CHAIN; + goto err_alloc_chain; + } + + /* Add certificates to chain */ + while ( data < end ) { + + /* Extract raw certificate data */ + certificate = data; + certificate_len = tls_uint24 ( certificate->length ); + next = ( certificate->data + certificate_len ); + if ( next > end ) { + DBGC ( tls, "TLS %p overlength certificate:\n", tls ); + DBGC_HDA ( tls, 0, data, ( end - data ) ); + rc = -EINVAL_CERTIFICATE; + goto err_overlength; + } + + /* Add certificate to chain */ + if ( ( rc = x509_append_raw ( tls->chain, certificate->data, + certificate_len ) ) != 0 ) { + DBGC ( tls, "TLS %p could not append certificate: %s\n", + tls, strerror ( rc ) ); + DBGC_HDA ( tls, 0, data, ( end - data ) ); + goto err_parse; + } + cert = x509_last ( tls->chain ); + DBGC ( tls, "TLS %p found certificate %s\n", + tls, x509_name ( cert ) ); + + /* Move to next certificate in list */ + data = next; + } + + return 0; + + err_parse: + err_overlength: + x509_chain_put ( tls->chain ); + tls->chain = NULL; + err_alloc_chain: + return rc; +} + +/** + * 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, + const void *data, size_t len ) { + const struct { + uint8_t length[3]; + uint8_t certificates[0]; + } __attribute__ (( packed )) *certificate = data; + size_t certificates_len = tls_uint24 ( certificate->length ); + const void *end = ( certificate->certificates + certificates_len ); + 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_CERTIFICATES; + } + + /* Parse certificate chain */ + if ( ( rc = tls_parse_chain ( tls, certificate->certificates, + certificates_len ) ) != 0 ) + return rc; + + return 0; +} + +/** + * Receive new Certificate Request 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_request ( struct tls_session *tls, + const void *data __unused, + size_t len __unused ) { + + /* We can only send a single certificate, so there is no point + * in parsing the Certificate Request. + */ + + /* Free any existing client certificate */ + x509_put ( tls->cert ); + + /* Determine client certificate to be sent */ + tls->cert = certstore_find_key ( &private_key ); + if ( ! tls->cert ) { + DBGC ( tls, "TLS %p could not find certificate corresponding " + "to private key\n", tls ); + return -EPERM_CLIENT_CERT; + } + x509_get ( tls->cert ); + DBGC ( tls, "TLS %p sending client certificate %s\n", + tls, x509_name ( tls->cert ) ); + + return 0; +} + +/** + * 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, + const void *data, size_t len ) { + const struct { + char next[0]; + } __attribute__ (( packed )) *hello_done = data; + const void *end = hello_done->next; + int rc; + + /* Sanity check */ + if ( end != ( data + len ) ) { + DBGC ( tls, "TLS %p received overlength Server Hello Done\n", + tls ); + DBGC_HD ( tls, data, len ); + return -EINVAL_HELLO_DONE; + } + + /* Begin certificate validation */ + if ( ( rc = create_validator ( &tls->validator, tls->chain ) ) != 0 ) { + DBGC ( tls, "TLS %p could not start certificate validation: " + "%s\n", tls, strerror ( rc ) ); + return rc; + } + + 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, + const void *data, size_t len ) { + struct digest_algorithm *digest = tls->handshake_digest; + const struct { + uint8_t verify_data[12]; + char next[0]; + } __attribute__ (( packed )) *finished = data; + const void *end = finished->next; + uint8_t digest_out[ digest->digestsize ]; + uint8_t verify_data[ sizeof ( finished->verify_data ) ]; + + /* Sanity check */ + if ( end != ( data + len ) ) { + DBGC ( tls, "TLS %p received overlength Finished\n", tls ); + DBGC_HD ( tls, data, len ); + return -EINVAL_FINISHED; + } + + /* Verify data */ + tls_verify_handshake ( tls, digest_out ); + tls_prf_label ( tls, &tls->master_secret, sizeof ( tls->master_secret ), + verify_data, sizeof ( verify_data ), "server finished", + digest_out, sizeof ( digest_out ) ); + if ( memcmp ( verify_data, finished->verify_data, + sizeof ( verify_data ) ) != 0 ) { + DBGC ( tls, "TLS %p verification failed\n", tls ); + return -EPERM_VERIFY; + } + + /* Mark server as finished */ + pending_put ( &tls->server_negotiation ); + + /* Send notification of a window change */ + xfer_window_changed ( &tls->plainstream ); + + 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, + const void *data, size_t len ) { + const void *end = ( data + len ); + int rc; + + while ( data != end ) { + const 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 *next = ( payload + payload_len ); + + /* Sanity check */ + if ( next > end ) { + DBGC ( tls, "TLS %p received overlength Handshake\n", + tls ); + DBGC_HD ( tls, data, len ); + return -EINVAL_HANDSHAKE; + } + + 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_CERTIFICATE_REQUEST: + rc = tls_new_certificate_request ( 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, + sizeof ( *handshake ) + + payload_len ); + + /* Abort on failure */ + if ( rc != 0 ) + return rc; + + /* Move to next handshake record */ + data = next; + } + + return 0; +} + +/** + * Receive new record + * + * @v tls TLS session + * @v type Record type + * @v rx_data List of received data buffers + * @ret rc Return status code + */ +static int tls_new_record ( struct tls_session *tls, unsigned int type, + struct list_head *rx_data ) { + struct io_buffer *iobuf; + int ( * handler ) ( struct tls_session *tls, const void *data, + size_t len ); + int rc; + + /* Deliver data records to the plainstream interface */ + if ( type == TLS_TYPE_DATA ) { + + /* Fail unless we are ready to receive data */ + if ( ! tls_ready ( tls ) ) + return -ENOTCONN; + + /* Deliver each I/O buffer in turn */ + while ( ( iobuf = list_first_entry ( rx_data, struct io_buffer, + list ) ) ) { + list_del ( &iobuf->list ); + if ( ( rc = xfer_deliver_iob ( &tls->plainstream, + iobuf ) ) != 0 ) { + DBGC ( tls, "TLS %p could not deliver data: " + "%s\n", tls, strerror ( rc ) ); + return rc; + } + } + return 0; + } + + /* For all other records, merge into a single I/O buffer */ + iobuf = iob_concatenate ( rx_data ); + if ( ! iobuf ) { + DBGC ( tls, "TLS %p could not concatenate non-data record " + "type %d\n", tls, type ); + return -ENOMEM_RX_CONCAT; + } + + /* Determine handler */ + switch ( type ) { + case TLS_TYPE_CHANGE_CIPHER: + handler = tls_new_change_cipher; + break; + case TLS_TYPE_ALERT: + handler = tls_new_alert; + break; + case TLS_TYPE_HANDSHAKE: + handler = tls_new_handshake; + break; + default: + /* RFC4346 says that we should just ignore unknown + * record types. + */ + handler = NULL; + DBGC ( tls, "TLS %p ignoring record type %d\n", tls, type ); + break; + } + + /* Handle record and free I/O buffer */ + rc = ( handler ? handler ( tls, iobuf->data, iob_len ( iobuf ) ) : 0 ); + free_iob ( iobuf ); + return rc; +} + +/****************************************************************************** + * + * Record encryption/decryption + * + ****************************************************************************** + */ + +/** + * Initialise HMAC + * + * @v cipherspec Cipher specification + * @v ctx Context + * @v seq Sequence number + * @v tlshdr TLS header + */ +static void tls_hmac_init ( struct tls_cipherspec *cipherspec, void *ctx, + uint64_t seq, struct tls_header *tlshdr ) { + struct digest_algorithm *digest = cipherspec->suite->digest; + + hmac_init ( digest, ctx, cipherspec->mac_secret, &digest->digestsize ); + seq = cpu_to_be64 ( seq ); + hmac_update ( digest, ctx, &seq, sizeof ( seq ) ); + hmac_update ( digest, ctx, tlshdr, sizeof ( *tlshdr ) ); +} + +/** + * Update HMAC + * + * @v cipherspec Cipher specification + * @v ctx Context + * @v data Data + * @v len Length of data + */ +static void tls_hmac_update ( struct tls_cipherspec *cipherspec, void *ctx, + const void *data, size_t len ) { + struct digest_algorithm *digest = cipherspec->suite->digest; + + hmac_update ( digest, ctx, data, len ); +} + +/** + * Finalise HMAC + * + * @v cipherspec Cipher specification + * @v ctx Context + * @v mac HMAC to fill in + */ +static void tls_hmac_final ( struct tls_cipherspec *cipherspec, void *ctx, + void *hmac ) { + struct digest_algorithm *digest = cipherspec->suite->digest; + + hmac_final ( digest, ctx, cipherspec->mac_secret, + &digest->digestsize, hmac ); +} + +/** + * Calculate HMAC + * + * @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_cipherspec *cipherspec, + uint64_t seq, struct tls_header *tlshdr, + const void *data, size_t len, void *hmac ) { + struct digest_algorithm *digest = cipherspec->suite->digest; + uint8_t ctx[digest->ctxsize]; + + tls_hmac_init ( cipherspec, ctx, seq, tlshdr ); + tls_hmac_update ( cipherspec, ctx, data, len ); + tls_hmac_final ( cipherspec, ctx, 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.suite->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.suite->cipher->blocksize; + size_t mac_len = tls->tx_cipherspec.suite->digest->digestsize; + size_t iv_len; + size_t padding_len; + void *plaintext; + void *iv; + void *content; + void *mac; + void *padding; + + /* TLSv1.1 and later use an explicit IV */ + iv_len = ( ( tls->version >= TLS_VERSION_TLS_1_1 ) ? blocksize : 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 */ + tls_generate_random ( tls, iv, 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; + struct cipher_algorithm *cipher = cipherspec->suite->cipher; + void *plaintext = NULL; + size_t plaintext_len; + struct io_buffer *ciphertext = NULL; + size_t ciphertext_len; + size_t mac_len = cipherspec->suite->digest->digestsize; + uint8_t mac[mac_len]; + int rc; + + /* Construct header */ + plaintext_tlshdr.type = type; + plaintext_tlshdr.version = htons ( tls->version ); + plaintext_tlshdr.length = htons ( len ); + + /* Calculate MAC */ + tls_hmac ( cipherspec, tls->tx_seq, &plaintext_tlshdr, data, len, mac ); + + /* Allocate and assemble plaintext struct */ + if ( is_stream_cipher ( 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_TX_PLAINTEXT; + 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, ciphertext_len ); + if ( ! ciphertext ) { + DBGC ( tls, "TLS %p could not allocate %zd bytes for " + "ciphertext\n", tls, ciphertext_len ); + rc = -ENOMEM_TX_CIPHERTEXT; + goto done; + } + + /* Assemble ciphertext */ + tlshdr = iob_put ( ciphertext, sizeof ( *tlshdr ) ); + tlshdr->type = type; + tlshdr->version = htons ( tls->version ); + tlshdr->length = htons ( plaintext_len ); + memcpy ( cipherspec->cipher_next_ctx, cipherspec->cipher_ctx, + cipher->ctxsize ); + cipher_encrypt ( 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 */ + if ( ( rc = xfer_deliver_iob ( &tls->cipherstream, + iob_disown ( ciphertext ) ) ) != 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, cipher->ctxsize ); + + done: + free ( plaintext ); + free_iob ( ciphertext ); + return rc; +} + +/** + * Split stream-ciphered record into data and MAC portions + * + * @v tls TLS session + * @v rx_data List of received data buffers + * @v mac MAC to fill in + * @ret rc Return status code + */ +static int tls_split_stream ( struct tls_session *tls, + struct list_head *rx_data, void **mac ) { + size_t mac_len = tls->rx_cipherspec.suite->digest->digestsize; + struct io_buffer *iobuf; + + /* Extract MAC */ + iobuf = list_last_entry ( rx_data, struct io_buffer, list ); + assert ( iobuf != NULL ); + if ( iob_len ( iobuf ) < mac_len ) { + DBGC ( tls, "TLS %p received underlength MAC\n", tls ); + DBGC_HD ( tls, iobuf->data, iob_len ( iobuf ) ); + return -EINVAL_STREAM; + } + iob_unput ( iobuf, mac_len ); + *mac = iobuf->tail; + + return 0; +} + +/** + * Split block-ciphered record into data and MAC portions + * + * @v tls TLS session + * @v rx_data List of received data buffers + * @v mac MAC to fill in + * @ret rc Return status code + */ +static int tls_split_block ( struct tls_session *tls, + struct list_head *rx_data, void **mac ) { + size_t mac_len = tls->rx_cipherspec.suite->digest->digestsize; + struct io_buffer *iobuf; + size_t iv_len; + uint8_t *padding_final; + uint8_t *padding; + size_t padding_len; + + /* TLSv1.1 and later use an explicit IV */ + iobuf = list_first_entry ( rx_data, struct io_buffer, list ); + iv_len = ( ( tls->version >= TLS_VERSION_TLS_1_1 ) ? + tls->rx_cipherspec.suite->cipher->blocksize : 0 ); + if ( iob_len ( iobuf ) < iv_len ) { + DBGC ( tls, "TLS %p received underlength IV\n", tls ); + DBGC_HD ( tls, iobuf->data, iob_len ( iobuf ) ); + return -EINVAL_BLOCK; + } + iob_pull ( iobuf, iv_len ); + + /* Extract and verify padding */ + iobuf = list_last_entry ( rx_data, struct io_buffer, list ); + padding_final = ( iobuf->tail - 1 ); + padding_len = *padding_final; + if ( ( padding_len + 1 ) > iob_len ( iobuf ) ) { + DBGC ( tls, "TLS %p received underlength padding\n", tls ); + DBGC_HD ( tls, iobuf->data, iob_len ( iobuf ) ); + return -EINVAL_BLOCK; + } + iob_unput ( iobuf, ( padding_len + 1 ) ); + for ( padding = iobuf->tail ; padding < padding_final ; padding++ ) { + if ( *padding != padding_len ) { + DBGC ( tls, "TLS %p received bad padding\n", tls ); + DBGC_HD ( tls, padding, padding_len ); + return -EINVAL_PADDING; + } + } + + /* Extract MAC */ + if ( iob_len ( iobuf ) < mac_len ) { + DBGC ( tls, "TLS %p received underlength MAC\n", tls ); + DBGC_HD ( tls, iobuf->data, iob_len ( iobuf ) ); + return -EINVAL_BLOCK; + } + iob_unput ( iobuf, mac_len ); + *mac = iobuf->tail; + + return 0; +} + +/** + * Receive new ciphertext record + * + * @v tls TLS session + * @v tlshdr Record header + * @v rx_data List of received data buffers + * @ret rc Return status code + */ +static int tls_new_ciphertext ( struct tls_session *tls, + struct tls_header *tlshdr, + struct list_head *rx_data ) { + struct tls_header plaintext_tlshdr; + struct tls_cipherspec *cipherspec = &tls->rx_cipherspec; + struct cipher_algorithm *cipher = cipherspec->suite->cipher; + struct digest_algorithm *digest = cipherspec->suite->digest; + uint8_t ctx[digest->ctxsize]; + uint8_t verify_mac[digest->digestsize]; + struct io_buffer *iobuf; + void *mac; + size_t len = 0; + int rc; + + /* Decrypt the received data */ + list_for_each_entry ( iobuf, &tls->rx_data, list ) { + cipher_decrypt ( cipher, cipherspec->cipher_ctx, + iobuf->data, iobuf->data, iob_len ( iobuf ) ); + } + + /* Split record into content and MAC */ + if ( is_stream_cipher ( cipher ) ) { + if ( ( rc = tls_split_stream ( tls, rx_data, &mac ) ) != 0 ) + return rc; + } else { + if ( ( rc = tls_split_block ( tls, rx_data, &mac ) ) != 0 ) + return rc; + } + + /* Calculate total length */ + DBGC2 ( tls, "Received plaintext data:\n" ); + list_for_each_entry ( iobuf, rx_data, list ) { + DBGC2_HD ( tls, iobuf->data, iob_len ( iobuf ) ); + len += iob_len ( iobuf ); + } + + /* Verify MAC */ + plaintext_tlshdr.type = tlshdr->type; + plaintext_tlshdr.version = tlshdr->version; + plaintext_tlshdr.length = htons ( len ); + tls_hmac_init ( cipherspec, ctx, tls->rx_seq, &plaintext_tlshdr ); + list_for_each_entry ( iobuf, rx_data, list ) { + tls_hmac_update ( cipherspec, ctx, iobuf->data, + iob_len ( iobuf ) ); + } + tls_hmac_final ( cipherspec, ctx, verify_mac ); + if ( memcmp ( mac, verify_mac, sizeof ( verify_mac ) ) != 0 ) { + DBGC ( tls, "TLS %p failed MAC verification\n", tls ); + return -EINVAL_MAC; + } + + /* Process plaintext record */ + if ( ( rc = tls_new_record ( tls, tlshdr->type, rx_data ) ) != 0 ) + return rc; + + return 0; +} + +/****************************************************************************** + * + * Plaintext stream operations + * + ****************************************************************************** + */ + +/** + * Check flow control window + * + * @v tls TLS session + * @ret len Length of window + */ +static size_t tls_plainstream_window ( struct tls_session *tls ) { + + /* Block window unless we are ready to accept data */ + if ( ! tls_ready ( tls ) ) + return 0; + + return xfer_window ( &tls->cipherstream ); +} + +/** + * Deliver datagram as raw data + * + * @v tls TLS session + * @v iobuf I/O buffer + * @v meta Data transfer metadata + * @ret rc Return status code + */ +static int tls_plainstream_deliver ( struct tls_session *tls, + struct io_buffer *iobuf, + struct xfer_metadata *meta __unused ) { + int rc; + + /* Refuse unless we are ready to accept data */ + if ( ! tls_ready ( tls ) ) { + rc = -ENOTCONN; + goto done; + } + + if ( ( rc = tls_send_plaintext ( tls, TLS_TYPE_DATA, iobuf->data, + iob_len ( iobuf ) ) ) != 0 ) + goto done; + + done: + free_iob ( iobuf ); + return rc; +} + +/** TLS plaintext stream interface operations */ +static struct interface_operation tls_plainstream_ops[] = { + INTF_OP ( xfer_deliver, struct tls_session *, tls_plainstream_deliver ), + INTF_OP ( xfer_window, struct tls_session *, tls_plainstream_window ), + INTF_OP ( intf_close, struct tls_session *, tls_close ), +}; + +/** TLS plaintext stream interface descriptor */ +static struct interface_descriptor tls_plainstream_desc = + INTF_DESC_PASSTHRU ( struct tls_session, plainstream, + tls_plainstream_ops, cipherstream ); + +/****************************************************************************** + * + * Ciphertext stream operations + * + ****************************************************************************** + */ + +/** + * 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 ); + size_t remaining = data_len; + size_t frag_len; + struct io_buffer *iobuf; + struct io_buffer *tmp; + int rc; + + /* Allocate data buffers now that we know the length */ + assert ( list_empty ( &tls->rx_data ) ); + while ( remaining ) { + + /* Calculate fragment length. Ensure that no block is + * smaller than TLS_RX_MIN_BUFSIZE (by increasing the + * allocation length if necessary). + */ + frag_len = remaining; + if ( frag_len > TLS_RX_BUFSIZE ) + frag_len = TLS_RX_BUFSIZE; + remaining -= frag_len; + if ( remaining < TLS_RX_MIN_BUFSIZE ) { + frag_len += remaining; + remaining = 0; + } + + /* Allocate buffer */ + iobuf = alloc_iob_raw ( frag_len, TLS_RX_ALIGN, 0 ); + if ( ! iobuf ) { + DBGC ( tls, "TLS %p could not allocate %zd of %zd " + "bytes for receive buffer\n", tls, + remaining, data_len ); + rc = -ENOMEM_RX_DATA; + goto err; + } + + /* Ensure tailroom is exactly what we asked for. This + * will result in unaligned I/O buffers when the + * fragment length is unaligned, which can happen only + * before we switch to using a block cipher. + */ + iob_reserve ( iobuf, ( iob_tailroom ( iobuf ) - frag_len ) ); + + /* Add I/O buffer to list */ + list_add_tail ( &iobuf->list, &tls->rx_data ); + } + + /* Move to data state */ + tls->rx_state = TLS_RX_DATA; + + return 0; + + err: + list_for_each_entry_safe ( iobuf, tmp, &tls->rx_data, list ) { + list_del ( &iobuf->list ); + free_iob ( iobuf ); + } + return rc; +} + +/** + * Handle received TLS data payload + * + * @v tls TLS session + * @ret rc Returned status code + */ +static int tls_newdata_process_data ( struct tls_session *tls ) { + struct io_buffer *iobuf; + int rc; + + /* Move current buffer to end of list */ + iobuf = list_first_entry ( &tls->rx_data, struct io_buffer, list ); + list_del ( &iobuf->list ); + list_add_tail ( &iobuf->list, &tls->rx_data ); + + /* Continue receiving data if any space remains */ + iobuf = list_first_entry ( &tls->rx_data, struct io_buffer, list ); + if ( iob_tailroom ( iobuf ) ) + return 0; + + /* 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; + + /* Return to header state */ + assert ( list_empty ( &tls->rx_data ) ); + tls->rx_state = TLS_RX_HEADER; + iob_unput ( &tls->rx_header_iobuf, sizeof ( tls->rx_header ) ); + + return 0; +} + +/** + * Receive new ciphertext + * + * @v tls TLS session + * @v iobuf I/O buffer + * @v meta Data transfer metadat + * @ret rc Return status code + */ +static int tls_cipherstream_deliver ( struct tls_session *tls, + struct io_buffer *iobuf, + struct xfer_metadata *xfer __unused ) { + size_t frag_len; + int ( * process ) ( struct tls_session *tls ); + struct io_buffer *dest; + int rc; + + while ( iob_len ( iobuf ) ) { + + /* Select buffer according to current state */ + switch ( tls->rx_state ) { + case TLS_RX_HEADER: + dest = &tls->rx_header_iobuf; + process = tls_newdata_process_header; + break; + case TLS_RX_DATA: + dest = list_first_entry ( &tls->rx_data, + struct io_buffer, list ); + assert ( dest != NULL ); + process = tls_newdata_process_data; + break; + default: + assert ( 0 ); + rc = -EINVAL_RX_STATE; + goto done; + } + + /* Copy data portion to buffer */ + frag_len = iob_len ( iobuf ); + if ( frag_len > iob_tailroom ( dest ) ) + frag_len = iob_tailroom ( dest ); + memcpy ( iob_put ( dest, frag_len ), iobuf->data, frag_len ); + iob_pull ( iobuf, frag_len ); + + /* Process data if buffer is now full */ + if ( iob_tailroom ( dest ) == 0 ) { + if ( ( rc = process ( tls ) ) != 0 ) { + tls_close ( tls, rc ); + goto done; + } + } + } + rc = 0; + + done: + free_iob ( iobuf ); + return rc; +} + +/** TLS ciphertext stream interface operations */ +static struct interface_operation tls_cipherstream_ops[] = { + INTF_OP ( xfer_deliver, struct tls_session *, + tls_cipherstream_deliver ), + INTF_OP ( xfer_window_changed, struct tls_session *, tls_tx_resume ), + INTF_OP ( intf_close, struct tls_session *, tls_close ), +}; + +/** TLS ciphertext stream interface descriptor */ +static struct interface_descriptor tls_cipherstream_desc = + INTF_DESC_PASSTHRU ( struct tls_session, cipherstream, + tls_cipherstream_ops, plainstream ); + +/****************************************************************************** + * + * Certificate validator + * + ****************************************************************************** + */ + +/** + * Handle certificate validation completion + * + * @v tls TLS session + * @v rc Reason for completion + */ +static void tls_validator_done ( struct tls_session *tls, int rc ) { + struct tls_cipherspec *cipherspec = &tls->tx_cipherspec_pending; + struct pubkey_algorithm *pubkey = cipherspec->suite->pubkey; + struct x509_certificate *cert; + + /* Close validator interface */ + intf_restart ( &tls->validator, rc ); + + /* Check for validation failure */ + if ( rc != 0 ) { + DBGC ( tls, "TLS %p certificate validation failed: %s\n", + tls, strerror ( rc ) ); + goto err; + } + DBGC ( tls, "TLS %p certificate validation succeeded\n", tls ); + + /* Extract first certificate */ + cert = x509_first ( tls->chain ); + assert ( cert != NULL ); + + /* Verify server name */ + if ( ( rc = x509_check_name ( cert, tls->name ) ) != 0 ) { + DBGC ( tls, "TLS %p server certificate does not match %s: %s\n", + tls, tls->name, strerror ( rc ) ); + goto err; + } + + /* Initialise public key algorithm */ + if ( ( rc = pubkey_init ( pubkey, cipherspec->pubkey_ctx, + cert->subject.public_key.raw.data, + cert->subject.public_key.raw.len ) ) != 0 ) { + DBGC ( tls, "TLS %p cannot initialise public key: %s\n", + tls, strerror ( rc ) ); + goto err; + } + + /* Schedule Client Key Exchange, Change Cipher, and Finished */ + tls->tx_pending |= ( TLS_TX_CLIENT_KEY_EXCHANGE | + TLS_TX_CHANGE_CIPHER | + TLS_TX_FINISHED ); + if ( tls->cert ) { + tls->tx_pending |= ( TLS_TX_CERTIFICATE | + TLS_TX_CERTIFICATE_VERIFY ); + } + tls_tx_resume ( tls ); + + return; + + err: + tls_close ( tls, rc ); + return; +} + +/** TLS certificate validator interface operations */ +static struct interface_operation tls_validator_ops[] = { + INTF_OP ( intf_close, struct tls_session *, tls_validator_done ), +}; + +/** TLS certificate validator interface descriptor */ +static struct interface_descriptor tls_validator_desc = + INTF_DESC ( struct tls_session, validator, tls_validator_ops ); + +/****************************************************************************** + * + * Controlling process + * + ****************************************************************************** + */ + +/** + * TLS TX state machine + * + * @v tls TLS session + */ +static void tls_tx_step ( struct tls_session *tls ) { + int rc; + + /* Wait for cipherstream to become ready */ + if ( ! xfer_window ( &tls->cipherstream ) ) + return; + + /* Send first pending transmission */ + if ( tls->tx_pending & 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_pending &= ~TLS_TX_CLIENT_HELLO; + } else if ( tls->tx_pending & TLS_TX_CERTIFICATE ) { + /* Send Certificate */ + if ( ( rc = tls_send_certificate ( tls ) ) != 0 ) { + DBGC ( tls, "TLS %p cold not send Certificate: %s\n", + tls, strerror ( rc ) ); + goto err; + } + tls->tx_pending &= ~TLS_TX_CERTIFICATE; + } else if ( tls->tx_pending & TLS_TX_CLIENT_KEY_EXCHANGE ) { + /* Send Client Key Exchange */ + if ( ( rc = tls_send_client_key_exchange ( tls ) ) != 0 ) { + DBGC ( tls, "TLS %p could not send Client Key " + "Exchange: %s\n", tls, strerror ( rc ) ); + goto err; + } + tls->tx_pending &= ~TLS_TX_CLIENT_KEY_EXCHANGE; + } else if ( tls->tx_pending & TLS_TX_CERTIFICATE_VERIFY ) { + /* Send Certificate Verify */ + if ( ( rc = tls_send_certificate_verify ( tls ) ) != 0 ) { + DBGC ( tls, "TLS %p could not send Certificate " + "Verify: %s\n", tls, strerror ( rc ) ); + goto err; + } + tls->tx_pending &= ~TLS_TX_CERTIFICATE_VERIFY; + } else if ( tls->tx_pending & 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_pending &= ~TLS_TX_CHANGE_CIPHER; + } else if ( tls->tx_pending & 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_pending &= ~TLS_TX_FINISHED; + } + + /* Reschedule process if pending transmissions remain */ + if ( tls->tx_pending ) + tls_tx_resume ( tls ); + + return; + + err: + tls_close ( tls, rc ); +} + +/** TLS TX process descriptor */ +static struct process_descriptor tls_process_desc = + PROC_DESC_ONCE ( struct tls_session, process, tls_tx_step ); + +/****************************************************************************** + * + * Instantiator + * + ****************************************************************************** + */ + +int add_tls ( struct interface *xfer, const char *name, + struct interface **next ) { + struct tls_session *tls; + int rc; + + /* Allocate and initialise TLS structure */ + tls = malloc ( sizeof ( *tls ) ); + if ( ! tls ) { + rc = -ENOMEM; + goto err_alloc; + } + memset ( tls, 0, sizeof ( *tls ) ); + ref_init ( &tls->refcnt, free_tls ); + tls->name = name; + intf_init ( &tls->plainstream, &tls_plainstream_desc, &tls->refcnt ); + intf_init ( &tls->cipherstream, &tls_cipherstream_desc, &tls->refcnt ); + intf_init ( &tls->validator, &tls_validator_desc, &tls->refcnt ); + process_init ( &tls->process, &tls_process_desc, &tls->refcnt ); + tls->version = TLS_VERSION_TLS_1_2; + 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 = time ( NULL ); + if ( ( rc = tls_generate_random ( tls, &tls->client_random.random, + ( sizeof ( tls->client_random.random ) ) ) ) != 0 ) { + goto err_random; + } + tls->pre_master_secret.version = htons ( tls->version ); + if ( ( rc = tls_generate_random ( tls, &tls->pre_master_secret.random, + ( sizeof ( tls->pre_master_secret.random ) ) ) ) != 0 ) { + goto err_random; + } + digest_init ( &md5_sha1_algorithm, tls->handshake_md5_sha1_ctx ); + digest_init ( &sha256_algorithm, tls->handshake_sha256_ctx ); + tls->handshake_digest = &sha256_algorithm; + tls->handshake_ctx = tls->handshake_sha256_ctx; + tls->tx_pending = TLS_TX_CLIENT_HELLO; + iob_populate ( &tls->rx_header_iobuf, &tls->rx_header, 0, + sizeof ( tls->rx_header ) ); + INIT_LIST_HEAD ( &tls->rx_data ); + + /* Add pending operations for server and client Finished messages */ + pending_get ( &tls->client_negotiation ); + pending_get ( &tls->server_negotiation ); + + /* Attach to parent interface, mortalise self, and return */ + intf_plug_plug ( &tls->plainstream, xfer ); + *next = &tls->cipherstream; + ref_put ( &tls->refcnt ); + return 0; + + err_random: + ref_put ( &tls->refcnt ); + err_alloc: + return rc; +} |