1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
|
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
#include "tls/s2n_renegotiate.h"
#include "error/s2n_errno.h"
#include "stuffer/s2n_stuffer.h"
#include "tls/s2n_connection.h"
#include "utils/s2n_safety.h"
/* We don't want to introduce a new blocked status for renegotiation
* because that would potentially require applications to update their
* blocked status handling logic for no reason.
*
* It is impossible to use both early data and renegotiation for the same handshake,
* and both are just application data received during the handshake.
* Therefore, we will alias S2N_BLOCKED_ON_EARLY_DATA for reuse with renegotiation.
*/
const s2n_blocked_status S2N_BLOCKED_ON_APPLICATION_DATA = S2N_BLOCKED_ON_EARLY_DATA;
S2N_RESULT s2n_renegotiate_validate(struct s2n_connection *conn)
{
RESULT_ENSURE_REF(conn);
RESULT_ENSURE(conn->mode == S2N_CLIENT, S2N_ERR_NO_RENEGOTIATION);
RESULT_ENSURE(conn->secure_renegotiation, S2N_ERR_NO_RENEGOTIATION);
RESULT_ENSURE(conn->handshake.renegotiation, S2N_ERR_INVALID_STATE);
RESULT_ENSURE(!conn->ktls_send_enabled, S2N_ERR_KTLS_RENEG);
RESULT_ENSURE(!conn->ktls_recv_enabled, S2N_ERR_KTLS_RENEG);
return S2N_RESULT_OK;
}
/*
* Prepare a connection to be reused for a second handshake.
*
* s2n-tls was not originally designed to support renegotiation.
* s2n_connection is a very large structure with configuration fields set by the application
* mixed in with internal state fields set by the handshake.
* Ensuring all existing internal state fields (and any new fields added) are safe to reuse for
* a renegotiated handshake would be extremely prone to errors.
* For safety, we instead wipe the entire connection and only restore fields we know we need
* in order to continue sending and receiving encrypted data.
*
* Any configuration fields set by the application will need to be set by the application again.
*/
int s2n_renegotiate_wipe(struct s2n_connection *conn)
{
POSIX_ENSURE_REF(conn);
/* We use this method to reset both clients and servers when testing.
* However, outside of tests, it should only be called for client connections
* because we only support renegotiation for clients.
*/
POSIX_ENSURE(conn->mode == S2N_CLIENT || s2n_in_unit_test(), S2N_ERR_NO_RENEGOTIATION);
/* Best effort check for pending input or output data.
* This method should not be called until the application has stopped sending and receiving.
* Saving partial read or parital write state would complicate this problem.
*/
POSIX_ENSURE(s2n_stuffer_data_available(&conn->header_in) == 0, S2N_ERR_INVALID_STATE);
POSIX_ENSURE(s2n_stuffer_data_available(&conn->in) == 0, S2N_ERR_INVALID_STATE);
POSIX_ENSURE(s2n_stuffer_data_available(&conn->out) == 0, S2N_ERR_INVALID_STATE);
/* Save the crypto parameters.
* We need to continue encrypting / decrypting with the old secure parameters.
*/
DEFER_CLEANUP(struct s2n_crypto_parameters *secure_crypto_params = conn->secure,
s2n_crypto_parameters_free);
conn->secure = NULL;
/* Save the fragment length so we continue properly fragmenting our records
* until a new fragment length is chosen.
*/
uint16_t max_frag_len = conn->max_outgoing_fragment_length;
/* Save the protocol versions.
* Various checks when sending and receiving records rely on the protocol version. */
uint8_t actual_protocol_version = conn->actual_protocol_version;
uint8_t server_protocol_version = conn->server_protocol_version;
uint8_t client_protocol_version = conn->client_protocol_version;
POSIX_ENSURE(actual_protocol_version < S2N_TLS13, S2N_ERR_PROTOCOL_VERSION_UNSUPPORTED);
/* Save byte tracking.
* This isn't strictly necessary, but potentially useful. */
uint64_t wire_bytes_in = conn->wire_bytes_in;
uint64_t wire_bytes_out = conn->wire_bytes_out;
/* Save io settings */
bool send_managed = conn->managed_send_io;
s2n_send_fn *send_fn = conn->send;
void *send_ctx = conn->send_io_context;
bool recv_managed = conn->managed_recv_io;
s2n_recv_fn *recv_fn = conn->recv;
void *recv_ctx = conn->recv_io_context;
/* Treat IO as unmanaged, since we don't want to clean it up yet */
conn->managed_send_io = false;
conn->managed_recv_io = false;
/* Save the secure_renegotiation flag.
* This flag should always be true, since we don't support insecure renegotiation,
* but copying its value seems safer than just setting it to 'true'.
*/
bool secure_renegotiation = conn->secure_renegotiation;
POSIX_ENSURE(secure_renegotiation, S2N_ERR_NO_RENEGOTIATION);
/* Save the finished data.
* This is required for the renegotiate_info extension on the new handshake.
*/
uint8_t finished_len = conn->handshake.finished_len;
uint8_t client_finished[sizeof(conn->handshake.client_finished)] = { 0 };
POSIX_CHECKED_MEMCPY(client_finished, conn->handshake.client_finished, finished_len);
uint8_t server_finished[sizeof(conn->handshake.server_finished)] = { 0 };
POSIX_CHECKED_MEMCPY(server_finished, conn->handshake.server_finished, finished_len);
POSIX_GUARD(s2n_connection_wipe(conn));
/* Setup the new crypto parameters.
* The new handshake will negotiate new secure crypto parameters,
* so the current secure crypto parameters become the initial crypto parameters.
*/
POSIX_GUARD_RESULT(s2n_crypto_parameters_free(&conn->initial));
conn->initial = secure_crypto_params;
ZERO_TO_DISABLE_DEFER_CLEANUP(secure_crypto_params);
conn->client = conn->initial;
conn->server = conn->initial;
/* Restore saved values */
POSIX_GUARD_RESULT(s2n_connection_set_max_fragment_length(conn, max_frag_len));
POSIX_CHECKED_MEMCPY(conn->handshake.client_finished, client_finished, finished_len);
POSIX_CHECKED_MEMCPY(conn->handshake.server_finished, server_finished, finished_len);
conn->handshake.finished_len = finished_len;
conn->actual_protocol_version = actual_protocol_version;
conn->server_protocol_version = server_protocol_version;
conn->client_protocol_version = client_protocol_version;
conn->wire_bytes_in = wire_bytes_in;
conn->wire_bytes_out = wire_bytes_out;
conn->managed_send_io = send_managed;
conn->send = send_fn;
conn->send_io_context = send_ctx;
conn->managed_recv_io = recv_managed;
conn->recv = recv_fn;
conn->recv_io_context = recv_ctx;
conn->secure_renegotiation = secure_renegotiation;
conn->handshake.renegotiation = true;
return S2N_SUCCESS;
}
static S2N_RESULT s2n_renegotiate_read_app_data(struct s2n_connection *conn, uint8_t *app_data_buf, ssize_t app_data_buf_size,
ssize_t *app_data_size, s2n_blocked_status *blocked)
{
RESULT_ENSURE_REF(blocked);
ssize_t r = s2n_recv(conn, app_data_buf, app_data_buf_size, blocked);
RESULT_GUARD_POSIX(r);
*app_data_size = r;
*blocked = S2N_BLOCKED_ON_APPLICATION_DATA;
RESULT_BAIL(S2N_ERR_APP_DATA_BLOCKED);
}
int s2n_renegotiate(struct s2n_connection *conn, uint8_t *app_data_buf, ssize_t app_data_buf_size,
ssize_t *app_data_size, s2n_blocked_status *blocked)
{
POSIX_GUARD_RESULT(s2n_renegotiate_validate(conn));
POSIX_ENSURE_REF(app_data_size);
*app_data_size = 0;
/* If there is outstanding application data, pass it back to the application.
* We can't read the next handshake record until we drain the buffer.
*/
if (s2n_peek(conn) > 0) {
POSIX_GUARD_RESULT(s2n_renegotiate_read_app_data(conn,
app_data_buf, app_data_buf_size, app_data_size, blocked));
}
int result = s2n_negotiate(conn, blocked);
/* If we encounter application data while reading handshake records,
* pass it back to the application.
*/
if (result != S2N_SUCCESS && s2n_errno == S2N_ERR_APP_DATA_BLOCKED) {
POSIX_GUARD_RESULT(s2n_renegotiate_read_app_data(conn,
app_data_buf, app_data_buf_size, app_data_size, blocked));
}
return result;
}
|