aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/restricted/aws/s2n/tls/s2n_ktls.c
blob: ad25fd4bd68800476e0a5090175e5d8fb013e608 (plain) (blame)
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
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
/*
 * 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_ktls.h"

#include "crypto/s2n_ktls_crypto.h"
#include "tls/s2n_prf.h"
#include "tls/s2n_tls.h"

/* Used for overriding setsockopt calls in testing */
s2n_setsockopt_fn s2n_setsockopt = setsockopt;

S2N_RESULT s2n_ktls_set_setsockopt_cb(s2n_setsockopt_fn cb)
{
    RESULT_ENSURE(s2n_in_test(), S2N_ERR_NOT_IN_TEST);
    s2n_setsockopt = cb;
    return S2N_RESULT_OK;
}

bool s2n_ktls_is_supported_on_platform()
{
#if defined(S2N_KTLS_SUPPORTED)
    return true;
#else
    return false;
#endif
}

static int s2n_ktls_disabled_read(void *io_context, uint8_t *buf, uint32_t len)
{
    POSIX_BAIL(S2N_ERR_IO);
}

static S2N_RESULT s2n_ktls_validate(struct s2n_connection *conn, s2n_ktls_mode ktls_mode)
{
    RESULT_ENSURE_REF(conn);
    const struct s2n_config *config = conn->config;
    RESULT_ENSURE_REF(config);

    RESULT_ENSURE(s2n_ktls_is_supported_on_platform(), S2N_ERR_KTLS_UNSUPPORTED_PLATFORM);

    /* kTLS enable should only be called once the handshake has completed. */
    RESULT_ENSURE(is_handshake_complete(conn), S2N_ERR_HANDSHAKE_NOT_COMPLETE);

    /* TODO support TLS 1.3
     *
     * TLS 1.3 support requires sending the KeyUpdate message when the cryptographic
     * key usage limits are met. However, this is currently only possible by applying a
     * kernel patch to support this functionality.
     */
    RESULT_ENSURE(conn->actual_protocol_version == S2N_TLS12, S2N_ERR_KTLS_UNSUPPORTED_CONN);

    /* Check if the cipher supports kTLS */
    const struct s2n_cipher *cipher = NULL;
    RESULT_GUARD(s2n_connection_get_secure_cipher(conn, &cipher));
    RESULT_ENSURE_REF(cipher);
    RESULT_ENSURE(cipher->set_ktls_info, S2N_ERR_KTLS_UNSUPPORTED_CONN);

    /* Renegotiation requires updating the keys, which kTLS doesn't currently support.
     *
     * Setting the renegotiation callback doesn't guarantee that a client will
     * attempt to renegotiate. The callback can also be used to send warning alerts
     * signaling that renegotiation was rejected. However, we can provide applications
     * with a clearer signal earlier by preventing them from enabling ktls on a
     * connection that MIGHT require renegotiation. We can relax this restriction
     * later if necessary.
     */
    bool may_receive_hello_request = s2n_result_is_ok(s2n_client_hello_request_validate(conn));
    bool may_renegotiate = may_receive_hello_request && config->renegotiate_request_cb;
    RESULT_ENSURE(!may_renegotiate, S2N_ERR_KTLS_RENEG);

    /* kTLS I/O functionality is managed by s2n-tls. kTLS cannot be enabled if the
     * application sets custom I/O (managed_send_io == false means application has
     * set custom I/O).
     */
    switch (ktls_mode) {
        case S2N_KTLS_MODE_SEND:
            RESULT_ENSURE(conn->managed_send_io, S2N_ERR_KTLS_MANAGED_IO);
            /* The output stuffer should be empty before enabling kTLS. */
            RESULT_ENSURE(s2n_stuffer_data_available(&conn->out) == 0, S2N_ERR_RECORD_STUFFER_NEEDS_DRAINING);
            break;
        case S2N_KTLS_MODE_RECV:
            RESULT_ENSURE(conn->managed_recv_io, S2N_ERR_KTLS_MANAGED_IO);
            /* The input stuffer should be empty before enabling kTLS. */
            RESULT_ENSURE(s2n_stuffer_data_available(&conn->in) == 0, S2N_ERR_RECORD_STUFFER_NEEDS_DRAINING);
            break;
        default:
            RESULT_BAIL(S2N_ERR_SAFETY);
            break;
    }

    return S2N_RESULT_OK;
}

