TLS: validate negotiated certificate type for raw public keys

Ensure a peer's certificate form (X.509 vs raw public key) matches the
negotiated certificate type, defaulting to X.509 when none was negotiated,
on both the client and server. Adds RPK regression tests covering both
directions.
This commit is contained in:
Tobias Frauenschläger
2026-06-16 15:30:49 +02:00
parent 3e30e69c35
commit c929798460
3 changed files with 90 additions and 21 deletions
+19 -21
View File
@@ -15690,31 +15690,29 @@ PRAGMA_GCC_DIAG_POP
ret = ParseCertRelative(args->dCert, certType, verify, SSL_CM(ssl), extraSigners);
#if defined(HAVE_RPK)
/* if cert type has negotiated with peer, confirm the cert received has
* the same type.
*/
if (ret == 0 ) {
if (ssl->options.side == WOLFSSL_CLIENT_END) {
if (ssl->options.rpkState.received_ServerCertTypeCnt == 1) {
/* Confirm the received certificate's form (X.509 vs raw public key) matches
* the type negotiated with the peer. A raw public key (RFC 7250) has no
* chain, so ParseCertRelative() accepts it without any trust verification;
* it must only be accepted when RPK was negotiated for this peer. When no
* type was negotiated the default is X.509 (RFC 7250/8446), so an
* un-negotiated bare key is rejected. The negotiated type is the received
* server cert type (client) or the selected client cert type (server). */
if (ret == 0) {
cType = WOLFSSL_CERT_TYPE_X509;
if (ssl->options.side == WOLFSSL_CLIENT_END) {
if (ssl->options.rpkState.received_ServerCertTypeCnt == 1)
cType = ssl->options.rpkState.received_ServerCertTypes[0];
if ((cType == WOLFSSL_CERT_TYPE_RPK && !args->dCert->isRPK) ||
(cType == WOLFSSL_CERT_TYPE_X509 && args->dCert->isRPK)) {
/* cert type mismatch */
WOLFSSL_MSG("unsupported certificate type received");
ret = UNSUPPORTED_CERTIFICATE;
}
}
}
else if (ssl->options.side == WOLFSSL_SERVER_END) {
if (ssl->options.rpkState.received_ClientCertTypeCnt == 1) {
if (ssl->options.rpkState.sending_ClientCertTypeCnt == 1)
cType = ssl->options.rpkState.sending_ClientCertTypes[0];
if ((cType == WOLFSSL_CERT_TYPE_RPK && !args->dCert->isRPK) ||
(cType == WOLFSSL_CERT_TYPE_X509 && args->dCert->isRPK)) {
/* cert type mismatch */
WOLFSSL_MSG("unsupported certificate type received");
ret = UNSUPPORTED_CERTIFICATE;
}
}
}
if ((cType == WOLFSSL_CERT_TYPE_RPK && !args->dCert->isRPK) ||
(cType != WOLFSSL_CERT_TYPE_RPK && args->dCert->isRPK)) {
/* cert type mismatch - includes an un-negotiated raw public key */
WOLFSSL_MSG("unsupported certificate type received");
ret = UNSUPPORTED_CERTIFICATE;
}
}
#endif /* HAVE_RPK */
+69
View File
@@ -2751,6 +2751,75 @@ int test_tls13_rpk_handshake(void)
return EXPECT_RESULT();
}
/* Regression: a peer must not present a raw public key (RPK) that was never
* negotiated. Neither side calls set_client/server_cert_type, so RPK is not
* negotiated and the default type is X.509; a received bare key must be
* rejected instead of accepted without any chain verification (auth bypass).
* Covers both directions: server presenting an RPK to the client, and client
* presenting an RPK to the server. */
int test_tls13_rpk_handshake_no_negotiation(void)
{
EXPECT_DECLS;
#if defined(HAVE_RPK) && defined(WOLFSSL_TLS13) && \
!defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER)
WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL;
WOLFSSL *ssl_c = NULL, *ssl_s = NULL;
struct test_memio_ctx test_ctx;
int tp = 0;
/* Direction 1: server loads an RPK cert, client is a plain X.509 client. */
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
ExpectIntEQ(
test_rpk_memio_setup(
&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
wolfTLSv1_3_client_method, wolfTLSv1_3_server_method,
cliCertFile, CERT_FILETYPE,
svrRpkCertFile, WOLFSSL_FILETYPE_ASN1,
cliKeyFile, CERT_FILETYPE,
svrKeyFile, CERT_FILETYPE)
, 0);
/* Handshake must fail and the client must not record RPK as negotiated. */
ExpectIntNE(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);
ExpectIntEQ(wolfSSL_get_negotiated_server_cert_type(ssl_c, &tp),
WOLFSSL_SUCCESS);
ExpectIntNE(tp, WOLFSSL_CERT_TYPE_RPK);
wolfSSL_free(ssl_c);
wolfSSL_CTX_free(ctx_c);
wolfSSL_free(ssl_s);
wolfSSL_CTX_free(ctx_s);
ssl_c = ssl_s = NULL;
ctx_c = ctx_s = NULL;
/* Direction 2: client loads an RPK cert and the server requests client
* auth (VERIFY_PEER, set in the helper). The server expects X.509 and must
* reject the client's un-negotiated bare key. */
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
ExpectIntEQ(
test_rpk_memio_setup(
&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
wolfTLSv1_3_client_method, wolfTLSv1_3_server_method,
clntRpkCertFile, WOLFSSL_FILETYPE_ASN1,
svrCertFile, CERT_FILETYPE,
cliKeyFile, CERT_FILETYPE,
svrKeyFile, CERT_FILETYPE)
, 0);
/* Handshake must fail and the server must not record RPK as negotiated. */
ExpectIntNE(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);
ExpectIntEQ(wolfSSL_get_negotiated_client_cert_type(ssl_s, &tp),
WOLFSSL_SUCCESS);
ExpectIntNE(tp, WOLFSSL_CERT_TYPE_RPK);
wolfSSL_free(ssl_c);
wolfSSL_CTX_free(ctx_c);
wolfSSL_free(ssl_s);
wolfSSL_CTX_free(ctx_s);
#endif
return EXPECT_RESULT();
}
#if defined(HAVE_IO_TESTS_DEPENDENCIES) && defined(WOLFSSL_TLS13) && \
defined(WOLFSSL_HAVE_MLKEM) && !defined(WOLFSSL_MLKEM_NO_ENCAPSULATE) && \
+2
View File
@@ -28,6 +28,7 @@ int test_tls13_apis(void);
int test_tls13_cipher_suites(void);
int test_tls13_bad_psk_binder(void);
int test_tls13_rpk_handshake(void);
int test_tls13_rpk_handshake_no_negotiation(void);
int test_tls13_pq_groups(void);
int test_tls13_multi_pqc_key_share(void);
int test_tls13_early_data(void);
@@ -88,6 +89,7 @@ int test_tls13_AEAD_limit_KU_aes128_ccm_8_sha256(void);
TEST_DECL_GROUP("tls13", test_tls13_cipher_suites), \
TEST_DECL_GROUP("tls13", test_tls13_bad_psk_binder), \
TEST_DECL_GROUP("tls13", test_tls13_rpk_handshake), \
TEST_DECL_GROUP("tls13", test_tls13_rpk_handshake_no_negotiation), \
TEST_DECL_GROUP("tls13", test_tls13_pq_groups), \
TEST_DECL_GROUP("tls13", test_tls13_multi_pqc_key_share), \
TEST_DECL_GROUP("tls13", test_tls13_early_data), \