cwist
Modern C web framework with libttak runtime
Loading...
Searching...
No Matches
tls_chain.c
Go to the documentation of this file.
1#define _POSIX_C_SOURCE 200809L
2
3#include "tls_chain.h"
4#include "curl_global.h"
5
6#include <curl/curl.h>
7
8#include <limits.h>
9#include <stdatomic.h>
10#include <stdbool.h>
11#include <stdint.h>
12#include <stdlib.h>
13#include <string.h>
14#include <strings.h>
15
16#ifdef CWIST_TLS_MBEDTLS
17/* mbedTLS backend (CWIST_TLS=mbedtls): the AIA chain-autoload machinery
18 * below is built on BoringSSL's X509/ASN.1/PKCS#7 API, which has no mbedTLS
19 * counterpart in the seam. Chain autoload degrades to a no-op: the PEM
20 * served by cwist_https_init_context() must already carry any intermediates
21 * (mbedtls_x509_crt_parse_file() loads the whole file). */
22
24 (void)ssl_ctx;
25 return 0;
26}
27#else
28#include <openssl/asn1.h>
29#include <openssl/bio.h>
30#include <openssl/bytestring.h>
31#include <openssl/err.h>
32#include <openssl/nid.h>
33#include <openssl/obj.h>
34#include <openssl/pem.h>
35#include <openssl/pkcs7.h>
36#include <openssl/x509.h>
37
38#define CWIST_AIA_MAX_RESPONSE_BYTES (1024u * 1024u)
39#define CWIST_AIA_MAX_CHAIN_DEPTH 8
40#define CWIST_AIA_CONNECT_TIMEOUT_MS 2000L
41#define CWIST_AIA_TIMEOUT_MS 5000L
42
43typedef struct cwist_cert_fetch_buffer {
44 uint8_t *data;
45 size_t len;
46 size_t cap;
48
57static bool cwist_url_has_http_scheme(const char *url) {
58 return url && (strncasecmp(url, "http://", 7) == 0 || strncasecmp(url, "https://", 8) == 0);
59}
60
74static size_t cwist_cert_fetch_write_cb(void *ptr, size_t size, size_t nmemb, void *userdata) {
75 if (size != 0 && nmemb > SIZE_MAX / size) return 0;
76
77 size_t total = size * nmemb;
79 if (total > CWIST_AIA_MAX_RESPONSE_BYTES || buf->len > CWIST_AIA_MAX_RESPONSE_BYTES - total) {
80 return 0;
81 }
82
83 size_t need = buf->len + total;
84 if (need > buf->cap) {
85 size_t new_cap = buf->cap ? buf->cap * 2 : 4096;
86 while (new_cap < need) {
89 break;
90 }
91 new_cap *= 2;
92 }
93
94 uint8_t *tmp = (uint8_t *)realloc(buf->data, new_cap);
95 if (!tmp) return 0;
96 buf->data = tmp;
97 buf->cap = new_cap;
98 }
99
100 memcpy(buf->data + buf->len, ptr, total);
101 buf->len += total;
102 return total;
103}
104
117static bool cwist_fetch_cert_url(const char *url, cwist_cert_fetch_buffer *out) {
118 if (!cwist_url_has_http_scheme(url) || !out) return false;
119
121 CURL *curl = curl_easy_init();
122 if (!curl) {
124 return false;
125 }
126
127 memset(out, 0, sizeof(*out));
128 curl_easy_setopt(curl, CURLOPT_URL, url);
136 curl_easy_setopt(curl, CURLOPT_USERAGENT, "CWIST-TLS-AIA/1.0");
137 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, cwist_cert_fetch_write_cb);
139#if defined(CURLOPT_PROTOCOLS_STR)
140 curl_easy_setopt(curl, CURLOPT_PROTOCOLS_STR, "http,https");
142#elif defined(CURLOPT_PROTOCOLS)
145#endif
146
148 curl_easy_cleanup(curl);
150
151 if (code != CURLE_OK || out->len == 0) {
152 free(out->data);
153 memset(out, 0, sizeof(*out));
154 return false;
155 }
156
157 return true;
158}
159
169static STACK_OF(X509) * cwist_parse_der_x509(const uint8_t *data, size_t len) {
170 if (!data || len == 0 || len > LONG_MAX) return NULL;
171
172 const uint8_t *p = data;
173 X509 *cert = d2i_X509(NULL, &p, (long)len);
174 if (!cert) {
176 return NULL;
177 }
178
179 STACK_OF(X509) *certs = sk_X509_new_null();
180 if (!certs || !sk_X509_push(certs, cert)) {
181 X509_free(cert);
183 return NULL;
184 }
185 return certs;
186}
187
199static STACK_OF(X509) * cwist_parse_pem_x509(const uint8_t *data, size_t len) {
200 if (!data || len == 0 || len > INT_MAX) return NULL;
201
202 BIO *bio = BIO_new_mem_buf(data, (int)len);
203 if (!bio) return NULL;
204
205 STACK_OF(X509) *certs = sk_X509_new_null();
206 if (!certs) {
207 BIO_free(bio);
208 return NULL;
209 }
210
211 X509 *cert = NULL;
212 while ((cert = PEM_read_bio_X509(bio, NULL, NULL, NULL)) != NULL) {
213 if (!sk_X509_push(certs, cert)) {
214 X509_free(cert);
216 BIO_free(bio);
217 return NULL;
218 }
219 }
221 BIO_free(bio);
222
223 if (sk_X509_num(certs) == 0) {
225 return NULL;
226 }
227 return certs;
228}
229
239static STACK_OF(X509) * cwist_parse_der_pkcs7(const uint8_t *data, size_t len) {
240 if (!data || len == 0) return NULL;
241
242 STACK_OF(X509) *certs = sk_X509_new_null();
243 if (!certs) return NULL;
244
245 CBS cbs;
246 CBS_init(&cbs, data, len);
250 return NULL;
251 }
252
253 return certs;
254}
255
265static STACK_OF(X509) * cwist_parse_pem_pkcs7(const uint8_t *data, size_t len) {
266 if (!data || len == 0 || len > INT_MAX) return NULL;
267
268 BIO *bio = BIO_new_mem_buf(data, (int)len);
269 if (!bio) return NULL;
270
271 STACK_OF(X509) *certs = sk_X509_new_null();
272 if (!certs) {
273 BIO_free(bio);
274 return NULL;
275 }
276
280 BIO_free(bio);
281 return NULL;
282 }
283
284 BIO_free(bio);
285 return certs;
286}
287
298static STACK_OF(X509) * cwist_parse_certificates(const uint8_t *data, size_t len) {
299 STACK_OF(X509) *certs = cwist_parse_der_x509(data, len);
300 if (certs) return certs;
301
302 certs = cwist_parse_pem_x509(data, len);
303 if (certs) return certs;
304
305 certs = cwist_parse_der_pkcs7(data, len);
306 if (certs) return certs;
307
308 return cwist_parse_pem_pkcs7(data, len);
309}
310
322static bool cwist_x509_is_valid_issuer(X509 *issuer, X509 *subject) {
323 if (!issuer || !subject || X509_cmp(issuer, subject) == 0) return false;
324 if (X509_check_ca(issuer) != 1) return false;
325 if (X509_check_issued(issuer, subject) != X509_V_OK) return false;
326
328 return issuer_key && X509_verify(subject, issuer_key) == 1;
329}
330
342static X509 *cwist_find_issuer(X509 *subject, STACK_OF(X509) * candidates) {
343 if (!subject || !candidates) return NULL;
344
345 for (size_t i = 0; i < sk_X509_num(candidates); i++) {
347 if (cwist_x509_is_valid_issuer(candidate, subject)) {
348 return candidate;
349 }
350 }
351 return NULL;
352}
353
365static bool cwist_ctx_contains_cert(SSL_CTX *ssl_ctx, X509 *cert) {
366 if (!ssl_ctx || !cert) return false;
367
369 if (leaf && X509_cmp(leaf, cert) == 0) return true;
370
371 STACK_OF(X509) *chain = NULL;
372 if (SSL_CTX_get0_chain_certs(ssl_ctx, &chain) == 1 && chain) {
373 for (size_t i = 0; i < sk_X509_num(chain); i++) {
374 if (X509_cmp(sk_X509_value(chain, i), cert) == 0) return true;
375 }
376 }
377 return false;
378}
379
390static X509 *cwist_ctx_chain_tail(SSL_CTX *ssl_ctx) {
391 if (!ssl_ctx) return NULL;
392
393 STACK_OF(X509) *chain = NULL;
394 if (SSL_CTX_get0_chain_certs(ssl_ctx, &chain) == 1 && chain && sk_X509_num(chain) > 0) {
395 return sk_X509_value(chain, sk_X509_num(chain) - 1);
396 }
397 return SSL_CTX_get0_certificate(ssl_ctx);
398}
399
408static bool cwist_x509_is_self_signed(X509 *cert) {
409 if (!cert) return true;
411 return false;
412 }
413
414 EVP_PKEY *key = X509_get0_pubkey(cert);
415 return key && X509_verify(cert, key) == 1;
416}
417
428static char *cwist_dup_aia_uri(const ACCESS_DESCRIPTION *desc) {
429 if (!desc || OBJ_obj2nid(desc->method) != NID_ad_ca_issuers || !desc->location) {
430 return NULL;
431 }
432
433 int name_type = -1;
435 if (name_type != GEN_URI || !uri) return NULL;
436
438 if (uri_len <= 0) return NULL;
439
440 const unsigned char *uri_data = ASN1_STRING_get0_data(uri);
441 if (!uri_data || memchr(uri_data, '\0', (size_t)uri_len)) return NULL;
442
443 char *url = (char *)malloc((size_t)uri_len + 1);
444 if (!url) return NULL;
445 memcpy(url, uri_data, (size_t)uri_len);
446 url[uri_len] = '\0';
447
448 if (!cwist_url_has_http_scheme(url)) {
449 free(url);
450 return NULL;
451 }
452 return url;
453}
454
467static X509 *cwist_fetch_issuer_from_url(const char *url, X509 *subject) {
468 cwist_cert_fetch_buffer buf = {0};
469 if (!cwist_fetch_cert_url(url, &buf)) return NULL;
470
471 STACK_OF(X509) *certs = cwist_parse_certificates(buf.data, buf.len);
472 free(buf.data);
473 if (!certs) return NULL;
474
475 X509 *issuer = cwist_find_issuer(subject, certs);
476 if (issuer && X509_up_ref(issuer) != 1) {
477 issuer = NULL;
478 }
479
481 return issuer;
482}
483
496static X509 *cwist_fetch_issuer_from_aia(X509 *subject) {
497 if (!subject) return NULL;
498
501 if (!aia) {
503 return NULL;
504 }
505
506 X509 *issuer = NULL;
507 for (size_t i = 0; i < sk_ACCESS_DESCRIPTION_num(aia); i++) {
509 char *url = cwist_dup_aia_uri(desc);
510 if (!url) continue;
511
512 issuer = cwist_fetch_issuer_from_url(url, subject);
513 free(url);
514 if (issuer) break;
515 }
516
518 return issuer;
519}
520#endif /* !CWIST_TLS_MBEDTLS */
521
537unsigned long cwist_tls_sess_cache_size(void) {
538 static _Atomic long cached = -1;
540 if (v < 0) {
541 const char *s = getenv("CWIST_TLS_SESS_CACHE_SIZE");
542 if (s && *s) {
543 char *end = NULL;
544 long parsed = strtol(s, &end, 10);
545 v = (end != s && parsed >= 0 && parsed <= 1048576L) ? parsed : 1024;
546 } else {
547 v = 1024;
548 }
550 }
551 return (unsigned long)v;
552}
553
567#ifndef CWIST_TLS_MBEDTLS
569 if (!ssl_ctx) return -1;
570
571 int added = 0;
572 for (int depth = 0; depth < CWIST_AIA_MAX_CHAIN_DEPTH; depth++) {
573 X509 *tail = cwist_ctx_chain_tail(ssl_ctx);
574 if (!tail || cwist_x509_is_self_signed(tail)) break;
575
576 X509 *issuer = cwist_fetch_issuer_from_aia(tail);
577 if (!issuer) break;
578
579 if (cwist_x509_is_self_signed(issuer)) {
581 break;
582 }
583
584 if (cwist_ctx_contains_cert(ssl_ctx, issuer)) {
586 break;
587 }
588
589 if (SSL_CTX_add1_chain_cert(ssl_ctx, issuer) != 1) {
591 return -1;
592 }
593
595 added++;
596 }
597
598 return added;
599}
600
601#endif /* !CWIST_TLS_MBEDTLS */
void cwist_curl_global_acquire(void)
Acquire a reference on the global libcurl instance.
Definition curl_global.c:19
void cwist_curl_global_release(void)
Release a reference on the global libcurl instance.
Definition curl_global.c:36
mbedTLS TLS backend: error queue subset.
#define ERR_clear_error
Definition err.h:20
size_t cap
Definition otel.c:56
struct cwist_mtls_ctx SSL_CTX
Definition ssl.h:43
int cwist_tls_autoload_intermediates(SSL_CTX *ssl_ctx)
Autoload missing intermediate CA certificates into an SSL_CTX chain.
Definition tls_chain.c:568
#define CWIST_AIA_TIMEOUT_MS
Definition tls_chain.c:41
#define CWIST_AIA_CONNECT_TIMEOUT_MS
Definition tls_chain.c:40
struct cwist_cert_fetch_buffer cwist_cert_fetch_buffer
#define CWIST_AIA_MAX_RESPONSE_BYTES
Definition tls_chain.c:38
#define CWIST_AIA_MAX_CHAIN_DEPTH
Definition tls_chain.c:39
unsigned long cwist_tls_sess_cache_size(void)
Definition tls_chain.c:537