| /* | |
| * 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 (at your option) 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 ); | |
| #include <string.h> | |
| #include <errno.h> | |
| #include <ipxe/malloc.h> | |
| #include <ipxe/crypto.h> | |
| #include <ipxe/hkdf.h> | |
| #include <ipxe/rbg.h> | |
| #include <ipxe/channel.h> | |
| /** @file | |
| * | |
| * Secure channel abstraction | |
| * | |
| * We define a concept of a secure channel as comprising a pair of | |
| * ciphers (one for transmit, one for receive) together with the | |
| * cryptographic state required to establish that these ciphers may be | |
| * used for encrypted communication with a trusted peer. The model is | |
| * loosely constructed as a generalisation of TLS minus the protocol | |
| * specifics. | |
| * | |
| * The channel starts out with both ciphers operating in plaintext | |
| * mode. Over this initially unencrypted channel, a shared secret is | |
| * agreed, bound to a peer identity, and used to derive keys for the | |
| * transmit and receive ciphers. | |
| * | |
| * The major channel operations are: | |
| * | |
| * - Key exchange: a shared secret is agreed (e.g. via Ephemeral | |
| * Diffie-Hellman key exchange) | |
| * | |
| * - Key binding: the shared secret is bound to a peer identity | |
| * (i.e. to a long-term public key as recorded in an X.509 | |
| * certificate) | |
| * | |
| * - Key derivation: the transmit and receive cipher keys are | |
| * derived from the shared secret | |
| * | |
| * - Key confirmation: the peer demonstrates possession of the | |
| * identity-bound shared secret | |
| * | |
| * - Identity validation: the long-term public key representing the | |
| * peer identity is validated as being trusted and as matching the | |
| * required identity (e.g. a TLS host name) | |
| * | |
| * The channel maintains the following security properties: | |
| * | |
| * - "keyed": a boolean flag indicating that the channel contains | |
| * key material derived from the shared secret | |
| * | |
| * - "bound": a certificate reference representing the peer identity | |
| * to which the shared secret has been bound | |
| * | |
| * - "confirmed": a certificate reference representing the peer | |
| * identity that has demonstrated possession of the identity-bound | |
| * shared secret | |
| * | |
| * - "established": a certificate reference representing the peer | |
| * identity that has demonstrated possession of the identity-bound | |
| * shared secret and has been verified as valid | |
| * | |
| * The peer identities are stored as references to X.509 certificates. | |
| * The secure channel conceptual model is agnostic about how peer | |
| * identities may be represented, but X.509 certificates are most | |
| * convenient for use within iPXE. Validation of the certificate | |
| * chain is outside the scope of the secure channel and must be | |
| * performed separately. | |
| * | |
| * When the channel has a "confirmed" certificate reference and that | |
| * certificate has separately been marked as valid, the secure channel | |
| * may become established and may subsequently be used to transmit and | |
| * receive application data. | |
| * | |
| * The channel is a passive entity: it does not itself initiate any | |
| * messages. All communication is driven by the consumer (such as the | |
| * TLS protocol). The set of operations described above that are used | |
| * to establish a secure channel must be performed by the consumer | |
| * according to its protocol definition. | |
| * | |
| * The order of these operations may vary based upon both the protocol | |
| * definition and the options negotiated at runtime with the peer. | |
| * The secure channel abstraction is responsible for maintaining the | |
| * listed security properties and determining whether or not the | |
| * required security conditions have been met, regardless of the order | |
| * in which operations are performed by the consumer. | |
| * | |
| */ | |
| /* Disambiguate the various error causes */ | |
| #define ENOTTY_TRANSPORT __einfo_error ( EINFO_ENOTTY_TRANSPORT ) | |
| #define EINFO_ENOTTY_TRANSPORT \ | |
| __einfo_uniqify ( EINFO_ENOTTY, 0x01, "Untransportable key" ) | |
| #define EPERM_NOT_CONFIDENTIAL __einfo_error ( EINFO_EPERM_NOT_CONFIDENTIAL ) | |
| #define EINFO_EPERM_NOT_CONFIDENTIAL \ | |
| __einfo_uniqify ( EINFO_EPERM, 0x01, "Not confidential" ) | |
| #define EPERM_NOT_VALID __einfo_error ( EINFO_EPERM_NOT_VALID ) | |
| #define EINFO_EPERM_NOT_VALID \ | |
| __einfo_uniqify ( EINFO_EPERM, 0x02, "Peer certificate not valid" ) | |
| #define EPROTO_NOT_KEYED __einfo_error ( EINFO_EPROTO_NOT_KEYED ) | |
| #define EINFO_EPROTO_NOT_KEYED \ | |
| __einfo_uniqify ( EINFO_EPROTO, 0x01, "No shared key material" ) | |
| #define EPROTO_NOT_BOUND __einfo_error ( EINFO_EPROTO_NOT_BOUND ) | |
| #define EINFO_EPROTO_NOT_BOUND \ | |
| __einfo_uniqify ( EINFO_EPROTO, 0x02, "No bound peer identity" ) | |
| #define EPROTO_NOT_CONFIRMED __einfo_error ( EINFO_EPROTO_NOT_CONFIRMED ) | |
| #define EINFO_EPROTO_NOT_CONFIRMED \ | |
| __einfo_uniqify ( EINFO_EPROTO, 0x03, "No confirmed peer identity" ) | |
| static void channel_unbind ( struct secure_channel *channel ); | |
| static void channel_unconfirm ( struct secure_channel *channel ); | |
| static void channel_unestablish ( struct secure_channel *channel ); | |
| /***************************************************************************** | |
| * | |
| * Ephemeral secrets | |
| * | |
| ***************************************************************************** | |
| * | |
| * The channel maintains a single ephemeral master secret that can be | |
| * used to derive an arbitrary number of other ephemeral secrets | |
| * (e.g. for Diffie-Hellman key exchange). | |
| * | |
| * The use of a single ephemeral master secret avoids the need for key | |
| * exchange mechanisms to retain individual copies of their ephemeral | |
| * private keys, since those private keys can always be regenerated on | |
| * demand. | |
| * | |
| * The ephemeral secret mechanism may also be used to derive other | |
| * temporary secrets that may be required by the consumer (e.g. TLS | |
| * client random data, or block cipher initialisation vectors). | |
| * | |
| */ | |
| /** | |
| * Initialise ephemeral master secret | |
| * | |
| * @v channel Secure channel | |
| * @ret rc Return status code | |
| */ | |
| static int channel_ephemeral_init ( struct secure_channel *channel ) { | |
| struct digest_algorithm *digest = &channel_ephemeral_algorithm; | |
| static const char salt[16] = "channel master"; | |
| int rc; | |
| /* Generate random bits with no additional input and without | |
| * prediction resistance | |
| */ | |
| if ( ( rc = rbg_generate ( NULL, 0, 0, channel->ephemeral, | |
| sizeof ( channel->ephemeral ) ) ) != 0 ) { | |
| DBGC ( channel, "CHANNEL %p could not generate random data: " | |
| "%s\n", channel, strerror ( rc ) ); | |
| return rc; | |
| } | |
| /* Generate ephemeral master secret */ | |
| hkdf_extract ( digest, salt, sizeof ( salt ), channel->ephemeral, | |
| sizeof ( channel->ephemeral ), channel->ephemeral ); | |
| return 0; | |
| } | |
| /** | |
| * Generate ephemeral secret | |
| * | |
| * @v channel Secure channel | |
| * @v info Additional information | |
| * @v info_len Length of additional information | |
| * @v out Ephemeral secret to fill in | |
| * @v len Length of ephemeral secret | |
| */ | |
| void channel_ephemeral ( struct secure_channel *channel, const void *info, | |
| size_t info_len, void *out, size_t len ) { | |
| struct digest_algorithm *digest = &channel_ephemeral_algorithm; | |
| /* Additional info should always be provided */ | |
| assert ( info != NULL ); | |
| assert ( info_len > 0 ); | |
| /* Generate from ephemeral master secret and additional information */ | |
| hkdf_expand ( digest, channel->ephemeral, info, info_len, out, len ); | |
| } | |
| /** | |
| * Generate labelled ephemeral secret | |
| * | |
| * @v channel Secure channel | |
| * @v label Additional information string | |
| * @v out Ephemeral secret to fill in | |
| * @v len Length of ephemeral secret | |
| */ | |
| void channel_ephemeral_label ( struct secure_channel *channel, | |
| const char *label, void *out, size_t len ) { | |
| /* Generate from ephemeral master secret and label */ | |
| channel_ephemeral ( channel, label, strlen ( label ), out, len ); | |
| DBGC2 ( channel, "CHANNEL %p ephemeral \"%s\":\n", channel, label ); | |
| DBGC2_HDA ( channel, 0, out, len ); | |
| } | |
| /** | |
| * Replace ephemeral master secret | |
| * | |
| * @v channel Secure channel | |
| */ | |
| static void channel_ephemeral_replace ( struct secure_channel *channel ) { | |
| /* Generate a new ephemeral master key */ | |
| channel_ephemeral_label ( channel, "channel replace", | |
| channel->ephemeral, | |
| sizeof ( channel->ephemeral ) ); | |
| } | |
| /***************************************************************************** | |
| * | |
| * Key exchange | |
| * | |
| ***************************************************************************** | |
| * | |
| * Key exchange (or key transport) is used over the as-yet unencrypted | |
| * channel to obtain a shared secret (e.g. a TLS pre-master secret | |
| * obtained using ECDHE). The shared secret is then used to derive | |
| * the channel key material (e.g. a TLS master secret). | |
| * | |
| * The channel is responsible for generating the ephemeral private | |
| * keys (and the corresponding public keys), and for handling the | |
| * underlying mechanics of the key exchange algorithm to obtain a | |
| * shared secret. | |
| * | |
| * The consumer is responsible for incorporating the resulting shared | |
| * secret into its key schedule to produce the key material required | |
| * by the consumer to operate the channel (e.g. to derive traffic | |
| * keys). | |
| * | |
| * The channel does not itself dictate how the key material is | |
| * produced, since the key schedule is necessarily defined by the | |
| * protocol in use (e.g. TLS). | |
| * | |
| * Key exchange alone provides no authentication of the peer. Key | |
| * binding (see below) must be used to bind the shared secret to a | |
| * peer identity. | |
| * | |
| */ | |
| /** | |
| * Clear shared secret | |
| * | |
| * @v channel Secure channel | |
| */ | |
| void channel_unkey ( struct secure_channel *channel ) { | |
| /* Key binding is defined only for the current shared secret, | |
| * and so any existing bound peer identity must be cleared. | |
| */ | |
| channel_unbind ( channel ); | |
| /* Clear shared key */ | |
| channel->props.keyed = 0; | |
| /* Reset the key schedule */ | |
| channel->op->reset ( channel ); | |
| } | |
| /** | |
| * Generate private key | |
| * | |
| * @v channel Secure channel | |
| * @v exchange Key exchange algorithm | |
| * @v private Private key to fill in | |
| */ | |
| static void channel_key_private ( struct secure_channel *channel, | |
| struct exchange_algorithm *exchange, | |
| void *private ) { | |
| size_t privsize = exchange->privsize; | |
| const char *label; | |
| /* Use exchange algorithm name as additional information */ | |
| label = exchange->name; | |
| /* (Re)generate private key | |
| * | |
| * Note that key exchange private keys are always small enough | |
| * to fit on the stack, even for algorithms such as FFDHE that | |
| * have large public and shared keys. | |
| */ | |
| channel_ephemeral_label ( channel, label, private, privsize ); | |
| } | |
| /** | |
| * Share public key | |
| * | |
| * @v channel Secure channel | |
| * @v exchange Key exchange algorithm | |
| * @v public Public key to fill in | |
| * @ret rc Return status code | |
| */ | |
| int channel_key_share ( struct secure_channel *channel, | |
| struct exchange_algorithm *exchange, void *public ) { | |
| size_t privsize = exchange->privsize; | |
| size_t pubsize = exchange->pubsize; | |
| uint8_t private[privsize]; | |
| int rc; | |
| /* (Re)generate private key */ | |
| channel_key_private ( channel, exchange, private ); | |
| /* Generate public key */ | |
| if ( ( rc = exchange_share ( exchange, private, public ) ) != 0 ) { | |
| DBGC ( channel, "CHANNEL %p could not share \"%s\": %s\n", | |
| channel, exchange->name, strerror ( rc ) ); | |
| goto err_share; | |
| } | |
| /* Show public key (for debugging) */ | |
| DBGC2 ( channel, "CHANNEL %p sharing \"%s\":\n", | |
| channel, exchange->name ); | |
| DBGC2_HDA ( channel, 0, public, pubsize ); | |
| err_share: | |
| /* Erase private key */ | |
| memset ( private, 0, sizeof ( private ) ); | |
| return rc; | |
| } | |
| /** | |
| * Generate and apply shared secret | |
| * | |
| * @v channel Secure channel | |
| * @v exchange Key exchange algorithm | |
| * @v partner Partner public key | |
| * @v shared Shared secret to fill in | |
| * @ret rc Return status code | |
| */ | |
| static int channel_key ( struct secure_channel *channel, | |
| struct exchange_algorithm *exchange, | |
| const void *partner, void *shared ) { | |
| size_t sharedsize = exchange->sharedsize; | |
| size_t privsize = exchange->privsize; | |
| uint8_t private[privsize]; | |
| int accumulated; | |
| int rc; | |
| /* (Re)generate private key */ | |
| channel_key_private ( channel, exchange, private ); | |
| /* Generate shared secret */ | |
| if ( ( rc = exchange_agree ( exchange, private, partner, | |
| shared ) ) != 0 ) { | |
| DBGC ( channel, "CHANNEL %p could not agree \"%s\": %s\n", | |
| channel, exchange->name, strerror ( rc ) ); | |
| goto err_agree; | |
| } | |
| /* Show shared secret (for debugging) */ | |
| DBGC2 ( channel, "CHANNEL %p agreed \"%s\":\n", | |
| channel, exchange->name ); | |
| DBGC2_HDA ( channel, 0, shared, sharedsize ); | |
| /* Apply shared secret to key schedule */ | |
| accumulated = 0; | |
| if ( ( rc = channel->op->apply ( channel, exchange, shared, | |
| &accumulated ) ) != 0 ) { | |
| DBGC ( channel, "CHANNEL %p could not apply \"%s\": %s\n", | |
| channel, exchange->name, strerror ( rc ) ); | |
| goto err_apply; | |
| } | |
| /* If the accumulation flag was not set, then the ability of | |
| * the peer to subsequently demonstrate possession of the key | |
| * material suffices to demonstrate possession of this shared | |
| * secret, but does not necessarily suffice to demonstrate | |
| * possession of any previously applied shared secrets. Any | |
| * binding of the peer identity is therefore no longer valid. | |
| */ | |
| if ( ! accumulated ) | |
| channel_unbind ( channel ); | |
| /* Channel now possesses a shared secret */ | |
| channel->props.keyed = 1; | |
| DBGC ( channel, "CHANNEL %p key material obtained via \"%s\"\n", | |
| channel, exchange->name ); | |
| err_apply: | |
| err_agree: | |
| /* Clear generated shared secret on any error */ | |
| if ( rc != 0 ) | |
| channel_unkey ( channel ); | |
| /* Erase private key */ | |
| memset ( private, 0, sizeof ( private ) ); | |
| return rc; | |
| } | |
| /** | |
| * Agree shared secret | |
| * | |
| * @v channel Secure channel | |
| * @v exchange Key exchange algorithm | |
| * @v partner Partner public key | |
| * @ret rc Return status code | |
| */ | |
| int channel_key_agree ( struct secure_channel *channel, | |
| struct exchange_algorithm *exchange, | |
| const void *partner ) { | |
| size_t sharedsize = exchange->sharedsize; | |
| size_t pubsize = exchange->pubsize; | |
| void *tmp; | |
| int rc; | |
| /* Show partner public key (for debugging) */ | |
| DBGC2 ( channel, "CHANNEL %p partner \"%s\":\n", | |
| channel, exchange->name ); | |
| DBGC2_HDA ( channel, 0, partner, pubsize ); | |
| /* Allocate space for shared secret (may be too large for stack) */ | |
| tmp = zalloc ( sharedsize ); | |
| if ( ! tmp ) { | |
| rc = -ENOMEM; | |
| goto err_alloc; | |
| } | |
| /* Generate and apply shared secret */ | |
| if ( ( rc = channel_key ( channel, exchange, partner, tmp ) ) != 0 ) | |
| goto err_key; | |
| err_key: | |
| /* Erase shared secret */ | |
| zfree ( tmp ); | |
| err_alloc: | |
| return rc; | |
| } | |
| /** | |
| * Transport shared secret | |
| * | |
| * @v channel Secure channel | |
| * @v exchange Key exchange algorithm | |
| * @v shared Shared secret to fill in | |
| * @ret rc Return status code | |
| * | |
| * Key transport is modelled as a key exchange algorithm with a public | |
| * key size of zero: the shared secret is unilaterally generated by | |
| * the local endpoint and must then be transported to the peer. | |
| */ | |
| static int channel_key_transport ( struct secure_channel *channel, | |
| struct exchange_algorithm *exchange, | |
| void *shared ) { | |
| int rc; | |
| /* Sanity check */ | |
| if ( ! is_key_transport ( exchange ) ) { | |
| DBGC ( channel, "CHANNEL %p cannot transport \"%s\"\n", | |
| channel, exchange->name ); | |
| return -ENOTTY_TRANSPORT; | |
| } | |
| /* Generate and apply shared secret */ | |
| if ( ( rc = channel_key ( channel, exchange, NULL, shared ) ) != 0 ) | |
| return rc; | |
| return 0; | |
| } | |
| /***************************************************************************** | |
| * | |
| * Key binding | |
| * | |
| ***************************************************************************** | |
| * | |
| * The shared secret that results from key exchange must be bound to a | |
| * peer identity. This binding represents a delegation of authority | |
| * from the peer's long-term authentication key (e.g. the RSA private | |
| * key corresponding to its X.509 certificate) to the shared secret | |
| * for the purpose of authenticating the channel by subsequently | |
| * confirming possession of the shared secret. | |
| * | |
| * There are two distinct mechanisms through which this delegation may | |
| * take place: | |
| * | |
| * - If the shared secret is obtained using key exchange, then the | |
| * peer may delegate authority to the shared secret by using its | |
| * long-term authentication key to sign a digest that covers at | |
| * least its own ephemeral public key (e.g. the Diffie-Hellman | |
| * parameters provided in a ServerKeyExchange record) and a | |
| * challenge nonce (e.g. the TLS client random bytes). The | |
| * signature over its public ephemeral key represents a choice by | |
| * the peer to allow possession of the corresponding ephemeral | |
| * private key to be used to demonstrate authenticity of the | |
| * channel. The local endpoint trusts the peer's long-term | |
| * identity to make this class of choice, and verifies the | |
| * signature to authenticate that the peer's long-term identity | |
| * made this choice. The possession of the ephemeral private key | |
| * is subsequently demonstrated by key confirmation, which | |
| * demonstrates possession of the shared secret (and hence of the | |
| * ephemeral private key used to obtain the shared secret). | |
| * | |
| * - If the shared secret is obtained using key transport, then the | |
| * local endpoint may delegate authority to the shared secret by | |
| * encrypting the unilaterally chosen shared secret using the | |
| * peer's long-term authentication key. The encryption represents | |
| * a choice by the local endpoint to allow possession of the | |
| * shared secret (as subsequently demonstrated by key | |
| * confirmation) to be used as evidence of possession of the | |
| * peer's long-term authentication key. | |
| * | |
| * In both cases, the end result is the same: the shared secret | |
| * (whether exchanged or transported) may be relied upon to | |
| * authenticate the channel as being connected to the peer. | |
| * | |
| * The shared secret may be bound to an identity that has not yet been | |
| * validated. The consumer is responsible for separately performing | |
| * validation of the identity (e.g. validation of the full X.509 | |
| * certificate chain, including OCSP checks) at some point before | |
| * relying upon the channel to protect application data. | |
| * | |
| */ | |
| /** | |
| * Clear bound peer identity | |
| * | |
| * @v channel Secure channel | |
| */ | |
| static void channel_unbind ( struct secure_channel *channel ) { | |
| /* Key confirmation is defined only for the currently bound | |
| * identity, and so any existing confirmed peer identity must | |
| * be cleared. | |
| */ | |
| channel_unconfirm ( channel ); | |
| /* Drop and clear reference */ | |
| x509_put ( channel->props.bound ); | |
| channel->props.bound = NULL; | |
| } | |
| /** | |
| * Set bound peer identity | |
| * | |
| * @v channel Secure channel | |
| * @v identity Identity to be bound | |
| * @ret rc Return status code | |
| */ | |
| static int channel_bind ( struct secure_channel *channel, | |
| struct x509_certificate *identity ) { | |
| /* Clear any existing binding */ | |
| channel_unbind ( channel ); | |
| /* Fail if there is no shared secret to be bound */ | |
| if ( ! channel->props.keyed ) { | |
| DBGC ( channel, "CHANNEL %p cannot bind to empty key\n", | |
| channel ); | |
| return -EPROTO_NOT_KEYED; | |
| } | |
| /* Record bound peer identity */ | |
| channel->props.bound = x509_get ( identity ); | |
| DBGC ( channel, "CHANNEL %p key material bound to \"%s\"\n", | |
| channel, x509_name ( identity ) ); | |
| return 0; | |
| } | |
| /** | |
| * Bind peer identity via ephemeral public key signature verification | |
| * | |
| * @v channel Secure channel | |
| * @v identity Identity to be bound | |
| * @v pubkey Public-key algorithm | |
| * @v digest Digest algorithm | |
| * @v value Digest value (must cover peer's ephemeral public key) | |
| * @v signature Signature | |
| * @ret rc Return status code | |
| * | |
| * The peer may delegate authority to the shared secret by signing a | |
| * digest that covers at least its own ephemeral public key (e.g. the | |
| * Diffie-Hellman parameters provided in a ServerKeyExchange record) | |
| * and a challenge nonce (e.g. the TLS client random bytes). | |
| * | |
| * The consumer asserts to the secure channel that the digest covers | |
| * at least the peer's ephemeral public key and a nonce. The digest | |
| * may cover arbitrary additional information (e.g. the entire | |
| * transcript hash as signed by a CertificateVerify record). | |
| * | |
| * This assertion is an unverifiable promise made by the consumer: | |
| * there is no way for the secure channel itself to determine what is | |
| * covered by the digest. | |
| */ | |
| int channel_bind_verify ( struct secure_channel *channel, | |
| struct x509_certificate *identity, | |
| struct pubkey_algorithm *pubkey, | |
| struct digest_algorithm *digest, const void *value, | |
| const struct asn1_cursor *signature ) { | |
| const struct asn1_cursor *key = &identity->subject.public_key.raw; | |
| int rc; | |
| /* Verify signature */ | |
| if ( ( rc = pubkey_verify ( pubkey, key, digest, value, | |
| signature ) ) != 0 ) { | |
| DBGC ( channel, "CHANNEL %p could not verify binding: %s\n", | |
| channel, strerror ( rc ) ); | |
| return rc; | |
| } | |
| /* Bind to the identity that signed the shared secret */ | |
| if ( ( rc = channel_bind ( channel, identity ) ) != 0 ) | |
| return rc; | |
| return 0; | |
| } | |
| /** | |
| * Bind peer identity via shared secret encryption | |
| * | |
| * @v channel Secure channel | |
| * @v identity Identity to be bound | |
| * @v exchange Key exchange algorithm | |
| * @v pubkey Public-key algorithm | |
| * @v ciphertext Encrypted shared secret | |
| * @ret rc Return status code | |
| * | |
| * The local endpoint may delegate authority to the shared secret by | |
| * encrypting a unilaterally chosen shared secret (e.g. a classic TLS | |
| * static RSA pre-master secret). | |
| */ | |
| int channel_bind_encrypt ( struct secure_channel *channel, | |
| struct x509_certificate *identity, | |
| struct exchange_algorithm *exchange, | |
| struct pubkey_algorithm *pubkey, | |
| struct asn1_builder *ciphertext ) { | |
| const struct asn1_cursor *key = &identity->subject.public_key.raw; | |
| size_t sharedsize = exchange->sharedsize; | |
| struct asn1_cursor plaintext; | |
| void *tmp; | |
| int rc; | |
| /* Allocate space for shared secret (may be too large for stack) */ | |
| tmp = zalloc ( sharedsize ); | |
| if ( ! tmp ) { | |
| rc = -ENOMEM; | |
| goto err_alloc; | |
| } | |
| plaintext.data = tmp; | |
| plaintext.len = sharedsize; | |
| /* Generate unencrypted shared secret */ | |
| if ( ( rc = channel_key_transport ( channel, exchange, tmp ) ) != 0 ) | |
| goto err_transport; | |
| /* Encrypt shared secret */ | |
| if ( ( rc = pubkey_encrypt ( pubkey, key, &plaintext, | |
| ciphertext ) ) != 0 ) { | |
| DBGC ( channel, "CHANNEL %p could not encrypt \"%s\": %s\n", | |
| channel, exchange->name, strerror ( rc ) ); | |
| goto err_encrypt; | |
| } | |
| /* Bind to the identity that can decrypt the shared secret */ | |
| if ( ( rc = channel_bind ( channel, identity ) ) != 0 ) | |
| goto err_bind; | |
| err_bind: | |
| err_encrypt: | |
| err_transport: | |
| /* Clear the generated shared secret on any error */ | |
| if ( rc != 0 ) | |
| channel_unkey ( channel ); | |
| /* Erase unencrypted shared secret */ | |
| zfree ( tmp ); | |
| err_alloc: | |
| return rc; | |
| } | |
| /***************************************************************************** | |
| * | |
| * Pre-shared keys | |
| * | |
| ***************************************************************************** | |
| * | |
| * Once a shared secret has been bound to a peer identity, it may be | |
| * saved as a pre-shared key alongside the bound peer identity. A | |
| * future secure channel may resume the key schedule from this | |
| * pre-shared and already bound key. | |
| * | |
| * The consumer is responsible for ensuring that the ability to | |
| * demonstrate possession of the key material after resumption implies | |
| * possession of the key material that was previously saved (and so | |
| * implies possession of the original shared secret). | |
| * | |
| * The consumer may choose to save a pre-shared key at any point after | |
| * the peer identity has been bound. A successful resumption will | |
| * restore the previously bound peer identity but will clear the | |
| * confirmed and established peer identities. | |
| * | |
| * Key confirmation to demonstrate possession of the key material is | |
| * still required after resuming a secure channel from a pre-shared | |
| * key. | |
| * | |
| * The establishment check is also still required after resuming a | |
| * secure channel from a pre-shared key. It is technically possible | |
| * (though unlikely in practice) for the establishment check to use a | |
| * new required peer identity name or root of trust. | |
| * | |
| * Support for pre-shared keys is optional. The consumer may choose | |
| * not to support the use of pre-shared keys. | |
| * | |
| */ | |
| /** | |
| * Save a pre-shared key | |
| * | |
| * @v channel Secure channel | |
| * @v psid Pre-shared bound peer identity | |
| * @ret rc Return status code | |
| */ | |
| int channel_save ( struct secure_channel *channel, | |
| struct secure_preshared_identity *psid ) { | |
| struct x509_certificate *identity; | |
| int rc; | |
| /* Clear any existing pre-shared bound peer identity */ | |
| channel_clear_preshared ( psid ); | |
| /* Fail if there is no bound peer identity to save */ | |
| identity = channel->props.bound; | |
| if ( ! identity ) { | |
| DBGC ( channel, "CHANNEL %p cannot create pre-shared key " | |
| "for unbound peer\n", channel ); | |
| return -EPROTO_NOT_BOUND; | |
| } | |
| /* Fail if consumer does not support pre-shared keys */ | |
| if ( ! channel->op->save ) { | |
| DBGC ( channel, "CHANNEL %p does not support pre-shared " | |
| "keys\n", channel ); | |
| return -ENOTSUP; | |
| } | |
| /* Save pre-shared key */ | |
| if ( ( rc = channel->op->save ( channel, psid ) ) != 0 ) { | |
| DBGC ( channel, "CHANNEL %p could not save pre-shared " | |
| "key: %s\n", channel, strerror ( rc ) ); | |
| return rc; | |
| } | |
| /* Record pre-shared bound peer identity */ | |
| psid->bound = x509_get ( identity ); | |
| DBGC ( channel, "CHANNEL %p key material saved for \"%s\"\n", | |
| channel, x509_name ( identity ) ); | |
| return 0; | |
| } | |
| /** | |
| * Load a pre-shared key | |
| * | |
| * @v channel Secure channel | |
| * @v psid Pre-shared bound peer identity | |
| * @ret rc Return status code | |
| */ | |
| int channel_load ( struct secure_channel *channel, | |
| struct secure_preshared_identity *psid ) { | |
| struct x509_certificate *identity; | |
| int rc; | |
| /* Clear any existing shared secret */ | |
| channel_unkey ( channel ); | |
| /* Fail if there is no bound peer identity to resume */ | |
| identity = psid->bound; | |
| if ( ! identity ) { | |
| DBGC ( channel, "CHANNEL %p cannot resume from empty " | |
| "pre-shared key\n", channel ); | |
| rc = -EPROTO_NOT_BOUND; | |
| goto err_empty; | |
| } | |
| /* Fail if consumer does not support pre-shared keys */ | |
| if ( ! channel->op->load ) { | |
| DBGC ( channel, "CHANNEL %p does not support pre-shared " | |
| "keys\n", channel ); | |
| rc = -ENOTSUP; | |
| goto err_unsupported; | |
| } | |
| /* Load pre-shared key */ | |
| if ( ( rc = channel->op->load ( channel, psid ) ) != 0 ) { | |
| DBGC ( channel, "CHANNEL %p could not load pre-shared key: " | |
| "%s\n", channel, strerror ( rc ) ); | |
| goto err_load; | |
| } | |
| /* Channel now possesses a shared secret */ | |
| channel->props.keyed = 1; | |
| DBGC ( channel, "CHANNEL %p key material obtained from pre-shared " | |
| "key\n", channel ); | |
| /* Bind to the identity that was bound to the pre-shared key */ | |
| if ( ( rc = channel_bind ( channel, identity ) ) != 0 ) | |
| goto err_bind; | |
| return 0; | |
| err_bind: | |
| err_load: | |
| err_unsupported: | |
| err_empty: | |
| channel_unkey ( channel ); | |
| return rc; | |
| } | |
| /***************************************************************************** | |
| * | |
| * Key confirmation | |
| * | |
| ***************************************************************************** | |
| * | |
| * The peer must authenticate the channel by demonstrating its | |
| * possession of the current key material that was derived from the | |
| * identity-bound shared secret in accordance with the key schedule. | |
| * | |
| * The peer may demonstrate its possession of the key material by | |
| * constructing and sending an authenticator value that can be | |
| * successfully verified by the local endpoint using the same key | |
| * material. | |
| * | |
| * The receive pipe must already have transitioned to a cipher capable | |
| * of providing confidentiality before the authenticator value is | |
| * received, so that the authentication of the channel meaningfully | |
| * endures beyond the receipt of the authenticator value itself. | |
| * | |
| * The key may be confirmed for an identity that has not yet been | |
| * validated. The consumer is responsible for separately performing | |
| * validation of the identity (e.g. validation of the full X.509 | |
| * certificate chain, including OCSP checks) at some point before | |
| * relying upon the channel to protect application data. | |
| * | |
| */ | |
| /** | |
| * Clear confirmed peer identity | |
| * | |
| * @v channel Secure channel | |
| */ | |
| static void channel_unconfirm ( struct secure_channel *channel ) { | |
| /* Establishment is defined only for the currently confirmed | |
| * identity, and so any existing established identity must be | |
| * cleared. | |
| */ | |
| channel_unestablish ( channel ); | |
| /* Drop and clear reference */ | |
| x509_put ( channel->props.confirmed ); | |
| channel->props.confirmed = NULL; | |
| } | |
| /** | |
| * Confirm peer identity | |
| * | |
| * @v channel Secure channel | |
| * @v auth Authenticator value | |
| * @v len Length of authenticator value | |
| * @ret rc Return status code | |
| * | |
| * The consumer asserts to the secure channel that the authenticator | |
| * value was received after the receive pipe was transitioned to a | |
| * cipher capable of providing confidentiality. | |
| * | |
| * This assertion is an unverifiable promise made by the consumer: | |
| * there is no way for the secure channel itself to determine when the | |
| * authenticator value was received. | |
| */ | |
| int channel_confirm ( struct secure_channel *channel, | |
| const void *auth, size_t len ) { | |
| struct x509_certificate *identity; | |
| int rc; | |
| /* Clear any existing confirmed peer identity */ | |
| channel_unconfirm ( channel ); | |
| /* Fail if there is no bound peer identity to authenticate */ | |
| identity = channel->props.bound; | |
| if ( ! identity ) { | |
| DBGC ( channel, "CHANNEL %p cannot authenticate unbound " | |
| "peer\n", channel ); | |
| return -EPROTO_NOT_BOUND; | |
| } | |
| /* Sanity check: binding is not possible without keying */ | |
| assert ( channel->props.keyed ); | |
| /* Require key confirmation over a confidential channel */ | |
| if ( ! channel->rx.cipher->confidential ) { | |
| DBGC ( channel, "CHANNEL %p cannot confirm key with a " | |
| "non-confidential cipher\n", channel ); | |
| return -EPERM_NOT_CONFIDENTIAL; | |
| } | |
| /* Verify authentication data */ | |
| if ( ( rc = channel->op->verify ( channel, auth, len ) ) != 0 ) { | |
| DBGC ( channel, "CHANNEL %p failed to authenticate: %s\n", | |
| channel, strerror ( rc ) ); | |
| return rc; | |
| } | |
| /* Record bound peer identity as confirmed peer identity */ | |
| channel->props.confirmed = x509_get ( identity ); | |
| DBGC ( channel, "CHANNEL %p key material confirmed for \"%s\"\n", | |
| channel, x509_name ( identity ) ); | |
| return 0; | |
| } | |
| /***************************************************************************** | |
| * | |
| * Establishment | |
| * | |
| ***************************************************************************** | |
| * | |
| * The channel may be used for application data once the peer identity | |
| * has been validated, the peer has authenticated the channel via key | |
| * confirmation, and both pipes have been transitioned to ciphers | |
| * capable of providing confidentiality. | |
| * | |
| * Validating the peer identity (i.e. validating its X.509 certificate | |
| * chain) is outside the scope of the secure channel and must be | |
| * performed separately by the consumer. Validation is an | |
| * asynchronous operation that will typically require network | |
| * communication (e.g. to perform OCSP checks). | |
| * | |
| * Once the consumer knows that validation has completed and that the | |
| * channel has been authenticated, it may mark the channel as | |
| * established. | |
| * | |
| * Establishment performs a one-off check that the confirmed peer | |
| * identity exists, matches the name required by the consumer, and is | |
| * valid at this point in time. Future changes to the validity of the | |
| * peer identity (e.g. due to certificate expiry) will not cause the | |
| * channel to cease to be established. | |
| * | |
| */ | |
| /** | |
| * Clear established peer identity | |
| * | |
| * @v channel Secure channel | |
| */ | |
| static void channel_unestablish ( struct secure_channel *channel ) { | |
| /* Drop and clear reference */ | |
| x509_put ( channel->props.established ); | |
| channel->props.established = NULL; | |
| } | |
| /** | |
| * Establish channel as trusted for application data | |
| * | |
| * @v channel Secure channel | |
| * @v name Required peer identity name | |
| * @v root Root certificate list, or NULL to use default | |
| * @ret rc Return status code | |
| */ | |
| int channel_establish ( struct secure_channel *channel, const char *name, | |
| struct x509_root *root ) { | |
| struct x509_certificate *identity; | |
| int rc; | |
| /* Clear any existing established peer identity */ | |
| channel_unestablish ( channel ); | |
| /* Fail if there is no confirmed peer identity to establish */ | |
| identity = channel->props.confirmed; | |
| if ( ! identity ) { | |
| DBGC ( channel, "CHANNEL %p cannot establish unconfirmed " | |
| "peer\n", channel ); | |
| return -EPROTO_NOT_CONFIRMED; | |
| } | |
| /* Sanity check: confirmation is not possible without binding */ | |
| assert ( channel->props.bound == identity ); | |
| assert ( channel->props.keyed ); | |
| /* Fail if ciphers are still operating in plaintext mode */ | |
| if ( ! ( channel->tx.cipher->confidential && | |
| channel->rx.cipher->confidential ) ) { | |
| DBGC ( channel, "CHANNEL %p cannot establish channel with a " | |
| "non-confidential cipher\n", channel ); | |
| return -EPERM_NOT_CONFIDENTIAL; | |
| } | |
| /* Fail if confirmed peer identity name is incorrect */ | |
| if ( ( rc = x509_check_name ( identity, name ) ) != 0 ) { | |
| DBGC ( channel, "CHANNEL %p identity \"%s\" does not match " | |
| "\"%s\": %s\n", channel, x509_name ( identity ), | |
| name, strerror ( rc ) ); | |
| return rc; | |
| } | |
| /* Fail if confirmed peer identity has not been validated */ | |
| if ( ! x509_is_valid ( identity, root ) ) { | |
| DBGC ( channel, "CHANNEL %p identity \"%s\" has not been " | |
| "validated\n", channel, x509_name ( identity ) ); | |
| return -EPERM_NOT_VALID; | |
| } | |
| /* Record confirmed peer identity as established peer identity */ | |
| channel->props.established = x509_get ( identity ); | |
| DBGC ( channel, "CHANNEL %p established for \"%s\"\n", | |
| channel, x509_name ( identity ) ); | |
| return 0; | |
| } | |
| /***************************************************************************** | |
| * | |
| * Cipher operation | |
| * | |
| ***************************************************************************** | |
| * | |
| */ | |
| /** | |
| * Encrypt or decrypt data via dead cipher | |
| * | |
| * @v cipher Cipher algorithm | |
| * @v ctx Context | |
| * @v src Data to encrypt or decrypt | |
| * @v dst Buffer for encrypted or decrypted data | |
| * @v len Length of data | |
| */ | |
| static void channel_dead_crypt ( struct cipher_algorithm *cipher __unused, | |
| void *ctx __unused, const void *src __unused, | |
| void *dst, size_t len ) { | |
| static const uint8_t pattern[] = { 0xde, 0xad, 0xc1, 0xfa }; | |
| uint8_t *bytes = dst; | |
| unsigned int i; | |
| /* Fill destination with fixed and easily spotted pattern */ | |
| for ( i = 0 ; i < len ; i++ ) | |
| bytes[i] = pattern[ i % sizeof ( pattern ) ]; | |
| } | |
| /** | |
| * Dead cipher | |
| * | |
| * We activate this cipher (rather than the null cipher) upon any | |
| * cipher-related failure, to guard against code paths that may fail | |
| * to check for cipher errors. | |
| */ | |
| struct cipher_algorithm channel_dead_cipher = { | |
| .name = "dead", | |
| .ctxsize = 0, | |
| .blocksize = 1, | |
| .alignsize = 1, | |
| .authsize = 0, | |
| .confidential = 0, | |
| .setkey = cipher_null_setkey, | |
| .setiv = cipher_null_setiv, | |
| .encrypt = channel_dead_crypt, | |
| .decrypt = channel_dead_crypt, | |
| .auth = cipher_null_auth, | |
| }; | |
| /** | |
| * Get pipe name (for debugging) | |
| * | |
| * @v channel Secure channel | |
| * @v pipe Secure channel pipe | |
| * @ret name Pipe name | |
| */ | |
| static inline const char * channel_pipe_name ( struct secure_channel *channel, | |
| struct secure_pipe *pipe ) { | |
| return ( ( pipe == &channel->tx ) ? "TX" : | |
| ( pipe == &channel->rx ) ? "RX" : "INVALID" ); | |
| } | |
| /** | |
| * Clear cipher algorithm | |
| * | |
| * @v pipe Secure channel pipe | |
| */ | |
| void channel_clear_cipher ( struct secure_pipe *pipe ) { | |
| /* Zero and free any existing cipher context */ | |
| zfree ( pipe->ctx ); | |
| pipe->ctx = NULL; | |
| /* Default to the dead cipher until set */ | |
| pipe->cipher = &channel_dead_cipher; | |
| } | |
| /** | |
| * Set cipher algorithm and key | |
| * | |
| * @v channel Secure channel | |
| * @v pipe Secure channel pipe | |
| * @v cipher Cipher algorithm | |
| * @v key Key | |
| * @v len Length of key | |
| * @ret rc Return status code | |
| * | |
| * The consumer asserts to the secure channel that the key is | |
| * ultimately derived from the shared secret that was previously | |
| * provided to the consumer by the secure channel, i.e. that | |
| * possession of the cipher key implies possession of the shared | |
| * secret. | |
| * | |
| * This assertion is an unverifiable promise made by the consumer: we | |
| * can check that a shared secret exists, but there is no way for the | |
| * secure channel itself to determine how the cipher key is derived. | |
| */ | |
| int channel_set_cipher ( struct secure_channel *channel, | |
| struct secure_pipe *pipe, | |
| struct cipher_algorithm *cipher, | |
| const void *key, size_t len ) { | |
| int rc; | |
| /* Zero and free any existing cipher context */ | |
| channel_clear_cipher ( pipe ); | |
| /* Fail if there is no shared secret from which a key could derive */ | |
| if ( ! channel->props.keyed ) { | |
| DBGC ( channel, "CHANNEL %p refusing to use definitely " | |
| "non-derived shared secret\n", channel ); | |
| rc = -EPROTO_NOT_KEYED; | |
| goto err_keyed; | |
| } | |
| /* Refuse to explicitly set a non-confidential cipher */ | |
| if ( ! cipher->confidential ) { | |
| DBGC ( channel, "CHANNEL %p %s refusing to use " | |
| "non-confidential cipher \"%s\"\n", channel, | |
| channel_pipe_name ( channel, pipe ), cipher->name ); | |
| rc = -EPERM_NOT_CONFIDENTIAL; | |
| goto err_confidential; | |
| } | |
| pipe->cipher = cipher; | |
| /* Allocate new context */ | |
| pipe->ctx = zalloc ( cipher->ctxsize ); | |
| if ( ! pipe->ctx ) { | |
| rc = -ENOMEM; | |
| goto err_alloc; | |
| } | |
| /* Set cipher key */ | |
| if ( ( rc = cipher_setkey ( cipher, pipe->ctx, key, len ) ) != 0 ) { | |
| DBGC ( channel, "CHANNEL %p %s could not set \"%s\" key: %s\n", | |
| channel, channel_pipe_name ( channel, pipe ), | |
| cipher->name, strerror ( rc ) ); | |
| goto err_setkey; | |
| } | |
| DBGC ( channel, "CHANNEL %p %s using \"%s\" with %zd-bit key\n", | |
| channel, channel_pipe_name ( channel, pipe ), | |
| cipher->name, ( 8 * len ) ); | |
| return 0; | |
| err_setkey: | |
| err_alloc: | |
| err_confidential: | |
| err_keyed: | |
| channel_clear_cipher ( pipe ); | |
| return rc; | |
| } | |
| /***************************************************************************** | |
| * | |
| * Life cycle management | |
| * | |
| ***************************************************************************** | |
| * | |
| */ | |
| /** | |
| * Open secure channel | |
| * | |
| * @v channel Secure channel | |
| * @ret rc Return status code | |
| */ | |
| int channel_open ( struct secure_channel *channel ) { | |
| int rc; | |
| /* Sanity checks */ | |
| assert ( channel != NULL ); | |
| assert ( channel->op != NULL ); | |
| assert ( channel->op->reset != NULL ); | |
| assert ( channel->op->apply != NULL ); | |
| assert ( channel->op->verify != NULL ); | |
| /* Clear security properties (which should already be clear) */ | |
| assert ( channel->props.keyed == 0 ); | |
| assert ( channel->props.bound == NULL ); | |
| assert ( channel->props.confirmed == NULL ); | |
| assert ( channel->props.established == NULL ); | |
| channel_unkey ( channel ); | |
| assert ( channel->props.keyed == 0 ); | |
| assert ( channel->props.bound == NULL ); | |
| assert ( channel->props.confirmed == NULL ); | |
| assert ( channel->props.established == NULL ); | |
| /* Reset ciphers (which should already have no contexts) */ | |
| assert ( channel->tx.ctx == NULL ); | |
| assert ( channel->rx.ctx == NULL ); | |
| channel_clear_cipher ( &channel->tx ); | |
| channel_clear_cipher ( &channel->rx ); | |
| assert ( channel->tx.ctx == NULL ); | |
| assert ( channel->rx.ctx == NULL ); | |
| /* Initialise ephemeral master secret */ | |
| if ( ( rc = channel_ephemeral_init ( channel ) ) != 0 ) | |
| return rc; | |
| /* Enable initial plaintext ciphers */ | |
| channel->tx.cipher = &cipher_null; | |
| channel->rx.cipher = &cipher_null; | |
| DBGC ( channel, "CHANNEL %p opened\n", channel ); | |
| return 0; | |
| } | |
| /** | |
| * Reopen secure channel | |
| * | |
| * @v channel Secure channel | |
| * | |
| * The channel will be returned to a freshly opened state (including a | |
| * new ephemeral master secret), but with any existing cipher state | |
| * retained. All security properties will be cleared. | |
| * | |
| * This allows a new secure channel to be established with either or | |
| * both of the transmit and receive pipes having already transitioned | |
| * to a cipher capable of providing confidentiality. | |
| * | |
| * The consumer may choose to reopen a fully established channel while | |
| * leaving the cipher keys intact (as is done in TLS renegotiation), | |
| * in which case it may choose to treat the channel as remaining | |
| * established (to the old peer identity) at least until the point | |
| * that the cipher keys are next changed (and possibly further, | |
| * depending upon the protocol design). The secure channel itself | |
| * does not attempt to model any such choice by the consumer: | |
| * reopening will clear all security properties including the | |
| * established peer identity. | |
| */ | |
| void channel_reopen ( struct secure_channel *channel ) { | |
| /* Clear security properties */ | |
| channel_unkey ( channel ); | |
| assert ( channel->props.keyed == 0 ); | |
| assert ( channel->props.bound == NULL ); | |
| assert ( channel->props.confirmed == NULL ); | |
| assert ( channel->props.established == NULL ); | |
| /* Replace ephemeral master secret | |
| * | |
| * We choose to replace rather than reinitialise the ephemeral | |
| * master secret so that this function may be used from code | |
| * paths that cannot allow for failure (such as when closing | |
| * the channel). | |
| */ | |
| channel_ephemeral_replace ( channel ); | |
| DBGC ( channel, "CHANNEL %p reopened\n", channel ); | |
| } | |
| /** | |
| * Close secure channel | |
| * | |
| * @v channel Secure channel | |
| * | |
| * All secret values held by the secure channel will be destroyed, | |
| * including the ephemeral master secret. | |
| * | |
| * This function may safely be called on a channel that has already | |
| * been closed. | |
| */ | |
| void channel_close ( struct secure_channel *channel ) { | |
| /* Clear security properties */ | |
| channel_unkey ( channel ); | |
| assert ( channel->props.keyed == 0 ); | |
| assert ( channel->props.bound == NULL ); | |
| assert ( channel->props.confirmed == NULL ); | |
| assert ( channel->props.established == NULL ); | |
| /* Reset ciphers */ | |
| channel_clear_cipher ( &channel->tx ); | |
| channel_clear_cipher ( &channel->rx ); | |
| assert ( channel->tx.ctx == NULL ); | |
| assert ( channel->rx.ctx == NULL ); | |
| /* Replace ephemeral master secret | |
| * | |
| * We choose to replace rather than zero the ephemeral master | |
| * secret. For any channel that has been successfully opened, | |
| * this ensures that an erroneous reuse of the channel after | |
| * it has been closed cannot lead to the use of predictable | |
| * ephemeral secrets (derived from an all-zero value), or to | |
| * reuse of its previous ephemeral secrets. | |
| */ | |
| channel_ephemeral_replace ( channel ); | |
| DBGC ( channel, "CHANNEL %p closed\n", channel ); | |
| } |