Merge pull request #10553 from julek-wolfssl/tls-12-mutual-auth

Allow RSA client certs on ECDHE-ECDSA mutual auth
This commit is contained in:
Daniel Pouzzner
2026-06-08 15:21:29 -05:00
committed by GitHub
6 changed files with 307 additions and 71 deletions
+168 -40
View File
@@ -25888,6 +25888,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)
{
@@ -25899,16 +26046,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 */
@@ -25961,43 +26116,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 */
@@ -39067,9 +39195,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
@@ -31600,8 +31600,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)
@@ -31662,17 +31663,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
@@ -1518,6 +1518,116 @@ int test_tls12_peerauth_failsafe(void)
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();
}
int test_wolfSSL_alert_desc_string(void)
{
EXPECT_DECLS;
+4
View File
@@ -40,6 +40,8 @@ int test_tls13_resumption_with_alpn(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);
int test_record_size_matches_build_message(void);
@@ -64,6 +66,8 @@ int test_record_size_cache_invalidated_on_renegotiation(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), \
TEST_DECL_GROUP("tls", test_tls12_peerauth_failsafe), \
-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
@@ -4530,6 +4530,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 };