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