/* * Copyright (C) 2007 Michael Brown . * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ FILE_LICENCE ( GPL2_OR_LATER ); /** * @file * * Transport Layer Security Protocol */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Disambiguate the various error causes */ #define EINVAL_CHANGE_CIPHER __einfo_error ( EINFO_EINVAL_CHANGE_CIPHER ) #define EINFO_EINVAL_CHANGE_CIPHER \ __einfo_uniqify ( EINFO_EINVAL, 0x01, \ "Invalid Change Cipher record" ) #define EINVAL_ALERT __einfo_error ( EINFO_EINVAL_ALERT ) #define EINFO_EINVAL_ALERT \ __einfo_uniqify ( EINFO_EINVAL, 0x02, \ "Invalid Alert record" ) #define EINVAL_HELLO __einfo_error ( EINFO_EINVAL_HELLO ) #define EINFO_EINVAL_HELLO \ __einfo_uniqify ( EINFO_EINVAL, 0x03, \ "Invalid Server Hello record" ) #define EINVAL_CERTIFICATE __einfo_error ( EINFO_EINVAL_CERTIFICATE ) #define EINFO_EINVAL_CERTIFICATE \ __einfo_uniqify ( EINFO_EINVAL, 0x04, \ "Invalid Certificate" ) #define EINVAL_CERTIFICATES __einfo_error ( EINFO_EINVAL_CERTIFICATES ) #define EINFO_EINVAL_CERTIFICATES \ __einfo_uniqify ( EINFO_EINVAL, 0x05, \ "Invalid Server Certificate record" ) #define EINVAL_HELLO_DONE __einfo_error ( EINFO_EINVAL_HELLO_DONE ) #define EINFO_EINVAL_HELLO_DONE \ __einfo_uniqify ( EINFO_EINVAL, 0x06, \ "Invalid Server Hello Done record" ) #define EINVAL_FINISHED __einfo_error ( EINFO_EINVAL_FINISHED ) #define EINFO_EINVAL_FINISHED \ __einfo_uniqify ( EINFO_EINVAL, 0x07, \ "Invalid Server Finished record" ) #define EINVAL_HANDSHAKE __einfo_error ( EINFO_EINVAL_HANDSHAKE ) #define EINFO_EINVAL_HANDSHAKE \ __einfo_uniqify ( EINFO_EINVAL, 0x08, \ "Invalid Handshake record" ) #define EINVAL_STREAM __einfo_error ( EINFO_EINVAL_STREAM ) #define EINFO_EINVAL_STREAM \ __einfo_uniqify ( EINFO_EINVAL, 0x09, \ "Invalid stream-ciphered record" ) #define EINVAL_BLOCK __einfo_error ( EINFO_EINVAL_BLOCK ) #define EINFO_EINVAL_BLOCK \ __einfo_uniqify ( EINFO_EINVAL, 0x0a, \ "Invalid block-ciphered record" ) #define EINVAL_PADDING __einfo_error ( EINFO_EINVAL_PADDING ) #define EINFO_EINVAL_PADDING \ __einfo_uniqify ( EINFO_EINVAL, 0x0b, \ "Invalid block padding" ) #define EINVAL_RX_STATE __einfo_error ( EINFO_EINVAL_RX_STATE ) #define EINFO_EINVAL_RX_STATE \ __einfo_uniqify ( EINFO_EINVAL, 0x0c, \ "Invalid receive state" ) #define EINVAL_MAC __einfo_error ( EINFO_EINVAL_MAC ) #define EINFO_EINVAL_MAC \ __einfo_uniqify ( EINFO_EINVAL, 0x0d, \ "Invalid MAC" ) #define EINVAL_TICKET __einfo_error ( EINFO_EINVAL_TICKET ) #define EINFO_EINVAL_TICKET \ __einfo_uniqify ( EINFO_EINVAL, 0x0e, \ "Invalid New Session Ticket record") #define EIO_ALERT __einfo_error ( EINFO_EIO_ALERT ) #define EINFO_EIO_ALERT \ __einfo_uniqify ( EINFO_EIO, 0x01, \ "Unknown alert level" ) #define ENOMEM_CONTEXT __einfo_error ( EINFO_ENOMEM_CONTEXT ) #define EINFO_ENOMEM_CONTEXT \ __einfo_uniqify ( EINFO_ENOMEM, 0x01, \ "Not enough space for crypto context" ) #define ENOMEM_CERTIFICATE __einfo_error ( EINFO_ENOMEM_CERTIFICATE ) #define EINFO_ENOMEM_CERTIFICATE \ __einfo_uniqify ( EINFO_ENOMEM, 0x02, \ "Not enough space for certificate" ) #define ENOMEM_CHAIN __einfo_error ( EINFO_ENOMEM_CHAIN ) #define EINFO_ENOMEM_CHAIN \ __einfo_uniqify ( EINFO_ENOMEM, 0x03, \ "Not enough space for certificate chain" ) #define ENOMEM_TX_PLAINTEXT __einfo_error ( EINFO_ENOMEM_TX_PLAINTEXT ) #define EINFO_ENOMEM_TX_PLAINTEXT \ __einfo_uniqify ( EINFO_ENOMEM, 0x04, \ "Not enough space for transmitted plaintext" ) #define ENOMEM_TX_CIPHERTEXT __einfo_error ( EINFO_ENOMEM_TX_CIPHERTEXT ) #define EINFO_ENOMEM_TX_CIPHERTEXT \ __einfo_uniqify ( EINFO_ENOMEM, 0x05, \ "Not enough space for transmitted ciphertext" ) #define ENOMEM_RX_DATA __einfo_error ( EINFO_ENOMEM_RX_DATA ) #define EINFO_ENOMEM_RX_DATA \ __einfo_uniqify ( EINFO_ENOMEM, 0x07, \ "Not enough space for received data" ) #define ENOMEM_RX_CONCAT __einfo_error ( EINFO_ENOMEM_RX_CONCAT ) #define EINFO_ENOMEM_RX_CONCAT \ __einfo_uniqify ( EINFO_ENOMEM, 0x08, \ "Not enough space to concatenate received data" ) #define ENOTSUP_CIPHER __einfo_error ( EINFO_ENOTSUP_CIPHER ) #define EINFO_ENOTSUP_CIPHER \ __einfo_uniqify ( EINFO_ENOTSUP, 0x01, \ "Unsupported cipher" ) #define ENOTSUP_NULL __einfo_error ( EINFO_ENOTSUP_NULL ) #define EINFO_ENOTSUP_NULL \ __einfo_uniqify ( EINFO_ENOTSUP, 0x02, \ "Refusing to use null cipher" ) #define ENOTSUP_SIG_HASH __einfo_error ( EINFO_ENOTSUP_SIG_HASH ) #define EINFO_ENOTSUP_SIG_HASH \ __einfo_uniqify ( EINFO_ENOTSUP, 0x03, \ "Unsupported signature and hash algorithm" ) #define ENOTSUP_VERSION __einfo_error ( EINFO_ENOTSUP_VERSION ) #define EINFO_ENOTSUP_VERSION \ __einfo_uniqify ( EINFO_ENOTSUP, 0x04, \ "Unsupported protocol version" ) #define EPERM_ALERT __einfo_error ( EINFO_EPERM_ALERT ) #define EINFO_EPERM_ALERT \ __einfo_uniqify ( EINFO_EPERM, 0x01, \ "Received fatal alert" ) #define EPERM_VERIFY __einfo_error ( EINFO_EPERM_VERIFY ) #define EINFO_EPERM_VERIFY \ __einfo_uniqify ( EINFO_EPERM, 0x02, \ "Handshake verification failed" ) #define EPERM_CLIENT_CERT __einfo_error ( EINFO_EPERM_CLIENT_CERT ) #define EINFO_EPERM_CLIENT_CERT \ __einfo_uniqify ( EINFO_EPERM, 0x03, \ "No suitable client certificate available" ) #define EPERM_RENEG_INSECURE __einfo_error ( EINFO_EPERM_RENEG_INSECURE ) #define EINFO_EPERM_RENEG_INSECURE \ __einfo_uniqify ( EINFO_EPERM, 0x04, \ "Secure renegotiation not supported" ) #define EPERM_RENEG_VERIFY __einfo_error ( EINFO_EPERM_RENEG_VERIFY ) #define EINFO_EPERM_RENEG_VERIFY \ __einfo_uniqify ( EINFO_EPERM, 0x05, \ "Secure renegotiation verification failed" ) #define EPROTO_VERSION __einfo_error ( EINFO_EPROTO_VERSION ) #define EINFO_EPROTO_VERSION \ __einfo_uniqify ( EINFO_EPROTO, 0x01, \ "Illegal protocol version upgrade" ) /** List of TLS session */ static LIST_HEAD ( tls_sessions ); static void tls_tx_resume_all ( struct tls_session *session ); static int tls_send_plaintext ( struct tls_connection *tls, unsigned int type, const void *data, size_t len ); static void tls_clear_cipher ( struct tls_connection *tls, struct tls_cipherspec *cipherspec ); /****************************************************************************** * * Utility functions * ****************************************************************************** */ /** A TLS 24-bit integer * * TLS uses 24-bit integers in several places, which are awkward to * parse in C. */ typedef struct { /** High byte */ uint8_t high; /** Low word */ uint16_t low; } __attribute__ (( packed )) tls24_t; /** * Extract 24-bit field value * * @v field24 24-bit field * @ret value Field value * */ static inline __attribute__ (( always_inline )) unsigned long tls_uint24 ( const tls24_t *field24 ) { return ( ( field24->high << 16 ) | be16_to_cpu ( field24->low ) ); } /** * Set 24-bit field value * * @v field24 24-bit field * @v value Field value */ static void tls_set_uint24 ( tls24_t *field24, unsigned long value ) { field24->high = ( value >> 16 ); field24->low = cpu_to_be16 ( value ); } /** * Determine if TLS connection is ready for application data * * @v tls TLS connection * @ret is_ready TLS connection is ready */ static int tls_ready ( struct tls_connection *tls ) { return ( ( ! is_pending ( &tls->client_negotiation ) ) && ( ! is_pending ( &tls->server_negotiation ) ) ); } /** * Check for TLS version * * @v tls TLS connection * @v version TLS version * @ret at_least TLS connection is using at least the specified version * * Check that TLS connection uses at least the specified protocol * version. Optimise down to a compile-time constant true result if * this is already guaranteed by the minimum supported version check. */ static inline __attribute__ (( always_inline )) int tls_version ( struct tls_connection *tls, unsigned int version ) { return ( ( TLS_VERSION_MIN >= version ) || ( tls->version >= version ) ); } /****************************************************************************** * * Hybrid MD5+SHA1 hash as used by TLSv1.1 and earlier * ****************************************************************************** */ /** * Initialise MD5+SHA1 algorithm * * @v ctx MD5+SHA1 context */ static void md5_sha1_init ( void *ctx ) { struct md5_sha1_context *context = ctx; digest_init ( &md5_algorithm, context->md5 ); digest_init ( &sha1_algorithm, context->sha1 ); } /** * Accumulate data with MD5+SHA1 algorithm * * @v ctx MD5+SHA1 context * @v data Data * @v len Length of data */ static void md5_sha1_update ( void *ctx, const void *data, size_t len ) { struct md5_sha1_context *context = ctx; digest_update ( &md5_algorithm, context->md5, data, len ); digest_update ( &sha1_algorithm, context->sha1, data, len ); } /** * Generate MD5+SHA1 digest * * @v ctx MD5+SHA1 context * @v out Output buffer */ static void md5_sha1_final ( void *ctx, void *out ) { struct md5_sha1_context *context = ctx; struct md5_sha1_digest *digest = out; digest_final ( &md5_algorithm, context->md5, digest->md5 ); digest_final ( &sha1_algorithm, context->sha1, digest->sha1 ); } /** Hybrid MD5+SHA1 digest algorithm */ static struct digest_algorithm md5_sha1_algorithm = { .name = "md5+sha1", .ctxsize = sizeof ( struct md5_sha1_context ), .blocksize = 0, /* Not applicable */ .digestsize = sizeof ( struct md5_sha1_digest ), .init = md5_sha1_init, .update = md5_sha1_update, .final = md5_sha1_final, }; /** RSA digestInfo prefix for MD5+SHA1 algorithm */ struct rsa_digestinfo_prefix rsa_md5_sha1_prefix __rsa_digestinfo_prefix = { .digest = &md5_sha1_algorithm, .data = NULL, /* MD5+SHA1 signatures have no digestInfo */ .len = 0, }; /****************************************************************************** * * Cleanup functions * ****************************************************************************** */ /** * Free TLS session * * @v refcnt Reference counter */ static void free_tls_session ( struct refcnt *refcnt ) { struct tls_session *session = container_of ( refcnt, struct tls_session, refcnt ); /* Sanity check */ assert ( list_empty ( &session->conn ) ); /* Remove from list of sessions */ list_del ( &session->list ); /* Free dynamically-allocated resources */ x509_root_put ( session->root ); privkey_put ( session->key ); free ( session->ticket ); /* Free session */ free ( session ); } /** * Free TLS connection * * @v refcnt Reference counter */ static void free_tls ( struct refcnt *refcnt ) { struct tls_connection *tls = container_of ( refcnt, struct tls_connection, refcnt ); struct tls_session *session = tls->session; struct io_buffer *iobuf; struct io_buffer *tmp; /* Free dynamically-allocated resources */ free ( tls->new_session_ticket ); tls_clear_cipher ( tls, &tls->tx_cipherspec ); tls_clear_cipher ( tls, &tls->tx_cipherspec_pending ); tls_clear_cipher ( tls, &tls->rx_cipherspec ); tls_clear_cipher ( tls, &tls->rx_cipherspec_pending ); list_for_each_entry_safe ( iobuf, tmp, &tls->rx_data, list ) { list_del ( &iobuf->list ); free_iob ( iobuf ); } x509_chain_put ( tls->certs ); x509_chain_put ( tls->chain ); x509_root_put ( tls->root ); privkey_put ( tls->key ); /* Drop reference to session */ assert ( list_empty ( &tls->list ) ); ref_put ( &session->refcnt ); /* Free TLS structure itself */ free ( tls ); } /** * Finish with TLS connection * * @v tls TLS connection * @v rc Status code */ static void tls_close ( struct tls_connection *tls, int rc ) { /* Remove pending operations, if applicable */ pending_put ( &tls->client_negotiation ); pending_put ( &tls->server_negotiation ); pending_put ( &tls->validation ); /* Remove process */ process_del ( &tls->process ); /* Close all interfaces */ intf_shutdown ( &tls->cipherstream, rc ); intf_shutdown ( &tls->plainstream, rc ); intf_shutdown ( &tls->validator, rc ); /* Remove from session */ list_del ( &tls->list ); INIT_LIST_HEAD ( &tls->list ); /* Resume all other connections, in case we were the lead connection */ tls_tx_resume_all ( tls->session ); } /****************************************************************************** * * Random number generation * ****************************************************************************** */ /** * Generate random data * * @v tls TLS connection * @v data Buffer to fill * @v len Length of buffer * @ret rc Return status code */ static int tls_generate_random ( struct tls_connection *tls, void *data, size_t len ) { int rc; /* Generate random bits with no additional input and without * prediction resistance */ if ( ( rc = rbg_generate ( NULL, 0, 0, data, len ) ) != 0 ) { DBGC ( tls, "TLS %p could not generate random data: %s\n", tls, strerror ( rc ) ); return rc; } return 0; } /** * Update HMAC with a list of ( data, len ) pairs * * @v digest Hash function to use * @v digest_ctx Digest context * @v args ( data, len ) pairs of data, terminated by NULL */ static void tls_hmac_update_va ( struct digest_algorithm *digest, void *digest_ctx, va_list args ) { void *data; size_t len; while ( ( data = va_arg ( args, void * ) ) ) { len = va_arg ( args, size_t ); hmac_update ( digest, digest_ctx, data, len ); } } /** * Generate secure pseudo-random data using a single hash function * * @v tls TLS connection * @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_connection *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 connection * @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_connection *tls, void *secret, size_t secret_len, void *out, size_t out_len, ... ) { va_list seeds; va_list tmp; size_t subsecret_len; void *md5_secret; void *sha1_secret; uint8_t buf[out_len]; unsigned int i; va_start ( seeds, out_len ); if ( tls_version ( tls, TLS_VERSION_TLS_1_2 ) ) { /* Use P_SHA256 for TLSv1.2 and later */ tls_p_hash_va ( tls, &sha256_algorithm, secret, secret_len, out, out_len, seeds ); } else { /* Use combination of P_MD5 and P_SHA-1 for TLSv1.1 * and earlier */ /* Split secret into two, with an overlap of up to one byte */ subsecret_len = ( ( secret_len + 1 ) / 2 ); md5_secret = secret; sha1_secret = ( secret + secret_len - subsecret_len ); /* Calculate MD5 portion */ va_copy ( tmp, seeds ); tls_p_hash_va ( tls, &md5_algorithm, md5_secret, subsecret_len, out, out_len, seeds ); va_end ( tmp ); /* Calculate SHA1 portion */ va_copy ( tmp, seeds ); tls_p_hash_va ( tls, &sha1_algorithm, sha1_secret, subsecret_len, buf, out_len, seeds ); va_end ( tmp ); /* XOR the two portions together into the final output buffer */ for ( i = 0 ; i < out_len ; i++ ) *( ( uint8_t * ) out + i ) ^= buf[i]; } va_end ( seeds ); } /** * Generate secure pseudo-random data * * @v secret Secret * @v secret_len Length of secret * @v out Output buffer * @v out_len Length of output buffer * @v label String literal label * @v ... ( data, len ) pairs of seed data */ #define tls_prf_label( tls, secret, secret_len, out, out_len, label, ... ) \ tls_prf ( (tls), (secret), (secret_len), (out), (out_len), \ label, ( sizeof ( label ) - 1 ), __VA_ARGS__, NULL ) /****************************************************************************** * * Secret management * ****************************************************************************** */ /** * Generate master secret * * @v tls TLS connection * * The pre-master secret and the client and server random values must * already be known. */ static void tls_generate_master_secret ( struct tls_connection *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 connection * * The master secret must already be known. */ static int tls_generate_keys ( struct tls_connection *tls ) { struct tls_cipherspec *tx_cipherspec = &tls->tx_cipherspec_pending; struct tls_cipherspec *rx_cipherspec = &tls->rx_cipherspec_pending; size_t hash_size = tx_cipherspec->suite->digest->digestsize; size_t key_size = tx_cipherspec->suite->key_len; size_t iv_size = tx_cipherspec->suite->cipher->blocksize; size_t total = ( 2 * ( hash_size + key_size + iv_size ) ); uint8_t key_block[total]; uint8_t *key; int rc; /* Generate key block */ tls_prf_label ( tls, &tls->master_secret, sizeof ( tls->master_secret ), key_block, sizeof ( key_block ), "key expansion", &tls->server_random, sizeof ( tls->server_random ), &tls->client_random, sizeof ( tls->client_random ) ); /* Split key block into portions */ key = key_block; /* TX MAC secret */ memcpy ( tx_cipherspec->mac_secret, key, hash_size ); DBGC ( tls, "TLS %p TX MAC secret:\n", tls ); DBGC_HD ( tls, key, hash_size ); key += hash_size; /* RX MAC secret */ memcpy ( rx_cipherspec->mac_secret, key, hash_size ); DBGC ( tls, "TLS %p RX MAC secret:\n", tls ); DBGC_HD ( tls, key, hash_size ); key += hash_size; /* TX key */ if ( ( rc = cipher_setkey ( tx_cipherspec->suite->cipher, tx_cipherspec->cipher_ctx, key, key_size ) ) != 0 ) { DBGC ( tls, "TLS %p could not set TX key: %s\n", tls, strerror ( rc ) ); return rc; } DBGC ( tls, "TLS %p TX key:\n", tls ); DBGC_HD ( tls, key, key_size ); key += key_size; /* RX key */ if ( ( rc = cipher_setkey ( rx_cipherspec->suite->cipher, rx_cipherspec->cipher_ctx, key, key_size ) ) != 0 ) { DBGC ( tls, "TLS %p could not set TX key: %s\n", tls, strerror ( rc ) ); return rc; } DBGC ( tls, "TLS %p RX key:\n", tls ); DBGC_HD ( tls, key, key_size ); key += key_size; /* TX initialisation vector */ cipher_setiv ( tx_cipherspec->suite->cipher, tx_cipherspec->cipher_ctx, key ); DBGC ( tls, "TLS %p TX IV:\n", tls ); DBGC_HD ( tls, key, iv_size ); key += iv_size; /* RX initialisation vector */ cipher_setiv ( rx_cipherspec->suite->cipher, rx_cipherspec->cipher_ctx, key ); DBGC ( tls, "TLS %p RX IV:\n", tls ); DBGC_HD ( tls, key, iv_size ); key += iv_size; assert ( ( key_block + total ) == key ); return 0; } /****************************************************************************** * * Cipher suite management * ****************************************************************************** */ /** Null cipher suite */ struct tls_cipher_suite tls_cipher_suite_null = { .pubkey = &pubkey_null, .cipher = &cipher_null, .digest = &digest_null, }; /** Number of supported cipher suites */ #define TLS_NUM_CIPHER_SUITES table_num_entries ( TLS_CIPHER_SUITES ) /** * Identify cipher suite * * @v cipher_suite Cipher suite specification * @ret suite Cipher suite, or NULL */ static struct tls_cipher_suite * tls_find_cipher_suite ( unsigned int cipher_suite ) { struct tls_cipher_suite *suite; /* Identify cipher suite */ for_each_table_entry ( suite, TLS_CIPHER_SUITES ) { if ( suite->code == cipher_suite ) return suite; } return NULL; } /** * Clear cipher suite * * @v cipherspec TLS cipher specification */ static void tls_clear_cipher ( struct tls_connection *tls __unused, struct tls_cipherspec *cipherspec ) { if ( cipherspec->suite ) { pubkey_final ( cipherspec->suite->pubkey, cipherspec->pubkey_ctx ); } free ( cipherspec->dynamic ); memset ( cipherspec, 0, sizeof ( *cipherspec ) ); cipherspec->suite = &tls_cipher_suite_null; } /** * Set cipher suite * * @v tls TLS connection * @v cipherspec TLS cipher specification * @v suite Cipher suite * @ret rc Return status code */ static int tls_set_cipher ( struct tls_connection *tls, struct tls_cipherspec *cipherspec, struct tls_cipher_suite *suite ) { struct pubkey_algorithm *pubkey = suite->pubkey; struct cipher_algorithm *cipher = suite->cipher; struct digest_algorithm *digest = suite->digest; size_t total; void *dynamic; /* Clear out old cipher contents, if any */ tls_clear_cipher ( tls, cipherspec ); /* Allocate dynamic storage */ total = ( pubkey->ctxsize + 2 * cipher->ctxsize + digest->digestsize ); dynamic = zalloc ( total ); if ( ! dynamic ) { DBGC ( tls, "TLS %p could not allocate %zd bytes for crypto " "context\n", tls, total ); return -ENOMEM_CONTEXT; } /* Assign storage */ cipherspec->dynamic = dynamic; cipherspec->pubkey_ctx = dynamic; dynamic += pubkey->ctxsize; cipherspec->cipher_ctx = dynamic; dynamic += cipher->ctxsize; cipherspec->cipher_next_ctx = dynamic; dynamic += cipher->ctxsize; cipherspec->mac_secret = dynamic; dynamic += digest->digestsize; assert ( ( cipherspec->dynamic + total ) == dynamic ); /* Store parameters */ cipherspec->suite = suite; return 0; } /** * Select next cipher suite * * @v tls TLS connection * @v cipher_suite Cipher suite specification * @ret rc Return status code */ static int tls_select_cipher ( struct tls_connection *tls, unsigned int cipher_suite ) { struct tls_cipher_suite *suite; int rc; /* Identify cipher suite */ suite = tls_find_cipher_suite ( cipher_suite ); if ( ! suite ) { DBGC ( tls, "TLS %p does not support cipher %04x\n", tls, ntohs ( cipher_suite ) ); return -ENOTSUP_CIPHER; } /* Set ciphers */ if ( ( rc = tls_set_cipher ( tls, &tls->tx_cipherspec_pending, suite ) ) != 0 ) return rc; if ( ( rc = tls_set_cipher ( tls, &tls->rx_cipherspec_pending, suite ) ) != 0 ) return rc; DBGC ( tls, "TLS %p selected %s-%s-%d-%s\n", tls, suite->pubkey->name, suite->cipher->name, ( suite->key_len * 8 ), suite->digest->name ); return 0; } /** * Activate next cipher suite * * @v tls TLS connection * @v pending Pending cipher specification * @v active Active cipher specification to replace * @ret rc Return status code */ static int tls_change_cipher ( struct tls_connection *tls, struct tls_cipherspec *pending, struct tls_cipherspec *active ) { /* Sanity check */ if ( pending->suite == &tls_cipher_suite_null ) { DBGC ( tls, "TLS %p refusing to use null cipher\n", tls ); return -ENOTSUP_NULL; } tls_clear_cipher ( tls, active ); memswap ( active, pending, sizeof ( *active ) ); return 0; } /****************************************************************************** * * Signature and hash algorithms * ****************************************************************************** */ /** Number of supported signature and hash algorithms */ #define TLS_NUM_SIG_HASH_ALGORITHMS \ table_num_entries ( TLS_SIG_HASH_ALGORITHMS ) /** * Find TLS signature and hash algorithm * * @v pubkey Public-key algorithm * @v digest Digest algorithm * @ret sig_hash Signature and hash algorithm, or NULL */ static struct tls_signature_hash_algorithm * tls_signature_hash_algorithm ( struct pubkey_algorithm *pubkey, struct digest_algorithm *digest ) { struct tls_signature_hash_algorithm *sig_hash; /* Identify signature and hash algorithm */ for_each_table_entry ( sig_hash, TLS_SIG_HASH_ALGORITHMS ) { if ( ( sig_hash->pubkey == pubkey ) && ( sig_hash->digest == digest ) ) { return sig_hash; } } return NULL; } /****************************************************************************** * * Handshake verification * ****************************************************************************** */ /** * Add handshake record to verification hash * * @v tls TLS connection * @v data Handshake record * @v len Length of handshake record */ static void tls_add_handshake ( struct tls_connection *tls, const void *data, size_t len ) { digest_update ( &md5_sha1_algorithm, tls->handshake_md5_sha1_ctx, data, len ); digest_update ( &sha256_algorithm, tls->handshake_sha256_ctx, data, len ); } /** * Calculate handshake verification hash * * @v tls TLS connection * @v out Output buffer * * Calculates the MD5+SHA1 or SHA256 digest over all handshake * messages seen so far. */ static void tls_verify_handshake ( struct tls_connection *tls, void *out ) { struct digest_algorithm *digest = tls->handshake_digest; uint8_t ctx[ digest->ctxsize ]; memcpy ( ctx, tls->handshake_ctx, sizeof ( ctx ) ); digest_final ( digest, ctx, out ); } /****************************************************************************** * * Record handling * ****************************************************************************** */ /** * Resume TX state machine * * @v tls TLS connection */ static void tls_tx_resume ( struct tls_connection *tls ) { process_add ( &tls->process ); } /** * Resume TX state machine for all connections within a session * * @v session TLS session */ static void tls_tx_resume_all ( struct tls_session *session ) { struct tls_connection *tls; list_for_each_entry ( tls, &session->conn, list ) tls_tx_resume ( tls ); } /** * Restart negotiation * * @v tls TLS connection */ static void tls_restart ( struct tls_connection *tls ) { /* Sanity check */ assert ( ! tls->tx_pending ); assert ( ! is_pending ( &tls->client_negotiation ) ); assert ( ! is_pending ( &tls->server_negotiation ) ); assert ( ! is_pending ( &tls->validation ) ); /* (Re)initialise handshake context */ digest_init ( &md5_sha1_algorithm, tls->handshake_md5_sha1_ctx ); digest_init ( &sha256_algorithm, tls->handshake_sha256_ctx ); tls->handshake_digest = &sha256_algorithm; tls->handshake_ctx = tls->handshake_sha256_ctx; /* (Re)start negotiation */ tls->tx_pending = TLS_TX_CLIENT_HELLO; tls_tx_resume ( tls ); pending_get ( &tls->client_negotiation ); pending_get ( &tls->server_negotiation ); } /** * Transmit Handshake record * * @v tls TLS connection * @v data Plaintext record * @v len Length of plaintext record * @ret rc Return status code */ static int tls_send_handshake ( struct tls_connection *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 connection * @ret rc Return status code */ static int tls_send_client_hello ( struct tls_connection *tls ) { struct tls_session *session = tls->session; size_t name_len = strlen ( session->name ); struct { uint32_t type_length; uint16_t version; uint8_t random[32]; uint8_t session_id_len; uint8_t session_id[tls->session_id_len]; uint16_t cipher_suite_len; uint16_t cipher_suites[TLS_NUM_CIPHER_SUITES]; uint8_t compression_methods_len; uint8_t compression_methods[1]; uint16_t extensions_len; struct { uint16_t server_name_type; uint16_t server_name_len; struct { uint16_t len; struct { uint8_t type; uint16_t len; uint8_t name[name_len]; } __attribute__ (( packed )) list[1]; } __attribute__ (( packed )) server_name; uint16_t max_fragment_length_type; uint16_t max_fragment_length_len; struct { uint8_t max; } __attribute__ (( packed )) max_fragment_length; uint16_t signature_algorithms_type; uint16_t signature_algorithms_len; struct { uint16_t len; struct tls_signature_hash_id code[TLS_NUM_SIG_HASH_ALGORITHMS]; } __attribute__ (( packed )) signature_algorithms; uint16_t renegotiation_info_type; uint16_t renegotiation_info_len; struct { uint8_t len; uint8_t data[ tls->secure_renegotiation ? sizeof ( tls->verify.client ) :0]; } __attribute__ (( packed )) renegotiation_info; uint16_t session_ticket_type; uint16_t session_ticket_len; struct { uint8_t data[session->ticket_len]; } __attribute__ (( packed )) session_ticket; } __attribute__ (( packed )) extensions; } __attribute__ (( packed )) hello; struct tls_cipher_suite *suite; struct tls_signature_hash_algorithm *sighash; unsigned int i; /* Construct record */ memset ( &hello, 0, sizeof ( hello ) ); hello.type_length = ( cpu_to_le32 ( TLS_CLIENT_HELLO ) | htonl ( sizeof ( hello ) - sizeof ( hello.type_length ) ) ); hello.version = htons ( tls->version ); memcpy ( &hello.random, &tls->client_random, sizeof ( hello.random ) ); hello.session_id_len = tls->session_id_len; memcpy ( hello.session_id, tls->session_id, sizeof ( hello.session_id ) ); hello.cipher_suite_len = htons ( sizeof ( hello.cipher_suites ) ); i = 0 ; for_each_table_entry ( suite, TLS_CIPHER_SUITES ) hello.cipher_suites[i++] = suite->code; hello.compression_methods_len = sizeof ( hello.compression_methods ); hello.extensions_len = htons ( sizeof ( hello.extensions ) ); hello.extensions.server_name_type = htons ( TLS_SERVER_NAME ); hello.extensions.server_name_len = htons ( sizeof ( hello.extensions.server_name ) ); hello.extensions.server_name.len = htons ( sizeof ( hello.extensions.server_name.list ) ); hello.extensions.server_name.list[0].type = TLS_SERVER_NAME_HOST_NAME; hello.extensions.server_name.list[0].len = htons ( sizeof ( hello.extensions.server_name.list[0].name )); memcpy ( hello.extensions.server_name.list[0].name, session->name, sizeof ( hello.extensions.server_name.list[0].name ) ); hello.extensions.max_fragment_length_type = htons ( TLS_MAX_FRAGMENT_LENGTH ); hello.extensions.max_fragment_length_len = htons ( sizeof ( hello.extensions.max_fragment_length ) ); hello.extensions.max_fragment_length.max = TLS_MAX_FRAGMENT_LENGTH_4096; hello.extensions.signature_algorithms_type = htons ( TLS_SIGNATURE_ALGORITHMS ); hello.extensions.signature_algorithms_len = htons ( sizeof ( hello.extensions.signature_algorithms ) ); hello.extensions.signature_algorithms.len = htons ( sizeof ( hello.extensions.signature_algorithms.code)); i = 0 ; for_each_table_entry ( sighash, TLS_SIG_HASH_ALGORITHMS ) hello.extensions.signature_algorithms.code[i++] = sighash->code; hello.extensions.renegotiation_info_type = htons ( TLS_RENEGOTIATION_INFO ); hello.extensions.renegotiation_info_len = htons ( sizeof ( hello.extensions.renegotiation_info ) ); hello.extensions.renegotiation_info.len = sizeof ( hello.extensions.renegotiation_info.data ); memcpy ( hello.extensions.renegotiation_info.data, tls->verify.client, sizeof ( hello.extensions.renegotiation_info.data ) ); hello.extensions.session_ticket_type = htons ( TLS_SESSION_TICKET ); hello.extensions.session_ticket_len = htons ( sizeof ( hello.extensions.session_ticket ) ); memcpy ( hello.extensions.session_ticket.data, session->ticket, sizeof ( hello.extensions.session_ticket.data ) ); return tls_send_handshake ( tls, &hello, sizeof ( hello ) ); } /** * Transmit Certificate record * * @v tls TLS connection * @ret rc Return status code */ static int tls_send_certificate ( struct tls_connection *tls ) { struct { tls24_t length; uint8_t data[0]; } __attribute__ (( packed )) *certificate; struct { uint32_t type_length; tls24_t length; typeof ( *certificate ) certificates[0]; } __attribute__ (( packed )) *certificates; struct x509_link *link; struct x509_certificate *cert; size_t len; int rc; /* Calculate length of client certificates */ len = 0; list_for_each_entry ( link, &tls->certs->links, list ) { cert = link->cert; len += ( sizeof ( *certificate ) + cert->raw.len ); DBGC ( tls, "TLS %p sending client certificate %s\n", tls, x509_name ( cert ) ); } /* Allocate storage for Certificate record (which may be too * large for the stack). */ certificates = zalloc ( sizeof ( *certificates ) + len ); if ( ! certificates ) return -ENOMEM_CERTIFICATE; /* Populate record */ certificates->type_length = ( cpu_to_le32 ( TLS_CERTIFICATE ) | htonl ( sizeof ( *certificates ) + len - sizeof ( certificates->type_length ) ) ); tls_set_uint24 ( &certificates->length, len ); certificate = &certificates->certificates[0]; list_for_each_entry ( link, &tls->certs->links, list ) { cert = link->cert; tls_set_uint24 ( &certificate->length, cert->raw.len ); memcpy ( certificate->data, cert->raw.data, cert->raw.len ); certificate = ( ( ( void * ) certificate->data ) + cert->raw.len ); } /* Transmit record */ rc = tls_send_handshake ( tls, certificates, ( sizeof ( *certificates ) + len ) ); /* Free record */ free ( certificates ); return rc; } /** * Transmit Client Key Exchange record * * @v tls TLS connection * @ret rc Return status code */ static int tls_send_client_key_exchange ( struct tls_connection *tls ) { struct tls_cipherspec *cipherspec = &tls->tx_cipherspec_pending; struct pubkey_algorithm *pubkey = cipherspec->suite->pubkey; size_t max_len = pubkey_max_len ( pubkey, cipherspec->pubkey_ctx ); struct { uint32_t type_length; uint16_t encrypted_pre_master_secret_len; uint8_t encrypted_pre_master_secret[max_len]; } __attribute__ (( packed )) key_xchg; size_t unused; int len; int rc; /* Encrypt pre-master secret using server's public key */ memset ( &key_xchg, 0, sizeof ( key_xchg ) ); len = pubkey_encrypt ( pubkey, cipherspec->pubkey_ctx, &tls->pre_master_secret, sizeof ( tls->pre_master_secret ), key_xchg.encrypted_pre_master_secret ); if ( len < 0 ) { rc = len; DBGC ( tls, "TLS %p could not encrypt pre-master secret: %s\n", tls, strerror ( rc ) ); return rc; } unused = ( max_len - len ); key_xchg.type_length = ( cpu_to_le32 ( TLS_CLIENT_KEY_EXCHANGE ) | htonl ( sizeof ( key_xchg ) - sizeof ( key_xchg.type_length ) - unused ) ); key_xchg.encrypted_pre_master_secret_len = htons ( sizeof ( key_xchg.encrypted_pre_master_secret ) - unused ); return tls_send_handshake ( tls, &key_xchg, ( sizeof ( key_xchg ) - unused ) ); } /** * Transmit Certificate Verify record * * @v tls TLS connection * @ret rc Return status code */ static int tls_send_certificate_verify ( struct tls_connection *tls ) { struct digest_algorithm *digest = tls->handshake_digest; struct x509_certificate *cert = x509_first ( tls->certs ); struct pubkey_algorithm *pubkey = cert->signature_algorithm->pubkey; struct asn1_cursor *key = privkey_cursor ( tls->key ); uint8_t digest_out[ digest->digestsize ]; uint8_t ctx[ pubkey->ctxsize ]; struct tls_signature_hash_algorithm *sig_hash = NULL; int rc; /* Generate digest to be signed */ tls_verify_handshake ( tls, digest_out ); /* Initialise public-key algorithm */ if ( ( rc = pubkey_init ( pubkey, ctx, key->data, key->len ) ) != 0 ) { DBGC ( tls, "TLS %p could not initialise %s client private " "key: %s\n", tls, pubkey->name, strerror ( rc ) ); goto err_pubkey_init; } /* TLSv1.2 and later use explicit algorithm identifiers */ if ( tls_version ( tls, TLS_VERSION_TLS_1_2 ) ) { sig_hash = tls_signature_hash_algorithm ( pubkey, digest ); if ( ! sig_hash ) { DBGC ( tls, "TLS %p could not identify (%s,%s) " "signature and hash algorithm\n", tls, pubkey->name, digest->name ); rc = -ENOTSUP_SIG_HASH; goto err_sig_hash; } } /* Generate and transmit record */ { size_t max_len = pubkey_max_len ( pubkey, ctx ); int use_sig_hash = ( ( sig_hash == NULL ) ? 0 : 1 ); struct { uint32_t type_length; struct tls_signature_hash_id sig_hash[use_sig_hash]; uint16_t signature_len; uint8_t signature[max_len]; } __attribute__ (( packed )) certificate_verify; size_t unused; int len; /* Sign digest */ len = pubkey_sign ( pubkey, ctx, digest, digest_out, certificate_verify.signature ); if ( len < 0 ) { rc = len; DBGC ( tls, "TLS %p could not sign %s digest using %s " "client private key: %s\n", tls, digest->name, pubkey->name, strerror ( rc ) ); goto err_pubkey_sign; } unused = ( max_len - len ); /* Construct Certificate Verify record */ certificate_verify.type_length = ( cpu_to_le32 ( TLS_CERTIFICATE_VERIFY ) | htonl ( sizeof ( certificate_verify ) - sizeof ( certificate_verify.type_length ) - unused ) ); if ( use_sig_hash ) { memcpy ( &certificate_verify.sig_hash[0], &sig_hash->code, sizeof ( certificate_verify.sig_hash[0] ) ); } certificate_verify.signature_len = htons ( sizeof ( certificate_verify.signature ) - unused ); /* Transmit record */ rc = tls_send_handshake ( tls, &certificate_verify, ( sizeof ( certificate_verify ) - unused ) ); } err_pubkey_sign: err_sig_hash: pubkey_final ( pubkey, ctx ); err_pubkey_init: return rc; } /** * Transmit Change Cipher record * * @v tls TLS connection * @ret rc Return status code */ static int tls_send_change_cipher ( struct tls_connection *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 connection * @ret rc Return status code */ static int tls_send_finished ( struct tls_connection *tls ) { struct digest_algorithm *digest = tls->handshake_digest; struct { uint32_t type_length; uint8_t verify_data[ sizeof ( tls->verify.client ) ]; } __attribute__ (( packed )) finished; uint8_t digest_out[ digest->digestsize ]; int rc; /* Construct client verification data */ tls_verify_handshake ( tls, digest_out ); tls_prf_label ( tls, &tls->master_secret, sizeof ( tls->master_secret ), tls->verify.client, sizeof ( tls->verify.client ), "client finished", digest_out, sizeof ( digest_out ) ); /* Construct record */ memset ( &finished, 0, sizeof ( finished ) ); finished.type_length = ( cpu_to_le32 ( TLS_FINISHED ) | htonl ( sizeof ( finished ) - sizeof ( finished.type_length ) ) ); memcpy ( finished.verify_data, tls->verify.client, sizeof ( finished.verify_data ) ); /* Transmit record */ if ( ( rc = tls_send_handshake ( tls, &finished, sizeof ( finished ) ) ) != 0 ) return rc; /* Mark client as finished */ pending_put ( &tls->client_negotiation ); return 0; } /** * Receive new Change Cipher record * * @v tls TLS connection * @v data Plaintext record * @v len Length of plaintext record * @ret rc Return status code */ static int tls_new_change_cipher ( struct tls_connection *tls, const void *data, size_t len ) { int rc; if ( ( len != 1 ) || ( *( ( uint8_t * ) data ) != 1 ) ) { DBGC ( tls, "TLS %p received invalid Change Cipher\n", tls ); DBGC_HD ( tls, data, len ); return -EINVAL_CHANGE_CIPHER; } if ( ( rc = tls_change_cipher ( tls, &tls->rx_cipherspec_pending, &tls->rx_cipherspec ) ) != 0 ) { DBGC ( tls, "TLS %p could not activate RX cipher: %s\n", tls, strerror ( rc ) ); return rc; } tls->rx_seq = ~( ( uint64_t ) 0 ); return 0; } /** * Receive new Alert record * * @v tls TLS connection * @v data Plaintext record * @v len Length of plaintext record * @ret rc Return status code */ static int tls_new_alert ( struct tls_connection *tls, const void *data, size_t len ) { const struct { uint8_t level; uint8_t description; char next[0]; } __attribute__ (( packed )) *alert = data; /* Sanity check */ if ( sizeof ( *alert ) != len ) { DBGC ( tls, "TLS %p received overlength Alert\n", tls ); DBGC_HD ( tls, data, len ); return -EINVAL_ALERT; } switch ( alert->level ) { case TLS_ALERT_WARNING: DBGC ( tls, "TLS %p received warning alert %d\n", tls, alert->description ); return 0; case TLS_ALERT_FATAL: DBGC ( tls, "TLS %p received fatal alert %d\n", tls, alert->description ); return -EPERM_ALERT; default: DBGC ( tls, "TLS %p received unknown alert level %d" "(alert %d)\n", tls, alert->level, alert->description ); return -EIO_ALERT; } } /** * Receive new Hello Request handshake record * * @v tls TLS connection * @v data Plaintext handshake record * @v len Length of plaintext handshake record * @ret rc Return status code */ static int tls_new_hello_request ( struct tls_connection *tls, const void *data __unused, size_t len __unused ) { /* Ignore if a handshake is in progress */ if ( ! tls_ready ( tls ) ) { DBGC ( tls, "TLS %p ignoring Hello Request\n", tls ); return 0; } /* Fail unless server supports secure renegotiation */ if ( ! tls->secure_renegotiation ) { DBGC ( tls, "TLS %p refusing to renegotiate insecurely\n", tls ); return -EPERM_RENEG_INSECURE; } /* Restart negotiation */ tls_restart ( tls ); return 0; } /** * Receive new Server Hello handshake record * * @v tls TLS connection * @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_connection *tls, const void *data, size_t len ) { const struct { uint16_t version; uint8_t random[32]; uint8_t session_id_len; uint8_t session_id[0]; } __attribute__ (( packed )) *hello_a = data; const uint8_t *session_id; const struct { uint16_t cipher_suite; uint8_t compression_method; char next[0]; } __attribute__ (( packed )) *hello_b; const struct { uint16_t len; uint8_t data[0]; } __attribute__ (( packed )) *exts; const struct { uint16_t type; uint16_t len; uint8_t data[0]; } __attribute__ (( packed )) *ext; const struct { uint8_t len; uint8_t data[0]; } __attribute__ (( packed )) *reneg = NULL; uint16_t version; size_t exts_len; size_t ext_len; size_t remaining; int rc; /* Parse header */ if ( ( sizeof ( *hello_a ) > len ) || ( hello_a->session_id_len > ( len - sizeof ( *hello_a ) ) ) || ( sizeof ( *hello_b ) > ( len - sizeof ( *hello_a ) - hello_a->session_id_len ) ) ) { DBGC ( tls, "TLS %p received underlength Server Hello\n", tls ); DBGC_HD ( tls, data, len ); return -EINVAL_HELLO; } session_id = hello_a->session_id; hello_b = ( ( void * ) ( session_id + hello_a->session_id_len ) ); /* Parse extensions, if present */ remaining = ( len - sizeof ( *hello_a ) - hello_a->session_id_len - sizeof ( *hello_b ) ); if ( remaining ) { /* Parse extensions length */ exts = ( ( void * ) hello_b->next ); if ( ( sizeof ( *exts ) > remaining ) || ( ( exts_len = ntohs ( exts->len ) ) > ( remaining - sizeof ( *exts ) ) ) ) { DBGC ( tls, "TLS %p received underlength extensions\n", tls ); DBGC_HD ( tls, data, len ); return -EINVAL_HELLO; } /* Parse extensions */ for ( ext = ( ( void * ) exts->data ), remaining = exts_len ; remaining ; ext = ( ( ( void * ) ext ) + sizeof ( *ext ) + ext_len ), remaining -= ( sizeof ( *ext ) + ext_len ) ) { /* Parse extension length */ if ( ( sizeof ( *ext ) > remaining ) || ( ( ext_len = ntohs ( ext->len ) ) > ( remaining - sizeof ( *ext ) ) ) ) { DBGC ( tls, "TLS %p received underlength " "extension\n", tls ); DBGC_HD ( tls, data, len ); return -EINVAL_HELLO; } /* Record known extensions */ switch ( ext->type ) { case htons ( TLS_RENEGOTIATION_INFO ) : reneg = ( ( void * ) ext->data ); if ( ( sizeof ( *reneg ) > ext_len ) || ( reneg->len > ( ext_len - sizeof ( *reneg ) ) ) ) { DBGC ( tls, "TLS %p received " "underlength renegotiation " "info\n", tls ); DBGC_HD ( tls, data, len ); return -EINVAL_HELLO; } break; } } } /* Check and store protocol version */ version = ntohs ( hello_a->version ); if ( version < TLS_VERSION_MIN ) { DBGC ( tls, "TLS %p does not support protocol version %d.%d\n", tls, ( version >> 8 ), ( version & 0xff ) ); return -ENOTSUP_VERSION; } if ( version > tls->version ) { DBGC ( tls, "TLS %p server attempted to illegally upgrade to " "protocol version %d.%d\n", tls, ( version >> 8 ), ( version & 0xff ) ); return -EPROTO_VERSION; } tls->version = version; DBGC ( tls, "TLS %p using protocol version %d.%d\n", tls, ( version >> 8 ), ( version & 0xff ) ); /* Use MD5+SHA1 digest algorithm for handshake verification * for versions earlier than TLSv1.2. */ if ( ! tls_version ( tls, TLS_VERSION_TLS_1_2 ) ) { tls->handshake_digest = &md5_sha1_algorithm; tls->handshake_ctx = tls->handshake_md5_sha1_ctx; } /* Copy out server random bytes */ memcpy ( &tls->server_random, &hello_a->random, sizeof ( tls->server_random ) ); /* Select cipher suite */ if ( ( rc = tls_select_cipher ( tls, hello_b->cipher_suite ) ) != 0 ) return rc; /* Reuse or generate master secret */ if ( hello_a->session_id_len && ( hello_a->session_id_len == tls->session_id_len ) && ( memcmp ( session_id, tls->session_id, tls->session_id_len ) == 0 ) ) { /* Session ID match: reuse master secret */ DBGC ( tls, "TLS %p resuming session ID:\n", tls ); DBGC_HDA ( tls, 0, tls->session_id, tls->session_id_len ); } else { /* Generate new master secret */ tls_generate_master_secret ( tls ); /* Record new session ID, if present */ if ( hello_a->session_id_len && ( hello_a->session_id_len <= sizeof ( tls->session_id ))){ tls->session_id_len = hello_a->session_id_len; memcpy ( tls->session_id, session_id, tls->session_id_len ); DBGC ( tls, "TLS %p new session ID:\n", tls ); DBGC_HDA ( tls, 0, tls->session_id, tls->session_id_len ); } } /* Generate keys */ if ( ( rc = tls_generate_keys ( tls ) ) != 0 ) return rc; /* Handle secure renegotiation */ if ( tls->secure_renegotiation ) { /* Secure renegotiation is expected; verify data */ if ( ( reneg == NULL ) || ( reneg->len != sizeof ( tls->verify ) ) || ( memcmp ( reneg->data, &tls->verify, sizeof ( tls->verify ) ) != 0 ) ) { DBGC ( tls, "TLS %p server failed secure " "renegotiation\n", tls ); return -EPERM_RENEG_VERIFY; } } else if ( reneg != NULL ) { /* Secure renegotiation is being enabled */ if ( reneg->len != 0 ) { DBGC ( tls, "TLS %p server provided non-empty initial " "renegotiation\n", tls ); return -EPERM_RENEG_VERIFY; } tls->secure_renegotiation = 1; } return 0; } /** * Receive New Session Ticket handshake record * * @v tls TLS connection * @v data Plaintext handshake record * @v len Length of plaintext handshake record * @ret rc Return status code */ static int tls_new_session_ticket ( struct tls_connection *tls, const void *data, size_t len ) { const struct { uint32_t lifetime; uint16_t len; uint8_t ticket[0]; } __attribute__ (( packed )) *new_session_ticket = data; size_t ticket_len; /* Parse header */ if ( sizeof ( *new_session_ticket ) > len ) { DBGC ( tls, "TLS %p received underlength New Session Ticket\n", tls ); DBGC_HD ( tls, data, len ); return -EINVAL_TICKET; } ticket_len = ntohs ( new_session_ticket->len ); if ( ticket_len > ( len - sizeof ( *new_session_ticket ) ) ) { DBGC ( tls, "TLS %p received overlength New Session Ticket\n", tls ); DBGC_HD ( tls, data, len ); return -EINVAL_TICKET; } /* Free any unapplied new session ticket */ free ( tls->new_session_ticket ); tls->new_session_ticket = NULL; tls->new_session_ticket_len = 0; /* Record ticket */ tls->new_session_ticket = malloc ( ticket_len ); if ( ! tls->new_session_ticket ) return -ENOMEM; memcpy ( tls->new_session_ticket, new_session_ticket->ticket, ticket_len ); tls->new_session_ticket_len = ticket_len; DBGC ( tls, "TLS %p new session ticket:\n", tls ); DBGC_HDA ( tls, 0, tls->new_session_ticket, tls->new_session_ticket_len ); return 0; } /** * Parse certificate chain * * @v tls TLS connection * @v data Certificate chain * @v len Length of certificate chain * @ret rc Return status code */ static int tls_parse_chain ( struct tls_connection *tls, const void *data, size_t len ) { size_t remaining = len; int rc; /* Free any existing certificate chain */ x509_chain_put ( tls->chain ); tls->chain = NULL; /* Create certificate chain */ tls->chain = x509_alloc_chain(); if ( ! tls->chain ) { rc = -ENOMEM_CHAIN; goto err_alloc_chain; } /* Add certificates to chain */ while ( remaining ) { const struct { tls24_t length; uint8_t data[0]; } __attribute__ (( packed )) *certificate = data; size_t certificate_len; size_t record_len; struct x509_certificate *cert; /* Parse header */ if ( sizeof ( *certificate ) > remaining ) { DBGC ( tls, "TLS %p underlength certificate:\n", tls ); DBGC_HDA ( tls, 0, data, remaining ); rc = -EINVAL_CERTIFICATE; goto err_underlength; } certificate_len = tls_uint24 ( &certificate->length ); if ( certificate_len > ( remaining - sizeof ( *certificate ) )){ DBGC ( tls, "TLS %p overlength certificate:\n", tls ); DBGC_HDA ( tls, 0, data, remaining ); rc = -EINVAL_CERTIFICATE; goto err_overlength; } record_len = ( sizeof ( *certificate ) + certificate_len ); /* Add certificate to chain */ if ( ( rc = x509_append_raw ( tls->chain, certificate->data, certificate_len ) ) != 0 ) { DBGC ( tls, "TLS %p could not append certificate: %s\n", tls, strerror ( rc ) ); DBGC_HDA ( tls, 0, data, remaining ); goto err_parse; } cert = x509_last ( tls->chain ); DBGC ( tls, "TLS %p found certificate %s\n", tls, x509_name ( cert ) ); /* Move to next certificate in list */ data += record_len; remaining -= record_len; } return 0; err_parse: err_overlength: err_underlength: x509_chain_put ( tls->chain ); tls->chain = NULL; err_alloc_chain: return rc; } /** * Receive new Certificate handshake record * * @v tls TLS connection * @v data Plaintext handshake record * @v len Length of plaintext handshake record * @ret rc Return status code */ static int tls_new_certificate ( struct tls_connection *tls, const void *data, size_t len ) { const struct { tls24_t length; uint8_t certificates[0]; } __attribute__ (( packed )) *certificate = data; size_t certificates_len; int rc; /* Parse header */ if ( sizeof ( *certificate ) > len ) { DBGC ( tls, "TLS %p received underlength Server Certificate\n", tls ); DBGC_HD ( tls, data, len ); return -EINVAL_CERTIFICATES; } certificates_len = tls_uint24 ( &certificate->length ); if ( certificates_len > ( len - sizeof ( *certificate ) ) ) { DBGC ( tls, "TLS %p received overlength Server Certificate\n", tls ); DBGC_HD ( tls, data, len ); return -EINVAL_CERTIFICATES; } /* Parse certificate chain */ if ( ( rc = tls_parse_chain ( tls, certificate->certificates, certificates_len ) ) != 0 ) return rc; return 0; } /** * Receive new Certificate Request handshake record * * @v tls TLS connection * @v data Plaintext handshake record * @v len Length of plaintext handshake record * @ret rc Return status code */ static int tls_new_certificate_request ( struct tls_connection *tls, const void *data __unused, size_t len __unused ) { struct x509_certificate *cert; int rc; /* We can only send a single certificate, so there is no point * in parsing the Certificate Request. */ /* Free any existing client certificate chain */ x509_chain_put ( tls->certs ); tls->certs = NULL; /* Determine client certificate to be sent */ cert = certstore_find_key ( tls->key ); if ( ! cert ) { DBGC ( tls, "TLS %p could not find certificate corresponding " "to private key\n", tls ); rc = -EPERM_CLIENT_CERT; goto err_find; } x509_get ( cert ); DBGC ( tls, "TLS %p selected client certificate %s\n", tls, x509_name ( cert ) ); /* Create client certificate chain */ tls->certs = x509_alloc_chain(); if ( ! tls->certs ) { rc = -ENOMEM; goto err_alloc; } /* Append client certificate to chain */ if ( ( rc = x509_append ( tls->certs, cert ) ) != 0 ) goto err_append; /* Append any relevant issuer certificates */ if ( ( rc = x509_auto_append ( tls->certs, &certstore ) ) != 0 ) goto err_auto_append; /* Drop local reference to client certificate */ x509_put ( cert ); return 0; err_auto_append: err_append: x509_chain_put ( tls->certs ); tls->certs = NULL; err_alloc: x509_put ( cert ); err_find: return rc; } /** * Receive new Server Hello Done handshake record * * @v tls TLS connection * @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_connection *tls, const void *data, size_t len ) { const struct { char next[0]; } __attribute__ (( packed )) *hello_done = data; int rc; /* Sanity check */ if ( sizeof ( *hello_done ) != len ) { DBGC ( tls, "TLS %p received overlength Server Hello Done\n", tls ); DBGC_HD ( tls, data, len ); return -EINVAL_HELLO_DONE; } /* Begin certificate validation */ if ( ( rc = create_validator ( &tls->validator, tls->chain, tls->root ) ) != 0 ) { DBGC ( tls, "TLS %p could not start certificate validation: " "%s\n", tls, strerror ( rc ) ); return rc; } pending_get ( &tls->validation ); return 0; } /** * Receive new Finished handshake record * * @v tls TLS connection * @v data Plaintext handshake record * @v len Length of plaintext handshake record * @ret rc Return status code */ static int tls_new_finished ( struct tls_connection *tls, const void *data, size_t len ) { struct tls_session *session = tls->session; struct digest_algorithm *digest = tls->handshake_digest; const struct { uint8_t verify_data[ sizeof ( tls->verify.server ) ]; char next[0]; } __attribute__ (( packed )) *finished = data; uint8_t digest_out[ digest->digestsize ]; /* Sanity check */ if ( sizeof ( *finished ) != len ) { DBGC ( tls, "TLS %p received overlength Finished\n", tls ); DBGC_HD ( tls, data, len ); return -EINVAL_FINISHED; } /* Verify data */ tls_verify_handshake ( tls, digest_out ); tls_prf_label ( tls, &tls->master_secret, sizeof ( tls->master_secret ), tls->verify.server, sizeof ( tls->verify.server ), "server finished", digest_out, sizeof ( digest_out ) ); if ( memcmp ( tls->verify.server, finished->verify_data, sizeof ( tls->verify.server ) ) != 0 ) { DBGC ( tls, "TLS %p verification failed\n", tls ); return -EPERM_VERIFY; } /* Mark server as finished */ pending_put ( &tls->server_negotiation ); /* If we are resuming a session (i.e. if the server Finished * arrives before the client Finished is sent), then schedule * transmission of Change Cipher and Finished. */ if ( is_pending ( &tls->client_negotiation ) ) { tls->tx_pending |= ( TLS_TX_CHANGE_CIPHER | TLS_TX_FINISHED ); tls_tx_resume ( tls ); } /* Record session ID, ticket, and master secret, if applicable */ if ( tls->session_id_len || tls->new_session_ticket_len ) { memcpy ( session->master_secret, tls->master_secret, sizeof ( session->master_secret ) ); } if ( tls->session_id_len ) { session->id_len = tls->session_id_len; memcpy ( session->id, tls->session_id, sizeof ( session->id ) ); } if ( tls->new_session_ticket_len ) { free ( session->ticket ); session->ticket = tls->new_session_ticket; session->ticket_len = tls->new_session_ticket_len; tls->new_session_ticket = NULL; tls->new_session_ticket_len = 0; } /* Move to end of session's connection list and allow other * connections to start making progress. */ list_del ( &tls->list ); list_add_tail ( &tls->list, &session->conn ); tls_tx_resume_all ( session ); /* Send notification of a window change */ xfer_window_changed ( &tls->plainstream ); return 0; } /** * Receive new Handshake record * * @v tls TLS connection * @v data Plaintext record * @v len Length of plaintext record * @ret rc Return status code */ static int tls_new_handshake ( struct tls_connection *tls, const void *data, size_t len ) { size_t remaining = len; int rc; while ( remaining ) { const struct { uint8_t type; tls24_t length; uint8_t payload[0]; } __attribute__ (( packed )) *handshake = data; const void *payload; size_t payload_len; size_t record_len; /* Parse header */ if ( sizeof ( *handshake ) > remaining ) { DBGC ( tls, "TLS %p received underlength Handshake\n", tls ); DBGC_HD ( tls, data, remaining ); return -EINVAL_HANDSHAKE; } payload_len = tls_uint24 ( &handshake->length ); if ( payload_len > ( remaining - sizeof ( *handshake ) ) ) { DBGC ( tls, "TLS %p received overlength Handshake\n", tls ); DBGC_HD ( tls, data, len ); return -EINVAL_HANDSHAKE; } payload = &handshake->payload; record_len = ( sizeof ( *handshake ) + payload_len ); /* Handle payload */ switch ( handshake->type ) { case TLS_HELLO_REQUEST: rc = tls_new_hello_request ( tls, payload, payload_len ); break; case TLS_SERVER_HELLO: rc = tls_new_server_hello ( tls, payload, payload_len ); break; case TLS_NEW_SESSION_TICKET: rc = tls_new_session_ticket ( tls, payload, payload_len ); break; case TLS_CERTIFICATE: rc = tls_new_certificate ( tls, payload, payload_len ); break; case TLS_CERTIFICATE_REQUEST: rc = tls_new_certificate_request ( tls, payload, payload_len ); break; case TLS_SERVER_HELLO_DONE: rc = tls_new_server_hello_done ( tls, payload, payload_len ); break; case TLS_FINISHED: rc = tls_new_finished ( tls, payload, payload_len ); break; default: DBGC ( tls, "TLS %p ignoring handshake type %d\n", tls, handshake->type ); rc = 0; break; } /* Add to handshake digest (except for Hello Requests, * which are explicitly excluded). */ if ( handshake->type != TLS_HELLO_REQUEST ) tls_add_handshake ( tls, data, record_len ); /* Abort on failure */ if ( rc != 0 ) return rc; /* Move to next handshake record */ data += record_len; remaining -= record_len; } return 0; } /** * Receive new record * * @v tls TLS connection * @v type Record type * @v rx_data List of received data buffers * @ret rc Return status code */ static int tls_new_record ( struct tls_connection *tls, unsigned int type, struct list_head *rx_data ) { struct io_buffer *iobuf; int ( * handler ) ( struct tls_connection *tls, const void *data, size_t len ); int rc; /* Deliver data records to the plainstream interface */ if ( type == TLS_TYPE_DATA ) { /* Fail unless we are ready to receive data */ if ( ! tls_ready ( tls ) ) return -ENOTCONN; /* Deliver each I/O buffer in turn */ while ( ( iobuf = list_first_entry ( rx_data, struct io_buffer, list ) ) ) { list_del ( &iobuf->list ); if ( ( rc = xfer_deliver_iob ( &tls->plainstream, iobuf ) ) != 0 ) { DBGC ( tls, "TLS %p could not deliver data: " "%s\n", tls, strerror ( rc ) ); return rc; } } return 0; } /* For all other records, merge into a single I/O buffer */ iobuf = iob_concatenate ( rx_data ); if ( ! iobuf ) { DBGC ( tls, "TLS %p could not concatenate non-data record " "type %d\n", tls, type ); return -ENOMEM_RX_CONCAT; } /* Determine handler */ switch ( type ) { case TLS_TYPE_CHANGE_CIPHER: handler = tls_new_change_cipher; break; case TLS_TYPE_ALERT: handler = tls_new_alert; break; case TLS_TYPE_HANDSHAKE: handler = tls_new_handshake; break; default: /* RFC4346 says that we should just ignore unknown * record types. */ handler = NULL; DBGC ( tls, "TLS %p ignoring record type %d\n", tls, type ); break; } /* Handle record and free I/O buffer */ rc = ( handler ? handler ( tls, iobuf->data, iob_len ( iobuf ) ) : 0 ); free_iob ( iobuf ); return rc; } /****************************************************************************** * * Record encryption/decryption * ****************************************************************************** */ /** * Initialise HMAC * * @v cipherspec Cipher specification * @v ctx Context * @v seq Sequence number * @v tlshdr TLS header */ static void tls_hmac_init ( struct tls_cipherspec *cipherspec, void *ctx, uint64_t seq, struct tls_header *tlshdr ) { struct digest_algorithm *digest = cipherspec->suite->digest; hmac_init ( digest, ctx, cipherspec->mac_secret, &digest->digestsize ); seq = cpu_to_be64 ( seq ); hmac_update ( digest, ctx, &seq, sizeof ( seq ) ); hmac_update ( digest, ctx, tlshdr, sizeof ( *tlshdr ) ); } /** * Update HMAC * * @v cipherspec Cipher specification * @v ctx Context * @v data Data * @v len Length of data */ static void tls_hmac_update ( struct tls_cipherspec *cipherspec, void *ctx, const void *data, size_t len ) { struct digest_algorithm *digest = cipherspec->suite->digest; hmac_update ( digest, ctx, data, len ); } /** * Finalise HMAC * * @v cipherspec Cipher specification * @v ctx Context * @v mac HMAC to fill in */ static void tls_hmac_final ( struct tls_cipherspec *cipherspec, void *ctx, void *hmac ) { struct digest_algorithm *digest = cipherspec->suite->digest; hmac_final ( digest, ctx, cipherspec->mac_secret, &digest->digestsize, hmac ); } /** * Calculate HMAC * * @v cipherspec Cipher specification * @v seq Sequence number * @v tlshdr TLS header * @v data Data * @v len Length of data * @v mac HMAC to fill in */ static void tls_hmac ( struct tls_cipherspec *cipherspec, uint64_t seq, struct tls_header *tlshdr, const void *data, size_t len, void *hmac ) { struct digest_algorithm *digest = cipherspec->suite->digest; uint8_t ctx[digest->ctxsize]; tls_hmac_init ( cipherspec, ctx, seq, tlshdr ); tls_hmac_update ( cipherspec, ctx, data, len ); tls_hmac_final ( cipherspec, ctx, hmac ); } /** * Allocate and assemble stream-ciphered record from data and MAC portions * * @v tls TLS connection * @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_connection *tls, const void *data, size_t len, void *digest, size_t *plaintext_len ) { size_t mac_len = tls->tx_cipherspec.suite->digest->digestsize; void *plaintext; void *content; void *mac; /* Calculate stream-ciphered struct length */ *plaintext_len = ( len + mac_len ); /* Allocate stream-ciphered struct */ plaintext = malloc ( *plaintext_len ); if ( ! plaintext ) return NULL; content = plaintext; mac = ( content + len ); /* Fill in stream-ciphered struct */ memcpy ( content, data, len ); memcpy ( mac, digest, mac_len ); return plaintext; } /** * Allocate and assemble block-ciphered record from data and MAC portions * * @v tls TLS connection * @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_connection *tls, const void *data, size_t len, void *digest, size_t *plaintext_len ) { size_t blocksize = tls->tx_cipherspec.suite->cipher->blocksize; size_t mac_len = tls->tx_cipherspec.suite->digest->digestsize; size_t iv_len; size_t padding_len; void *plaintext; void *iv; void *content; void *mac; void *padding; /* TLSv1.1 and later use an explicit IV */ iv_len = ( tls_version ( tls, TLS_VERSION_TLS_1_1 ) ? blocksize : 0 ); /* Calculate block-ciphered struct length */ padding_len = ( ( blocksize - 1 ) & -( iv_len + len + mac_len + 1 ) ); *plaintext_len = ( iv_len + len + mac_len + padding_len + 1 ); /* Allocate block-ciphered struct */ plaintext = malloc ( *plaintext_len ); if ( ! plaintext ) return NULL; iv = plaintext; content = ( iv + iv_len ); mac = ( content + len ); padding = ( mac + mac_len ); /* Fill in block-ciphered struct */ tls_generate_random ( tls, iv, iv_len ); memcpy ( content, data, len ); memcpy ( mac, digest, mac_len ); memset ( padding, padding_len, ( padding_len + 1 ) ); return plaintext; } /** * Send plaintext record * * @v tls TLS connection * @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_connection *tls, unsigned int type, const void *data, size_t len ) { struct tls_header plaintext_tlshdr; struct tls_header *tlshdr; struct tls_cipherspec *cipherspec = &tls->tx_cipherspec; struct cipher_algorithm *cipher = cipherspec->suite->cipher; void *plaintext = NULL; size_t plaintext_len; struct io_buffer *ciphertext = NULL; size_t ciphertext_len; size_t mac_len = cipherspec->suite->digest->digestsize; uint8_t mac[mac_len]; int rc; /* Construct header */ plaintext_tlshdr.type = type; plaintext_tlshdr.version = htons ( tls->version ); plaintext_tlshdr.length = htons ( len ); /* Calculate MAC */ tls_hmac ( cipherspec, tls->tx_seq, &plaintext_tlshdr, data, len, mac ); /* Allocate and assemble plaintext struct */ if ( is_stream_cipher ( cipher ) ) { plaintext = tls_assemble_stream ( tls, data, len, mac, &plaintext_len ); } else { plaintext = tls_assemble_block ( tls, data, len, mac, &plaintext_len ); } if ( ! plaintext ) { DBGC ( tls, "TLS %p could not allocate %zd bytes for " "plaintext\n", tls, plaintext_len ); rc = -ENOMEM_TX_PLAINTEXT; goto done; } DBGC2 ( tls, "Sending plaintext data:\n" ); DBGC2_HD ( tls, plaintext, plaintext_len ); /* Allocate ciphertext */ ciphertext_len = ( sizeof ( *tlshdr ) + plaintext_len ); ciphertext = xfer_alloc_iob ( &tls->cipherstream, ciphertext_len ); if ( ! ciphertext ) { DBGC ( tls, "TLS %p could not allocate %zd bytes for " "ciphertext\n", tls, ciphertext_len ); rc = -ENOMEM_TX_CIPHERTEXT; goto done; } /* Assemble ciphertext */ tlshdr = iob_put ( ciphertext, sizeof ( *tlshdr ) ); tlshdr->type = type; tlshdr->version = htons ( tls->version ); tlshdr->length = htons ( plaintext_len ); memcpy ( cipherspec->cipher_next_ctx, cipherspec->cipher_ctx, cipher->ctxsize ); cipher_encrypt ( cipher, cipherspec->cipher_next_ctx, plaintext, iob_put ( ciphertext, plaintext_len ), plaintext_len ); /* Free plaintext as soon as possible to conserve memory */ free ( plaintext ); plaintext = NULL; /* Send ciphertext */ if ( ( rc = xfer_deliver_iob ( &tls->cipherstream, iob_disown ( ciphertext ) ) ) != 0 ) { DBGC ( tls, "TLS %p could not deliver ciphertext: %s\n", tls, strerror ( rc ) ); goto done; } /* Update TX state machine to next record */ tls->tx_seq += 1; memcpy ( tls->tx_cipherspec.cipher_ctx, tls->tx_cipherspec.cipher_next_ctx, cipher->ctxsize ); done: free ( plaintext ); free_iob ( ciphertext ); return rc; } /** * Split stream-ciphered record into data and MAC portions * * @v tls TLS connection * @v rx_data List of received data buffers * @v mac MAC to fill in * @ret rc Return status code */ static int tls_split_stream ( struct tls_connection *tls, struct list_head *rx_data, void **mac ) { size_t mac_len = tls->rx_cipherspec.suite->digest->digestsize; struct io_buffer *iobuf; /* Extract MAC */ iobuf = list_last_entry ( rx_data, struct io_buffer, list ); assert ( iobuf != NULL ); if ( iob_len ( iobuf ) < mac_len ) { DBGC ( tls, "TLS %p received underlength MAC\n", tls ); DBGC_HD ( tls, iobuf->data, iob_len ( iobuf ) ); return -EINVAL_STREAM; } iob_unput ( iobuf, mac_len ); *mac = iobuf->tail; return 0; } /** * Split block-ciphered record into data and MAC portions * * @v tls TLS connection * @v rx_data List of received data buffers * @v mac MAC to fill in * @ret rc Return status code */ static int tls_split_block ( struct tls_connection *tls, struct list_head *rx_data, void **mac ) { size_t mac_len = tls->rx_cipherspec.suite->digest->digestsize; struct io_buffer *iobuf; size_t iv_len; uint8_t *padding_final; uint8_t *padding; size_t padding_len; /* TLSv1.1 and later use an explicit IV */ iobuf = list_first_entry ( rx_data, struct io_buffer, list ); iv_len = ( tls_version ( tls, TLS_VERSION_TLS_1_1 ) ? tls->rx_cipherspec.suite->cipher->blocksize : 0 ); if ( iob_len ( iobuf ) < iv_len ) { DBGC ( tls, "TLS %p received underlength IV\n", tls ); DBGC_HD ( tls, iobuf->data, iob_len ( iobuf ) ); return -EINVAL_BLOCK; } iob_pull ( iobuf, iv_len ); /* Extract and verify padding */ iobuf = list_last_entry ( rx_data, struct io_buffer, list ); padding_final = ( iobuf->tail - 1 ); padding_len = *padding_final; if ( ( padding_len + 1 ) > iob_len ( iobuf ) ) { DBGC ( tls, "TLS %p received underlength padding\n", tls ); DBGC_HD ( tls, iobuf->data, iob_len ( iobuf ) ); return -EINVAL_BLOCK; } iob_unput ( iobuf, ( padding_len + 1 ) ); for ( padding = iobuf->tail ; padding < padding_final ; padding++ ) { if ( *padding != padding_len ) { DBGC ( tls, "TLS %p received bad padding\n", tls ); DBGC_HD ( tls, padding, padding_len ); return -EINVAL_PADDING; } } /* Extract MAC */ if ( iob_len ( iobuf ) < mac_len ) { DBGC ( tls, "TLS %p received underlength MAC\n", tls ); DBGC_HD ( tls, iobuf->data, iob_len ( iobuf ) ); return -EINVAL_BLOCK; } iob_unput ( iobuf, mac_len ); *mac = iobuf->tail; return 0; } /** * Receive new ciphertext record * * @v tls TLS connection * @v tlshdr Record header * @v rx_data List of received data buffers * @ret rc Return status code */ static int tls_new_ciphertext ( struct tls_connection *tls, struct tls_header *tlshdr, struct list_head *rx_data ) { struct tls_header plaintext_tlshdr; struct tls_cipherspec *cipherspec = &tls->rx_cipherspec; struct cipher_algorithm *cipher = cipherspec->suite->cipher; struct digest_algorithm *digest = cipherspec->suite->digest; uint8_t ctx[digest->ctxsize]; uint8_t verify_mac[digest->digestsize]; struct io_buffer *iobuf; void *mac; size_t len = 0; int rc; /* Decrypt the received data */ list_for_each_entry ( iobuf, &tls->rx_data, list ) { cipher_decrypt ( cipher, cipherspec->cipher_ctx, iobuf->data, iobuf->data, iob_len ( iobuf ) ); } /* Split record into content and MAC */ if ( is_stream_cipher ( cipher ) ) { if ( ( rc = tls_split_stream ( tls, rx_data, &mac ) ) != 0 ) return rc; } else { if ( ( rc = tls_split_block ( tls, rx_data, &mac ) ) != 0 ) return rc; } /* Calculate total length */ DBGC2 ( tls, "Received plaintext data:\n" ); list_for_each_entry ( iobuf, rx_data, list ) { DBGC2_HD ( tls, iobuf->data, iob_len ( iobuf ) ); len += iob_len ( iobuf ); } /* Verify MAC */ plaintext_tlshdr.type = tlshdr->type; plaintext_tlshdr.version = tlshdr->version; plaintext_tlshdr.length = htons ( len ); tls_hmac_init ( cipherspec, ctx, tls->rx_seq, &plaintext_tlshdr ); list_for_each_entry ( iobuf, rx_data, list ) { tls_hmac_update ( cipherspec, ctx, iobuf->data, iob_len ( iobuf ) ); } tls_hmac_final ( cipherspec, ctx, verify_mac ); if ( memcmp ( mac, verify_mac, sizeof ( verify_mac ) ) != 0 ) { DBGC ( tls, "TLS %p failed MAC verification\n", tls ); return -EINVAL_MAC; } /* Process plaintext record */ if ( ( rc = tls_new_record ( tls, tlshdr->type, rx_data ) ) != 0 ) return rc; return 0; } /****************************************************************************** * * Plaintext stream operations * ****************************************************************************** */ /** * Check flow control window * * @v tls TLS connection * @ret len Length of window */ static size_t tls_plainstream_window ( struct tls_connection *tls ) { /* Block window unless we are ready to accept data */ if ( ! tls_ready ( tls ) ) return 0; return xfer_window ( &tls->cipherstream ); } /** * Deliver datagram as raw data * * @v tls TLS connection * @v iobuf I/O buffer * @v meta Data transfer metadata * @ret rc Return status code */ static int tls_plainstream_deliver ( struct tls_connection *tls, struct io_buffer *iobuf, struct xfer_metadata *meta __unused ) { int rc; /* Refuse unless we are ready to accept data */ if ( ! tls_ready ( tls ) ) { rc = -ENOTCONN; goto done; } if ( ( rc = tls_send_plaintext ( tls, TLS_TYPE_DATA, iobuf->data, iob_len ( iobuf ) ) ) != 0 ) goto done; done: free_iob ( iobuf ); return rc; } /** * Report job progress * * @v tls TLS connection * @v progress Progress report to fill in * @ret ongoing_rc Ongoing job status code (if known) */ static int tls_progress ( struct tls_connection *tls, struct job_progress *progress ) { /* Return cipherstream or validator progress as applicable */ if ( is_pending ( &tls->validation ) ) { return job_progress ( &tls->validator, progress ); } else { return job_progress ( &tls->cipherstream, progress ); } } /** TLS plaintext stream interface operations */ static struct interface_operation tls_plainstream_ops[] = { INTF_OP ( xfer_deliver, struct tls_connection *, tls_plainstream_deliver ), INTF_OP ( xfer_window, struct tls_connection *, tls_plainstream_window ), INTF_OP ( job_progress, struct tls_connection *, tls_progress ), INTF_OP ( intf_close, struct tls_connection *, tls_close ), }; /** TLS plaintext stream interface descriptor */ static struct interface_descriptor tls_plainstream_desc = INTF_DESC_PASSTHRU ( struct tls_connection, plainstream, tls_plainstream_ops, cipherstream ); /****************************************************************************** * * Ciphertext stream operations * ****************************************************************************** */ /** * Handle received TLS header * * @v tls TLS connection * @ret rc Returned status code */ static int tls_newdata_process_header ( struct tls_connection *tls ) { size_t data_len = ntohs ( tls->rx_header.length ); size_t remaining = data_len; size_t frag_len; struct io_buffer *iobuf; struct io_buffer *tmp; int rc; /* Allocate data buffers now that we know the length */ assert ( list_empty ( &tls->rx_data ) ); while ( remaining ) { /* Calculate fragment length. Ensure that no block is * smaller than TLS_RX_MIN_BUFSIZE (by increasing the * allocation length if necessary). */ frag_len = remaining; if ( frag_len > TLS_RX_BUFSIZE ) frag_len = TLS_RX_BUFSIZE; remaining -= frag_len; if ( remaining < TLS_RX_MIN_BUFSIZE ) { frag_len += remaining; remaining = 0; } /* Allocate buffer */ iobuf = alloc_iob_raw ( frag_len, TLS_RX_ALIGN, 0 ); if ( ! iobuf ) { DBGC ( tls, "TLS %p could not allocate %zd of %zd " "bytes for receive buffer\n", tls, remaining, data_len ); rc = -ENOMEM_RX_DATA; goto err; } /* Ensure tailroom is exactly what we asked for. This * will result in unaligned I/O buffers when the * fragment length is unaligned, which can happen only * before we switch to using a block cipher. */ iob_reserve ( iobuf, ( iob_tailroom ( iobuf ) - frag_len ) ); /* Add I/O buffer to list */ list_add_tail ( &iobuf->list, &tls->rx_data ); } /* Move to data state */ tls->rx_state = TLS_RX_DATA; return 0; err: list_for_each_entry_safe ( iobuf, tmp, &tls->rx_data, list ) { list_del ( &iobuf->list ); free_iob ( iobuf ); } return rc; } /** * Handle received TLS data payload * * @v tls TLS connection * @ret rc Returned status code */ static int tls_newdata_process_data ( struct tls_connection *tls ) { struct io_buffer *iobuf; int rc; /* Move current buffer to end of list */ iobuf = list_first_entry ( &tls->rx_data, struct io_buffer, list ); list_del ( &iobuf->list ); list_add_tail ( &iobuf->list, &tls->rx_data ); /* Continue receiving data if any space remains */ iobuf = list_first_entry ( &tls->rx_data, struct io_buffer, list ); if ( iob_tailroom ( iobuf ) ) return 0; /* Process record */ if ( ( rc = tls_new_ciphertext ( tls, &tls->rx_header, &tls->rx_data ) ) != 0 ) return rc; /* Increment RX sequence number */ tls->rx_seq += 1; /* Return to header state */ assert ( list_empty ( &tls->rx_data ) ); tls->rx_state = TLS_RX_HEADER; iob_unput ( &tls->rx_header_iobuf, sizeof ( tls->rx_header ) ); return 0; } /** * Check flow control window * * @v tls TLS connection * @ret len Length of window */ static size_t tls_cipherstream_window ( struct tls_connection *tls ) { /* Open window until we are ready to accept data */ if ( ! tls_ready ( tls ) ) return -1UL; return xfer_window ( &tls->plainstream ); } /** * Receive new ciphertext * * @v tls TLS connection * @v iobuf I/O buffer * @v meta Data transfer metadat * @ret rc Return status code */ static int tls_cipherstream_deliver ( struct tls_connection *tls, struct io_buffer *iobuf, struct xfer_metadata *xfer __unused ) { size_t frag_len; int ( * process ) ( struct tls_connection *tls ); struct io_buffer *dest; int rc; while ( iob_len ( iobuf ) ) { /* Select buffer according to current state */ switch ( tls->rx_state ) { case TLS_RX_HEADER: dest = &tls->rx_header_iobuf; process = tls_newdata_process_header; break; case TLS_RX_DATA: dest = list_first_entry ( &tls->rx_data, struct io_buffer, list ); assert ( dest != NULL ); process = tls_newdata_process_data; break; default: assert ( 0 ); rc = -EINVAL_RX_STATE; goto done; } /* Copy data portion to buffer */ frag_len = iob_len ( iobuf ); if ( frag_len > iob_tailroom ( dest ) ) frag_len = iob_tailroom ( dest ); memcpy ( iob_put ( dest, frag_len ), iobuf->data, frag_len ); iob_pull ( iobuf, frag_len ); /* Process data if buffer is now full */ if ( iob_tailroom ( dest ) == 0 ) { if ( ( rc = process ( tls ) ) != 0 ) { tls_close ( tls, rc ); goto done; } } } rc = 0; done: free_iob ( iobuf ); return rc; } /** TLS ciphertext stream interface operations */ static struct interface_operation tls_cipherstream_ops[] = { INTF_OP ( xfer_deliver, struct tls_connection *, tls_cipherstream_deliver ), INTF_OP ( xfer_window, struct tls_connection *, tls_cipherstream_window ), INTF_OP ( xfer_window_changed, struct tls_connection *, tls_tx_resume ), INTF_OP ( intf_close, struct tls_connection *, tls_close ), }; /** TLS ciphertext stream interface descriptor */ static struct interface_descriptor tls_cipherstream_desc = INTF_DESC_PASSTHRU ( struct tls_connection, cipherstream, tls_cipherstream_ops, plainstream ); /****************************************************************************** * * Certificate validator * ****************************************************************************** */ /** * Handle certificate validation completion * * @v tls TLS connection * @v rc Reason for completion */ static void tls_validator_done ( struct tls_connection *tls, int rc ) { struct tls_session *session = tls->session; struct tls_cipherspec *cipherspec = &tls->tx_cipherspec_pending; struct pubkey_algorithm *pubkey = cipherspec->suite->pubkey; struct x509_certificate *cert; /* Mark validation as complete */ pending_put ( &tls->validation ); /* Close validator interface */ intf_restart ( &tls->validator, rc ); /* Check for validation failure */ if ( rc != 0 ) { DBGC ( tls, "TLS %p certificate validation failed: %s\n", tls, strerror ( rc ) ); goto err; } DBGC ( tls, "TLS %p certificate validation succeeded\n", tls ); /* Extract first certificate */ cert = x509_first ( tls->chain ); assert ( cert != NULL ); /* Verify server name */ if ( ( rc = x509_check_name ( cert, session->name ) ) != 0 ) { DBGC ( tls, "TLS %p server certificate does not match %s: %s\n", tls, session->name, strerror ( rc ) ); goto err; } /* Initialise public key algorithm */ if ( ( rc = pubkey_init ( pubkey, cipherspec->pubkey_ctx, cert->subject.public_key.raw.data, cert->subject.public_key.raw.len ) ) != 0 ) { DBGC ( tls, "TLS %p cannot initialise public key: %s\n", tls, strerror ( rc ) ); goto err; } /* Schedule Client Key Exchange, Change Cipher, and Finished */ tls->tx_pending |= ( TLS_TX_CLIENT_KEY_EXCHANGE | TLS_TX_CHANGE_CIPHER | TLS_TX_FINISHED ); if ( tls->certs ) { tls->tx_pending |= ( TLS_TX_CERTIFICATE | TLS_TX_CERTIFICATE_VERIFY ); } tls_tx_resume ( tls ); return; err: tls_close ( tls, rc ); return; } /** TLS certificate validator interface operations */ static struct interface_operation tls_validator_ops[] = { INTF_OP ( intf_close, struct tls_connection *, tls_validator_done ), }; /** TLS certificate validator interface descriptor */ static struct interface_descriptor tls_validator_desc = INTF_DESC ( struct tls_connection, validator, tls_validator_ops ); /****************************************************************************** * * Controlling process * ****************************************************************************** */ /** * TLS TX state machine * * @v tls TLS connection */ static void tls_tx_step ( struct tls_connection *tls ) { struct tls_session *session = tls->session; struct tls_connection *conn; int rc; /* Wait for cipherstream to become ready */ if ( ! xfer_window ( &tls->cipherstream ) ) return; /* Send first pending transmission */ if ( tls->tx_pending & TLS_TX_CLIENT_HELLO ) { /* Serialise server negotiations within a session, to * provide a consistent view of session IDs and * session tickets. */ list_for_each_entry ( conn, &session->conn, list ) { if ( conn == tls ) break; if ( is_pending ( &conn->server_negotiation ) ) return; } /* Record or generate session ID and associated master secret */ if ( session->id_len ) { /* Attempt to resume an existing session */ memcpy ( tls->session_id, session->id, sizeof ( tls->session_id ) ); tls->session_id_len = session->id_len; memcpy ( tls->master_secret, session->master_secret, sizeof ( tls->master_secret ) ); } else { /* No existing session: use a random session ID */ assert ( sizeof ( tls->session_id ) == sizeof ( tls->client_random ) ); memcpy ( tls->session_id, &tls->client_random, sizeof ( tls->session_id ) ); tls->session_id_len = sizeof ( tls->session_id ); } /* Send Client Hello */ if ( ( rc = tls_send_client_hello ( tls ) ) != 0 ) { DBGC ( tls, "TLS %p could not send Client Hello: %s\n", tls, strerror ( rc ) ); goto err; } tls->tx_pending &= ~TLS_TX_CLIENT_HELLO; } else if ( tls->tx_pending & TLS_TX_CERTIFICATE ) { /* Send Certificate */ if ( ( rc = tls_send_certificate ( tls ) ) != 0 ) { DBGC ( tls, "TLS %p cold not send Certificate: %s\n", tls, strerror ( rc ) ); goto err; } tls->tx_pending &= ~TLS_TX_CERTIFICATE; } else if ( tls->tx_pending & TLS_TX_CLIENT_KEY_EXCHANGE ) { /* Send Client Key Exchange */ if ( ( rc = tls_send_client_key_exchange ( tls ) ) != 0 ) { DBGC ( tls, "TLS %p could not send Client Key " "Exchange: %s\n", tls, strerror ( rc ) ); goto err; } tls->tx_pending &= ~TLS_TX_CLIENT_KEY_EXCHANGE; } else if ( tls->tx_pending & TLS_TX_CERTIFICATE_VERIFY ) { /* Send Certificate Verify */ if ( ( rc = tls_send_certificate_verify ( tls ) ) != 0 ) { DBGC ( tls, "TLS %p could not send Certificate " "Verify: %s\n", tls, strerror ( rc ) ); goto err; } tls->tx_pending &= ~TLS_TX_CERTIFICATE_VERIFY; } else if ( tls->tx_pending & TLS_TX_CHANGE_CIPHER ) { /* Send Change Cipher, and then change the cipher in use */ if ( ( rc = tls_send_change_cipher ( tls ) ) != 0 ) { DBGC ( tls, "TLS %p could not send Change Cipher: " "%s\n", tls, strerror ( rc ) ); goto err; } if ( ( rc = tls_change_cipher ( tls, &tls->tx_cipherspec_pending, &tls->tx_cipherspec )) != 0 ){ DBGC ( tls, "TLS %p could not activate TX cipher: " "%s\n", tls, strerror ( rc ) ); goto err; } tls->tx_seq = 0; tls->tx_pending &= ~TLS_TX_CHANGE_CIPHER; } else if ( tls->tx_pending & TLS_TX_FINISHED ) { /* Send Finished */ if ( ( rc = tls_send_finished ( tls ) ) != 0 ) { DBGC ( tls, "TLS %p could not send Finished: %s\n", tls, strerror ( rc ) ); goto err; } tls->tx_pending &= ~TLS_TX_FINISHED; } /* Reschedule process if pending transmissions remain, * otherwise send notification of a window change. */ if ( tls->tx_pending ) { tls_tx_resume ( tls ); } else { xfer_window_changed ( &tls->plainstream ); } return; err: tls_close ( tls, rc ); } /** TLS TX process descriptor */ static struct process_descriptor tls_process_desc = PROC_DESC_ONCE ( struct tls_connection, process, tls_tx_step ); /****************************************************************************** * * Session management * ****************************************************************************** */ /** * Find or create session for TLS connection * * @v tls TLS connection * @v name Server name * @ret rc Return status code */ static int tls_session ( struct tls_connection *tls, const char *name ) { struct tls_session *session; char *name_copy; int rc; /* Find existing matching session, if any */ list_for_each_entry ( session, &tls_sessions, list ) { if ( ( strcmp ( name, session->name ) == 0 ) && ( tls->root == session->root ) && ( tls->key == session->key ) ) { ref_get ( &session->refcnt ); tls->session = session; DBGC ( tls, "TLS %p joining session %s\n", tls, name ); return 0; } } /* Create new session */ session = zalloc ( sizeof ( *session ) + strlen ( name ) + 1 /* NUL */ ); if ( ! session ) { rc = -ENOMEM; goto err_alloc; } ref_init ( &session->refcnt, free_tls_session ); name_copy = ( ( ( void * ) session ) + sizeof ( *session ) ); strcpy ( name_copy, name ); session->name = name_copy; session->root = x509_root_get ( tls->root ); session->key = privkey_get ( tls->key ); INIT_LIST_HEAD ( &session->conn ); list_add ( &session->list, &tls_sessions ); /* Record session */ tls->session = session; DBGC ( tls, "TLS %p created session %s\n", tls, name ); return 0; ref_put ( &session->refcnt ); err_alloc: return rc; } /****************************************************************************** * * Instantiator * ****************************************************************************** */ /** * Add TLS on an interface * * @v xfer Data transfer interface * @v name Host name * @v root Root of trust (or NULL to use default) * @v key Private key (or NULL to use default) * @ret rc Return status code */ int add_tls ( struct interface *xfer, const char *name, struct x509_root *root, struct private_key *key ) { struct tls_connection *tls; int rc; /* Allocate and initialise TLS structure */ tls = malloc ( sizeof ( *tls ) ); if ( ! tls ) { rc = -ENOMEM; goto err_alloc; } memset ( tls, 0, sizeof ( *tls ) ); ref_init ( &tls->refcnt, free_tls ); INIT_LIST_HEAD ( &tls->list ); intf_init ( &tls->plainstream, &tls_plainstream_desc, &tls->refcnt ); intf_init ( &tls->cipherstream, &tls_cipherstream_desc, &tls->refcnt ); intf_init ( &tls->validator, &tls_validator_desc, &tls->refcnt ); process_init_stopped ( &tls->process, &tls_process_desc, &tls->refcnt ); tls->key = privkey_get ( key ? key : &private_key ); tls->root = x509_root_get ( root ? root : &root_certificates ); tls->version = TLS_VERSION_TLS_1_2; tls_clear_cipher ( tls, &tls->tx_cipherspec ); tls_clear_cipher ( tls, &tls->tx_cipherspec_pending ); tls_clear_cipher ( tls, &tls->rx_cipherspec ); tls_clear_cipher ( tls, &tls->rx_cipherspec_pending ); tls->client_random.gmt_unix_time = time ( NULL ); iob_populate ( &tls->rx_header_iobuf, &tls->rx_header, 0, sizeof ( tls->rx_header ) ); INIT_LIST_HEAD ( &tls->rx_data ); if ( ( rc = tls_generate_random ( tls, &tls->client_random.random, ( sizeof ( tls->client_random.random ) ) ) ) != 0 ) { goto err_random; } tls->pre_master_secret.version = htons ( tls->version ); if ( ( rc = tls_generate_random ( tls, &tls->pre_master_secret.random, ( sizeof ( tls->pre_master_secret.random ) ) ) ) != 0 ) { goto err_random; } if ( ( rc = tls_session ( tls, name ) ) != 0 ) goto err_session; list_add_tail ( &tls->list, &tls->session->conn ); /* Start negotiation */ tls_restart ( tls ); /* Attach to parent interface, mortalise self, and return */ intf_insert ( xfer, &tls->plainstream, &tls->cipherstream ); ref_put ( &tls->refcnt ); return 0; err_session: err_random: ref_put ( &tls->refcnt ); err_alloc: return rc; } /* Drag in objects via add_tls() */ REQUIRING_SYMBOL ( add_tls ); /* Drag in crypto configuration */ REQUIRE_OBJECT ( config_crypto );