mirror of https://github.com/ipxe/ipxe.git
[tls] Use asynchronous certificate validator
Signed-off-by: Michael Brown <mcb30@ipxe.org>pull/6/head
parent
29dcb0631b
commit
f19565f58f
|
@ -237,6 +237,13 @@ struct tls_session {
|
||||||
|
|
||||||
/** Server certificate chain */
|
/** Server certificate chain */
|
||||||
struct x509_chain *chain;
|
struct x509_chain *chain;
|
||||||
|
/** Certificate validator */
|
||||||
|
struct interface validator;
|
||||||
|
|
||||||
|
/** Client has finished security negotiation */
|
||||||
|
unsigned int client_finished;
|
||||||
|
/** Server has finished security negotiation */
|
||||||
|
unsigned int server_finished;
|
||||||
|
|
||||||
/** TX sequence number */
|
/** TX sequence number */
|
||||||
uint64_t tx_seq;
|
uint64_t tx_seq;
|
||||||
|
@ -244,8 +251,6 @@ struct tls_session {
|
||||||
unsigned int tx_pending;
|
unsigned int tx_pending;
|
||||||
/** TX process */
|
/** TX process */
|
||||||
struct process process;
|
struct process process;
|
||||||
/** TX ready for plaintext data */
|
|
||||||
int tx_ready;
|
|
||||||
|
|
||||||
/** RX sequence number */
|
/** RX sequence number */
|
||||||
uint64_t rx_seq;
|
uint64_t rx_seq;
|
||||||
|
|
157
src/net/tls.c
157
src/net/tls.c
|
@ -43,6 +43,7 @@ FILE_LICENCE ( GPL2_OR_LATER );
|
||||||
#include <ipxe/x509.h>
|
#include <ipxe/x509.h>
|
||||||
#include <ipxe/clientcert.h>
|
#include <ipxe/clientcert.h>
|
||||||
#include <ipxe/rbg.h>
|
#include <ipxe/rbg.h>
|
||||||
|
#include <ipxe/validator.h>
|
||||||
#include <ipxe/tls.h>
|
#include <ipxe/tls.h>
|
||||||
|
|
||||||
/* Disambiguate the various error causes */
|
/* Disambiguate the various error causes */
|
||||||
|
@ -93,6 +94,16 @@ static void tls_set_uint24 ( uint8_t field24[3], unsigned long value ) {
|
||||||
*field32 |= cpu_to_be32 ( value << 8 );
|
*field32 |= cpu_to_be32 ( value << 8 );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if TLS session is ready for application data
|
||||||
|
*
|
||||||
|
* @v tls TLS session
|
||||||
|
* @ret is_ready TLS session is ready
|
||||||
|
*/
|
||||||
|
static int tls_ready ( struct tls_session *tls ) {
|
||||||
|
return ( tls->client_finished && tls->server_finished );
|
||||||
|
}
|
||||||
|
|
||||||
/******************************************************************************
|
/******************************************************************************
|
||||||
*
|
*
|
||||||
* Hybrid MD5+SHA1 hash as used by TLSv1.1 and earlier
|
* Hybrid MD5+SHA1 hash as used by TLSv1.1 and earlier
|
||||||
|
@ -197,9 +208,10 @@ static void tls_close ( struct tls_session *tls, int rc ) {
|
||||||
/* Remove process */
|
/* Remove process */
|
||||||
process_del ( &tls->process );
|
process_del ( &tls->process );
|
||||||
|
|
||||||
/* Close ciphertext and plaintext streams */
|
/* Close all interfaces */
|
||||||
intf_shutdown ( &tls->cipherstream, rc );
|
intf_shutdown ( &tls->cipherstream, rc );
|
||||||
intf_shutdown ( &tls->plainstream, rc );
|
intf_shutdown ( &tls->plainstream, rc );
|
||||||
|
intf_shutdown ( &tls->validator, rc );
|
||||||
}
|
}
|
||||||
|
|
||||||
/******************************************************************************
|
/******************************************************************************
|
||||||
|
@ -1111,7 +1123,9 @@ static int tls_send_finished ( struct tls_session *tls ) {
|
||||||
uint8_t verify_data[12];
|
uint8_t verify_data[12];
|
||||||
} __attribute__ (( packed )) finished;
|
} __attribute__ (( packed )) finished;
|
||||||
uint8_t digest_out[ digest->digestsize ];
|
uint8_t digest_out[ digest->digestsize ];
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
/* Construct record */
|
||||||
memset ( &finished, 0, sizeof ( finished ) );
|
memset ( &finished, 0, sizeof ( finished ) );
|
||||||
finished.type_length = ( cpu_to_le32 ( TLS_FINISHED ) |
|
finished.type_length = ( cpu_to_le32 ( TLS_FINISHED ) |
|
||||||
htonl ( sizeof ( finished ) -
|
htonl ( sizeof ( finished ) -
|
||||||
|
@ -1121,7 +1135,15 @@ static int tls_send_finished ( struct tls_session *tls ) {
|
||||||
finished.verify_data, sizeof ( finished.verify_data ),
|
finished.verify_data, sizeof ( finished.verify_data ),
|
||||||
"client finished", digest_out, sizeof ( digest_out ) );
|
"client finished", digest_out, sizeof ( digest_out ) );
|
||||||
|
|
||||||
return tls_send_handshake ( tls, &finished, sizeof ( finished ) );
|
/* Transmit record */
|
||||||
|
if ( ( rc = tls_send_handshake ( tls, &finished,
|
||||||
|
sizeof ( finished ) ) ) != 0 )
|
||||||
|
return rc;
|
||||||
|
|
||||||
|
/* Mark client as finished */
|
||||||
|
tls->client_finished = 1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1354,10 +1376,6 @@ static int tls_new_certificate ( struct tls_session *tls,
|
||||||
} __attribute__ (( packed )) *certificate = data;
|
} __attribute__ (( packed )) *certificate = data;
|
||||||
size_t certificates_len = tls_uint24 ( certificate->length );
|
size_t certificates_len = tls_uint24 ( certificate->length );
|
||||||
const void *end = ( certificate->certificates + certificates_len );
|
const void *end = ( certificate->certificates + certificates_len );
|
||||||
struct tls_cipherspec *cipherspec = &tls->tx_cipherspec_pending;
|
|
||||||
struct pubkey_algorithm *pubkey = cipherspec->suite->pubkey;
|
|
||||||
struct x509_certificate *cert;
|
|
||||||
time_t now;
|
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
/* Sanity check */
|
/* Sanity check */
|
||||||
|
@ -1373,35 +1391,6 @@ static int tls_new_certificate ( struct tls_session *tls,
|
||||||
certificates_len ) ) != 0 )
|
certificates_len ) ) != 0 )
|
||||||
return rc;
|
return rc;
|
||||||
|
|
||||||
/* Validate certificate chain */
|
|
||||||
now = time ( NULL );
|
|
||||||
if ( ( rc = x509_validate_chain ( tls->chain, now, NULL ) ) != 0 ) {
|
|
||||||
DBGC ( tls, "TLS %p could not validate certificate chain: %s\n",
|
|
||||||
tls, strerror ( rc ) );
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Extract first certificate */
|
|
||||||
cert = x509_first ( tls->chain );
|
|
||||||
assert ( cert != NULL );
|
|
||||||
|
|
||||||
/* Verify server name */
|
|
||||||
if ( ( cert->subject.name == NULL ) ||
|
|
||||||
( strcmp ( cert->subject.name, tls->name ) != 0 ) ) {
|
|
||||||
DBGC ( tls, "TLS %p server name incorrect (expected %s, got "
|
|
||||||
"%s)\n", tls, tls->name, cert->subject.name );
|
|
||||||
return -EACCES_WRONG_NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Initialise public key algorithm */
|
|
||||||
if ( ( rc = pubkey_init ( pubkey, cipherspec->pubkey_ctx,
|
|
||||||
cert->subject.public_key.raw.data,
|
|
||||||
cert->subject.public_key.raw.len ) ) != 0 ) {
|
|
||||||
DBGC ( tls, "TLS %p cannot initialise public key: %s\n",
|
|
||||||
tls, strerror ( rc ) );
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1442,6 +1431,7 @@ static int tls_new_server_hello_done ( struct tls_session *tls,
|
||||||
char next[0];
|
char next[0];
|
||||||
} __attribute__ (( packed )) *hello_done = data;
|
} __attribute__ (( packed )) *hello_done = data;
|
||||||
const void *end = hello_done->next;
|
const void *end = hello_done->next;
|
||||||
|
int rc;
|
||||||
|
|
||||||
/* Sanity check */
|
/* Sanity check */
|
||||||
if ( end != ( data + len ) ) {
|
if ( end != ( data + len ) ) {
|
||||||
|
@ -1451,11 +1441,12 @@ static int tls_new_server_hello_done ( struct tls_session *tls,
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Schedule Client Key Exchange, Change Cipher, and Finished */
|
/* Begin certificate validation */
|
||||||
tls->tx_pending |= ( TLS_TX_CLIENT_KEY_EXCHANGE |
|
if ( ( rc = create_validator ( &tls->validator, tls->chain ) ) != 0 ) {
|
||||||
TLS_TX_CHANGE_CIPHER |
|
DBGC ( tls, "TLS %p could not start certificate validation: "
|
||||||
TLS_TX_FINISHED );
|
"%s\n", tls, strerror ( rc ) );
|
||||||
tls_tx_resume ( tls );
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -1497,8 +1488,8 @@ static int tls_new_finished ( struct tls_session *tls,
|
||||||
return -EPERM;
|
return -EPERM;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Mark session as ready to transmit plaintext data */
|
/* Mark server as finished */
|
||||||
tls->tx_ready = 1;
|
tls->server_finished = 1;
|
||||||
|
|
||||||
/* Send notification of a window change */
|
/* Send notification of a window change */
|
||||||
xfer_window_changed ( &tls->plainstream );
|
xfer_window_changed ( &tls->plainstream );
|
||||||
|
@ -1601,6 +1592,8 @@ static int tls_new_record ( struct tls_session *tls, unsigned int type,
|
||||||
case TLS_TYPE_HANDSHAKE:
|
case TLS_TYPE_HANDSHAKE:
|
||||||
return tls_new_handshake ( tls, data, len );
|
return tls_new_handshake ( tls, data, len );
|
||||||
case TLS_TYPE_DATA:
|
case TLS_TYPE_DATA:
|
||||||
|
if ( ! tls_ready ( tls ) )
|
||||||
|
return -ENOTCONN;
|
||||||
return xfer_deliver_raw ( &tls->plainstream, data, len );
|
return xfer_deliver_raw ( &tls->plainstream, data, len );
|
||||||
default:
|
default:
|
||||||
/* RFC4346 says that we should just ignore unknown
|
/* RFC4346 says that we should just ignore unknown
|
||||||
|
@ -2015,7 +2008,7 @@ static int tls_new_ciphertext ( struct tls_session *tls,
|
||||||
static size_t tls_plainstream_window ( struct tls_session *tls ) {
|
static size_t tls_plainstream_window ( struct tls_session *tls ) {
|
||||||
|
|
||||||
/* Block window unless we are ready to accept data */
|
/* Block window unless we are ready to accept data */
|
||||||
if ( ! tls->tx_ready )
|
if ( ! tls_ready ( tls ) )
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
return xfer_window ( &tls->cipherstream );
|
return xfer_window ( &tls->cipherstream );
|
||||||
|
@ -2035,7 +2028,7 @@ static int tls_plainstream_deliver ( struct tls_session *tls,
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
/* Refuse unless we are ready to accept data */
|
/* Refuse unless we are ready to accept data */
|
||||||
if ( ! tls->tx_ready ) {
|
if ( ! tls_ready ( tls ) ) {
|
||||||
rc = -ENOTCONN;
|
rc = -ENOTCONN;
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
@ -2192,6 +2185,79 @@ static struct interface_descriptor tls_cipherstream_desc =
|
||||||
INTF_DESC_PASSTHRU ( struct tls_session, cipherstream,
|
INTF_DESC_PASSTHRU ( struct tls_session, cipherstream,
|
||||||
tls_cipherstream_ops, plainstream );
|
tls_cipherstream_ops, plainstream );
|
||||||
|
|
||||||
|
/******************************************************************************
|
||||||
|
*
|
||||||
|
* Certificate validator
|
||||||
|
*
|
||||||
|
******************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle certificate validation completion
|
||||||
|
*
|
||||||
|
* @v tls TLS session
|
||||||
|
* @v rc Reason for completion
|
||||||
|
*/
|
||||||
|
static void tls_validator_done ( struct tls_session *tls, int rc ) {
|
||||||
|
struct tls_cipherspec *cipherspec = &tls->tx_cipherspec_pending;
|
||||||
|
struct pubkey_algorithm *pubkey = cipherspec->suite->pubkey;
|
||||||
|
struct x509_certificate *cert;
|
||||||
|
|
||||||
|
/* Close validator interface */
|
||||||
|
intf_restart ( &tls->validator, rc );
|
||||||
|
|
||||||
|
/* Check for validation failure */
|
||||||
|
if ( rc != 0 ) {
|
||||||
|
DBGC ( tls, "TLS %p certificate validation failed: %s\n",
|
||||||
|
tls, strerror ( rc ) );
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
DBGC ( tls, "TLS %p certificate validation succeeded\n", tls );
|
||||||
|
|
||||||
|
/* Extract first certificate */
|
||||||
|
cert = x509_first ( tls->chain );
|
||||||
|
assert ( cert != NULL );
|
||||||
|
|
||||||
|
/* Verify server name */
|
||||||
|
if ( ( cert->subject.name == NULL ) ||
|
||||||
|
( strcmp ( cert->subject.name, tls->name ) != 0 ) ) {
|
||||||
|
DBGC ( tls, "TLS %p server name incorrect (expected %s, got "
|
||||||
|
"%s)\n", tls, tls->name, cert->subject.name );
|
||||||
|
rc = -EACCES_WRONG_NAME;
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Initialise public key algorithm */
|
||||||
|
if ( ( rc = pubkey_init ( pubkey, cipherspec->pubkey_ctx,
|
||||||
|
cert->subject.public_key.raw.data,
|
||||||
|
cert->subject.public_key.raw.len ) ) != 0 ) {
|
||||||
|
DBGC ( tls, "TLS %p cannot initialise public key: %s\n",
|
||||||
|
tls, strerror ( rc ) );
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Schedule Client Key Exchange, Change Cipher, and Finished */
|
||||||
|
tls->tx_pending |= ( TLS_TX_CLIENT_KEY_EXCHANGE |
|
||||||
|
TLS_TX_CHANGE_CIPHER |
|
||||||
|
TLS_TX_FINISHED );
|
||||||
|
tls_tx_resume ( tls );
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
err:
|
||||||
|
tls_close ( tls, rc );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** TLS certificate validator interface operations */
|
||||||
|
static struct interface_operation tls_validator_ops[] = {
|
||||||
|
INTF_OP ( intf_close, struct tls_session *, tls_validator_done ),
|
||||||
|
};
|
||||||
|
|
||||||
|
/** TLS certificate validator interface descriptor */
|
||||||
|
static struct interface_descriptor tls_validator_desc =
|
||||||
|
INTF_DESC ( struct tls_session, validator, tls_validator_ops );
|
||||||
|
|
||||||
/******************************************************************************
|
/******************************************************************************
|
||||||
*
|
*
|
||||||
* Controlling process
|
* Controlling process
|
||||||
|
@ -2307,6 +2373,8 @@ int add_tls ( struct interface *xfer, const char *name,
|
||||||
tls->name = name;
|
tls->name = name;
|
||||||
intf_init ( &tls->plainstream, &tls_plainstream_desc, &tls->refcnt );
|
intf_init ( &tls->plainstream, &tls_plainstream_desc, &tls->refcnt );
|
||||||
intf_init ( &tls->cipherstream, &tls_cipherstream_desc, &tls->refcnt );
|
intf_init ( &tls->cipherstream, &tls_cipherstream_desc, &tls->refcnt );
|
||||||
|
intf_init ( &tls->validator, &tls_validator_desc, &tls->refcnt );
|
||||||
|
process_init ( &tls->process, &tls_process_desc, &tls->refcnt );
|
||||||
tls->version = TLS_VERSION_TLS_1_2;
|
tls->version = TLS_VERSION_TLS_1_2;
|
||||||
tls_clear_cipher ( tls, &tls->tx_cipherspec );
|
tls_clear_cipher ( tls, &tls->tx_cipherspec );
|
||||||
tls_clear_cipher ( tls, &tls->tx_cipherspec_pending );
|
tls_clear_cipher ( tls, &tls->tx_cipherspec_pending );
|
||||||
|
@ -2327,7 +2395,6 @@ int add_tls ( struct interface *xfer, const char *name,
|
||||||
tls->handshake_digest = &sha256_algorithm;
|
tls->handshake_digest = &sha256_algorithm;
|
||||||
tls->handshake_ctx = tls->handshake_sha256_ctx;
|
tls->handshake_ctx = tls->handshake_sha256_ctx;
|
||||||
tls->tx_pending = TLS_TX_CLIENT_HELLO;
|
tls->tx_pending = TLS_TX_CLIENT_HELLO;
|
||||||
process_init ( &tls->process, &tls_process_desc, &tls->refcnt );
|
|
||||||
|
|
||||||
/* Attach to parent interface, mortalise self, and return */
|
/* Attach to parent interface, mortalise self, and return */
|
||||||
intf_plug_plug ( &tls->plainstream, xfer );
|
intf_plug_plug ( &tls->plainstream, xfer );
|
||||||
|
|
Loading…
Reference in New Issue