TLS client: allow cert verify callback return -1 for SSL_ERROR_WANT_RETRY_VERIFY

The client-side cert verification callback function may not only return
as usual for success or 0 for failure, but also -1,
typically on failure verifying the server certificate.
This makes the handshake suspend and return control to the calling application
with SSL_ERROR_WANT_RETRY_VERIFY.
The app can for instance fetch further certificates or cert status information
needed for the verification.
Calling SSL_connect() again resumes the connection attempt
by retrying the server certificate verification step.
This process may even be repeated if need be.

The core implementation of the feature is in ssl/statem/statem_clnt.c,
splitting tls_process_server_certificate() into a preparation step
that just copies the certificates received from the server to s->session->peer_chain
(rather than having them in a local variable at first) and returns to the state machine,
and a post-processing step in tls_post_process_server_certificate() that can be repeated:
Try verifying the current contents of s->session->peer_chain basically as before,
but give the verification callback function the chance to pause connecting and
make the TLS state machine later call tls_post_process_server_certificate() again.
Otherwise processing continues as usual.

The documentation of the new feature is added to SSL_CTX_set_cert_verify_callback.pod
and SSL_want.pod.

This adds two tests:
* A generic test in test/helpers/handshake.c
  on the usability of the new server cert verification retry feature.
  It is triggered via test/ssl-tests/03-custom_verify.cnf.in (while the bulky auto-
  generated changes to test/ssl-tests/03-custom_verify.cnf can be basically ignored).
* A test in test/sslapitest.c that demonstrates the effectiveness of the approach
  for augmenting the cert chain provided by the server in between SSL_connect() calls.

Reviewed-by: Matt Caswell <matt@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/13906)
This commit is contained in:
Dr. David von Oheimb 2021-01-16 20:43:00 +01:00
parent 1395a84e48
commit 0c3eb2793b
14 changed files with 296 additions and 112 deletions

View File

@ -33,7 +33,19 @@ argument I<arg> is specified by the application when setting I<callback>.
I<callback> should return 1 to indicate verification success and 0 to
indicate verification failure. If SSL_VERIFY_PEER is set and I<callback>
returns 0, the handshake will fail. As the verification procedure may
returns 0, the handshake will fail.
In client mode I<callback> may also return -1,
typically on failure verifying the server certificate.
This makes the handshake suspend and return control to the calling application
with B<SSL_ERROR_WANT_RETRY_VERIFY>.
The app can for instance fetch further certificates or cert status information
needed for the verification.
Calling L<SSL_connect(3)> again resumes the connection attempt
by retrying the server certificate verification step.
This process may even be repeated if need be.
As the verification procedure may
allow the connection to continue in the case of failure (by always
returning 1) the verification result must be set in any case using the
B<error> member of I<x509_store_ctx> so that the calling application

View File

