blob: b172845372800163305985f8f7722ec61491f26f [file]
/*
* Copyright (C) 2026 Michael Brown <mbrown@fensystems.co.uk>.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*
* You can also choose to distribute this program under the terms of
* the Unmodified Binary Distribution Licence (as given in the file
* COPYING.UBDL), provided that you have satisfied its requirements.
*/
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
FILE_SECBOOT ( PERMITTED );
/** @file
*
* TLS key schedules
*
* The TLS key schedule is responsible for maintaining the running
* handshake transcript digest, for deriving key material from the
* shared secret that was negotiated by a key exchange mechanism, and
* for performing all cryptographic calculations required by the TLS
* protocol.
*
* A single abstraction of a key schedule is defined that is
* independent of the TLS protocol version. Three implementations of
* this abstraction are provided:
*
* - a TLS version 1.3 key schedule using HKDF
*
* - a TLS version 1.2 key schedule using PRF based on P_Hash()
*
* - a TLS version 1.0/1.1 key schedule using PRF based on P_MD5()+P_SHA1()
*
* The basic set of operations is as required by the TLS protocol:
*
* - Start the key schedule by specifying the key schedule
* implementation to be used along with the handshake digest
* algorithm
*
* - Add each handshake record to the running transcript digest
*
* - Apply a shared secret (e.g. a pre-master secret that was agreed
* using Ephemeral Diffie-Hellman key exchange)
*
* - Derive a master secret from the shared secret
*
* - Derive client and server verification data for the Finished
* handshake records
*
* - Derive cipher keys, fixed IVs, and MAC secrets
*
* - Derive digest values to be signed or verified (e.g. for
* CertificateVerify and ServerKeyExchange)
*
* - Save pre-shared key for future session resumption
*
* - Load pre-shared key to resume a session
*
* The key schedule is responsible only for raw key material: it has
* no notion of the peer identity and so is fundamentally anonymous in
* nature.
*
* The separate secure channel abstraction handles all concepts of
* peer identity. The key schedule provides the cryptographic
* operations and properties that are documented as required by the
* secure channel, e.g. the calculations required to demonstrate or
* verify possession of key material.
*
*/
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <byteswap.h>
#include <ipxe/malloc.h>
#include <ipxe/hkdf.h>
#include <ipxe/hmac.h>
#include <ipxe/md5_sha1.h>
#include <ipxe/tlskey.h>
/*****************************************************************************
*
* Endpoints
*
*****************************************************************************
*/
/** Client endpoint */
const struct tls_endpoint tls_client = {
.index = TLS_CLIENT,
.name = "client",
};
/** Server endpoint */
const struct tls_endpoint tls_server = {
.index = TLS_SERVER,
.name = "server",
};
/** Endpoint list */
static const struct tls_endpoint *tls_endpoint[] = {
[TLS_CLIENT] = &tls_client,
[TLS_SERVER] = &tls_server,
};
/*****************************************************************************
*
* Traffic phases
*
*****************************************************************************
*/
/** A TLS traffic phase */
struct tls_phase {
/** Name */
const char *name;
/** Key expansion label */
const char label[ 3 /* "[e|hs|ap]" + NUL */ ];
/** Required key derivation function flags */
uint8_t flags;
};
/** Early traffic phase */
const struct tls_phase tls_early = {
.name = "early",
.label = "e",
.flags = TLSKEY_KDF_KEYED,
};
/** Handshake traffic phase */
const struct tls_phase tls_handshake = {
.name = "handshake",
.label = "hs",
.flags = TLSKEY_KDF_KEYED,
};
/** Application traffic phase */
const struct tls_phase tls_application = {
.name = "application",
.label = "ap",
.flags = ( TLSKEY_KDF_KEYED | TLSKEY_KDF_MASTER ),
};
/*****************************************************************************
*
* Lifecycle management
*
*****************************************************************************
*/
/**
* Start key schedule
*
* @v tlskey Key schedule
* @v op Key schedule operations
* @v digest Digest algorithm
* @v nonce Local endpoint random bytes
* @ret rc Return status code
*
* The random bytes must be the random bytes that the local endpoint
* will subsequently incorporate within its own ClientHello (or
* ServerHello, if acting as a server). The caller must guarantee
* that these are fresh and are unpredictable by the peer. A freshly
* generated secure channel ephemeral secret can be used to fulfil
* this requirement.
*/
int tlskey_start ( struct tls_key_schedule *tlskey,
const struct tls_key_schedule_operations *op,
struct digest_algorithm *digest,
const struct tls_random *nonce ) {
size_t digestsize = digest->digestsize;
size_t ctxsize = digest->ctxsize;
size_t secretsize;
size_t total;
void *dynamic;
int rc;
/* Stop any existing running key schedule */
tlskey_stop ( tlskey );
/* Sanity check */
if ( ! digestsize ) {
rc = -ENOTTY;
goto err_sanity;
}
/* Determine required sizes */
secretsize = op->secretsize ( digest );
if ( ! secretsize ) {
DBGC ( tlskey, "TLSKEY %p cannot use %s with %s\n",
tlskey, op->name, digest->name );
rc = -ENOTSUP;
goto err_secretsize;
}
total = ( secretsize + /* secret */
ctxsize + /* transcript.ctx */
digestsize + /* transcript.running */
digestsize /* transcript.finishing */ );
/* Allocate dynamic storage */
dynamic = zalloc ( total );
if ( ! dynamic ) {
rc = -ENOMEM;
goto err_alloc;
}
/* Initialise schedule */
tlskey->op = op;
tlskey->digest = digest;
tlskey->secretsize = secretsize;
DBGC ( tlskey, "TLSKEY %p using %s with %s\n",
tlskey, tlskey->op->name, tlskey->digest->name );
/* Assign dynamic storage */
tlskey->dynamic = dynamic;
tlskey->secret = dynamic; dynamic += secretsize;
tlskey->transcript.ctx = dynamic; dynamic += ctxsize;
tlskey->transcript.running = dynamic; dynamic += digestsize;
tlskey->transcript.finishing = dynamic; dynamic += digestsize;
assert ( dynamic == ( tlskey->dynamic + total ) );
/* Initialise transcript digest */
digest_init ( digest, tlskey->transcript.ctx );
tlskey_digest ( tlskey, NULL, 0 );
memcpy ( &tlskey->nonce, nonce, sizeof ( tlskey->nonce ) );
DBGC ( tlskey, "TLSKEY %p local nonce:\n", tlskey );
DBGC_HDA ( tlskey, 0, &tlskey->nonce, sizeof ( tlskey->nonce ) );
/* Reset key schedule */
tlskey_reset ( tlskey );
return 0;
tlskey_stop ( tlskey );
err_alloc:
err_secretsize:
err_sanity:
return rc;
}
/**
* Stop key schedule
*
* @v tlskey Key schedule
*/
void tlskey_stop ( struct tls_key_schedule *tlskey ) {
/* Clear and free any dynamic storage */
zfree ( tlskey->dynamic );
/* Clear key schedule contents */
memset ( tlskey, 0, sizeof ( *tlskey ) );
/* Set null digest algorithm */
tlskey->digest = &digest_null;
}
/*****************************************************************************
*
* Handshake transcript digest
*
*****************************************************************************
*/
/** A ClientHello or ServerHello handshake record prefix of interest */
struct tlskey_hello {
uint8_t type;
uint8_t length[3];
uint16_t version;
struct tls_random random;
} __attribute__ (( packed ));
/** Calculate endpoint index from ClientHello or ServerHello record type */
#define TLSKEY_HELLO_IDX( type ) ( (type) - 1 )
/** A Finished handshake record prefix of interest */
struct tlskey_finished {
uint8_t type;
} __attribute__ (( packed ));
/** Finished record type */
#define TLSKEY_FINISHED 20
/**
* Process digested ClientHello or ServerHello handshake record
*
* @v tlskey Key schedule
* @v hello ClientHello or ServerHello handshake record
* @v index Endpoint index
* @ret flags Additional transcript flags to set
*/
static unsigned int tlskey_hello ( struct tls_key_schedule *tlskey,
const struct tlskey_hello *hello,
unsigned int index ) {
const struct tls_endpoint *end = tls_endpoint[index];
const struct tls_random *nonce = &tlskey->nonce;
struct tls_random *random = &tlskey->random[index];
unsigned int flags = 0;
/* We check that the local endpoint random bytes have been
* incorporated into the running transcript digest, since this
* is required in order to provide our non-replayability
* guarantees.
*/
if ( memcmp ( nonce, &hello->random, sizeof ( *nonce ) ) == 0 )
flags |= TLSKEY_TSF_NONCED;
/* We also capture and store the client and server random
* bytes from these records, since TLS versions 1.2 and
* earlier sometimes require these values separately from the
* full running transcript digest.
*/
memcpy ( random, &hello->random, sizeof ( *random ) );
DBGC ( tlskey, "TLSKEY %p %s random bytes%s:\n", tlskey, end->name,
( ( flags & TLSKEY_TSF_NONCED ) ? " (local nonce)" : "" ) );
DBGC_HDA ( tlskey, 0, random, sizeof ( *random ) );
return flags;
}
/**
* Process digested Finished handshake record
*
* @v tlskey Key schedule
* @ret flags Additional transcript flags to set
*/
static unsigned int tlskey_finished ( struct tls_key_schedule *tlskey ) {
struct tls_transcript *transcript = &tlskey->transcript;
struct digest_algorithm *digest = tlskey->digest;
size_t digestsize = digest->digestsize;
/* The TLS version 1.3 key schedule derives application
* traffic secrets from the transcript hash up to and
* including only the server Finished, but requires the
* handshake traffic secret to remain available for
* calculating the client Finished.
*
* If there are any intervening handshake records (e.g. an
* EndOfEarlyData) then this makes it impossible to construct
* the client application traffic secret without either:
*
* - retaining a separate record of the transcript hash up
* to and including the server Finished, or
*
* - retaining the client handshake traffic secret after
* calculating the client application traffic secret.
*
* The TLS version 1.3 key schedule also derives resumption
* secrets from NewSessionTicket handshake records that arrive
* after the client Finished, but using the transcript hash up
* to and including only the client Finished.
*
* We choose to solve both of these problems simultaneously by
* retaining a second "finishing" digest value that mirrors
* the continuously running digest value until the first
* Finished is digested, and then switches to updating only
* when a new Finished is digested.
*/
DBGC ( tlskey, "TLSKEY %p finishing digest:\n", tlskey );
DBGC_HDA ( tlskey, 0, transcript->running, digestsize );
return TLSKEY_TSF_FINISHED;
}
/**
* Process digested handshake record
*
* @v tlskey Key schedule
* @v data Handshake record
* @v len Length of handshake record
* @ret flags Additional transcript flags to set
*/
static unsigned int tlskey_handshake ( struct tls_key_schedule *tlskey,
const void *data, size_t len ) {
const struct tlskey_hello *hello;
const struct tlskey_finished *finished;
unsigned int index;
/* Check for ClientHello and ServerHello */
if ( len >= sizeof ( *hello ) ) {
hello = data;
index = TLSKEY_HELLO_IDX ( hello->type );
if ( ( index == TLS_CLIENT ) || ( index == TLS_SERVER ) )
return tlskey_hello ( tlskey, hello, index );
}
/* Check for Finished */
if ( len >= sizeof ( *finished ) ) {
finished = data;
if ( finished->type == TLSKEY_FINISHED )
return tlskey_finished ( tlskey );
}
return 0;
}
/**
* Add handshake to running transcript digest
*
* @v tlskey Key schedule
* @v data Handshake record
* @v len Length of handshake record
*/
void tlskey_digest ( struct tls_key_schedule *tlskey, const void *data,
size_t len ) {
struct tls_transcript *transcript = &tlskey->transcript;
struct digest_algorithm *digest = tlskey->digest;
size_t digestsize = digest->digestsize;
size_t ctxsize = digest->ctxsize;
unsigned int flags;
struct {
uint8_t ctx[ctxsize];
} tmp;
/* Append to running transcript digest */
digest_update ( digest, transcript->ctx, data, len );
/* Update running transcript digest output */
memcpy ( tmp.ctx, transcript->ctx, ctxsize );
digest_final ( digest, tmp.ctx, transcript->running );
/* Process digested handshake record */
flags = tlskey_handshake ( tlskey, data, len );
transcript->flags |= flags;
/* Update the finishing digest value, if applicable */
if ( ! ( ( transcript->flags ^ flags ) & TLSKEY_TSF_FINISHED ) ) {
memcpy ( transcript->finishing, transcript->running,
digestsize );
}
/* Clear temporary secrets */
memset ( &tmp, 0, sizeof ( tmp ) );
}
/*****************************************************************************
*
* Key material
*
*****************************************************************************
*/
/**
* Expand key material
*
* @v tlskey Key schedule
* @v secret Secret
* @v label Label string
* @v seed Seed material
* @v seed_len Length of seed material
* @v out Output buffer
* @v out_len Length of output buffer
*/
static void tlskey_expand ( struct tls_key_schedule *tlskey,
const void *secret, const char *label,
const void *seed, size_t seed_len,
void *out, size_t out_len ) {
const struct tls_key_schedule_operations *op = tlskey->op;
struct digest_algorithm *digest = tlskey->digest;
/* Sanity check */
assert ( op != NULL );
/* Expand key material */
DBGC2 ( tlskey, "TLSKEY %p expanding seed \"%s\":\n", tlskey, label );
DBGC2_HDA ( tlskey, 0, seed, seed_len );
op->expand ( digest, secret, label, seed, seed_len, out, out_len );
DBGC2 ( tlskey, "TLSKEY %p expanded:\n", tlskey );
DBGC2_HDA ( tlskey, 0, out, out_len );
}
/**
* Reset key schedule
*
* @v tlskey Key schedule
*
* It is safe to reset a key schedule that has been stopped, or that
* has not yet been started.
*/
void tlskey_reset ( struct tls_key_schedule *tlskey ) {
const struct tls_key_schedule_operations *op = tlskey->op;
/* Do nothing if key schedule is stopped */
if ( ! op ) {
assert ( tlskey->secretsize == 0 );
assert ( tlskey->kdf.flags == 0 );
assert ( tlskey->traffic[TLS_CLIENT].phase == NULL );
assert ( tlskey->traffic[TLS_SERVER].phase == NULL );
return;
}
/* Clear secrets, flags, and traffic phases */
memset ( tlskey->secret, 0, tlskey->secretsize );
tlskey->kdf.flags = 0;
tlskey->traffic[TLS_CLIENT].phase = NULL;
tlskey->traffic[TLS_SERVER].phase = NULL;
/* Reset key schedule */
op->reset ( tlskey );
}
/**
* Apply a new shared secret
*
* @v tlskey Key schedule
* @v shared New shared secret
* @v shared_len Length of new shared secret
* @ret rc Return status code
*/
int tlskey_apply ( struct tls_key_schedule *tlskey, const void *shared,
size_t shared_len ) {
const struct tls_key_schedule_operations *op = tlskey->op;
int rc;
/* Sanity check */
if ( ! op )
return -ENOTTY;
/* All schedules define the master secret as immutable */
if ( tlskey->kdf.flags & TLSKEY_KDF_MASTER ) {
DBGC ( tlskey, "TLSKEY %p cannot apply a new shared secret "
"after generating master secret\n", tlskey );
return -EPROTO;
}
/* Apply new shared secret */
DBGC ( tlskey, "TLSKEY %p applying secret:\n", tlskey );
DBGC_HDA ( tlskey, 0, shared, shared_len );
if ( ( rc = op->apply ( tlskey, shared, shared_len ) ) != 0 )
return rc;
/* Mark key schedule as containing key material */
tlskey->kdf.flags |= TLSKEY_KDF_KEYED;
return 0;
}
/**
* Generate master secret
*
* @v tlskey Key schedule
* @v ems Extended master secret extension is enabled
* @ret rc Return status code
*/
int tlskey_master ( struct tls_key_schedule *tlskey, int ems ) {
const struct tls_key_schedule_operations *op = tlskey->op;
int rc;
/* Sanity check */
if ( ! op )
return -ENOTTY;
/* Any conceivable schedule requires key material */
if ( ! ( tlskey->kdf.flags & TLSKEY_KDF_KEYED ) ) {
DBGC ( tlskey, "TLSKEY %p cannot generate master secret "
"without key material\n", tlskey );
return -EPROTO;
}
/* All schedules define the master secret as immutable */
if ( tlskey->kdf.flags & TLSKEY_KDF_MASTER ) {
DBGC ( tlskey, "TLSKEY %p cannot regenerate master secret\n",
tlskey );
return -EPROTO;
}
/* Generate master secret */
DBGC ( tlskey, "TLSKEY %p generating %smaster secret\n",
tlskey, ( ems ? "extended " : "" ) );
if ( ( rc = op->master ( tlskey, ems ) ) != 0 )
return rc;
/* Mark key schedule as containing master secret */
tlskey->kdf.flags |= TLSKEY_KDF_MASTER;
if ( ems )
tlskey->kdf.flags |= TLSKEY_KDF_EMS;
return 0;
}
/**
* Generate verification data
*
* @v tlskey Key schedule
* @v end Verification endpoint
* @v verify Verification data to fill in
* @v verify_len Length of verification data
* @ret rc Return status code
*/
int tlskey_verify ( struct tls_key_schedule *tlskey,
const struct tls_endpoint *end,
void *verify, size_t verify_len ) {
const struct tls_key_schedule_operations *op = tlskey->op;
int rc;
/* Sanity check */
if ( ! op )
return -ENOTTY;
/* Verification data must be non-replayable */
if ( ! ( tlskey->transcript.flags & TLSKEY_TSF_NONCED ) ) {
DBGC ( tlskey, "TLSKEY %p cannot generate verification data "
"without a digested nonce\n", tlskey );
return -EPROTO;
}
/* Generate verification data */
DBGC2 ( tlskey, "TLSKEY %p generating %s verification data\n",
tlskey, end->name );
if ( ( rc = op->verify ( tlskey, end, verify, verify_len ) ) != 0 )
return rc;
DBGC ( tlskey, "TLSKEY %p %s verification data:\n",
tlskey, end->name );
DBGC_HDA ( tlskey, 0, verify, verify_len );
return 0;
}
/**
* Generate traffic secret
*
* @v tlskey Key schedule
* @v writer Writer endpoint
* @v phase Traffic phase
* @ret rc Return status code
*/
int tlskey_traffic ( struct tls_key_schedule *tlskey,
const struct tls_endpoint *writer,
const struct tls_phase *phase ) {
const struct tls_key_schedule_operations *op = tlskey->op;
struct tls_traffic_secret *traffic = &tlskey->traffic[writer->index];
unsigned int missing;
int rc;
/* Sanity check */
if ( ! op )
return -ENOTTY;
/* Check flags required for this traffic phase */
assert ( phase->flags & TLSKEY_KDF_KEYED );
missing = ( ( tlskey->kdf.flags ^ phase->flags ) & phase->flags );
if ( missing ) {
DBGC ( tlskey, "TLSKEY %p cannot generate %s %s traffic "
"secrets (missing KDF flags %#04x)\n",
tlskey, writer->name, phase->name, missing );
return -EPROTO;
}
/* Generate traffic secret */
DBGC2 ( tlskey, "TLSKEY %p generating %s %s traffic secret\n",
tlskey, writer->name, phase->name );
if ( ( rc = op->traffic ( tlskey, writer, phase ) ) != 0 )
return rc;
/* Record phase */
traffic->phase = phase;
return 0;
}
/**
* Generate cipher key material
*
* @v tlskey Key schedule
* @v writer Writer endpoint
* @v key Cipher key to fill in
* @v key_len Length of cipher key
* @v iv Fixed portion of initialisation vector to fill in
* @v iv_len Length of fixed portion of initialisation vector
* @v mac MAC secret to fill in
* @v mac_len Length of MAC secret
* @ret rc Return status code
*
* For key schedules that include a ratchet mechanism, each call
* consumes the current generation of traffic secret and replaces it
* with the next generation. Repeated calls may therefore be used to
* obtain multiple generations of the cipher key material.
*/
int tlskey_cipher ( struct tls_key_schedule *tlskey,
const struct tls_endpoint *writer,
void *key, size_t key_len, void *iv, size_t iv_len,
void *mac, size_t mac_len ) {
const struct tls_key_schedule_operations *op = tlskey->op;
struct tls_traffic_secret *traffic = &tlskey->traffic[writer->index];
int rc;
/* Sanity check */
if ( ! op )
return -ENOTTY;
/* Ensure that traffic secret has actually been generated */
if ( ! traffic->phase ) {
DBGC ( tlskey, "TLSKEY %p cannot generate cipher key "
"material without traffic secrets\n", tlskey );
return -EPROTO;
}
/* Generate cipher key material */
DBGC2 ( tlskey, "TLSKEY %p generating %s %s cipher key material\n",
tlskey, writer->name, traffic->phase->name );
if ( ( rc = op->cipher ( tlskey, writer, key, key_len, iv, iv_len,
mac, mac_len ) ) != 0 ) {
return rc;
}
if ( key_len ) {
DBGC ( tlskey, "TLSKEY %p %s key:\n", tlskey, writer->name );
DBGC_HDA ( tlskey, 0, key, key_len );
}
if ( iv_len ) {
DBGC ( tlskey, "TLSKEY %p %s IV:\n", tlskey, writer->name );
DBGC_HDA ( tlskey, 0, iv, iv_len );
}
if ( mac_len ) {
DBGC ( tlskey, "TLSKEY %p %s MAC:\n", tlskey, writer->name );
DBGC_HDA ( tlskey, 0, mac, mac_len );
}
return 0;
}
/**
* Generate signable digest value
*
* @v tlskey Key schedule
* @v end Endpoint
* @v digest Signature digest algorithm
* @v data Additional data
* @v len Length of additional data
* @v tbs Signable digest value to fill in
* @ret rc Return status code
*/
int tlskey_tbshash ( struct tls_key_schedule *tlskey,
const struct tls_endpoint *end,
struct digest_algorithm *digest,
const void *data, size_t len, void *tbs ) {
const struct tls_key_schedule_operations *op = tlskey->op;
int rc;
/* Sanity check */
if ( ! op )
return -ENOTTY;
/* Signable digest values must be non-replayable */
if ( ! ( tlskey->transcript.flags & TLSKEY_TSF_NONCED ) ) {
DBGC ( tlskey, "TLSKEY %p cannot generate signable digest "
"without a digested nonce\n", tlskey );
return -EPROTO;
}
/* Signable digest values must be constructed over the
* parameters used to establish a shared secret, and so a
* shared secret must exist.
*/
if ( ! ( tlskey->kdf.flags & TLSKEY_KDF_KEYED ) ) {
DBGC ( tlskey, "TLSKEY %p cannot generate signable digest "
"without a shared secret\n", tlskey );
return -EPROTO;
}
/* Generate digest value */
DBGC2 ( tlskey, "TLSKEY %p generating signable %s %s digest\n",
tlskey, end->name, digest->name );
if ( ( rc = op->tbshash ( tlskey, end, digest, data, len,
tbs ) ) != 0 ) {
return rc;
}
DBGC ( tlskey, "TLSKEY %p generated signable %s %s digest:\n",
tlskey, end->name, digest->name );
DBGC_HDA ( tlskey, 0, tbs, digest->digestsize );
return 0;
}
/**
* Save pre-shared key
*
* @v tlskey Key schedule
* @v nonce Ticket nonce
* @v nonce_len Length of ticket nonce
* @v psk Pre-shared key to fill in
* @ret rc Return status code
*/
int tlskey_save ( struct tls_key_schedule *tlskey, const void *nonce,
size_t nonce_len, struct tls_preshared_key *psk ) {
const struct tls_key_schedule_operations *op = tlskey->op;
struct digest_algorithm *digest = tlskey->digest;
int rc;
/* Clear any existing pre-shared key */
memset ( psk, 0, sizeof ( *psk ) );
/* Sanity check */
if ( ! op )
return -ENOTTY;
/* Session resumption requires a master secret */
if ( ! ( tlskey->kdf.flags & TLSKEY_KDF_MASTER ) ) {
DBGC ( tlskey, "TLSKEY %p cannot save key without a master "
"secret\n", tlskey );
return -EPROTO;
}
/* Save key material */
DBGC2 ( tlskey, "TLSKEY %p saving key\n", tlskey );
if ( ( rc = op->save ( tlskey, nonce, nonce_len, psk ) ) != 0 )
return rc;
DBGC ( tlskey, "TLSKEY %p saved key:\n", tlskey );
DBGC_HDA ( tlskey, 0, &psk->key, sizeof ( psk->key ) );
/* Record key properties */
psk->op = op;
psk->digest = digest;
psk->flags = tlskey->kdf.flags;
return 0;
}
/**
* Load pre-shared key
*
* @v tlskey Key schedule
* @v ems Extended master secret extension is enabled
* @v psk Pre-shared key
* @ret rc Return status code
*/
int tlskey_load ( struct tls_key_schedule *tlskey, int ems,
const struct tls_preshared_key *psk ) {
const struct tls_key_schedule_operations *op = tlskey->op;
struct digest_algorithm *digest = tlskey->digest;
int rc;
/* Reset key schedule */
tlskey_reset ( tlskey );
/* Sanity check */
if ( ! op )
return -ENOTTY;
/* Session resumption requires a master secret */
if ( ! ( psk->flags & TLSKEY_KDF_MASTER ) ) {
DBGC ( tlskey, "TLSKEY %p cannot load from a non-master "
"secret\n", tlskey );
return -EPROTO;
}
/* Pre-shared key must match extended/non-extended usage */
if ( ( !! ems ) != ( !! ( psk->flags & TLSKEY_KDF_EMS ) ) ) {
DBGC ( tlskey, "TLSKEY %p cannot load from %sextended master "
"secret\n", tlskey, ( ems ? "non-" : "" ) );
return -EPERM;
}
/* Pre-shared key must match schedule and digest */
if ( ( op != psk->op ) || ( digest != psk->digest ) ) {
DBGC ( tlskey, "TLSKEY %p cannot load from %s with %s\n",
tlskey, ( psk->op ? psk->op->name : "(unknown)" ),
( psk->digest ? psk->digest->name : "(unknown)" ) );
return -EPERM;
}
/* Load key material */
DBGC ( tlskey, "TLSKEY %p loading key:\n", tlskey );
DBGC_HDA ( tlskey, 0, &psk->key, sizeof ( psk->key ) );
if ( ( rc = op->load ( tlskey, psk ) ) != 0 )
return rc;
/* Set flags */
tlskey->kdf.flags = ( psk->flags & op->mask );
return 0;
}
/**
* Generate pre-shared key binder value
*
* @v psk Pre-shared key
* @v prefix ClientHello prefix
* @v prefix_len Length of ClientHello prefix
* @v binder Binder value to fill in
* @v binder_len Length of binder value
* @ret rc Return status code
*/
int tlskey_bind ( const struct tls_preshared_key *psk, const void *prefix,
size_t prefix_len, void *binder, size_t binder_len ) {
const struct tls_key_schedule_operations *op = psk->op;
struct digest_algorithm *digest = psk->digest;
int rc;
/* Generate prefix digest and binder value */
if ( op && digest ) {
size_t digestsize = digest->digestsize;
size_t ctxsize = digest->ctxsize;
uint8_t ctx[ctxsize];
uint8_t out[digestsize];
/* Calculate prefix digest */
digest_init ( digest, ctx );
digest_update ( digest, ctx, prefix, prefix_len );
digest_final ( digest, ctx, out );
/* Generate binder value */
DBGC2 ( psk, "TLSKEY %p generating key binder:\n", psk );
DBGC2_HDA ( psk, 0, &psk->key, sizeof ( psk->key ) );
if ( ( rc = op->bind ( psk, out, binder, binder_len ) ) != 0 )
return rc;
DBGC ( psk, "TLSKEY %p generated key binder:\n", psk );
DBGC_HDA ( psk, 0, binder, binder_len );
return 0;
}
DBGC ( psk, "TLSKEY %p cannot bind empty pre-shared key\n", psk );
return -ENOENT;
}
/*****************************************************************************
*
* TLS version 1.3 key schedule using HKDF
*
*****************************************************************************
*/
/**
* Calculate secret size
*
* @v digest Digest algorithm
* @ret secretsize Secret size, or zero if unsupported
*/
static size_t tlskey_hkdf_secretsize ( struct digest_algorithm *digest ) {
size_t digestsize = digest->digestsize;
return ( digestsize * 3 /* KDF, client, and server secrets */ );
}
/**
* Expand key material
*
* @v digest Digest algorithm
* @v secret Secret
* @v label Label string
* @v seed Seed material
* @v seed_len Length of seed material
* @v out Output buffer
* @v out_len Length of output buffer
*/
static void tlskey_hkdf_expand ( struct digest_algorithm *digest,
const void *secret, const char *label,
const void *seed, size_t seed_len,
void *out, size_t out_len ) {
static const char prefix[6] = "tls13 ";
const void *context = seed;
size_t context_len = seed_len;
size_t prefix_len = sizeof ( prefix );
size_t label_len = strlen ( label );
struct {
uint16_t len;
uint8_t label_len;
char prefix[prefix_len];
char label[label_len];
uint8_t context_len;
uint8_t context[context_len];
} __attribute__ (( packed )) info;
/* Construct additional information */
info.len = htons ( out_len );
info.label_len = ( prefix_len + label_len );
memcpy ( info.prefix, prefix, prefix_len );
memcpy ( info.label, label, label_len );
info.context_len = context_len;
memcpy ( info.context, context, context_len );
/* Generate output using HKDF */
hkdf_expand ( digest, secret, &info, sizeof ( info ), out, out_len );
}
/**
* Calculate empty digest value
*
* @v digest Digest algorithm
* @v empty Empty digest to fill in
*/
static void tlskey_hkdf_empty ( struct digest_algorithm *digest,
void *empty ) {
size_t ctxsize = digest->ctxsize;
uint8_t ctx[ctxsize];
/* Calculate empty digest */
digest_init ( digest, ctx );
digest_final ( digest, ctx, empty );
}
/**
* Calculate a finished MAC
*
* @v digest Digest algorithm
* @v secret Secret
* @v hash Message hash
* @v out Output buffer
*/
static void tlskey_hkdf_finished ( struct digest_algorithm *digest,
const void *secret, const void *hash,
void *out ) {
size_t digestsize = digest->digestsize;
size_t hctxsize = hmac_ctxsize ( digest );
struct {
uint8_t hctx[hctxsize];
uint8_t key[digestsize];
} tmp;
/* Generate HMAC key */
tlskey_hkdf_expand ( digest, secret, "finished", NULL, 0,
tmp.key, sizeof ( tmp.key ) );
/* Generate HMAC */
hmac_init ( digest, tmp.hctx, tmp.key, sizeof ( tmp.key ) );
hmac_update ( digest, tmp.hctx, hash, digestsize );
hmac_final ( digest, tmp.hctx, out );
/* Clear temporary secrets */
memset ( &tmp, 0, sizeof ( tmp ) );
}
/**
* Reset key schedule
*
* @v key Key schedule
*/
static void tlskey_hkdf_reset ( struct tls_key_schedule *tlskey ) {
struct digest_algorithm *digest = tlskey->digest;
size_t digestsize = digest->digestsize;
void *secret = tlskey->secret;
/* Allocate and initialise secrets */
tlskey->kdf.secret = secret; secret += digestsize;
tlskey->traffic[TLS_CLIENT].secret = secret; secret += digestsize;
tlskey->traffic[TLS_SERVER].secret = secret; secret += digestsize;
assert ( secret == ( tlskey->secret + tlskey->secretsize ) );
/* Initialise empty early secret */
hkdf_extract ( digest, NULL, 0, tlskey->kdf.secret, digestsize,
tlskey->kdf.secret );
}
/**
* Apply a new shared secret
*
* @v tlskey Key schedule
* @v shared New shared secret
* @v shared_len Length of new shared secret
* @ret rc Return status code
*/
static int tlskey_hkdf_apply ( struct tls_key_schedule *tlskey,
const void *shared, size_t shared_len ) {
struct digest_algorithm *digest = tlskey->digest;
size_t digestsize = digest->digestsize;
uint8_t empty[digestsize];
/* Generate derived secret */
tlskey_hkdf_empty ( digest, empty );
tlskey_expand ( tlskey, tlskey->kdf.secret, "derived", empty,
digestsize, tlskey->kdf.secret, digestsize );
DBGC2 ( tlskey, "TLSKEY %p derived:\n", tlskey );
DBGC2_HDA ( tlskey, 0, tlskey->kdf.secret, digestsize );
/* Extract new pseudorandom key */
hkdf_extract ( digest, tlskey->kdf.secret, digestsize, shared,
shared_len, tlskey->kdf.secret );
DBGC2 ( tlskey, "TLSKEY %p extracted PRK:\n", tlskey );
DBGC2_HDA ( tlskey, 0, tlskey->kdf.secret, digestsize );
return 0;
}
/**
* Generate master secret
*
* @v tlskey Key schedule
* @v ems Extended master secret extension is enabled
* @ret rc Return status code
*/
static int tlskey_hkdf_master ( struct tls_key_schedule *tlskey, int ems ) {
struct digest_algorithm *digest = tlskey->digest;
size_t digestsize = digest->digestsize;
uint8_t zero[digestsize];
int rc;
/* Extended master secret must always be used */
if ( ! ems ) {
DBGC ( tlskey, "TLSKEY %p requires an extended master "
"secret\n", tlskey );
return -EPROTO;
}
/* Generate master secret */
memset ( zero, 0, sizeof ( zero ) );
if ( ( rc = tlskey_apply ( tlskey, zero, sizeof ( zero ) ) ) != 0 )
return rc;
return 0;
}
/**
* Generate verification data
*
* @v tlskey Key schedule
* @v end Verification endpoint
* @v verify Verification data to fill in
* @v verify_len Length of verification data
* @ret rc Return status code
*/
static int tlskey_hkdf_verify ( struct tls_key_schedule *tlskey,
const struct tls_endpoint *end,
void *verify, size_t verify_len ) {
struct tls_traffic_secret *traffic = &tlskey->traffic[end->index];
struct digest_algorithm *digest = tlskey->digest;
size_t digestsize = digest->digestsize;
/* Verification data must be a complete digest output */
if ( verify_len != digestsize )
return -EINVAL;
/* Verification data is derived from handshake secrets */
if ( traffic->phase != &tls_handshake ) {
DBGC ( tlskey, "TLSKEY %p cannot generate %s verification "
"data without %s handshake traffic secrets\n",
tlskey, end->name, end->name );
return -EPROTO;
}
/* Generate verification data */
tlskey_hkdf_finished ( digest, traffic->secret,
tlskey->transcript.running, verify );
DBGC ( tlskey, "TLSKEY %p %s verification:\n", tlskey, end->name );
DBGC_HDA ( tlskey, 0, verify, verify_len );
return 0;
}
/**
* Generate traffic secret
*
* @v tlskey Key schedule
* @v writer Writer endpoint
* @v phase Traffic phase
* @ret rc Return status code
*/
static int tlskey_hkdf_traffic ( struct tls_key_schedule *tlskey,
const struct tls_endpoint *writer,
const struct tls_phase *phase ) {
struct tls_traffic_secret *traffic = &tlskey->traffic[writer->index];
struct digest_algorithm *digest = tlskey->digest;
size_t digestsize = digest->digestsize;
char label[ 13 /* "[c|s] [e|hs|ap] traffic" + NUL */ ];
/* Generate label */
snprintf ( label, sizeof ( label ), "%c %s traffic",
writer->name[0], phase->label );
/* Generate traffic secret */
tlskey_expand ( tlskey, tlskey->kdf.secret, label,
tlskey->transcript.finishing, digestsize,
traffic->secret, digestsize );
return 0;
}
/**
* Generate cipher key material
*
* @v tlskey Key schedule
* @v writer Writer endpoint
* @v key Cipher key to fill in
* @v key_len Length of cipher key
* @v iv Fixed portion of initialisation vector to fill in
* @v iv_len Length of fixed portion of initialisation vector
* @v mac MAC secret to fill in
* @v mac_len Length of MAC secret
* @ret rc Return status code
*/
static int tlskey_hkdf_cipher ( struct tls_key_schedule *tlskey,
const struct tls_endpoint *writer,
void *key, size_t key_len, void *iv,
size_t iv_len, void *mac __unused,
size_t mac_len ) {
struct tls_traffic_secret *traffic = &tlskey->traffic[writer->index];
struct digest_algorithm *digest = tlskey->digest;
size_t digestsize = digest->digestsize;
void *secret = traffic->secret;
/* The key schedule does not define a way to generate MAC secrets */
if ( mac_len ) {
DBGC ( tlskey, "TLSKEY %p does not support MAC secrets\n",
tlskey );
return -ENOTSUP;
}
/* Generate cipher key */
tlskey_expand ( tlskey, secret, "key", NULL, 0, key, key_len );
/* Generate initialisation vector */
tlskey_expand ( tlskey, secret, "iv", NULL, 0, iv, iv_len );
/* Update traffic secret, if applicable */
if ( traffic->phase == &tls_application ) {
tlskey_expand ( tlskey, secret, "traffic upd", NULL, 0,
secret, digestsize );
}
return 0;
}
/**
* Generate signable digest value
*
* @v tlskey Key schedule
* @v end Endpoint
* @v digest Signature digest algorithm
* @v data Additional data
* @v len Length of additional data
* @v tbs Signable digest value to fill in
* @ret rc Return status code
*/
static int tlskey_hkdf_tbshash ( struct tls_key_schedule *tlskey,
const struct tls_endpoint *end,
struct digest_algorithm *digest,
const void *data __unused, size_t len,
void *tbs ) {
size_t ctxsize = digest->ctxsize;
uint8_t ctx[ctxsize];
char buf[64];
int label_len;
/* There is no way to incorporate additional data */
if ( len ) {
DBGC ( tlskey, "TLSKEY %p cannot generate digest with "
"additional data\n", tlskey );
return -ENOTSUP;
}
/* Generate digest value (all input is public) */
digest_init ( digest, ctx );
memset ( buf, 0x20, sizeof ( buf ) );
digest_update ( digest, ctx, buf, sizeof ( buf ) );
label_len = snprintf ( buf, sizeof ( buf ),
"TLS 1.3, %s CertificateVerify", end->name );
assert ( label_len == 33 /* for both "client" and "server" */ );
digest_update ( digest, ctx, buf, ( label_len + 1 /* NUL */ ) );
digest_update ( digest, ctx, tlskey->transcript.running,
tlskey->digest->digestsize );
digest_final ( digest, ctx, tbs );
return 0;
}
/**
* Save pre-shared key
*
* @v tlskey Key schedule
* @v nonce Ticket nonce
* @v nonce_len Length of ticket nonce
* @v psk Pre-shared key to fill in
* @ret rc Return status code
*/
static int tlskey_hkdf_save ( struct tls_key_schedule *tlskey,
const void *nonce, size_t nonce_len,
struct tls_preshared_key *psk ) {
struct digest_algorithm *digest = tlskey->digest;
size_t digestsize = digest->digestsize;
struct {
uint8_t master[digestsize];
} tmp;
/* The pre-shared key currently uses a fixed-size buffer */
if ( digestsize > sizeof ( psk->key.resumption ) ) {
DBGC ( tlskey, "TLSKEY %p cannot save %s pre-shared key\n",
tlskey, digest->name );
return -ENOTSUP;
}
/* Generate resumption master secret */
tlskey_expand ( tlskey, tlskey->kdf.secret, "res master",
tlskey->transcript.finishing, digestsize,
tmp.master, sizeof ( tmp.master ) );
/* Generate resumption secret */
tlskey_expand ( tlskey, tmp.master, "resumption", nonce, nonce_len,
psk->key.resumption, digestsize );
/* Clear temporary secrets */
memset ( &tmp, 0, sizeof ( tmp ) );
return 0;
}
/**
* Load pre-shared key
*
* @v tlskey Key schedule
* @v psk Pre-shared key
* @ret rc Return status code
*/
static int tlskey_hkdf_load ( struct tls_key_schedule *tlskey,
const struct tls_preshared_key *psk ) {
struct digest_algorithm *digest = tlskey->digest;
size_t digestsize = digest->digestsize;
/* Sanity checks */
assert ( digest == psk->digest );
assert ( digestsize <= sizeof ( psk->key.resumption ) );
/* Extract early secret */
hkdf_extract ( digest, NULL, 0, &psk->key, digestsize,
tlskey->kdf.secret );
DBGC2 ( tlskey, "TLSKEY %p early secret:\n", tlskey );
DBGC2_HDA ( tlskey, 0, tlskey->kdf.secret, digestsize );
return 0;
}
/**
* Generate pre-shared key binder value
*
* @v psk Pre-shared key
* @v hash Partial transcript hash
* @v binder Binder value to fill in
* @v binder_len Length of binder value
* @ret rc Return status code
*/
static int tlskey_hkdf_bind ( const struct tls_preshared_key *psk,
const void *hash, void *binder,
size_t binder_len ) {
struct digest_algorithm *digest = psk->digest;
size_t digestsize = digest->digestsize;
uint8_t empty[digestsize];
struct {
uint8_t key[digestsize];
} tmp;
/* Binder value must be a complete digest output */
if ( binder_len != digestsize )
return -EINVAL;
/* Extract temporary copy of early secret */
hkdf_extract ( digest, NULL, 0, &psk->key, digestsize, tmp.key );
DBGC2 ( psk, "TLSKEY %p temporary early secret:\n", psk );
DBGC2_HDA ( psk, 0, tmp.key, sizeof ( tmp.key ) );
/* Generate resumption binder secret */
tlskey_hkdf_empty ( digest, empty );
tlskey_hkdf_expand ( digest, tmp.key, "res binder", empty, digestsize,
tmp.key, digestsize );
DBGC2 ( psk, "TLSKEY %p resumption binder secret:\n", psk );
DBGC2_HDA ( psk, 0, tmp.key, sizeof ( tmp.key ) );
/* Generate binder */
tlskey_hkdf_finished ( digest, tmp.key, hash, binder );
DBGC2 ( psk, "TLSKEY %p binder:\n", psk );
DBGC2_HDA ( psk, 0, binder, binder_len );
/* Clear temporary secrets */
memset ( &tmp, 0, sizeof ( tmp ) );
return 0;
}
/** TLS key schedule based on HKDF */
const struct tls_key_schedule_operations tlskey_hkdf = {
.name = "HKDF",
.accumulates = 1,
.mask = TLSKEY_KDF_KEYED,
.secretsize = tlskey_hkdf_secretsize,
.expand = tlskey_hkdf_expand,
.reset = tlskey_hkdf_reset,
.apply = tlskey_hkdf_apply,
.master = tlskey_hkdf_master,
.verify = tlskey_hkdf_verify,
.traffic = tlskey_hkdf_traffic,
.cipher = tlskey_hkdf_cipher,
.tbshash = tlskey_hkdf_tbshash,
.save = tlskey_hkdf_save,
.load = tlskey_hkdf_load,
.bind = tlskey_hkdf_bind,
};
/*****************************************************************************
*
* TLS version 1.2 key schedule using PRF based on P_Hash()
*
*****************************************************************************
*/
/** Minimum length for verification data */
#define TLSKEY_HASH_VERIFY_MIN 12
/**
* Calculate secret size
*
* @v digest Digest algorithm
* @ret secretsize Secret size, or zero if unsupported
*/
static size_t tlskey_hash_secretsize ( struct digest_algorithm *digest ) {
/* A P_Hash() key is an HMAC key */
return hmac_keysize ( digest );
}
/**
* Expand key material
*
* @v digest Digest algorithm
* @v secret Secret
* @v label Label string
* @v seed Seed material
* @v seed_len Length of seed material
* @v out Output buffer
* @v out_len Length of output buffer
*/
static void tlskey_hash_expand ( struct digest_algorithm *digest,
const void *secret, const char *label,
const void *seed, size_t seed_len,
void *out, size_t out_len ) {
size_t digestsize = digest->digestsize;
size_t hctxsize = hmac_ctxsize ( digest );
size_t frag_len = digestsize;
size_t label_len = strlen ( label );
unsigned int index = 0;
struct {
uint8_t ctx[2][hctxsize];
uint8_t a[digestsize];
uint8_t frag[digestsize];
} tmp;
/* Generate as much output as required */
while ( out_len ) {
/* Generate A(n) and output fragment */
hmac_init_key ( digest, tmp.ctx[0], secret );
if ( index ) {
hmac_update ( digest, tmp.ctx[0], tmp.a,
sizeof ( tmp.a ) );
memcpy ( tmp.ctx[1], tmp.ctx[0],
sizeof ( tmp.ctx[1] ) );
}
hmac_update ( digest, tmp.ctx[0], label, label_len );
hmac_update ( digest, tmp.ctx[0], seed, seed_len );
hmac_final ( digest, tmp.ctx[ ( index != 0 ) ], tmp.a );
if ( index++ == 0 )
continue;
hmac_final ( digest, tmp.ctx[0], tmp.frag );
/* Copy output */
if ( frag_len > out_len )
frag_len = out_len;
memcpy ( out, tmp.frag, frag_len );
/* Move to next fragment */
out += frag_len;
out_len -= frag_len;
}
/* Clear temporary secrets */
memset ( &tmp, 0, sizeof ( tmp ) );
}
/**
* Reset key schedule
*
* @v key Key schedule
*/
static void tlskey_hash_reset ( struct tls_key_schedule *tlskey ) {
void *secret = tlskey->secret;
/* Allocate and initialise secrets */
tlskey->kdf.secret = secret;
tlskey->traffic[TLS_CLIENT].secret = secret;
tlskey->traffic[TLS_SERVER].secret = secret;
}
/**
* Apply a new shared secret
*
* @v tlskey Key schedule
* @v shared New shared secret
* @v shared_len Length of new shared secret
* @ret rc Return status code
*/
static int tlskey_hash_apply ( struct tls_key_schedule *tlskey,
const void *shared, size_t shared_len ) {
struct digest_algorithm *digest = tlskey->digest;
size_t hctxsize = hmac_ctxsize ( digest );
struct {
uint8_t hctx[hctxsize];
} tmp;
/* Set HMAC key */
hmac_key ( digest, tmp.hctx, shared, shared_len, tlskey->kdf.secret );
/* Clear temporary secrets */
memset ( &tmp, 0, sizeof ( tmp ) );
return 0;
}
/**
* Generate master secret
*
* @v tlskey Key schedule
* @v ems Extended master secret extension is enabled
* @ret rc Return status code
*/
static int tlskey_hash_master ( struct tls_key_schedule *tlskey, int ems ) {
struct digest_algorithm *digest = tlskey->digest;
size_t digestsize = digest->digestsize;
const char *label;
const void *seed;
size_t seed_len;
struct {
uint8_t master[48];
} tmp;
int rc;
/* Master secret is derived from the client and server random
* values (or the full transcript digest).
*/
if ( ! ( tlskey->transcript.flags & TLSKEY_TSF_NONCED ) ) {
DBGC ( tlskey, "TLSKEY %p cannot generate master secret "
"without a digested nonce\n", tlskey );
return -EPROTO;
}
/* Generate master secret */
if ( ems ) {
label = "extended master secret";
seed = tlskey->transcript.running;
seed_len = digestsize;
} else {
label = "master secret";
seed = &tlskey->random;
seed_len = sizeof ( tlskey->random );
}
tlskey_expand ( tlskey, tlskey->kdf.secret, label, seed, seed_len,
tmp.master, sizeof ( tmp.master ) );
/* Apply master secret */
if ( ( rc = tlskey_apply ( tlskey, tmp.master,
sizeof ( tmp.master ) ) ) != 0 ) {
goto err_apply;
}
err_apply:
memset ( &tmp, 0, sizeof ( tmp ) );
return rc;
}
/**
* Generate verification data
*
* @v tlskey Key schedule
* @v end Verification endpoint
* @v verify Verification data to fill in
* @v verify_len Length of verification data
* @ret rc Return status code
*/
static int tlskey_hash_verify ( struct tls_key_schedule *tlskey,
const struct tls_endpoint *end,
void *verify, size_t verify_len ) {
struct digest_algorithm *digest = tlskey->digest;
size_t digestsize = digest->digestsize;
char label[ 16 /* "[client|server] finished" + NUL */ ];
/* Verification data must be at least 12 bytes for all digests */
if ( verify_len < TLSKEY_HASH_VERIFY_MIN )
return -EINVAL;
/* Verification data is derived from the master secret */
if ( ! ( tlskey->kdf.flags & TLSKEY_KDF_MASTER ) ) {
DBGC ( tlskey, "TLSKEY %p cannot generate verification data "
"without a master secret\n", tlskey );
return -EPROTO;
}
/* Construct label */
snprintf ( label, sizeof ( label ), "%s finished", end->name );
/* Generate verification data */
tlskey_expand ( tlskey, tlskey->kdf.secret, label,
tlskey->transcript.running, digestsize,
verify, verify_len );
return 0;
}
/**
* Generate traffic secret
*
* @v tlskey Key schedule
* @v writer Writer endpoint
* @v phase Traffic phase
* @ret rc Return status code
*/
static int tlskey_hash_traffic ( struct tls_key_schedule *tlskey,
const struct tls_endpoint *writer,
const struct tls_phase *phase ) {
struct tls_traffic_secret *traffic = &tlskey->traffic[writer->index];
/* Only application phase keys are supported */
if ( phase != &tls_application ) {
DBGC ( tlskey, "TLSKEY %p does not support %s traffic keys\n",
tlskey, phase->name );
return -ENOTSUP;
}
/* There are no separate traffic secrets */
assert ( traffic->secret == tlskey->kdf.secret );
return 0;
}
/**
* Generate cipher key material
*
* @v tlskey Key schedule
* @v writer Writer endpoint
* @v key Cipher key to fill in
* @v key_len Length of cipher key
* @v iv Fixed portion of initialisation vector to fill in
* @v iv_len Length of fixed portion of initialisation vector
* @v mac MAC secret to fill in
* @v mac_len Length of MAC secret
* @ret rc Return status code
*/
static int tlskey_hash_cipher ( struct tls_key_schedule *tlskey,
const struct tls_endpoint *writer,
void *key, size_t key_len, void *iv,
size_t iv_len, void *mac, size_t mac_len ) {
size_t total = ( 2 * ( mac_len + key_len + iv_len ) );
struct {
uint8_t key[total];
} tmp;
struct {
struct tls_random server;
struct tls_random client;
} __attribute__ (( packed )) seed;
const void *src;
size_t mask;
/* Construct seed (with swapped client/server random bytes) */
memcpy ( &seed.server, &tlskey->random[TLS_SERVER],
sizeof ( seed.server ) );
memcpy ( &seed.client, &tlskey->random[TLS_CLIENT],
sizeof ( seed.client ) );
/* Generate key material */
tlskey_expand ( tlskey, tlskey->kdf.secret, "key expansion", &seed,
sizeof ( seed ), tmp.key, sizeof ( tmp.key ) );
/* Partition key material */
src = tmp.key;
mask = ( ( writer->index == TLS_CLIENT ) ? 0 : -1UL );
memcpy ( mac, ( src + ( mac_len & mask ) ), mac_len );
src += ( 2 * mac_len );
memcpy ( key, ( src + ( key_len & mask ) ), key_len );
src += ( 2 * key_len );
memcpy ( iv, ( src + ( iv_len & mask ) ), iv_len );
src += ( 2 * iv_len );
assert ( src == &tmp.key[total] );
/* Clear temporary secrets */
memset ( &tmp, 0, sizeof ( tmp ) );
return 0;
}
/**
* Generate signable digest value
*
* @v tlskey Key schedule
* @v end Endpoint
* @v digest Signature digest algorithm
* @v data Additional data
* @v len Length of additional data
* @v tbs Signable digest value to fill in
* @ret rc Return status code
*/
static int tlskey_hash_tbshash ( struct tls_key_schedule *tlskey,
const struct tls_endpoint *end,
struct digest_algorithm *digest,
const void *data, size_t len, void *tbs ) {
size_t ctxsize = digest->ctxsize;
struct {
uint8_t ctx[ctxsize];
} tmp;
/* Generate endpoint-specific digest value */
if ( end->index == TLS_CLIENT ) {
/* The client CertificateVerify digest value is the
* raw transcript digest. We retain only a single
* running transcript digest, and can therefore
* provide this only for a digest algorithm that
* matches our transcript digest algorithm.
*/
if ( digest != tlskey->digest ) {
DBGC ( tlskey, "TLSKEY %p cannot generate %s "
"transcript digest\n", tlskey, digest->name );
return -ENOTSUP;
}
/* There is no way to incorporate additional data */
if ( len ) {
DBGC ( tlskey, "TLSKEY %p cannot generate digest "
"with additional data\n", tlskey );
return -ENOTSUP;
}
/* Copy transcript digest value */
memcpy ( tbs, tlskey->transcript.running,
digest->digestsize );
} else {
/* Additional data (i.e. the ServerKeyExchange
* parameters) must be incorporated, since otherwise
* the digest does not cover the parameters used to
* establish the shared secret.
*/
if ( ! len ) {
DBGC ( tlskey, "TLSKEY %p cannot generate digest "
"without additional data\n", tlskey );
return -ENOTSUP;
}
/* Generate ServerKeyExchange digest */
digest_init ( digest, tmp.ctx );
digest_update ( digest, tmp.ctx, &tlskey->random,
sizeof ( tlskey->random ) );
digest_update ( digest, tmp.ctx, data, len );
digest_final ( digest, tmp.ctx, tbs );
}
/* Clear temporary secrets */
memset ( &tmp, 0, sizeof ( tmp ) );
return 0;
}
/**
* Save pre-shared key
*
* @v tlskey Key schedule
* @v nonce Ticket nonce
* @v nonce_len Length of ticket nonce
* @v psk Pre-shared key to fill in
* @ret rc Return status code
*/
static int tlskey_hash_save ( struct tls_key_schedule *tlskey,
const void *nonce __unused, size_t nonce_len,
struct tls_preshared_key *psk ) {
struct digest_algorithm *digest = tlskey->digest;
/* A ticket nonce is not supported */
if ( nonce_len )
return -ENOTSUP;
/* For TLS version 1.2, the pre-master secret may be any
* length but the master secret is fixed at 48 bytes. This is
* smaller than the block size for all supported digest
* algorithms. The HMAC key constructed from the master
* secret will therefore be just the zero-padded master secret
* value. We can therefore preserve just these first 48 bytes
* of the KDF master secret (ignoring the zero padding up to
* the digest block size).
*/
assert ( sizeof ( psk->key.master_secret ) <=
hmac_keysize ( digest ) );
memcpy ( &psk->key.master_secret, tlskey->kdf.secret,
sizeof ( psk->key.master_secret ) );
return 0;
}
/**
* Load pre-shared key
*
* @v tlskey Key schedule
* @v psk Pre-shared key
* @ret rc Return status code
*/
static int tlskey_hash_load ( struct tls_key_schedule *tlskey,
const struct tls_preshared_key *psk ) {
const void *secret;
size_t secret_len;
int rc;
/* For TLS versions 1.2 and earlier, the pre-shared key
* material contains the master secret and so we just apply
* this as the key derivation function secret.
*/
secret = &psk->key.master_secret;
secret_len = sizeof ( psk->key.master_secret );
if ( ( rc = tlskey_apply ( tlskey, secret, secret_len ) ) != 0 )
return rc;
return 0;
}
/**
* Generate pre-shared key binder value
*
* @v psk Pre-shared key
* @v hash Partial transcript hash
* @v binder Binder value to fill in
* @v binder_len Length of binder value
* @ret rc Return status code
*/
static int tlskey_hash_bind ( const struct tls_preshared_key *psk __unused,
const void *hash __unused,
void *binder __unused, size_t binder_len ) {
/* Binder values are not supported */
if ( binder_len )
return -ENOTSUP;
return 0;
}
/** TLS key schedule based on P_Hash() */
const struct tls_key_schedule_operations tlskey_hash = {
.name = "P_Hash()",
.accumulates = 0,
.mask = ( TLSKEY_KDF_KEYED | TLSKEY_KDF_MASTER | TLSKEY_KDF_EMS ),
.secretsize = tlskey_hash_secretsize,
.expand = tlskey_hash_expand,
.reset = tlskey_hash_reset,
.apply = tlskey_hash_apply,
.master = tlskey_hash_master,
.verify = tlskey_hash_verify,
.traffic = tlskey_hash_traffic,
.cipher = tlskey_hash_cipher,
.tbshash = tlskey_hash_tbshash,
.save = tlskey_hash_save,
.load = tlskey_hash_load,
.bind = tlskey_hash_bind,
};
/*****************************************************************************
*
* TLS version 1.0/1.1 key schedule using PRF based on P_MD5()+P_SHA1()
*
*****************************************************************************
*/
/**
* Calculate secret size
*
* @v digest Digest algorithm
* @ret secretsize Secret size, or zero if unsupported
*/
static size_t tlskey_md5_sha1_secretsize ( struct digest_algorithm *digest ) {
struct md5_sha1_hmac_keys *hkeys;
size_t secretsize;
/* P_MD5()+P_SHA1() can be used only with the MD5+SHA1 algorithm */
if ( digest != &md5_sha1_algorithm )
return 0;
/* A P_MD5()+P_SHA1() key is a split HMAC-MD5 and HMAC-SHA1 key */
assert ( sizeof ( hkeys->md5 ) == hmac_keysize ( &md5_algorithm ) );
assert ( sizeof ( hkeys->sha1 ) == hmac_keysize ( &sha1_algorithm ) );
secretsize = sizeof ( *hkeys );
return secretsize;
}
/**
* Expand key material
*
* @v digest Digest algorithm
* @v secret Secret
* @v label Label string
* @v seed Seed material
* @v seed_len Length of seed material
* @v out Output buffer
* @v out_len Length of output buffer
*/
static void tlskey_md5_sha1_expand ( struct digest_algorithm *digest,
const void *secret, const char *label,
const void *seed, size_t seed_len,
void *out, size_t out_len ) {
const struct md5_sha1_hmac_keys *hkeys = secret;
struct {
uint8_t sha1[out_len];
} tmp;
uint8_t *xor;
unsigned int i;
/* P_MD5()+P_SHA1() can be used only with the MD5+SHA1 algorithm */
assert ( digest == &md5_sha1_algorithm );
/* Generate MD5 portion into output buffer */
tlskey_hash_expand ( &md5_algorithm, hkeys->md5, label, seed,
seed_len, out, out_len );
/* Generate SHA-1 portion into temporary buffer */
tlskey_hash_expand ( &sha1_algorithm, hkeys->sha1, label, seed,
seed_len, tmp.sha1, out_len );
/* XOR together into output buffer */
xor = out;
for ( i = 0 ; i < out_len ; i++ )
xor[i] ^= tmp.sha1[i];
/* Clear temporary secrets */
memset ( &tmp, 0, sizeof ( tmp ) );
}
/**
* Apply a new shared secret
*
* @v tlskey Key schedule
* @v shared New shared secret
* @v shared_len Length of new shared secret
* @ret rc Return status code
*/
static int tlskey_md5_sha1_apply ( struct tls_key_schedule *tlskey,
const void *shared, size_t shared_len ) {
struct digest_algorithm *digest = tlskey->digest;
struct md5_sha1_hmac_keys *hkeys = tlskey->kdf.secret;
size_t hctxsize = hmac_ctxsize ( digest );
struct {
uint8_t hctx[hctxsize];
} tmp;
const void *shared_md5;
const void *shared_sha1;
size_t shared_sublen;
/* P_MD5()+P_SHA1() can be used only with the MD5+SHA1 algorithm */
assert ( digest == &md5_sha1_algorithm );
/* Split secret into two, with an overlap of up to one byte */
shared_sublen = ( ( shared_len + 1 ) / 2 );
shared_md5 = shared;
shared_sha1 = ( shared + shared_len - shared_sublen );
/* Set HMAC-MD5 key */
assert ( sizeof ( tmp.hctx ) >= hmac_ctxsize ( &md5_algorithm ) );
hmac_key ( &md5_algorithm, tmp.hctx, shared_md5, shared_sublen,
hkeys->md5 );
/* Set HMAC-SHA1 key */
assert ( sizeof ( tmp.hctx ) >= hmac_ctxsize ( &sha1_algorithm ) );
hmac_key ( &sha1_algorithm, tmp.hctx, shared_sha1, shared_sublen,
hkeys->sha1 );
/* Clear temporary secrets */
memset ( &tmp, 0, sizeof ( tmp ) );
return 0;
}
/**
* Save pre-shared key
*
* @v tlskey Key schedule
* @v nonce Ticket nonce
* @v nonce_len Length of ticket nonce
* @v psk Pre-shared key
* @ret rc Return status code
*/
static int tlskey_md5_sha1_save ( struct tls_key_schedule *tlskey,
const void *nonce __unused,
size_t nonce_len,
struct tls_preshared_key *psk ) {
struct digest_algorithm *digest = tlskey->digest;
struct md5_sha1_hmac_keys *hkeys = tlskey->kdf.secret;
/* P_MD5()+P_SHA1() can be used only with the MD5+SHA1 algorithm */
assert ( digest == &md5_sha1_algorithm );
/* A ticket nonce is not supported */
if ( nonce_len )
return -ENOTSUP;
/* For TLS versions 1.1 and earlier, the pre-master secret may
* be any length but the master secret is fixed at 48 bytes.
* It will have been split to become 24 bytes in each of the
* MD5 and SHA-1 HMAC keys.
*/
assert ( sizeof ( psk->key.master_secret.md5 ) <=
hmac_keysize ( &md5_algorithm ) );
assert ( sizeof ( psk->key.master_secret.sha1 ) <=
hmac_keysize ( &sha1_algorithm ) );
memcpy ( psk->key.master_secret.md5, hkeys->md5,
sizeof ( psk->key.master_secret.md5 ) );
memcpy ( psk->key.master_secret.sha1, hkeys->sha1,
sizeof ( psk->key.master_secret.sha1 ) );
return 0;
}
/** TLS key schedule based on P_MD5()+P_SHA1() */
const struct tls_key_schedule_operations tlskey_md5_sha1 = {
.name = "P_MD5()+P_SHA1()",
.accumulates = 0,
.mask = ( TLSKEY_KDF_KEYED | TLSKEY_KDF_MASTER | TLSKEY_KDF_EMS ),
.secretsize = tlskey_md5_sha1_secretsize,
.expand = tlskey_md5_sha1_expand,
.reset = tlskey_hash_reset,
.apply = tlskey_md5_sha1_apply,
.master = tlskey_hash_master,
.verify = tlskey_hash_verify,
.traffic = tlskey_hash_traffic,
.cipher = tlskey_hash_cipher,
.tbshash = tlskey_hash_tbshash,
.save = tlskey_md5_sha1_save,
.load = tlskey_hash_load,
.bind = tlskey_hash_bind,
};