Commit b86f4c1631 for openssl.org
commit b86f4c163100a4626597d28126c8b6807a83d1dc
Author: Mounir IDRASSI <mounir.idrassi@idrix.fr>
Date: Mon Sep 14 17:26:11 2026 +0900
Test PHA verification results across session resumption
An application verification callback may accept a nonempty client
certificate or RPK while retaining a nonzero verification error. That
result must survive post-handshake authentication and resumption,
without changing the previously published session.
Expand pending-identity coverage to exercise X.509 and RPK replacement
with successful and callback-accepted error results, using stateful
and stateless refreshed tickets. Check the replacement identity and
result on the live connection and session, then on a resumed connection.
Also check that the retained cache-owned session remains unchanged.
Start empty-RPK tests with a nonzero prior verification result. An
optional empty response must remove that stale result, while a required
empty response must fail without replacing the previous session state.
Document that the empty-response helper retains the SSL_get_verify_result()
semantics for an absent peer certificate: X509_V_OK does not establish
authentication. The helper does not clear errors for supplied credentials.
Remove two unused local pointer resets immediately before successful
parse returns. No library verification-result behavior is changed.
Validation: debug --strict-warnings build; test_sslapi, test_rpk,
test_dtls and test_dtls13_internal all pass (no-fips).
Assisted-by: Codex:gpt-6-astra
Reviewed-by: Neil Horman <nhorman@openssl.org>
Reviewed-by: Matt Caswell <matt@openssl.foundation>
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
Merge-date: Thu Oct 8 16:16:17 2026
Merged-from: https://github.com/openssl/openssl/pull/32638
diff --git a/doc/man3/SSL_get_verify_result.pod b/doc/man3/SSL_get_verify_result.pod
index ca76945886..4777747826 100644
--- a/doc/man3/SSL_get_verify_result.pod
+++ b/doc/man3/SSL_get_verify_result.pod
@@ -13,7 +13,8 @@ SSL_get_verify_result - get result of peer certificate verification
=head1 DESCRIPTION
SSL_get_verify_result() returns the result of the verification of the
-X509 certificate presented by the peer, if any. I<ssl> B<MUST NOT> be NULL.
+X509 certificate or raw public key presented by the peer, if any.
+I<ssl> B<MUST NOT> be NULL.
=head1 NOTES
@@ -32,12 +33,26 @@ these errors may no longer be available.
The verification result is part of the established session and is restored
when a session is reused.
+When a server accepts an empty client Certificate message during the initial
+handshake, the result is X509_V_OK. This applies to both X.509 certificates
+and raw public keys. No credential verification was performed, and this result
+does not establish that the peer was authenticated. The handshake can still
+complete if client authentication is optional.
+
+An empty response to an optional post-handshake authentication request leaves
+the previous peer identity and verification result unchanged. No certificate
+or raw public key verification is performed for an empty response.
+
=head1 BUGS
-If no peer certificate was presented, the returned result code is
-X509_V_OK. This is because no verification error occurred, it does however
-not indicate success. SSL_get_verify_result() is only useful in connection
-with L<SSL_get_peer_certificate(3)>.
+In other cases where no peer certificate or raw public key was presented, the
+returned result can still be X509_V_OK, for example when a server did not request
+a client certificate during the initial handshake. X509_V_OK alone does not establish
+how or whether the peer was authenticated. A suitably provisioned external PSK
+can authenticate a peer without a certificate or raw public key.
+When certificate or raw public key authentication is expected, applications
+must also check that the expected credential is present using
+L<SSL_get_peer_certificate(3)> or L<SSL_get0_peer_rpk(3)>.
=head1 RETURN VALUES
@@ -47,7 +62,8 @@ The following return values can currently occur:
=item X509_V_OK
-The verification succeeded or no peer certificate was presented.
+No verification error was recorded. A peer certificate or raw public key might
+not have been presented; see L</BUGS>.
=item Any other value
@@ -59,11 +75,12 @@ Documented in L<openssl-verify(1)>.
L<ssl(7)>, L<SSL_set_verify_result(3)>,
L<SSL_get_peer_certificate(3)>,
+L<SSL_get0_peer_rpk(3)>,
L<openssl-verify(1)>
=head1 COPYRIGHT
-Copyright 2000-2023 The OpenSSL Project Authors. All Rights Reserved.
+Copyright 2000-2026 The OpenSSL Project Authors. All Rights Reserved.
Licensed under the Apache License 2.0 (the "License"). You may not use
this file except in compliance with the License. You can obtain a copy
diff --git a/ssl/ssl_local.h b/ssl/ssl_local.h
index 7ad94d0058..d7d4723818 100644
--- a/ssl/ssl_local.h
+++ b/ssl/ssl_local.h
@@ -2931,6 +2931,8 @@ __owur SSL_SESSION *lookup_sess_in_cache(SSL_CONNECTION *s,
size_t sess_id_len);
__owur int ssl_get_prev_session(SSL_CONNECTION *s, CLIENTHELLO_MSG *hello);
__owur SSL_SESSION *ssl_session_dup(const SSL_SESSION *src, int ticket);
+void ossl_session_set0_peer(SSL_SESSION *sess, STACK_OF(X509) *peer_chain,
+ EVP_PKEY *peer_rpk, long verify_result);
__owur int ssl_cipher_id_cmp(const SSL_CIPHER *a, const SSL_CIPHER *b);
DECLARE_OBJ_BSEARCH_GLOBAL_CMP_FN(SSL_CIPHER, SSL_CIPHER, ssl_cipher_id);
__owur int ssl_cipher_ptr_id_cmp(const SSL_CIPHER *const *ap,
diff --git a/ssl/ssl_sess.c b/ssl/ssl_sess.c
index 472cb6dbc9..7a74b5bbef 100644
--- a/ssl/ssl_sess.c
+++ b/ssl/ssl_sess.c
@@ -1167,6 +1167,23 @@ X509 *SSL_SESSION_get0_peer(SSL_SESSION *s)
return s->peer;
}
+/*
+ * Replace the client identity in an unshared session, taking ownership of the
+ * supplied chain or RPK. The server keeps the leaf separately from its issuers.
+ */
+void ossl_session_set0_peer(SSL_SESSION *sess, STACK_OF(X509) *peer_chain,
+ EVP_PKEY *peer_rpk, long verify_result)
+{
+ X509_free(sess->peer);
+ OSSL_STACK_OF_X509_free(sess->peer_chain);
+ EVP_PKEY_free(sess->peer_rpk);
+
+ sess->peer = peer_chain == NULL ? NULL : sk_X509_shift(peer_chain);
+ sess->peer_chain = peer_chain;
+ sess->peer_rpk = peer_rpk;
+ sess->verify_result = verify_result;
+}
+
EVP_PKEY *SSL_SESSION_get0_peer_rpk(SSL_SESSION *s)
{
return s->peer_rpk;
diff --git a/ssl/statem/statem_srvr.c b/ssl/statem/statem_srvr.c
index 0ecf7968bd..5fe3f2ae59 100644
--- a/ssl/statem/statem_srvr.c
+++ b/ssl/statem/statem_srvr.c
@@ -140,7 +140,7 @@ static int ossl_statem_server13_read_transition(SSL_CONNECTION *s, int mt)
case TLS_ST_SR_COMP_CERT:
case TLS_ST_SR_CERT:
- if (!received_client_cert(s)) {
+ if (st->no_cert_verify || !received_client_cert(s)) {
if (mt == SSL3_MT_FINISHED) {
st->hand_state = TLS_ST_SR_FINISHED;
return 1;
@@ -4095,7 +4095,12 @@ static void tls_clear_pending_client_identity(SSL_CONNECTION *sc)
sc->s3.tmp.pending_peer_rpk = NULL;
}
-static void tls_clear_client_verification_state(SSL_CONNECTION *sc)
+/*
+ * Clear old verification state before verifying fresh credentials, or after
+ * accepting an empty initial Certificate response. Empty PHA responses retain
+ * the previous state. Do not clear results produced by the new verification.
+ */
+static void tls_clear_client_verification_state(SSL_CONNECTION *sc, long verify_result)
{
OSSL_STACK_OF_X509_free(sc->verified_chain);
sc->verified_chain = NULL;
@@ -4105,7 +4110,7 @@ static void tls_clear_client_verification_state(SSL_CONNECTION *sc)
X509_free(sc->dane.mcert);
sc->dane.mcert = NULL;
sc->dane.mtlsa = NULL;
- sc->verify_result = X509_V_OK;
+ sc->verify_result = verify_result;
}
MSG_PROCESS_RETURN tls_process_client_rpk(SSL_CONNECTION *sc, PACKET *pkt)
@@ -4120,7 +4125,8 @@ MSG_PROCESS_RETURN tls_process_client_rpk(SSL_CONNECTION *sc, PACKET *pkt)
/* Stash the parsed RPK; verification runs in the post-process step. */
tls_clear_pending_client_identity(sc);
sc->s3.tmp.pending_peer_rpk = peer_rpk;
- peer_rpk = NULL;
+ if (peer_rpk != NULL)
+ tls_clear_client_verification_state(sc, X509_V_ERR_UNSPECIFIED);
return MSG_PROCESS_CONTINUE_PROCESSING;
}
@@ -4134,6 +4140,9 @@ static WORK_STATE tls_post_process_client_rpk(SSL_CONNECTION *sc,
(void)wst;
+ if (SSL_CONNECTION_IS_VERSION13(sc))
+ sc->statem.no_cert_verify = peer_rpk == NULL;
+
if (peer_rpk == NULL) {
if ((sc->verify_mode & SSL_VERIFY_FAIL_IF_NO_PEER_CERT)
&& (sc->verify_mode & SSL_VERIFY_PEER)) {
@@ -4162,8 +4171,8 @@ static WORK_STATE tls_post_process_client_rpk(SSL_CONNECTION *sc,
* Sessions must be immutable once they go into the session cache. Otherwise
* we can get multi-thread problems. Therefore we don't "update" sessions,
* we replace them with a duplicate. Here, we need to do this every time
- * a new RPK (or certificate) is received via post-handshake authentication,
- * as the session may have already gone into the session cache.
+ * a PHA response is accepted, even if empty, because refreshed tickets
+ * will update the session's key and ID.
*/
if (sc->post_handshake_auth == SSL_PHA_REQUESTED) {
if ((new_sess = ssl_session_dup(sc->session, 0)) == NULL) {
@@ -4175,20 +4184,12 @@ static WORK_STATE tls_post_process_client_rpk(SSL_CONNECTION *sc,
sc->session = new_sess;
}
- /* Ensure there is no peer/peer_chain */
- X509_free(sc->session->peer);
- sc->session->peer = NULL;
- sk_X509_pop_free(sc->session->peer_chain, X509_free);
- sc->session->peer_chain = NULL;
-
- EVP_PKEY_free(sc->session->peer_rpk);
- sc->session->peer_rpk = peer_rpk;
- sc->s3.tmp.pending_peer_rpk = NULL;
-
- if (peer_rpk == NULL)
- tls_clear_client_verification_state(sc);
-
- sc->session->verify_result = sc->verify_result;
+ if (peer_rpk != NULL || sc->post_handshake_auth != SSL_PHA_REQUESTED) {
+ if (peer_rpk == NULL)
+ tls_clear_client_verification_state(sc, X509_V_OK);
+ ossl_session_set0_peer(sc->session, NULL, peer_rpk, sc->verify_result);
+ sc->s3.tmp.pending_peer_rpk = NULL;
+ }
/*
* Freeze the handshake buffer. For <TLS1.3 we do this after the CKE
@@ -4324,7 +4325,8 @@ MSG_PROCESS_RETURN tls_process_client_certificate(SSL_CONNECTION *s,
*/
tls_clear_pending_client_identity(s);
s->s3.tmp.pending_peer_chain = sk;
- sk = NULL;
+ if (sk_X509_num(sk) > 0)
+ tls_clear_client_verification_state(s, X509_V_ERR_UNSPECIFIED);
return MSG_PROCESS_CONTINUE_PROCESSING;
@@ -4356,6 +4358,8 @@ WORK_STATE tls_post_process_client_certificate(SSL_CONNECTION *s,
(void)wst;
has_cert = sk != NULL && sk_X509_num(sk) > 0;
+ if (SSL_CONNECTION_IS_VERSION13(s))
+ s->statem.no_cert_verify = !has_cert;
if (!has_cert) {
/* Fail only if we required a certificate */
if ((s->verify_mode & SSL_VERIFY_PEER)
@@ -4400,8 +4404,8 @@ WORK_STATE tls_post_process_client_certificate(SSL_CONNECTION *s,
* Sessions must be immutable once they go into the session cache. Otherwise
* we can get multi-thread problems. Therefore we don't "update" sessions,
* we replace them with a duplicate. Here, we need to do this every time
- * a new certificate is received via post-handshake authentication, as the
- * session may have already gone into the session cache.
+ * a PHA response is accepted, even if empty, because refreshed tickets
+ * will update the session's key and ID.
*/
if (s->post_handshake_auth == SSL_PHA_REQUESTED) {
if ((new_sess = ssl_session_dup(s->session, 0)) == 0) {
@@ -4413,23 +4417,15 @@ WORK_STATE tls_post_process_client_certificate(SSL_CONNECTION *s,
s->session = new_sess;
}
- X509_free(s->session->peer);
- s->session->peer = NULL;
- OSSL_STACK_OF_X509_free(s->session->peer_chain);
- s->session->peer_chain = NULL;
- EVP_PKEY_free(s->session->peer_rpk);
- s->session->peer_rpk = NULL;
-
- if (has_cert) {
- /* Server keeps the EE cert in session->peer; peer_chain holds issuers only. */
- s->session->peer = sk_X509_shift(sk);
- s->session->peer_chain = sk;
- s->s3.tmp.pending_peer_chain = NULL;
- s->session->verify_result = s->verify_result;
- } else {
+ if (!has_cert) {
tls_clear_pending_client_identity(s);
- tls_clear_client_verification_state(s);
- s->session->verify_result = s->verify_result;
+ sk = NULL;
+ if (s->post_handshake_auth != SSL_PHA_REQUESTED)
+ tls_clear_client_verification_state(s, X509_V_OK);
+ }
+ if (has_cert || s->post_handshake_auth != SSL_PHA_REQUESTED) {
+ ossl_session_set0_peer(s->session, sk, NULL, s->verify_result);
+ s->s3.tmp.pending_peer_chain = NULL;
}
/*
diff --git a/test/sslapitest.c b/test/sslapitest.c
index bdd769b940..e0de7651e1 100644
--- a/test/sslapitest.c
+++ b/test/sslapitest.c
@@ -42,6 +42,7 @@
#include "internal/ktls.h"
#include "internal/ssl_unwrap.h"
#include "../ssl/ssl_local.h"
+#include "../ssl/statem/statem_local.h"
#include "../ssl/record/methods/recmethod_local.h"
#include "filterprov.h"
@@ -1148,13 +1149,86 @@ end:
return ret;
}
-#ifndef OSSL_NO_USABLE_TLS1_3
+#if !defined(OPENSSL_NO_TLS1_2) || !defined(OSSL_NO_USABLE_TLS1_3)
+/* An absent credential must not invoke the verification callback. */
+static int empty_client_cert_verify_cb(X509_STORE_CTX *ctx, void *arg)
+{
+ int *calls = arg;
+
+ (*calls)++;
+ return 0;
+}
+
+/* Optional X.509/RPK authentication, plus a no-request control per version. */
+static const struct {
+ int version, use_rpk, request_cert;
+} empty_client_cert_tests[] = {
+ { TLS1_2_VERSION, 0, 1 },
+ { TLS1_2_VERSION, 1, 1 },
+ { TLS1_3_VERSION, 0, 1 },
+ { TLS1_3_VERSION, 1, 1 },
+ { TLS1_2_VERSION, 0, 0 },
+ { TLS1_3_VERSION, 0, 0 }
+};
+
+static int test_empty_client_certificate(int idx)
+{
+ static const unsigned char cert_type_rpk[] = { TLSEXT_cert_type_rpk };
+ SSL_CTX *sctx = NULL, *cctx = NULL;
+ SSL *serverssl = NULL, *clientssl = NULL;
+ int version = empty_client_cert_tests[idx].version;
+ int request_cert = empty_client_cert_tests[idx].request_cert;
+ long expected = X509_V_OK;
+ int calls = 0, testresult = 0;
+
+#ifdef OPENSSL_NO_TLS1_2
+ if (version == TLS1_2_VERSION)
+ return TEST_skip("TLS 1.2 is disabled");
+#endif
+#ifdef OSSL_NO_USABLE_TLS1_3
+ if (version == TLS1_3_VERSION)
+ return TEST_skip("TLS 1.3 is disabled");
+#endif
+ if (!TEST_true(create_ssl_ctx_pair(libctx, TLS_server_method(),
+ TLS_client_method(), version, version,
+ &sctx, &cctx, cert, privkey)))
+ goto end;
+ if (request_cert)
+ SSL_CTX_set_verify(sctx, SSL_VERIFY_PEER, NULL);
+ SSL_CTX_set_cert_verify_callback(sctx, empty_client_cert_verify_cb, &calls);
+ if (empty_client_cert_tests[idx].use_rpk
+ && (!TEST_true(SSL_CTX_set1_client_cert_type(sctx, cert_type_rpk,
+ sizeof(cert_type_rpk)))
+ || !TEST_true(SSL_CTX_set1_client_cert_type(cctx, cert_type_rpk,
+ sizeof(cert_type_rpk)))))
+ goto end;
+ if (!TEST_true(create_ssl_objects(sctx, cctx, &serverssl, &clientssl,
+ NULL, NULL))
+ || !TEST_true(create_ssl_connection(serverssl, clientssl, SSL_ERROR_NONE))
+ || !TEST_ptr_null(SSL_get0_peer_certificate(serverssl))
+ || !TEST_ptr_null(SSL_get0_peer_rpk(serverssl))
+ || !TEST_long_eq(SSL_get_verify_result(serverssl), expected)
+ || !TEST_long_eq(SSL_get_session(serverssl)->verify_result, expected)
+ || !TEST_int_eq(calls, 0))
+ goto end;
+ testresult = 1;
+end:
+ SSL_free(serverssl);
+ SSL_free(clientssl);
+ SSL_CTX_free(sctx);
+ SSL_CTX_free(cctx);
+ return testresult;
+}
+#endif
+
+#if !defined(OSSL_NO_USABLE_TLS1_3) || !defined(OSSL_NO_USABLE_DTLS1_3)
typedef struct {
int pha;
int calls;
int accept;
int candidate_ok;
int initial_error;
+ int accepted_error; /* Result retained when the PHA callback accepts. */
X509 *expected_cert;
EVP_PKEY *expected_rpk;
} PHA_VERIFY_DATA;
@@ -1166,6 +1240,9 @@ static int pha_verify_retry_cb(X509_STORE_CTX *ctx, void *arg)
int idx = SSL_get_ex_data_X509_STORE_CTX_idx();
if (!data->pha) {
+ if (X509_STORE_CTX_get0_cert(ctx) != NULL
+ && !TEST_int_eq(X509_verify_cert(ctx), 1))
+ return 0;
X509_STORE_CTX_set_error(ctx, data->initial_error);
return 1;
}
@@ -1190,99 +1267,62 @@ static int pha_verify_retry_cb(X509_STORE_CTX *ctx, void *arg)
if (!data->accept)
X509_STORE_CTX_set_error(ctx,
X509_V_ERR_APPLICATION_VERIFICATION);
+ else
+ X509_STORE_CTX_set_error(ctx, data->accepted_error);
return data->accept;
}
-static int setup_pha_resumption(PHA_VERIFY_DATA *verify_data,
- int initial_client_cert, int use_rpk, SSL_CTX **sctx_out,
- SSL_CTX **cctx_out, SSL **serverssl_out, SSL **clientssl_out,
- SSL_SESSION **ticket_out)
+/* Outputs belong to the caller, including on failure. */
+static int setup_pha(PHA_VERIFY_DATA *verify_data, int initial_client_cert,
+ int use_rpk, int testdtls, SSL_CTX **sctx, SSL_CTX **cctx,
+ SSL **serverssl, SSL **clientssl)
{
static const unsigned char cert_type_rpk[] = { TLSEXT_cert_type_rpk };
static const unsigned char sid_ctx[] = { 1 };
- SSL_CTX *sctx = NULL, *cctx = NULL;
- SSL *serverssl = NULL, *clientssl = NULL;
- SSL_SESSION *first_ticket = NULL, *ticket = NULL;
- int ret = 0;
+ int version = testdtls ? DTLS1_3_VERSION : TLS1_3_VERSION;
- /*
- * Force cache-owned stateful TLS 1.3 tickets. The session id context is
- * required when the tests enable peer verification; the owner assertions
- * below prove that each session was published to the internal cache.
- */
- if (!TEST_true(create_ssl_ctx_pair(libctx, TLS_server_method(),
- TLS_client_method(), TLS1_3_VERSION, TLS1_3_VERSION,
- &sctx, &cctx, cert, privkey))
- || !TEST_true(SSL_CTX_set_num_tickets(sctx, 1))
- || !TEST_true(SSL_CTX_set_options(sctx, SSL_OP_NO_TICKET))
- || !TEST_true(SSL_CTX_set_session_id_context(sctx, sid_ctx,
+ /* One stateful ticket makes the active server session cache-owned. */
+ if (!TEST_true(create_ssl_ctx_pair(libctx,
+ testdtls ? DTLS_server_method() : TLS_server_method(),
+ testdtls ? DTLS_client_method() : TLS_client_method(), version, version,
+ sctx, cctx, cert, privkey))
+ || !TEST_true(SSL_CTX_set_num_tickets(*sctx, 1))
+ || !TEST_true(SSL_CTX_set_options(*sctx, SSL_OP_NO_TICKET))
+ || !TEST_true(SSL_CTX_set_session_id_context(*sctx, sid_ctx,
sizeof(sid_ctx))))
- goto end;
-
+ return 0;
if (use_rpk
- && (!TEST_true(SSL_CTX_set1_client_cert_type(sctx, cert_type_rpk,
+ && (!TEST_true(SSL_CTX_set1_client_cert_type(*sctx, cert_type_rpk,
sizeof(cert_type_rpk)))
- || !TEST_true(SSL_CTX_set1_client_cert_type(cctx, cert_type_rpk,
+ || !TEST_true(SSL_CTX_set1_client_cert_type(*cctx, cert_type_rpk,
sizeof(cert_type_rpk)))))
- goto end;
-
- SSL_CTX_set_cert_verify_callback(sctx, pha_verify_retry_cb, verify_data);
+ return 0;
+ SSL_CTX_set_cert_verify_callback(*sctx, pha_verify_retry_cb, verify_data);
if (initial_client_cert)
- SSL_CTX_set_verify(sctx,
+ SSL_CTX_set_verify(*sctx,
SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
-
- if (!TEST_true(create_ssl_objects(sctx, cctx, &serverssl,
- &clientssl, NULL, NULL)))
- goto end;
+ if (!TEST_true(create_ssl_objects(*sctx, *cctx, serverssl, clientssl,
+ NULL, NULL)))
+ return 0;
if (initial_client_cert
- && (!TEST_int_eq(SSL_use_certificate_file(clientssl, cert,
+ && (!TEST_int_eq(SSL_use_certificate_file(*clientssl, cert,
SSL_FILETYPE_PEM),
1)
- || !TEST_int_eq(SSL_use_PrivateKey_file(clientssl, privkey,
+ || !TEST_int_eq(SSL_use_PrivateKey_file(*clientssl, privkey,
SSL_FILETYPE_PEM),
- 1)
- || !TEST_int_eq(SSL_check_private_key(clientssl), 1)))
- goto end;
- if (!TEST_true(create_ssl_connection(serverssl, clientssl,
- SSL_ERROR_NONE))
- || !TEST_ptr(first_ticket = SSL_get1_session(clientssl)))
- goto end;
-
- SSL_shutdown(clientssl);
- SSL_shutdown(serverssl);
- SSL_free(serverssl);
- SSL_free(clientssl);
- serverssl = clientssl = NULL;
-
- if (!TEST_true(create_ssl_objects(sctx, cctx, &serverssl,
- &clientssl, NULL, NULL))
- || !TEST_true(SSL_set_session(clientssl, first_ticket)))
- goto end;
- SSL_set_post_handshake_auth(clientssl, 1);
- if (!TEST_true(create_ssl_connection(serverssl, clientssl,
- SSL_ERROR_NONE))
- || !TEST_true(SSL_session_reused(clientssl))
- || !TEST_ptr(ticket = SSL_get1_session(clientssl)))
- goto end;
-
- *sctx_out = sctx;
- *cctx_out = cctx;
- *serverssl_out = serverssl;
- *clientssl_out = clientssl;
- *ticket_out = ticket;
- sctx = cctx = NULL;
- serverssl = clientssl = NULL;
- ticket = NULL;
- ret = 1;
-
-end:
- SSL_SESSION_free(first_ticket);
- SSL_SESSION_free(ticket);
- SSL_free(serverssl);
- SSL_free(clientssl);
- SSL_CTX_free(sctx);
- SSL_CTX_free(cctx);
- return ret;
+ 1)))
+ return 0;
+ /* Populate chain and peername through verification of the initial X.509 peer. */
+ if (initial_client_cert && !use_rpk
+ && (!TEST_true(X509_STORE_add_cert(SSL_CTX_get_cert_store(*sctx),
+ SSL_get_certificate(*clientssl)))
+ || !TEST_true(SSL_set_purpose(*serverssl, X509_PURPOSE_ANY))
+ || !TEST_true(SSL_set1_dnsname(*serverssl, "server.example"))
+ || !TEST_true(X509_VERIFY_PARAM_set_flags(SSL_get0_param(*serverssl),
+ X509_V_FLAG_PARTIAL_CHAIN))))
+ return 0;
+ SSL_set_post_handshake_auth(*clientssl, 1);
+ return TEST_true(create_ssl_connection(*serverssl, *clientssl, SSL_ERROR_NONE));
}
static int start_pha_and_pause(SSL *serverssl, SSL *clientssl)
@@ -1301,100 +1341,36 @@ static int start_pha_and_pause(SSL *serverssl, SSL *clientssl)
}
static int resume_pha_ticket(SSL_CTX *sctx, SSL_CTX *cctx,
- SSL_SESSION *ticket, SSL **serverssl_out, SSL **clientssl_out)
+ SSL_SESSION *ticket, SSL **serverssl, SSL **clientssl)
{
- SSL *serverssl = NULL, *clientssl = NULL;
+ return TEST_true(create_ssl_objects(sctx, cctx, serverssl, clientssl, NULL, NULL))
+ && TEST_true(SSL_set_session(*clientssl, ticket))
+ && TEST_true(create_ssl_connection(*serverssl, *clientssl, SSL_ERROR_NONE))
+ && TEST_true(SSL_session_reused(*clientssl));
+}
- if (!TEST_true(create_ssl_objects(sctx, cctx, &serverssl, &clientssl,
- NULL, NULL))
- || !TEST_true(SSL_set_session(clientssl, ticket))
- || !TEST_true(create_ssl_connection(serverssl, clientssl,
- SSL_ERROR_NONE))
- || !TEST_true(SSL_session_reused(clientssl))) {
- SSL_free(serverssl);
- SSL_free(clientssl);
- return 0;
- }
+static int check_pha_peer_identity(SSL *ssl, const PHA_VERIFY_DATA *data)
+{
+ if (data->expected_rpk != NULL)
+ return TEST_ptr_null(SSL_get0_peer_certificate(ssl))
+ && TEST_ptr(SSL_get0_peer_rpk(ssl))
+ && TEST_int_eq(EVP_PKEY_eq(SSL_get0_peer_rpk(ssl),
+ data->expected_rpk),
+ 1);
- *serverssl_out = serverssl;
- *clientssl_out = clientssl;
- return 1;
+ return TEST_ptr_null(SSL_get0_peer_rpk(ssl))
+ && TEST_ptr(SSL_get0_peer_certificate(ssl))
+ && TEST_int_eq(X509_cmp(SSL_get0_peer_certificate(ssl),
+ data->expected_cert),
+ 0);
}
/*
- * verified_chain, peername, and DANE verification results are connection-only
- * and are not restored from a resumed session. Populate them so the empty-PHA
- * cleanup assertions below have non-empty preconditions.
+ * Cases 0..3 accept X.509/RPK with a retained error and stateful/stateless
+ * refreshed tickets. Cases 4..5 reject X.509/RPK while another connection
+ * resumes the old session. Case 6 repeats stateful X.509 acceptance with DTLS.
*/
-static int seed_pha_verification_state(SSL *serverssl)
-{
- static const unsigned char tlsa_digest[SHA256_DIGEST_LENGTH] = { 0 };
- SSL_CONNECTION *servercon = SSL_CONNECTION_FROM_SSL(serverssl);
- SSL_CTX *sctx = SSL_get_SSL_CTX(serverssl);
- X509 *peer = SSL_get0_peer_certificate(serverssl);
- X509_STORE *store = NULL;
- X509_STORE_CTX *store_ctx = NULL;
- X509_VERIFY_PARAM *param;
- STACK_OF(X509) *verified_chain = NULL;
- int peer_ref = 0, ret = 0;
-
- if (!TEST_ptr(servercon)
- || !TEST_ptr(sctx)
- || !TEST_ptr(peer)
- || !TEST_ptr(store = X509_STORE_new())
- || !TEST_ptr(store_ctx = X509_STORE_CTX_new_ex(libctx, NULL))
- || !TEST_true(X509_STORE_add_cert(store, peer))
- || !TEST_true(X509_STORE_CTX_init(store_ctx, store, peer, NULL)))
- goto end;
-
- param = X509_STORE_CTX_get0_param(store_ctx);
- if (!TEST_ptr(param)
- || !TEST_true(X509_VERIFY_PARAM_set_flags(param,
- X509_V_FLAG_PARTIAL_CHAIN))
- || !TEST_true(X509_VERIFY_PARAM_set1_host(param,
- "server.example", 0))
- || !TEST_int_eq(X509_verify_cert(store_ctx), 1)
- || !TEST_str_eq(X509_VERIFY_PARAM_get0_peername(param),
- "server.example")
- || !TEST_ptr(verified_chain = sk_X509_new_null()))
- goto end;
- if (!TEST_true(X509_up_ref(peer)))
- goto end;
- peer_ref = 1;
- if (!TEST_true(sk_X509_push(verified_chain, peer)))
- goto end;
- peer_ref = 0;
-
- OSSL_STACK_OF_X509_free(servercon->verified_chain);
- servercon->verified_chain = verified_chain;
- verified_chain = NULL;
- X509_VERIFY_PARAM_move_peername(servercon->param, param);
-
- if (!TEST_int_gt(SSL_CTX_dane_enable(sctx), 0)
- || !TEST_int_gt(SSL_dane_enable(serverssl, NULL), 0)
- || !TEST_int_gt(SSL_dane_tlsa_add(serverssl,
- DANETLS_USAGE_DANE_EE, DANETLS_SELECTOR_SPKI,
- DANETLS_MATCHING_2256, tlsa_digest, sizeof(tlsa_digest)),
- 0)
- || !TEST_true(X509_up_ref(peer)))
- goto end;
- servercon->dane.mdpth = 0;
- servercon->dane.pdpth = 0;
- servercon->dane.mcert = peer;
- servercon->dane.mtlsa = sk_danetls_record_value(
- servercon->dane.trecs, 0);
- ret = 1;
-
-end:
- if (peer_ref)
- X509_free(peer);
- OSSL_STACK_OF_X509_free(verified_chain);
- X509_STORE_CTX_free(store_ctx);
- X509_STORE_free(store);
- return ret;
-}
-
-static int test_pha_x509_pending_identity(void)
+static int test_pha_pending_identity(int idx)
{
char *leaf_chain = test_mk_file_path(certsdir, "leaf-chain.pem");
char *leaf_key = test_mk_file_path(certsdir, "leaf.key");
@@ -1406,16 +1382,50 @@ static int test_pha_x509_pending_identity(void)
SSL_SESSION *ticket = NULL, *old_session = NULL, *fresh_ticket = NULL;
STACK_OF(X509) *old_chain;
X509 *old_peer;
- int testresult = 0;
+ EVP_PKEY *old_rpk;
+ int testdtls = idx == 6;
+ int reject = idx == 4 || idx == 5;
+ int use_rpk = idx & 1;
+ int stateless = idx == 2 || idx == 3;
+ int ret, testresult = 0;
+#ifdef OSSL_NO_USABLE_TLS1_3
+ if (!testdtls) {
+ testresult = TEST_skip("TLS 1.3 is disabled");
+ goto end;
+ }
+#endif
+#ifdef OSSL_NO_USABLE_DTLS1_3
+ if (testdtls) {
+ testresult = TEST_skip("DTLS 1.3 is disabled");
+ goto end;
+ }
+#endif
+ verify_data.initial_error = reject ? X509_V_OK
+ : X509_V_ERR_APPLICATION_VERIFICATION;
+ verify_data.accepted_error = X509_V_ERR_CERT_REVOKED;
if (!TEST_ptr(leaf_chain) || !TEST_ptr(leaf_key)
- || !setup_pha_resumption(&verify_data, 1, 0, &sctx, &cctx,
- &serverssl, &clientssl, &ticket)
+ || !setup_pha(&verify_data, !reject, use_rpk, testdtls, &sctx, &cctx,
+ &serverssl, &clientssl)
+ || !TEST_ptr(ticket = SSL_get1_session(clientssl))
|| !TEST_ptr(old_session = SSL_get1_session(serverssl))
|| !TEST_ptr(old_session->owner)
- || !TEST_ptr(old_peer = old_session->peer))
+ || !TEST_long_eq(old_session->verify_result, verify_data.initial_error)
+ || !TEST_long_eq(SSL_get_verify_result(serverssl),
+ verify_data.initial_error))
goto end;
+ old_peer = old_session->peer;
+ old_rpk = old_session->peer_rpk;
old_chain = old_session->peer_chain;
+ if (reject) {
+ if (!TEST_ptr_null(old_peer) || !TEST_ptr_null(old_rpk))
+ goto end;
+ } else if (use_rpk) {
+ if (!TEST_ptr(old_rpk) || !TEST_ptr_null(old_peer))
+ goto end;
+ } else if (!TEST_ptr(old_peer) || !TEST_ptr_null(old_rpk)) {
+ goto end;
+ }
if (!TEST_int_eq(SSL_use_certificate_chain_file(clientssl,
leaf_chain),
@@ -1427,8 +1437,19 @@ static int test_pha_x509_pending_identity(void)
goto end;
verify_data.pha = 1;
- verify_data.accept = 1;
- verify_data.expected_cert = SSL_get_certificate(clientssl);
+ verify_data.accept = !reject;
+ if (use_rpk) {
+ verify_data.expected_rpk = X509_get0_pubkey(SSL_get_certificate(clientssl));
+ if (!TEST_ptr(verify_data.expected_rpk))
+ goto end;
+ } else {
+ verify_data.expected_cert = SSL_get_certificate(clientssl);
+ }
+ if (stateless) {
+ /* Keep the old cached session, but issue stateless tickets after PHA. */
+ SSL_CTX_clear_options(sctx, SSL_OP_NO_TICKET);
+ SSL_clear_options(serverssl, SSL_OP_NO_TICKET);
+ }
SSL_set_verify(serverssl, SSL_VERIFY_PEER, NULL);
if (!start_pha_and_pause(serverssl, clientssl))
goto end;
@@ -1437,34 +1458,59 @@ static int test_pha_x509_pending_identity(void)
if (!TEST_int_eq(verify_data.calls, 1)
|| !TEST_true(verify_data.candidate_ok)
|| !TEST_ptr_eq(SSL_get_session(serverssl), old_session)
- || !TEST_ptr_eq(old_session->peer, old_peer)
- || !TEST_ptr_eq(old_session->peer_chain, old_chain)
+ || !TEST_long_eq(old_session->verify_result, verify_data.initial_error)
|| !TEST_ptr_eq(SSL_get0_peer_certificate(serverssl), old_peer)
- || !TEST_ptr_eq(SSL_get_peer_cert_chain(serverssl), old_chain)
- || !TEST_ptr(servercon->s3.tmp.pending_peer_chain))
+ || !TEST_ptr_eq(SSL_get0_peer_rpk(serverssl), old_rpk)
+ || !TEST_ptr_eq(SSL_get_peer_cert_chain(serverssl), old_chain))
goto end;
- if (!TEST_true(create_ssl_connection(serverssl, clientssl,
- SSL_ERROR_NONE))
- || !TEST_int_eq(verify_data.calls, 2)
+ if (reject) {
+ /* A second connection must not see the pending credential. */
+ if (!resume_pha_ticket(sctx, cctx, ticket, &resume_serverssl,
+ &resume_clientssl)
+ || !TEST_int_eq(verify_data.calls, 1)
+ || !TEST_ptr_null(SSL_get0_peer_certificate(resume_serverssl))
+ || !TEST_ptr_null(SSL_get0_peer_rpk(resume_serverssl))
+ || !TEST_ptr_null(SSL_get_peer_cert_chain(resume_serverssl)))
+ goto end;
+ ret = SSL_read(serverssl, NULL, 0);
+ if (!TEST_int_le(ret, 0)
+ || !TEST_int_eq(SSL_get_error(serverssl, ret), SSL_ERROR_SSL)
+ || !TEST_int_eq(ERR_GET_REASON(ERR_peek_last_error()),
+ SSL_R_CERTIFICATE_VERIFY_FAILED)
+ || !TEST_ptr_eq(SSL_get_session(serverssl), old_session)
+ || !TEST_ptr_null(SSL_get0_peer_certificate(serverssl))
+ || !TEST_ptr_null(SSL_get0_peer_rpk(serverssl))
+ || !TEST_ptr_null(SSL_get_peer_cert_chain(serverssl))
+ || !TEST_ptr_null(SSL_get0_peer_certificate(resume_serverssl))
+ || !TEST_ptr_null(SSL_get0_peer_rpk(resume_serverssl))
+ || !TEST_ptr_null(SSL_get_peer_cert_chain(resume_serverssl)))
+ goto end;
+ } else if (!TEST_true(create_ssl_connection(serverssl, clientssl,
+ SSL_ERROR_NONE))
|| !TEST_ptr_ne(SSL_get_session(serverssl), old_session)
- || !TEST_ptr(SSL_get0_peer_certificate(serverssl))
- || !TEST_int_eq(X509_cmp(SSL_get0_peer_certificate(serverssl),
- verify_data.expected_cert),
- 0)
- || !TEST_ptr_null(servercon->s3.tmp.pending_peer_chain)
- || !TEST_ptr_eq(old_session->peer, old_peer)
- || !TEST_ptr_eq(old_session->peer_chain, old_chain)
+ || !check_pha_peer_identity(serverssl, &verify_data)
+ || !TEST_long_eq(SSL_get_verify_result(serverssl),
+ verify_data.accepted_error)
+ || !TEST_long_eq(servercon->session->verify_result,
+ verify_data.accepted_error)
|| !TEST_ptr(fresh_ticket = SSL_get1_session(clientssl))
|| !resume_pha_ticket(sctx, cctx, fresh_ticket, &resume_serverssl,
&resume_clientssl)
- || !TEST_ptr(SSL_get0_peer_certificate(resume_serverssl))
- || !TEST_int_eq(X509_cmp(
- SSL_get0_peer_certificate(resume_serverssl),
- verify_data.expected_cert),
- 0))
+ || !check_pha_peer_identity(resume_serverssl, &verify_data)
+ || !TEST_long_eq(SSL_get_verify_result(resume_serverssl),
+ verify_data.accepted_error)) {
goto end;
+ }
+ if (!TEST_int_eq(verify_data.calls, 2)
+ || !TEST_ptr_null(servercon->s3.tmp.pending_peer_chain)
+ || !TEST_ptr_null(servercon->s3.tmp.pending_peer_rpk)
+ || !TEST_ptr_eq(old_session->peer, old_peer)
+ || !TEST_ptr_eq(old_session->peer_rpk, old_rpk)
+ || !TEST_ptr_eq(old_session->peer_chain, old_chain)
+ || !TEST_long_eq(old_session->verify_result, verify_data.initial_error))
+ goto end;
testresult = 1;
end:
@@ -1482,113 +1528,212 @@ end:
return testresult;
}
-static int test_pha_empty_client_certificate(int required)
+/* Seed a prior DANE match to check preservation and fresh-credential cleanup. */
+static int seed_pha_dane(SSL *ssl)
+{
+ static const unsigned char digest[SHA256_DIGEST_LENGTH] = { 0 };
+ SSL_CONNECTION *sc = SSL_CONNECTION_FROM_SSL(ssl);
+ X509 *peer = SSL_get0_peer_certificate(ssl);
+
+ if (!TEST_int_gt(SSL_CTX_dane_enable(SSL_get_SSL_CTX(ssl)), 0)
+ || !TEST_int_gt(SSL_dane_enable(ssl, "server.example"), 0)
+ || !TEST_int_gt(SSL_dane_tlsa_add(ssl, DANETLS_USAGE_DANE_EE,
+ DANETLS_SELECTOR_SPKI, DANETLS_MATCHING_2256,
+ digest, sizeof(digest)),
+ 0)
+ || !TEST_true(X509_up_ref(peer)))
+ return 0;
+ sc->dane.mdpth = sc->dane.pdpth = 0;
+ sc->dane.mcert = peer;
+ sc->dane.mtlsa = sk_danetls_record_value(sc->dane.trecs, 0);
+ return 1;
+}
+
+static int check_pha_verification_state(SSL *ssl, int populated)
+{
+ SSL_CONNECTION *sc = SSL_CONNECTION_FROM_SSL(ssl);
+
+ return TEST_int_eq(SSL_get0_verified_chain(ssl) != NULL, populated)
+ && TEST_str_eq(SSL_get0_peername(ssl), populated ? "server.example" : NULL)
+ && TEST_int_eq(sc->dane.mdpth, populated ? 0 : -1)
+ && TEST_int_eq(sc->dane.pdpth, populated ? 0 : -1)
+ && TEST_int_eq(sc->dane.mcert != NULL, populated)
+ && TEST_int_eq(sc->dane.mtlsa != NULL, populated)
+ && (populated || TEST_int_eq(SSL_get0_dane_authority(ssl, NULL, NULL), -1));
+}
+
+/*
+ * Cases 0..3 accept empty X.509/RPK with stateful/stateless refreshed tickets;
+ * 4..5 require a credential; 6..7 repeat optional stateful PHA with DTLS.
+ */
+static int test_pha_empty_client_certificate(int idx)
{
PHA_VERIFY_DATA verify_data = { 0 };
SSL_CTX *sctx = NULL, *cctx = NULL;
SSL *serverssl = NULL, *clientssl = NULL;
SSL *resume_serverssl = NULL, *resume_clientssl = NULL;
SSL_CONNECTION *servercon;
- SSL_SESSION *ticket = NULL, *old_session = NULL, *fresh_ticket = NULL;
+ SSL_SESSION *old_session = NULL, *fresh_ticket = NULL;
+ SSL_SESSION *previous_session = NULL, *previous_snapshot = NULL;
STACK_OF(X509) *old_chain;
X509 *old_peer;
- long old_verify_result;
- int ret, testresult = 0;
+ EVP_PKEY *old_rpk;
+ int use_rpk = idx & 1;
+ int required = idx == 4 || idx == 5;
+ int testdtls = idx >= 6;
+ int attempt, i, ret, testresult = 0;
- verify_data.initial_error = X509_V_ERR_APPLICATION_VERIFICATION;
- if (!setup_pha_resumption(&verify_data, 1, 0, &sctx, &cctx,
- &serverssl, &clientssl, &ticket)
+#ifdef OSSL_NO_USABLE_TLS1_3
+ if (!testdtls)
+ return TEST_skip("TLS 1.3 is disabled");
+#endif
+#ifdef OSSL_NO_USABLE_DTLS1_3
+ if (testdtls)
+ return TEST_skip("DTLS 1.3 is disabled");
+#endif
+ verify_data.initial_error = idx < 2 ? X509_V_OK
+ : X509_V_ERR_APPLICATION_VERIFICATION;
+ if (!setup_pha(&verify_data, 1, use_rpk, testdtls, &sctx, &cctx,
+ &serverssl, &clientssl)
|| !TEST_ptr(old_session = SSL_get1_session(serverssl))
- || !TEST_ptr(old_session->owner)
- || !TEST_ptr(old_peer = old_session->peer))
+ || !TEST_ptr(old_session->owner))
goto end;
+ old_peer = old_session->peer;
+ old_rpk = old_session->peer_rpk;
old_chain = old_session->peer_chain;
- old_verify_result = old_session->verify_result;
+ verify_data.expected_cert = old_peer;
+ verify_data.expected_rpk = old_rpk;
servercon = SSL_CONNECTION_FROM_SSL(serverssl);
- if (!TEST_long_eq(old_verify_result,
- X509_V_ERR_APPLICATION_VERIFICATION)
- || !TEST_long_eq(SSL_get_verify_result(serverssl),
- old_verify_result)
- || !seed_pha_verification_state(serverssl)
- || !TEST_ptr(SSL_get0_verified_chain(serverssl))
- || !TEST_str_eq(SSL_get0_peername(serverssl), "server.example")
- || !TEST_int_eq(servercon->dane.mdpth, 0)
- || !TEST_int_eq(servercon->dane.pdpth, 0)
- || !TEST_ptr(servercon->dane.mcert)
- || !TEST_ptr(servercon->dane.mtlsa))
+ if (use_rpk) {
+ if (!TEST_ptr(old_rpk) || !TEST_ptr_null(old_peer))
+ goto end;
+ } else if (!TEST_ptr(old_peer) || !TEST_ptr_null(old_rpk)
+ || !seed_pha_dane(serverssl)
+ || !check_pha_verification_state(serverssl, 1)) {
goto end;
-
+ }
+ SSL_certs_clear(clientssl);
+ if (idx == 2 || idx == 3) {
+ SSL_CTX_clear_options(sctx, SSL_OP_NO_TICKET);
+ SSL_clear_options(serverssl, SSL_OP_NO_TICKET);
+ }
+ verify_data.pha = 1;
SSL_set_verify(serverssl,
- SSL_VERIFY_PEER
- | (required ? SSL_VERIFY_FAIL_IF_NO_PEER_CERT : 0),
+ SSL_VERIFY_PEER | (required ? SSL_VERIFY_FAIL_IF_NO_PEER_CERT : 0),
NULL);
- if (!TEST_true(SSL_verify_client_post_handshake(serverssl))
- || !TEST_int_eq(SSL_do_handshake(serverssl), 1)
- || !TEST_int_le(SSL_read(clientssl, NULL, 0), 0))
- goto end;
- ret = SSL_read(serverssl, NULL, 0);
- if (!TEST_int_le(ret, 0))
- goto end;
- if (required) {
- if (!TEST_int_eq(SSL_get_error(serverssl, ret), SSL_ERROR_SSL)
- || !TEST_ptr_eq(SSL_get_session(serverssl), old_session)
- || !TEST_ptr_eq(SSL_get0_peer_certificate(serverssl), old_peer)
- || !TEST_ptr_eq(SSL_get_peer_cert_chain(serverssl), old_chain)
- || !TEST_ptr_eq(old_session->peer, old_peer)
- || !TEST_ptr_eq(old_session->peer_chain, old_chain)
- || !TEST_long_eq(old_session->verify_result,
- old_verify_result)
- || !TEST_ptr(SSL_get0_verified_chain(serverssl))
- || !TEST_long_eq(SSL_get_verify_result(serverssl),
- old_verify_result)
- || !TEST_str_eq(SSL_get0_peername(serverssl),
- "server.example")
- || !TEST_int_eq(servercon->dane.mdpth, 0)
- || !TEST_int_eq(servercon->dane.pdpth, 0)
- || !TEST_ptr(servercon->dane.mcert)
- || !TEST_ptr(servercon->dane.mtlsa)
- || !TEST_ptr_null(servercon->s3.tmp.pending_peer_chain))
+ /* Repeated empty PHA preserves the previous identity and verification state. */
+ for (attempt = 0; attempt < (required ? 1 : 2); attempt++) {
+ SSL_SESSION_free(previous_session);
+ SSL_SESSION_free(previous_snapshot);
+ previous_snapshot = NULL;
+ previous_session = SSL_get1_session(serverssl);
+ if (!TEST_ptr(previous_session)
+ || !TEST_ptr(previous_snapshot = SSL_SESSION_dup(previous_session))
+ || !TEST_long_eq(previous_session->verify_result, verify_data.initial_error)
+ || !TEST_long_eq(SSL_get_verify_result(serverssl), verify_data.initial_error)
+ || !TEST_true(SSL_verify_client_post_handshake(serverssl))
+ || !TEST_int_eq(SSL_do_handshake(serverssl), 1)
+ || !TEST_int_le(SSL_read(clientssl, NULL, 0), 0))
+ goto end;
+ ret = SSL_read(serverssl, NULL, 0);
+ if (!TEST_int_le(ret, 0))
+ goto end;
+
+ if (required) {
+ if (!TEST_int_eq(SSL_get_error(serverssl, ret), SSL_ERROR_SSL)
+ || !TEST_int_eq(ERR_GET_REASON(ERR_peek_last_error()),
+ SSL_R_PEER_DID_NOT_RETURN_A_CERTIFICATE)
+ || !TEST_ptr_eq(SSL_get_session(serverssl), old_session)
+ || !TEST_ptr_eq(SSL_get0_peer_certificate(serverssl), old_peer)
+ || !TEST_ptr_eq(SSL_get0_peer_rpk(serverssl), old_rpk)
+ || !TEST_ptr_eq(SSL_get_peer_cert_chain(serverssl), old_chain)
+ || !TEST_long_eq(SSL_get_verify_result(serverssl), verify_data.initial_error))
+ goto end;
+ } else if (!TEST_true(create_ssl_connection(serverssl, clientssl,
+ SSL_ERROR_NONE))
+ || !TEST_ptr_ne(SSL_get_session(serverssl), previous_session)
+ || !check_pha_peer_identity(serverssl, &verify_data)
+ || !TEST_long_eq(SSL_get_verify_result(serverssl), verify_data.initial_error)
+ || !TEST_long_eq(servercon->session->verify_result, verify_data.initial_error)) {
goto end;
- } else {
- if (!TEST_true(create_ssl_connection(serverssl, clientssl,
- SSL_ERROR_NONE))
- || !TEST_ptr_ne(SSL_get_session(serverssl), old_session)
- || !TEST_ptr_null(SSL_get0_peer_certificate(serverssl))
- || !TEST_ptr_null(SSL_get_peer_cert_chain(serverssl))
- || !TEST_ptr_null(SSL_get0_verified_chain(serverssl))
- || !TEST_long_eq(SSL_get_verify_result(serverssl), X509_V_OK)
- || !TEST_ptr_null(SSL_get0_peername(serverssl))
- || !TEST_int_eq(servercon->dane.mdpth, -1)
- || !TEST_int_eq(servercon->dane.pdpth, -1)
- || !TEST_ptr_null(servercon->dane.mcert)
- || !TEST_ptr_null(servercon->dane.mtlsa)
- || !TEST_int_eq(
- SSL_get0_dane_authority(serverssl, NULL, NULL), -1)
- || !TEST_ptr_null(servercon->session->peer_rpk)
- || !TEST_long_eq(servercon->session->verify_result, X509_V_OK)
+ }
+ if ((!use_rpk && !check_pha_verification_state(serverssl, 1))
+ || !TEST_int_eq(sk_X509_num(SSL_get_peer_cert_chain(serverssl)),
+ sk_X509_num(old_chain)))
+ goto end;
+ for (i = 0; i < sk_X509_num(old_chain); i++) {
+ if (!TEST_ptr_eq(sk_X509_value(SSL_get_peer_cert_chain(serverssl), i),
+ sk_X509_value(old_chain, i)))
+ goto end;
+ }
+ if (!TEST_int_eq(verify_data.calls, 0)
+ || !TEST_ptr_null(servercon->s3.tmp.pending_peer_chain)
+ || !TEST_ptr_null(servercon->s3.tmp.pending_peer_rpk)
|| !TEST_ptr_eq(old_session->peer, old_peer)
+ || !TEST_ptr_eq(old_session->peer_rpk, old_rpk)
|| !TEST_ptr_eq(old_session->peer_chain, old_chain)
- || !TEST_long_eq(old_session->verify_result,
- old_verify_result)
- || !TEST_ptr(fresh_ticket = SSL_get1_session(clientssl))
+ || !TEST_long_eq(old_session->verify_result, verify_data.initial_error)
+ || !TEST_ptr_eq(previous_session->peer, old_peer)
+ || !TEST_ptr_eq(previous_session->peer_rpk, old_rpk)
+ || !TEST_long_eq(previous_session->verify_result, verify_data.initial_error)
+ || !TEST_mem_eq(previous_session->master_key,
+ previous_session->master_key_length,
+ previous_snapshot->master_key, previous_snapshot->master_key_length)
+ || !TEST_mem_eq(previous_session->session_id,
+ previous_session->session_id_length,
+ previous_snapshot->session_id, previous_snapshot->session_id_length))
+ goto end;
+ }
+
+ if (!required
+ && (!TEST_ptr(fresh_ticket = SSL_get1_session(clientssl))
|| !resume_pha_ticket(sctx, cctx, fresh_ticket,
&resume_serverssl, &resume_clientssl)
- || !TEST_ptr_null(
- SSL_get0_peer_certificate(resume_serverssl))
- || !TEST_ptr_null(SSL_get_peer_cert_chain(resume_serverssl))
+ || !check_pha_peer_identity(resume_serverssl, &verify_data)
|| !TEST_long_eq(SSL_get_verify_result(resume_serverssl),
- X509_V_OK)
- || !TEST_ptr_null(SSL_get0_peername(resume_serverssl)))
+ verify_data.initial_error)
+ || !TEST_ptr_null(SSL_get0_peername(resume_serverssl))
+ || !TEST_int_eq(verify_data.calls, 0)))
+ goto end;
+
+ if (idx < 2) {
+ /*
+ * Check the read transition directly: the retained credential must not
+ * allow CertificateVerify after an empty Certificate response.
+ */
+ servercon->statem.hand_state = TLS_ST_SR_CERT;
+ if (!TEST_false(ossl_statem_server_read_transition(servercon,
+ SSL3_MT_CERTIFICATE_VERIFY))
+ || !TEST_int_eq(ERR_GET_REASON(ERR_peek_last_error()),
+ SSL_R_UNEXPECTED_MESSAGE))
+ goto end;
+ } else if (!required) {
+ /* A later nonempty response must verify afresh and require CertificateVerify. */
+ if (!TEST_int_eq(SSL_use_certificate_file(clientssl, cert, SSL_FILETYPE_PEM), 1)
+ || !TEST_int_eq(SSL_use_PrivateKey_file(clientssl, privkey,
+ SSL_FILETYPE_PEM),
+ 1))
+ goto end;
+ verify_data.accept = 1;
+ verify_data.accepted_error = X509_V_ERR_CERT_REVOKED;
+ if (!start_pha_and_pause(serverssl, clientssl)
+ || !check_pha_verification_state(serverssl, 0)
+ || !TEST_true(create_ssl_connection(serverssl, clientssl, SSL_ERROR_NONE))
+ || !TEST_int_eq(verify_data.calls, 2)
+ || !TEST_true(verify_data.candidate_ok)
+ || !check_pha_peer_identity(serverssl, &verify_data)
+ || !TEST_long_eq(SSL_get_verify_result(serverssl), verify_data.accepted_error)
+ || !TEST_long_eq(servercon->session->verify_result, verify_data.accepted_error))
goto end;
}
testresult = 1;
-
end:
- SSL_SESSION_free(ticket);
SSL_SESSION_free(old_session);
SSL_SESSION_free(fresh_ticket);
+ SSL_SESSION_free(previous_session);
+ SSL_SESSION_free(previous_snapshot);
SSL_free(resume_serverssl);
SSL_free(resume_clientssl);
SSL_free(serverssl);
@@ -1598,170 +1743,114 @@ end:
return testresult;
}
-static int test_pha_empty_client_rpk(int required)
+/*
+ * Empty PHA must also retain results supplied through an external PSK session.
+ * Cover TLS/DTLS with X.509/RPK responses, then supply a fresh credential.
+ */
+static int test_pha_psk(int idx)
{
+ static const unsigned char cert_type_rpk[] = { TLSEXT_cert_type_rpk };
+ static const unsigned char sid_ctx[] = { 1 };
+ static const long results[] = {
+ X509_V_OK, X509_V_ERR_UNSPECIFIED,
+ X509_V_ERR_APPLICATION_VERIFICATION, X509_V_OK
+ };
PHA_VERIFY_DATA verify_data = { 0 };
SSL_CTX *sctx = NULL, *cctx = NULL;
SSL *serverssl = NULL, *clientssl = NULL;
- SSL *resume_serverssl = NULL, *resume_clientssl = NULL;
- SSL_CONNECTION *servercon;
- SSL_SESSION *ticket = NULL, *old_session = NULL, *fresh_ticket = NULL;
- EVP_PKEY *old_rpk;
- long old_verify_result;
- int ret, testresult = 0;
+ SSL_SESSION *old_session = NULL;
+ int use_rpk = idx & 1;
+ int testdtls = idx >= 2;
+ int version = testdtls ? DTLS1_3_VERSION : TLS1_3_VERSION;
+ int attempt, testresult = 0;
- if (!setup_pha_resumption(&verify_data, 1, 1, &sctx, &cctx,
- &serverssl, &clientssl, &ticket)
- || !TEST_ptr(old_session = SSL_get1_session(serverssl))
- || !TEST_ptr(old_session->owner)
- || !TEST_ptr(old_rpk = old_session->peer_rpk)
- || !TEST_ptr_eq(SSL_get0_peer_rpk(serverssl), old_rpk))
+#ifdef OSSL_NO_USABLE_TLS1_3
+ if (!testdtls)
+ return TEST_skip("TLS 1.3 is disabled");
+#endif
+#ifdef OSSL_NO_USABLE_DTLS1_3
+ if (testdtls)
+ return TEST_skip("DTLS 1.3 is disabled");
+#endif
+ if (!TEST_true(create_ssl_ctx_pair(libctx,
+ testdtls ? DTLS_server_method() : TLS_server_method(),
+ testdtls ? DTLS_client_method() : TLS_client_method(), version, version,
+ &sctx, &cctx, NULL, NULL))
+ || !TEST_true(SSL_CTX_set_num_tickets(sctx, 1))
+ || !TEST_true(SSL_CTX_set_session_id_context(sctx, sid_ctx, sizeof(sid_ctx)))
+ || !TEST_true(SSL_CTX_set_ciphersuites(sctx, "TLS_AES_128_GCM_SHA256"))
+ || !TEST_true(SSL_CTX_set_ciphersuites(cctx, "TLS_AES_128_GCM_SHA256")))
goto end;
- old_verify_result = old_session->verify_result;
- servercon = SSL_CONNECTION_FROM_SSL(serverssl);
-
- SSL_set_verify(serverssl,
- SSL_VERIFY_PEER
- | (required ? SSL_VERIFY_FAIL_IF_NO_PEER_CERT : 0),
- NULL);
- if (!TEST_true(SSL_verify_client_post_handshake(serverssl))
- || !TEST_int_eq(SSL_do_handshake(serverssl), 1)
- || !TEST_int_le(SSL_read(clientssl, NULL, 0), 0))
+ if (use_rpk
+ && (!TEST_true(SSL_CTX_set1_client_cert_type(sctx, cert_type_rpk,
+ sizeof(cert_type_rpk)))
+ || !TEST_true(SSL_CTX_set1_client_cert_type(cctx, cert_type_rpk,
+ sizeof(cert_type_rpk)))))
goto end;
- ret = SSL_read(serverssl, NULL, 0);
- if (!TEST_int_le(ret, 0))
+ SSL_CTX_set_psk_use_session_callback(cctx, use_session_cb);
+ SSL_CTX_set_psk_find_session_callback(sctx, find_session_cb);
+ use_session_cb_cnt = find_session_cb_cnt = 0;
+ srvid = pskid;
+ verify_data.pha = verify_data.accept = 1;
+ verify_data.accepted_error = X509_V_ERR_CERT_REVOKED;
+ SSL_CTX_set_cert_verify_callback(sctx, pha_verify_retry_cb, &verify_data);
+ if (!TEST_true(create_ssl_objects(sctx, cctx, &serverssl, &clientssl, NULL, NULL))
+ || !TEST_ptr(clientpsk = create_a_psk(clientssl, SHA256_DIGEST_LENGTH))
+ || !TEST_true(SSL_SESSION_up_ref(clientpsk)))
goto end;
-
- if (required) {
- if (!TEST_int_eq(SSL_get_error(serverssl, ret), SSL_ERROR_SSL)
- || !TEST_ptr_eq(SSL_get_session(serverssl), old_session)
- || !TEST_ptr_eq(SSL_get0_peer_rpk(serverssl), old_rpk)
+ serverpsk = clientpsk;
+ if (!TEST_true(SSL_SESSION_set_protocol_version(clientpsk, version)))
+ goto end;
+ /* Explicit fixture values: PSK sessions need not all start with X509_V_OK. */
+ serverpsk->verify_result = results[idx];
+ SSL_set_post_handshake_auth(clientssl, 1);
+ if (!TEST_true(create_ssl_connection(serverssl, clientssl, SSL_ERROR_NONE))
+ || !TEST_ptr(old_session = SSL_get1_session(serverssl))
+ || !TEST_long_eq(SSL_get_verify_result(serverssl), results[idx]))
+ goto end;
+ SSL_set_verify(serverssl, SSL_VERIFY_PEER, NULL);
+ for (attempt = 0; attempt < 2; attempt++) {
+ if (!TEST_true(SSL_verify_client_post_handshake(serverssl))
+ || !TEST_int_eq(SSL_do_handshake(serverssl), 1)
+ || !TEST_int_le(SSL_read(clientssl, NULL, 0), 0)
+ || !TEST_int_le(SSL_read(serverssl, NULL, 0), 0)
+ || !TEST_true(create_ssl_connection(serverssl, clientssl, SSL_ERROR_NONE))
|| !TEST_ptr_null(SSL_get0_peer_certificate(serverssl))
- || !TEST_ptr_null(SSL_get_peer_cert_chain(serverssl))
- || !TEST_ptr_eq(old_session->peer_rpk, old_rpk)
- || !TEST_long_eq(old_session->verify_result,
- old_verify_result)
- || !TEST_ptr_null(servercon->s3.tmp.pending_peer_rpk))
- goto end;
- } else {
- if (!TEST_true(create_ssl_connection(serverssl, clientssl,
- SSL_ERROR_NONE))
- || !TEST_ptr_ne(SSL_get_session(serverssl), old_session)
|| !TEST_ptr_null(SSL_get0_peer_rpk(serverssl))
- || !TEST_ptr_null(SSL_get0_peer_certificate(serverssl))
- || !TEST_ptr_null(SSL_get_peer_cert_chain(serverssl))
- || !TEST_ptr_null(SSL_get0_verified_chain(serverssl))
- || !TEST_long_eq(SSL_get_verify_result(serverssl), X509_V_OK)
- || !TEST_ptr_null(SSL_get0_peername(serverssl))
- || !TEST_long_eq(servercon->session->verify_result, X509_V_OK)
- || !TEST_ptr_eq(old_session->peer_rpk, old_rpk)
- || !TEST_long_eq(old_session->verify_result,
- old_verify_result)
- || !TEST_ptr(fresh_ticket = SSL_get1_session(clientssl))
- || !resume_pha_ticket(sctx, cctx, fresh_ticket,
- &resume_serverssl, &resume_clientssl)
- || !TEST_ptr_null(SSL_get0_peer_rpk(resume_serverssl))
- || !TEST_ptr_null(
- SSL_get0_peer_certificate(resume_serverssl))
- || !TEST_ptr_null(SSL_get_peer_cert_chain(resume_serverssl))
- || !TEST_long_eq(SSL_get_verify_result(resume_serverssl),
- X509_V_OK)
- || !TEST_ptr_null(SSL_get0_peername(resume_serverssl)))
+ || !TEST_long_eq(SSL_get_verify_result(serverssl), results[idx])
+ || !TEST_long_eq(SSL_get_session(serverssl)->verify_result, results[idx])
+ || !TEST_int_eq(verify_data.calls, 0))
goto end;
}
-
- testresult = 1;
-
-end:
- SSL_SESSION_free(ticket);
- SSL_SESSION_free(old_session);
- SSL_SESSION_free(fresh_ticket);
- SSL_free(resume_serverssl);
- SSL_free(resume_clientssl);
- SSL_free(serverssl);
- SSL_free(clientssl);
- SSL_CTX_free(sctx);
- SSL_CTX_free(cctx);
- return testresult;
-}
-
-static int test_pha_rejected_rpk_session(void)
-{
- char *leaf = test_mk_file_path(certsdir, "leaf.pem");
- char *leaf_key = test_mk_file_path(certsdir, "leaf.key");
- PHA_VERIFY_DATA verify_data = { 0 };
- SSL_CTX *sctx = NULL, *cctx = NULL;
- SSL *serverssl = NULL, *clientssl = NULL;
- SSL *resume_serverssl = NULL, *resume_clientssl = NULL;
- SSL_CONNECTION *servercon;
- SSL_SESSION *ticket = NULL, *old_session = NULL;
- int ret, testresult = 0;
-
- if (!TEST_ptr(leaf) || !TEST_ptr(leaf_key)
- || !setup_pha_resumption(&verify_data, 0, 1, &sctx, &cctx,
- &serverssl, &clientssl, &ticket)
- || !TEST_ptr(old_session = SSL_get1_session(serverssl))
- || !TEST_ptr(old_session->owner)
- || !TEST_ptr_null(old_session->peer_rpk)
- || !TEST_ptr_null(SSL_get0_peer_rpk(serverssl)))
- goto end;
-
- if (!TEST_int_eq(SSL_use_certificate_file(clientssl, leaf,
- SSL_FILETYPE_PEM),
- 1)
- || !TEST_int_eq(SSL_use_PrivateKey_file(clientssl, leaf_key,
- SSL_FILETYPE_PEM),
- 1)
- || !TEST_int_eq(SSL_check_private_key(clientssl), 1))
- goto end;
-
- verify_data.pha = 1;
- verify_data.expected_rpk = X509_get0_pubkey(SSL_get_certificate(clientssl));
- SSL_set_verify(serverssl, SSL_VERIFY_PEER, NULL);
- if (!TEST_ptr(verify_data.expected_rpk)
- || !start_pha_and_pause(serverssl, clientssl))
- goto end;
-
- servercon = SSL_CONNECTION_FROM_SSL(serverssl);
- /*
- * Resume the ticket while verification is paused. The pending key must not
- * be visible through the already-published session.
- */
- if (!TEST_int_eq(verify_data.calls, 1)
- || !TEST_true(verify_data.candidate_ok)
- || !TEST_ptr_eq(SSL_get_session(serverssl), old_session)
- || !TEST_ptr_null(old_session->peer_rpk)
- || !TEST_ptr_null(SSL_get0_peer_rpk(serverssl))
- || !TEST_ptr(servercon->s3.tmp.pending_peer_rpk)
- || !resume_pha_ticket(sctx, cctx, ticket, &resume_serverssl,
- &resume_clientssl)
- || !TEST_int_eq(verify_data.calls, 1)
- || !TEST_ptr_null(SSL_get0_peer_rpk(resume_serverssl)))
+ if (!TEST_int_eq(SSL_use_certificate_file(clientssl, cert, SSL_FILETYPE_PEM), 1)
+ || !TEST_int_eq(SSL_use_PrivateKey_file(clientssl, privkey, SSL_FILETYPE_PEM), 1))
goto end;
-
- ret = SSL_read(serverssl, NULL, 0);
- if (!TEST_int_le(ret, 0)
- || !TEST_int_eq(SSL_get_error(serverssl, ret), SSL_ERROR_SSL)
+ if (use_rpk)
+ verify_data.expected_rpk = X509_get0_pubkey(SSL_get_certificate(clientssl));
+ else
+ verify_data.expected_cert = SSL_get_certificate(clientssl);
+ if (!start_pha_and_pause(serverssl, clientssl)
+ || !TEST_true(create_ssl_connection(serverssl, clientssl, SSL_ERROR_NONE))
|| !TEST_int_eq(verify_data.calls, 2)
- || !TEST_ptr_eq(SSL_get_session(serverssl), old_session)
+ || !TEST_true(verify_data.candidate_ok)
+ || !check_pha_peer_identity(serverssl, &verify_data)
+ || !TEST_long_eq(SSL_get_verify_result(serverssl), verify_data.accepted_error)
+ || !TEST_long_eq(SSL_get_session(serverssl)->verify_result,
+ verify_data.accepted_error)
+ || !TEST_ptr_null(old_session->peer)
|| !TEST_ptr_null(old_session->peer_rpk)
- || !TEST_ptr_null(SSL_get0_peer_rpk(serverssl))
- || !TEST_ptr_null(servercon->s3.tmp.pending_peer_rpk))
+ || !TEST_long_eq(old_session->verify_result, results[idx]))
goto end;
-
testresult = 1;
-
end:
- SSL_SESSION_free(ticket);
SSL_SESSION_free(old_session);
- SSL_free(resume_serverssl);
- SSL_free(resume_clientssl);
SSL_free(serverssl);
SSL_free(clientssl);
SSL_CTX_free(sctx);
SSL_CTX_free(cctx);
- OPENSSL_free(leaf);
- OPENSSL_free(leaf_key);
+ SSL_SESSION_free(clientpsk);
+ SSL_SESSION_free(serverpsk);
+ clientpsk = serverpsk = NULL;
return testresult;
}
#endif
@@ -18779,11 +18868,13 @@ int setup_tests(void)
#endif
ADD_TEST(test_ssl_build_cert_chain);
ADD_TEST(test_ssl_ctx_build_cert_chain);
-#ifndef OSSL_NO_USABLE_TLS1_3
- ADD_TEST(test_pha_x509_pending_identity);
- ADD_ALL_TESTS(test_pha_empty_client_certificate, 2);
- ADD_ALL_TESTS(test_pha_empty_client_rpk, 2);
- ADD_TEST(test_pha_rejected_rpk_session);
+#if !defined(OPENSSL_NO_TLS1_2) || !defined(OSSL_NO_USABLE_TLS1_3)
+ ADD_ALL_TESTS(test_empty_client_certificate, OSSL_NELEM(empty_client_cert_tests));
+#endif
+#if !defined(OSSL_NO_USABLE_TLS1_3) || !defined(OSSL_NO_USABLE_DTLS1_3)
+ ADD_ALL_TESTS(test_pha_pending_identity, 7);
+ ADD_ALL_TESTS(test_pha_empty_client_certificate, 8);
+ ADD_ALL_TESTS(test_pha_psk, 4);
#endif
#ifndef OPENSSL_NO_TLS1_2
ADD_TEST(test_client_hello_cb);