Skip to content

Commit 1568dab

Browse files
committed
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.
1 parent 7467ce2 commit 1568dab

6 files changed

Lines changed: 305 additions & 71 deletions

File tree

src/internal.c

Lines changed: 168 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -25818,6 +25818,153 @@ int SendCertificate(WOLFSSL* ssl)
2581825818

2581925819

2582025820
#if !defined(NO_TLS)
25821+
/* Returns the certificate_types this server advertises in its
25822+
* CertificateRequest. The list is broader than the negotiated cipher suite's
25823+
* own signature algorithm so a client may authenticate with a certificate of
25824+
* a different type (e.g. an RSA client on an ECDHE-ECDSA suite). */
25825+
WC_MAYBE_UNUSED static int GetServerCertReqCertTypes(const WOLFSSL* ssl,
25826+
byte* certTypes)
25827+
{
25828+
int n = 0;
25829+
(void)ssl;
25830+
(void)certTypes;
25831+
#ifdef HAVE_ECC
25832+
if ((ssl->options.cipherSuite0 == ECC_BYTE ||
25833+
ssl->options.cipherSuite0 == CHACHA_BYTE) &&
25834+
ssl->specs.sig_algo == ecc_dsa_sa_algo) {
25835+
certTypes[n++] = ecdsa_sign;
25836+
#ifndef NO_RSA
25837+
certTypes[n++] = rsa_sign;
25838+
#endif
25839+
}
25840+
else
25841+
#if defined(WOLFSSL_SM2) && defined(WOLFSSL_SM3) && \
25842+
(defined(WOLFSSL_SM4_CBC) || defined(WOLFSSL_SM4_GCM) || \
25843+
defined(WOLFSSL_SM4_CCM))
25844+
if (ssl->options.cipherSuite0 == SM_BYTE && (0
25845+
#ifdef BUILD_TLS_ECDHE_ECDSA_WITH_SM4_CBC_SM3
25846+
|| ssl->options.cipherSuite == TLS_ECDHE_ECDSA_WITH_SM4_CBC_SM3
25847+
#endif
25848+
#ifdef BUILD_TLS_ECDHE_ECDSA_WITH_SM4_GCM_SM3
25849+
|| ssl->options.cipherSuite == TLS_ECDHE_ECDSA_WITH_SM4_GCM_SM3
25850+
#endif
25851+
#ifdef BUILD_TLS_ECDHE_ECDSA_WITH_SM4_CCM_SM3
25852+
|| ssl->options.cipherSuite == TLS_ECDHE_ECDSA_WITH_SM4_CCM_SM3
25853+
#endif
25854+
)) {
25855+
certTypes[n++] = ecdsa_sign;
25856+
}
25857+
else
25858+
#endif
25859+
#endif /* HAVE_ECC */
25860+
{
25861+
#ifndef NO_RSA
25862+
certTypes[n++] = rsa_sign;
25863+
#endif
25864+
#ifdef HAVE_ECC
25865+
certTypes[n++] = ecdsa_sign;
25866+
#endif
25867+
}
25868+
return n;
25869+
}
25870+
25871+
/* Returns the set of sig families covered by the given hash/sig algorithm
25872+
* list, as a bitmask of SIG_* values. Uses DecodeSigAlg so the NEW_SA_MAJOR
25873+
* encoding (ED25519/ED448/RSA-PSS-PSS/brainpool) is classified correctly. */
25874+
WC_MAYBE_UNUSED static int HashSigAlgoCoverage(const byte* hashSigAlgo,
25875+
word16 hashSigAlgoSz)
25876+
{
25877+
int coverage = 0;
25878+
word16 j;
25879+
byte hashAlgo;
25880+
byte sigAlgo;
25881+
for (j = 0; (j + 1) < hashSigAlgoSz; j += HELLO_EXT_SIGALGO_SZ) {
25882+
DecodeSigAlg(&hashSigAlgo[j], &hashAlgo, &sigAlgo);
25883+
(void)hashAlgo;
25884+
switch (sigAlgo) {
25885+
case rsa_sa_algo:
25886+
#ifdef WC_RSA_PSS
25887+
case rsa_pss_sa_algo:
25888+
case rsa_pss_pss_algo:
25889+
#endif
25890+
coverage |= SIG_RSA;
25891+
break;
25892+
#ifdef HAVE_ECC
25893+
case ecc_dsa_sa_algo:
25894+
#ifdef HAVE_ECC_BRAINPOOL
25895+
case ecc_brainpool_sa_algo:
25896+
#endif
25897+
#if defined(WOLFSSL_SM2) && defined(WOLFSSL_SM3)
25898+
case sm2_sa_algo:
25899+
#endif
25900+
coverage |= SIG_ECDSA;
25901+
break;
25902+
#endif
25903+
default:
25904+
break;
25905+
}
25906+
}
25907+
return coverage;
25908+
}
25909+
25910+
/* Builds the signature_algorithms this server advertises in its
25911+
* CertificateRequest. Respects a user-configured suites->hashSigAlgo (e.g.
25912+
* via wolfSSL_set1_sigalgs_list) and only broadens the list when one of the
25913+
* advertised certificate_types has no matching signature algorithm in the
25914+
* configured list. The result is written to the caller's buffer; no SSL
25915+
* state is modified. */
25916+
WC_MAYBE_UNUSED static void GetServerCertReqHashSigAlgo(const WOLFSSL* ssl,
25917+
byte* hashSigAlgo, word16* hashSigAlgoSz)
25918+
{
25919+
const Suites* suites = WOLFSSL_SUITES(ssl);
25920+
byte certTypes[MAX_CERT_REQ_CERT_TYPE_CNT];
25921+
int typeTotal;
25922+
int need = 0;
25923+
int have;
25924+
int j;
25925+
word16 localSz = 0;
25926+
25927+
typeTotal = GetServerCertReqCertTypes(ssl, certTypes);
25928+
for (j = 0; j < typeTotal; j++) {
25929+
if (certTypes[j] == rsa_sign)
25930+
need |= SIG_RSA;
25931+
else if (certTypes[j] == ecdsa_sign)
25932+
need |= SIG_ECDSA;
25933+
}
25934+
have = HashSigAlgoCoverage(suites->hashSigAlgo, suites->hashSigAlgoSz);
25935+
25936+
if ((need & ~have) != 0) {
25937+
/* The configured list is missing signature algorithms for at least
25938+
* one of the advertised certificate_types. Build a broader list
25939+
* locally that covers every advertised type. */
25940+
InitSuitesHashSigAlgo(hashSigAlgo, need | have, 1, 0,
25941+
ssl->buffers.keySz, &localSz);
25942+
*hashSigAlgoSz = localSz;
25943+
return;
25944+
}
25945+
25946+
XMEMCPY(hashSigAlgo, suites->hashSigAlgo, suites->hashSigAlgoSz);
25947+
*hashSigAlgoSz = suites->hashSigAlgoSz;
25948+
}
25949+
25950+
/* Returns 1 if algo (2 bytes) is in the server's CertificateRequest
25951+
* signature_algorithms list, 0 otherwise. Used to validate the client's
25952+
* CertificateVerify against what we actually advertised. */
25953+
WC_MAYBE_UNUSED static int InServerCertReqHashSigAlgo(const WOLFSSL* ssl,
25954+
const byte* algo)
25955+
{
25956+
byte list[WOLFSSL_MAX_SIGALGO];
25957+
word16 listSz = 0;
25958+
word16 j;
25959+
25960+
GetServerCertReqHashSigAlgo(ssl, list, &listSz);
25961+
for (j = 0; (j + 1) < listSz; j += HELLO_EXT_SIGALGO_SZ) {
25962+
if (XMEMCMP(&list[j], algo, HELLO_EXT_SIGALGO_SZ) == 0)
25963+
return 1;
25964+
}
25965+
return 0;
25966+
}
25967+
2582125968
/* handle generation of certificate_request (13) */
2582225969
int SendCertificateRequest(WOLFSSL* ssl)
2582325970
{
@@ -25829,16 +25976,24 @@ int SendCertificateRequest(WOLFSSL* ssl)
2582925976
#ifndef WOLFSSL_NO_CA_NAMES
2583025977
WOLF_STACK_OF(WOLFSSL_X509_NAME)* names;
2583125978
#endif
25832-
const Suites* suites = WOLFSSL_SUITES(ssl);
25833-
25834-
int typeTotal = 1; /* only 1 for now */
25835-
int reqSz = ENUM_LEN + typeTotal + REQ_HEADER_SZ; /* add auth later */
25979+
byte certTypes[MAX_CERT_REQ_CERT_TYPE_CNT];
25980+
int typeTotal;
25981+
int t;
25982+
byte localHashSigAlgo[WOLFSSL_MAX_SIGALGO];
25983+
word16 localHashSigAlgoSz = 0;
25984+
int reqSz;
2583625985

2583725986
WOLFSSL_START(WC_FUNC_CERTIFICATE_REQUEST_SEND);
2583825987
WOLFSSL_ENTER("SendCertificateRequest");
2583925988

25989+
typeTotal = GetServerCertReqCertTypes(ssl, certTypes);
25990+
if (IsAtLeastTLSv1_2(ssl))
25991+
GetServerCertReqHashSigAlgo(ssl, localHashSigAlgo, &localHashSigAlgoSz);
25992+
25993+
reqSz = ENUM_LEN + typeTotal + REQ_HEADER_SZ; /* add auth later */
25994+
2584025995
if (IsAtLeastTLSv1_2(ssl))
25841-
reqSz += LENGTH_SZ + suites->hashSigAlgoSz;
25996+
reqSz += LENGTH_SZ + localHashSigAlgoSz;
2584225997

2584325998
#ifndef WOLFSSL_NO_CA_NAMES
2584425999
/* Certificate Authorities */
@@ -25891,43 +26046,16 @@ int SendCertificateRequest(WOLFSSL* ssl)
2589126046

2589226047
/* write to output */
2589326048
output[i++] = (byte)typeTotal; /* # of types */
25894-
#ifdef HAVE_ECC
25895-
if ((ssl->options.cipherSuite0 == ECC_BYTE ||
25896-
ssl->options.cipherSuite0 == CHACHA_BYTE) &&
25897-
ssl->specs.sig_algo == ecc_dsa_sa_algo) {
25898-
output[i++] = ecdsa_sign;
25899-
}
25900-
else
25901-
#if defined(WOLFSSL_SM2) && defined(WOLFSSL_SM3) && \
25902-
(defined(WOLFSSL_SM4_CBC) || defined(WOLFSSL_SM4_GCM) || \
25903-
defined(WOLFSSL_SM4_CCM))
25904-
if (ssl->options.cipherSuite0 == SM_BYTE && (0
25905-
#ifdef BUILD_TLS_ECDHE_ECDSA_WITH_SM4_CBC_SM3
25906-
|| ssl->options.cipherSuite == TLS_ECDHE_ECDSA_WITH_SM4_CBC_SM3
25907-
#endif
25908-
#ifdef BUILD_TLS_ECDHE_ECDSA_WITH_SM4_GCM_SM3
25909-
|| ssl->options.cipherSuite == TLS_ECDHE_ECDSA_WITH_SM4_GCM_SM3
25910-
#endif
25911-
#ifdef BUILD_TLS_ECDHE_ECDSA_WITH_SM4_CCM_SM3
25912-
|| ssl->options.cipherSuite == TLS_ECDHE_ECDSA_WITH_SM4_CCM_SM3
25913-
#endif
25914-
)) {
25915-
output[i++] = ecdsa_sign;
25916-
}
25917-
else
25918-
#endif
25919-
#endif /* HAVE_ECC */
25920-
{
25921-
output[i++] = rsa_sign;
25922-
}
26049+
for (t = 0; t < typeTotal; t++)
26050+
output[i++] = certTypes[t];
2592326051

2592426052
/* supported hash/sig */
2592526053
if (IsAtLeastTLSv1_2(ssl)) {
25926-
c16toa(suites->hashSigAlgoSz, &output[i]);
26054+
c16toa(localHashSigAlgoSz, &output[i]);
2592726055
i += OPAQUE16_LEN;
2592826056

25929-
XMEMCPY(&output[i], suites->hashSigAlgo, suites->hashSigAlgoSz);
25930-
i += suites->hashSigAlgoSz;
26057+
XMEMCPY(&output[i], localHashSigAlgo, localHashSigAlgoSz);
26058+
i += localHashSigAlgoSz;
2593126059
}
2593226060

2593326061
/* Certificate Authorities */
@@ -38949,9 +39077,9 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl)
3894939077
ERROR_OUT(BUFFER_ERROR, exit_dcv);
3895039078
}
3895139079