/* Enabling kTLS preserves the original *io_context; making this functions
 * safe to call even after kTLS has been enabled on the connection.
 *
 * Retrieving fd assumes that the connection is using socket IO and has the
 * send_io_context set. While kTLS overrides IO and essentially disables
 * the socket conn->send function callback, it doesn't modify the
 * send_io_context. */
S2N_RESULT s2n_ktls_get_file_descriptor(struct s2n_connection *conn, s2n_ktls_mode ktls_mode, int *fd)
{
    RESULT_ENSURE_REF(conn);
    RESULT_ENSURE_REF(fd);

    if (ktls_mode == S2N_KTLS_MODE_RECV) {
        RESULT_GUARD_POSIX(s2n_connection_get_read_fd(conn, fd));
    } else if (ktls_mode == S2N_KTLS_MODE_SEND) {
        RESULT_GUARD_POSIX(s2n_connection_get_write_fd(conn, fd));
    }
    return S2N_RESULT_OK;
}

static S2N_RESULT s2n_ktls_get_io_mode(s2n_ktls_mode ktls_mode, int *tls_tx_rx_mode)
{
    RESULT_ENSURE_REF(tls_tx_rx_mode);

    if (ktls_mode == S2N_KTLS_MODE_SEND) {
        *tls_tx_rx_mode = S2N_TLS_TX;
    } else {
        *tls_tx_rx_mode = S2N_TLS_RX;
    }
    return S2N_RESULT_OK;
}

static S2N_RESULT s2n_ktls_crypto_info_init(struct s2n_connection *conn, s2n_ktls_mode ktls_mode,
        struct s2n_ktls_crypto_info *crypto_info)
{
    RESULT_ENSURE_REF(conn);
    struct s2n_crypto_parameters *secure = conn->secure;
    RESULT_ENSURE_REF(secure);

    /* In order to avoid storing the encryption keys on the connection, we instead
     * regenerate them when required by ktls.
     *
     * s2n_key_material also includes an IV, but we should use the IV stored
     * on the connection instead. Some record algorithms (like CBC) mutate the
     * "implicit_iv" when writing records, so the IV may change after generation.
     */
    struct s2n_key_material key_material = { 0 };
    RESULT_GUARD(s2n_prf_generate_key_material(conn, &key_material));

    bool is_sending_key = (ktls_mode == S2N_KTLS_MODE_SEND);
    s2n_mode key_mode = (is_sending_key) ? conn->mode : S2N_PEER_MODE(conn->mode);

    struct s2n_ktls_crypto_info_inputs inputs = { 0 };
    if (key_mode == S2N_CLIENT) {
        inputs.key = key_material.client_key;
        RESULT_GUARD_POSIX(s2n_blob_init(&inputs.iv,
                secure->client_implicit_iv, sizeof(secure->client_implicit_iv)));
        RESULT_GUARD_POSIX(s2n_blob_init(&inputs.seq,
                secure->client_sequence_number, sizeof(secure->client_sequence_number)));
    } else {
        inputs.key = key_material.server_key;
        RESULT_GUARD_POSIX(s2n_blob_init(&inputs.iv,
                secure->server_implicit_iv, sizeof(secure->server_implicit_iv)));
        RESULT_GUARD_POSIX(s2n_blob_init(&inputs.seq,
                secure->server_sequence_number, sizeof(secure->server_sequence_number)));
    }

