/* * 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; }