Skip to content

Commit fdda31b

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 95158fa commit fdda31b

6 files changed

Lines changed: 307 additions & 71 deletions

File tree

src/internal.c

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

2582925829

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

2584725996
WOLFSSL_START(WC_FUNC_CERTIFICATE_REQUEST_SEND);
2584825997
WOLFSSL_ENTER("SendCertificateRequest");
2584925998

25999+
typeTotal = GetServerCertReqCertTypes(ssl, certTypes);
26000+
if (IsAtLeastTLSv1_2(ssl))
26001+
GetServerCertReqHashSigAlgo(ssl, localHashSigAlgo, &localHashSigAlgoSz);
26002+
26003+
reqSz = ENUM_LEN + typeTotal + REQ_HEADER_SZ; /* add auth later */
26004+
2585026005
if (IsAtLeastTLSv1_2(ssl))
25851-
reqSz += LENGTH_SZ + suites->hashSigAlgoSz;
26006+
reqSz += LENGTH_SZ + localHashSigAlgoSz;
2585226007

2585326008
#ifndef WOLFSSL_NO_CA_NAMES
2585426009
/* Certificate Authorities */
@@ -25901,43 +26056,16 @@ int SendCertificateRequest(WOLFSSL* ssl)
2590126056

2590226057
/* write to output */
2590326058
output[i++] = (byte)typeTotal; /* # of types */
25904-
#ifdef HAVE_ECC
25905-
if ((ssl->options.cipherSuite0 == ECC_BYTE ||
25906-
ssl->options.cipherSuite0 == CHACHA_BYTE) &&
25907-
ssl->specs.sig_algo == ecc_dsa_sa_algo) {
25908-
output[i++] = ecdsa_sign;
25909-
}
25910-
else
25911-
#if defined(WOLFSSL_SM2) && defined(WOLFSSL_SM3) && \
25912-
(defined(WOLFSSL_SM4_CBC) || defined(WOLFSSL_SM4_GCM) || \
25913-
defined(WOLFSSL_SM4_CCM))
25914-
if (ssl->options.cipherSuite0 == SM_BYTE && (0
25915-
#ifdef BUILD_TLS_ECDHE_ECDSA_WITH_SM4_CBC_SM3
25916-
|| ssl->options.cipherSuite == TLS_ECDHE_ECDSA_WITH_SM4_CBC_SM3
25917-
#endif
25918-
#ifdef BUILD_TLS_ECDHE_ECDSA_WITH_SM4_GCM_SM3
25919-
|| ssl->options.cipherSuite == TLS_ECDHE_ECDSA_WITH_SM4_GCM_SM3
25920-
#endif
25921-
#ifdef BUILD_TLS_ECDHE_ECDSA_WITH_SM4_CCM_SM3
25922-
|| ssl->options.cipherSuite == TLS_ECDHE_ECDSA_WITH_SM4_CCM_SM3
25923-
#endif
25924-
)) {
25925-
output[i++] = ecdsa_sign;
25926-
}
25927-
else
25928-
#endif
25929-
#endif /* HAVE_ECC */
25930-
{
25931-
output[i++] = rsa_sign;
25932-
}
26059+
for (t = 0; t < typeTotal; t++)
26060+
output[i++] = certTypes[t];
2593326061

2593426062
/* supported hash/sig */
2593526063
if (IsAtLeastTLSv1_2(ssl)) {
25936-
c16toa(suites->hashSigAlgoSz, &output[i]);
26064+
c16toa(localHashSigAlgoSz, &output[i]);
2593726065
i += OPAQUE16_LEN;
2593826066

25939-
XMEMCPY(&output[i], suites->hashSigAlgo, suites->hashSigAlgoSz);
25940-
i += suites->hashSigAlgoSz;
26067+
XMEMCPY(&output[i], localHashSigAlgo, localHashSigAlgoSz);
26068+
i += localHashSigAlgoSz;
2594126069
}
2594226070

