Skip to content

Commit 792d366

Browse files
authored
feat: add async public key support (#5473)
1 parent 2fb244e commit 792d366

16 files changed

+650
-54
lines changed

api/unstable/async_offload.h

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
#pragma once
17+
18+
#include <s2n.h>
19+
20+
/**
21+
* @file async_offload.h
22+
*
23+
* The following APIs enable applications to offload expensive handshake operations that do not require user input.
24+
* This model can be useful to move CPU-heavy operations (e.g. cryptographic calculations) out of the main event loop.
25+
*/
26+
27+
/**
28+
* Opaque struct for the async offloading operation
29+
*/
30+
struct s2n_async_offload_op;
31+
32+
/**
33+
* The type of operations supported by the async offloading callback. Each type is represented by a different bit.
34+
*
35+
* S2N_ASYNC_OFFLOAD_ALLOW_ALL will automatically opt in to all the new types added in the future.
36+
*/
37+
typedef enum {
38+
S2N_ASYNC_OFFLOAD_PKEY_VERIFY = 0x01,
39+
/* Max value: ISO C restricts enumerator values to range of ‘int’ before C2X. */
40+
S2N_ASYNC_OFFLOAD_ALLOW_ALL = 0x7FFFFFFF,
41+
} s2n_async_offload_op_type;
42+
43+
/**
44+
* The callback function invoked every time an allowed async operation is encountered during the handshake.
45+
*
46+
* To perform an operation asynchronously, the following condiditions must be satisfied:
47+
* 1) This op type must be included in the allow list;
48+
* 2) Async offloading callback returns success and s2n_async_offload_op_perform() is invoked outside the callback.
49+
*
50+
* If s2n_async_offload_op_perform() is invoked inside the callback, it is equivalent to the synchronous use case.
51+
*
52+
* `op` is owned by s2n-tls and will be freed along with s2n_connection eventually.
53+
*
54+
* @param conn Connection which triggered the async offloading callback
55+
* @param op An opaque object representing the async operation
56+
* @param ctx Application data provided to the callback via s2n_config_set_async_offload_callback()
57+
*/
58+
typedef int (*s2n_async_offload_cb)(struct s2n_connection *conn, struct s2n_async_offload_op *op, void *ctx);
59+
60+
/**
61+
* Sets up the custom async offloading callback and configures the offloaded handshake operations via allow_list.
62+
*
63+
* The value of allow_list should be the Bit-OR of all the allowed s2n_async_offload_op_type values.
64+
*
65+
* S2N_ASYNC_OFFLOAD_ALLOW_ALL provides the performance benefit of offloading all the supported operations;
66+
* ensure your callback can support arbitrary operations. Otherwise, only allow operations that fit your use case.
67+
*
68+
* @param config Config to set the callback
69+
* @param allow_list A bit representation of allowed operations
70+
* @param fn The function that should be called for each allowed async operation
71+
* @param ctx Optional application data passed to the callback
72+
*/
73+
S2N_API extern int s2n_config_set_async_offload_callback(struct s2n_config *config, uint32_t allow_list,
74+
s2n_async_offload_cb fn, void *ctx);
75+
76+
/**
77+
* Performs the operation triggered by the async offloading callback.
78+
*
79+
* To execute operations asynchronously, users should spawn a separate thread to invoke s2n_async_offload_op_perform()
80+
* and immediately return S2N_SUCCESS from the callback without waiting for that separate thread to complete.
81+
*
82+
* s2n_negotiate() will throw an `S2N_ERR_T_BLOCKED` error if the handshake is pending on the async offloading callback.
83+
* Retrying s2n_negotiate() will produce the same result until s2n_async_offload_op_perform() is completed.
84+
*
85+
* s2n_async_offload_op_perform() can only be called once for each triggered operation.
86+
*
87+
* @param op An opaque object representing the async operation
88+
*/
89+
S2N_API extern int s2n_async_offload_op_perform(struct s2n_async_offload_op *op);

bindings/rust/extended/s2n-tls-sys/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ fips = ["aws-lc-rs/fips"]
3131
pq = []
3232
internal = []
3333
stacktrace = []
34+
unstable-async_offload = []
3435
unstable-cert_authorities = []
3536
unstable-cleanup = []
3637
unstable-crl = []
Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
#include "api/s2n.h"
17+
#include "error/s2n_errno.h"
18+
#include "s2n_test.h"
19+
#include "testlib/s2n_testlib.h"
20+
#include "tls/s2n_async_offload.h"
21+
#include "tls/s2n_cipher_suites.h"
22+
#include "tls/s2n_connection.h"
23+
#include "tls/s2n_security_policies.h"
24+
#include "utils/s2n_safety.h"
25+
26+
#define S2N_ASYNC_OFFLOAD_OP_NONE 0
27+
28+
struct s2n_async_offload_cb_test {
29+
unsigned async_test : 1;
30+
int result;
31+
int invoked_count;
32+
struct s2n_async_offload_op *last_seen_op;
33+
};
34+
35+
int s2n_async_offload_test_callback(struct s2n_connection *conn, struct s2n_async_offload_op *op, void *ctx)
36+
{
37+
EXPECT_NOT_NULL(op);
38+
struct s2n_async_offload_cb_test *data = (struct s2n_async_offload_cb_test *) ctx;
39+
data->invoked_count += 1;
40+
data->last_seen_op = op;
41+
42+
if (!data->async_test) {
43+
EXPECT_SUCCESS(s2n_async_offload_op_perform(op));
44+
}
45+
return data->result;
46+
}
47+
48+
static int s2n_test_handshake_async(struct s2n_connection *server_conn, struct s2n_connection *client_conn,
49+
struct s2n_async_offload_cb_test *data)
50+
{
51+
for (size_t retry = 0; retry < 20; retry++) {
52+
int ret_val = s2n_negotiate_test_server_and_client(server_conn, client_conn);
53+
54+
if (ret_val != S2N_SUCCESS && s2n_errno == S2N_ERR_ASYNC_BLOCKED) {
55+
/* Handshake remains blocked as long as op_perform() is not invoked. */
56+
EXPECT_FAILURE_WITH_ERRNO(s2n_negotiate_test_server_and_client(server_conn, client_conn),
57+
S2N_ERR_ASYNC_BLOCKED);
58+
59+
EXPECT_SUCCESS(s2n_async_offload_op_perform(data->last_seen_op));
60+
/* Each operation can only be performed once. */
61+
EXPECT_FAILURE_WITH_ERRNO(s2n_async_offload_op_perform(data->last_seen_op), S2N_ERR_INVALID_STATE);
62+
} else {
63+
return ret_val;
64+
}
65+
}
66+
67+
FAIL_MSG("Async offload operation does not terminate");
68+
}
69+
70+
int main(int argc, char *argv[])
71+
{
72+
BEGIN_TEST();
73+
74+
DEFER_CLEANUP(struct s2n_cert_chain_and_key *chain_and_key = NULL, s2n_cert_chain_and_key_ptr_free);
75+
EXPECT_SUCCESS(s2n_test_cert_chain_and_key_new(&chain_and_key,
76+
S2N_DEFAULT_TEST_CERT_CHAIN, S2N_DEFAULT_TEST_PRIVATE_KEY));
77+
78+
/* Safety Check */
79+
{
80+
struct s2n_async_offload_cb_test test_data = { 0 };
81+
EXPECT_FAILURE_WITH_ERRNO(
82+
s2n_config_set_async_offload_callback(NULL, S2N_ASYNC_OFFLOAD_ALLOW_ALL,
83+
s2n_async_offload_test_callback, &test_data),
84+
S2N_ERR_NULL);
85+
86+
DEFER_CLEANUP(struct s2n_config *test_config = s2n_config_new(), s2n_config_ptr_free);
87+
EXPECT_NOT_NULL(test_config);
88+
EXPECT_EQUAL(test_config->async_offload_allow_list, S2N_ASYNC_OFFLOAD_OP_NONE);
89+
EXPECT_FAILURE_WITH_ERRNO(
90+
s2n_config_set_async_offload_callback(test_config, S2N_ASYNC_OFFLOAD_ALLOW_ALL,
91+
NULL, &test_data),
92+
S2N_ERR_NULL);
93+
94+
EXPECT_SUCCESS(s2n_config_set_async_offload_callback(test_config, S2N_ASYNC_OFFLOAD_PKEY_VERIFY,
95+
s2n_async_offload_test_callback, &test_data));
96+
EXPECT_TRUE(s2n_async_offload_op_is_in_allow_list(test_config, S2N_ASYNC_OFFLOAD_PKEY_VERIFY));
97+
98+
EXPECT_FAILURE_WITH_ERRNO(s2n_async_offload_op_perform(NULL), S2N_ERR_NULL);
99+
struct s2n_async_offload_op test_op = { 0 };
100+
EXPECT_FAILURE_WITH_ERRNO(s2n_async_offload_op_perform(&test_op), S2N_ERR_INVALID_STATE);
101+
}
102+
103+
/* clang-format off */
104+
struct s2n_async_offload_test_case {
105+
bool async_test;
106+
s2n_async_offload_op_type allow_list;
107+
int cb_return;
108+
int cb_invoked;
109+
bool client_auth;
110+
s2n_error expected_error;
111+
} test_cases[] = {
112+
/* Default option: no op type is allowed. */
113+
{
114+
.async_test = false,
115+
.allow_list = S2N_ASYNC_OFFLOAD_OP_NONE,
116+
.cb_return = S2N_SUCCESS,
117+
.cb_invoked = 0,
118+
.client_auth = true,
119+
.expected_error = S2N_ERR_OK,
120+
},
121+
/* Test a random value that has not been used by any op type. */
122+
/* Changing return value does not fail the handshake because the callback is not invoked. */
123+
{
124+
.async_test = false,
125+
.allow_list = 0x70000000,
126+
.cb_return = S2N_FAILURE,
127+
.cb_invoked = 0,
128+
.client_auth = true,
129+
.expected_error = S2N_ERR_OK,
130+
},
131+
/* Async PKEY_VERIFY allowed. Client auth enabled. A successful handshake performs pkey_verify() twice. */
132+
{
133+
.async_test = false,
134+
.allow_list = S2N_ASYNC_OFFLOAD_PKEY_VERIFY,
135+
.cb_return = S2N_SUCCESS,
136+
.cb_invoked = 2,
137+
.client_auth = true,
138+
.expected_error = S2N_ERR_OK,
139+
},
140+
/* Any op type is allowed. Handshake failed because the callback failed in the first attempt. */
141+
{
142+
.async_test = false,
143+
.allow_list = S2N_ASYNC_OFFLOAD_ALLOW_ALL,
144+
.cb_return = S2N_FAILURE,
145+
.cb_invoked = 1,
146+
.client_auth = true,
147+
.expected_error = S2N_ERR_CANCELLED,
148+
},
149+
/* Client auth is enabled. pkey_verify() is performed by both server side and client side. */
150+
{
151+
.async_test = true,
152+
.allow_list = S2N_ASYNC_OFFLOAD_PKEY_VERIFY,
153+
.cb_return = S2N_SUCCESS,
154+
.cb_invoked = 2,
155+
.client_auth = true,
156+
.expected_error = S2N_ERR_ASYNC_BLOCKED,
157+
},
158+
/* Client auth is not enabled. pkey_verify() is performed only by client side. */
159+
{
160+
.async_test = true,
161+
.allow_list = S2N_ASYNC_OFFLOAD_ALLOW_ALL,
162+
.cb_return = S2N_SUCCESS,
163+
.cb_invoked = 1,
164+
.client_auth = false,
165+
.expected_error = S2N_ERR_ASYNC_BLOCKED,
166+
},
167+
};
168+
/* clang-format on */
169+
170+
/* Test with both TLS 1.2 and TLS 1.3 policies */
171+
const char *versions[] = { "20240501", "default_tls13" };
172+
173+
for (int test_idx = 0; test_idx < s2n_array_len(test_cases); test_idx++) {
174+
for (int version_idx = 0; version_idx < s2n_array_len(versions); version_idx++) {
175+
DEFER_CLEANUP(struct s2n_config *config = s2n_config_new(), s2n_config_ptr_free);
176+
EXPECT_NOT_NULL(config);
177+
EXPECT_SUCCESS(s2n_config_add_cert_chain_and_key_to_store(config, chain_and_key));
178+
EXPECT_SUCCESS(s2n_config_set_verification_ca_location(config, S2N_DEFAULT_TEST_CERT_CHAIN, NULL));
179+
EXPECT_SUCCESS(s2n_config_set_cipher_preferences(config, versions[version_idx]));
180+
181+
struct s2n_async_offload_test_case test_case = test_cases[test_idx];
182+
if (test_case.client_auth) {
183+
EXPECT_SUCCESS(s2n_config_set_client_auth_type(config, S2N_CERT_AUTH_REQUIRED));
184+
}
185+
186+
struct s2n_async_offload_cb_test data = { .async_test = test_case.async_test, .result = test_case.cb_return };
187+
if (test_case.allow_list != S2N_ASYNC_OFFLOAD_OP_NONE) {
188+
EXPECT_SUCCESS(s2n_config_set_async_offload_callback(config, test_case.allow_list,
189+
s2n_async_offload_test_callback, &data));
190+
}
191+
192+
DEFER_CLEANUP(struct s2n_connection *server_conn = s2n_connection_new(S2N_SERVER), s2n_connection_ptr_free);
193+
EXPECT_NOT_NULL(server_conn);
194+
EXPECT_SUCCESS(s2n_connection_set_config(server_conn, config));
195+
196+
DEFER_CLEANUP(struct s2n_connection *client_conn = s2n_connection_new(S2N_CLIENT), s2n_connection_ptr_free);
197+
EXPECT_NOT_NULL(client_conn);
198+
EXPECT_SUCCESS(s2n_connection_set_config(client_conn, config));
199+
EXPECT_SUCCESS(s2n_connection_set_blinding(client_conn, S2N_SELF_SERVICE_BLINDING));
200+
EXPECT_SUCCESS(s2n_set_server_name(client_conn, "localhost"));
201+
202+
DEFER_CLEANUP(struct s2n_test_io_pair io_pair = { 0 }, s2n_io_pair_close);
203+
EXPECT_SUCCESS(s2n_io_pair_init_non_blocking(&io_pair));
204+
EXPECT_SUCCESS(s2n_connection_set_io_pair(client_conn, &io_pair));
205+
EXPECT_SUCCESS(s2n_connection_set_io_pair(server_conn, &io_pair));
206+
207+
s2n_error expected_error = test_case.expected_error;
208+
if (test_case.async_test) {
209+
EXPECT_SUCCESS(s2n_test_handshake_async(server_conn, client_conn, &data));
210+
} else if (expected_error == S2N_ERR_OK) {
211+
EXPECT_SUCCESS(s2n_negotiate_test_server_and_client(server_conn, client_conn));
212+
} else {
213+
EXPECT_FAILURE_WITH_ERRNO(s2n_negotiate_test_server_and_client(server_conn, client_conn), expected_error);
214+
}
215+
EXPECT_EQUAL(data.invoked_count, test_case.cb_invoked);
216+
217+
if (s2n_is_tls13_fully_supported() && version_idx == 1) {
218+
EXPECT_EQUAL(s2n_connection_get_actual_protocol_version(server_conn), S2N_TLS13);
219+
} else {
220+
EXPECT_EQUAL(s2n_connection_get_actual_protocol_version(server_conn), S2N_TLS12);
221+
}
222+
}
223+
}
224+
225+
END_TEST();
226+
}

tests/unit/s2n_connection_size_test.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ int main(int argc, char **argv)
4444
}
4545

