Skip to content

Commit f8904b1

Browse files
authored
feat: add async cert validation support (#5110)
1 parent 7ab8cd0 commit f8904b1

File tree

6 files changed

+218
-39
lines changed

6 files changed

+218
-39
lines changed

api/unstable/crl.h

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -187,12 +187,16 @@ struct s2n_cert_validation_info;
187187
*
188188
* If the validation performed in the callback is successful, `s2n_cert_validation_accept()` MUST be called to allow
189189
* `s2n_negotiate()` to continue the handshake. If the validation is unsuccessful, `s2n_cert_validation_reject()`
190-
* MUST be called, which will cause `s2n_negotiate()` to error. The behavior of `s2n_negotiate()` is undefined if
191-
* neither `s2n_cert_validation_accept()` or `s2n_cert_validation_reject()` are called.
190+
* MUST be called, which will cause `s2n_negotiate()` to error.
191+
*
192+
* To use the validation callback asynchronously, return `S2N_SUCCESS` without calling `s2n_cert_validation_accept()`
193+
* or `s2n_cert_validation_reject()`. This will pause the handshake, and `s2n_negotiate()` will throw an `S2N_ERR_T_BLOCKED`
194+
* error and `s2n_blocked_status` will be set to `S2N_BLOCKED_ON_APPLICATION_INPUT`. Applications should call
195+
* `s2n_cert_validation_accept()` or `s2n_cert_validation_reject()` to unpause the handshake before retrying `s2n_negotiate()`.
192196
*
193197
* The `info` parameter is passed to the callback in order to call APIs specific to the cert validation callback, like
194-
* `s2n_cert_validation_accept()` and `s2n_cert_validation_reject()`. The `info` argument is only valid for the
195-
* lifetime of the callback, and must not be used after the callback has finished.
198+
* `s2n_cert_validation_accept()` and `s2n_cert_validation_reject()`. The `info` argument shares the same lifetime as
199+
* `s2n_connection`.
196200
*
197201
* After calling `s2n_cert_validation_reject()`, `s2n_negotiate()` will fail with a protocol error indicating that
198202
* the cert has been rejected from the callback. If more information regarding an application's custom validation

tests/unit/s2n_cert_validation_callback_test.c

Lines changed: 136 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,16 @@ struct s2n_cert_validation_data {
2323
unsigned return_success : 1;
2424

2525
int invoked_count;
26+
struct s2n_cert_validation_info *info;
2627
};
2728

2829
static int s2n_test_cert_validation_callback(struct s2n_connection *conn, struct s2n_cert_validation_info *info, void *ctx)
2930
{
3031
struct s2n_cert_validation_data *data = (struct s2n_cert_validation_data *) ctx;
3132

3233
data->invoked_count += 1;
34+
/* Pass the `s2n_cert_validation_info` struct to application-defined `ctx` */
35+
data->info = info;
3336

3437
int ret = S2N_FAILURE;
3538
if (data->return_success) {
@@ -187,16 +190,6 @@ int main(int argc, char *argv[])
187190
.data = { .call_accept_or_reject = true, .accept = false, .return_success = false },
188191
.expected_error = S2N_ERR_CANCELLED
189192
},
190-
{
191-
.data = { .call_accept_or_reject = false, .return_success = false },
192-
.expected_error = S2N_ERR_CANCELLED
193-
},
194-
195-
/* Error if accept or reject wasn't called from the callback */
196-
{
197-
.data = { .call_accept_or_reject = false, .return_success = true },
198-
.expected_error = S2N_ERR_INVALID_STATE
199-
},
200193
};
201194
/* clang-format on */
202195

@@ -444,6 +437,139 @@ int main(int argc, char *argv[])
444437

445438
EXPECT_EQUAL(data.invoked_count, 1);
446439
}
440+
441+
/* For async cases, accept or reject API will be called outside of the validation callback.
442+
* Iterate over both TLS 1.3 and 1.2 policies to ensure the stuffer reset logic works in all cases.
443+
*/
444+
struct s2n_cert_validation_data async_test_cases[] = {
445+
{ .call_accept_or_reject = false, .accept = true, .return_success = true },
446+
{ .call_accept_or_reject = false, .accept = false, .return_success = true },
447+
};
448+
const char *versions[] = { "20240501", "20170210" };
449+
450+
/* Async callback is invoked on the client after receiving the server's certificate */
451+
for (int test_case_idx = 0; test_case_idx < s2n_array_len(async_test_cases); test_case_idx++) {
452+
for (int version_idx = 0; version_idx < s2n_array_len(versions); version_idx++) {
453+
DEFER_CLEANUP(struct s2n_config *config = s2n_config_new(), s2n_config_ptr_free);
454+
EXPECT_NOT_NULL(config);
455+
EXPECT_SUCCESS(s2n_config_add_cert_chain_and_key_to_store(config, chain_and_key));
456+
EXPECT_SUCCESS(s2n_config_set_verification_ca_location(config, S2N_DEFAULT_TEST_CERT_CHAIN, NULL));
457+
EXPECT_SUCCESS(s2n_config_set_cipher_preferences(config, versions[version_idx]));
458+
459+
struct s2n_cert_validation_data data = async_test_cases[test_case_idx];
460+
EXPECT_SUCCESS(s2n_config_set_cert_validation_cb(config, s2n_test_cert_validation_callback_self_talk, &data));
461+
462+
DEFER_CLEANUP(struct s2n_connection *server_conn = s2n_connection_new(S2N_SERVER), s2n_connection_ptr_free);
463+
EXPECT_NOT_NULL(server_conn);
464+
EXPECT_SUCCESS(s2n_connection_set_config(server_conn, config));
465+
466+
DEFER_CLEANUP(struct s2n_connection *client_conn = s2n_connection_new(S2N_CLIENT), s2n_connection_ptr_free);
467+
EXPECT_NOT_NULL(client_conn);
468+
EXPECT_SUCCESS(s2n_connection_set_config(client_conn, config));
469+
EXPECT_SUCCESS(s2n_connection_set_blinding(client_conn, S2N_SELF_SERVICE_BLINDING));
470+
EXPECT_SUCCESS(s2n_set_server_name(client_conn, "localhost"));
471+
472+
DEFER_CLEANUP(struct s2n_test_io_pair io_pair = { 0 }, s2n_io_pair_close);
473+
EXPECT_SUCCESS(s2n_io_pair_init_non_blocking(&io_pair));
474+
EXPECT_SUCCESS(s2n_connection_set_io_pair(client_conn, &io_pair));
475+
EXPECT_SUCCESS(s2n_connection_set_io_pair(server_conn, &io_pair));
476+
477+
for (int i = 0; i < 3; i++) {
478+
EXPECT_FAILURE_WITH_ERRNO(s2n_negotiate_test_server_and_client(server_conn, client_conn),
479+
S2N_ERR_ASYNC_BLOCKED);
480+
EXPECT_EQUAL(data.invoked_count, 1);
481+
}
482+
483+
/* Ensure that the server's certificate chain can be retrieved after `S2N_ERR_ASYNC_BLOCKED` */
484+
DEFER_CLEANUP(struct s2n_cert_chain_and_key *peer_cert_chain = s2n_cert_chain_and_key_new(),
485+
s2n_cert_chain_and_key_ptr_free);
486+
EXPECT_NOT_NULL(peer_cert_chain);
487+
EXPECT_SUCCESS(s2n_connection_get_peer_cert_chain(client_conn, peer_cert_chain));
488+
/* Ensure the certificate chain is non-empty */
489+
uint32_t peer_cert_chain_len = 0;
490+
EXPECT_SUCCESS(s2n_cert_chain_get_length(peer_cert_chain, &peer_cert_chain_len));
491+
EXPECT_TRUE(peer_cert_chain_len > 0);
492+
493+
struct s2n_cert_validation_info *info = data.info;
494+
EXPECT_NOT_NULL(info);
495+
496+
if (async_test_cases[test_case_idx].accept) {
497+
EXPECT_SUCCESS(s2n_cert_validation_accept(info));
498+
EXPECT_SUCCESS(s2n_negotiate_test_server_and_client(server_conn, client_conn));
499+
} else {
500+
EXPECT_SUCCESS(s2n_cert_validation_reject(info));
501+
EXPECT_FAILURE_WITH_ERRNO(s2n_negotiate_test_server_and_client(server_conn, client_conn),
502+
S2N_ERR_CERT_REJECTED);
503+
}
504+
505+
EXPECT_EQUAL(data.invoked_count, 1);
506+
}
507+
}
508+
509+
/* Async callback is invoked on the server after receiving the client's certificate */
510+
for (int test_case_idx = 0; test_case_idx < s2n_array_len(async_test_cases); test_case_idx++) {
511+
for (int version_idx = 0; version_idx < s2n_array_len(versions); version_idx++) {
512+
DEFER_CLEANUP(struct s2n_config *server_config = s2n_config_new(), s2n_config_ptr_free);
513+
EXPECT_NOT_NULL(server_config);
514+
EXPECT_SUCCESS(s2n_config_add_cert_chain_and_key_to_store(server_config, chain_and_key));
515+
EXPECT_SUCCESS(s2n_config_set_verification_ca_location(server_config, S2N_DEFAULT_TEST_CERT_CHAIN, NULL));
516+
EXPECT_SUCCESS(s2n_config_set_cipher_preferences(server_config, versions[version_idx]));
517+
EXPECT_SUCCESS(s2n_config_set_client_auth_type(server_config, S2N_CERT_AUTH_REQUIRED));
518+
519+
struct s2n_cert_validation_data data = async_test_cases[test_case_idx];
520+
EXPECT_SUCCESS(s2n_config_set_cert_validation_cb(server_config,
521+
s2n_test_cert_validation_callback_self_talk_server, &data));
522+
523+
DEFER_CLEANUP(struct s2n_connection *server_conn = s2n_connection_new(S2N_SERVER), s2n_connection_ptr_free);
524+
EXPECT_NOT_NULL(server_conn);
525+
EXPECT_SUCCESS(s2n_connection_set_config(server_conn, server_config));
526+
EXPECT_SUCCESS(s2n_connection_set_blinding(server_conn, S2N_SELF_SERVICE_BLINDING));
527+
528+
DEFER_CLEANUP(struct s2n_config *client_config = s2n_config_new(), s2n_config_ptr_free);
529+
EXPECT_NOT_NULL(client_config);
530+
EXPECT_SUCCESS(s2n_config_add_cert_chain_and_key_to_store(client_config, chain_and_key));
531+
EXPECT_SUCCESS(s2n_config_set_verification_ca_location(client_config, S2N_DEFAULT_TEST_CERT_CHAIN, NULL));
532+
EXPECT_SUCCESS(s2n_config_set_cipher_preferences(client_config, versions[version_idx]));
533+
EXPECT_SUCCESS(s2n_config_set_client_auth_type(client_config, S2N_CERT_AUTH_OPTIONAL));
534+
535+
DEFER_CLEANUP(struct s2n_connection *client_conn = s2n_connection_new(S2N_CLIENT), s2n_connection_ptr_free);
536+
EXPECT_NOT_NULL(client_conn);
537+
EXPECT_SUCCESS(s2n_connection_set_config(client_conn, client_config));
538+
EXPECT_SUCCESS(s2n_set_server_name(client_conn, "localhost"));
539+
540+
DEFER_CLEANUP(struct s2n_test_io_pair io_pair = { 0 }, s2n_io_pair_close);
541+
EXPECT_SUCCESS(s2n_io_pair_init_non_blocking(&io_pair));
542+
EXPECT_SUCCESS(s2n_connection_set_io_pair(client_conn, &io_pair));
543+
EXPECT_SUCCESS(s2n_connection_set_io_pair(server_conn, &io_pair));
544+
545+
for (int i = 0; i < 3; i++) {
546+
EXPECT_FAILURE_WITH_ERRNO(s2n_negotiate_test_server_and_client(server_conn, client_conn),
547+
S2N_ERR_ASYNC_BLOCKED);
548+
EXPECT_EQUAL(data.invoked_count, 1);
549+
}
550+
551+
/* Ensure that the client's certificate chain can be retrieved after `S2N_ERR_ASYNC_BLOCKED` */
552+
uint8_t *der_cert_chain = 0;
553+
uint32_t cert_chain_len = 0;
554+
EXPECT_SUCCESS(s2n_connection_get_client_cert_chain(server_conn, &der_cert_chain, &cert_chain_len));
555+
/* Ensure the certificate chain is non-empty */
556+
EXPECT_TRUE(cert_chain_len > 0);
557+
558+
struct s2n_cert_validation_info *info = data.info;
559+
EXPECT_NOT_NULL(info);
560+
561+
if (async_test_cases[test_case_idx].accept) {
562+
EXPECT_SUCCESS(s2n_cert_validation_accept(info));
563+
EXPECT_SUCCESS(s2n_negotiate_test_server_and_client(server_conn, client_conn));
564+
} else {
565+
EXPECT_SUCCESS(s2n_cert_validation_reject(info));
566+
EXPECT_FAILURE_WITH_ERRNO(s2n_negotiate_test_server_and_client(server_conn, client_conn),
567+
S2N_ERR_CERT_REJECTED);
568+
}
569+
570+
EXPECT_EQUAL(data.invoked_count, 1);
571+
}
572+
}
447573
}
448574