38952-
/* Check if hashSigAlgo in CertificateVerify is supported
38953-
* in our ssl->suites or ssl->ctx->suites. */
38954-
if (!SupportedHashSigAlgo(ssl, &input[args->idx])) {
39080+
/* Check the algorithm in CertificateVerify against the
39081+
* list we actually advertised in our CertificateRequest. */
39082+
if (!InServerCertReqHashSigAlgo(ssl, &input[args->idx])) {
3895539083
WOLFSSL_MSG("Signature algorithm was not in "
3895639084
"CertificateRequest");
3895739085
ERROR_OUT(INVALID_PARAMETER, exit_dcv);

tests/api.c

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -35648,8 +35648,9 @@ static int test_dtls_seq_num_downgrade(void)
3564835648
}
3564935649

3565035650
/**
35651-
* Make sure we don't send RSA Signature Hash Algorithms in the
35652-
* CertificateRequest when we don't have any such ciphers set.
35651+
* Make sure the CertificateRequest advertises ECDSA signature hash algorithms
35652+
* for an ECDHE-ECDSA server, and also includes RSA algorithms so that RSA
35653+
* clients can authenticate (the certificate_type advertised covers both).
3565335654
* @return EXPECT_RESULT()
3565435655
*/
3565535656
static int test_certreq_sighash_algos(void)
@@ -35710,17 +35711,24 @@ static int test_certreq_sighash_algos(void)
3571035711
idx += OPAQUE16_LEN;
3571135712
maxIdx = idx + (int)len;
3571235713
for (; idx < maxIdx && EXPECT_SUCCESS(); idx += OPAQUE16_LEN) {
35713-
if (test_ctx.c_buff[idx+1] == ED25519_SA_MINOR ||
35714-
test_ctx.c_buff[idx+1] == ED448_SA_MINOR ||
35715-
test_ctx.c_buff[idx+1] ==
35716-
ECDSA_BRAINPOOLP256R1TLS13_SHA256_MINOR ||
35717-
test_ctx.c_buff[idx+1] ==
35718-
ECDSA_BRAINPOOLP384R1TLS13_SHA384_MINOR ||
35719-
test_ctx.c_buff[idx+1] ==
35720-
ECDSA_BRAINPOOLP512R1TLS13_SHA512_MINOR)
35721-
ExpectIntEQ(test_ctx.c_buff[idx], NEW_SA_MAJOR);
35722-
else
35723-
ExpectIntEQ(test_ctx.c_buff[idx+1], ecc_dsa_sa_algo);
35714+
byte first = test_ctx.c_buff[idx];
35715+
byte second = test_ctx.c_buff[idx+1];
35716+
if (second == ED25519_SA_MINOR ||
35717+
second == ED448_SA_MINOR ||
35718+
second == ECDSA_BRAINPOOLP256R1TLS13_SHA256_MINOR ||
35719+
second == ECDSA_BRAINPOOLP384R1TLS13_SHA384_MINOR ||
35720+
second == ECDSA_BRAINPOOLP512R1TLS13_SHA512_MINOR) {
35721+
ExpectIntEQ(first, NEW_SA_MAJOR);
35722+
}
35723+
else {
35724+
/* ECDHE-ECDSA suites advertise ECDSA so the negotiated
35725+
* cipher can be used, and also RSA / RSA-PSS so RSA
35726+
* clients can authenticate via mutual auth. Note that
35727+
* RSA-PSS is encoded with sigAlgo first then mac. */
35728+
ExpectTrue(second == ecc_dsa_sa_algo ||
35729+
second == rsa_sa_algo ||
35730+
first == rsa_pss_sa_algo);
35731+
}
3572435732
}
3572535733
break;
3572635734
}

0 commit comments

Comments
 (0)