4646
/* Carefully consider any increases to this number. */
47-
const uint16_t max_connection_size = 4350;
47+
const uint16_t max_connection_size = 4360;
4848
const uint16_t min_connection_size = max_connection_size * 0.9;
4949

5050
size_t connection_size = sizeof(struct s2n_connection);

tests/unit/s2n_self_talk_offload_signing_test.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ static S2N_RESULT s2n_test_pkey_sign(const struct s2n_pkey *pkey,
6060
return S2N_RESULT_OK;
6161
}
6262

63-
static S2N_RESULT s2n_async_pkey_sign(struct s2n_cert_chain_and_key *complete_chain)
63+
static S2N_RESULT s2n_async_pkey_sign_test(struct s2n_cert_chain_and_key *complete_chain)
6464
{
6565
RESULT_ENSURE_REF(pkey_op);
6666
RESULT_ENSURE_REF(pkey_op_conn);
@@ -134,7 +134,7 @@ static S2N_RESULT s2n_do_test_handshake(struct s2n_config *config, struct s2n_ce
134134

135135
while (s2n_negotiate_test_server_and_client(server_conn, client_conn) != S2N_SUCCESS) {
136136
EXPECT_EQUAL(s2n_errno, S2N_ERR_ASYNC_BLOCKED);
137-
RESULT_GUARD(s2n_async_pkey_sign(complete_chain));
137+
RESULT_GUARD(s2n_async_pkey_sign_test(complete_chain));
138138
}
139139

140140
RESULT_ENSURE_EQ(client_conn->actual_protocol_version, expected_protocol_version);

0 commit comments

Comments
 (0)