iodine 0.7.16 → 0.7.17
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of iodine might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.travis.yml +5 -4
- data/.yardopts +8 -0
- data/CHANGELOG.md +26 -0
- data/LICENSE.txt +1 -1
- data/LIMITS.md +6 -0
- data/README.md +93 -13
- data/{SPEC-Websocket-Draft.md → SPEC-WebSocket-Draft.md} +0 -0
- data/examples/tcp_client.rb +66 -0
- data/examples/x-sendfile.ru +14 -0
- data/exe/iodine +3 -3
- data/ext/iodine/extconf.rb +21 -0
- data/ext/iodine/fio.c +659 -69
- data/ext/iodine/fio.h +350 -95
- data/ext/iodine/fio_cli.c +4 -3
- data/ext/iodine/fio_json_parser.h +1 -1
- data/ext/iodine/fio_siphash.c +13 -11
- data/ext/iodine/fio_siphash.h +6 -3
- data/ext/iodine/fio_tls.h +129 -0
- data/ext/iodine/fio_tls_missing.c +634 -0
- data/ext/iodine/fio_tls_openssl.c +1011 -0
- data/ext/iodine/fio_tmpfile.h +1 -1
- data/ext/iodine/fiobj.h +1 -1
- data/ext/iodine/fiobj_ary.c +1 -1
- data/ext/iodine/fiobj_ary.h +1 -1
- data/ext/iodine/fiobj_data.c +1 -1
- data/ext/iodine/fiobj_data.h +1 -1
- data/ext/iodine/fiobj_hash.c +1 -1
- data/ext/iodine/fiobj_hash.h +1 -1
- data/ext/iodine/fiobj_json.c +18 -16
- data/ext/iodine/fiobj_json.h +1 -1
- data/ext/iodine/fiobj_mustache.c +4 -0
- data/ext/iodine/fiobj_mustache.h +4 -0
- data/ext/iodine/fiobj_numbers.c +1 -1
- data/ext/iodine/fiobj_numbers.h +1 -1
- data/ext/iodine/fiobj_str.c +3 -3
- data/ext/iodine/fiobj_str.h +1 -1
- data/ext/iodine/fiobject.c +1 -1
- data/ext/iodine/fiobject.h +8 -2
- data/ext/iodine/http.c +128 -337
- data/ext/iodine/http.h +11 -18
- data/ext/iodine/http1.c +6 -6
- data/ext/iodine/http1.h +1 -1
- data/ext/iodine/http1_parser.c +1 -1
- data/ext/iodine/http1_parser.h +1 -1
- data/ext/iodine/http_internal.c +10 -8
- data/ext/iodine/http_internal.h +13 -3
- data/ext/iodine/http_mime_parser.h +1 -1
- data/ext/iodine/iodine.c +806 -22
- data/ext/iodine/iodine.h +33 -0
- data/ext/iodine/iodine_connection.c +23 -18
- data/ext/iodine/iodine_http.c +239 -225
- data/ext/iodine/iodine_http.h +4 -1
- data/ext/iodine/iodine_mustache.c +59 -54
- data/ext/iodine/iodine_pubsub.c +1 -1
- data/ext/iodine/iodine_tcp.c +34 -100
- data/ext/iodine/iodine_tcp.h +4 -0
- data/ext/iodine/iodine_tls.c +267 -0
- data/ext/iodine/iodine_tls.h +13 -0
- data/ext/iodine/mustache_parser.h +1 -1
- data/ext/iodine/redis_engine.c +14 -6
- data/ext/iodine/redis_engine.h +1 -1
- data/ext/iodine/resp_parser.h +1 -1
- data/ext/iodine/websocket_parser.h +1 -1
- data/ext/iodine/websockets.c +1 -1
- data/ext/iodine/websockets.h +1 -1
- data/iodine.gemspec +2 -1
- data/lib/iodine.rb +19 -5
- data/lib/iodine/connection.rb +13 -0
- data/lib/iodine/mustache.rb +7 -24
- data/lib/iodine/tls.rb +16 -0
- data/lib/iodine/version.rb +1 -1
- data/lib/rack/handler/iodine.rb +1 -1
- metadata +15 -5
@@ -0,0 +1,1011 @@
|
|
1
|
+
/*
|
2
|
+
Copyright: Boaz Segev, 2018-2019
|
3
|
+
License: MIT
|
4
|
+
|
5
|
+
Feel free to copy, use and enjoy according to the license provided.
|
6
|
+
*/
|
7
|
+
#include <fio.h>
|
8
|
+
|
9
|
+
/**
|
10
|
+
* This implementation of the facil.io SSL/TLS wrapper API wraps the OpenSSL API
|
11
|
+
* to provide TLS 1.2 and TLS 1.3 to facil.io applications.
|
12
|
+
*
|
13
|
+
* The implementation requires `HAVE_OPENSSL` to be set.
|
14
|
+
*/
|
15
|
+
#include "fio_tls.h"
|
16
|
+
|
17
|
+
#if HAVE_OPENSSL
|
18
|
+
#include <openssl/bio.h>
|
19
|
+
#include <openssl/err.h>
|
20
|
+
#include <openssl/ssl.h>
|
21
|
+
|
22
|
+
#define REQUIRE_LIBRARY()
|
23
|
+
#define FIO_TLS_WEAK
|
24
|
+
|
25
|
+
/* *****************************************************************************
|
26
|
+
The SSL/TLS helper data types (can be left as is)
|
27
|
+
***************************************************************************** */
|
28
|
+
#define FIO_INCLUDE_STR 1
|
29
|
+
#define FIO_FORCE_MALLOC_TMP 1
|
30
|
+
#include <fio.h>
|
31
|
+
|
32
|
+
typedef struct {
|
33
|
+
fio_str_s private_key;
|
34
|
+
fio_str_s public_key;
|
35
|
+
fio_str_s password;
|
36
|
+
} cert_s;
|
37
|
+
|
38
|
+
static inline int fio_tls_cert_cmp(const cert_s *dest, const cert_s *src) {
|
39
|
+
return fio_str_iseq(&dest->private_key, &src->private_key);
|
40
|
+
}
|
41
|
+
static inline void fio_tls_cert_copy(cert_s *dest, cert_s *src) {
|
42
|
+
*dest = (cert_s){
|
43
|
+
.private_key = FIO_STR_INIT,
|
44
|
+
.public_key = FIO_STR_INIT,
|
45
|
+
.password = FIO_STR_INIT,
|
46
|
+
};
|
47
|
+
fio_str_concat(&dest->private_key, &src->private_key);
|
48
|
+
fio_str_concat(&dest->public_key, &src->public_key);
|
49
|
+
fio_str_concat(&dest->password, &src->password);
|
50
|
+
}
|
51
|
+
static inline void fio_tls_cert_destroy(cert_s *obj) {
|
52
|
+
fio_str_free(&obj->private_key);
|
53
|
+
fio_str_free(&obj->public_key);
|
54
|
+
fio_str_free(&obj->password);
|
55
|
+
}
|
56
|
+
|
57
|
+
#define FIO_ARY_NAME cert_ary
|
58
|
+
#define FIO_ARY_TYPE cert_s
|
59
|
+
#define FIO_ARY_COMPARE(k1, k2) (fio_tls_cert_cmp(&(k1), &(k2)))
|
60
|
+
#define FIO_ARY_COPY(dest, obj) fio_tls_cert_copy(&(dest), &(obj))
|
61
|
+
#define FIO_ARY_DESTROY(key) fio_tls_cert_destroy(&(key))
|
62
|
+
#define FIO_FORCE_MALLOC_TMP 1
|
63
|
+
#include <fio.h>
|
64
|
+
|
65
|
+
typedef struct {
|
66
|
+
fio_str_s pem;
|
67
|
+
} trust_s;
|
68
|
+
|
69
|
+
static inline int fio_tls_trust_cmp(const trust_s *dest, const trust_s *src) {
|
70
|
+
return fio_str_iseq(&dest->pem, &src->pem);
|
71
|
+
}
|
72
|
+
static inline void fio_tls_trust_copy(trust_s *dest, trust_s *src) {
|
73
|
+
*dest = (trust_s){
|
74
|
+
.pem = FIO_STR_INIT,
|
75
|
+
};
|
76
|
+
fio_str_concat(&dest->pem, &src->pem);
|
77
|
+
}
|
78
|
+
static inline void fio_tls_trust_destroy(trust_s *obj) {
|
79
|
+
fio_str_free(&obj->pem);
|
80
|
+
}
|
81
|
+
|
82
|
+
#define FIO_ARY_NAME trust_ary
|
83
|
+
#define FIO_ARY_TYPE trust_s
|
84
|
+
#define FIO_ARY_COMPARE(k1, k2) (fio_tls_trust_cmp(&(k1), &(k2)))
|
85
|
+
#define FIO_ARY_COPY(dest, obj) fio_tls_trust_copy(&(dest), &(obj))
|
86
|
+
#define FIO_ARY_DESTROY(key) fio_tls_trust_destroy(&(key))
|
87
|
+
#define FIO_FORCE_MALLOC_TMP 1
|
88
|
+
#include <fio.h>
|
89
|
+
|
90
|
+
typedef struct {
|
91
|
+
fio_str_s name; /* fio_str_s provides cache locality for small strings */
|
92
|
+
void (*on_selected)(intptr_t uuid, void *udata_connection, void *udata_tls);
|
93
|
+
void *udata_tls;
|
94
|
+
void (*on_cleanup)(void *udata_tls);
|
95
|
+
} alpn_s;
|
96
|
+
|
97
|
+
static inline int fio_alpn_cmp(const alpn_s *dest, const alpn_s *src) {
|
98
|
+
return fio_str_iseq(&dest->name, &src->name);
|
99
|
+
}
|
100
|
+
static inline void fio_alpn_copy(alpn_s *dest, alpn_s *src) {
|
101
|
+
*dest = (alpn_s){
|
102
|
+
.name = FIO_STR_INIT,
|
103
|
+
.on_selected = src->on_selected,
|
104
|
+
.udata_tls = src->udata_tls,
|
105
|
+
.on_cleanup = src->on_cleanup,
|
106
|
+
};
|
107
|
+
fio_str_concat(&dest->name, &src->name);
|
108
|
+
}
|
109
|
+
static inline void fio_alpn_destroy(alpn_s *obj) {
|
110
|
+
if (obj->on_cleanup)
|
111
|
+
obj->on_cleanup(obj->udata_tls);
|
112
|
+
fio_str_free(&obj->name);
|
113
|
+
}
|
114
|
+
|
115
|
+
#define FIO_SET_NAME alpn_list
|
116
|
+
#define FIO_SET_OBJ_TYPE alpn_s
|
117
|
+
#define FIO_SET_OBJ_COMPARE(k1, k2) fio_alpn_cmp(&(k1), &(k2))
|
118
|
+
#define FIO_SET_OBJ_COPY(dest, obj) fio_alpn_copy(&(dest), &(obj))
|
119
|
+
#define FIO_SET_OBJ_DESTROY(key) fio_alpn_destroy(&(key))
|
120
|
+
#define FIO_FORCE_MALLOC_TMP 1
|
121
|
+
#include <fio.h>
|
122
|
+
|
123
|
+
/* *****************************************************************************
|
124
|
+
The SSL/TLS type
|
125
|
+
***************************************************************************** */
|
126
|
+
|
127
|
+
/** An opaque type used for the SSL/TLS functions. */
|
128
|
+
struct fio_tls_s {
|
129
|
+
size_t ref; /* Reference counter, to guards the ALPN registry */
|
130
|
+
alpn_list_s alpn; /* ALPN is the name for the protocol selection extension */
|
131
|
+
|
132
|
+
/*** the next two components could be optimized away with tweaking stuff ***/
|
133
|
+
|
134
|
+
cert_ary_s sni; /* SNI (server name extension) stores ID certificates */
|
135
|
+
trust_ary_s trust; /* Trusted certificate registry (peer verification) */
|
136
|
+
|
137
|
+
/************ TODO: implementation data fields go here ******************/
|
138
|
+
|
139
|
+
SSL_CTX *ctx; /* The Open SSL context (updated each time). */
|
140
|
+
unsigned char *alpn_str; /* the computed server-format ALPN string */
|
141
|
+
int alpn_len;
|
142
|
+
};
|
143
|
+
|
144
|
+
/* *****************************************************************************
|
145
|
+
ALPN Helpers
|
146
|
+
***************************************************************************** */
|
147
|
+
|
148
|
+
/** Returns a pointer to the ALPN data (callback, etc') IF exists in the TLS. */
|
149
|
+
FIO_FUNC inline alpn_s *alpn_find(fio_tls_s *tls, char *name, size_t len) {
|
150
|
+
alpn_s tmp = {.name = FIO_STR_INIT_STATIC2(name, len)};
|
151
|
+
alpn_list__map_s_ *pos =
|
152
|
+
alpn_list__find_map_pos_(&tls->alpn, fio_str_hash(&tmp.name), tmp);
|
153
|
+
if (!pos || !pos->pos)
|
154
|
+
return NULL;
|
155
|
+
return &pos->pos->obj;
|
156
|
+
}
|
157
|
+
|
158
|
+
/** Adds an ALPN data object to the ALPN "list" (set) */
|
159
|
+
FIO_FUNC inline void alpn_add(
|
160
|
+
fio_tls_s *tls, const char *protocol_name,
|
161
|
+
void (*on_selected)(intptr_t uuid, void *udata_connection, void *udata_tls),
|
162
|
+
void *udata_tls, void (*on_cleanup)(void *udata_tls)) {
|
163
|
+
alpn_s tmp = {
|
164
|
+
.name = FIO_STR_INIT_STATIC(protocol_name),
|
165
|
+
.on_selected = on_selected,
|
166
|
+
.udata_tls = udata_tls,
|
167
|
+
.on_cleanup = on_cleanup,
|
168
|
+
};
|
169
|
+
if (fio_str_len(&tmp.name) > 255) {
|
170
|
+
FIO_LOG_ERROR("ALPN protocol names are limited to 255 bytes.");
|
171
|
+
return;
|
172
|
+
}
|
173
|
+
alpn_list_overwrite(&tls->alpn, fio_str_hash(&tmp.name), tmp, NULL);
|
174
|
+
tmp.on_cleanup = NULL;
|
175
|
+
fio_alpn_destroy(&tmp);
|
176
|
+
}
|
177
|
+
|
178
|
+
/** Returns a pointer to the default (first) ALPN object in the TLS (if any). */
|
179
|
+
FIO_FUNC inline alpn_s *alpn_default(fio_tls_s *tls) {
|
180
|
+
if (!tls || !alpn_list_count(&tls->alpn) || !tls->alpn.ordered)
|
181
|
+
return NULL;
|
182
|
+
return &tls->alpn.ordered[0].obj;
|
183
|
+
}
|
184
|
+
|
185
|
+
typedef struct {
|
186
|
+
alpn_s alpn;
|
187
|
+
intptr_t uuid;
|
188
|
+
void *udata_connection;
|
189
|
+
} alpn_task_s;
|
190
|
+
|
191
|
+
FIO_FUNC inline void alpn_select___task(void *t_, void *ignr_) {
|
192
|
+
alpn_task_s *t = t_;
|
193
|
+
t->alpn.on_selected((fio_is_valid(t->uuid) ? t->uuid : -1),
|
194
|
+
t->udata_connection, t->alpn.udata_tls);
|
195
|
+
fio_free(t);
|
196
|
+
(void)ignr_;
|
197
|
+
}
|
198
|
+
|
199
|
+
/** Schedules the ALPN protocol callback. */
|
200
|
+
FIO_FUNC inline void alpn_select(alpn_s *alpn, intptr_t uuid,
|
201
|
+
void *udata_connection) {
|
202
|
+
if (!alpn || !alpn->on_selected)
|
203
|
+
return;
|
204
|
+
alpn_task_s *t = fio_malloc(sizeof(*t));
|
205
|
+
*t = (alpn_task_s){
|
206
|
+
.alpn = *alpn,
|
207
|
+
.uuid = uuid,
|
208
|
+
.udata_connection = udata_connection,
|
209
|
+
};
|
210
|
+
fio_defer(alpn_select___task, t, NULL);
|
211
|
+
}
|
212
|
+
|
213
|
+
/* *****************************************************************************
|
214
|
+
OpenSSL Helpers
|
215
|
+
***************************************************************************** */
|
216
|
+
|
217
|
+
static EVP_PKEY *fio_tls_pkey = NULL;
|
218
|
+
|
219
|
+
static void fio_tls_clear_root_key(void *key) {
|
220
|
+
EVP_PKEY_free(key);
|
221
|
+
fio_tls_pkey = NULL;
|
222
|
+
}
|
223
|
+
|
224
|
+
static void fio_tls_make_root_key(void) {
|
225
|
+
static fio_lock_i lock = FIO_LOCK_INIT;
|
226
|
+
fio_lock(&lock);
|
227
|
+
if (fio_tls_pkey)
|
228
|
+
goto finish;
|
229
|
+
/* create private key, free it at exit */
|
230
|
+
FIO_LOG_DEBUG("calculating a new TLS private key... might take a while.");
|
231
|
+
|
232
|
+
fio_tls_pkey = EVP_PKEY_new();
|
233
|
+
FIO_ASSERT(fio_tls_pkey, "OpenSSL failed to create private key.");
|
234
|
+
|
235
|
+
/* TODO: replace RSA with something else? is there something else? */
|
236
|
+
RSA *rsa = RSA_new();
|
237
|
+
BIGNUM *e = BN_new();
|
238
|
+
BN_clear(e);
|
239
|
+
BN_add_word(e, 65537);
|
240
|
+
FIO_ASSERT_ALLOC(e);
|
241
|
+
FIO_ASSERT(RSA_generate_key_ex(rsa, 2048, e, NULL),
|
242
|
+
"OpenSSL failed to create RSA key.");
|
243
|
+
BN_free(e);
|
244
|
+
EVP_PKEY_assign_RSA(fio_tls_pkey, rsa);
|
245
|
+
fio_state_callback_add(FIO_CALL_AT_EXIT, fio_tls_clear_root_key,
|
246
|
+
fio_tls_pkey);
|
247
|
+
finish:
|
248
|
+
fio_unlock(&lock);
|
249
|
+
}
|
250
|
+
|
251
|
+
static X509 *fio_tls_create_self_signed(char *server_name) {
|
252
|
+
X509 *cert = X509_new();
|
253
|
+
static uint32_t counter = 0;
|
254
|
+
FIO_ASSERT(cert,
|
255
|
+
"OpenSSL failed to allocate memory for self-signed ceritifcate.");
|
256
|
+
fio_tls_make_root_key();
|
257
|
+
|
258
|
+
/* serial number */
|
259
|
+
fio_atomic_add(&counter, 1);
|
260
|
+
ASN1_INTEGER_set(X509_get_serialNumber(cert), counter);
|
261
|
+
|
262
|
+
/* validity (180 days) */
|
263
|
+
X509_gmtime_adj(X509_get_notBefore(cert), 0);
|
264
|
+
X509_gmtime_adj(X509_get_notAfter(cert), 15552000L);
|
265
|
+
|
266
|
+
/* set (public) key */
|
267
|
+
X509_set_pubkey(cert, fio_tls_pkey);
|
268
|
+
|
269
|
+
/* set identity details */
|
270
|
+
X509_NAME *s = X509_get_subject_name(cert);
|
271
|
+
size_t srv_name_len = strlen(server_name);
|
272
|
+
X509_NAME_add_entry_by_txt(s, "O", MBSTRING_ASC, (unsigned char *)server_name,
|
273
|
+
srv_name_len, -1, 0);
|
274
|
+
X509_NAME_add_entry_by_txt(s, "CN", MBSTRING_ASC,
|
275
|
+
(unsigned char *)server_name, srv_name_len, -1, 0);
|
276
|
+
X509_NAME_add_entry_by_txt(s, "CA", MBSTRING_ASC,
|
277
|
+
(unsigned char *)server_name, srv_name_len, -1, 0);
|
278
|
+
X509_set_issuer_name(cert, s);
|
279
|
+
|
280
|
+
/* sign certificate */
|
281
|
+
FIO_ASSERT(X509_sign(cert, fio_tls_pkey, EVP_sha512()),
|
282
|
+
"OpenSSL failed to signe self-signed certificate");
|
283
|
+
// FILE *fp = fopen("tmp.pem", "ab+");
|
284
|
+
// if (fp) {
|
285
|
+
// PEM_write_X509(fp, cert);
|
286
|
+
// fclose(fp);
|
287
|
+
// }
|
288
|
+
|
289
|
+
return cert;
|
290
|
+
}
|
291
|
+
|
292
|
+
/* *****************************************************************************
|
293
|
+
SSL/TLS Context (re)-building
|
294
|
+
***************************************************************************** */
|
295
|
+
|
296
|
+
#define TLS_BUFFER_LENGTH (1 << 15)
|
297
|
+
typedef struct {
|
298
|
+
SSL *ssl;
|
299
|
+
fio_tls_s *tls;
|
300
|
+
void *alpn_arg;
|
301
|
+
intptr_t uuid;
|
302
|
+
uint8_t is_server;
|
303
|
+
volatile uint8_t alpn_ok;
|
304
|
+
} fio_tls_connection_s;
|
305
|
+
|
306
|
+
static void fio_tls_alpn_fallback(fio_tls_connection_s *c) {
|
307
|
+
alpn_s *alpn = alpn_default(c->tls);
|
308
|
+
if (!alpn || !alpn->on_selected)
|
309
|
+
return;
|
310
|
+
/* set protocol to default protocol */
|
311
|
+
FIO_LOG_DEBUG("TLS ALPN handshake missing, falling back on %s for %p",
|
312
|
+
fio_str_info(&alpn->name).data, (void *)c->uuid);
|
313
|
+
alpn_select(alpn, c->uuid, c->alpn_arg);
|
314
|
+
}
|
315
|
+
static int fio_tls_alpn_selector_cb(SSL *ssl, const unsigned char **out,
|
316
|
+
unsigned char *outlen,
|
317
|
+
const unsigned char *in, unsigned int inlen,
|
318
|
+
void *tls_) {
|
319
|
+
fio_tls_s *tls = tls_;
|
320
|
+
alpn_s *alpn;
|
321
|
+
/* TODO: select ALPN and call on_selected */
|
322
|
+
fio_tls_connection_s *c = SSL_get_ex_data(ssl, 0);
|
323
|
+
c->alpn_ok = 1;
|
324
|
+
|
325
|
+
if (alpn_list_count(&tls->alpn) == 0)
|
326
|
+
return SSL_TLSEXT_ERR_NOACK;
|
327
|
+
const unsigned char *end = in + inlen;
|
328
|
+
while (in < end) {
|
329
|
+
uint8_t l = in[0];
|
330
|
+
alpn = alpn_find(tls, (char *)in + 1, l);
|
331
|
+
in += l + 1;
|
332
|
+
if (!alpn)
|
333
|
+
continue;
|
334
|
+
fio_str_info_s info = fio_str_info(&alpn->name);
|
335
|
+
*out = (unsigned char *)info.data;
|
336
|
+
*outlen = (unsigned char)info.len;
|
337
|
+
FIO_LOG_DEBUG("TLS ALPN set to: %s for %p", info.data, (void *)c->uuid);
|
338
|
+
alpn_select(alpn, c->uuid, c->alpn_arg);
|
339
|
+
return SSL_TLSEXT_ERR_OK;
|
340
|
+
}
|
341
|
+
/* set protocol to default protocol */
|
342
|
+
alpn = alpn_default(tls);
|
343
|
+
alpn_select(alpn, c->uuid, c->alpn_arg);
|
344
|
+
FIO_LOG_DEBUG(
|
345
|
+
"TLS ALPN handshake failed, falling back on default (%s) for %p",
|
346
|
+
fio_str_data(&alpn->name), (void *)c->uuid);
|
347
|
+
return SSL_TLSEXT_ERR_NOACK;
|
348
|
+
(void)ssl;
|
349
|
+
(void)out;
|
350
|
+
(void)outlen;
|
351
|
+
(void)in;
|
352
|
+
(void)inlen;
|
353
|
+
(void)tls;
|
354
|
+
}
|
355
|
+
|
356
|
+
/** Called when the library specific data for the context should be destroyed */
|
357
|
+
static void fio_tls_destroy_context(fio_tls_s *tls) {
|
358
|
+
/* TODO: Library specific implementation */
|
359
|
+
SSL_CTX_free(tls->ctx);
|
360
|
+
free(tls->alpn_str);
|
361
|
+
|
362
|
+
tls->ctx = NULL;
|
363
|
+
tls->alpn_str = NULL;
|
364
|
+
tls->alpn_len = 0;
|
365
|
+
FIO_LOG_DEBUG("destroyed TLS context for OpenSSL %p", (void *)tls);
|
366
|
+
}
|
367
|
+
|
368
|
+
static int fio_tls_pem_passwd_cb(char *buf, int size, int rwflag,
|
369
|
+
void *password) {
|
370
|
+
fio_str_info_s *p = password;
|
371
|
+
if (!p || !p->len || !size)
|
372
|
+
return 0;
|
373
|
+
int len = (size <= (int)p->len) ? (size - 1) : (int)p->len;
|
374
|
+
memcpy(buf, p->data, len);
|
375
|
+
buf[len] = 0;
|
376
|
+
return len;
|
377
|
+
(void)rwflag;
|
378
|
+
}
|
379
|
+
|
380
|
+
/** Called when the library specific data for the context should be built */
|
381
|
+
static void fio_tls_build_context(fio_tls_s *tls) {
|
382
|
+
fio_tls_destroy_context(tls);
|
383
|
+
/* TODO: Library specific implementation */
|
384
|
+
|
385
|
+
/* create new context */
|
386
|
+
tls->ctx = SSL_CTX_new(TLS_method());
|
387
|
+
SSL_CTX_set_mode(tls->ctx, SSL_MODE_ENABLE_PARTIAL_WRITE);
|
388
|
+
/* see: https://caniuse.com/#search=tls */
|
389
|
+
SSL_CTX_set_min_proto_version(tls->ctx, TLS1_2_VERSION);
|
390
|
+
SSL_CTX_set_options(tls->ctx, SSL_OP_NO_COMPRESSION);
|
391
|
+
|
392
|
+
/* attach certificates */
|
393
|
+
FIO_ARY_FOR(&tls->sni, pos) {
|
394
|
+
fio_str_info_s keys[4] = {
|
395
|
+
fio_str_info(&pos->private_key), fio_str_info(&pos->public_key),
|
396
|
+
fio_str_info(&pos->password),
|
397
|
+
/* empty password slot for public key */
|
398
|
+
};
|
399
|
+
if (keys[0].len && keys[1].len) {
|
400
|
+
if (1) {
|
401
|
+
/* Extract private key from private key file */
|
402
|
+
BIO *bio = BIO_new_mem_buf(keys[0].data, keys[0].len);
|
403
|
+
if (bio) {
|
404
|
+
EVP_PKEY *k = PEM_read_bio_PrivateKey(
|
405
|
+
bio, NULL, fio_tls_pem_passwd_cb, keys + 2);
|
406
|
+
if (k) {
|
407
|
+
FIO_LOG_DEBUG("TLS read private key from PEM file.");
|
408
|
+
SSL_CTX_use_PrivateKey(tls->ctx, k);
|
409
|
+
}
|
410
|
+
BIO_free(bio);
|
411
|
+
}
|
412
|
+
}
|
413
|
+
/* Certificate Files loaded */
|
414
|
+
for (int ki = 0; ki < 2; ++ki) {
|
415
|
+
/* Extract as much data as possible from each file */
|
416
|
+
BIO *bio = BIO_new_mem_buf(keys[ki].data, keys[ki].len);
|
417
|
+
FIO_ASSERT(bio, "OpenSSL error allocating BIO.");
|
418
|
+
STACK_OF(X509_INFO) *inf = PEM_X509_INFO_read_bio(
|
419
|
+
bio, NULL, fio_tls_pem_passwd_cb, keys + ki + 2);
|
420
|
+
if (inf) {
|
421
|
+
for (int i = 0; i < sk_X509_INFO_num(inf); ++i) {
|
422
|
+
/* for each element in PEM */
|
423
|
+
X509_INFO *tmp = sk_X509_INFO_value(inf, i);
|
424
|
+
if (tmp->x509) {
|
425
|
+
FIO_LOG_DEBUG("TLS adding certificate from PEM file.");
|
426
|
+
SSL_CTX_use_certificate(tls->ctx, tmp->x509);
|
427
|
+
}
|
428
|
+
if (tmp->x_pkey) {
|
429
|
+
FIO_LOG_DEBUG("TLS adding private key from PEM file.");
|
430
|
+
SSL_CTX_use_PrivateKey(tls->ctx, tmp->x_pkey->dec_pkey);
|
431
|
+
}
|
432
|
+
}
|
433
|
+
sk_X509_INFO_pop_free(inf, X509_INFO_free);
|
434
|
+
} else {
|
435
|
+
/* TODO: attempt DER format? */
|
436
|
+
// X509 *c;
|
437
|
+
// EVP_PKEY *k;
|
438
|
+
// const uint8_t *pdata = (uint8_t *)&keys[ki].data;
|
439
|
+
// d2i_X509(&c, &pdata, keys[ki].len);
|
440
|
+
// pdata = (uint8_t *)&keys[ki].data;
|
441
|
+
// d2i_AutoPrivateKey(&k, &pdata, keys[ki].len);
|
442
|
+
}
|
443
|
+
BIO_free(bio);
|
444
|
+
}
|
445
|
+
} else if (keys[0].len) {
|
446
|
+
/* Self Signed Certificates, only if server name is provided. */
|
447
|
+
SSL_CTX_use_certificate(tls->ctx,
|
448
|
+
fio_tls_create_self_signed(keys[0].data));
|
449
|
+
SSL_CTX_use_PrivateKey(tls->ctx, fio_tls_pkey);
|
450
|
+
}
|
451
|
+
}
|
452
|
+
|
453
|
+
/* setup ALPN support */
|
454
|
+
if (1) {
|
455
|
+
size_t alpn_pos = 0;
|
456
|
+
/* looping twice is better than malloc fragmentation. */
|
457
|
+
FIO_SET_FOR_LOOP(&tls->alpn, pos) {
|
458
|
+
fio_str_info_s s = fio_str_info(&pos->obj.name);
|
459
|
+
if (!s.len)
|
460
|
+
continue;
|
461
|
+
alpn_pos += s.len + 1;
|
462
|
+
}
|
463
|
+
tls->alpn_str = malloc((alpn_pos | 15) + 1); /* round up to 16 + padding */
|
464
|
+
alpn_pos = 0;
|
465
|
+
FIO_SET_FOR_LOOP(&tls->alpn, pos) {
|
466
|
+
fio_str_info_s s = fio_str_info(&pos->obj.name);
|
467
|
+
if (!s.len)
|
468
|
+
continue;
|
469
|
+
tls->alpn_str[alpn_pos++] = (uint8_t)s.len;
|
470
|
+
memcpy(tls->alpn_str + alpn_pos, s.data, s.len);
|
471
|
+
alpn_pos += s.len;
|
472
|
+
}
|
473
|
+
tls->alpn_len = alpn_pos;
|
474
|
+
SSL_CTX_set_alpn_select_cb(tls->ctx, fio_tls_alpn_selector_cb, tls);
|
475
|
+
SSL_CTX_set_alpn_protos(tls->ctx, tls->alpn_str, tls->alpn_len);
|
476
|
+
}
|
477
|
+
|
478
|
+
/* Peer Verification / Trust */
|
479
|
+
if (trust_ary_count(&tls->trust)) {
|
480
|
+
/* TODO: enable peer verification */
|
481
|
+
X509_STORE *store = X509_STORE_new();
|
482
|
+
SSL_CTX_set_cert_store(tls->ctx, store);
|
483
|
+
SSL_CTX_set_verify(tls->ctx, SSL_VERIFY_PEER, NULL);
|
484
|
+
/* TODO: Add each ceriticate in the PEM to the trust "store" */
|
485
|
+
FIO_ARY_FOR(&tls->trust, pos) {
|
486
|
+
fio_str_info_s pem = fio_str_info(&pos->pem);
|
487
|
+
BIO *bio = BIO_new_mem_buf(pem.data, pem.len);
|
488
|
+
FIO_ASSERT(bio, "OpenSSL error allocating BIO.");
|
489
|
+
STACK_OF(X509_INFO) *inf = PEM_X509_INFO_read_bio(bio, NULL, NULL, NULL);
|
490
|
+
if (inf) {
|
491
|
+
for (int i = 0; i < sk_X509_INFO_num(inf); ++i) {
|
492
|
+
/* for each element in PEM */
|
493
|
+
X509_INFO *tmp = sk_X509_INFO_value(inf, i);
|
494
|
+
if (tmp->x509) {
|
495
|
+
FIO_LOG_DEBUG("TLS trusting certificate from PEM file.");
|
496
|
+
X509_STORE_add_cert(store, tmp->x509);
|
497
|
+
}
|
498
|
+
if (tmp->crl) {
|
499
|
+
X509_STORE_add_crl(store, tmp->crl);
|
500
|
+
}
|
501
|
+
}
|
502
|
+
sk_X509_INFO_pop_free(inf, X509_INFO_free);
|
503
|
+
}
|
504
|
+
BIO_free(bio);
|
505
|
+
}
|
506
|
+
}
|
507
|
+
|
508
|
+
FIO_LOG_DEBUG("(re)built TLS context for OpenSSL %p", (void *)tls);
|
509
|
+
}
|
510
|
+
|
511
|
+
/* *****************************************************************************
|
512
|
+
SSL/TLS RW Hooks
|
513
|
+
***************************************************************************** */
|
514
|
+
|
515
|
+
static void fio_tls_delayed_close(void *uuid, void *ignr_) {
|
516
|
+
fio_close((intptr_t)uuid);
|
517
|
+
(void)ignr_;
|
518
|
+
}
|
519
|
+
|
520
|
+
/* TODO: this is an example implementation - fix for specific library. */
|
521
|
+
|
522
|
+
/**
|
523
|
+
* Implement reading from a file descriptor. Should behave like the file
|
524
|
+
* system `read` call, including the setup or errno to EAGAIN / EWOULDBLOCK.
|
525
|
+
*
|
526
|
+
* Note: facil.io library functions MUST NEVER be called by any r/w hook, or a
|
527
|
+
* deadlock might occur.
|
528
|
+
*/
|
529
|
+
static ssize_t fio_tls_read(intptr_t uuid, void *udata, void *buf,
|
530
|
+
size_t count) {
|
531
|
+
fio_tls_connection_s *c = udata;
|
532
|
+
ssize_t ret = SSL_read(c->ssl, buf, count);
|
533
|
+
if (ret > 0)
|
534
|
+
return ret;
|
535
|
+
ret = SSL_get_error(c->ssl, ret);
|
536
|
+
switch (ret) {
|
537
|
+
case SSL_ERROR_SSL: /* overflow */
|
538
|
+
case SSL_ERROR_ZERO_RETURN:
|
539
|
+
return 0; /* EOF */
|
540
|
+
case SSL_ERROR_NONE: /* overflow */
|
541
|
+
case SSL_ERROR_WANT_CONNECT: /* overflow */
|
542
|
+
case SSL_ERROR_WANT_ACCEPT: /* overflow */
|
543
|
+
case SSL_ERROR_WANT_X509_LOOKUP: /* overflow */
|
544
|
+
#ifdef SSL_ERROR_WANT_ASYNC
|
545
|
+
case SSL_ERROR_WANT_ASYNC: /* overflow */
|
546
|
+
#endif
|
547
|
+
case SSL_ERROR_WANT_WRITE: /* overflow */
|
548
|
+
case SSL_ERROR_WANT_READ:
|
549
|
+
default:
|
550
|
+
break;
|
551
|
+
}
|
552
|
+
errno = EWOULDBLOCK;
|
553
|
+
return -1;
|
554
|
+
(void)uuid;
|
555
|
+
}
|
556
|
+
|
557
|
+
/**
|
558
|
+
* When implemented, this function will be called to flush any data remaining
|
559
|
+
* in the internal buffer.
|
560
|
+
*
|
561
|
+
* The function should return the number of bytes remaining in the internal
|
562
|
+
* buffer (0 is a valid response) or -1 (on error).
|
563
|
+
*
|
564
|
+
* Note: facil.io library functions MUST NEVER be called by any r/w hook, or a
|
565
|
+
* deadlock might occur.
|
566
|
+
*/
|
567
|
+
static ssize_t fio_tls_flush(intptr_t uuid, void *udata) {
|
568
|
+
(void)uuid;
|
569
|
+
(void)udata;
|
570
|
+
return 0;
|
571
|
+
}
|
572
|
+
|
573
|
+
/**
|
574
|
+
* Implement writing to a file descriptor. Should behave like the file system
|
575
|
+
* `write` call.
|
576
|
+
*
|
577
|
+
* If an internal buffer is implemented and it is full, errno should be set to
|
578
|
+
* EWOULDBLOCK and the function should return -1.
|
579
|
+
*
|
580
|
+
* Note: facil.io library functions MUST NEVER be called by any r/w hook, or a
|
581
|
+
* deadlock might occur.
|
582
|
+
*/
|
583
|
+
static ssize_t fio_tls_write(intptr_t uuid, void *udata, const void *buf,
|
584
|
+
size_t count) {
|
585
|
+
fio_tls_connection_s *c = udata;
|
586
|
+
ssize_t ret = SSL_write(c->ssl, buf, count);
|
587
|
+
if (ret > 0)
|
588
|
+
return ret;
|
589
|
+
ret = SSL_get_error(c->ssl, ret);
|
590
|
+
switch (ret) {
|
591
|
+
case SSL_ERROR_SSL: /* overflow */
|
592
|
+
case SSL_ERROR_ZERO_RETURN:
|
593
|
+
return 0; /* EOF */
|
594
|
+
case SSL_ERROR_NONE: /* overflow */
|
595
|
+
case SSL_ERROR_WANT_CONNECT: /* overflow */
|
596
|
+
case SSL_ERROR_WANT_ACCEPT: /* overflow */
|
597
|
+
case SSL_ERROR_WANT_X509_LOOKUP: /* overflow */
|
598
|
+
#ifdef SSL_ERROR_WANT_ASYNC
|
599
|
+
case SSL_ERROR_WANT_ASYNC: /* overflow */
|
600
|
+
#endif
|
601
|
+
case SSL_ERROR_WANT_WRITE: /* overflow */
|
602
|
+
case SSL_ERROR_WANT_READ:
|
603
|
+
default:
|
604
|
+
break;
|
605
|
+
}
|
606
|
+
errno = EWOULDBLOCK;
|
607
|
+
return -1;
|
608
|
+
(void)uuid;
|
609
|
+
}
|
610
|
+
|
611
|
+
/**
|
612
|
+
* The `close` callback should close the underlying socket / file descriptor.
|
613
|
+
*
|
614
|
+
* If the function returns a non-zero value, it will be called again after an
|
615
|
+
* attempt to flush the socket and any pending outgoing buffer.
|
616
|
+
*
|
617
|
+
* Note: facil.io library functions MUST NEVER be called by any r/w hook, or a
|
618
|
+
* deadlock might occur.
|
619
|
+
* */
|
620
|
+
static ssize_t fio_tls_before_close(intptr_t uuid, void *udata) {
|
621
|
+
fio_tls_connection_s *c = udata;
|
622
|
+
SSL_shutdown(c->ssl);
|
623
|
+
return 1;
|
624
|
+
(void)uuid;
|
625
|
+
}
|
626
|
+
/**
|
627
|
+
* Called to perform cleanup after the socket was closed.
|
628
|
+
* */
|
629
|
+
static void fio_tls_cleanup(void *udata) {
|
630
|
+
fio_tls_connection_s *c = udata;
|
631
|
+
if (!c->alpn_ok) {
|
632
|
+
alpn_select(alpn_default(c->tls), -1, c->alpn_arg);
|
633
|
+
}
|
634
|
+
SSL_free(c->ssl);
|
635
|
+
FIO_LOG_DEBUG("TLS cleanup for %p", (void *)c->uuid);
|
636
|
+
fio_tls_destroy(c->tls); /* manage reference count */
|
637
|
+
free(udata);
|
638
|
+
}
|
639
|
+
|
640
|
+
static fio_rw_hook_s FIO_TLS_HOOKS = {
|
641
|
+
.read = fio_tls_read,
|
642
|
+
.write = fio_tls_write,
|
643
|
+
.before_close = fio_tls_before_close,
|
644
|
+
.flush = fio_tls_flush,
|
645
|
+
.cleanup = fio_tls_cleanup,
|
646
|
+
};
|
647
|
+
|
648
|
+
static size_t fio_tls_handshake(intptr_t uuid, void *udata) {
|
649
|
+
fio_tls_connection_s *c = udata;
|
650
|
+
int ri;
|
651
|
+
if (c->is_server) {
|
652
|
+
ri = SSL_accept(c->ssl);
|
653
|
+
} else {
|
654
|
+
ri = SSL_connect(c->ssl);
|
655
|
+
}
|
656
|
+
if (ri != 1) {
|
657
|
+
ri = SSL_get_error(c->ssl, ri);
|
658
|
+
switch (ri) {
|
659
|
+
case SSL_ERROR_NONE:
|
660
|
+
// FIO_LOG_DEBUG("SSL_accept/SSL_connect %p state: SSL_ERROR_NONE",
|
661
|
+
// (void *)uuid);
|
662
|
+
return 0;
|
663
|
+
case SSL_ERROR_WANT_WRITE:
|
664
|
+
// FIO_LOG_DEBUG("SSL_accept/SSL_connect %p state: SSL_ERROR_WANT_WRITE",
|
665
|
+
// (void *)uuid);
|
666
|
+
// fio_force_event(uuid, FIO_EVENT_ON_READY);
|
667
|
+
return 0;
|
668
|
+
case SSL_ERROR_WANT_READ:
|
669
|
+
// FIO_LOG_DEBUG("SSL_accept/SSL_connect %p state: SSL_ERROR_WANT_READ",
|
670
|
+
// (void *)uuid);
|
671
|
+
// fio_force_event(uuid, FIO_EVENT_ON_DATA);
|
672
|
+
return 0;
|
673
|
+
case SSL_ERROR_SYSCALL:
|
674
|
+
FIO_LOG_DEBUG(
|
675
|
+
"SSL_accept/SSL_connect %p error: SSL_ERROR_SYSCALL, errno: %s",
|
676
|
+
(void *)uuid, strerror(errno));
|
677
|
+
// fio_force_event(uuid, FIO_EVENT_ON_DATA);
|
678
|
+
return 0;
|
679
|
+
case SSL_ERROR_SSL:
|
680
|
+
FIO_LOG_DEBUG("SSL_accept/SSL_connect %p error: SSL_ERROR_SSL",
|
681
|
+
(void *)uuid);
|
682
|
+
break;
|
683
|
+
case SSL_ERROR_ZERO_RETURN:
|
684
|
+
FIO_LOG_DEBUG("SSL_accept/SSL_connect %p error: SSL_ERROR_ZERO_RETURN",
|
685
|
+
(void *)uuid);
|
686
|
+
break;
|
687
|
+
case SSL_ERROR_WANT_CONNECT:
|
688
|
+
FIO_LOG_DEBUG("SSL_accept/SSL_connect %p error: SSL_ERROR_WANT_CONNECT",
|
689
|
+
(void *)uuid);
|
690
|
+
break;
|
691
|
+
case SSL_ERROR_WANT_ACCEPT:
|
692
|
+
FIO_LOG_DEBUG("SSL_accept/SSL_connect %p error: SSL_ERROR_WANT_ACCEPT",
|
693
|
+
(void *)uuid);
|
694
|
+
break;
|
695
|
+
case SSL_ERROR_WANT_X509_LOOKUP:
|
696
|
+
FIO_LOG_DEBUG(
|
697
|
+
"SSL_accept/SSL_connect %p error: SSL_ERROR_WANT_X509_LOOKUP",
|
698
|
+
(void *)uuid);
|
699
|
+
break;
|
700
|
+
#ifdef SSL_ERROR_WANT_ASYNC
|
701
|
+
case SSL_ERROR_WANT_ASYNC:
|
702
|
+
FIO_LOG_DEBUG("SSL_accept/SSL_connect %p error: SSL_ERROR_WANT_ASYNC",
|
703
|
+
(void *)uuid);
|
704
|
+
break;
|
705
|
+
#endif
|
706
|
+
#ifdef SSL_ERROR_WANT_CLIENT_HELLO_CB
|
707
|
+
case SSL_ERROR_WANT_CLIENT_HELLO_CB:
|
708
|
+
FIO_LOG_DEBUG(
|
709
|
+
"SSL_accept/SSL_connect %p error: SSL_ERROR_WANT_CLIENT_HELLO_CB",
|
710
|
+
(void *)uuid);
|
711
|
+
break;
|
712
|
+
#endif
|
713
|
+
default:
|
714
|
+
FIO_LOG_DEBUG("SSL_accept/SSL_connect %p error: unknown (%d).",
|
715
|
+
(void *)uuid, ri);
|
716
|
+
break;
|
717
|
+
}
|
718
|
+
fio_defer(fio_tls_delayed_close, (void *)uuid, NULL);
|
719
|
+
return 0;
|
720
|
+
}
|
721
|
+
if (!c->alpn_ok) {
|
722
|
+
c->alpn_ok = 1;
|
723
|
+
if (c->is_server) {
|
724
|
+
fio_tls_alpn_fallback(c);
|
725
|
+
} else {
|
726
|
+
const unsigned char *proto;
|
727
|
+
unsigned int proto_len;
|
728
|
+
SSL_get0_alpn_selected(c->ssl, &proto, &proto_len);
|
729
|
+
alpn_s *alpn = NULL;
|
730
|
+
if (proto_len > 0) {
|
731
|
+
alpn = alpn_find(c->tls, (char *)proto, proto_len);
|
732
|
+
}
|
733
|
+
if (!alpn) {
|
734
|
+
alpn = alpn_default(c->tls);
|
735
|
+
FIO_LOG_DEBUG("ALPN missing for TLS client %p", (void *)uuid);
|
736
|
+
}
|
737
|
+
if (alpn)
|
738
|
+
FIO_LOG_DEBUG("setting ALPN %s for TLS client %p",
|
739
|
+
fio_str_data(&alpn->name), (void *)uuid);
|
740
|
+
alpn_select(alpn, c->uuid, c->alpn_arg);
|
741
|
+
}
|
742
|
+
}
|
743
|
+
if (fio_rw_hook_replace_unsafe(uuid, &FIO_TLS_HOOKS, udata) == 0) {
|
744
|
+
FIO_LOG_DEBUG("Completed TLS handshake for %p", (void *)uuid);
|
745
|
+
} else {
|
746
|
+
FIO_LOG_DEBUG("Something went wrong during TLS handshake for %p",
|
747
|
+
(void *)uuid);
|
748
|
+
return 0;
|
749
|
+
}
|
750
|
+
/* make sure the connection is re-added to the reactor */
|
751
|
+
fio_force_event(uuid, FIO_EVENT_ON_DATA);
|
752
|
+
/* log session ID for WireShark */
|
753
|
+
#if FIO_TLS_PRINT_SECRET
|
754
|
+
if (FIO_LOG_LEVEL >= FIO_LOG_LEVEL_DEBUG) {
|
755
|
+
unsigned char buff[SSL_MAX_MASTER_KEY_LENGTH + 2];
|
756
|
+
size_t ret = SSL_SESSION_get_master_key(SSL_get_session(c->ssl), buff,
|
757
|
+
SSL_MAX_MASTER_KEY_LENGTH + 1);
|
758
|
+
buff[ret] = 0;
|
759
|
+
unsigned char buff2[(SSL_MAX_MASTER_KEY_LENGTH + 2) << 1];
|
760
|
+
for (size_t i = 0; i < ret; ++i) {
|
761
|
+
buff2[i] = ((buff[i] >> 4) >= 10) ? ('A' + (buff[i] >> 4) - 10)
|
762
|
+
: ('0' + (buff[i] >> 4));
|
763
|
+
buff2[i + 1] = ((buff[i] & 15) >= 10) ? ('A' + (buff[i] & 15) - 10)
|
764
|
+
: ('0' + (buff[i] & 15));
|
765
|
+
}
|
766
|
+
buff2[(ret << 1)] = 0;
|
767
|
+
FIO_LOG_DEBUG("OpenSSL Master Key for uuid %p:\n\t\t%s", (void *)uuid,
|
768
|
+
buff2);
|
769
|
+
}
|
770
|
+
#endif
|
771
|
+
return 1;
|
772
|
+
}
|
773
|
+
|
774
|
+
static ssize_t fio_tls_read4handshake(intptr_t uuid, void *udata, void *buf,
|
775
|
+
size_t count) {
|
776
|
+
// FIO_LOG_DEBUG("TLS handshake from read %p", (void *)uuid);
|
777
|
+
if (fio_tls_handshake(uuid, udata))
|
778
|
+
return fio_tls_read(uuid, udata, buf, count);
|
779
|
+
errno = EWOULDBLOCK;
|
780
|
+
return -1;
|
781
|
+
}
|
782
|
+
|
783
|
+
static ssize_t fio_tls_write4handshake(intptr_t uuid, void *udata,
|
784
|
+
const void *buf, size_t count) {
|
785
|
+
// FIO_LOG_DEBUG("TLS handshake from write %p", (void *)uuid);
|
786
|
+
if (fio_tls_handshake(uuid, udata))
|
787
|
+
return fio_tls_write(uuid, udata, buf, count);
|
788
|
+
errno = EWOULDBLOCK;
|
789
|
+
return -1;
|
790
|
+
}
|
791
|
+
|
792
|
+
static ssize_t fio_tls_flush4handshake(intptr_t uuid, void *udata) {
|
793
|
+
// FIO_LOG_DEBUG("TLS handshake from flush %p", (void *)uuid);
|
794
|
+
if (fio_tls_handshake(uuid, udata)) {
|
795
|
+
return fio_tls_flush(uuid, udata);
|
796
|
+
}
|
797
|
+
errno = 0;
|
798
|
+
return 1;
|
799
|
+
}
|
800
|
+
static fio_rw_hook_s FIO_TLS_HANDSHAKE_HOOKS = {
|
801
|
+
.read = fio_tls_read4handshake,
|
802
|
+
.write = fio_tls_write4handshake,
|
803
|
+
.before_close = fio_tls_before_close,
|
804
|
+
.flush = fio_tls_flush4handshake,
|
805
|
+
.cleanup = fio_tls_cleanup,
|
806
|
+
};
|
807
|
+
static inline void fio_tls_attach2uuid(intptr_t uuid, fio_tls_s *tls,
|
808
|
+
void *udata, uint8_t is_server) {
|
809
|
+
fio_atomic_add(&tls->ref, 1);
|
810
|
+
/* create SSL connection context from global context */
|
811
|
+
fio_tls_connection_s *c = malloc(sizeof(*c));
|
812
|
+
FIO_ASSERT_ALLOC(c);
|
813
|
+
*c = (fio_tls_connection_s){
|
814
|
+
.alpn_arg = udata,
|
815
|
+
.tls = tls,
|
816
|
+
.uuid = uuid,
|
817
|
+
.ssl = SSL_new(tls->ctx),
|
818
|
+
.is_server = is_server,
|
819
|
+
.alpn_ok = 0,
|
820
|
+
};
|
821
|
+
FIO_ASSERT_ALLOC(c->ssl);
|
822
|
+
/* set facil.io data in the SSL object */
|
823
|
+
SSL_set_ex_data(c->ssl, 0, (void *)c);
|
824
|
+
/* attach socket - TODO: Switch to BIO socket */
|
825
|
+
BIO *bio = BIO_new_socket(fio_uuid2fd(uuid), 0);
|
826
|
+
BIO_up_ref(bio);
|
827
|
+
SSL_set0_rbio(c->ssl, bio);
|
828
|
+
SSL_set0_wbio(c->ssl, bio);
|
829
|
+
/* set RW hooks */
|
830
|
+
fio_rw_hook_set(uuid, &FIO_TLS_HANDSHAKE_HOOKS, c);
|
831
|
+
if (is_server) {
|
832
|
+
/* Server mode (accept) */
|
833
|
+
FIO_LOG_DEBUG("Attaching TLS read/write hook for %p (server mode).",
|
834
|
+
(void *)uuid);
|
835
|
+
SSL_set_accept_state(c->ssl);
|
836
|
+
} else {
|
837
|
+
/* Client mode (connect) */
|
838
|
+
FIO_LOG_DEBUG("Attaching TLS read/write hook for %p (client mode).",
|
839
|
+
(void *)uuid);
|
840
|
+
SSL_set_connect_state(c->ssl);
|
841
|
+
}
|
842
|
+
fio_force_event(uuid, FIO_EVENT_ON_READY);
|
843
|
+
}
|
844
|
+
|
845
|
+
/* *****************************************************************************
|
846
|
+
SSL/TLS API implementation - this can be pretty much used as is...
|
847
|
+
***************************************************************************** */
|
848
|
+
|
849
|
+
/**
|
850
|
+
* Creates a new SSL/TLS context / settings object with a default certificate
|
851
|
+
* (if any).
|
852
|
+
*/
|
853
|
+
fio_tls_s *FIO_TLS_WEAK fio_tls_new(const char *server_name, const char *cert,
|
854
|
+
const char *key, const char *pk_password) {
|
855
|
+
REQUIRE_LIBRARY();
|
856
|
+
fio_tls_s *tls = calloc(sizeof(*tls), 1);
|
857
|
+
tls->ref = 1;
|
858
|
+
fio_tls_cert_add(tls, server_name, key, cert, pk_password);
|
859
|
+
return tls;
|
860
|
+
}
|
861
|
+
|
862
|
+
/**
|
863
|
+
* Adds a certificate a new SSL/TLS context / settings object.
|
864
|
+
*/
|
865
|
+
void FIO_TLS_WEAK fio_tls_cert_add(fio_tls_s *tls, const char *server_name,
|
866
|
+
const char *cert, const char *key,
|
867
|
+
const char *pk_password) {
|
868
|
+
REQUIRE_LIBRARY();
|
869
|
+
cert_s c = {
|
870
|
+
.private_key = FIO_STR_INIT,
|
871
|
+
.public_key = FIO_STR_INIT,
|
872
|
+
.password = FIO_STR_INIT_STATIC2(pk_password,
|
873
|
+
(pk_password ? strlen(pk_password) : 0)),
|
874
|
+
};
|
875
|
+
if (key && cert) {
|
876
|
+
if (fio_str_readfile(&c.private_key, key, 0, 0).data == NULL)
|
877
|
+
goto file_missing;
|
878
|
+
if (fio_str_readfile(&c.public_key, cert, 0, 0).data == NULL)
|
879
|
+
goto file_missing;
|
880
|
+
cert_ary_push(&tls->sni, c);
|
881
|
+
} else if (server_name) {
|
882
|
+
/* Self-Signed TLS Certificates */
|
883
|
+
c.private_key = FIO_STR_INIT_STATIC(server_name);
|
884
|
+
cert_ary_push(&tls->sni, c);
|
885
|
+
}
|
886
|
+
fio_tls_cert_destroy(&c);
|
887
|
+
fio_tls_build_context(tls);
|
888
|
+
return;
|
889
|
+
file_missing:
|
890
|
+
FIO_LOG_FATAL("TLS certificate file missing for either %s or %s or both.",
|
891
|
+
key, cert);
|
892
|
+
exit(-1);
|
893
|
+
}
|
894
|
+
|
895
|
+
/**
|
896
|
+
* Adds an ALPN protocol callback to the SSL/TLS context.
|
897
|
+
*
|
898
|
+
* The first protocol added will act as the default protocol to be selected.
|
899
|
+
*
|
900
|
+
* The callback should accept the `uuid`, the user data pointer passed to
|
901
|
+
* either `fio_tls_accept` or `fio_tls_connect` (here: `udata_connetcion`) and
|
902
|
+
* the user data pointer passed to the `fio_tls_alpn_add` function
|
903
|
+
* (`udata_tls`).
|
904
|
+
*
|
905
|
+
* The `on_cleanup` callback will be called when the TLS object is destroyed
|
906
|
+
* (or `fio_tls_alpn_add` is called again with the same protocol name). The
|
907
|
+
* `udata_tls` argumrnt will be passed along, as is, to the callback (if set).
|
908
|
+
*
|
909
|
+
* Except for the `tls` and `protocol_name` arguments, all arguments can be
|
910
|
+
* NULL.
|
911
|
+
*/
|
912
|
+
void FIO_TLS_WEAK fio_tls_alpn_add(
|
913
|
+
fio_tls_s *tls, const char *protocol_name,
|
914
|
+
void (*on_selected)(intptr_t uuid, void *udata_connection, void *udata_tls),
|
915
|
+
void *udata_tls, void (*on_cleanup)(void *udata_tls)) {
|
916
|
+
REQUIRE_LIBRARY();
|
917
|
+
alpn_add(tls, protocol_name, on_selected, udata_tls, on_cleanup);
|
918
|
+
fio_tls_build_context(tls);
|
919
|
+
}
|
920
|
+
|
921
|
+
/**
|
922
|
+
* Returns the number of registered ALPN protocol names.
|
923
|
+
*
|
924
|
+
* This could be used when deciding if protocol selection should be delegated
|
925
|
+
* to the ALPN mechanism, or whether a protocol should be immediately
|
926
|
+
* assigned.
|
927
|
+
*
|
928
|
+
* If no ALPN protocols are registered, zero (0) is returned.
|
929
|
+
*/
|
930
|
+
uintptr_t FIO_TLS_WEAK fio_tls_alpn_count(fio_tls_s *tls) {
|
931
|
+
return tls ? alpn_list_count(&tls->alpn) : 0;
|
932
|
+
}
|
933
|
+
|
934
|
+
/**
|
935
|
+
* Adds a certificate to the "trust" list, which automatically adds a peer
|
936
|
+
* verification requirement.
|
937
|
+
*
|
938
|
+
* fio_tls_trust(tls, "google-ca.pem" );
|
939
|
+
*/
|
940
|
+
void FIO_TLS_WEAK fio_tls_trust(fio_tls_s *tls, const char *public_cert_file) {
|
941
|
+
REQUIRE_LIBRARY();
|
942
|
+
trust_s c = {
|
943
|
+
.pem = FIO_STR_INIT,
|
944
|
+
};
|
945
|
+
if (!public_cert_file)
|
946
|
+
return;
|
947
|
+
if (fio_str_readfile(&c.pem, public_cert_file, 0, 0).data == NULL)
|
948
|
+
goto file_missing;
|
949
|
+
trust_ary_push(&tls->trust, c);
|
950
|
+
fio_tls_trust_destroy(&c);
|
951
|
+
fio_tls_build_context(tls);
|
952
|
+
return;
|
953
|
+
file_missing:
|
954
|
+
FIO_LOG_FATAL("TLS certificate file missing for %s ", public_cert_file);
|
955
|
+
exit(-1);
|
956
|
+
}
|
957
|
+
|
958
|
+
/**
|
959
|
+
* Establishes an SSL/TLS connection as an SSL/TLS Server, using the specified
|
960
|
+
* context / settings object.
|
961
|
+
*
|
962
|
+
* The `uuid` should be a socket UUID that is already connected to a peer
|
963
|
+
* (i.e., the result of `fio_accept`).
|
964
|
+
*
|
965
|
+
* The `udata` is an opaque user data pointer that is passed along to the
|
966
|
+
* protocol selected (if any protocols were added using `fio_tls_alpn_add`).
|
967
|
+
*/
|
968
|
+
void FIO_TLS_WEAK fio_tls_accept(intptr_t uuid, fio_tls_s *tls, void *udata) {
|
969
|
+
REQUIRE_LIBRARY();
|
970
|
+
fio_tls_attach2uuid(uuid, tls, udata, 1);
|
971
|
+
}
|
972
|
+
|
973
|
+
/**
|
974
|
+
* Establishes an SSL/TLS connection as an SSL/TLS Client, using the specified
|
975
|
+
* context / settings object.
|
976
|
+
*
|
977
|
+
* The `uuid` should be a socket UUID that is already connected to a peer
|
978
|
+
* (i.e., one received by a `fio_connect` specified callback `on_connect`).
|
979
|
+
*
|
980
|
+
* The `udata` is an opaque user data pointer that is passed along to the
|
981
|
+
* protocol selected (if any protocols were added using `fio_tls_alpn_add`).
|
982
|
+
*/
|
983
|
+
void FIO_TLS_WEAK fio_tls_connect(intptr_t uuid, fio_tls_s *tls, void *udata) {
|
984
|
+
REQUIRE_LIBRARY();
|
985
|
+
fio_tls_attach2uuid(uuid, tls, udata, 0);
|
986
|
+
}
|
987
|
+
|
988
|
+
/**
|
989
|
+
* Increase the reference count for the TLS object.
|
990
|
+
*
|
991
|
+
* Decrease with `fio_tls_destroy`.
|
992
|
+
*/
|
993
|
+
void FIO_TLS_WEAK fio_tls_dup(fio_tls_s *tls) { fio_atomic_add(&tls->ref, 1); }
|
994
|
+
|
995
|
+
/**
|
996
|
+
* Destroys the SSL/TLS context / settings object and frees any related
|
997
|
+
* resources / memory.
|
998
|
+
*/
|
999
|
+
void FIO_TLS_WEAK fio_tls_destroy(fio_tls_s *tls) {
|
1000
|
+
if (!tls)
|
1001
|
+
return;
|
1002
|
+
REQUIRE_LIBRARY();
|
1003
|
+
if (fio_atomic_sub(&tls->ref, 1))
|
1004
|
+
return;
|
1005
|
+
fio_tls_destroy_context(tls);
|
1006
|
+
alpn_list_free(&tls->alpn);
|
1007
|
+
cert_ary_free(&tls->sni);
|
1008
|
+
free(tls);
|
1009
|
+
}
|
1010
|
+
|
1011
|
+
#endif /* Library compiler flags */
|