2594326071
/* Certificate Authorities */
@@ -38959,9 +39087,9 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl)
3895939087
ERROR_OUT(BUFFER_ERROR, exit_dcv);
3896039088
}
3896139089

38962-
/* Check if hashSigAlgo in CertificateVerify is supported
38963-
* in our ssl->suites or ssl->ctx->suites. */
38964-
if (!SupportedHashSigAlgo(ssl, &input[args->idx])) {
39090+
/* Check the algorithm in CertificateVerify against the
39091+
* list we actually advertised in our CertificateRequest. */
39092+
if (!InServerCertReqHashSigAlgo(ssl, &input[args->idx])) {
3896539093
WOLFSSL_MSG("Signature algorithm was not in "
3896639094
"CertificateRequest");
3896739095
ERROR_OUT(INVALID_PARAMETER, exit_dcv);

tests/api.c

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30950,8 +30950,9 @@ static int test_session_ticket_hs_update(void)
3095030950

3095130951

3095230952
/**
30953-
* Make sure we don't send RSA Signature Hash Algorithms in the
30954-
* CertificateRequest when we don't have any such ciphers set.
30953+
* Make sure the CertificateRequest advertises ECDSA signature hash algorithms
30954+
* for an ECDHE-ECDSA server, and also includes RSA algorithms so that RSA
30955+
* clients can authenticate (the certificate_type advertised covers both).
3095530956
* @return EXPECT_RESULT()
3095630957
*/
3095730958
static int test_certreq_sighash_algos(void)
@@ -31012,17 +31013,24 @@ static int test_certreq_sighash_algos(void)
3101231013
idx += OPAQUE16_LEN;
3101331014
maxIdx = idx + (int)len;
3101431015
for (; idx < maxIdx && EXPECT_SUCCESS(); idx += OPAQUE16_LEN) {
31015-
if (test_ctx.c_buff[idx+1] == ED25519_SA_MINOR ||
31016-
test_ctx.c_buff[idx+1] == ED448_SA_MINOR ||
31017-
test_ctx.c_buff[idx+1] ==
31018-
ECDSA_BRAINPOOLP256R1TLS13_SHA256_MINOR ||
31019-
test_ctx.c_buff[idx+1] ==
31020-
ECDSA_BRAINPOOLP384R1TLS13_SHA384_MINOR ||
31021-
test_ctx.c_buff[idx+1] ==
31022-
ECDSA_BRAINPOOLP512R1TLS13_SHA512_MINOR)
31023-
ExpectIntEQ(test_ctx.c_buff[idx], NEW_SA_MAJOR);
31024-
else
31025-
ExpectIntEQ(test_ctx.c_buff[idx+1], ecc_dsa_sa_algo);
31016+
byte first = test_ctx.c_buff[idx];
31017+
byte second = test_ctx.c_buff[idx+1];
31018+
if (second == ED25519_SA_MINOR ||
31019+
second == ED448_SA_MINOR ||
31020+
second == ECDSA_BRAINPOOLP256R1TLS13_SHA256_MINOR ||
31021+
second == ECDSA_BRAINPOOLP384R1TLS13_SHA384_MINOR ||
31022+
second == ECDSA_BRAINPOOLP512R1TLS13_SHA512_MINOR) {
31023+
ExpectIntEQ(first, NEW_SA_MAJOR);
31024+
}
31025+
else {
31026+
/* ECDHE-ECDSA suites advertise ECDSA so the negotiated
31027+
* cipher can be used, and also RSA / RSA-PSS so RSA
31028+
* clients can authenticate via mutual auth. Note that
31029+
* RSA-PSS is encoded with sigAlgo first then mac. */
31030+
ExpectTrue(second == ecc_dsa_sa_algo ||
31031+
second == rsa_sa_algo ||
31032+
first == rsa_pss_sa_algo);
31033+
}
3102631034
}
3102731035
break;
3102831036
}

0 commit comments

Comments
 (0)