comparison src/event/quic/ngx_event_quic_openssl_compat.c @ 9080:7da4791e0264 quic

QUIC: OpenSSL compatibility layer. The change allows to compile QUIC with OpenSSL which lacks BoringSSL QUIC API. This implementation does not support 0-RTT.
author Roman Arutyunyan <arut@nginx.com>
date Wed, 22 Feb 2023 19:16:53 +0400
parents
children b4a57278bf24
comparison
equal deleted inserted replaced
9079:639fa6723700 9080:7da4791e0264
1
2 /*
3 * Copyright (C) Nginx, Inc.
4 */
5
6
7 #include <ngx_config.h>
8 #include <ngx_core.h>
9 #include <ngx_event.h>
10 #include <ngx_event_quic_connection.h>
11
12
13 #if (NGX_QUIC_OPENSSL_COMPAT)
14
15 #define NGX_QUIC_COMPAT_RECORD_SIZE 1024
16
17 #define NGX_QUIC_COMPAT_SSL_TP_EXT 0x39
18
19 #define NGX_QUIC_COMPAT_CLIENT_HANDSHAKE "CLIENT_HANDSHAKE_TRAFFIC_SECRET"
20 #define NGX_QUIC_COMPAT_SERVER_HANDSHAKE "SERVER_HANDSHAKE_TRAFFIC_SECRET"
21 #define NGX_QUIC_COMPAT_CLIENT_APPLICATION "CLIENT_TRAFFIC_SECRET_0"
22 #define NGX_QUIC_COMPAT_SERVER_APPLICATION "SERVER_TRAFFIC_SECRET_0"
23
24
25 typedef struct {
26 ngx_quic_secret_t secret;
27 ngx_uint_t cipher;
28 } ngx_quic_compat_keys_t;
29
30
31 typedef struct {
32 ngx_log_t *log;
33
34 u_char type;
35 ngx_str_t payload;
36 uint64_t number;
37 ngx_quic_compat_keys_t *keys;
38
39 enum ssl_encryption_level_t level;
40 } ngx_quic_compat_record_t;
41
42
43 struct ngx_quic_compat_s {
44 const SSL_QUIC_METHOD *method;
45
46 enum ssl_encryption_level_t write_level;
47 enum ssl_encryption_level_t read_level;
48
49 uint64_t read_record;
50 ngx_quic_compat_keys_t keys;
51
52 ngx_str_t tp;
53 ngx_str_t ctp;
54 };
55
56
57 static void ngx_quic_compat_keylog_callback(const SSL *ssl, const char *line);
58 static ngx_int_t ngx_quic_compat_set_encryption_secret(ngx_log_t *log,
59 ngx_quic_compat_keys_t *keys, enum ssl_encryption_level_t level,
60 const SSL_CIPHER *cipher, const uint8_t *secret, size_t secret_len);
61 static int ngx_quic_compat_add_transport_params_callback(SSL *ssl,
62 unsigned int ext_type, unsigned int context, const unsigned char **out,
63 size_t *outlen, X509 *x, size_t chainidx, int *al, void *add_arg);
64 static int ngx_quic_compat_parse_transport_params_callback(SSL *ssl,
65 unsigned int ext_type, unsigned int context, const unsigned char *in,
66 size_t inlen, X509 *x, size_t chainidx, int *al, void *parse_arg);
67 static void ngx_quic_compat_message_callback(int write_p, int version,
68 int content_type, const void *buf, size_t len, SSL *ssl, void *arg);
69 static size_t ngx_quic_compat_create_header(ngx_quic_compat_record_t *rec,
70 u_char *out, ngx_uint_t plain);
71 static ngx_int_t ngx_quic_compat_create_record(ngx_quic_compat_record_t *rec,
72 ngx_str_t *res);
73
74
75 ngx_int_t
76 ngx_quic_compat_init(ngx_conf_t *cf, SSL_CTX *ctx)
77 {
78 SSL_CTX_set_keylog_callback(ctx, ngx_quic_compat_keylog_callback);
79
80 if (SSL_CTX_has_client_custom_ext(ctx, NGX_QUIC_COMPAT_SSL_TP_EXT)) {
81 return NGX_OK;
82 }
83
84 if (SSL_CTX_add_custom_ext(ctx, NGX_QUIC_COMPAT_SSL_TP_EXT,
85 SSL_EXT_CLIENT_HELLO
86 |SSL_EXT_TLS1_3_ENCRYPTED_EXTENSIONS,
87 ngx_quic_compat_add_transport_params_callback,
88 NULL,
89 NULL,
90 ngx_quic_compat_parse_transport_params_callback,
91 NULL)
92 == 0)
93 {
94 ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
95 "SSL_CTX_add_custom_ext() failed");
96 return NGX_ERROR;
97 }
98
99 return NGX_OK;
100 }
101
102
103 static void
104 ngx_quic_compat_keylog_callback(const SSL *ssl, const char *line)
105 {
106 u_char ch, *p, *start, value;
107 size_t n;
108 ngx_uint_t write;
109 const SSL_CIPHER *cipher;
110 ngx_quic_compat_t *com;
111 ngx_connection_t *c;
112 ngx_quic_connection_t *qc;
113 enum ssl_encryption_level_t level;
114 u_char secret[EVP_MAX_MD_SIZE];
115
116 c = ngx_ssl_get_connection(ssl);
117 if (c->type != SOCK_DGRAM) {
118 return;
119 }
120
121 p = (u_char *) line;
122
123 for (start = p; *p && *p != ' '; p++);
124
125 n = p - start;
126
127 ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
128 "quic compat secret %*s", n, start);
129
130 if (n == sizeof(NGX_QUIC_COMPAT_CLIENT_HANDSHAKE) - 1
131 && ngx_strncmp(start, NGX_QUIC_COMPAT_CLIENT_HANDSHAKE, n) == 0)
132 {
133 level = ssl_encryption_handshake;
134 write = 0;
135
136 } else if (n == sizeof(NGX_QUIC_COMPAT_SERVER_HANDSHAKE) - 1
137 && ngx_strncmp(start, NGX_QUIC_COMPAT_SERVER_HANDSHAKE, n) == 0)
138 {
139 level = ssl_encryption_handshake;
140 write = 1;
141
142 } else if (n == sizeof(NGX_QUIC_COMPAT_CLIENT_APPLICATION) - 1
143 && ngx_strncmp(start, NGX_QUIC_COMPAT_CLIENT_APPLICATION, n)
144 == 0)
145 {
146 level = ssl_encryption_application;
147 write = 0;
148
149 } else if (n == sizeof(NGX_QUIC_COMPAT_SERVER_APPLICATION) - 1
150 && ngx_strncmp(start, NGX_QUIC_COMPAT_SERVER_APPLICATION, n)
151 == 0)
152 {
153 level = ssl_encryption_application;
154 write = 1;
155
156 } else {
157 return;
158 }
159
160 if (*p++ == '\0') {
161 return;
162 }
163
164 for ( /* void */ ; *p && *p != ' '; p++);
165
166 if (*p++ == '\0') {
167 return;
168 }
169
170 for (n = 0, start = p; *p; p++) {
171 ch = *p;
172
173 if (ch >= '0' && ch <= '9') {
174 value = ch - '0';
175 goto next;
176 }
177
178 ch = (u_char) (ch | 0x20);
179
180 if (ch >= 'a' && ch <= 'f') {
181 value = ch - 'a' + 10;
182 goto next;
183 }
184
185 ngx_log_error(NGX_LOG_EMERG, c->log, 0,
186 "invalid OpenSSL QUIC secret format");
187
188 return;
189
190 next:
191
192 if ((p - start) % 2) {
193 secret[n++] += value;
194
195 } else {
196 if (n >= EVP_MAX_MD_SIZE) {
197 ngx_log_error(NGX_LOG_EMERG, c->log, 0,
198 "too big OpenSSL QUIC secret");
199 return;
200 }
201
202 secret[n] = (value << 4);
203 }
204 }
205
206 qc = ngx_quic_get_connection(c);
207 com = qc->compat;
208 cipher = SSL_get_current_cipher(ssl);
209
210 if (write) {
211 com->method->set_write_secret((SSL *) ssl, level, cipher, secret, n);
212 com->write_level = level;
213
214 } else {
215 com->method->set_read_secret((SSL *) ssl, level, cipher, secret, n);
216 com->read_level = level;
217 com->read_record = 0;
218
219 (void) ngx_quic_compat_set_encryption_secret(c->log, &com->keys, level,
220 cipher, secret, n);
221 }
222 }
223
224
225 static ngx_int_t
226 ngx_quic_compat_set_encryption_secret(ngx_log_t *log,
227 ngx_quic_compat_keys_t *keys, enum ssl_encryption_level_t level,
228 const SSL_CIPHER *cipher, const uint8_t *secret, size_t secret_len)
229 {
230 ngx_int_t key_len;
231 ngx_str_t secret_str;
232 ngx_uint_t i;
233 ngx_quic_hkdf_t seq[2];
234 ngx_quic_secret_t *peer_secret;
235 ngx_quic_ciphers_t ciphers;
236
237 peer_secret = &keys->secret;
238
239 keys->cipher = SSL_CIPHER_get_id(cipher);
240
241 key_len = ngx_quic_ciphers(keys->cipher, &ciphers, level);
242
243 if (key_len == NGX_ERROR) {
244 ngx_ssl_error(NGX_LOG_INFO, log, 0, "unexpected cipher");
245 return NGX_ERROR;
246 }
247
248 if (sizeof(peer_secret->secret.data) < secret_len) {
249 ngx_log_error(NGX_LOG_ALERT, log, 0,
250 "unexpected secret len: %uz", secret_len);
251 return NGX_ERROR;
252 }
253
254 peer_secret->secret.len = secret_len;
255 ngx_memcpy(peer_secret->secret.data, secret, secret_len);
256
257 peer_secret->key.len = key_len;
258 peer_secret->iv.len = NGX_QUIC_IV_LEN;
259
260 secret_str.len = secret_len;
261 secret_str.data = (u_char *) secret;
262
263 ngx_quic_hkdf_set(&seq[0], "tls13 key", &peer_secret->key, &secret_str);
264 ngx_quic_hkdf_set(&seq[1], "tls13 iv", &peer_secret->iv, &secret_str);
265
266 for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) {
267 if (ngx_quic_hkdf_expand(&seq[i], ciphers.d, log) != NGX_OK) {
268 return NGX_ERROR;
269 }
270 }
271
272 return NGX_OK;
273 }
274
275
276 static int
277 ngx_quic_compat_add_transport_params_callback(SSL *ssl, unsigned int ext_type,
278 unsigned int context, const unsigned char **out, size_t *outlen, X509 *x,
279 size_t chainidx, int *al, void *add_arg)
280 {
281 ngx_connection_t *c;
282 ngx_quic_compat_t *com;
283 ngx_quic_connection_t *qc;
284
285 c = ngx_ssl_get_connection(ssl);
286 if (c->type != SOCK_DGRAM) {
287 return 0;
288 }
289
290 ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
291 "quic compat add transport params");
292
293 qc = ngx_quic_get_connection(c);
294 com = qc->compat;
295
296 *out = com->tp.data;
297 *outlen = com->tp.len;
298
299 return 1;
300 }
301
302
303 static int
304 ngx_quic_compat_parse_transport_params_callback(SSL *ssl, unsigned int ext_type,
305 unsigned int context, const unsigned char *in, size_t inlen, X509 *x,
306 size_t chainidx, int *al, void *parse_arg)
307 {
308 u_char *p;
309 ngx_connection_t *c;
310 ngx_quic_compat_t *com;
311 ngx_quic_connection_t *qc;
312
313 c = ngx_ssl_get_connection(ssl);
314 if (c->type != SOCK_DGRAM) {
315 return 0;
316 }
317
318 ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
319 "quic compat parse transport params");
320
321 qc = ngx_quic_get_connection(c);
322 com = qc->compat;
323
324 p = ngx_pnalloc(c->pool, inlen);
325 if (p == NULL) {
326 return 0;
327 }
328
329 ngx_memcpy(p, in, inlen);
330
331 com->ctp.data = p;
332 com->ctp.len = inlen;
333
334 return 1;
335 }
336
337
338 int
339 SSL_set_quic_method(SSL *ssl, const SSL_QUIC_METHOD *quic_method)
340 {
341 BIO *rbio, *wbio;
342 ngx_connection_t *c;
343 ngx_quic_compat_t *com;
344 ngx_quic_connection_t *qc;
345
346 c = ngx_ssl_get_connection(ssl);
347
348 ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic compat set method");
349
350 qc = ngx_quic_get_connection(c);
351
352 qc->compat = ngx_pcalloc(c->pool, sizeof(ngx_quic_compat_t));
353 if (qc->compat == NULL) {
354 return 0;
355 }
356
357 com = qc->compat;
358 com->method = quic_method;
359
360 rbio = BIO_new(BIO_s_mem());
361 if (rbio == NULL) {
362 return 0;
363 }
364
365 wbio = BIO_new(BIO_s_null());
366 if (wbio == NULL) {
367 return 0;
368 }
369
370 SSL_set_bio(ssl, rbio, wbio);
371
372 SSL_set_msg_callback(ssl, ngx_quic_compat_message_callback);
373
374 /* early data is not supported */
375 SSL_set_max_early_data(ssl, 0);
376
377 return 1;
378 }
379
380
381 static void
382 ngx_quic_compat_message_callback(int write_p, int version, int content_type,
383 const void *buf, size_t len, SSL *ssl, void *arg)
384 {
385 ngx_uint_t alert;
386 ngx_connection_t *c;
387 ngx_quic_compat_t *com;
388 ngx_quic_connection_t *qc;
389 enum ssl_encryption_level_t level;
390
391 if (!write_p) {
392 return;
393 }
394
395 c = ngx_ssl_get_connection(ssl);
396 qc = ngx_quic_get_connection(c);
397
398 if (qc == NULL) {
399 /* closing */
400 return;
401 }
402
403 com = qc->compat;
404 level = com->write_level;
405
406 switch (content_type) {
407
408 case SSL3_RT_HANDSHAKE:
409 ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
410 "quic compat tx %s len:%uz ",
411 ngx_quic_level_name(level), len);
412
413 (void) com->method->add_handshake_data(ssl, level, buf, len);
414
415 break;
416
417 case SSL3_RT_ALERT:
418 if (len >= 2) {
419 alert = ((u_char *) buf)[1];
420
421 ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
422 "quic compat %s alert:%ui len:%uz ",
423 ngx_quic_level_name(level), alert, len);
424
425 (void) com->method->send_alert(ssl, level, alert);
426 }
427
428 break;
429 }
430 }
431
432
433 int
434 SSL_provide_quic_data(SSL *ssl, enum ssl_encryption_level_t level,
435 const uint8_t *data, size_t len)
436 {
437 BIO *rbio;
438 size_t n;
439 u_char *p;
440 ngx_str_t res;
441 ngx_connection_t *c;
442 ngx_quic_compat_t *com;
443 ngx_quic_connection_t *qc;
444 ngx_quic_compat_record_t rec;
445 u_char in[NGX_QUIC_COMPAT_RECORD_SIZE + 1];
446 u_char out[NGX_QUIC_COMPAT_RECORD_SIZE + 1
447 + SSL3_RT_HEADER_LENGTH
448 + EVP_GCM_TLS_TAG_LEN];
449
450 c = ngx_ssl_get_connection(ssl);
451
452 ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic compat rx %s len:%uz",
453 ngx_quic_level_name(level), len);
454
455 qc = ngx_quic_get_connection(c);
456 com = qc->compat;
457 rbio = SSL_get_rbio(ssl);
458
459 while (len) {
460 ngx_memzero(&rec, sizeof(ngx_quic_compat_record_t));
461
462 rec.type = SSL3_RT_HANDSHAKE;
463 rec.log = c->log;
464 rec.number = com->read_record++;
465 rec.keys = &com->keys;
466
467 if (level == ssl_encryption_initial) {
468 n = ngx_min(len, 65535);
469
470 rec.payload.len = n;
471 rec.payload.data = (u_char *) data;
472
473 ngx_quic_compat_create_header(&rec, out, 1);
474
475 BIO_write(rbio, out, SSL3_RT_HEADER_LENGTH);
476 BIO_write(rbio, data, n);
477
478 #if defined(NGX_QUIC_DEBUG_CRYPTO) && defined(NGX_QUIC_DEBUG_PACKETS)
479 ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0,
480 "quic compat record len:%uz %*xs%*xs",
481 n + SSL3_RT_HEADER_LENGTH,
482 (size_t) SSL3_RT_HEADER_LENGTH, out, n, data);
483 #endif
484
485 } else {
486 n = ngx_min(len, NGX_QUIC_COMPAT_RECORD_SIZE);
487
488 p = ngx_cpymem(in, data, n);
489 *p++ = SSL3_RT_HANDSHAKE;
490
491 rec.payload.len = p - in;
492 rec.payload.data = in;
493
494 res.data = out;
495
496 if (ngx_quic_compat_create_record(&rec, &res) != NGX_OK) {
497 return 0;
498 }
499
500 #if defined(NGX_QUIC_DEBUG_CRYPTO) && defined(NGX_QUIC_DEBUG_PACKETS)
501 ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
502 "quic compat record len:%uz %xV", res.len, &res);
503 #endif
504
505 BIO_write(rbio, res.data, res.len);
506 }
507
508 data += n;
509 len -= n;
510 }
511
512 return 1;
513 }
514
515
516 static size_t
517 ngx_quic_compat_create_header(ngx_quic_compat_record_t *rec, u_char *out,
518 ngx_uint_t plain)
519 {
520 u_char type;
521 size_t len;
522
523 len = rec->payload.len;
524
525 if (plain) {
526 type = rec->type;
527
528 } else {
529 type = SSL3_RT_APPLICATION_DATA;
530 len += EVP_GCM_TLS_TAG_LEN;
531 }
532
533 out[0] = type;
534 out[1] = 0x03;
535 out[2] = 0x03;
536 out[3] = (len >> 8);
537 out[4] = len;
538
539 return 5;
540 }
541
542
543 static ngx_int_t
544 ngx_quic_compat_create_record(ngx_quic_compat_record_t *rec, ngx_str_t *res)
545 {
546 ngx_str_t ad, out;
547 ngx_quic_secret_t *secret;
548 ngx_quic_ciphers_t ciphers;
549 u_char nonce[NGX_QUIC_IV_LEN];
550
551 ad.data = res->data;
552 ad.len = ngx_quic_compat_create_header(rec, ad.data, 0);
553
554 out.len = rec->payload.len + EVP_GCM_TLS_TAG_LEN;
555 out.data = res->data + ad.len;
556
557 #ifdef NGX_QUIC_DEBUG_CRYPTO
558 ngx_log_debug2(NGX_LOG_DEBUG_EVENT, rec->log, 0,
559 "quic compat ad len:%uz %xV", ad.len, &ad);
560 #endif
561
562 if (ngx_quic_ciphers(rec->keys->cipher, &ciphers, rec->level) == NGX_ERROR)
563 {
564 return NGX_ERROR;
565 }
566
567 secret = &rec->keys->secret;
568
569 ngx_memcpy(nonce, secret->iv.data, secret->iv.len);
570 ngx_quic_compute_nonce(nonce, sizeof(nonce), rec->number);
571
572 if (ngx_quic_tls_seal(ciphers.c, secret, &out,
573 nonce, &rec->payload, &ad, rec->log)
574 != NGX_OK)
575 {
576 return NGX_ERROR;
577 }
578
579 res->len = ad.len + out.len;
580
581 return NGX_OK;
582 }
583
584
585 enum ssl_encryption_level_t
586 SSL_quic_read_level(const SSL *ssl)
587 {
588 ngx_connection_t *c;
589 ngx_quic_connection_t *qc;
590
591 c = ngx_ssl_get_connection(ssl);
592 qc = ngx_quic_get_connection(c);
593
594 return qc->compat->read_level;
595 }
596
597
598 enum ssl_encryption_level_t
599 SSL_quic_write_level(const SSL *ssl)
600 {
601 ngx_connection_t *c;
602 ngx_quic_connection_t *qc;
603
604 c = ngx_ssl_get_connection(ssl);
605 qc = ngx_quic_get_connection(c);
606
607 return qc->compat->write_level;
608 }
609
610
611 int
612 SSL_set_quic_transport_params(SSL *ssl, const uint8_t *params,
613 size_t params_len)
614 {
615 ngx_connection_t *c;
616 ngx_quic_compat_t *com;
617 ngx_quic_connection_t *qc;
618
619 c = ngx_ssl_get_connection(ssl);
620 qc = ngx_quic_get_connection(c);
621 com = qc->compat;
622
623 com->tp.len = params_len;
624 com->tp.data = (u_char *) params;
625
626 return 1;
627 }
628
629
630 void
631 SSL_get_peer_quic_transport_params(const SSL *ssl, const uint8_t **out_params,
632 size_t *out_params_len)
633 {
634 ngx_connection_t *c;
635 ngx_quic_compat_t *com;
636 ngx_quic_connection_t *qc;
637
638 c = ngx_ssl_get_connection(ssl);
639 qc = ngx_quic_get_connection(c);
640 com = qc->compat;
641
642 *out_params = com->ctp.data;
643 *out_params_len = com->ctp.len;
644 }
645
646 #endif /* NGX_QUIC_OPENSSL_COMPAT */