2007-07-30 03:48:38 +02:00
|
|
|
/*
|
|
|
|
* 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
|
|
|
|
*
|
|
|
|
* 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>
|
2009-02-10 18:37:24 +01:00
|
|
|
#include <gpxe/asn1.h>
|
|
|
|
#include <gpxe/x509.h>
|
2007-07-30 03:48:38 +02:00
|
|
|
#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 );
|
|
|
|
|
2009-02-10 18:37:24 +01:00
|
|
|
/******************************************************************************
|
|
|
|
*
|
|
|
|
* 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
|
|
|
|
*
|
|
|
|
******************************************************************************
|
|
|
|
*/
|
|
|
|
|
2007-07-30 03:48:38 +02:00
|
|
|
/**
|
|
|
|
* 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 );
|
2009-02-10 18:37:24 +01:00
|
|
|
x509_free_rsa_public_key ( &tls->rsa );
|
2007-07-30 03:48:38 +02:00
|
|
|
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 ) {
|
2007-07-30 04:06:08 +02:00
|
|
|
/* FIXME: Some real random data source would be nice... */
|
2007-07-30 03:48:38 +02:00
|
|
|
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 crypto_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 crypto_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 );
|
2008-11-18 02:52:40 +01:00
|
|
|
DBGC_HD ( tls, &tls->client_random, sizeof ( tls->client_random ) );
|
2007-07-30 03:48:38 +02:00
|
|
|
DBGC ( tls, "TLS %p server random bytes:\n", tls );
|
|
|
|
DBGC_HD ( tls, &tls->server_random, sizeof ( tls->server_random ) );
|
|
|
|
|
2008-11-18 02:52:40 +01:00
|
|
|
tls_prf_label ( tls, &tls->pre_master_secret,
|
2007-07-30 03:48:38 +02:00
|
|
|
sizeof ( tls->pre_master_secret ),
|
2008-11-18 02:52:40 +01:00
|
|
|
&tls->master_secret, sizeof ( tls->master_secret ),
|
2007-07-30 03:48:38 +02:00
|
|
|
"master secret",
|
2008-11-18 02:52:40 +01:00
|
|
|
&tls->client_random, sizeof ( tls->client_random ),
|
|
|
|
&tls->server_random, sizeof ( tls->server_random ) );
|
2007-07-30 03:48:38 +02:00
|
|
|
|
|
|
|
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 */
|
2008-11-18 02:52:40 +01:00
|
|
|
tls_prf_label ( tls, &tls->master_secret, sizeof ( tls->master_secret ),
|
2007-07-30 03:48:38 +02:00
|
|
|
key_block, sizeof ( key_block ), "key expansion",
|
2008-11-18 02:52:40 +01:00
|
|
|
&tls->server_random, sizeof ( tls->server_random ),
|
|
|
|
&tls->client_random, sizeof ( tls->client_random ) );
|
2007-07-30 03:48:38 +02:00
|
|
|
|
|
|
|
/* 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;
|
|
|
|
}
|
|
|
|
|
2007-07-30 04:06:08 +02:00
|
|
|
/* FIXME: AES needs to be fixed to not require this */
|
2007-07-30 03:48:38 +02:00
|
|
|
AES_convert_key ( rx_cipherspec->cipher_ctx );
|
|
|
|
|
|
|
|
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 = &crypto_null;
|
|
|
|
cipherspec->cipher = &crypto_null;
|
|
|
|
cipherspec->digest = &crypto_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 crypto_algorithm *pubkey,
|
|
|
|
struct crypto_algorithm *cipher,
|
|
|
|
struct crypto_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 crypto_algorithm *pubkey = &crypto_null;
|
|
|
|
struct crypto_algorithm *cipher = &crypto_null;
|
|
|
|
struct crypto_algorithm *digest = &crypto_null;
|
2007-12-07 01:11:43 +01:00
|
|
|
unsigned int key_len = 0;
|
2007-07-30 03:48:38 +02:00
|
|
|
int rc;
|
|
|
|
|
|
|
|
switch ( cipher_suite ) {
|
|
|
|
case htons ( TLS_RSA_WITH_AES_128_CBC_SHA ):
|
|
|
|
key_len = ( 128 / 8 );
|
2009-02-18 22:25:14 +01:00
|
|
|
cipher = &aes_cbc_algorithm;
|
2007-07-30 03:48:38 +02:00
|
|
|
digest = &sha1_algorithm;
|
|
|
|
break;
|
|
|
|
case htons ( TLS_RSA_WITH_AES_256_CBC_SHA ):
|
|
|
|
key_len = ( 256 / 8 );
|
2009-02-18 22:25:14 +01:00
|
|
|
cipher = &aes_cbc_algorithm;
|
2007-07-30 03:48:38 +02:00
|
|
|
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 */
|
2007-07-30 04:30:27 +02:00
|
|
|
if ( /* FIXME (when pubkey is not hard-coded to RSA):
|
|
|
|
* ( pending->pubkey == &crypto_null ) || */
|
2007-07-30 03:48:38 +02:00
|
|
|
( pending->cipher == &crypto_null ) ||
|
|
|
|
( pending->digest == &crypto_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 crypto_algorithm *md5 = &md5_algorithm;
|
|
|
|
struct crypto_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 );
|
2008-11-18 02:52:40 +01:00
|
|
|
memcpy ( &hello.random, &tls->client_random, sizeof ( hello.random ) );
|
2007-07-30 03:48:38 +02:00
|
|
|
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 ) {
|
2007-07-30 04:06:08 +02:00
|
|
|
/* FIXME: Hack alert */
|
2007-07-30 03:48:38 +02:00
|
|
|
RSA_CTX *rsa_ctx;
|
2009-02-10 18:37:24 +01:00
|
|
|
RSA_pub_key_new ( &rsa_ctx, tls->rsa.modulus, tls->rsa.modulus_len,
|
|
|
|
tls->rsa.exponent, tls->rsa.exponent_len );
|
2007-07-30 03:48:38 +02:00
|
|
|
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 ) );
|
|
|
|
|
2007-07-30 04:06:08 +02:00
|
|
|
/* FIXME: Hack alert */
|
2007-07-30 03:48:38 +02:00
|
|
|
DBGC ( tls, "RSA encrypting plaintext, modulus, exponent:\n" );
|
|
|
|
DBGC_HD ( tls, &tls->pre_master_secret,
|
|
|
|
sizeof ( tls->pre_master_secret ) );
|
2009-02-10 18:37:24 +01:00
|
|
|
DBGC_HD ( tls, tls->rsa.modulus, tls->rsa.modulus_len );
|
|
|
|
DBGC_HD ( tls, tls->rsa.exponent, tls->rsa.exponent_len );
|
2008-11-18 02:52:40 +01:00
|
|
|
RSA_encrypt ( rsa_ctx, ( const uint8_t * ) &tls->pre_master_secret,
|
2007-07-30 03:48:38 +02:00
|
|
|
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 );
|
2008-11-18 02:52:40 +01:00
|
|
|
tls_prf_label ( tls, &tls->master_secret, sizeof ( tls->master_secret ),
|
2007-07-30 03:48:38 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2009-02-10 18:37:24 +01:00
|
|
|
* Receive new Server Hello handshake record
|
2007-07-30 03:48:38 +02:00
|
|
|
*
|
|
|
|
* @v tls TLS session
|
2009-02-10 18:37:24 +01:00
|
|
|
* @v data Plaintext handshake record
|
|
|
|
* @v len Length of plaintext handshake record
|
2007-07-30 03:48:38 +02:00
|
|
|
* @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 */
|
2008-11-18 02:52:40 +01:00
|
|
|
memcpy ( &tls->server_random, &hello_a->random,
|
2007-07-30 03:48:38 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2009-02-10 18:37:24 +01:00
|
|
|
* Receive new Certificate handshake record
|
2007-07-30 03:48:38 +02:00
|
|
|
*
|
|
|
|
* @v tls TLS session
|
2009-02-10 18:37:24 +01:00
|
|
|
* @v data Plaintext handshake record
|
|
|
|
* @v len Length of plaintext handshake record
|
2007-07-30 03:48:38 +02:00
|
|
|
* @ret rc Return status code
|
|
|
|
*/
|
|
|
|
static int tls_new_certificate ( struct tls_session *tls,
|
|
|
|
void *data, size_t len ) {
|
|
|
|
struct {
|
|
|
|
uint8_t length[3];
|
2009-02-10 18:37:24 +01:00
|
|
|
uint8_t certificates[0];
|
2007-07-30 03:48:38 +02:00
|
|
|
} __attribute__ (( packed )) *certificate = data;
|
2009-02-10 18:37:24 +01:00
|
|
|
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;
|
2007-07-30 03:48:38 +02:00
|
|
|
}
|
|
|
|
|
2009-02-10 18:37:24 +01:00
|
|
|
/* 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;
|
2007-07-30 03:48:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2009-02-10 18:37:24 +01:00
|
|
|
* Receive new Server Hello Done handshake record
|
2007-07-30 03:48:38 +02:00
|
|
|
*
|
|
|
|
* @v tls TLS session
|
2009-02-10 18:37:24 +01:00
|
|
|
* @v data Plaintext handshake record
|
|
|
|
* @v len Length of plaintext handshake record
|
2007-07-30 03:48:38 +02:00
|
|
|
* @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;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2009-02-10 18:37:24 +01:00
|
|
|
* Receive new Finished handshake record
|
2007-07-30 03:48:38 +02:00
|
|
|
*
|
|
|
|
* @v tls TLS session
|
2009-02-10 18:37:24 +01:00
|
|
|
* @v data Plaintext handshake record
|
|
|
|
* @v len Length of plaintext handshake record
|
2007-07-30 03:48:38 +02:00
|
|
|
* @ret rc Return status code
|
|
|
|
*/
|
|
|
|
static int tls_new_finished ( struct tls_session *tls,
|
|
|
|
void *data, size_t len ) {
|
|
|
|
|
2007-07-30 04:06:08 +02:00
|
|
|
/* FIXME: Handle this properly */
|
2007-07-30 03:48:38 +02:00
|
|
|
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 ) {
|
2009-02-10 18:37:24 +01:00
|
|
|
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 );
|
2007-07-30 03:48:38 +02:00
|
|
|
int rc;
|
|
|
|
|
2009-02-10 18:37:24 +01:00
|
|
|
/* 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 ) {
|
2007-07-30 03:48:38 +02:00
|
|
|
case TLS_SERVER_HELLO:
|
2009-02-10 18:37:24 +01:00
|
|
|
rc = tls_new_server_hello ( tls, payload, payload_len );
|
2007-07-30 03:48:38 +02:00
|
|
|
break;
|
|
|
|
case TLS_CERTIFICATE:
|
2009-02-10 18:37:24 +01:00
|
|
|
rc = tls_new_certificate ( tls, payload, payload_len );
|
2007-07-30 03:48:38 +02:00
|
|
|
break;
|
|
|
|
case TLS_SERVER_HELLO_DONE:
|
2009-02-10 18:37:24 +01:00
|
|
|
rc = tls_new_server_hello_done ( tls, payload, payload_len );
|
2007-07-30 03:48:38 +02:00
|
|
|
break;
|
|
|
|
case TLS_FINISHED:
|
2009-02-10 18:37:24 +01:00
|
|
|
rc = tls_new_finished ( tls, payload, payload_len );
|
2007-07-30 03:48:38 +02:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
DBGC ( tls, "TLS %p ignoring handshake type %d\n",
|
2009-02-10 18:37:24 +01:00
|
|
|
tls, handshake->type );
|
2007-07-30 03:48:38 +02:00
|
|
|
rc = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Add to handshake digest (except for Hello Requests, which
|
2009-02-10 18:37:24 +01:00
|
|
|
* are explicitly excluded).
|
2007-07-30 03:48:38 +02:00
|
|
|
*/
|
2009-02-10 18:37:24 +01:00
|
|
|
if ( handshake->type != TLS_HELLO_REQUEST )
|
2007-07-30 03:48:38 +02:00
|
|
|
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 crypto_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
|
|
|
|
*/
|
2007-08-20 20:30:24 +02:00
|
|
|
static void * __malloc tls_assemble_stream ( struct tls_session *tls,
|
2007-07-30 03:48:38 +02:00
|
|
|
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;
|
|
|
|
|
2007-07-30 04:06:08 +02:00
|
|
|
/* FIXME: TLSv1.1 has an explicit IV */
|
2007-07-30 03:48:38 +02:00
|
|
|
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 );
|
|
|
|
if ( ( rc = cipher_encrypt ( cipherspec->cipher,
|
|
|
|
cipherspec->cipher_next_ctx, plaintext,
|
|
|
|
iob_put ( ciphertext, plaintext_len ),
|
|
|
|
plaintext_len ) ) != 0 ) {
|
|
|
|
DBGC ( tls, "TLS %p could not encrypt: %s\n",
|
|
|
|
tls, strerror ( rc ) );
|
|
|
|
DBGC_HD ( tls, plaintext, plaintext_len );
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 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;
|
|
|
|
|
2007-07-30 04:06:08 +02:00
|
|
|
/* FIXME: TLSv1.1 uses an explicit IV */
|
2007-07-30 03:48:38 +02:00
|
|
|
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 */
|
|
|
|
if ( ( rc = cipher_decrypt ( cipherspec->cipher,
|
|
|
|
cipherspec->cipher_ctx, ciphertext,
|
|
|
|
plaintext, record_len ) ) != 0 ) {
|
|
|
|
DBGC ( tls, "TLS %p could not decrypt: %s\n",
|
|
|
|
tls, strerror ( rc ) );
|
|
|
|
DBGC_HD ( tls, ciphertext, record_len );
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 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_vopen,
|
|
|
|
.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 );
|
2008-11-18 02:52:40 +01:00
|
|
|
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 ) ) );
|
2007-07-30 03:48:38 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|