[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:
Michael Brown
2026-09-14 18:22:49 +01:00
parent 14c23c5bb8
commit c2e9bd951a
2 changed files with 159 additions and 86 deletions
+1 -1
View File
@@ -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
View File
@@ -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, &params ) ) != 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 );