Allow RSA client certs on ECDHE-ECDSA mutual auth

The TLS 1.2 server derived the single advertised ClientCertificateType
and the signature_algorithms list in its CertificateRequest from the
negotiated cipher suite's own signature algorithm. On an ECDHE-ECDSA
suite only ecdsa_sign was offered (and only ECDSA sig algs), so RSA
clients could not authenticate even though the server could happily
verify an RSA certificate. The same was true in reverse for an RSA
server: the CertificateRequest only advertised rsa_sign.

Refactor SendCertificateRequest to advertise certificate_types and
signature_algorithms covering both sig families when both are compiled
in. Three static helpers in internal.c keep the logic in one place
without mutating ssl->suites:

  GetServerCertReqCertTypes    - certificate_types to emit
  GetServerCertReqHashSigAlgo  - signature_algorithms to emit
  InServerCertReqHashSigAlgo   - membership check used for verification

The advertised lists are written to stack buffers in the caller. To
keep DoCertificateVerify in agreement with what we actually sent, the
SupportedHashSigAlgo call site there is replaced with
InServerCertReqHashSigAlgo, which rebuilds the same list locally and
looks up the client's chosen algo.

Replace the magic certTypes buffer size with a new
MAX_CERT_REQ_CERT_TYPE_CNT constant declared next to
ClientCertificateType.

Add two end-to-end mutual-auth tests covering both directions:

  test_tls12_ecdhe_ecdsa_rsa_client_cert - ECDSA server, RSA client
  test_tls12_ecdhe_rsa_ecdsa_client_cert - RSA  server, ECDSA client

Update test_certreq_sighash_algos to permit RSA / RSA-PSS sig algs in
the ECDHE-ECDSA CertificateRequest; the previous assertion locked in
the ECDSA-only behaviour that this change corrects.