@ -2,9 +2,9 @@
=head1 NAME
SSL_want, SSL_want_nothing, SSL_want_read, SSL_want_write, SSL_want_x509_lookup,
SSL_want_async, SSL_want_async_job, SSL_want_client_hello_cb - obtain state
information TLS/SSL I/O operation
SSL_want, SSL_want_nothing, SSL_want_read, SSL_want_write,
SSL_want_x509_lookup, SSL_want_retry_verify, SSL_want_async, SSL_want_async_job,
SSL_want_client_hello_cb - obtain state information TLS/SSL I/O operation
=head1 SYNOPSIS
@ -15,6 +15,7 @@ information TLS/SSL I/O operation
int SSL_want_read(const SSL *ssl);
int SSL_want_write(const SSL *ssl);
int SSL_want_x509_lookup(const SSL *ssl);
int SSL_want_retry_verify(const SSL *ssl);
int SSL_want_async(const SSL *ssl);
int SSL_want_async_job(const SSL *ssl);
int SSL_want_client_hello_cb(const SSL *ssl);
@ -53,47 +54,50 @@ There is no data to be written or to be read.
There are data in the SSL buffer that must be written to the underlying
B<BIO> layer in order to complete the actual SSL_*() operation.
A call to L<SSL_get_error(3)> should return
SSL_ERROR_WANT_WRITE.
A call to L<SSL_get_error(3)> should return B<SSL_ERROR_WANT_WRITE>.
=item SSL_READING
More data must be read from the underlying B<BIO> layer in order to
complete the actual SSL_*() operation.
A call to L<SSL_get_error(3)> should return
SSL_ERROR_WANT_READ.
A call to L<SSL_get_error(3)> should return B<SSL_ERROR_WANT_READ>.
=item SSL_X509_LOOKUP
The operation did not complete because an application callback set by
SSL_CTX_set_client_cert_cb() has asked to be called again.
A call to L<SSL_get_error(3)> should return
SSL_ERROR_WANT_X509_LOOKUP.
A call to L<SSL_get_error(3)> should return B<SSL_ERROR_WANT_X509_LOOKUP>.
=item SSL_RETRY_VERIFY
The operation did not complete because an application callback set by
SSL_CTX_set_cert_verify_callback() has asked to be called again.
A call to L<SSL_get_error(3)> should return B<SSL_ERROR_WANT_RETRY_VERIFY>.
=item SSL_ASYNC_PAUSED
An asynchronous operation partially completed and was then paused. See
L<SSL_get_all_async_fds(3)>. A call to L<SSL_get_error(3)> should return
SSL_ERROR_WANT_ASYNC.
B<SSL_ERROR_WANT_ASYNC>.
=item SSL_ASYNC_NO_JOBS
The asynchronous job could not be started because there were no async jobs
available in the pool (see ASYNC_init_thread(3)). A call to L<SSL_get_error(3)>
should return SSL_ERROR_WANT_ASYNC_JOB.
should return B<SSL_ERROR_WANT_ASYNC_JOB>.
=item SSL_CLIENT_HELLO_CB
The operation did not complete because an application callback set by
SSL_CTX_set_client_hello_cb() has asked to be called again.
A call to L<SSL_get_error(3)> should return
SSL_ERROR_WANT_CLIENT_HELLO_CB.
A call to L<SSL_get_error(3)> should return B<SSL_ERROR_WANT_CLIENT_HELLO_CB>.
=back
SSL_want_nothing(), SSL_want_read(), SSL_want_write(), SSL_want_x509_lookup(),
SSL_want_async(), SSL_want_async_job(), and SSL_want_client_hello_cb() return
1, when the corresponding condition is true or 0 otherwise.
SSL_want_nothing(), SSL_want_read(), SSL_want_write(),
SSL_want_x509_lookup(), SSL_want_retry_verify(),
SSL_want_async(), SSL_want_async_job(), and SSL_want_client_hello_cb()
return 1 when the corresponding condition is true or 0 otherwise.
=head1 SEE ALSO

View File

@ -927,12 +927,14 @@ __owur int SSL_extension_supported(unsigned int ext_type);
# define SSL_ASYNC_PAUSED 5
# define SSL_ASYNC_NO_JOBS 6
# define SSL_CLIENT_HELLO_CB 7
# define SSL_RETRY_VERIFY 8
/* These will only be used when doing non-blocking IO */
# define SSL_want_nothing(s) (SSL_want(s) == SSL_NOTHING)
# define SSL_want_read(s) (SSL_want(s) == SSL_READING)
# define SSL_want_write(s) (SSL_want(s) == SSL_WRITING)
# define SSL_want_x509_lookup(s) (SSL_want(s) == SSL_X509_LOOKUP)
# define SSL_want_retry_verify(s) (SSL_want(s) == SSL_RETRY_VERIFY)
# define SSL_want_async(s) (SSL_want(s) == SSL_ASYNC_PAUSED)
# define SSL_want_async_job(s) (SSL_want(s) == SSL_ASYNC_NO_JOBS)
# define SSL_want_client_hello_cb(s) (SSL_want(s) == SSL_CLIENT_HELLO_CB)
@ -1227,6 +1229,7 @@ DECLARE_PEM_rw(SSL_SESSION, SSL_SESSION)
# define SSL_ERROR_WANT_ASYNC 9
# define SSL_ERROR_WANT_ASYNC_JOB 10
# define SSL_ERROR_WANT_CLIENT_HELLO_CB 11
# define SSL_ERROR_WANT_RETRY_VERIFY 12
# ifndef OPENSSL_NO_DEPRECATED_3_0
# define SSL_CTRL_SET_TMP_DH 3

