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