TLS 1.3 is unaffected: RFC 8446 removed certificate_types from
CertificateRequest, and TLS 1.3 cipher suites do not bind a signature
algorithm, so the server's hashSigAlgo already covers both sig
families when either has been compiled in.
This commit is contained in:
Juliusz Sosinowicz
2026-05-28 14:05:19 +00:00
parent 95158fa31f
commit fdda31b5c3
6 changed files with 307 additions and 71 deletions
+168 -40
View File
@@ -25828,6 +25828,153 @@ int SendCertificate(WOLFSSL* ssl)
#if !defined(NO_TLS)
/* Returns the certificate_types this server advertises in its
* CertificateRequest. The list is broader than the negotiated cipher suite's
* own signature algorithm so a client may authenticate with a certificate of
* a different type (e.g. an RSA client on an ECDHE-ECDSA suite). */
WC_MAYBE_UNUSED static int GetServerCertReqCertTypes(const WOLFSSL* ssl,
byte* certTypes)
{
int n = 0;
(void)ssl;
(void)certTypes;
#ifdef HAVE_ECC
if ((ssl->options.cipherSuite0 == ECC_BYTE ||
ssl->options.cipherSuite0 == CHACHA_BYTE) &&
ssl->specs.sig_algo == ecc_dsa_sa_algo) {
certTypes[n++] = ecdsa_sign;
#ifndef NO_RSA
certTypes[n++] = rsa_sign;
#endif
}
else
#if defined(WOLFSSL_SM2) && defined(WOLFSSL_SM3) && \
(defined(WOLFSSL_SM4_CBC) || defined(WOLFSSL_SM4_GCM) || \
defined(WOLFSSL_SM4_CCM))
if (ssl->options.cipherSuite0 == SM_BYTE && (0
#ifdef BUILD_TLS_ECDHE_ECDSA_WITH_SM4_CBC_SM3
|| ssl->options.cipherSuite == TLS_ECDHE_ECDSA_WITH_SM4_CBC_SM3
#endif
#ifdef BUILD_TLS_ECDHE_ECDSA_WITH_SM4_GCM_SM3
|| ssl->options.cipherSuite == TLS_ECDHE_ECDSA_WITH_SM4_GCM_SM3
#endif
#ifdef BUILD_TLS_ECDHE_ECDSA_WITH_SM4_CCM_SM3
|| ssl->options.cipherSuite == TLS_ECDHE_ECDSA_WITH_SM4_CCM_SM3
#endif
)) {
certTypes[n++] = ecdsa_sign;
}
else
#endif
#endif /* HAVE_ECC */
{
#ifndef NO_RSA
certTypes[n++] = rsa_sign;
#endif
#ifdef HAVE_ECC
certTypes[n++] = ecdsa_sign;
#endif
}
return n;
}
/* Returns the set of sig families covered by the given hash/sig algorithm
* list, as a bitmask of SIG_* values. Uses DecodeSigAlg so the NEW_SA_MAJOR
* encoding (ED25519/ED448/RSA-PSS-PSS/brainpool) is classified correctly. */
WC_MAYBE_UNUSED static int HashSigAlgoCoverage(const byte* hashSigAlgo,
word16 hashSigAlgoSz)
{
int coverage = 0;
word16 j;
byte hashAlgo;
byte sigAlgo;
for (j = 0; (j + 1) < hashSigAlgoSz; j += HELLO_EXT_SIGALGO_SZ) {
DecodeSigAlg(&hashSigAlgo[j], &hashAlgo, &sigAlgo);
(void)hashAlgo;
switch (sigAlgo) {
case rsa_sa_algo:
#ifdef WC_RSA_PSS
case rsa_pss_sa_algo:
case rsa_pss_pss_algo:
#endif
coverage |= SIG_RSA;
break;
#ifdef HAVE_ECC
case ecc_dsa_sa_algo:
#ifdef HAVE_ECC_BRAINPOOL
case ecc_brainpool_sa_algo:
#endif
#if defined(WOLFSSL_SM2) && defined(WOLFSSL_SM3)
case sm2_sa_algo:
#endif
coverage |= SIG_ECDSA;
break;
#endif
default:
break;
}
}
return coverage;
}
/* Builds the signature_algorithms this server advertises in its
* CertificateRequest. Respects a user-configured suites->hashSigAlgo (e.g.
* via wolfSSL_set1_sigalgs_list) and only broadens the list when one of the
* advertised certificate_types has no matching signature algorithm in the
* configured list. The result is written to the caller's buffer; no SSL
* state is modified. */
WC_MAYBE_UNUSED static void GetServerCertReqHashSigAlgo(const WOLFSSL* ssl,
byte* hashSigAlgo, word16* hashSigAlgoSz)
{
const Suites* suites = WOLFSSL_SUITES(ssl);
byte certTypes[MAX_CERT_REQ_CERT_TYPE_CNT];
int typeTotal;
int need = 0;
int have;
int j;
word16 localSz = 0;
typeTotal = GetServerCertReqCertTypes(ssl, certTypes);
for (j = 0; j < typeTotal; j++) {
if (certTypes[j] == rsa_sign)
need |= SIG_RSA;
else if (certTypes[j] == ecdsa_sign)
need |= SIG_ECDSA;
}
have = HashSigAlgoCoverage(suites->hashSigAlgo, suites->hashSigAlgoSz);
if ((need & ~have) != 0) {
/* The configured list is missing signature algorithms for at least
* one of the advertised certificate_types. Build a broader list
* locally that covers every advertised type. */
InitSuitesHashSigAlgo(hashSigAlgo, need | have, 1, 0,
ssl->buffers.keySz, &localSz);
*hashSigAlgoSz = localSz;
return;
}
XMEMCPY(hashSigAlgo, suites->hashSigAlgo, suites->hashSigAlgoSz);
*hashSigAlgoSz = suites->hashSigAlgoSz;
}
/* Returns 1 if algo (2 bytes) is in the server's CertificateRequest
* signature_algorithms list, 0 otherwise. Used to validate the client's
* CertificateVerify against what we actually advertised. */
WC_MAYBE_UNUSED static int InServerCertReqHashSigAlgo(const WOLFSSL* ssl,
const byte* algo)
{
byte list[WOLFSSL_MAX_SIGALGO];
word16 listSz = 0;
word16 j;
GetServerCertReqHashSigAlgo(ssl, list, &listSz);
for (j = 0; (j + 1) < listSz; j += HELLO_EXT_SIGALGO_SZ) {
if (XMEMCMP(&list[j], algo, HELLO_EXT_SIGALGO_SZ) == 0)
return 1;
}
return 0;
}
/* handle generation of certificate_request (13) */
int SendCertificateRequest(WOLFSSL* ssl)
{
@@ -25839,16 +25986,24 @@ int SendCertificateRequest(WOLFSSL* ssl)
#ifndef WOLFSSL_NO_CA_NAMES
WOLF_STACK_OF(WOLFSSL_X509_NAME)* names;
#endif
const Suites* suites = WOLFSSL_SUITES(ssl);
int typeTotal = 1; /* only 1 for now */
int reqSz = ENUM_LEN + typeTotal + REQ_HEADER_SZ; /* add auth later */
byte certTypes[MAX_CERT_REQ_CERT_TYPE_CNT];
int typeTotal;
int t;
byte localHashSigAlgo[WOLFSSL_MAX_SIGALGO];
word16 localHashSigAlgoSz = 0;
int reqSz;
WOLFSSL_START(WC_FUNC_CERTIFICATE_REQUEST_SEND);
WOLFSSL_ENTER("SendCertificateRequest");
typeTotal = GetServerCertReqCertTypes(ssl, certTypes);
if (IsAtLeastTLSv1_2(ssl))
reqSz += LENGTH_SZ + suites->hashSigAlgoSz;
GetServerCertReqHashSigAlgo(ssl, localHashSigAlgo, &localHashSigAlgoSz);
reqSz = ENUM_LEN + typeTotal + REQ_HEADER_SZ; /* add auth later */
if (IsAtLeastTLSv1_2(ssl))
reqSz += LENGTH_SZ + localHashSigAlgoSz;
#ifndef WOLFSSL_NO_CA_NAMES
/* Certificate Authorities */
@@ -25901,43 +26056,16 @@ int SendCertificateRequest(WOLFSSL* ssl)
/* write to output */
output[i++] = (byte)typeTotal; /* # of types */
#ifdef HAVE_ECC
if ((ssl->options.cipherSuite0 == ECC_BYTE ||
ssl->options.cipherSuite0 == CHACHA_BYTE) &&
ssl->specs.sig_algo == ecc_dsa_sa_algo) {
output[i++] = ecdsa_sign;
}
else
#if defined(WOLFSSL_SM2) && defined(WOLFSSL_SM3) && \
(defined(WOLFSSL_SM4_CBC) || defined(WOLFSSL_SM4_GCM) || \
defined(WOLFSSL_SM4_CCM))
if (ssl->options.cipherSuite0 == SM_BYTE && (0
#ifdef BUILD_TLS_ECDHE_ECDSA_WITH_SM4_CBC_SM3
|| ssl->options.cipherSuite == TLS_ECDHE_ECDSA_WITH_SM4_CBC_SM3
#endif
#ifdef BUILD_TLS_ECDHE_ECDSA_WITH_SM4_GCM_SM3
|| ssl->options.cipherSuite == TLS_ECDHE_ECDSA_WITH_SM4_GCM_SM3
#endif
#ifdef BUILD_TLS_ECDHE_ECDSA_WITH_SM4_CCM_SM3
|| ssl->options.cipherSuite == TLS_ECDHE_ECDSA_WITH_SM4_CCM_SM3
#endif
)) {
output[i++] = ecdsa_sign;
}
else
#endif
#endif /* HAVE_ECC */
{
output[i++] = rsa_sign;
}
for (t = 0; t < typeTotal; t++)
output[i++] = certTypes[t];
/* supported hash/sig */
if (IsAtLeastTLSv1_2(ssl)) {
c16toa(suites->hashSigAlgoSz, &output[i]);
c16toa(localHashSigAlgoSz, &output[i]);
i += OPAQUE16_LEN;
XMEMCPY(&output[i], suites->hashSigAlgo, suites->hashSigAlgoSz);
i += suites->hashSigAlgoSz;
XMEMCPY(&output[i], localHashSigAlgo, localHashSigAlgoSz);
i += localHashSigAlgoSz;
}
/* Certificate Authorities */
@@ -38959,9 +39087,9 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl)
ERROR_OUT(BUFFER_ERROR, exit_dcv);
}
/* Check if hashSigAlgo in CertificateVerify is supported
* in our ssl->suites or ssl->ctx->suites. */
if (!SupportedHashSigAlgo(ssl, &input[args->idx])) {
/* Check the algorithm in CertificateVerify against the
* list we actually advertised in our CertificateRequest. */
if (!InServerCertReqHashSigAlgo(ssl, &input[args->idx])) {
WOLFSSL_MSG("Signature algorithm was not in "
"CertificateRequest");
ERROR_OUT(INVALID_PARAMETER, exit_dcv);
+21 -13
View File
@@ -30950,8 +30950,9 @@ static int test_session_ticket_hs_update(void)
/**
* Make sure we don't send RSA Signature Hash Algorithms in the
* CertificateRequest when we don't have any such ciphers set.
* Make sure the CertificateRequest advertises ECDSA signature hash algorithms
* for an ECDHE-ECDSA server, and also includes RSA algorithms so that RSA
* clients can authenticate (the certificate_type advertised covers both).
* @return EXPECT_RESULT()
*/
static int test_certreq_sighash_algos(void)
@@ -31012,17 +31013,24 @@ static int test_certreq_sighash_algos(void)
idx += OPAQUE16_LEN;
maxIdx = idx + (int)len;
for (; idx < maxIdx && EXPECT_SUCCESS(); idx += OPAQUE16_LEN) {
if (test_ctx.c_buff[idx+1] == ED25519_SA_MINOR ||
test_ctx.c_buff[idx+1] == ED448_SA_MINOR ||
test_ctx.c_buff[idx+1] ==
ECDSA_BRAINPOOLP256R1TLS13_SHA256_MINOR ||
test_ctx.c_buff[idx+1] ==
ECDSA_BRAINPOOLP384R1TLS13_SHA384_MINOR ||
test_ctx.c_buff[idx+1] ==
ECDSA_BRAINPOOLP512R1TLS13_SHA512_MINOR)
ExpectIntEQ(test_ctx.c_buff[idx], NEW_SA_MAJOR);
else
ExpectIntEQ(test_ctx.c_buff[idx+1], ecc_dsa_sa_algo);
byte first = test_ctx.c_buff[idx];
byte second = test_ctx.c_buff[idx+1];
if (second == ED25519_SA_MINOR ||
second == ED448_SA_MINOR ||
second == ECDSA_BRAINPOOLP256R1TLS13_SHA256_MINOR ||
second == ECDSA_BRAINPOOLP384R1TLS13_SHA384_MINOR ||
second == ECDSA_BRAINPOOLP512R1TLS13_SHA512_MINOR) {
ExpectIntEQ(first, NEW_SA_MAJOR);
}
else {
/* ECDHE-ECDSA suites advertise ECDSA so the negotiated
* cipher can be used, and also RSA / RSA-PSS so RSA
* clients can authenticate via mutual auth. Note that
* RSA-PSS is encoded with sigAlgo first then mac. */
ExpectTrue(second == ecc_dsa_sa_algo ||
second == rsa_sa_algo ||
first == rsa_pss_sa_algo);
}
}
break;
}
+110
View File
@@ -1219,3 +1219,113 @@ int test_wolfSSL_alert_desc_string(void)
#endif
return EXPECT_RESULT();
}
/* TLS 1.2 mutual auth: an ECDHE-ECDSA server (ECDSA certificate) accepting an
* RSA client certificate. */
int test_tls12_ecdhe_ecdsa_rsa_client_cert(void)
{
EXPECT_DECLS;
#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && !defined(WOLFSSL_NO_TLS12) \
&& defined(HAVE_ECC) && !defined(NO_RSA) && !defined(NO_SHA256) \
&& defined(HAVE_AESGCM) && defined(KEEP_PEER_CERT) \
&& !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) \
&& !defined(WOLFSSL_NO_CLIENT_AUTH) \
&& !defined(NO_FILESYSTEM) && !defined(NO_CERTS)
WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL;
WOLFSSL *ssl_c = NULL, *ssl_s = NULL;
struct test_memio_ctx test_ctx;
WOLFSSL_X509* peer = NULL;
const char* cipher = "ECDHE-ECDSA-AES128-GCM-SHA256";
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0);
/* Server: ECDSA certificate (=> ECDHE-ECDSA suite), require client
* authentication, and trust the (self-signed) RSA client certificate. */
ExpectIntEQ(wolfSSL_use_certificate_file(ssl_s, eccCertFile,
WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_use_PrivateKey_file(ssl_s, eccKeyFile,
WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx_s, cliCertFile, NULL),
WOLFSSL_SUCCESS);
wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_PEER |
WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
ExpectIntEQ(wolfSSL_set_cipher_list(ssl_s, cipher), WOLFSSL_SUCCESS);
/* Client: RSA certificate/key, and trust the ECC CA that signed the
* server's ECDSA certificate. */
ExpectIntEQ(wolfSSL_use_certificate_file(ssl_c, cliCertFile,
WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_use_PrivateKey_file(ssl_c, cliKeyFile,
WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx_c, caEccCertFile, NULL),
WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_set_cipher_list(ssl_c, cipher), WOLFSSL_SUCCESS);
/* Mutual authentication completes and the server obtains the client's
* RSA certificate even though the negotiated suite is ECDHE-ECDSA. */
ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);
ExpectStrEQ(wolfSSL_get_cipher_name(ssl_c), cipher);
ExpectNotNull(peer = wolfSSL_get_peer_certificate(ssl_s));
wolfSSL_X509_free(peer);
wolfSSL_free(ssl_c);
wolfSSL_free(ssl_s);
wolfSSL_CTX_free(ctx_c);
wolfSSL_CTX_free(ctx_s);
#endif
return EXPECT_RESULT();
}
/* TLS 1.2 mutual auth: an ECDHE-RSA server (RSA certificate) accepting an
* ECDSA client certificate. */
int test_tls12_ecdhe_rsa_ecdsa_client_cert(void)
{
EXPECT_DECLS;
#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && !defined(WOLFSSL_NO_TLS12) \
&& defined(HAVE_ECC) && !defined(NO_RSA) && !defined(NO_SHA256) \
&& defined(HAVE_AESGCM) && defined(KEEP_PEER_CERT) \
&& !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) \
&& !defined(WOLFSSL_NO_CLIENT_AUTH) \
&& !defined(NO_FILESYSTEM) && !defined(NO_CERTS)
WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL;
WOLFSSL *ssl_c = NULL, *ssl_s = NULL;
struct test_memio_ctx test_ctx;
WOLFSSL_X509* peer = NULL;
const char* cipher = "ECDHE-RSA-AES128-GCM-SHA256";
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0);
/* Server: default RSA certificate (=> ECDHE-RSA), require client
* authentication, and trust the (self-signed) ECDSA client certificate. */
ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx_s, cliEccCertFile, NULL),
WOLFSSL_SUCCESS);
wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_PEER |
WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
ExpectIntEQ(wolfSSL_set_cipher_list(ssl_s, cipher), WOLFSSL_SUCCESS);
/* Client: ECDSA certificate/key. The default client CTX already trusts
* the RSA CA that signed the server's certificate. */
ExpectIntEQ(wolfSSL_use_certificate_file(ssl_c, cliEccCertFile,
WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_use_PrivateKey_file(ssl_c, cliEccKeyFile,
WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_set_cipher_list(ssl_c, cipher), WOLFSSL_SUCCESS);
/* Mutual authentication completes and the server obtains the client's
* ECDSA certificate even though the negotiated suite is ECDHE-RSA. */
ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);
ExpectStrEQ(wolfSSL_get_cipher_name(ssl_c), cipher);
ExpectNotNull(peer = wolfSSL_get_peer_certificate(ssl_s));
wolfSSL_X509_free(peer);
wolfSSL_free(ssl_c);
wolfSSL_free(ssl_s);
wolfSSL_CTX_free(ctx_c);
wolfSSL_CTX_free(ctx_s);
#endif
return EXPECT_RESULT();
}
+4
View File
@@ -36,6 +36,8 @@ int test_tls_set_session_min_downgrade(void);
int test_tls_set_curves_list_ecc_fallback(void);
int test_tls12_corrupted_finished(void);
int test_tls12_peerauth_failsafe(void);
int test_tls12_ecdhe_ecdsa_rsa_client_cert(void);
int test_tls12_ecdhe_rsa_ecdsa_client_cert(void);
int test_wolfSSL_alert_type_string(void);
int test_wolfSSL_alert_desc_string(void);
@@ -54,6 +56,8 @@ int test_wolfSSL_alert_desc_string(void);
TEST_DECL_GROUP("tls", test_tls_set_curves_list_ecc_fallback), \
TEST_DECL_GROUP("tls", test_tls12_corrupted_finished), \
TEST_DECL_GROUP("tls", test_tls12_peerauth_failsafe), \
TEST_DECL_GROUP("tls", test_tls12_ecdhe_ecdsa_rsa_client_cert), \
TEST_DECL_GROUP("tls", test_tls12_ecdhe_rsa_ecdsa_client_cert), \
TEST_DECL_GROUP("tls", test_wolfSSL_alert_type_string), \
TEST_DECL_GROUP("tls", test_wolfSSL_alert_desc_string)
-18
View File
@@ -161,24 +161,6 @@
-l ECDHE-ECDSA-AES128-GCM-SHA256
-H verifyFail
# Client is using RSA certificate with ECDSA cipher suite. Server will fail.
# server
-v 3
-l ECDHE-ECDSA-AES128-GCM-SHA256
-c ./certs/server-ecc.pem
-k ./certs/ecc-key.pem
-A ./certs/client-cert.pem
-H verifyFail
-H exitWithRet
# client
-v 3
-l ECDHE-ECDSA-AES128-GCM-SHA256
-c ./certs/client-cert.pem
-k ./certs/client-key.pem
-A ./certs/ca-ecc-cert.pem
-H exitWithRet
# server send alert on no mutual authentication
-v 3
-F
+4
View File
@@ -4525,6 +4525,10 @@ enum ClientCertificateType {
mldsa_sign = 68,
};
/* Maximum number of ClientCertificateType bytes the server emits in a
* CertificateRequest. Currently rsa_sign and ecdsa_sign. */
#define MAX_CERT_REQ_CERT_TYPE_CNT 2
#ifndef WOLFSSL_AEAD_ONLY
enum CipherType { stream, block, aead };