View File

@ -3808,6 +3808,8 @@ int SSL_get_error(const SSL *s, int i)
}
if (SSL_want_x509_lookup(s))
return SSL_ERROR_WANT_X509_LOOKUP;
if (SSL_want_retry_verify(s))
return SSL_ERROR_WANT_RETRY_VERIFY;
if (SSL_want_async(s))
return SSL_ERROR_WANT_ASYNC;
if (SSL_want_async_job(s))

View File

@ -1009,7 +1009,7 @@ size_t ossl_statem_client_max_message_size(SSL *s)
}
/*
* Process a message that the client has been received from the server.
* Process a message that the client has received from the server.
*/
MSG_PROCESS_RETURN ossl_statem_client_process_message(SSL *s, PACKET *pkt)
{
@ -1079,6 +1079,9 @@ WORK_STATE ossl_statem_client_post_process_message(SSL *s, WORK_STATE wst)
SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
return WORK_ERROR;
case TLS_ST_CR_CERT:
return tls_post_process_server_certificate(s, wst);
case TLS_ST_CR_CERT_VRFY:
case TLS_ST_CR_CERT_REQ:
return tls_prepare_client_certificate(s, wst);
@ -1762,20 +1765,16 @@ static MSG_PROCESS_RETURN tls_process_as_hello_retry_request(SSL *s,
return MSG_PROCESS_ERROR;
}
/* prepare server cert verificaton by setting s->session->peer_chain from pkt */
MSG_PROCESS_RETURN tls_process_server_certificate(SSL *s, PACKET *pkt)
{
int i;
MSG_PROCESS_RETURN ret = MSG_PROCESS_ERROR;
unsigned long cert_list_len, cert_len;
X509 *x = NULL;
const unsigned char *certstart, *certbytes;
STACK_OF(X509) *sk = NULL;
EVP_PKEY *pkey = NULL;
size_t chainidx, certidx;
size_t chainidx;
unsigned int context = 0;
const SSL_CERT_LOOKUP *clu;
if ((sk = sk_X509_new_null()) == NULL) {
if ((s->session->peer_chain = sk_X509_new_null()) == NULL) {
SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_MALLOC_FAILURE);
goto err;
}
@ -1834,14 +1833,39 @@ MSG_PROCESS_RETURN tls_process_server_certificate(SSL *s, PACKET *pkt)
OPENSSL_free(rawexts);
}
if (!sk_X509_push(sk, x)) {
if (!sk_X509_push(s->session->peer_chain, x)) {
SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_MALLOC_FAILURE);
goto err;
}
x = NULL;
}
return MSG_PROCESS_CONTINUE_PROCESSING;
i = ssl_verify_cert_chain(s, sk);
err:
X509_free(x);
sk_X509_pop_free(s->session->peer_chain, X509_free);
s->session->peer_chain = NULL;
return MSG_PROCESS_ERROR;
}
/*
* Verify the s->session->peer_chain and check server cert type.
* On success set s->session->peer and s->session->verify_result.
* Else the peer certificate verification callback may request retry.
*/
WORK_STATE tls_post_process_server_certificate(SSL *s, WORK_STATE wst)
{
X509 *x;
EVP_PKEY *pkey = NULL;
const SSL_CERT_LOOKUP *clu;
size_t certidx;
int i;
i = ssl_verify_cert_chain(s, s->session->peer_chain);
if (i == -1) {
s->rwstate = SSL_RETRY_VERIFY;
return WORK_MORE_A;
}
/*
* The documented interface is that SSL_VERIFY_PEER should be set in order
* for client side verification of the server certificate to take place.
@ -1859,35 +1883,31 @@ MSG_PROCESS_RETURN tls_process_server_certificate(SSL *s, PACKET *pkt)
if (s->verify_mode != SSL_VERIFY_NONE && i <= 0) {
SSLfatal(s, ssl_x509err2alert(s->verify_result),
SSL_R_CERTIFICATE_VERIFY_FAILED);
goto err;
return WORK_ERROR;
}
ERR_clear_error(); /* but we keep s->verify_result */
if (i > 1) {
SSLfatal(s, SSL_AD_HANDSHAKE_FAILURE, i);
goto err;
return WORK_ERROR;
}
s->session->peer_chain = sk;
/*
* Inconsistency alert: cert_chain does include the peer's certificate,
* which we don't include in statem_srvr.c
*/
x = sk_X509_value(sk, 0);
sk = NULL;
x = sk_X509_value(s->session->peer_chain, 0);
pkey = X509_get0_pubkey(x);
if (pkey == NULL || EVP_PKEY_missing_parameters(pkey)) {
x = NULL;
SSLfatal(s, SSL_AD_INTERNAL_ERROR,
SSL_R_UNABLE_TO_FIND_PUBLIC_KEY_PARAMETERS);
goto err;
return WORK_ERROR;
}
if ((clu = ssl_cert_lookup_by_pkey(pkey, &certidx)) == NULL) {
x = NULL;
SSLfatal(s, SSL_AD_ILLEGAL_PARAMETER, SSL_R_UNKNOWN_CERTIFICATE_TYPE);
goto err;
return WORK_ERROR;
}
/*
* Check certificate type is consistent with ciphersuite. For TLS 1.3
@ -1896,9 +1916,8 @@ MSG_PROCESS_RETURN tls_process_server_certificate(SSL *s, PACKET *pkt)
*/
if (!SSL_IS_TLS13(s)) {
if ((clu->amask & s->s3.tmp.new_cipher->algorithm_auth) == 0) {
x = NULL;
SSLfatal(s, SSL_AD_ILLEGAL_PARAMETER, SSL_R_WRONG_CERTIFICATE_TYPE);
goto err;
return WORK_ERROR;
}
}
s->session->peer_type = certidx;
@ -1907,7 +1926,6 @@ MSG_PROCESS_RETURN tls_process_server_certificate(SSL *s, PACKET *pkt)
X509_up_ref(x);
s->session->peer = x;
s->session->verify_result = s->verify_result;
x = NULL;
/* Save the current hash state for when we receive the CertificateVerify */
if (SSL_IS_TLS13(s)
@ -1915,15 +1933,9 @@ MSG_PROCESS_RETURN tls_process_server_certificate(SSL *s, PACKET *pkt)
sizeof(s->cert_verify_hash),
&s->cert_verify_hash_len)) {
/* SSLfatal() already called */;
goto err;
return WORK_ERROR;
}
ret = MSG_PROCESS_CONTINUE_READING;
err:
X509_free(x);
sk_X509_pop_free(sk, X509_free);
return ret;
return WORK_FINISHED_CONTINUE;
}
static int tls_process_ske_psk_preamble(SSL *s, PACKET *pkt)