449575
END_TEST();

tls/s2n_client_cert.c

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,12 @@ static S2N_RESULT s2n_client_cert_chain_store(struct s2n_connection *conn,
5757
RESULT_ENSURE_REF(conn);
5858
RESULT_ENSURE_REF(raw_cert_chain);
5959

60-
/* There shouldn't already be a client cert chain, but free just in case */
61-
RESULT_GUARD_POSIX(s2n_free(&conn->handshake_params.client_cert_chain));
60+
/* If a client cert chain has already been stored (e.g. on the re-entry case
61+
* of an async callback), no need to store it again.
62+
*/
63+
if (conn->handshake_params.client_cert_chain.size > 0) {
64+
return S2N_RESULT_OK;
65+
}
6266

6367
/* Earlier versions are a basic copy */
6468
if (conn->actual_protocol_version < S2N_TLS13) {
@@ -101,23 +105,26 @@ static S2N_RESULT s2n_client_cert_chain_store(struct s2n_connection *conn,
101105

102106
int s2n_client_cert_recv(struct s2n_connection *conn)
103107
{
108+
/* s2n_client_cert_recv() may be re-entered due to handling an async callback.
109+
* We operate on a copy of `handshake.io` to ensure the stuffer is initilized properly on the re-entry case.
110+
*/
111+
struct s2n_stuffer in = conn->handshake.io;
112+
104113
if (conn->actual_protocol_version == S2N_TLS13) {
105114
uint8_t certificate_request_context_len = 0;
106-
POSIX_GUARD(s2n_stuffer_read_uint8(&conn->handshake.io, &certificate_request_context_len));
115+
POSIX_GUARD(s2n_stuffer_read_uint8(&in, &certificate_request_context_len));
107116
S2N_ERROR_IF(certificate_request_context_len != 0, S2N_ERR_BAD_MESSAGE);
108117
}
109118

110-
struct s2n_stuffer *in = &conn->handshake.io;
111-
112119
uint32_t cert_chain_size = 0;
113-
POSIX_GUARD(s2n_stuffer_read_uint24(in, &cert_chain_size));
114-
POSIX_ENSURE(cert_chain_size <= s2n_stuffer_data_available(in), S2N_ERR_BAD_MESSAGE);
120+
POSIX_GUARD(s2n_stuffer_read_uint24(&in, &cert_chain_size));
121+
POSIX_ENSURE(cert_chain_size <= s2n_stuffer_data_available(&in), S2N_ERR_BAD_MESSAGE);
115122
if (cert_chain_size == 0) {
116123
POSIX_GUARD(s2n_conn_set_handshake_no_client_cert(conn));
117124
return S2N_SUCCESS;
118125
}
119126

120-
uint8_t *cert_chain_data = s2n_stuffer_raw_read(in, cert_chain_size);
127+
uint8_t *cert_chain_data = s2n_stuffer_raw_read(&in, cert_chain_size);
121128
POSIX_ENSURE_REF(cert_chain_data);
122129

123130
struct s2n_blob cert_chain = { 0 };
@@ -139,6 +146,9 @@ int s2n_client_cert_recv(struct s2n_connection *conn)
139146
POSIX_GUARD(s2n_pkey_check_key_exists(&public_key));
140147
conn->handshake_params.client_public_key = public_key;
141148

149+
/* Update handshake.io to reflect the true stuffer state after all async callbacks are handled. */
150+
conn->handshake.io = in;
151+
142152
return S2N_SUCCESS;
143153
}
144154

tls/s2n_server_cert.c

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,21 @@
2222

2323
int s2n_server_cert_recv(struct s2n_connection *conn)
2424
{
25+
/* s2n_server_cert_recv() may be re-entered due to handling an async callback.
26+
* We operate on a copy of `handshake.io` to ensure the stuffer is initilized properly on the re-entry case.
27+
*/
28+
struct s2n_stuffer in = conn->handshake.io;
29+
2530
if (conn->actual_protocol_version == S2N_TLS13) {
2631
uint8_t certificate_request_context_len = 0;
27-
POSIX_GUARD(s2n_stuffer_read_uint8(&conn->handshake.io, &certificate_request_context_len));
32+
POSIX_GUARD(s2n_stuffer_read_uint8(&in, &certificate_request_context_len));
2833
S2N_ERROR_IF(certificate_request_context_len != 0, S2N_ERR_BAD_MESSAGE);
2934
}
3035

3136
uint32_t size_of_all_certificates = 0;
32-
POSIX_GUARD(s2n_stuffer_read_uint24(&conn->handshake.io, &size_of_all_certificates));
37+
POSIX_GUARD(s2n_stuffer_read_uint24(&in, &size_of_all_certificates));
3338

34-
S2N_ERROR_IF(size_of_all_certificates > s2n_stuffer_data_available(&conn->handshake.io) || size_of_all_certificates < 3,
39+
S2N_ERROR_IF(size_of_all_certificates > s2n_stuffer_data_available(&in) || size_of_all_certificates < 3,
3540
S2N_ERR_BAD_MESSAGE);
3641

3742
s2n_cert_public_key public_key;
@@ -40,7 +45,7 @@ int s2n_server_cert_recv(struct s2n_connection *conn)
4045
s2n_pkey_type actual_cert_pkey_type;
4146
struct s2n_blob cert_chain = { 0 };
4247
cert_chain.size = size_of_all_certificates;
43-
cert_chain.data = s2n_stuffer_raw_read(&conn->handshake.io, size_of_all_certificates);
48+
cert_chain.data = s2n_stuffer_raw_read(&in, size_of_all_certificates);
4449
POSIX_ENSURE_REF(cert_chain.data);
4550

4651
POSIX_GUARD_RESULT(s2n_x509_validator_validate_cert_chain(&conn->x509_validator, conn, cert_chain.data,
@@ -50,6 +55,9 @@ int s2n_server_cert_recv(struct s2n_connection *conn)
5055
POSIX_GUARD_RESULT(s2n_pkey_setup_for_type(&public_key, actual_cert_pkey_type));
5156
conn->handshake_params.server_public_key = public_key;
5257

58+
/* Update handshake.io to reflect the true stuffer state after all async callbacks are handled. */
59+
conn->handshake.io = in;
60+
5361
return 0;
5462
}
5563

tls/s2n_x509_validator.c

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,8 @@ int s2n_x509_validator_init_no_x509_validation(struct s2n_x509_validator *valida
149149
validator->state = INIT;
150150
validator->cert_chain_from_wire = sk_X509_new_null();
151151
validator->crl_lookup_list = NULL;
152+
validator->cert_validation_info = (struct s2n_cert_validation_info){ 0 };
153+
validator->cert_validation_cb_invoked = false;
152154

153155
return 0;
154156
}
@@ -168,6 +170,8 @@ int s2n_x509_validator_init(struct s2n_x509_validator *validator, struct s2n_x50
168170
validator->cert_chain_from_wire = sk_X509_new_null();
169171
validator->state = INIT;
170172
validator->crl_lookup_list = NULL;
173+
validator->cert_validation_info = (struct s2n_cert_validation_info){ 0 };
174+
validator->cert_validation_cb_invoked = false;
171175

172176
return 0;
173177
}
@@ -750,8 +754,8 @@ static S2N_RESULT s2n_x509_validator_parse_leaf_certificate_extensions(struct s2
750754
return S2N_RESULT_OK;
751755
}
752756

753-
S2N_RESULT s2n_x509_validator_validate_cert_chain(struct s2n_x509_validator *validator, struct s2n_connection *conn,
754-
uint8_t *cert_chain_in, uint32_t cert_chain_len, s2n_pkey_type *pkey_type, struct s2n_pkey *public_key_out)
757+
S2N_RESULT s2n_x509_validator_validate_cert_chain_pre_cb(struct s2n_x509_validator *validator, struct s2n_connection *conn,
758+
uint8_t *cert_chain_in, uint32_t cert_chain_len)
755759
{
756760
RESULT_ENSURE_REF(conn);
757761
RESULT_ENSURE_REF(conn->config);
@@ -788,12 +792,37 @@ S2N_RESULT s2n_x509_validator_validate_cert_chain(struct s2n_x509_validator *val
788792
RESULT_GUARD_POSIX(s2n_extension_list_process(S2N_EXTENSION_LIST_CERTIFICATE, conn, &first_certificate_extensions));
789793
}
790794

791-
if (conn->config->cert_validation_cb) {
792-
struct s2n_cert_validation_info info = { 0 };
793-
RESULT_ENSURE(conn->config->cert_validation_cb(conn, &info, conn->config->cert_validation_ctx) >= S2N_SUCCESS,
794-
S2N_ERR_CANCELLED);
795-
RESULT_ENSURE(info.finished, S2N_ERR_INVALID_STATE);
796-
RESULT_ENSURE(info.accepted, S2N_ERR_CERT_REJECTED);
795+
return S2N_RESULT_OK;
796+
}
797+
798+
static S2N_RESULT s2n_x509_validator_handle_cert_validation_callback_result(struct s2n_x509_validator *validator)
799+
{
800+
RESULT_ENSURE_REF(validator);
801+
802+
if (!validator->cert_validation_info.finished) {
803+
RESULT_BAIL(S2N_ERR_ASYNC_BLOCKED);
804+
}
805+
806+
RESULT_ENSURE(validator->cert_validation_info.accepted, S2N_ERR_CERT_REJECTED);
807+
return S2N_RESULT_OK;
808+
}
809+
810+
S2N_RESULT s2n_x509_validator_validate_cert_chain(struct s2n_x509_validator *validator, struct s2n_connection *conn,
811+
uint8_t *cert_chain_in, uint32_t cert_chain_len, s2n_pkey_type *pkey_type, struct s2n_pkey *public_key_out)
812+
{
813+
RESULT_ENSURE_REF(validator);
814+
815+
if (validator->cert_validation_cb_invoked) {
816+
RESULT_GUARD(s2n_x509_validator_handle_cert_validation_callback_result(validator));
817+
} else {
818+
RESULT_GUARD(s2n_x509_validator_validate_cert_chain_pre_cb(validator, conn, cert_chain_in, cert_chain_len));
819+
820+
if (conn->config->cert_validation_cb) {
821+
RESULT_ENSURE(conn->config->cert_validation_cb(conn, &(validator->cert_validation_info), conn->config->cert_validation_ctx) >= S2N_SUCCESS,
822+
S2N_ERR_CANCELLED);
823+
validator->cert_validation_cb_invoked = true;
824+
RESULT_GUARD(s2n_x509_validator_handle_cert_validation_callback_result(validator));
825+
}
797826
}
798827

799828
/* retrieve information from leaf cert */

0 commit comments

Comments
 (0)