mirror of
https://github.com/ipxe/ipxe
synced 2026-10-07 15:05:42 +03:00
[tls] Add support for binding via a CertificateVerify record
The format of the signature found within a CertificateVerify is identical to the format of the signature within a ServerKeyExchange. Abstract out the logic for verifying a ServerKeyExchange and use it to verify the signature for both ServerKeyExchange and CertificateVerify. Note that a CertificateVerify that is erroneously received under TLS version 1.2 will always fail verification because the key schedule is not able to generate a signable digest for the server endpoint. A ServerKeyExchange that is erroneously received under TLS version 1.3 will fail validation because the TLS version 1.3 cipher suites provide no way to parse the ServerKeyExchange parameters. (An interestingly deviant server that chooses to negotiate TLS version 1.3 with a TLS version 1.2 cipher suite would be able to send a ServerKeyExchange with a valid signature and have that key contribute accumulatively to the key schedule: this would not conform to the protocol, but does not actually weaken any of the security properties required to establish the secure channel.) Signed-off-by: Michael Brown <mcb30@ipxe.org>
This commit is contained in:
@@ -353,7 +353,7 @@ struct tls_signature_hash_algorithm {
|
||||
struct digest_algorithm *digest;
|
||||
/** Public-key algorithm */
|
||||
struct pubkey_algorithm *pubkey;
|
||||
/** Required certificate OID-identified algorithm */
|
||||
/** Required certificate OID-identified algorithm, if any */
|
||||
struct asn1_algorithm *algorithm;
|
||||
/** Numeric code (in network-endian order) */
|
||||
uint16_t code;
|
||||
|
||||
+158
-85
@@ -110,6 +110,10 @@ FILE_SECBOOT ( PERMITTED );
|
||||
#define EINFO_EINVAL_INNER \
|
||||
__einfo_uniqify ( EINFO_EINVAL, 0x10, \
|
||||
"Invalid inner plaintext" )
|
||||
#define EINVAL_SIGNATURE __einfo_error ( EINFO_EINVAL_SIGNATURE )
|
||||
#define EINFO_EINVAL_SIGNATURE \
|
||||
__einfo_uniqify ( EINFO_EINVAL, 0x11, \
|
||||
"Invalid signature" )
|
||||
#define EIO_ALERT __einfo_error ( EINFO_EIO_ALERT )
|
||||
#define EINFO_EIO_ALERT \
|
||||
__einfo_uniqify ( EINFO_EIO, 0x01, \
|
||||
@@ -1302,6 +1306,68 @@ static int tls_key_build ( struct tls_connection *tls,
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify signature over parameters used to construct shared secret
|
||||
*
|
||||
* @v tls TLS connection
|
||||
* @v sig_hash Signature hash algorithm
|
||||
* @v sig Signature
|
||||
* @v sig_len Length of signature
|
||||
* @v params Additional parameters
|
||||
* @v params_len Length of additional parameters
|
||||
* @ret rc Return status code
|
||||
*/
|
||||
static int tls_key_verify ( struct tls_connection *tls,
|
||||
struct tls_signature_hash_algorithm *sig_hash,
|
||||
const void *sig, size_t sig_len,
|
||||
const void *params, size_t params_len ) {
|
||||
const struct asn1_cursor signature = { sig, sig_len };
|
||||
struct pubkey_algorithm *pubkey = sig_hash->pubkey;
|
||||
struct digest_algorithm *digest = sig_hash->digest;
|
||||
struct x509_certificate *cert;
|
||||
uint8_t tbshash[digest->digestsize];
|
||||
int rc;
|
||||
|
||||
/* Identify server certificate */
|
||||
if ( ! tls->server.chain ) {
|
||||
DBGC ( tls, "TLS %p has no server certificate chain\n", tls );
|
||||
return -ENOENT_CERT;
|
||||
}
|
||||
cert = x509_first ( tls->server.chain );
|
||||
if ( ! cert ) {
|
||||
DBGC ( tls, "TLS %p has no server certificate\n", tls );
|
||||
return -ENOENT_CERT;
|
||||
}
|
||||
|
||||
/* Identify algorithms */
|
||||
if ( sig_hash->algorithm &&
|
||||
( sig_hash->algorithm != cert->subject.public_key.algorithm ) ) {
|
||||
DBGC ( tls, "TLS %p cannot use %s public key\n",
|
||||
tls, cert->subject.public_key.algorithm->name );
|
||||
return -EPERM_KEY_EXCHANGE;
|
||||
}
|
||||
DBGC ( tls, "TLS %p using signature hash %s-%s\n",
|
||||
tls, pubkey->name, digest->name );
|
||||
|
||||
/* Calculate digest */
|
||||
if ( ( rc = tlskey_tbshash ( &tls->key, &tls_server, digest, params,
|
||||
params_len, tbshash ) ) != 0 ) {
|
||||
DBGC ( tls, "TLS %p could not generate signable digest: %s\n",
|
||||
tls, strerror ( rc ) );
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Verify signature and bind shared secret */
|
||||
if ( ( rc = channel_bind_verify ( &tls->channel, cert, pubkey, digest,
|
||||
tbshash, &signature ) ) != 0 ) {
|
||||
DBGC ( tls, "TLS %p failed signature verification: %s\n",
|
||||
tls, strerror ( rc ) );
|
||||
return rc;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
*
|
||||
* Secure channel operations
|
||||
@@ -2911,6 +2977,89 @@ static int tls_new_certificate ( struct tls_connection *tls,
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify a signature record
|
||||
*
|
||||
* @v tls TLS connection
|
||||
* @v data Signature record
|
||||
* @v len Length of signature record
|
||||
* @v params Additional parameters
|
||||
* @v params_len Length of additional parameters
|
||||
* @ret rc Return status code
|
||||
*/
|
||||
static int tls_verify_signature ( struct tls_connection *tls,
|
||||
const void *data, size_t len,
|
||||
const void *params, size_t params_len ) {
|
||||
struct tls_cipher_suite *suite = tls->suite;
|
||||
struct tls_signature_hash_algorithm *sig_hash;
|
||||
struct tls_signature_hash_algorithm tmp;
|
||||
int use_sig_hash = tls_version ( tls, TLS_VERSION_TLS_1_2 );
|
||||
const struct {
|
||||
uint16_t sig_hash[use_sig_hash];
|
||||
uint16_t signature_len;
|
||||
uint8_t signature[0];
|
||||
} __attribute__ (( packed )) *sig;
|
||||
size_t signature_len;
|
||||
int rc;
|
||||
|
||||
/* Parse signature */
|
||||
if ( sizeof ( *sig ) > len ) {
|
||||
DBGC ( tls, "TLS %p received underlength signature\n", tls );
|
||||
DBGC_HDA ( tls, 0, data, len );
|
||||
return -EINVAL_SIGNATURE;
|
||||
}
|
||||
sig = data;
|
||||
signature_len = ntohs ( sig->signature_len );
|
||||
if ( signature_len > ( len - sizeof ( *sig ) ) ) {
|
||||
DBGC ( tls, "TLS %p received overlength signature\n", tls );
|
||||
DBGC_HDA ( tls, 0, data, len );
|
||||
return -EINVAL_SIGNATURE;
|
||||
}
|
||||
|
||||
/* Identify signature and hash algorithm */
|
||||
if ( use_sig_hash ) {
|
||||
sig_hash = tls_find_signature_hash ( sig->sig_hash[0] );
|
||||
if ( ! sig_hash ) {
|
||||
DBGC ( tls, "TLS %p unsupported signature hash "
|
||||
"%#04x\n", tls, sig->sig_hash[0] );
|
||||
return -ENOTSUP_SIG_HASH;
|
||||
}
|
||||
} else {
|
||||
sig_hash = &tmp;
|
||||
memset ( sig_hash, 0, sizeof ( *sig_hash ) );
|
||||
sig_hash->pubkey = suite->pubkey;
|
||||
sig_hash->digest = &md5_sha1_algorithm;
|
||||
}
|
||||
|
||||
/* Verify signature */
|
||||
if ( ( rc = tls_key_verify ( tls, sig_hash, sig->signature,
|
||||
signature_len, params,
|
||||
params_len ) ) != 0 ) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive new Certificate Verify handshake record
|
||||
*
|
||||
* @v tls TLS connection
|
||||
* @v data Plaintext handshake record
|
||||
* @v len Length of plaintext handshake record
|
||||
* @ret rc Return status code
|
||||
*/
|
||||
static int tls_new_certificate_verify ( struct tls_connection *tls,
|
||||
const void *data, size_t len ) {
|
||||
int rc;
|
||||
|
||||
/* Verify signature */
|
||||
if ( ( rc = tls_verify_signature ( tls, data, len, NULL, 0 ) ) != 0 )
|
||||
return rc;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive new Server Key Exchange handshake record
|
||||
*
|
||||
@@ -2923,76 +3072,14 @@ static int tls_new_server_key_exchange ( struct tls_connection *tls,
|
||||
const void *data, size_t len ) {
|
||||
struct tls_cipher_suite *suite = tls->suite;
|
||||
struct tls_key_exchange_parameters params;
|
||||
struct tls_signature_hash_algorithm *sig_hash;
|
||||
struct x509_certificate *cert;
|
||||
struct pubkey_algorithm *pubkey;
|
||||
struct digest_algorithm *digest;
|
||||
int use_sig_hash = tls_version ( tls, TLS_VERSION_TLS_1_2 );
|
||||
const struct {
|
||||
uint16_t sig_hash[use_sig_hash];
|
||||
uint16_t signature_len;
|
||||
uint8_t signature[0];
|
||||
} __attribute__ (( packed )) *sig;
|
||||
struct asn1_cursor signature;
|
||||
size_t remaining;
|
||||
int rc;
|
||||
|
||||
/* Identify server certificate */
|
||||
if ( ! tls->server.chain ) {
|
||||
DBGC ( tls, "TLS %p has no server certificate chain\n", tls );
|
||||
return -ENOENT_CERT;
|
||||
}
|
||||
cert = x509_first ( tls->server.chain );
|
||||
if ( ! cert ) {
|
||||
DBGC ( tls, "TLS %p has no server certificate\n", tls );
|
||||
return -ENOENT_CERT;
|
||||
}
|
||||
|
||||
/* Parse parameters */
|
||||
if ( ( rc = suite->exchange->parse ( tls, data, len, ¶ms ) ) != 0)
|
||||
return rc;
|
||||
DBGC ( tls, "TLS %p using named group %s-%s\n",
|
||||
tls, suite->exchange->name, params.group->exchange->name );
|
||||
|
||||
/* Signature follows parameters */
|
||||
assert ( params.len <= len );
|
||||
sig = ( data + params.len );
|
||||
remaining = ( len - params.len );
|
||||
|
||||
/* Parse signature from ServerKeyExchange */
|
||||
if ( ( sizeof ( *sig ) > remaining ) ||
|
||||
( ntohs ( sig->signature_len ) > ( remaining -
|
||||
sizeof ( *sig ) ) ) ) {
|
||||
DBGC ( tls, "TLS %p received underlength ServerKeyExchange\n",
|
||||
tls );
|
||||
DBGC_HDA ( tls, 0, data, len );
|
||||
return -EINVAL_KEY_EXCHANGE;
|
||||
}
|
||||
signature.data = sig->signature;
|
||||
signature.len = ntohs ( sig->signature_len );
|
||||
|
||||
/* Identify signature and hash algorithm */
|
||||
if ( use_sig_hash ) {
|
||||
sig_hash = tls_find_signature_hash ( sig->sig_hash[0] );
|
||||
if ( ! sig_hash ) {
|
||||
DBGC ( tls, "TLS %p unsupported signature hash "
|
||||
"%#04x\n", tls, sig->sig_hash[0] );
|
||||
return -ENOTSUP_SIG_HASH;
|
||||
}
|
||||
pubkey = sig_hash->pubkey;
|
||||
digest = sig_hash->digest;
|
||||
DBGC ( tls, "TLS %p using signature hash %s-%s\n",
|
||||
tls, pubkey->name, digest->name );
|
||||
if ( sig_hash->algorithm !=
|
||||
cert->subject.public_key.algorithm ) {
|
||||
DBGC ( tls, "TLS %p cannot use %s public key\n", tls,
|
||||
cert->subject.public_key.algorithm->name );
|
||||
return -EPERM_KEY_EXCHANGE;
|
||||
}
|
||||
} else {
|
||||
pubkey = suite->pubkey;
|
||||
digest = &md5_sha1_algorithm;
|
||||
}
|
||||
|
||||
/* Generate pre-master secret */
|
||||
if ( ( rc = tls_key_agree ( tls, params.group, params.partner,
|
||||
@@ -3000,29 +3087,11 @@ static int tls_new_server_key_exchange ( struct tls_connection *tls,
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Verify signature */
|
||||
{
|
||||
uint8_t tbshash[digest->digestsize];
|
||||
|
||||
/* Calculate digest */
|
||||
if ( ( rc = tlskey_tbshash ( &tls->key, &tls_server,
|
||||
digest, data, params.len,
|
||||
tbshash ) ) != 0 ) {
|
||||
DBGC ( tls, "TLS %p could not generate "
|
||||
"ServerKeyExchange digest: %s\n",
|
||||
tls, strerror ( rc ) );
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Verify signature to bind pre-master secret */
|
||||
if ( ( rc = channel_bind_verify ( &tls->channel, cert,
|
||||
pubkey, digest, tbshash,
|
||||
&signature ) ) != 0 ) {
|
||||
DBGC ( tls, "TLS %p ServerKeyExchange failed "
|
||||
"verification: %s\n", tls, strerror ( rc ) );
|
||||
DBGC_HDA ( tls, 0, data, len );
|
||||
return rc;
|
||||
}
|
||||
/* Verify signature (immediately follows parameters) */
|
||||
if ( ( rc = tls_verify_signature ( tls, ( data + params.len ),
|
||||
( len - params.len ),
|
||||
data, params.len ) ) != 0 ) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Record named group */
|
||||
@@ -3244,6 +3313,10 @@ static int tls_new_handshake ( struct tls_connection *tls,
|
||||
case TLS_CERTIFICATE:
|
||||
rc = tls_new_certificate ( tls, payload, payload_len );
|
||||
break;
|
||||
case TLS_CERTIFICATE_VERIFY:
|
||||
rc = tls_new_certificate_verify ( tls, payload,
|
||||
payload_len );
|
||||
break;
|
||||
case TLS_SERVER_KEY_EXCHANGE:
|
||||
rc = tls_new_server_key_exchange ( tls, payload,
|
||||
payload_len );
|
||||
|
||||
Reference in New Issue
Block a user