View File

@ -129,6 +129,7 @@ __owur int tls_construct_cert_status_body(SSL *s, WPACKET *pkt);
__owur int tls_construct_cert_status(SSL *s, WPACKET *pkt);
__owur MSG_PROCESS_RETURN tls_process_key_exchange(SSL *s, PACKET *pkt);
__owur MSG_PROCESS_RETURN tls_process_server_certificate(SSL *s, PACKET *pkt);
__owur WORK_STATE tls_post_process_server_certificate(SSL *s, WORK_STATE wst);
__owur int ssl3_check_cert_and_algorithm(SSL *s);
#ifndef OPENSSL_NO_NEXTPROTONEG
__owur int tls_construct_next_proto(SSL *s, WPACKET *pkt);

View File

@ -314,6 +314,14 @@ static int verify_reject_cb(X509_STORE_CTX *ctx, void *arg) {
return 0;
}
static int n_retries = 0;
static int verify_retry_cb(X509_STORE_CTX *ctx, void *arg) {
if (--n_retries < 0)
return 1;
X509_STORE_CTX_set_error(ctx, X509_V_ERR_APPLICATION_VERIFICATION);
return -1;
}
static int verify_accept_cb(X509_STORE_CTX *ctx, void *arg) {
return 1;
}
@ -527,6 +535,10 @@ static int configure_handshake_ctx(SSL_CTX *server_ctx, SSL_CTX *server2_ctx,
case SSL_TEST_VERIFY_ACCEPT_ALL:
SSL_CTX_set_cert_verify_callback(client_ctx, &verify_accept_cb, NULL);
break;
case SSL_TEST_VERIFY_RETRY_ONCE:
n_retries = 1;
SSL_CTX_set_cert_verify_callback(client_ctx, &verify_retry_cb, NULL);
break;
case SSL_TEST_VERIFY_REJECT_ALL:
SSL_CTX_set_cert_verify_callback(client_ctx, &verify_reject_cb, NULL);
break;
@ -807,8 +819,10 @@ static void do_handshake_step(PEER *peer)
peer->status = PEER_ERROR;
} else {
int error = SSL_get_error(peer->ssl, ret);
/* Memory bios should never block with SSL_ERROR_WANT_WRITE. */
if (error != SSL_ERROR_WANT_READ)
if (error != SSL_ERROR_WANT_READ
&& error != SSL_ERROR_WANT_RETRY_VERIFY)
peer->status = PEER_ERROR;
}
}
@ -1674,6 +1688,10 @@ static HANDSHAKE_RESULT *do_handshake_internal(
ret->session_id = SSL_TEST_SESSION_ID_YES;
ret->session_ticket_do_not_call = server_ex_data.session_ticket_do_not_call;
if (extra->client.verify_callback == SSL_TEST_VERIFY_RETRY_ONCE
&& n_retries != -1)
ret->result = SSL_TEST_SERVER_FAIL;
#ifndef OPENSSL_NO_NEXTPROTONEG
SSL_get0_next_proto_negotiated(client.ssl, &proto, &proto_len);
ret->client_npn_negotiated = dup_str(proto, proto_len);

View File

@ -177,6 +177,7 @@ const char *ssl_protocol_name(int protocol)
static const test_enum ssl_verify_callbacks[] = {
{"None", SSL_TEST_VERIFY_NONE},
{"AcceptAll", SSL_TEST_VERIFY_ACCEPT_ALL},
{"RetryOnce", SSL_TEST_VERIFY_RETRY_ONCE},
{"RejectAll", SSL_TEST_VERIFY_REJECT_ALL},
};
@ -184,6 +185,7 @@ __owur static int parse_client_verify_callback(SSL_TEST_CLIENT_CONF *client_conf
const char *value)
{
int ret_value;
if (!parse_enum(ssl_verify_callbacks, OSSL_NELEM(ssl_verify_callbacks),
&ret_value, value)) {
return 0;

View File

@ -25,6 +25,7 @@ typedef enum {
typedef enum {
SSL_TEST_VERIFY_NONE = 0, /* Default */
SSL_TEST_VERIFY_ACCEPT_ALL,
SSL_TEST_VERIFY_RETRY_ONCE,
SSL_TEST_VERIFY_REJECT_ALL
} ssl_verify_callback_t;

View File

@ -157,12 +157,12 @@ sub test_conf {
"Getting output from generate_ssl_tests.pl.");
SKIP: {
# Test 2. Compare against existing output in test/ssl_tests.cnf.
# Test 2. Compare against existing output in test/ssl-tests/
skip "Skipping generated source test for $conf", 1
if !$check_source;
$run_test = is(cmp_text($output_file, $conf_file), 0,
"Comparing generated sources.");
"Comparing generated $output_file with $conf_file.");
}
# Test 3. Run the test.

View File

@ -1,16 +1,17 @@
# Generated with generate_ssl_tests.pl
num_tests = 9
num_tests = 10
test-0 = 0-verify-success
test-1 = 1-verify-custom-reject
test-2 = 2-verify-custom-allow
test-3 = 3-noverify-success
test-4 = 4-noverify-ignore-custom-reject
test-5 = 5-noverify-accept-custom-allow
test-6 = 6-verify-fail-no-root
test-7 = 7-verify-custom-success-no-root
test-8 = 8-verify-custom-fail-no-root
test-3 = 3-verify-custom-retry
test-4 = 4-noverify-success
test-5 = 5-noverify-ignore-custom-reject
test-6 = 6-noverify-accept-custom-allow
test-7 = 7-verify-fail-no-root
test-8 = 8-verify-custom-success-no-root
test-9 = 9-verify-custom-fail-no-root
# ===========================================================
[0-verify-success]
@ -91,148 +92,175 @@ VerifyCallback = AcceptAll
# ===========================================================
[3-noverify-success]
ssl_conf = 3-noverify-success-ssl
[3-verify-custom-retry]
ssl_conf = 3-verify-custom-retry-ssl
[3-noverify-success-ssl]
server = 3-noverify-success-server
client = 3-noverify-success-client
[3-verify-custom-retry-ssl]
server = 3-verify-custom-retry-server
client = 3-verify-custom-retry-client
[3-noverify-success-server]
[3-verify-custom-retry-server]
Certificate = ${ENV::TEST_CERTS_DIR}/servercert.pem
CipherString = DEFAULT
PrivateKey = ${ENV::TEST_CERTS_DIR}/serverkey.pem
[3-noverify-success-client]
[3-verify-custom-retry-client]
CipherString = DEFAULT
VerifyCAFile = ${ENV::TEST_CERTS_DIR}/rootcert.pem
VerifyMode = Peer
[test-3]
ExpectedResult = Success
client = 3-verify-custom-retry-client-extra
[3-verify-custom-retry-client-extra]
VerifyCallback = RetryOnce
# ===========================================================
[4-noverify-success]
ssl_conf = 4-noverify-success-ssl
[4-noverify-success-ssl]
server = 4-noverify-success-server
client = 4-noverify-success-client
[4-noverify-success-server]
Certificate = ${ENV::TEST_CERTS_DIR}/servercert.pem
CipherString = DEFAULT
PrivateKey = ${ENV::TEST_CERTS_DIR}/serverkey.pem
[4-noverify-success-client]
CipherString = DEFAULT
[test-4]
ExpectedResult = Success
# ===========================================================
[4-noverify-ignore-custom-reject]
ssl_conf = 4-noverify-ignore-custom-reject-ssl
[5-noverify-ignore-custom-reject]
ssl_conf = 5-noverify-ignore-custom-reject-ssl
[4-noverify-ignore-custom-reject-ssl]
server = 4-noverify-ignore-custom-reject-server
client = 4-noverify-ignore-custom-reject-client
[5-noverify-ignore-custom-reject-ssl]
server = 5-noverify-ignore-custom-reject-server
client = 5-noverify-ignore-custom-reject-client
[4-noverify-ignore-custom-reject-server]
[5-noverify-ignore-custom-reject-server]
Certificate = ${ENV::TEST_CERTS_DIR}/servercert.pem
CipherString = DEFAULT
PrivateKey = ${ENV::TEST_CERTS_DIR}/serverkey.pem
[4-noverify-ignore-custom-reject-client]
[5-noverify-ignore-custom-reject-client]
CipherString = DEFAULT
[test-4]
[test-5]
ExpectedResult = Success
client = 4-noverify-ignore-custom-reject-client-extra
client = 5-noverify-ignore-custom-reject-client-extra
[4-noverify-ignore-custom-reject-client-extra]
[5-noverify-ignore-custom-reject-client-extra]
VerifyCallback = RejectAll
# ===========================================================
[5-noverify-accept-custom-allow]
ssl_conf = 5-noverify-accept-custom-allow-ssl
[6-noverify-accept-custom-allow]
ssl_conf = 6-noverify-accept-custom-allow-ssl
[5-noverify-accept-custom-allow-ssl]
server = 5-noverify-accept-custom-allow-server
client = 5-noverify-accept-custom-allow-client
[6-noverify-accept-custom-allow-ssl]
server = 6-noverify-accept-custom-allow-server
client = 6-noverify-accept-custom-allow-client
[5-noverify-accept-custom-allow-server]
[6-noverify-accept-custom-allow-server]
Certificate = ${ENV::TEST_CERTS_DIR}/servercert.pem
CipherString = DEFAULT
PrivateKey = ${ENV::TEST_CERTS_DIR}/serverkey.pem
[5-noverify-accept-custom-allow-client]
[6-noverify-accept-custom-allow-client]
CipherString = DEFAULT
[test-5]
[test-6]
ExpectedResult = Success
client = 5-noverify-accept-custom-allow-client-extra
client = 6-noverify-accept-custom-allow-client-extra
[5-noverify-accept-custom-allow-client-extra]
[6-noverify-accept-custom-allow-client-extra]
VerifyCallback = AcceptAll
# ===========================================================
[6-verify-fail-no-root]
ssl_conf = 6-verify-fail-no-root-ssl
[7-verify-fail-no-root]
ssl_conf = 7-verify-fail-no-root-ssl
[6-verify-fail-no-root-ssl]
server = 6-verify-fail-no-root-server
client = 6-verify-fail-no-root-client
[7-verify-fail-no-root-ssl]
server = 7-verify-fail-no-root-server
client = 7-verify-fail-no-root-client
[6-verify-fail-no-root-server]
[7-verify-fail-no-root-server]
Certificate = ${ENV::TEST_CERTS_DIR}/servercert.pem
CipherString = DEFAULT
PrivateKey = ${ENV::TEST_CERTS_DIR}/serverkey.pem
[6-verify-fail-no-root-client]
[7-verify-fail-no-root-client]
CipherString = DEFAULT
VerifyMode = Peer
[test-6]
[test-7]
ExpectedClientAlert = UnknownCA
ExpectedResult = ClientFail
# ===========================================================
[7-verify-custom-success-no-root]
ssl_conf = 7-verify-custom-success-no-root-ssl
[8-verify-custom-success-no-root]
ssl_conf = 8-verify-custom-success-no-root-ssl
[7-verify-custom-success-no-root-ssl]
server = 7-verify-custom-success-no-root-server
client = 7-verify-custom-success-no-root-client
[8-verify-custom-success-no-root-ssl]
server = 8-verify-custom-success-no-root-server
client = 8-verify-custom-success-no-root-client
[7-verify-custom-success-no-root-server]
[8-verify-custom-success-no-root-server]
Certificate = ${ENV::TEST_CERTS_DIR}/servercert.pem
CipherString = DEFAULT
PrivateKey = ${ENV::TEST_CERTS_DIR}/serverkey.pem
[7-verify-custom-success-no-root-client]
[8-verify-custom-success-no-root-client]
CipherString = DEFAULT
VerifyMode = Peer
[test-7]
[test-8]
ExpectedResult = Success
client = 7-verify-custom-success-no-root-client-extra
client = 8-verify-custom-success-no-root-client-extra
[7-verify-custom-success-no-root-client-extra]
[8-verify-custom-success-no-root-client-extra]
VerifyCallback = AcceptAll
# ===========================================================
[8-verify-custom-fail-no-root]
ssl_conf = 8-verify-custom-fail-no-root-ssl
[9-verify-custom-fail-no-root]
ssl_conf = 9-verify-custom-fail-no-root-ssl
[8-verify-custom-fail-no-root-ssl]
server = 8-verify-custom-fail-no-root-server
client = 8-verify-custom-fail-no-root-client
[9-verify-custom-fail-no-root-ssl]
server = 9-verify-custom-fail-no-root-server
client = 9-verify-custom-fail-no-root-client
[8-verify-custom-fail-no-root-server]
[9-verify-custom-fail-no-root-server]
Certificate = ${ENV::TEST_CERTS_DIR}/servercert.pem
CipherString = DEFAULT
PrivateKey = ${ENV::TEST_CERTS_DIR}/serverkey.pem
[8-verify-custom-fail-no-root-client]
[9-verify-custom-fail-no-root-client]
CipherString = DEFAULT
VerifyMode = Peer
[test-8]
[test-9]
ExpectedClientAlert = HandshakeFailure
ExpectedResult = ClientFail
client = 8-verify-custom-fail-no-root-client-extra
client = 9-verify-custom-fail-no-root-client-extra
[8-verify-custom-fail-no-root-client-extra]
[9-verify-custom-fail-no-root-client-extra]
VerifyCallback = RejectAll

View File

@ -51,6 +51,20 @@ our @tests = (
},
},
# Same test as above but with a custom callback that requests retry once.
{
name => "verify-custom-retry",
server => { },
client => {
extra => {
"VerifyCallback" => "RetryOnce",
},
},
test => {
"ExpectedResult" => "Success",
},
},
# Sanity-check that verification indeed succeeds if peer verification
# is not requested.
{

View File

@ -549,6 +549,91 @@ end:
}
#endif
static int verify_retry_cb(X509_STORE_CTX *ctx, void *arg)
{
int res = X509_verify_cert(ctx);
if (res == 0 && X509_STORE_CTX_get_error(ctx) ==
X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY)
return -1; /* indicate SSL_ERROR_WANT_RETRY_VERIFY */
return res;
}
static int test_client_cert_verify_cb(void)
{
/* server key, cert, chain, and root */
char *skey = test_mk_file_path(certsdir, "leaf.key");
char *leaf = test_mk_file_path(certsdir, "leaf.pem");
char *int2 = test_mk_file_path(certsdir, "subinterCA.pem");
char *int1 = test_mk_file_path(certsdir, "interCA.pem");
char *root = test_mk_file_path(certsdir, "rootCA.pem");
X509 *crt1 = NULL, *crt2 = NULL;
STACK_OF(X509) *server_chain;
SSL_CTX *cctx = NULL, *sctx = NULL;
SSL *clientssl = NULL, *serverssl = NULL;
int testresult = 0;
if (!TEST_true(create_ssl_ctx_pair(libctx, TLS_server_method(),
TLS_client_method(), TLS1_VERSION, 0,
&sctx, &cctx, NULL, NULL)))
goto end;
if (!TEST_int_eq(SSL_CTX_use_certificate_chain_file(sctx, leaf), 1)
|| !TEST_int_eq(SSL_CTX_use_PrivateKey_file(sctx, skey,
SSL_FILETYPE_PEM), 1)
|| !TEST_int_eq(SSL_CTX_check_private_key(sctx), 1))
goto end;
if (!TEST_true(SSL_CTX_load_verify_locations(cctx, root, NULL)))
goto end;
SSL_CTX_set_verify(cctx, SSL_VERIFY_PEER, NULL);
SSL_CTX_set_cert_verify_callback(cctx, verify_retry_cb, NULL);
if (!TEST_true(create_ssl_objects(sctx, cctx, &serverssl,
&clientssl, NULL, NULL)))
goto end;
/* attempt SSL_connect() with incomplete server chain */
if (!TEST_false(create_ssl_connection(serverssl, clientssl,
SSL_ERROR_WANT_RETRY_VERIFY)))
goto end;
/* application provides intermediate certs needed to verify server cert */
if (!TEST_ptr((crt1 = load_cert_pem(int1, libctx)))
|| !TEST_ptr((crt2 = load_cert_pem(int2, libctx)))
|| !TEST_ptr((server_chain = SSL_get_peer_cert_chain(clientssl))))
goto end;
/* add certs in reverse order to demonstrate real chain building */
if (!TEST_true(sk_X509_push(server_chain, crt1)))
goto end;
crt1 = NULL;
if (!TEST_true(sk_X509_push(server_chain, crt2)))
goto end;
crt2 = NULL;
/* continue SSL_connect(), must now succeed with completed server chain */
if (!TEST_true(create_ssl_connection(serverssl, clientssl,
SSL_ERROR_NONE)))
goto end;
testresult = 1;
end:
X509_free(crt1);
X509_free(crt2);
SSL_shutdown(clientssl);
SSL_shutdown(serverssl);
SSL_free(serverssl);
SSL_free(clientssl);
SSL_CTX_free(sctx);
SSL_CTX_free(cctx);
OPENSSL_free(skey);
OPENSSL_free(leaf);
OPENSSL_free(int2);
OPENSSL_free(int1);
OPENSSL_free(root);
return testresult;
}
#ifndef OPENSSL_NO_TLS1_2
static int full_client_hello_callback(SSL *s, int *al, void *arg)
{
@ -8618,6 +8703,7 @@ int setup_tests(void)
#ifndef OPENSSL_NO_TLS1_3
ADD_TEST(test_keylog_no_master_key);
#endif
ADD_TEST(test_client_cert_verify_cb);
#ifndef OPENSSL_NO_TLS1_2
ADD_TEST(test_client_hello_cb);
ADD_TEST(test_no_ems);

View File

@ -579,6 +579,7 @@ SSL_want_async_job define
SSL_want_client_hello_cb define
SSL_want_nothing define
SSL_want_read define
SSL_want_retry_verify define
SSL_want_write define
SSL_want_x509_lookup define
SSLv23_client_method define