diff --git a/src/internal.c b/src/internal.c index 3de4b1d060..043e8492ac 100644 --- a/src/internal.c +++ b/src/internal.c @@ -12053,17 +12053,27 @@ static int wolfSSLReceive(WOLFSSL* ssl, byte* buf, word32 sz) } -/* Switch dynamic output buffer back to static, buffer is assumed clear */ +/* Switch dynamic output buffer back to static, discarding any pending output. + * Safe to call whether or not the buffer is dynamic; callers need not check + * dynamicFlag first. */ void ShrinkOutputBuffer(WOLFSSL* ssl) { WOLFSSL_MSG("Shrinking output buffer"); - XFREE(ssl->buffers.outputBuffer.buffer - ssl->buffers.outputBuffer.offset, - ssl->heap, DYNAMIC_TYPE_OUT_BUFFER); + if (ssl->buffers.outputBuffer.dynamicFlag) { + /* Discarded records may hold plaintext, so wipe before releasing, + * as ShrinkInputBuffer does. */ + ForceZero(ssl->buffers.outputBuffer.buffer, + ssl->buffers.outputBuffer.bufferSize); + XFREE(ssl->buffers.outputBuffer.buffer - + ssl->buffers.outputBuffer.offset, + ssl->heap, DYNAMIC_TYPE_OUT_BUFFER); + } ssl->buffers.outputBuffer.buffer = ssl->buffers.outputBuffer.staticBuffer; ssl->buffers.outputBuffer.bufferSize = STATIC_BUFFER_LEN; ssl->buffers.outputBuffer.dynamicFlag = 0; ssl->buffers.outputBuffer.offset = 0; - /* idx and length are assumed to be 0. */ + ssl->buffers.outputBuffer.idx = 0; + ssl->buffers.outputBuffer.length = 0; } diff --git a/src/ssl.c b/src/ssl.c index d7cb7bb4c9..1cff1b3130 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -5705,6 +5705,15 @@ size_t wolfSSL_get_client_random(const WOLFSSL* ssl, unsigned char* out, ssl->earlyData = no_early_data; ssl->earlyDataSz = 0; #endif + #ifdef HAVE_RPK + /* Drop the negotiated cert types so one peer's choice cannot carry into + * the next handshake. Clearing the counts is enough: every read of the + * type arrays is gated on its count. */ + ssl->options.rpkState.sending_ClientCertTypeCnt = 0; + ssl->options.rpkState.sending_ServerCertTypeCnt = 0; + ssl->options.rpkState.received_ClientCertTypeCnt = 0; + ssl->options.rpkState.received_ServerCertTypeCnt = 0; + #endif #if defined(HAVE_TLS_EXTENSIONS) && !defined(NO_TLS) TLSX_FreeAll(ssl->extensions, ssl->heap); diff --git a/src/tls.c b/src/tls.c index 1fe31d43af..0a8ab986aa 100644 --- a/src/tls.c +++ b/src/tls.c @@ -13540,6 +13540,22 @@ static int TLSX_ClientCertificateType_Parse(WOLFSSL* ssl, const byte* input, else if (msgType == server_hello || msgType == encrypted_extensions) { /* parse it in client side */ if (length == 1) { + /* Same offered-vs-received binding as server_cert_type: an + * unsolicited value lets the peer pick the form this client + * presents its own credential in. */ + if (ssl->options.rpkState.sending_ClientCertTypeCnt == 0) { + WOLFSSL_MSG("client_cert_type received but never offered"); + WOLFSSL_ERROR_VERBOSE(UNSUPPORTED_EXTENSION); + return UNSUPPORTED_EXTENSION; + } + if (!IsCertTypeListed(*input, + ssl->options.rpkState.sending_ClientCertTypeCnt, + ssl->options.rpkState.sending_ClientCertTypes)) { + WOLFSSL_MSG("client_cert_type value was not offered"); + WOLFSSL_ERROR_VERBOSE(UNSUPPORTED_EXTENSION); + return UNSUPPORTED_EXTENSION; + } + ssl->options.rpkState.received_ClientCertTypeCnt = 1; ssl->options.rpkState.received_ClientCertTypes[0] = *input; } @@ -13740,6 +13756,23 @@ static int TLSX_ServerCertificateType_Parse(WOLFSSL* ssl, const byte* input, if (length != 1) /* length slould be 1 */ return BUFFER_E; + /* RFC 7250 4.1, RFC 8446 4.2: the server may only answer with a type + * the client offered. ProcessPeerCertParse() treats the stored value as + * negotiated, so an unsolicited one lets the peer select RawPublicKey + * and skip chain verification. */ + if (ssl->options.rpkState.sending_ServerCertTypeCnt == 0) { + WOLFSSL_MSG("server_cert_type received but never offered"); + WOLFSSL_ERROR_VERBOSE(UNSUPPORTED_EXTENSION); + return UNSUPPORTED_EXTENSION; + } + if (!IsCertTypeListed(*input, + ssl->options.rpkState.sending_ServerCertTypeCnt, + ssl->options.rpkState.sending_ServerCertTypes)) { + WOLFSSL_MSG("server_cert_type value was not offered"); + WOLFSSL_ERROR_VERBOSE(UNSUPPORTED_EXTENSION); + return UNSUPPORTED_EXTENSION; + } + ssl->options.rpkState.received_ServerCertTypeCnt = 1; ssl->options.rpkState.received_ServerCertTypes[0] = *input; } diff --git a/src/x509_str.c b/src/x509_str.c index 7bd2eb22f1..9df1673dad 100644 --- a/src/x509_str.c +++ b/src/x509_str.c @@ -858,7 +858,6 @@ int wolfSSL_X509_verify_cert(WOLFSSL_X509_STORE_CTX* ctx) { int ret = WC_NO_ERR_TRACE(WOLFSSL_FAILURE); int done = 0; - int added = 0; int i = 0; int numFailedCerts = 0; int depth = 0; @@ -997,11 +996,8 @@ int wolfSSL_X509_verify_cert(WOLFSSL_X509_STORE_CTX* ctx) &depth, origDepth); continue; } - added = 1; ret = X509StoreVerifyCert(ctx); if (ret != WOLFSSL_SUCCESS) { - if ((origDepth - depth) <= 1) - added = 0; X509VerifyCertSetupRetry(ctx, certs, failedCerts, &depth, origDepth); continue; @@ -1023,21 +1019,14 @@ int wolfSSL_X509_verify_cert(WOLFSSL_X509_STORE_CTX* ctx) != WOLFSSL_SUCCESS) { /* Could not guarantee the temporary intermediates were * dropped; fail closed rather than risk verifying the current - * certificate against one. Leave `added` set: they are still - * loaded, so the exit cleanup makes a final attempt to drop - * them. */ + * certificate against one. */ ret = WOLFSSL_FATAL_ERROR; goto exit; } - added = 0; ret = X509StoreVerifyCert(ctx); if (ret != WOLFSSL_SUCCESS) { /* WOLFSSL_PARTIAL_CHAIN may only terminate the chain at a - * certificate the caller actually trusts. The previous - * "added == 1" guard merely confirmed that some untrusted - * intermediate had been temporarily loaded into the - * CertManager during chain building, which would accept - * chains that never reach a trust anchor. Verify that + * certificate the caller actually trusts, so verify that * ctx->current_cert is itself in the original trust set. */ if (((ctx->flags & WOLFSSL_PARTIAL_CHAIN) || (ctx->store->param != NULL && @@ -1149,11 +1138,17 @@ int wolfSSL_X509_verify_cert(WOLFSSL_X509_STORE_CTX* ctx) } } } - /* Remove intermediates that were added to CM */ + /* Remove intermediates that were added to CM. Unconditional: anything left + * resident anchors verification for every other user of this CM. */ if (ctx != NULL) { if (ctx->store != NULL) { - if (added == 1) { - wolfSSL_CertManagerUnloadTempIntermediateCerts(ctx->store->cm); + if (wolfSSL_CertManagerUnloadTempIntermediateCerts(ctx->store->cm) + != WOLFSSL_SUCCESS) { + WOLFSSL_MSG("Failed to unload temporary intermediates"); + /* Any left resident would anchor later verifications, so do + * not report success. */ + if (ret == WOLFSSL_SUCCESS) + ret = WOLFSSL_FAILURE; } } if (orig != NULL) { @@ -1777,13 +1772,15 @@ static void X509StoreFreeObjList(WOLFSSL_X509_STORE* store, * the numAdded to the store >= is used when comparing to 0. */ i = wolfSSL_sk_X509_OBJECT_num(objs) - 1; while (cnt > 0 && i >= 0) { - /* The inner X509 is owned by somebody else, NULL out the reference */ obj = (WOLFSSL_X509_OBJECT *)wolfSSL_sk_X509_OBJECT_value(objs, i); - if (obj != NULL) { + /* Only certificates are borrowed, so only they consume numAdded. The + * CRL object appended after them must not shift this window. */ + if (obj != NULL && obj->type == WOLFSSL_X509_LU_X509) { + /* The inner X509 is owned by somebody else, NULL out the ref */ obj->type = (WOLFSSL_X509_LOOKUP_TYPE)0; obj->data.ptr = NULL; + cnt--; } - cnt--; i--; } @@ -2522,6 +2519,9 @@ WOLF_STACK_OF(WOLFSSL_X509_OBJECT)* wolfSSL_X509_STORE_get0_objects( { WOLFSSL_STACK* ret = NULL; WOLFSSL_STACK* cert_stack = NULL; + /* Set once the certificates have been handed over to "ret". Until then + * cert_stack still owns them and the error path must not free them. */ + byte certsOwned = 0; #if ((defined(WOLFSSL_SIGNER_DER_CERT) && !defined(NO_FILESYSTEM)) || \ (defined(HAVE_CRL))) WOLFSSL_X509_OBJECT* obj = NULL; @@ -2595,6 +2595,7 @@ WOLF_STACK_OF(WOLFSSL_X509_OBJECT)* wolfSSL_X509_STORE_get0_objects( while (wolfSSL_sk_X509_num(cert_stack) > 0) { wolfSSL_sk_X509_pop(cert_stack); } + certsOwned = 1; #endif #ifdef HAVE_CRL @@ -2625,8 +2626,28 @@ WOLF_STACK_OF(WOLFSSL_X509_OBJECT)* wolfSSL_X509_STORE_get0_objects( store->objs = ret; return ret; err_cleanup: - if (ret != NULL) - X509StoreFreeObjList(store, ret); + if (ret != NULL) { + if (certsOwned) { + X509StoreFreeObjList(store, ret); + } + else { + /* cert_stack still owns these certificates. pop_free() below runs + * wolfSSL_X509_OBJECT_free() on each entry, which frees the inner + * X509, so clear the references or they are freed twice. */ + int j; + WOLFSSL_X509_OBJECT* cur; + + for (j = 0; j < wolfSSL_sk_X509_OBJECT_num(ret); j++) { + cur = (WOLFSSL_X509_OBJECT*)wolfSSL_sk_X509_OBJECT_value(ret, + j); + if (cur != NULL) { + cur->type = (WOLFSSL_X509_LOOKUP_TYPE)0; + cur->data.ptr = NULL; + } + } + wolfSSL_sk_X509_OBJECT_pop_free(ret, NULL); + } + } if (cert_stack != NULL) { while (store->numAdded > 0) { wolfSSL_sk_X509_pop(cert_stack); diff --git a/tests/api/test_dtls13.c b/tests/api/test_dtls13.c index c8fdcd65df..c7f7f83e0c 100644 --- a/tests/api/test_dtls13.c +++ b/tests/api/test_dtls13.c @@ -1122,6 +1122,45 @@ int test_dtls13_epochs(void) { return EXPECT_RESULT(); } +int test_dtls13_alert_with_pending_output(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + char msg[1300]; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + XMEMSET(msg, 'A', sizeof(msg)); + + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + /* Stall the transport, then queue a record big enough that adding the + * alert would exceed the MTU. */ + test_ctx.s_force_want_write = 1; + ExpectIntLT(wolfSSL_write(ssl_s, msg, (int)sizeof(msg)), 0); + ExpectIntGT((int)ssl_s->buffers.outputBuffer.length, 1288); + + /* EndOfEarlyData is not valid in DTLS 1.3 and raises a fatal alert. */ + ExpectIntEQ(Dtls13CheckEpoch(ssl_s, end_of_early_data), SANITY_MSG_E); + + ExpectIntLE((int)(ssl_s->buffers.outputBuffer.idx + + ssl_s->buffers.outputBuffer.length), + (int)ssl_s->buffers.outputBuffer.bufferSize); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + /*-- ack_order (test_dtls.c lines 873,951) ---*/ int test_dtls13_ack_order(void) { diff --git a/tests/api/test_dtls13.h b/tests/api/test_dtls13.h index 2cf31bec27..ac211f1db6 100644 --- a/tests/api/test_dtls13.h +++ b/tests/api/test_dtls13.h @@ -44,6 +44,7 @@ int test_dtls13_basic_connection_id(void); int test_dtls13_hrr_want_write(void); int test_dtls13_every_write_want_write(void); int test_dtls13_epochs(void); +int test_dtls13_alert_with_pending_output(void); int test_dtls13_ack_order(void); int test_dtls13_ack_overflow(void); int test_dtls13_ack_dup_write_counter(void); @@ -71,6 +72,7 @@ int test_dtls13_reuse_after_clear(void); TEST_DECL_GROUP("dtls13", test_dtls13_hrr_want_write), \ TEST_DECL_GROUP("dtls13", test_dtls13_every_write_want_write), \ TEST_DECL_GROUP("dtls13", test_dtls13_epochs), \ + TEST_DECL_GROUP("dtls13", test_dtls13_alert_with_pending_output), \ TEST_DECL_GROUP("dtls13", test_dtls13_ack_order), \ TEST_DECL_GROUP("dtls13", test_dtls13_ack_overflow), \ TEST_DECL_GROUP("dtls13", test_dtls13_ack_dup_write_counter), \ diff --git a/tests/api/test_ossl_x509_str.c b/tests/api/test_ossl_x509_str.c index 2257fa5ed3..42409ce797 100644 --- a/tests/api/test_ossl_x509_str.c +++ b/tests/api/test_ossl_x509_str.c @@ -1657,6 +1657,60 @@ static int test_untrusted_inter_retry(X509* leaf, X509* inter, sk_X509_free(badOnly); return EXPECT_RESULT(); } + +/* A first-link signature failure must not leave the caller-supplied issuer + * loaded in the store's CertManager. X509_verify_cert() adds it as a + * WOLFSSL_TEMP_CA before checking the child; if the check fails the anchor has + * to go with it. The compat verifier drops TEMP_CAs before its own trust + * check, so residue is only visible through another user of the same + * CertManager - signer lookups there do not filter on type. */ +static int test_untrusted_inter_no_temp_ca_residue(X509* leaf, X509* inter, + X509* root) +{ + EXPECT_DECLS; + X509_STORE* store = NULL; + X509_STORE_CTX* ctx = NULL; + STACK_OF(X509)* untrusted = NULL; + X509* badLeaf = NULL; + unsigned char* der = NULL; + const unsigned char* p = NULL; + int derSz = 0; + + /* Flip a bit in the trailing signature BIT STRING: the leaf still names + * inter as its issuer, so inter is still selected and loaded, but the + * signature check against it now fails. */ + ExpectIntGT(derSz = wolfSSL_i2d_X509(leaf, &der), 0); + ExpectNotNull(der); + if (EXPECT_SUCCESS() && der != NULL) { + der[derSz - 1] ^= 0x01; + p = der; + ExpectNotNull(badLeaf = wolfSSL_d2i_X509(NULL, &p, derSz)); + der[derSz - 1] ^= 0x01; + } + + ExpectNotNull(store = X509_STORE_new()); + ExpectIntEQ(X509_STORE_add_cert(store, root), 1); + ExpectNotNull(untrusted = sk_X509_new_null()); + ExpectIntGT(sk_X509_push(untrusted, inter), 0); + ExpectNotNull(ctx = X509_STORE_CTX_new()); + ExpectIntEQ(X509_STORE_CTX_init(ctx, store, badLeaf, untrusted), 1); + ExpectIntEQ(X509_verify_cert(ctx), 0); + ExpectIntNE(X509_STORE_CTX_get_error(ctx), X509_V_OK); + + /* Only root was ever trusted, so the genuine leaf must not verify through + * the CertManager. It would if inter were still resident as a TEMP_CA. */ + if (EXPECT_SUCCESS() && store != NULL && der != NULL) { + ExpectIntNE(wolfSSL_CertManagerVerifyBuffer(store->cm, der, derSz, + WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS); + } + + X509_STORE_CTX_free(ctx); + X509_STORE_free(store); + sk_X509_free(untrusted); + X509_free(badLeaf); + XFREE(der, NULL, DYNAMIC_TYPE_OPENSSL); + return EXPECT_RESULT(); +} #endif /* OPENSSL_EXTRA && !NO_RSA && !NO_CERTS && !NO_FILESYSTEM */ int test_X509_verify_cert_untrusted_inter(void) @@ -1682,6 +1736,7 @@ int test_X509_verify_cert_untrusted_inter(void) int depthExhaustRes = 0; int trustedStackCleanupRes = 0; int retryRes = 0; + int noTempCaResidueRes = 0; ExpectNotNull(leaf = untrusted_inter_load(UA_CERT_DIR "leaf-cert.pem")); ExpectNotNull(leafDeep = @@ -1716,6 +1771,8 @@ int test_X509_verify_cert_untrusted_inter(void) trustedStackCleanupRes = test_untrusted_inter_trusted_stack_cleanup( leaf, inter, root); retryRes = test_untrusted_inter_retry(leaf, inter, tamperedInter, root); + noTempCaResidueRes = test_untrusted_inter_no_temp_ca_residue(leaf, + inter, root); ExpectIntEQ(sanityRes, 1); ExpectIntEQ(twoLevelRes, 1); ExpectIntEQ(emptyStoreRes, 1); @@ -1726,6 +1783,7 @@ int test_X509_verify_cert_untrusted_inter(void) ExpectIntEQ(depthExhaustRes, 1); ExpectIntEQ(trustedStackCleanupRes, 1); ExpectIntEQ(retryRes, 1); + ExpectIntEQ(noTempCaResidueRes, 1); } X509_free(leaf); @@ -2568,6 +2626,48 @@ int test_X509_STORE_get0_objects(void) return EXPECT_RESULT(); } +int test_X509_STORE_get0_objects_borrowed_crl(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_ALL) && defined(HAVE_CRL) && \ + defined(WOLFSSL_SIGNER_DER_CERT) && !defined(NO_FILESYSTEM) && \ + !defined(NO_RSA) + int pass; + + /* pass 0: one get0_objects call. pass 1: a second call, which rebuilds + * the list and so tears the first one down while the store is alive. */ + for (pass = 0; pass < 2 && EXPECT_SUCCESS(); pass++) { + X509_STORE* store = NULL; + X509* borrowed = NULL; + STACK_OF(X509_OBJECT)* objs = NULL; + + /* Not self-signed, so add_cert up_refs it onto store->certs. */ + ExpectNotNull(borrowed = wolfSSL_X509_load_certificate_file(svrCertFile, + WOLFSSL_FILETYPE_PEM)); + ExpectNotNull(store = X509_STORE_new()); + ExpectIntEQ(X509_STORE_add_cert(store, borrowed), 1); + /* Arms cm->crl and puts one decoded CA in the CertManager. */ + ExpectIntEQ(X509_STORE_load_locations(store, caCertFile, NULL), + WOLFSSL_SUCCESS); + + ExpectNotNull(objs = X509_STORE_get0_objects(store)); + /* CM decode + borrowed cert + CRL. */ + ExpectIntEQ(sk_X509_OBJECT_num(objs), 3); + if (pass == 1) { + ExpectNotNull(objs = X509_STORE_get0_objects(store)); + ExpectIntEQ(sk_X509_OBJECT_num(objs), 3); + } + + X509_STORE_free(store); + + /* The store is gone but the caller's reference must have survived. */ + ExpectNotNull(X509_get_subject_name(borrowed)); + X509_free(borrowed); + } +#endif + return EXPECT_RESULT(); +} + int test_wolfSSL_X509_STORE_get1_certs(void) { EXPECT_DECLS; diff --git a/tests/api/test_ossl_x509_str.h b/tests/api/test_ossl_x509_str.h index d320133b62..274b231dda 100644 --- a/tests/api/test_ossl_x509_str.h +++ b/tests/api/test_ossl_x509_str.h @@ -44,6 +44,7 @@ int test_wolfSSL_X509_STORE_set_flags(void); int test_wolfSSL_X509_STORE(void); int test_wolfSSL_X509_STORE_load_locations(void); int test_X509_STORE_get0_objects(void); +int test_X509_STORE_get0_objects_borrowed_crl(void); int test_wolfSSL_X509_STORE_get1_certs(void); int test_wolfSSL_X509_STORE_set_get_crl(void); int test_wolfSSL_X509_STORE_CTX_set0_crls(void); @@ -82,6 +83,8 @@ int test_wolfSSL_CTX_set_cert_store(void); TEST_DECL_GROUP("ossl_x509_store", \ test_wolfSSL_X509_STORE_load_locations), \ TEST_DECL_GROUP("ossl_x509_store", test_X509_STORE_get0_objects), \ + TEST_DECL_GROUP("ossl_x509_store", \ + test_X509_STORE_get0_objects_borrowed_crl), \ TEST_DECL_GROUP("ossl_x509_store", test_wolfSSL_X509_STORE_get1_certs), \ TEST_DECL_GROUP("ossl_x509_store", test_wolfSSL_X509_STORE_set_get_crl), \ TEST_DECL_GROUP("ossl_x509_store", \ diff --git a/tests/api/test_tls13.c b/tests/api/test_tls13.c index c5fe3189c3..5fdd4322da 100644 --- a/tests/api/test_tls13.c +++ b/tests/api/test_tls13.c @@ -4078,6 +4078,61 @@ int test_tls13_rpk_trust(void) return EXPECT_RESULT(); } +int test_tls13_rpk_unoffered_cert_type(void) +{ + EXPECT_DECLS; +#if defined(HAVE_RPK) && defined(WOLFSSL_TLS13) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) + int isServerType; + + /* round 0: server_cert_type. round 1: the client_cert_type twin. */ + for (isServerType = 1; isServerType >= 0 && EXPECT_SUCCESS(); + isServerType--) { + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_rpk_nopin_setup(&test_ctx, &ctx_c, &ctx_s, + &ssl_c, &ssl_s, wolfTLSv1_3_client_method, + wolfTLSv1_3_server_method), 0); + + /* Both ends must authenticate the peer. Under + * OPENSSL_COMPATIBLE_DEFAULTS (--enable-all) the CTX defaults to + * WOLFSSL_VERIFY_NONE, and a non-verifying server omits the + * client_certificate_type response entirely, leaving nothing for the + * second round to reject. Set it explicitly so both rounds behave the + * same across build configs. */ + wolfSSL_set_verify(ssl_c, WOLFSSL_VERIFY_PEER, NULL); + wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_PEER, NULL); + + /* ClientHello out, then the server's flight carrying the response. */ + ExpectIntNE(wolfSSL_connect(ssl_c), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_get_error(ssl_c, + WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), WOLFSSL_ERROR_WANT_READ); + ExpectIntNE(wolfSSL_accept(ssl_s), WOLFSSL_SUCCESS); + + /* Drop the offer, making the response unsolicited. */ + if (ssl_c != NULL) { + if (isServerType) + ssl_c->options.rpkState.sending_ServerCertTypeCnt = 0; + else + ssl_c->options.rpkState.sending_ClientCertTypeCnt = 0; + } + + ExpectIntNE(wolfSSL_connect(ssl_c), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_get_error(ssl_c, + WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), UNSUPPORTED_EXTENSION); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + } +#endif /* HAVE_RPK && WOLFSSL_TLS13 && client && server */ + 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 30ac62952f..922811cfc9 100644 --- a/tests/api/test_tls13.h +++ b/tests/api/test_tls13.h @@ -32,6 +32,7 @@ int test_tls13_rpk_handshake_no_negotiation(void); int test_tls13_pha(void); int test_tls13_rpk_untrusted(void); int test_tls13_rpk_trust(void); +int test_tls13_rpk_unoffered_cert_type(void); int test_tls13_pq_groups(void); int test_tls13_multi_pqc_key_share(void); int test_tls13_early_data(void); @@ -115,6 +116,7 @@ int test_tls13_pha_status_request(void); TEST_DECL_GROUP("tls13", test_tls13_pha), \ TEST_DECL_GROUP("tls13", test_tls13_rpk_untrusted), \ TEST_DECL_GROUP("tls13", test_tls13_rpk_trust), \ + TEST_DECL_GROUP("tls13", test_tls13_rpk_unoffered_cert_type), \ 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), \