diff --git a/src/internal.c b/src/internal.c index ba1ff99467..7cfb9cfb1a 100644 --- a/src/internal.c +++ b/src/internal.c @@ -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 */ diff --git a/tests/api/test_tls13.c b/tests/api/test_tls13.c index b3ec1ff0d9..d820c55de5 100644 --- a/tests/api/test_tls13.c +++ b/tests/api/test_tls13.c @@ -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) && \ diff --git a/tests/api/test_tls13.h b/tests/api/test_tls13.h index 984b89372d..6e392f46d5 100644 --- a/tests/api/test_tls13.h +++ b/tests/api/test_tls13.h @@ -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), \