diff --git a/src/internal.c b/src/internal.c index 75c007c657..854c0c9894 100644 --- a/src/internal.c +++ b/src/internal.c @@ -25818,6 +25818,145 @@ 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). */ +static int GetServerCertReqCertTypes(const WOLFSSL* ssl, byte* certTypes) +{ + int n = 0; +#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. */ +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 + 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. */ +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. */ +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) { @@ -25829,16 +25968,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)) + GetServerCertReqHashSigAlgo(ssl, localHashSigAlgo, &localHashSigAlgoSz); + + reqSz = ENUM_LEN + typeTotal + REQ_HEADER_SZ; /* add auth later */ + if (IsAtLeastTLSv1_2(ssl)) - reqSz += LENGTH_SZ + suites->hashSigAlgoSz; + reqSz += LENGTH_SZ + localHashSigAlgoSz; #ifndef WOLFSSL_NO_CA_NAMES /* Certificate Authorities */ @@ -25891,43 +26038,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 */ @@ -38949,9 +39069,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); diff --git a/tests/api.c b/tests/api.c index fe92bdee85..02282e1884 100644 --- a/tests/api.c +++ b/tests/api.c @@ -35648,8 +35648,9 @@ static int test_dtls_seq_num_downgrade(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) @@ -35710,17 +35711,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; } diff --git a/tests/api/test_tls.c b/tests/api/test_tls.c index ee2e112bfa..5fb8d814da 100644 --- a/tests/api/test_tls.c +++ b/tests/api/test_tls.c @@ -1219,3 +1219,111 @@ 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(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(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(); +} diff --git a/tests/api/test_tls.h b/tests/api/test_tls.h index fb796244d7..48f200ce8b 100644 --- a/tests/api/test_tls.h +++ b/tests/api/test_tls.h @@ -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) diff --git a/tests/test-fails.conf b/tests/test-fails.conf index 74530e3e0e..e8f2772276 100644 --- a/tests/test-fails.conf +++ b/tests/test-fails.conf @@ -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 diff --git a/wolfssl/internal.h b/wolfssl/internal.h index fc918fbc77..59d4686572 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -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 };