    const struct s2n_cipher *cipher = NULL;
    RESULT_GUARD(s2n_connection_get_secure_cipher(conn, &cipher));
    RESULT_ENSURE_REF(cipher);
    RESULT_ENSURE_REF(cipher->set_ktls_info);
    RESULT_GUARD(cipher->set_ktls_info(&inputs, crypto_info));

    return S2N_RESULT_OK;
}

/* This method intentionally returns void because it may NOT perform any fallible
 * operations. See s2n_connection_ktls_enable.
 */
void s2n_ktls_configure_connection(struct s2n_connection *conn, s2n_ktls_mode ktls_mode)
{
    if (conn == NULL) {
        return;
    }
    if (ktls_mode == S2N_KTLS_MODE_SEND) {
        conn->ktls_send_enabled = true;
        conn->send = s2n_ktls_send_cb;
    } else {
        conn->ktls_recv_enabled = true;
        conn->recv = s2n_ktls_disabled_read;
    }
}

static S2N_RESULT s2n_connection_ktls_enable(struct s2n_connection *conn, s2n_ktls_mode ktls_mode)
{
    RESULT_ENSURE_REF(conn);
    RESULT_GUARD(s2n_ktls_validate(conn, ktls_mode));

    int fd = 0;
    RESULT_GUARD(s2n_ktls_get_file_descriptor(conn, ktls_mode, &fd));

    /* This call doesn't actually enable ktls or modify the IO behavior of the socket.
     * Instead, this is just a prerequisite for calling setsockopt with SOL_TLS.
     *
     * We intentionally ignore the result of this call. It may fail because ktls
     * is not supported, but it might also fail because ktls has already been enabled
     * for the socket. If SOL_TLS isn't enabled on the socket, our next call to
     * setsockopt with SOL_TLS will also fail, and we DO check that result.
     */
    s2n_setsockopt(fd, S2N_SOL_TCP, S2N_TCP_ULP, S2N_TLS_ULP_NAME, S2N_TLS_ULP_NAME_SIZE);

    int tls_tx_rx_mode = 0;
    RESULT_GUARD(s2n_ktls_get_io_mode(ktls_mode, &tls_tx_rx_mode));

    struct s2n_ktls_crypto_info crypto_info = { 0 };
    RESULT_GUARD(s2n_ktls_crypto_info_init(conn, ktls_mode, &crypto_info));

    /* If this call succeeds, then ktls is enabled for that io mode and will be offloaded */
    int ret = s2n_setsockopt(fd, S2N_SOL_TLS, tls_tx_rx_mode, crypto_info.value.data, crypto_info.value.size);
    RESULT_ENSURE(ret == 0, S2N_ERR_KTLS_ENABLE);

    /* At this point, ktls is enabled on the socket for the requested IO mode.
     * No further fallible operations may be performed, or else the caller may
     * incorrectly assume that enabling ktls failed and they should therefore
     * fall back to using application layer TLS.
     *
     * That means no calls to RESULT_ENSURE, RESULT_GUARD, etc. after this point.
     */

    s2n_ktls_configure_connection(conn, ktls_mode);
    return S2N_RESULT_OK;
}

int s2n_connection_ktls_enable_send(struct s2n_connection *conn)
{
    POSIX_ENSURE_REF(conn);

    /* If already enabled then return success */
    if (conn->ktls_send_enabled) {
        return S2N_SUCCESS;
    }

    POSIX_GUARD_RESULT(s2n_connection_ktls_enable(conn, S2N_KTLS_MODE_SEND));
    return S2N_SUCCESS;
}

int s2n_connection_ktls_enable_recv(struct s2n_connection *conn)
{
    POSIX_ENSURE_REF(conn);

    /* If already enabled then return success */
    if (conn->ktls_recv_enabled) {
        return S2N_SUCCESS;
    }

    POSIX_GUARD_RESULT(s2n_connection_ktls_enable(conn, S2N_KTLS_MODE_RECV));
    return S2N_SUCCESS;
}