diff options
author | Simon Rettberg | 2024-04-12 14:00:15 +0200 |
---|---|---|
committer | Simon Rettberg | 2024-04-12 14:00:15 +0200 |
commit | 98dc341428e247141f120d05fac48c4e144a4c0f (patch) | |
tree | 3ebacb37927e338383ac64c2e20eb0b2f820cb85 /src/net | |
parent | Merge branch 'master' into openslx (diff) | |
parent | Merge branch 'ipxe:master' into aqc1xx (diff) | |
download | ipxe-98dc341428e247141f120d05fac48c4e144a4c0f.tar.gz ipxe-98dc341428e247141f120d05fac48c4e144a4c0f.tar.xz ipxe-98dc341428e247141f120d05fac48c4e144a4c0f.zip |
Merge branch 'aqc1xx' into openslx
Diffstat (limited to 'src/net')
-rw-r--r-- | src/net/aoe.c | 2 | ||||
-rw-r--r-- | src/net/eap.c | 181 | ||||
-rw-r--r-- | src/net/eap_md5.c | 116 | ||||
-rw-r--r-- | src/net/eap_mschapv2.c | 251 | ||||
-rw-r--r-- | src/net/eapol.c | 80 | ||||
-rw-r--r-- | src/net/tls.c | 502 | ||||
-rw-r--r-- | src/net/validator.c | 327 |
7 files changed, 1165 insertions, 294 deletions
diff --git a/src/net/aoe.c b/src/net/aoe.c index e785e897..dba4f51b 100644 --- a/src/net/aoe.c +++ b/src/net/aoe.c @@ -374,7 +374,7 @@ static void aoecmd_ata_cmd ( struct aoe_command *aoecmd, struct aoeata *aoeata = &aoehdr->payload[0].ata; /* Sanity check */ - linker_assert ( AOE_FL_DEV_HEAD == ATA_DEV_SLAVE, __fix_ata_h__ ); + static_assert ( AOE_FL_DEV_HEAD == ATA_DEV_SLAVE ); assert ( len == ( sizeof ( *aoehdr ) + sizeof ( *aoeata ) + command->data_out_len ) ); diff --git a/src/net/eap.c b/src/net/eap.c index beaeb61d..87327d72 100644 --- a/src/net/eap.c +++ b/src/net/eap.c @@ -23,7 +23,10 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); +#include <stdlib.h> #include <errno.h> +#include <string.h> +#include <byteswap.h> #include <ipxe/netdevice.h> #include <ipxe/eap.h> @@ -34,54 +37,183 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); */ /** + * Transmit EAP response + * + * @v supplicant EAP supplicant + * @v rsp Response type data + * @v rsp_len Length of response type data + * @ret rc Return status code + */ +int eap_tx_response ( struct eap_supplicant *supplicant, + const void *rsp, size_t rsp_len ) { + struct net_device *netdev = supplicant->netdev; + struct eap_message *msg; + size_t len; + int rc; + + /* Allocate and populate response */ + len = ( sizeof ( *msg ) + rsp_len ); + msg = malloc ( len ); + if ( ! msg ) { + rc = -ENOMEM; + goto err_alloc; + } + msg->hdr.code = EAP_CODE_RESPONSE; + msg->hdr.id = supplicant->id; + msg->hdr.len = htons ( len ); + msg->type = supplicant->type; + memcpy ( msg->data, rsp, rsp_len ); + DBGC ( netdev, "EAP %s Response id %#02x type %d\n", + netdev->name, msg->hdr.id, msg->type ); + + /* Transmit response */ + if ( ( rc = supplicant->tx ( supplicant, msg, len ) ) != 0 ) { + DBGC ( netdev, "EAP %s could not transmit: %s\n", + netdev->name, strerror ( rc ) ); + goto err_tx; + } + + err_tx: + free ( msg ); + err_alloc: + return rc; +} + +/** + * Transmit EAP NAK + * + * @v supplicant EAP supplicant + * @ret rc Return status code + */ +static int eap_tx_nak ( struct eap_supplicant *supplicant ) { + struct net_device *netdev = supplicant->netdev; + unsigned int max = table_num_entries ( EAP_METHODS ); + uint8_t methods[ max + 1 /* potential EAP_TYPE_NONE */ ]; + unsigned int count = 0; + struct eap_method *method; + + /* Populate methods list */ + DBGC ( netdev, "EAP %s Nak offering types {", netdev->name ); + for_each_table_entry ( method, EAP_METHODS ) { + if ( method->type > EAP_TYPE_NAK ) { + DBGC ( netdev, "%s%d", + ( count ? ", " : "" ), method->type ); + methods[count++] = method->type; + } + } + if ( ! count ) + methods[count++] = EAP_TYPE_NONE; + DBGC ( netdev, "}\n" ); + assert ( count <= max ); + + /* Transmit response */ + supplicant->type = EAP_TYPE_NAK; + return eap_tx_response ( supplicant, methods, count ); +} + +/** * Handle EAP Request-Identity * * @v supplicant EAP supplicant + * @v req Request type data + * @v req_len Length of request type data * @ret rc Return status code */ -static int eap_rx_request_identity ( struct eap_supplicant *supplicant ) { +static int eap_rx_identity ( struct eap_supplicant *supplicant, + const void *req, size_t req_len ) { struct net_device *netdev = supplicant->netdev; + void *rsp; + int rsp_len; + int rc; /* Treat Request-Identity as blocking the link */ DBGC ( netdev, "EAP %s Request-Identity blocking link\n", netdev->name ); + DBGC_HDA ( netdev, 0, req, req_len ); netdev_link_block ( netdev, EAP_BLOCK_TIMEOUT ); - return 0; + /* Mark EAP as in progress */ + supplicant->flags |= EAP_FL_ONGOING; + + /* Construct response, if applicable */ + rsp_len = fetch_raw_setting_copy ( netdev_settings ( netdev ), + &username_setting, &rsp ); + if ( rsp_len < 0 ) { + /* We have no identity to offer, so wait until the + * switch times out and switches to MAC Authentication + * Bypass (MAB). + */ + DBGC2 ( netdev, "EAP %s has no identity\n", netdev->name ); + supplicant->flags |= EAP_FL_PASSIVE; + rc = 0; + goto no_response; + } + + /* Transmit response */ + if ( ( rc = eap_tx_response ( supplicant, rsp, rsp_len ) ) != 0 ) + goto err_tx; + + err_tx: + free ( rsp ); + no_response: + return rc; } +/** EAP Request-Identity method */ +struct eap_method eap_identity_method __eap_method = { + .type = EAP_TYPE_IDENTITY, + .rx = eap_rx_identity, +}; + /** * Handle EAP Request * * @v supplicant EAP supplicant - * @v req EAP request + * @v msg EAP request * @v len Length of EAP request * @ret rc Return status code */ static int eap_rx_request ( struct eap_supplicant *supplicant, - const struct eap_request *req, size_t len ) { + const struct eap_message *msg, size_t len ) { struct net_device *netdev = supplicant->netdev; + struct eap_method *method; + const void *req; + size_t req_len; - /* Sanity check */ - if ( len < sizeof ( *req ) ) { + /* Sanity checks */ + if ( len < sizeof ( *msg ) ) { DBGC ( netdev, "EAP %s underlength request:\n", netdev->name ); - DBGC_HDA ( netdev, 0, req, len ); + DBGC_HDA ( netdev, 0, msg, len ); + return -EINVAL; + } + if ( len < ntohs ( msg->hdr.len ) ) { + DBGC ( netdev, "EAP %s truncated request:\n", netdev->name ); + DBGC_HDA ( netdev, 0, msg, len ); return -EINVAL; } + req = msg->data; + req_len = ( ntohs ( msg->hdr.len ) - sizeof ( *msg ) ); - /* Mark authentication as incomplete */ - supplicant->done = 0; + /* Record request details */ + supplicant->id = msg->hdr.id; + supplicant->type = msg->type; + DBGC ( netdev, "EAP %s Request id %#02x type %d\n", + netdev->name, msg->hdr.id, msg->type ); /* Handle according to type */ - switch ( req->type ) { - case EAP_TYPE_IDENTITY: - return eap_rx_request_identity ( supplicant ); - default: - DBGC ( netdev, "EAP %s requested type %d unknown:\n", - netdev->name, req->type ); - DBGC_HDA ( netdev, 0, req, len ); - return -ENOTSUP; + for_each_table_entry ( method, EAP_METHODS ) { + if ( msg->type == method->type ) + return method->rx ( supplicant, req, req_len ); } + DBGC ( netdev, "EAP %s requested type %d unknown:\n", + netdev->name, msg->type ); + DBGC_HDA ( netdev, 0, msg, len ); + + /* Send NAK if applicable */ + if ( msg->type > EAP_TYPE_NAK ) + return eap_tx_nak ( supplicant ); + + return -ENOTSUP; } /** @@ -94,7 +226,7 @@ static int eap_rx_success ( struct eap_supplicant *supplicant ) { struct net_device *netdev = supplicant->netdev; /* Mark authentication as complete */ - supplicant->done = 1; + supplicant->flags = EAP_FL_PASSIVE; /* Mark link as unblocked */ DBGC ( netdev, "EAP %s Success\n", netdev->name ); @@ -113,7 +245,7 @@ static int eap_rx_failure ( struct eap_supplicant *supplicant ) { struct net_device *netdev = supplicant->netdev; /* Mark authentication as complete */ - supplicant->done = 1; + supplicant->flags = EAP_FL_PASSIVE; /* Record error */ DBGC ( netdev, "EAP %s Failure\n", netdev->name ); @@ -143,7 +275,10 @@ int eap_rx ( struct eap_supplicant *supplicant, const void *data, /* Handle according to code */ switch ( eap->hdr.code ) { case EAP_CODE_REQUEST: - return eap_rx_request ( supplicant, &eap->req, len ); + return eap_rx_request ( supplicant, &eap->msg, len ); + case EAP_CODE_RESPONSE: + DBGC2 ( netdev, "EAP %s ignoring response\n", netdev->name ); + return 0; case EAP_CODE_SUCCESS: return eap_rx_success ( supplicant ); case EAP_CODE_FAILURE: @@ -155,3 +290,9 @@ int eap_rx ( struct eap_supplicant *supplicant, const void *data, return -ENOTSUP; } } + +/* Drag in objects via eap_rx() */ +REQUIRING_SYMBOL ( eap_rx ); + +/* Drag in EAP configuration */ +REQUIRE_OBJECT ( config_eap ); diff --git a/src/net/eap_md5.c b/src/net/eap_md5.c new file mode 100644 index 00000000..0664174f --- /dev/null +++ b/src/net/eap_md5.c @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2024 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <ipxe/md5.h> +#include <ipxe/chap.h> +#include <ipxe/eap.h> + +/** @file + * + * EAP MD5-Challenge authentication method + * + */ + +/** + * Handle EAP MD5-Challenge + * + * @v supplicant EAP supplicant + * @v req Request type data + * @v req_len Length of request type data + * @ret rc Return status code + */ +static int eap_rx_md5 ( struct eap_supplicant *supplicant, + const void *req, size_t req_len ) { + struct net_device *netdev = supplicant->netdev; + const struct eap_md5 *md5req = req; + struct { + uint8_t len; + uint8_t value[MD5_DIGEST_SIZE]; + } __attribute__ (( packed )) md5rsp; + struct chap_response chap; + void *secret; + int secret_len; + int rc; + + /* Sanity checks */ + if ( req_len < sizeof ( *md5req ) ) { + DBGC ( netdev, "EAP %s underlength MD5-Challenge:\n", + netdev->name ); + DBGC_HDA ( netdev, 0, req, req_len ); + rc = -EINVAL; + goto err_sanity; + } + if ( ( req_len - sizeof ( *md5req ) ) < md5req->len ) { + DBGC ( netdev, "EAP %s truncated MD5-Challenge:\n", + netdev->name ); + DBGC_HDA ( netdev, 0, req, req_len ); + rc = -EINVAL; + goto err_sanity; + } + + /* Construct response */ + if ( ( rc = chap_init ( &chap, &md5_algorithm ) ) != 0 ) { + DBGC ( netdev, "EAP %s could not initialise CHAP: %s\n", + netdev->name, strerror ( rc ) ); + goto err_chap; + } + chap_set_identifier ( &chap, supplicant->id ); + secret_len = fetch_raw_setting_copy ( netdev_settings ( netdev ), + &password_setting, &secret ); + if ( secret_len < 0 ) { + rc = secret_len; + DBGC ( netdev, "EAP %s has no secret: %s\n", + netdev->name, strerror ( rc ) ); + goto err_secret; + } + chap_update ( &chap, secret, secret_len ); + chap_update ( &chap, md5req->value, md5req->len ); + chap_respond ( &chap ); + assert ( chap.response_len == sizeof ( md5rsp.value ) ); + md5rsp.len = sizeof ( md5rsp.value ); + memcpy ( md5rsp.value, chap.response, sizeof ( md5rsp.value ) ); + + /* Transmit response */ + if ( ( rc = eap_tx_response ( supplicant, &md5rsp, + sizeof ( md5rsp ) ) ) != 0 ) + goto err_tx; + + err_tx: + free ( secret ); + err_secret: + chap_finish ( &chap ); + err_chap: + err_sanity: + return rc; +} + +/** EAP MD5-Challenge method */ +struct eap_method eap_md5_method __eap_method = { + .type = EAP_TYPE_MD5, + .rx = eap_rx_md5, +}; diff --git a/src/net/eap_mschapv2.c b/src/net/eap_mschapv2.c new file mode 100644 index 00000000..0be62ed5 --- /dev/null +++ b/src/net/eap_mschapv2.c @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2024 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <byteswap.h> +#include <ipxe/mschapv2.h> +#include <ipxe/eap.h> + +/** @file + * + * EAP MS-CHAPv2 authentication method + * + * EAP-MSCHAPv2 was described in a draft RFC first published in 2002 + * (draft-kamath-pppext-eap-mschapv2-02.txt). The draft eventually + * expired in 2007 without becoming an official RFC, quite possibly + * because the protocol design was too ugly to be called an IETF + * standard. It is, however, fairly widely used. + */ + +/** An EAP MS-CHAPv2 request message */ +struct eap_mschapv2_request { + /** EAP-MSCHAPv2 header */ + struct eap_mschapv2 hdr; + /** MS-CHAPv2 challenge length (fixed value) */ + uint8_t len; + /** MS-CHAPv2 challenge */ + struct mschapv2_challenge msg; +} __attribute__ (( packed )); + +/** An EAP MS-CHAPv2 response message */ +struct eap_mschapv2_response { + /** EAP-MSCHAPv2 header */ + struct eap_mschapv2 hdr; + /** MS-CHAPv2 response length (fixed value) */ + uint8_t len; + /** MS-CHAPv2 response */ + struct mschapv2_response msg; + /** User name */ + char name[0]; +} __attribute__ (( packed )); + +/** An EAP MS-CHAPv2 success request message */ +struct eap_mschapv2_success_request { + /** EAP-MSCHAPv2 header */ + struct eap_mschapv2 hdr; + /** Message */ + char message[0]; +} __attribute__ (( packed )); + +/** An EAP MS-CHAPv2 success response message */ +struct eap_mschapv2_success_response { + /** Opcode */ + uint8_t code; +} __attribute__ (( packed )); + +/** + * Handle EAP MS-CHAPv2 request + * + * @v supplicant EAP supplicant + * @v hdr EAP-MSCHAPv2 header + * @v len Message length + * @ret rc Return status code + */ +static int eap_rx_mschapv2_request ( struct eap_supplicant *supplicant, + const struct eap_mschapv2 *hdr, + size_t len ) { + struct net_device *netdev = supplicant->netdev; + struct settings *settings = netdev_settings ( netdev ); + const struct eap_mschapv2_request *msreq = + container_of ( hdr, struct eap_mschapv2_request, hdr ); + struct eap_mschapv2_response *msrsp; + struct mschapv2_challenge peer; + char *username; + char *password; + int username_len; + int password_len; + size_t msrsp_len; + unsigned int i; + int rc; + + /* Sanity check */ + if ( len < sizeof ( *msreq ) ) { + DBGC ( netdev, "EAP %s underlength MS-CHAPv2 request\n", + netdev->name ); + DBGC_HDA ( netdev, 0, hdr, len ); + rc = -EINVAL; + goto err_sanity; + } + + /* Fetch username and password */ + username_len = fetch_string_setting_copy ( settings, &username_setting, + &username ); + if ( username_len < 0 ) { + rc = username_len; + DBGC ( netdev, "EAP %s has no username: %s\n", + netdev->name, strerror ( rc ) ); + goto err_username; + } + password_len = fetch_string_setting_copy ( settings, &password_setting, + &password ); + if ( password_len < 0 ) { + rc = password_len; + DBGC ( netdev, "EAP %s has no password: %s\n", + netdev->name, strerror ( rc ) ); + goto err_password; + } + + /* Construct a peer challenge. We do not perform mutual + * authentication, so this does not need to be strong. + */ + for ( i = 0 ; i < ( sizeof ( peer.byte ) / + sizeof ( peer.byte[0] ) ) ; i++ ) { + peer.byte[i] = random(); + } + + /* Allocate response */ + msrsp_len = ( sizeof ( *msrsp ) + username_len ); + msrsp = malloc ( msrsp_len ); + if ( ! msrsp ) { + rc = -ENOMEM; + goto err_alloc; + } + + /* Construct response */ + msrsp->hdr.code = EAP_CODE_RESPONSE; + msrsp->hdr.id = msreq->hdr.id; + msrsp->hdr.len = htons ( msrsp_len ); + msrsp->len = sizeof ( msrsp->msg ); + mschapv2_response ( username, password, &msreq->msg, &peer, + &msrsp->msg ); + memcpy ( msrsp->name, username, username_len ); + + /* Send response */ + if ( ( rc = eap_tx_response ( supplicant, msrsp, msrsp_len ) ) != 0 ) + goto err_tx; + + err_tx: + free ( msrsp ); + err_alloc: + free ( password ); + err_password: + free ( username ); + err_username: + err_sanity: + return rc; +} + +/** + * Handle EAP MS-CHAPv2 success request + * + * @v supplicant EAP supplicant + * @v hdr EAP-MSCHAPv2 header + * @v len Message length + * @ret rc Return status code + */ +static int eap_rx_mschapv2_success ( struct eap_supplicant *supplicant, + const struct eap_mschapv2 *hdr, + size_t len ) { + const struct eap_mschapv2_success_request *msreq = + container_of ( hdr, struct eap_mschapv2_success_request, hdr ); + static const struct eap_mschapv2_success_response msrsp = { + .code = EAP_CODE_SUCCESS, + }; + + /* Sanity check */ + assert ( len >= sizeof ( *msreq ) ); + + /* The success request contains the MS-CHAPv2 authenticator + * response, which could potentially be used to verify that + * the EAP authenticator also knew the password (or, at least, + * the MD4 hash of the password). + * + * Our model for EAP does not encompass mutual authentication: + * we will starting sending plaintext packets (e.g. DHCP + * requests) over the link even before EAP completes, and our + * only use for an EAP success is to mark the link as + * unblocked. + * + * We therefore ignore the content of the success request and + * just send back a success response, so that the EAP + * authenticator will complete the process and send through + * the real EAP success packet (which will, in turn, cause us + * to unblock the link). + */ + return eap_tx_response ( supplicant, &msrsp, sizeof ( msrsp ) ); +} + +/** + * Handle EAP MS-CHAPv2 + * + * @v supplicant EAP supplicant + * @v req Request type data + * @v req_len Length of request type data + * @ret rc Return status code + */ +static int eap_rx_mschapv2 ( struct eap_supplicant *supplicant, + const void *req, size_t req_len ) { + struct net_device *netdev = supplicant->netdev; + const struct eap_mschapv2 *hdr = req; + + /* Sanity check */ + if ( req_len < sizeof ( *hdr ) ) { + DBGC ( netdev, "EAP %s underlength MS-CHAPv2:\n", + netdev->name ); + DBGC_HDA ( netdev, 0, req, req_len ); + return -EINVAL; + } + + /* Handle according to opcode */ + switch ( hdr->code ) { + case EAP_CODE_REQUEST: + return eap_rx_mschapv2_request ( supplicant, hdr, req_len ); + case EAP_CODE_SUCCESS: + return eap_rx_mschapv2_success ( supplicant, hdr, req_len ); + default: + DBGC ( netdev, "EAP %s unsupported MS-CHAPv2 opcode %d\n", + netdev->name, hdr->code ); + DBGC_HDA ( netdev, 0, req, req_len ); + return -ENOTSUP; + } +} + +/** EAP MS-CHAPv2 method */ +struct eap_method eap_mschapv2_method __eap_method = { + .type = EAP_TYPE_MSCHAPV2, + .rx = eap_rx_mschapv2, +}; diff --git a/src/net/eapol.c b/src/net/eapol.c index 1b843e89..8b09ca23 100644 --- a/src/net/eapol.c +++ b/src/net/eapol.c @@ -49,37 +49,6 @@ static const uint8_t eapol_mac[ETH_ALEN] = { }; /** - * Update EAPoL supplicant state - * - * @v supplicant EAPoL supplicant - * @v timeout Timer ticks until next EAPoL-Start (if applicable) - */ -static void eapol_update ( struct eapol_supplicant *supplicant, - unsigned long timeout ) { - struct net_device *netdev = supplicant->eap.netdev; - - /* Check device and EAP state */ - if ( netdev_is_open ( netdev ) && netdev_link_ok ( netdev ) ) { - if ( supplicant->eap.done ) { - - /* EAP has completed: stop sending EAPoL-Start */ - stop_timer ( &supplicant->timer ); - - } else if ( ! timer_running ( &supplicant->timer ) ) { - - /* EAP has not yet begun: start sending EAPoL-Start */ - start_timer_fixed ( &supplicant->timer, timeout ); - } - - } else { - - /* Not ready: clear completion and stop sending EAPoL-Start */ - supplicant->eap.done = 0; - stop_timer ( &supplicant->timer ); - } -} - -/** * Process EAPoL packet * * @v iobuf I/O buffer @@ -186,8 +155,20 @@ static int eapol_eap_rx ( struct eapol_supplicant *supplicant, goto drop; } - /* Update supplicant state */ - eapol_update ( supplicant, EAPOL_START_INTERVAL ); + /* Update EAPoL-Start transmission timer */ + if ( supplicant->eap.flags & EAP_FL_PASSIVE ) { + /* Stop sending EAPoL-Start */ + if ( timer_running ( &supplicant->timer ) ) { + DBGC ( netdev, "EAPOL %s becoming passive\n", + netdev->name ); + } + stop_timer ( &supplicant->timer ); + } else if ( supplicant->eap.flags & EAP_FL_ONGOING ) { + /* Delay EAPoL-Start until after next expected packet */ + DBGC ( netdev, "EAPOL %s deferring Start\n", netdev->name ); + start_timer_fixed ( &supplicant->timer, EAP_WAIT_TIMEOUT ); + supplicant->count = 0; + } drop: free_iob ( iobuf ); @@ -270,6 +251,12 @@ static void eapol_expired ( struct retry_timer *timer, int fail __unused ) { container_of ( timer, struct eapol_supplicant, timer ); struct net_device *netdev = supplicant->eap.netdev; + /* Stop transmitting after maximum number of attempts */ + if ( supplicant->count++ >= EAPOL_START_COUNT ) { + DBGC ( netdev, "EAPOL %s giving up\n", netdev->name ); + return; + } + /* Schedule next transmission */ start_timer_fixed ( timer, EAPOL_START_INTERVAL ); @@ -309,15 +296,36 @@ static int eapol_probe ( struct net_device *netdev, void *priv ) { * @v netdev Network device * @v priv Private data */ -static void eapol_notify ( struct net_device *netdev __unused, void *priv ) { +static void eapol_notify ( struct net_device *netdev, void *priv ) { struct eapol_supplicant *supplicant = priv; /* Ignore non-EAPoL devices */ if ( ! supplicant->eap.netdev ) return; - /* Update supplicant state */ - eapol_update ( supplicant, 0 ); + /* Terminate and reset EAP when link goes down */ + if ( ! ( netdev_is_open ( netdev ) && netdev_link_ok ( netdev ) ) ) { + if ( timer_running ( &supplicant->timer ) ) { + DBGC ( netdev, "EAPOL %s shutting down\n", + netdev->name ); + } + supplicant->eap.flags = 0; + stop_timer ( &supplicant->timer ); + return; + } + + /* Do nothing if EAP is already in progress */ + if ( timer_running ( &supplicant->timer ) ) + return; + + /* Do nothing if EAP has already finished transmitting */ + if ( supplicant->eap.flags & EAP_FL_PASSIVE ) + return; + + /* Otherwise, start sending EAPoL-Start */ + start_timer_nodelay ( &supplicant->timer ); + supplicant->count = 0; + DBGC ( netdev, "EAPOL %s starting up\n", netdev->name ); } /** EAPoL driver */ diff --git a/src/net/tls.c b/src/net/tls.c index 000a8a78..5f89be45 100644 --- a/src/net/tls.c +++ b/src/net/tls.c @@ -158,6 +158,10 @@ FILE_LICENCE ( GPL2_OR_LATER ); #define EINFO_ENOTSUP_VERSION \ __einfo_uniqify ( EINFO_ENOTSUP, 0x04, \ "Unsupported protocol version" ) +#define ENOTSUP_CURVE __einfo_error ( EINFO_ENOTSUP_CURVE ) +#define EINFO_ENOTSUP_CURVE \ + __einfo_uniqify ( EINFO_ENOTSUP, 0x05, \ + "Unsupported elliptic curve" ) #define EPERM_ALERT __einfo_error ( EINFO_EPERM_ALERT ) #define EINFO_EPERM_ALERT \ __einfo_uniqify ( EINFO_EPERM, 0x01, \ @@ -1044,6 +1048,35 @@ tls_signature_hash_digest ( struct tls_signature_hash_id code ) { /****************************************************************************** * + * Ephemeral Elliptic Curve Diffie-Hellman key exchange + * + ****************************************************************************** + */ + +/** Number of supported named curves */ +#define TLS_NUM_NAMED_CURVES table_num_entries ( TLS_NAMED_CURVES ) + +/** + * Identify named curve + * + * @v named_curve Named curve specification + * @ret curve Named curve, or NULL + */ +static struct tls_named_curve * +tls_find_named_curve ( unsigned int named_curve ) { + struct tls_named_curve *curve; + + /* Identify named curve */ + for_each_table_entry ( curve, TLS_NAMED_CURVES ) { + if ( curve->code == named_curve ) + return curve; + } + + return NULL; +} + +/****************************************************************************** + * * Record handling * ****************************************************************************** @@ -1122,6 +1155,67 @@ static int tls_client_hello ( struct tls_connection *tls, struct tls_session *session = tls->session; size_t name_len = strlen ( session->name ); struct { + uint16_t type; + uint16_t len; + struct { + uint16_t len; + struct { + uint8_t type; + uint16_t len; + uint8_t name[name_len]; + } __attribute__ (( packed )) list[1]; + } __attribute__ (( packed )) data; + } __attribute__ (( packed )) *server_name_ext; + struct { + uint16_t type; + uint16_t len; + struct { + uint8_t max; + } __attribute__ (( packed )) data; + } __attribute__ (( packed )) *max_fragment_length_ext; + struct { + uint16_t type; + uint16_t len; + struct { + uint16_t len; + struct tls_signature_hash_id + code[TLS_NUM_SIG_HASH_ALGORITHMS]; + } __attribute__ (( packed )) data; + } __attribute__ (( packed )) *signature_algorithms_ext; + struct { + uint16_t type; + uint16_t len; + struct { + uint8_t len; + uint8_t data[ tls->secure_renegotiation ? + sizeof ( tls->verify.client ) :0 ]; + } __attribute__ (( packed )) data; + } __attribute__ (( packed )) *renegotiation_info_ext; + struct { + uint16_t type; + uint16_t len; + struct { + uint8_t data[session->ticket_len]; + } __attribute__ (( packed )) data; + } __attribute__ (( packed )) *session_ticket_ext; + struct { + uint16_t type; + uint16_t len; + struct { + uint16_t len; + uint16_t code[TLS_NUM_NAMED_CURVES]; + } __attribute__ (( packed )) data; + } __attribute__ (( packed )) *named_curve_ext; + struct { + typeof ( *server_name_ext ) server_name; + typeof ( *max_fragment_length_ext ) max_fragment_length; + typeof ( *signature_algorithms_ext ) signature_algorithms; + typeof ( *renegotiation_info_ext ) renegotiation_info; + typeof ( *session_ticket_ext ) session_ticket; + typeof ( *named_curve_ext ) + named_curve[TLS_NUM_NAMED_CURVES ? 1 : 0]; + } __attribute__ (( packed )) *extensions; + struct { uint32_t type_length; uint16_t version; uint8_t random[32]; @@ -1132,45 +1226,11 @@ static int tls_client_hello ( struct tls_connection *tls, uint8_t compression_methods_len; uint8_t compression_methods[1]; uint16_t extensions_len; - struct { - uint16_t server_name_type; - uint16_t server_name_len; - struct { - uint16_t len; - struct { - uint8_t type; - uint16_t len; - uint8_t name[name_len]; - } __attribute__ (( packed )) list[1]; - } __attribute__ (( packed )) server_name; - uint16_t max_fragment_length_type; - uint16_t max_fragment_length_len; - struct { - uint8_t max; - } __attribute__ (( packed )) max_fragment_length; - uint16_t signature_algorithms_type; - uint16_t signature_algorithms_len; - struct { - uint16_t len; - struct tls_signature_hash_id - code[TLS_NUM_SIG_HASH_ALGORITHMS]; - } __attribute__ (( packed )) signature_algorithms; - uint16_t renegotiation_info_type; - uint16_t renegotiation_info_len; - struct { - uint8_t len; - uint8_t data[ tls->secure_renegotiation ? - sizeof ( tls->verify.client ) :0]; - } __attribute__ (( packed )) renegotiation_info; - uint16_t session_ticket_type; - uint16_t session_ticket_len; - struct { - uint8_t data[session->ticket_len]; - } __attribute__ (( packed )) session_ticket; - } __attribute__ (( packed )) extensions; + typeof ( *extensions ) extensions; } __attribute__ (( packed )) hello; struct tls_cipher_suite *suite; struct tls_signature_hash_algorithm *sighash; + struct tls_named_curve *curve; unsigned int i; /* Construct record */ @@ -1188,43 +1248,66 @@ static int tls_client_hello ( struct tls_connection *tls, hello.cipher_suites[i++] = suite->code; hello.compression_methods_len = sizeof ( hello.compression_methods ); hello.extensions_len = htons ( sizeof ( hello.extensions ) ); - hello.extensions.server_name_type = htons ( TLS_SERVER_NAME ); - hello.extensions.server_name_len - = htons ( sizeof ( hello.extensions.server_name ) ); - hello.extensions.server_name.len - = htons ( sizeof ( hello.extensions.server_name.list ) ); - hello.extensions.server_name.list[0].type = TLS_SERVER_NAME_HOST_NAME; - hello.extensions.server_name.list[0].len - = htons ( sizeof ( hello.extensions.server_name.list[0].name )); - memcpy ( hello.extensions.server_name.list[0].name, session->name, - sizeof ( hello.extensions.server_name.list[0].name ) ); - hello.extensions.max_fragment_length_type - = htons ( TLS_MAX_FRAGMENT_LENGTH ); - hello.extensions.max_fragment_length_len - = htons ( sizeof ( hello.extensions.max_fragment_length ) ); - hello.extensions.max_fragment_length.max - = TLS_MAX_FRAGMENT_LENGTH_4096; - hello.extensions.signature_algorithms_type - = htons ( TLS_SIGNATURE_ALGORITHMS ); - hello.extensions.signature_algorithms_len - = htons ( sizeof ( hello.extensions.signature_algorithms ) ); - hello.extensions.signature_algorithms.len - = htons ( sizeof ( hello.extensions.signature_algorithms.code)); + extensions = &hello.extensions; + + /* Construct server name extension */ + server_name_ext = &extensions->server_name; + server_name_ext->type = htons ( TLS_SERVER_NAME ); + server_name_ext->len = htons ( sizeof ( server_name_ext->data ) ); + server_name_ext->data.len + = htons ( sizeof ( server_name_ext->data.list ) ); + server_name_ext->data.list[0].type = TLS_SERVER_NAME_HOST_NAME; + server_name_ext->data.list[0].len + = htons ( sizeof ( server_name_ext->data.list[0].name ) ); + memcpy ( server_name_ext->data.list[0].name, session->name, + sizeof ( server_name_ext->data.list[0].name ) ); + + /* Construct maximum fragment length extension */ + max_fragment_length_ext = &extensions->max_fragment_length; + max_fragment_length_ext->type = htons ( TLS_MAX_FRAGMENT_LENGTH ); + max_fragment_length_ext->len + = htons ( sizeof ( max_fragment_length_ext->data ) ); + max_fragment_length_ext->data.max = TLS_MAX_FRAGMENT_LENGTH_4096; + + /* Construct supported signature algorithms extension */ + signature_algorithms_ext = &extensions->signature_algorithms; + signature_algorithms_ext->type = htons ( TLS_SIGNATURE_ALGORITHMS ); + signature_algorithms_ext->len + = htons ( sizeof ( signature_algorithms_ext->data ) ); + signature_algorithms_ext->data.len + = htons ( sizeof ( signature_algorithms_ext->data.code ) ); i = 0 ; for_each_table_entry ( sighash, TLS_SIG_HASH_ALGORITHMS ) - hello.extensions.signature_algorithms.code[i++] = sighash->code; - hello.extensions.renegotiation_info_type - = htons ( TLS_RENEGOTIATION_INFO ); - hello.extensions.renegotiation_info_len - = htons ( sizeof ( hello.extensions.renegotiation_info ) ); - hello.extensions.renegotiation_info.len - = sizeof ( hello.extensions.renegotiation_info.data ); - memcpy ( hello.extensions.renegotiation_info.data, tls->verify.client, - sizeof ( hello.extensions.renegotiation_info.data ) ); - hello.extensions.session_ticket_type = htons ( TLS_SESSION_TICKET ); - hello.extensions.session_ticket_len - = htons ( sizeof ( hello.extensions.session_ticket ) ); - memcpy ( hello.extensions.session_ticket.data, session->ticket, - sizeof ( hello.extensions.session_ticket.data ) ); + signature_algorithms_ext->data.code[i++] = sighash->code; + + /* Construct renegotiation information extension */ + renegotiation_info_ext = &extensions->renegotiation_info; + renegotiation_info_ext->type = htons ( TLS_RENEGOTIATION_INFO ); + renegotiation_info_ext->len + = htons ( sizeof ( renegotiation_info_ext->data ) ); + renegotiation_info_ext->data.len + = sizeof ( renegotiation_info_ext->data.data ); + memcpy ( renegotiation_info_ext->data.data, tls->verify.client, + sizeof ( renegotiation_info_ext->data.data ) ); + + /* Construct session ticket extension */ + session_ticket_ext = &extensions->session_ticket; + session_ticket_ext->type = htons ( TLS_SESSION_TICKET ); + session_ticket_ext->len + = htons ( sizeof ( session_ticket_ext->data ) ); + memcpy ( session_ticket_ext->data.data, session->ticket, + sizeof ( session_ticket_ext->data.data ) ); + + /* Construct named curves extension, if applicable */ + if ( sizeof ( extensions->named_curve ) ) { + named_curve_ext = &extensions->named_curve[0]; + named_curve_ext->type = htons ( TLS_NAMED_CURVE ); + named_curve_ext->len + = htons ( sizeof ( named_curve_ext->data ) ); + named_curve_ext->data.len + = htons ( sizeof ( named_curve_ext->data.code ) ); + i = 0 ; for_each_table_entry ( curve, TLS_NAMED_CURVES ) + named_curve_ext->data.code[i++] = curve->code; + } return action ( tls, &hello, sizeof ( hello ) ); } @@ -1336,13 +1419,6 @@ static int tls_send_client_key_exchange_pubkey ( struct tls_connection *tls ) { tls_generate_master_secret ( tls, &pre_master_secret, sizeof ( pre_master_secret ) ); - /* Generate keys */ - if ( ( rc = tls_generate_keys ( tls ) ) != 0 ) { - DBGC ( tls, "TLS %p could not generate keys: %s\n", - tls, strerror ( rc ) ); - return rc; - } - /* Encrypt pre-master secret using server's public key */ memset ( &key_xchg, 0, sizeof ( key_xchg ) ); len = pubkey_encrypt ( pubkey, cipherspec->pubkey_ctx, @@ -1374,21 +1450,18 @@ struct tls_key_exchange_algorithm tls_pubkey_exchange_algorithm = { }; /** - * Transmit Client Key Exchange record using DHE key exchange + * Verify Diffie-Hellman parameter signature * * @v tls TLS connection + * @v param_len Diffie-Hellman parameter length * @ret rc Return status code */ -static int tls_send_client_key_exchange_dhe ( struct tls_connection *tls ) { +static int tls_verify_dh_params ( struct tls_connection *tls, + size_t param_len ) { struct tls_cipherspec *cipherspec = &tls->tx_cipherspec_pending; struct pubkey_algorithm *pubkey; struct digest_algorithm *digest; int use_sig_hash = tls_version ( tls, TLS_VERSION_TLS_1_2 ); - uint8_t private[ sizeof ( tls->client_random.random ) ]; - const struct { - uint16_t len; - uint8_t data[0]; - } __attribute__ (( packed )) *dh_val[3]; const struct { struct tls_signature_hash_id sig_hash[use_sig_hash]; uint16_t signature_len; @@ -1396,29 +1469,14 @@ static int tls_send_client_key_exchange_dhe ( struct tls_connection *tls ) { } __attribute__ (( packed )) *sig; const void *data; size_t remaining; - size_t frag_len; - unsigned int i; int rc; - /* Parse ServerKeyExchange */ - data = tls->server_key; - remaining = tls->server_key_len; - for ( i = 0 ; i < ( sizeof ( dh_val ) / sizeof ( dh_val[0] ) ) ; i++ ){ - dh_val[i] = data; - if ( ( sizeof ( *dh_val[i] ) > remaining ) || - ( ntohs ( dh_val[i]->len ) > ( remaining - - sizeof ( *dh_val[i] ) ) )){ - DBGC ( tls, "TLS %p received underlength " - "ServerKeyExchange\n", tls ); - DBGC_HDA ( tls, 0, tls->server_key, - tls->server_key_len ); - rc = -EINVAL_KEY_EXCHANGE; - goto err_header; - } - frag_len = ( sizeof ( *dh_val[i] ) + ntohs ( dh_val[i]->len )); - data += frag_len; - remaining -= frag_len; - } + /* Signature follows parameters */ + assert ( param_len <= tls->server_key_len ); + data = ( tls->server_key + param_len ); + remaining = ( tls->server_key_len - param_len ); + + /* Parse signature from ServerKeyExchange */ sig = data; if ( ( sizeof ( *sig ) > remaining ) || ( ntohs ( sig->signature_len ) > ( remaining - @@ -1426,8 +1484,7 @@ static int tls_send_client_key_exchange_dhe ( struct tls_connection *tls ) { DBGC ( tls, "TLS %p received underlength ServerKeyExchange\n", tls ); DBGC_HDA ( tls, 0, tls->server_key, tls->server_key_len ); - rc = -EINVAL_KEY_EXCHANGE; - goto err_header; + return -EINVAL_KEY_EXCHANGE; } /* Identify signature and hash algorithm */ @@ -1437,15 +1494,13 @@ static int tls_send_client_key_exchange_dhe ( struct tls_connection *tls ) { if ( ( ! pubkey ) || ( ! digest ) ) { DBGC ( tls, "TLS %p ServerKeyExchange unsupported " "signature and hash algorithm\n", tls ); - rc = -ENOTSUP_SIG_HASH; - goto err_sig_hash; + return -ENOTSUP_SIG_HASH; } if ( pubkey != cipherspec->suite->pubkey ) { DBGC ( tls, "TLS %p ServerKeyExchange incorrect " "signature algorithm %s (expected %s)\n", tls, pubkey->name, cipherspec->suite->pubkey->name ); - rc = -EPERM_KEY_EXCHANGE; - goto err_sig_hash; + return -EPERM_KEY_EXCHANGE; } } else { pubkey = cipherspec->suite->pubkey; @@ -1465,8 +1520,7 @@ static int tls_send_client_key_exchange_dhe ( struct tls_connection *tls ) { sizeof ( tls->client_random ) ); digest_update ( digest, ctx, tls->server_random, sizeof ( tls->server_random ) ); - digest_update ( digest, ctx, tls->server_key, - ( tls->server_key_len - remaining ) ); + digest_update ( digest, ctx, tls->server_key, param_len ); digest_final ( digest, ctx, hash ); /* Verify signature */ @@ -1477,10 +1531,56 @@ static int tls_send_client_key_exchange_dhe ( struct tls_connection *tls ) { "verification\n", tls ); DBGC_HDA ( tls, 0, tls->server_key, tls->server_key_len ); - rc = -EPERM_KEY_EXCHANGE; - goto err_verify; + return -EPERM_KEY_EXCHANGE; + } + } + + return 0; +} + +/** + * Transmit Client Key Exchange record using DHE key exchange + * + * @v tls TLS connection + * @ret rc Return status code + */ +static int tls_send_client_key_exchange_dhe ( struct tls_connection *tls ) { + uint8_t private[ sizeof ( tls->client_random.random ) ]; + const struct { + uint16_t len; + uint8_t data[0]; + } __attribute__ (( packed )) *dh_val[3]; + const void *data; + size_t remaining; + size_t frag_len; + size_t param_len; + unsigned int i; + int rc; + + /* Parse ServerKeyExchange */ + data = tls->server_key; + remaining = tls->server_key_len; + for ( i = 0 ; i < ( sizeof ( dh_val ) / sizeof ( dh_val[0] ) ) ; i++ ){ + dh_val[i] = data; + if ( ( sizeof ( *dh_val[i] ) > remaining ) || + ( ntohs ( dh_val[i]->len ) > ( remaining - + sizeof ( *dh_val[i] ) ) )){ + DBGC ( tls, "TLS %p received underlength " + "ServerKeyExchange\n", tls ); + DBGC_HDA ( tls, 0, tls->server_key, + tls->server_key_len ); + rc = -EINVAL_KEY_EXCHANGE; + goto err_header; } + frag_len = ( sizeof ( *dh_val[i] ) + ntohs ( dh_val[i]->len )); + data += frag_len; + remaining -= frag_len; } + param_len = ( tls->server_key_len - remaining ); + + /* Verify parameter signature */ + if ( ( rc = tls_verify_dh_params ( tls, param_len ) ) != 0 ) + goto err_verify; /* Generate Diffie-Hellman private key */ if ( ( rc = tls_generate_random ( tls, private, @@ -1540,13 +1640,6 @@ static int tls_send_client_key_exchange_dhe ( struct tls_connection *tls ) { /* Generate master secret */ tls_generate_master_secret ( tls, pre_master_secret, len ); - /* Generate keys */ - if ( ( rc = tls_generate_keys ( tls ) ) != 0 ) { - DBGC ( tls, "TLS %p could not generate keys: %s\n", - tls, strerror ( rc ) ); - goto err_generate_keys; - } - /* Transmit Client Key Exchange record */ if ( ( rc = tls_send_handshake ( tls, key_xchg, sizeof ( *key_xchg ) ) ) !=0){ @@ -1554,14 +1647,12 @@ static int tls_send_client_key_exchange_dhe ( struct tls_connection *tls ) { } err_send_handshake: - err_generate_keys: err_dhe_key: free ( dynamic ); } err_alloc: err_random: err_verify: - err_sig_hash: err_header: return rc; } @@ -1573,6 +1664,119 @@ struct tls_key_exchange_algorithm tls_dhe_exchange_algorithm = { }; /** + * Transmit Client Key Exchange record using ECDHE key exchange + * + * @v tls TLS connection + * @ret rc Return status code + */ +static int tls_send_client_key_exchange_ecdhe ( struct tls_connection *tls ) { + struct tls_named_curve *curve; + const struct { + uint8_t curve_type; + uint16_t named_curve; + uint8_t public_len; + uint8_t public[0]; + } __attribute__ (( packed )) *ecdh; + size_t param_len; + int rc; + + /* Parse ServerKeyExchange record */ + ecdh = tls->server_key; + if ( ( sizeof ( *ecdh ) > tls->server_key_len ) || + ( ecdh->public_len > ( tls->server_key_len - sizeof ( *ecdh ) ))){ + DBGC ( tls, "TLS %p received underlength ServerKeyExchange\n", + tls ); + DBGC_HDA ( tls, 0, tls->server_key, tls->server_key_len ); + return -EINVAL_KEY_EXCHANGE; + } + param_len = ( sizeof ( *ecdh ) + ecdh->public_len ); + + /* Verify parameter signature */ + if ( ( rc = tls_verify_dh_params ( tls, param_len ) ) != 0 ) + return rc; + + /* Identify named curve */ + if ( ecdh->curve_type != TLS_NAMED_CURVE_TYPE ) { + DBGC ( tls, "TLS %p unsupported curve type %d\n", + tls, ecdh->curve_type ); + DBGC_HDA ( tls, 0, tls->server_key, tls->server_key_len ); + return -ENOTSUP_CURVE; + } + curve = tls_find_named_curve ( ecdh->named_curve ); + if ( ! curve ) { + DBGC ( tls, "TLS %p unsupported named curve %d\n", + tls, ntohs ( ecdh->named_curve ) ); + DBGC_HDA ( tls, 0, tls->server_key, tls->server_key_len ); + return -ENOTSUP_CURVE; + } + + /* Check key length */ + if ( ecdh->public_len != curve->curve->keysize ) { + DBGC ( tls, "TLS %p invalid %s key\n", + tls, curve->curve->name ); + DBGC_HDA ( tls, 0, tls->server_key, tls->server_key_len ); + return -EINVAL_KEY_EXCHANGE; + } + + /* Construct pre-master secret and ClientKeyExchange record */ + { + size_t len = curve->curve->keysize; + uint8_t private[len]; + uint8_t pre_master_secret[len]; + struct { + uint32_t type_length; + uint8_t public_len; + uint8_t public[len]; + } __attribute__ (( packed )) key_xchg; + + /* Generate ephemeral private key */ + if ( ( rc = tls_generate_random ( tls, private, + sizeof ( private ) ) ) != 0){ + return rc; + } + + /* Calculate pre-master secret */ + if ( ( rc = elliptic_multiply ( curve->curve, + ecdh->public, private, + pre_master_secret ) ) != 0 ) { + DBGC ( tls, "TLS %p could not exchange ECDHE key: %s\n", + tls, strerror ( rc ) ); + return rc; + } + + /* Generate master secret */ + tls_generate_master_secret ( tls, pre_master_secret, len ); + + /* Generate Client Key Exchange record */ + key_xchg.type_length = + ( cpu_to_le32 ( TLS_CLIENT_KEY_EXCHANGE ) | + htonl ( sizeof ( key_xchg ) - + sizeof ( key_xchg.type_length ) ) ); + key_xchg.public_len = len; + if ( ( rc = elliptic_multiply ( curve->curve, NULL, private, + key_xchg.public ) ) != 0 ) { + DBGC ( tls, "TLS %p could not generate ECDHE key: %s\n", + tls, strerror ( rc ) ); + return rc; + } + + /* Transmit Client Key Exchange record */ + if ( ( rc = tls_send_handshake ( tls, &key_xchg, + sizeof ( key_xchg ) ) ) !=0){ + return rc; + } + } + + return 0; +} + +/** Ephemeral Elliptic Curve Diffie-Hellman key exchange algorithm */ +struct tls_key_exchange_algorithm tls_ecdhe_exchange_algorithm = { + .name = "ecdhe", + .exchange = tls_send_client_key_exchange_ecdhe, +}; + +/** * Transmit Client Key Exchange record * * @v tls TLS connection @@ -1581,9 +1785,23 @@ struct tls_key_exchange_algorithm tls_dhe_exchange_algorithm = { static int tls_send_client_key_exchange ( struct tls_connection *tls ) { struct tls_cipherspec *cipherspec = &tls->tx_cipherspec_pending; struct tls_cipher_suite *suite = cipherspec->suite; + int rc; /* Transmit Client Key Exchange record via key exchange algorithm */ - return suite->exchange->exchange ( tls ); + if ( ( rc = suite->exchange->exchange ( tls ) ) != 0 ) { + DBGC ( tls, "TLS %p could not exchange keys: %s\n", + tls, strerror ( rc ) ); + return rc; + } + + /* Generate keys from master secret */ + if ( ( rc = tls_generate_keys ( tls ) ) != 0 ) { + DBGC ( tls, "TLS %p could not generate keys: %s\n", + tls, strerror ( rc ) ); + return rc; + } + + return 0; } /** @@ -2727,9 +2945,9 @@ static int tls_send_plaintext ( struct tls_connection *tls, unsigned int type, } __attribute__ (( packed )) iv; struct tls_auth_header authhdr; struct tls_header *tlshdr; - void *plaintext = NULL; - size_t plaintext_len = len; - struct io_buffer *ciphertext = NULL; + void *plaintext; + size_t plaintext_len; + struct io_buffer *ciphertext; size_t ciphertext_len; size_t padding_len; uint8_t mac[digest->digestsize]; @@ -2738,7 +2956,10 @@ static int tls_send_plaintext ( struct tls_connection *tls, unsigned int type, /* Construct initialisation vector */ memcpy ( iv.fixed, cipherspec->fixed_iv, sizeof ( iv.fixed ) ); - tls_generate_random ( tls, iv.record, sizeof ( iv.record ) ); + if ( ( rc = tls_generate_random ( tls, iv.record, + sizeof ( iv.record ) ) ) != 0 ) { + goto err_random; + } /* Construct authentication data */ authhdr.seq = cpu_to_be64 ( tls->tx_seq ); @@ -2747,7 +2968,7 @@ static int tls_send_plaintext ( struct tls_connection *tls, unsigned int type, authhdr.header.length = htons ( len ); /* Calculate padding length */ - plaintext_len += suite->mac_len; + plaintext_len = ( len + suite->mac_len ); if ( is_block_cipher ( cipher ) ) { padding_len = ( ( ( cipher->blocksize - 1 ) & -( plaintext_len + 1 ) ) + 1 ); @@ -2762,7 +2983,7 @@ static int tls_send_plaintext ( struct tls_connection *tls, unsigned int type, DBGC ( tls, "TLS %p could not allocate %zd bytes for " "plaintext\n", tls, plaintext_len ); rc = -ENOMEM_TX_PLAINTEXT; - goto done; + goto err_plaintext; } /* Assemble plaintext */ @@ -2796,7 +3017,7 @@ static int tls_send_plaintext ( struct tls_connection *tls, unsigned int type, DBGC ( tls, "TLS %p could not allocate %zd bytes for " "ciphertext\n", tls, ciphertext_len ); rc = -ENOMEM_TX_CIPHERTEXT; - goto done; + goto err_ciphertext; } /* Assemble ciphertext */ @@ -2821,15 +3042,22 @@ static int tls_send_plaintext ( struct tls_connection *tls, unsigned int type, iob_disown ( ciphertext ) ) ) != 0 ) { DBGC ( tls, "TLS %p could not deliver ciphertext: %s\n", tls, strerror ( rc ) ); - goto done; + goto err_deliver; } /* Update TX state machine to next record */ tls->tx_seq += 1; - done: - free ( plaintext ); + assert ( plaintext == NULL ); + assert ( ciphertext == NULL ); + return 0; + + err_deliver: free_iob ( ciphertext ); + err_ciphertext: + free ( plaintext ); + err_plaintext: + err_random: return rc; } diff --git a/src/net/validator.c b/src/net/validator.c index 693d4464..69c0df33 100644 --- a/src/net/validator.c +++ b/src/net/validator.c @@ -57,8 +57,7 @@ struct validator_action { /** Name */ const char *name; /** Action to take upon completed transfer */ - int ( * done ) ( struct validator *validator, const void *data, - size_t len ); + void ( * done ) ( struct validator *validator, int rc ); }; /** A certificate validator */ @@ -72,6 +71,40 @@ struct validator { /** Process */ struct process process; + /** Most relevant status code + * + * The cross-signed certificate mechanism may attempt several + * downloads as it works its way up the provided partial chain + * to locate a suitable cross-signed certificate with which to + * complete the chain. + * + * Some of these download or validation attempts may fail for + * uninteresting reasons (i.e. because a cross-signed + * certificate has never existed for that link in the chain). + * + * We must therefore keep track of the most relevant error + * that has occurred, in order to be able to report a + * meaningful overall status to the user. + * + * As a concrete example: consider the case of an expired OCSP + * signer for an intermediate certificate. This will cause + * OCSP validation to fail for that intermediate certificate, + * and this is the error that should eventually be reported to + * the user. We do not want to instead report the + * uninteresting fact that no cross-signed certificate was + * found for the remaining links in the chain, nor do we want + * to report just a generic "OCSP required" error. + * + * We record the most relevant status code whenever a + * definitely relevant error occurs, and clear it whenever we + * successfully make forward progress (e.g. by completing + * OCSP, or by adding new cross-signed certificates). + * + * When we subsequently attempt to validate the chain, we + * report the most relevant error status code (if recorded), + * otherwise we report the validation error itself. + */ + int rc; /** Root of trust (or NULL to use default) */ struct x509_root *root; @@ -84,13 +117,15 @@ struct validator { /** Current action */ const struct validator_action *action; - /** Current certificate + /** Current certificate (for progress reporting) * * This will always be present within the certificate chain * and so this pointer does not hold a reference to the * certificate. */ struct x509_certificate *cert; + /** Current link within certificate chain */ + struct x509_link *link; }; /** @@ -196,17 +231,36 @@ static const char crosscert_default[] = CROSSCERT; * Append cross-signing certificates to certificate chain * * @v validator Certificate validator - * @v data Raw cross-signing certificate data - * @v len Length of raw data + * @v rc Completion status code * @ret rc Return status code */ -static int validator_append ( struct validator *validator, - const void *data, size_t len ) { +static void validator_append ( struct validator *validator, int rc ) { struct asn1_cursor cursor; struct x509_chain *certs; struct x509_certificate *cert; - struct x509_certificate *last; - int rc; + struct x509_link *link; + struct x509_link *prev; + + /* Check for errors */ + if ( rc != 0 ) { + DBGC ( validator, "VALIDATOR %p \"%s\" could not download ", + validator, validator_name ( validator ) ); + DBGC ( validator, "\"%s\" cross-signature: %s\n", + x509_name ( validator->cert ), strerror ( rc ) ); + /* If the overall validation is going to fail, then we + * will end up attempting multiple downloads for + * non-existent cross-signed certificates as we work + * our way up the certificate chain. Do not record + * these as relevant errors, since we want to + * eventually report whichever much more relevant + * error occurred previously. + */ + goto err_irrelevant; + } + DBGC ( validator, "VALIDATOR %p \"%s\" downloaded ", + validator, validator_name ( validator ) ); + DBGC ( validator, "\"%s\" cross-signature\n", + x509_name ( validator->cert ) ); /* Allocate certificate list */ certs = x509_alloc_chain(); @@ -216,8 +270,8 @@ static int validator_append ( struct validator *validator, } /* Initialise cursor */ - cursor.data = data; - cursor.len = len; + cursor.data = validator->buffer.data; + cursor.len = validator->buffer.len; /* Enter certificateSet */ if ( ( rc = asn1_enter ( &cursor, ASN1_SET ) ) != 0 ) { @@ -230,14 +284,14 @@ static int validator_append ( struct validator *validator, /* Add each certificate to list */ while ( cursor.len ) { - /* Add certificate to chain */ + /* Add certificate to list */ if ( ( rc = x509_append_raw ( certs, cursor.data, cursor.len ) ) != 0 ) { DBGC ( validator, "VALIDATOR %p \"%s\" could not " "append certificate: %s\n", validator, validator_name ( validator ), strerror ( rc) ); DBGC_HDA ( validator, 0, cursor.data, cursor.len ); - return rc; + goto err_append_raw; } cert = x509_last ( certs ); DBGC ( validator, "VALIDATOR %p \"%s\" found certificate ", @@ -248,8 +302,12 @@ static int validator_append ( struct validator *validator, asn1_skip_any ( &cursor ); } + /* Truncate existing certificate chain at current link */ + link = validator->link; + assert ( link->flags & X509_LINK_FL_CROSSED ); + x509_truncate ( validator->chain, link ); + /* Append certificates to chain */ - last = x509_last ( validator->chain ); if ( ( rc = x509_auto_append ( validator->chain, certs ) ) != 0 ) { DBGC ( validator, "VALIDATOR %p \"%s\" could not append " "certificates: %s\n", validator, @@ -257,26 +315,31 @@ static int validator_append ( struct validator *validator, goto err_auto_append; } - /* Check that at least one certificate has been added */ - if ( last == x509_last ( validator->chain ) ) { - DBGC ( validator, "VALIDATOR %p \"%s\" failed to append any " - "applicable certificates\n", validator, - validator_name ( validator ) ); - rc = -EACCES; - goto err_no_progress; + /* Record that a cross-signed certificate download has already + * been performed for all but the last of the appended + * certificates. (It may be necessary to perform a further + * download to complete the chain, if this download did not + * extend all the way to a root of trust.) + */ + prev = NULL; + list_for_each_entry_continue ( link, &validator->chain->links, list ) { + if ( prev ) + prev->flags |= X509_LINK_FL_CROSSED; + prev = link; } - /* Drop reference to certificate list */ - x509_chain_put ( certs ); - - return 0; + /* Success */ + rc = 0; - err_no_progress: err_auto_append: + err_append_raw: err_certificateset: x509_chain_put ( certs ); err_alloc_certs: - return rc; + validator->rc = rc; + err_irrelevant: + /* Do not record irrelevant errors */ + return; } /** Cross-signing certificate download validator action */ @@ -289,11 +352,12 @@ static const struct validator_action validator_crosscert = { * Start download of cross-signing certificate * * @v validator Certificate validator - * @v cert X.509 certificate + * @v link Link in certificate chain * @ret rc Return status code */ static int validator_start_download ( struct validator *validator, - struct x509_certificate *cert ) { + struct x509_link *link ) { + struct x509_certificate *cert = link->cert; const struct asn1_cursor *issuer = &cert->issuer.raw; const char *crosscert; char *crosscert_copy; @@ -336,6 +400,7 @@ static int validator_start_download ( struct validator *validator, /* Set completion handler */ validator->action = &validator_crosscert; validator->cert = cert; + validator->link = link; /* Open URI */ if ( ( rc = xfer_open_uri_string ( &validator->xfer, @@ -346,14 +411,20 @@ static int validator_start_download ( struct validator *validator, goto err_open_uri_string; } + /* Free temporary allocations */ + free ( uri_string ); + free ( crosscert_copy ); + /* Success */ - rc = 0; + return 0; + intf_restart ( &validator->xfer, rc ); err_open_uri_string: free ( uri_string ); err_alloc_uri_string: err_check_uri_string: free ( crosscert_copy ); + validator->rc = rc; return rc; } @@ -367,21 +438,27 @@ static int validator_start_download ( struct validator *validator, * Validate OCSP response * * @v validator Certificate validator - * @v data Raw OCSP response - * @v len Length of raw data - * @ret rc Return status code + * @v rc Completion status code */ -static int validator_ocsp_validate ( struct validator *validator, - const void *data, size_t len ) { +static void validator_ocsp_validate ( struct validator *validator, int rc ) { + const void *data = validator->buffer.data; + size_t len = validator->buffer.len; time_t now; - int rc; + + /* Check for errors */ + if ( rc != 0 ) { + DBGC ( validator, "VALIDATOR %p \"%s\" could not fetch OCSP " + "response: %s\n", validator, + validator_name ( validator ), strerror ( rc ) ); + goto err_status; + } /* Record OCSP response */ if ( ( rc = ocsp_response ( validator->ocsp, data, len ) ) != 0 ) { DBGC ( validator, "VALIDATOR %p \"%s\" could not record OCSP " "response: %s\n", validator, - validator_name ( validator ),strerror ( rc ) ); - return rc; + validator_name ( validator ), strerror ( rc ) ); + goto err_response; } /* Validate OCSP response */ @@ -390,14 +467,20 @@ static int validator_ocsp_validate ( struct validator *validator, DBGC ( validator, "VALIDATOR %p \"%s\" could not validate " "OCSP response: %s\n", validator, validator_name ( validator ), strerror ( rc ) ); - return rc; + goto err_validate; } - /* Drop reference to OCSP check */ + /* Success */ + DBGC ( validator, "VALIDATOR %p \"%s\" checked ", + validator, validator_name ( validator ) ); + DBGC ( validator, "\"%s\" via OCSP\n", x509_name ( validator->cert ) ); + + err_validate: + err_response: + err_status: ocsp_put ( validator->ocsp ); validator->ocsp = NULL; - - return 0; + validator->rc = rc; } /** OCSP validator action */ @@ -426,7 +509,7 @@ static int validator_start_ocsp ( struct validator *validator, DBGC ( validator, "VALIDATOR %p \"%s\" could not create OCSP " "check: %s\n", validator, validator_name ( validator ), strerror ( rc ) ); - return rc; + goto err_check; } /* Set completion handler */ @@ -444,10 +527,18 @@ static int validator_start_ocsp ( struct validator *validator, DBGC ( validator, "VALIDATOR %p \"%s\" could not open %s: " "%s\n", validator, validator_name ( validator ), uri_string, strerror ( rc ) ); - return rc; + goto err_open; } return 0; + + intf_restart ( &validator->xfer, rc ); + err_open: + ocsp_put ( validator->ocsp ); + validator->ocsp = NULL; + err_check: + validator->rc = rc; + return rc; } /**************************************************************************** @@ -466,34 +557,18 @@ static void validator_xfer_close ( struct validator *validator, int rc ) { /* Close data transfer interface */ intf_restart ( &validator->xfer, rc ); - - /* Check for errors */ - if ( rc != 0 ) { - DBGC ( validator, "VALIDATOR %p \"%s\" transfer failed: %s\n", - validator, validator_name ( validator ), - strerror ( rc ) ); - goto err_transfer; - } DBGC2 ( validator, "VALIDATOR %p \"%s\" transfer complete\n", validator, validator_name ( validator ) ); /* Process completed download */ assert ( validator->action != NULL ); - if ( ( rc = validator->action->done ( validator, validator->buffer.data, - validator->buffer.len ) ) != 0 ) - goto err_append; + validator->action->done ( validator, rc ); /* Free downloaded data */ xferbuf_free ( &validator->buffer ); /* Resume validation process */ process_add ( &validator->process ); - - return; - - err_append: - err_transfer: - validator_finished ( validator, rc ); } /** @@ -515,7 +590,7 @@ static int validator_xfer_deliver ( struct validator *validator, DBGC ( validator, "VALIDATOR %p \"%s\" could not receive " "data: %s\n", validator, validator_name ( validator ), strerror ( rc ) ); - validator_finished ( validator, rc ); + validator_xfer_close ( validator, rc ); return rc; } @@ -544,10 +619,10 @@ static struct interface_descriptor validator_xfer_desc = * @v validator Certificate validator */ static void validator_step ( struct validator *validator ) { + struct x509_chain *chain = validator->chain; struct x509_link *link; + struct x509_link *prev; struct x509_certificate *cert; - struct x509_certificate *issuer = NULL; - struct x509_certificate *last; time_t now; int rc; @@ -556,57 +631,109 @@ static void validator_step ( struct validator *validator ) { * previously. */ now = time ( NULL ); - if ( ( rc = x509_validate_chain ( validator->chain, now, NULL, + if ( ( rc = x509_validate_chain ( chain, now, NULL, validator->root ) ) == 0 ) { DBGC ( validator, "VALIDATOR %p \"%s\" validated\n", validator, validator_name ( validator ) ); validator_finished ( validator, 0 ); return; } + DBGC ( validator, "VALIDATOR %p \"%s\" not yet valid: %s\n", + validator, validator_name ( validator ), strerror ( rc ) ); - /* If there is a certificate that could be validated using - * OCSP, try it. + /* Record as the most relevant error, if no more relevant + * error has already been recorded. */ - list_for_each_entry ( link, &validator->chain->links, list ) { - cert = issuer; - issuer = link->cert; - if ( ! cert ) - continue; - if ( ! x509_is_valid ( issuer, validator->root ) ) - continue; - /* The issuer is valid, but this certificate is not - * yet valid. If OCSP is applicable, start it. - */ - if ( ocsp_required ( cert ) ) { - /* Start OCSP */ - if ( ( rc = validator_start_ocsp ( validator, cert, - issuer ) ) != 0 ) { - validator_finished ( validator, rc ); - return; - } - return; - } - /* Otherwise, this is a permanent failure */ - validator_finished ( validator, rc ); - return; + if ( validator->rc == 0 ) + validator->rc = rc; + + /* Find the first valid link in the chain, if any + * + * There is no point in attempting OCSP or cross-signed + * certificate downloads for certificates after the first + * valid link in the chain, since they cannot make a + * difference to the overall validation of the chain. + */ + prev = NULL; + list_for_each_entry ( link, &chain->links, list ) { + + /* Dump link information (for debugging) */ + DBGC ( validator, "VALIDATOR %p \"%s\" has link ", + validator, validator_name ( validator ) ); + DBGC ( validator, "\"%s\"%s%s%s%s%s\n", + x509_name ( link->cert ), + ( ocsp_required ( link->cert ) ? " [NEEDOCSP]" : "" ), + ( ( link->flags & X509_LINK_FL_OCSPED ) ? + " [OCSPED]" : "" ), + ( ( link->flags & X509_LINK_FL_CROSSED ) ? + " [CROSSED]" : "" ), + ( x509_is_self_signed ( link->cert ) ? " [SELF]" : "" ), + ( x509_is_valid ( link->cert, validator->root ) ? + " [VALID]" : "" ) ); + + /* Stop at first valid link */ + if ( x509_is_valid ( link->cert, validator->root ) ) + break; + prev = link; } - /* If chain ends with a self-issued certificate, then there is - * nothing more to do. + /* If this link is the issuer for a certificate that is + * pending an OCSP check attempt, then start OCSP to validate + * that certificate. + * + * If OCSP is not required for the issued certificate, or has + * already been attempted, or if we were unable to start OCSP + * for any reason, then proceed to attempting a cross-signed + * certificate download (which may end up replacing this + * issuer anyway). */ - last = x509_last ( validator->chain ); - if ( asn1_compare ( &last->issuer.raw, &last->subject.raw ) == 0 ) { - validator_finished ( validator, rc ); - return; + if ( ( ! list_is_head_entry ( link, &chain->links, list ) ) && + ( ! ( link->flags & X509_LINK_FL_OCSPED ) ) && + ( prev != NULL ) && ocsp_required ( prev->cert ) ) { + + /* Mark OCSP as attempted with this issuer */ + link->flags |= X509_LINK_FL_OCSPED; + + /* Start OCSP */ + if ( ( rc = validator_start_ocsp ( validator, prev->cert, + link->cert ) ) == 0 ) { + /* Sleep until OCSP is complete */ + return; + } } - /* Otherwise, try to download a suitable cross-signing - * certificate. + /* Work back up the chain (starting from the already + * identified first valid link, if any) to find a not-yet + * valid certificate for which we could attempt to download a + * cross-signed certificate chain. */ - if ( ( rc = validator_start_download ( validator, last ) ) != 0 ) { - validator_finished ( validator, rc ); - return; + list_for_each_entry_continue_reverse ( link, &chain->links, list ) { + cert = link->cert; + + /* Sanity check */ + assert ( ! x509_is_valid ( cert, validator->root ) ); + + /* Skip self-signed certificates (cannot be cross-signed) */ + if ( x509_is_self_signed ( cert ) ) + continue; + + /* Skip previously attempted cross-signed downloads */ + if ( link->flags & X509_LINK_FL_CROSSED ) + continue; + + /* Mark cross-signed certificate download as attempted */ + link->flags |= X509_LINK_FL_CROSSED; + + /* Start cross-signed certificate download */ + if ( ( rc = validator_start_download ( validator, + link ) ) == 0 ) { + /* Sleep until download is complete */ + return; + } } + + /* Nothing more to try: fail the validation */ + validator_finished ( validator, validator->rc ); } /** Certificate validator process descriptor */ |