blob: 2f5eae26f9493b37fdb5f3598321d41305e19bc9 [file] [log] [blame]
William Lallemand03c331c2020-05-13 10:10:01 +02001/*
2 *
3 * Copyright (C) 2020 HAProxy Technologies, William Lallemand <wlallemand@haproxy.com>
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version
8 * 2 of the License, or (at your option) any later version.
9 *
10 */
11
12#define _GNU_SOURCE
13#include <ctype.h>
William Lallemand87fd9942022-04-01 20:12:03 +020014#include <dirent.h>
William Lallemand03c331c2020-05-13 10:10:01 +020015#include <errno.h>
16#include <fcntl.h>
17#include <stdio.h>
18#include <stdlib.h>
19#include <string.h>
Willy Tarreauaeed4a82020-06-04 22:01:04 +020020#include <syslog.h>
William Lallemand03c331c2020-05-13 10:10:01 +020021#include <unistd.h>
22
23#include <sys/stat.h>
24#include <sys/types.h>
25
Willy Tarreau74f24562021-10-06 17:54:12 +020026#include <import/ebpttree.h>
Willy Tarreaub2551052020-06-09 09:07:15 +020027#include <import/ebsttree.h>
28
Willy Tarreau50c2f1e2022-05-04 19:26:59 +020029#include <haproxy/applet.h>
Willy Tarreau8d366972020-05-27 16:10:29 +020030#include <haproxy/base64.h>
Willy Tarreauf1d32c42020-06-04 21:07:02 +020031#include <haproxy/channel.h>
Willy Tarreau83487a82020-06-04 20:19:54 +020032#include <haproxy/cli.h>
Christopher Faulet908628c2022-03-25 16:43:49 +010033#include <haproxy/conn_stream.h>
34#include <haproxy/cs_utils.h>
Willy Tarreau8d366972020-05-27 16:10:29 +020035#include <haproxy/errors.h>
Willy Tarreau47d7f902020-06-04 14:25:47 +020036#include <haproxy/ssl_ckch.h>
Willy Tarreau209108d2020-06-04 20:30:20 +020037#include <haproxy/ssl_sock.h>
Willy Tarreaub2bd8652020-06-04 14:21:22 +020038#include <haproxy/ssl_utils.h>
Willy Tarreau48fbcae2020-06-03 18:09:46 +020039#include <haproxy/tools.h>
William Lallemand03c331c2020-05-13 10:10:01 +020040
William Lallemandda8584c2020-05-14 10:14:37 +020041/* Uncommitted CKCH transaction */
42
43static struct {
44 struct ckch_store *new_ckchs;
45 struct ckch_store *old_ckchs;
46 char *path;
47} ckchs_transaction;
48
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +010049/* Uncommitted CA file transaction */
50
51static struct {
52 struct cafile_entry *old_cafile_entry;
53 struct cafile_entry *new_cafile_entry;
54 char *path;
55} cafile_transaction;
William Lallemandda8584c2020-05-14 10:14:37 +020056
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +020057/* Uncommitted CRL file transaction */
58
59static struct {
60 struct cafile_entry *old_crlfile_entry;
61 struct cafile_entry *new_crlfile_entry;
62 char *path;
63} crlfile_transaction;
64
Willy Tarreau50c2f1e2022-05-04 19:26:59 +020065/* CLI context used by "show cafile" */
66struct show_cafile_ctx {
67 struct cafile_entry *cur_cafile_entry;
68 struct cafile_entry *old_cafile_entry;
69 int ca_index;
70 int show_all;
71};
William Lallemand03c331c2020-05-13 10:10:01 +020072
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +020073/* CLI context used by "show crlfile" */
74struct show_crlfile_ctx {
75 struct cafile_entry *cafile_entry;
76 struct crlfile_entry *old_crlfile_entry;
77 int index;
78};
79
Willy Tarreau96c9a6c2022-05-04 19:51:37 +020080/* CLI context used by "show cert" */
81struct show_cert_ctx {
82 struct ckch_store *old_ckchs;
83 struct ckch_store *cur_ckchs;
84 int transaction;
85};
86
Willy Tarreaua645b6a2022-05-04 19:58:00 +020087/* CLI context used by "commit cert" */
88struct commit_cert_ctx {
89 struct ckch_store *old_ckchs;
90 struct ckch_store *new_ckchs;
91 struct ckch_inst *next_ckchi;
92};
93
Willy Tarreau96c9a6c2022-05-04 19:51:37 +020094
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +010095
William Lallemand03c331c2020-05-13 10:10:01 +020096/******************** cert_key_and_chain functions *************************
97 * These are the functions that fills a cert_key_and_chain structure. For the
98 * functions filling a SSL_CTX from a cert_key_and_chain, see ssl_sock.c
99 */
100
101/*
102 * Try to parse Signed Certificate Timestamp List structure. This function
103 * makes only basic test if the data seems like SCTL. No signature validation
104 * is performed.
105 */
106static int ssl_sock_parse_sctl(struct buffer *sctl)
107{
108 int ret = 1;
109 int len, pos, sct_len;
110 unsigned char *data;
111
112 if (sctl->data < 2)
113 goto out;
114
115 data = (unsigned char *) sctl->area;
116 len = (data[0] << 8) | data[1];
117
118 if (len + 2 != sctl->data)
119 goto out;
120
121 data = data + 2;
122 pos = 0;
123 while (pos < len) {
124 if (len - pos < 2)
125 goto out;
126
127 sct_len = (data[pos] << 8) | data[pos + 1];
128 if (pos + sct_len + 2 > len)
129 goto out;
130
131 pos += sct_len + 2;
132 }
133
134 ret = 0;
135
136out:
137 return ret;
138}
139
140/* Try to load a sctl from a buffer <buf> if not NULL, or read the file <sctl_path>
141 * It fills the ckch->sctl buffer
142 * return 0 on success or != 0 on failure */
143int ssl_sock_load_sctl_from_file(const char *sctl_path, char *buf, struct cert_key_and_chain *ckch, char **err)
144{
145 int fd = -1;
146 int r = 0;
147 int ret = 1;
148 struct buffer tmp;
149 struct buffer *src;
150 struct buffer *sctl;
151
152 if (buf) {
William Lallemand8d673942021-01-27 14:58:51 +0100153 chunk_initstr(&tmp, buf);
William Lallemand03c331c2020-05-13 10:10:01 +0200154 src = &tmp;
155 } else {
156 fd = open(sctl_path, O_RDONLY);
157 if (fd == -1)
158 goto end;
159
160 trash.data = 0;
161 while (trash.data < trash.size) {
162 r = read(fd, trash.area + trash.data, trash.size - trash.data);
163 if (r < 0) {
164 if (errno == EINTR)
165 continue;
166 goto end;
167 }
168 else if (r == 0) {
169 break;
170 }
171 trash.data += r;
172 }
173 src = &trash;
174 }
175
176 ret = ssl_sock_parse_sctl(src);
177 if (ret)
178 goto end;
179
180 sctl = calloc(1, sizeof(*sctl));
181 if (!chunk_dup(sctl, src)) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100182 ha_free(&sctl);
William Lallemand03c331c2020-05-13 10:10:01 +0200183 goto end;
184 }
185 /* no error, fill ckch with new context, old context must be free */
186 if (ckch->sctl) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100187 ha_free(&ckch->sctl->area);
William Lallemand03c331c2020-05-13 10:10:01 +0200188 free(ckch->sctl);
189 }
190 ckch->sctl = sctl;
191 ret = 0;
192end:
193 if (fd != -1)
194 close(fd);
195
196 return ret;
197}
198
199#if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) || defined OPENSSL_IS_BORINGSSL)
200/*
Ilya Shipitsin46a030c2020-07-05 16:36:08 +0500201 * This function load the OCSP Response in DER format contained in file at
William Lallemand03c331c2020-05-13 10:10:01 +0200202 * path 'ocsp_path' or base64 in a buffer <buf>
203 *
204 * Returns 0 on success, 1 in error case.
205 */
206int ssl_sock_load_ocsp_response_from_file(const char *ocsp_path, char *buf, struct cert_key_and_chain *ckch, char **err)
207{
208 int fd = -1;
209 int r = 0;
210 int ret = 1;
211 struct buffer *ocsp_response;
212 struct buffer *src = NULL;
213
214 if (buf) {
215 int i, j;
216 /* if it's from a buffer it will be base64 */
217
218 /* remove \r and \n from the payload */
219 for (i = 0, j = 0; buf[i]; i++) {
220 if (buf[i] == '\r' || buf[i] == '\n')
221 continue;
222 buf[j++] = buf[i];
223 }
224 buf[j] = 0;
225
226 ret = base64dec(buf, j, trash.area, trash.size);
227 if (ret < 0) {
228 memprintf(err, "Error reading OCSP response in base64 format");
229 goto end;
230 }
231 trash.data = ret;
232 src = &trash;
233 } else {
234 fd = open(ocsp_path, O_RDONLY);
235 if (fd == -1) {
236 memprintf(err, "Error opening OCSP response file");
237 goto end;
238 }
239
240 trash.data = 0;
241 while (trash.data < trash.size) {
242 r = read(fd, trash.area + trash.data, trash.size - trash.data);
243 if (r < 0) {
244 if (errno == EINTR)
245 continue;
246
247 memprintf(err, "Error reading OCSP response from file");
248 goto end;
249 }
250 else if (r == 0) {
251 break;
252 }
253 trash.data += r;
254 }
255 close(fd);
256 fd = -1;
257 src = &trash;
258 }
259
260 ocsp_response = calloc(1, sizeof(*ocsp_response));
261 if (!chunk_dup(ocsp_response, src)) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100262 ha_free(&ocsp_response);
William Lallemand03c331c2020-05-13 10:10:01 +0200263 goto end;
264 }
265 /* no error, fill ckch with new context, old context must be free */
266 if (ckch->ocsp_response) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100267 ha_free(&ckch->ocsp_response->area);
William Lallemand03c331c2020-05-13 10:10:01 +0200268 free(ckch->ocsp_response);
269 }
270 ckch->ocsp_response = ocsp_response;
271 ret = 0;
272end:
273 if (fd != -1)
274 close(fd);
275
276 return ret;
277}
278#endif
279
280/*
281 * Try to load in a ckch every files related to a ckch.
282 * (PEM, sctl, ocsp, issuer etc.)
283 *
284 * This function is only used to load files during the configuration parsing,
285 * it is not used with the CLI.
286 *
287 * This allows us to carry the contents of the file without having to read the
288 * file multiple times. The caller must call
289 * ssl_sock_free_cert_key_and_chain_contents.
290 *
291 * returns:
292 * 0 on Success
293 * 1 on SSL Failure
294 */
295int ssl_sock_load_files_into_ckch(const char *path, struct cert_key_and_chain *ckch, char **err)
296{
William Lallemand8e8581e2020-10-20 17:36:46 +0200297 struct buffer *fp = NULL;
William Lallemand03c331c2020-05-13 10:10:01 +0200298 int ret = 1;
299
300 /* try to load the PEM */
301 if (ssl_sock_load_pem_into_ckch(path, NULL, ckch , err) != 0) {
302 goto end;
303 }
304
William Lallemand8e8581e2020-10-20 17:36:46 +0200305 fp = alloc_trash_chunk();
306 if (!fp) {
307 memprintf(err, "%sCan't allocate memory\n", err && *err ? *err : "");
308 goto end;
309 }
310
311 if (!chunk_strcpy(fp, path) || (b_data(fp) > MAXPATHLEN)) {
312 memprintf(err, "%s '%s' filename too long'.\n",
313 err && *err ? *err : "", fp->area);
314 ret = 1;
315 goto end;
316 }
317
William Lallemand089c1382020-10-23 17:35:12 +0200318 /* remove the ".crt" extension */
William Lallemand8e8581e2020-10-20 17:36:46 +0200319 if (global_ssl.extra_files_noext) {
320 char *ext;
321
322 /* look for the extension */
323 if ((ext = strrchr(fp->area, '.'))) {
William Lallemand8e8581e2020-10-20 17:36:46 +0200324
Tim Duesterhuse5ff1412021-01-02 22:31:53 +0100325 if (strcmp(ext, ".crt") == 0) {
William Lallemand8e8581e2020-10-20 17:36:46 +0200326 *ext = '\0';
William Lallemand089c1382020-10-23 17:35:12 +0200327 fp->data = strlen(fp->area);
328 }
William Lallemand8e8581e2020-10-20 17:36:46 +0200329 }
330
331 }
332
William Lallemand03c331c2020-05-13 10:10:01 +0200333 /* try to load an external private key if it wasn't in the PEM */
334 if ((ckch->key == NULL) && (global_ssl.extra_files & SSL_GF_KEY)) {
William Lallemand03c331c2020-05-13 10:10:01 +0200335 struct stat st;
336
William Lallemand8e8581e2020-10-20 17:36:46 +0200337
338 if (!chunk_strcat(fp, ".key") || (b_data(fp) > MAXPATHLEN)) {
339 memprintf(err, "%s '%s' filename too long'.\n",
340 err && *err ? *err : "", fp->area);
341 ret = 1;
342 goto end;
343 }
344
345 if (stat(fp->area, &st) == 0) {
346 if (ssl_sock_load_key_into_ckch(fp->area, NULL, ckch, err)) {
William Lallemand03c331c2020-05-13 10:10:01 +0200347 memprintf(err, "%s '%s' is present but cannot be read or parsed'.\n",
William Lallemand8e8581e2020-10-20 17:36:46 +0200348 err && *err ? *err : "", fp->area);
William Lallemand03c331c2020-05-13 10:10:01 +0200349 goto end;
350 }
351 }
William Lallemand03c331c2020-05-13 10:10:01 +0200352
William Lallemand8e8581e2020-10-20 17:36:46 +0200353 if (ckch->key == NULL) {
354 memprintf(err, "%sNo Private Key found in '%s'.\n", err && *err ? *err : "", fp->area);
355 goto end;
356 }
357 /* remove the added extension */
358 *(fp->area + fp->data - strlen(".key")) = '\0';
359 b_sub(fp, strlen(".key"));
William Lallemand03c331c2020-05-13 10:10:01 +0200360 }
361
362 if (!X509_check_private_key(ckch->cert, ckch->key)) {
363 memprintf(err, "%sinconsistencies between private key and certificate loaded '%s'.\n",
364 err && *err ? *err : "", path);
365 goto end;
366 }
367
Ilya Shipitsinc47d6762021-02-13 11:45:33 +0500368#ifdef HAVE_SSL_SCTL
William Lallemand03c331c2020-05-13 10:10:01 +0200369 /* try to load the sctl file */
370 if (global_ssl.extra_files & SSL_GF_SCTL) {
William Lallemand03c331c2020-05-13 10:10:01 +0200371 struct stat st;
372
William Lallemand8e8581e2020-10-20 17:36:46 +0200373 if (!chunk_strcat(fp, ".sctl") || b_data(fp) > MAXPATHLEN) {
374 memprintf(err, "%s '%s' filename too long'.\n",
375 err && *err ? *err : "", fp->area);
376 ret = 1;
377 goto end;
378 }
379
380 if (stat(fp->area, &st) == 0) {
381 if (ssl_sock_load_sctl_from_file(fp->area, NULL, ckch, err)) {
William Lallemand03c331c2020-05-13 10:10:01 +0200382 memprintf(err, "%s '%s.sctl' is present but cannot be read or parsed'.\n",
William Lallemand8e8581e2020-10-20 17:36:46 +0200383 err && *err ? *err : "", fp->area);
William Lallemand03c331c2020-05-13 10:10:01 +0200384 ret = 1;
385 goto end;
386 }
387 }
William Lallemand8e8581e2020-10-20 17:36:46 +0200388 /* remove the added extension */
389 *(fp->area + fp->data - strlen(".sctl")) = '\0';
390 b_sub(fp, strlen(".sctl"));
William Lallemand03c331c2020-05-13 10:10:01 +0200391 }
392#endif
393
394 /* try to load an ocsp response file */
395 if (global_ssl.extra_files & SSL_GF_OCSP) {
William Lallemand03c331c2020-05-13 10:10:01 +0200396 struct stat st;
397
William Lallemand8e8581e2020-10-20 17:36:46 +0200398 if (!chunk_strcat(fp, ".ocsp") || b_data(fp) > MAXPATHLEN) {
399 memprintf(err, "%s '%s' filename too long'.\n",
400 err && *err ? *err : "", fp->area);
401 ret = 1;
402 goto end;
403 }
404
405 if (stat(fp->area, &st) == 0) {
406 if (ssl_sock_load_ocsp_response_from_file(fp->area, NULL, ckch, err)) {
William Lallemand03c331c2020-05-13 10:10:01 +0200407 ret = 1;
408 goto end;
409 }
410 }
William Lallemand8e8581e2020-10-20 17:36:46 +0200411 /* remove the added extension */
412 *(fp->area + fp->data - strlen(".ocsp")) = '\0';
413 b_sub(fp, strlen(".ocsp"));
William Lallemand03c331c2020-05-13 10:10:01 +0200414 }
415
416#ifndef OPENSSL_IS_BORINGSSL /* Useless for BoringSSL */
417 if (ckch->ocsp_response && (global_ssl.extra_files & SSL_GF_OCSP_ISSUER)) {
418 /* if no issuer was found, try to load an issuer from the .issuer */
419 if (!ckch->ocsp_issuer) {
420 struct stat st;
William Lallemand8e8581e2020-10-20 17:36:46 +0200421
422 if (!chunk_strcat(fp, ".issuer") || b_data(fp) > MAXPATHLEN) {
423 memprintf(err, "%s '%s' filename too long'.\n",
424 err && *err ? *err : "", fp->area);
425 ret = 1;
426 goto end;
427 }
William Lallemand03c331c2020-05-13 10:10:01 +0200428
William Lallemand8e8581e2020-10-20 17:36:46 +0200429 if (stat(fp->area, &st) == 0) {
430 if (ssl_sock_load_issuer_file_into_ckch(fp->area, NULL, ckch, err)) {
William Lallemand03c331c2020-05-13 10:10:01 +0200431 ret = 1;
432 goto end;
433 }
434
435 if (X509_check_issued(ckch->ocsp_issuer, ckch->cert) != X509_V_OK) {
436 memprintf(err, "%s '%s' is not an issuer'.\n",
William Lallemand8e8581e2020-10-20 17:36:46 +0200437 err && *err ? *err : "", fp->area);
William Lallemand03c331c2020-05-13 10:10:01 +0200438 ret = 1;
439 goto end;
440 }
441 }
William Lallemand8e8581e2020-10-20 17:36:46 +0200442 /* remove the added extension */
443 *(fp->area + fp->data - strlen(".issuer")) = '\0';
444 b_sub(fp, strlen(".issuer"));
William Lallemand03c331c2020-05-13 10:10:01 +0200445 }
446 }
447#endif
448
449 ret = 0;
450
451end:
452
453 ERR_clear_error();
454
455 /* Something went wrong in one of the reads */
456 if (ret != 0)
457 ssl_sock_free_cert_key_and_chain_contents(ckch);
458
William Lallemand8e8581e2020-10-20 17:36:46 +0200459 free_trash_chunk(fp);
460
William Lallemand03c331c2020-05-13 10:10:01 +0200461 return ret;
462}
463
464/*
465 * Try to load a private key file from a <path> or a buffer <buf>
466 *
467 * If it failed you should not attempt to use the ckch but free it.
468 *
469 * Return 0 on success or != 0 on failure
470 */
471int ssl_sock_load_key_into_ckch(const char *path, char *buf, struct cert_key_and_chain *ckch , char **err)
472{
473 BIO *in = NULL;
474 int ret = 1;
475 EVP_PKEY *key = NULL;
476
477 if (buf) {
478 /* reading from a buffer */
479 in = BIO_new_mem_buf(buf, -1);
480 if (in == NULL) {
481 memprintf(err, "%sCan't allocate memory\n", err && *err ? *err : "");
482 goto end;
483 }
484
485 } else {
486 /* reading from a file */
487 in = BIO_new(BIO_s_file());
488 if (in == NULL)
489 goto end;
490
491 if (BIO_read_filename(in, path) <= 0)
492 goto end;
493 }
494
495 /* Read Private Key */
496 key = PEM_read_bio_PrivateKey(in, NULL, NULL, NULL);
497 if (key == NULL) {
498 memprintf(err, "%sunable to load private key from file '%s'.\n",
499 err && *err ? *err : "", path);
500 goto end;
501 }
502
503 ret = 0;
504
505 SWAP(ckch->key, key);
506
507end:
508
509 ERR_clear_error();
510 if (in)
511 BIO_free(in);
512 if (key)
513 EVP_PKEY_free(key);
514
515 return ret;
516}
517
518/*
519 * Try to load a PEM file from a <path> or a buffer <buf>
520 * The PEM must contain at least a Certificate,
521 * It could contain a DH, a certificate chain and a PrivateKey.
522 *
523 * If it failed you should not attempt to use the ckch but free it.
524 *
525 * Return 0 on success or != 0 on failure
526 */
527int ssl_sock_load_pem_into_ckch(const char *path, char *buf, struct cert_key_and_chain *ckch , char **err)
528{
529 BIO *in = NULL;
530 int ret = 1;
531 X509 *ca;
532 X509 *cert = NULL;
533 EVP_PKEY *key = NULL;
Remi Tricot-Le Bretonc76c3c42022-02-11 12:04:55 +0100534 HASSL_DH *dh = NULL;
William Lallemand03c331c2020-05-13 10:10:01 +0200535 STACK_OF(X509) *chain = NULL;
536
537 if (buf) {
538 /* reading from a buffer */
539 in = BIO_new_mem_buf(buf, -1);
540 if (in == NULL) {
541 memprintf(err, "%sCan't allocate memory\n", err && *err ? *err : "");
542 goto end;
543 }
544
545 } else {
546 /* reading from a file */
547 in = BIO_new(BIO_s_file());
548 if (in == NULL) {
549 memprintf(err, "%sCan't allocate memory\n", err && *err ? *err : "");
550 goto end;
551 }
552
553 if (BIO_read_filename(in, path) <= 0) {
554 memprintf(err, "%scannot open the file '%s'.\n",
555 err && *err ? *err : "", path);
556 goto end;
557 }
558 }
559
560 /* Read Private Key */
561 key = PEM_read_bio_PrivateKey(in, NULL, NULL, NULL);
562 /* no need to check for errors here, because the private key could be loaded later */
563
564#ifndef OPENSSL_NO_DH
565 /* Seek back to beginning of file */
566 if (BIO_reset(in) == -1) {
567 memprintf(err, "%san error occurred while reading the file '%s'.\n",
568 err && *err ? *err : "", path);
569 goto end;
570 }
571
Remi Tricot-Le Bretonc76c3c42022-02-11 12:04:55 +0100572 dh = ssl_sock_get_dh_from_bio(in);
573 ERR_clear_error();
William Lallemand03c331c2020-05-13 10:10:01 +0200574 /* no need to return an error there, dh is not mandatory */
575#endif
576
577 /* Seek back to beginning of file */
578 if (BIO_reset(in) == -1) {
579 memprintf(err, "%san error occurred while reading the file '%s'.\n",
580 err && *err ? *err : "", path);
581 goto end;
582 }
583
584 /* Read Certificate */
585 cert = PEM_read_bio_X509_AUX(in, NULL, NULL, NULL);
586 if (cert == NULL) {
587 memprintf(err, "%sunable to load certificate from file '%s'.\n",
588 err && *err ? *err : "", path);
589 goto end;
590 }
591
592 /* Look for a Certificate Chain */
593 while ((ca = PEM_read_bio_X509(in, NULL, NULL, NULL))) {
594 if (chain == NULL)
595 chain = sk_X509_new_null();
596 if (!sk_X509_push(chain, ca)) {
597 X509_free(ca);
598 goto end;
599 }
600 }
601
602 ret = ERR_get_error();
603 if (ret && (ERR_GET_LIB(ret) != ERR_LIB_PEM && ERR_GET_REASON(ret) != PEM_R_NO_START_LINE)) {
604 memprintf(err, "%sunable to load certificate chain from file '%s'.\n",
605 err && *err ? *err : "", path);
606 goto end;
607 }
608
609 /* once it loaded the PEM, it should remove everything else in the ckch */
610 if (ckch->ocsp_response) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100611 ha_free(&ckch->ocsp_response->area);
612 ha_free(&ckch->ocsp_response);
William Lallemand03c331c2020-05-13 10:10:01 +0200613 }
614
615 if (ckch->sctl) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100616 ha_free(&ckch->sctl->area);
617 ha_free(&ckch->sctl);
William Lallemand03c331c2020-05-13 10:10:01 +0200618 }
619
620 if (ckch->ocsp_issuer) {
621 X509_free(ckch->ocsp_issuer);
622 ckch->ocsp_issuer = NULL;
623 }
624
625 /* no error, fill ckch with new context, old context will be free at end: */
626 SWAP(ckch->key, key);
627 SWAP(ckch->dh, dh);
628 SWAP(ckch->cert, cert);
629 SWAP(ckch->chain, chain);
630
631 ret = 0;
632
633end:
634
635 ERR_clear_error();
636 if (in)
637 BIO_free(in);
638 if (key)
639 EVP_PKEY_free(key);
640 if (dh)
Remi Tricot-Le Bretonc76c3c42022-02-11 12:04:55 +0100641 HASSL_DH_free(dh);
William Lallemand03c331c2020-05-13 10:10:01 +0200642 if (cert)
643 X509_free(cert);
644 if (chain)
645 sk_X509_pop_free(chain, X509_free);
646
647 return ret;
648}
649
650/* Frees the contents of a cert_key_and_chain
651 */
652void ssl_sock_free_cert_key_and_chain_contents(struct cert_key_and_chain *ckch)
653{
654 if (!ckch)
655 return;
656
657 /* Free the certificate and set pointer to NULL */
658 if (ckch->cert)
659 X509_free(ckch->cert);
660 ckch->cert = NULL;
661
662 /* Free the key and set pointer to NULL */
663 if (ckch->key)
664 EVP_PKEY_free(ckch->key);
665 ckch->key = NULL;
666
667 /* Free each certificate in the chain */
668 if (ckch->chain)
669 sk_X509_pop_free(ckch->chain, X509_free);
670 ckch->chain = NULL;
671
672 if (ckch->dh)
Remi Tricot-Le Bretonc76c3c42022-02-11 12:04:55 +0100673 HASSL_DH_free(ckch->dh);
William Lallemand03c331c2020-05-13 10:10:01 +0200674 ckch->dh = NULL;
675
676 if (ckch->sctl) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100677 ha_free(&ckch->sctl->area);
678 ha_free(&ckch->sctl);
William Lallemand03c331c2020-05-13 10:10:01 +0200679 }
680
681 if (ckch->ocsp_response) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100682 ha_free(&ckch->ocsp_response->area);
683 ha_free(&ckch->ocsp_response);
William Lallemand03c331c2020-05-13 10:10:01 +0200684 }
685
686 if (ckch->ocsp_issuer)
687 X509_free(ckch->ocsp_issuer);
688 ckch->ocsp_issuer = NULL;
689}
690
691/*
692 *
693 * This function copy a cert_key_and_chain in memory
694 *
695 * It's used to try to apply changes on a ckch before committing them, because
696 * most of the time it's not possible to revert those changes
697 *
698 * Return a the dst or NULL
699 */
700struct cert_key_and_chain *ssl_sock_copy_cert_key_and_chain(struct cert_key_and_chain *src,
701 struct cert_key_and_chain *dst)
702{
William Lallemand6c096142021-02-23 14:45:45 +0100703 if (!src || !dst)
704 return NULL;
705
William Lallemand03c331c2020-05-13 10:10:01 +0200706 if (src->cert) {
707 dst->cert = src->cert;
708 X509_up_ref(src->cert);
709 }
710
711 if (src->key) {
712 dst->key = src->key;
713 EVP_PKEY_up_ref(src->key);
714 }
715
716 if (src->chain) {
717 dst->chain = X509_chain_up_ref(src->chain);
718 }
719
720 if (src->dh) {
Remi Tricot-Le Bretonc76c3c42022-02-11 12:04:55 +0100721 HASSL_DH_up_ref(src->dh);
William Lallemand03c331c2020-05-13 10:10:01 +0200722 dst->dh = src->dh;
723 }
724
725 if (src->sctl) {
726 struct buffer *sctl;
727
728 sctl = calloc(1, sizeof(*sctl));
729 if (!chunk_dup(sctl, src->sctl)) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100730 ha_free(&sctl);
William Lallemand03c331c2020-05-13 10:10:01 +0200731 goto error;
732 }
733 dst->sctl = sctl;
734 }
735
736 if (src->ocsp_response) {
737 struct buffer *ocsp_response;
738
739 ocsp_response = calloc(1, sizeof(*ocsp_response));
740 if (!chunk_dup(ocsp_response, src->ocsp_response)) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100741 ha_free(&ocsp_response);
William Lallemand03c331c2020-05-13 10:10:01 +0200742 goto error;
743 }
744 dst->ocsp_response = ocsp_response;
745 }
746
747 if (src->ocsp_issuer) {
748 X509_up_ref(src->ocsp_issuer);
749 dst->ocsp_issuer = src->ocsp_issuer;
750 }
751
752 return dst;
753
754error:
755
756 /* free everything */
757 ssl_sock_free_cert_key_and_chain_contents(dst);
758
759 return NULL;
760}
761
762/*
763 * return 0 on success or != 0 on failure
764 */
765int ssl_sock_load_issuer_file_into_ckch(const char *path, char *buf, struct cert_key_and_chain *ckch, char **err)
766{
767 int ret = 1;
768 BIO *in = NULL;
769 X509 *issuer;
770
771 if (buf) {
772 /* reading from a buffer */
773 in = BIO_new_mem_buf(buf, -1);
774 if (in == NULL) {
775 memprintf(err, "%sCan't allocate memory\n", err && *err ? *err : "");
776 goto end;
777 }
778
779 } else {
780 /* reading from a file */
781 in = BIO_new(BIO_s_file());
782 if (in == NULL)
783 goto end;
784
785 if (BIO_read_filename(in, path) <= 0)
786 goto end;
787 }
788
789 issuer = PEM_read_bio_X509_AUX(in, NULL, NULL, NULL);
790 if (!issuer) {
791 memprintf(err, "%s'%s' cannot be read or parsed'.\n",
792 err && *err ? *err : "", path);
793 goto end;
794 }
795 /* no error, fill ckch with new context, old context must be free */
796 if (ckch->ocsp_issuer)
797 X509_free(ckch->ocsp_issuer);
798 ckch->ocsp_issuer = issuer;
799 ret = 0;
800
801end:
802
803 ERR_clear_error();
804 if (in)
805 BIO_free(in);
806
807 return ret;
808}
809
810/******************** ckch_store functions ***********************************
811 * The ckch_store is a structure used to cache and index the SSL files used in
812 * configuration
813 */
814
815/*
816 * Free a ckch_store, its ckch, its instances and remove it from the ebtree
817 */
818void ckch_store_free(struct ckch_store *store)
819{
820 struct ckch_inst *inst, *inst_s;
821
822 if (!store)
823 return;
824
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200825 ssl_sock_free_cert_key_and_chain_contents(store->ckch);
William Lallemand03c331c2020-05-13 10:10:01 +0200826
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100827 ha_free(&store->ckch);
William Lallemand03c331c2020-05-13 10:10:01 +0200828
829 list_for_each_entry_safe(inst, inst_s, &store->ckch_inst, by_ckchs) {
830 ckch_inst_free(inst);
831 }
832 ebmb_delete(&store->node);
833 free(store);
834}
835
836/*
837 * create and initialize a ckch_store
838 * <path> is the key name
839 * <nmemb> is the number of store->ckch objects to allocate
840 *
841 * Return a ckch_store or NULL upon failure.
842 */
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200843struct ckch_store *ckch_store_new(const char *filename)
William Lallemand03c331c2020-05-13 10:10:01 +0200844{
845 struct ckch_store *store;
846 int pathlen;
847
848 pathlen = strlen(filename);
849 store = calloc(1, sizeof(*store) + pathlen + 1);
850 if (!store)
851 return NULL;
852
William Lallemand03c331c2020-05-13 10:10:01 +0200853 memcpy(store->path, filename, pathlen + 1);
854
855 LIST_INIT(&store->ckch_inst);
856 LIST_INIT(&store->crtlist_entry);
857
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200858 store->ckch = calloc(1, sizeof(*store->ckch));
William Lallemand03c331c2020-05-13 10:10:01 +0200859 if (!store->ckch)
860 goto error;
861
862 return store;
863error:
864 ckch_store_free(store);
865 return NULL;
866}
867
868/* allocate and duplicate a ckch_store
869 * Return a new ckch_store or NULL */
870struct ckch_store *ckchs_dup(const struct ckch_store *src)
871{
872 struct ckch_store *dst;
873
William Lallemand6c096142021-02-23 14:45:45 +0100874 if (!src)
875 return NULL;
876
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200877 dst = ckch_store_new(src->path);
Eric Salama6ac61e32021-02-23 16:50:57 +0100878 if (!dst)
879 return NULL;
William Lallemand03c331c2020-05-13 10:10:01 +0200880
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200881 if (!ssl_sock_copy_cert_key_and_chain(src->ckch, dst->ckch))
882 goto error;
William Lallemand03c331c2020-05-13 10:10:01 +0200883
884 return dst;
885
886error:
887 ckch_store_free(dst);
888
889 return NULL;
890}
891
892/*
893 * lookup a path into the ckchs tree.
894 */
895struct ckch_store *ckchs_lookup(char *path)
896{
897 struct ebmb_node *eb;
898
899 eb = ebst_lookup(&ckchs_tree, path);
900 if (!eb)
901 return NULL;
902
903 return ebmb_entry(eb, struct ckch_store, node);
904}
905
906/*
907 * This function allocate a ckch_store and populate it with certificates from files.
908 */
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200909struct ckch_store *ckchs_load_cert_file(char *path, char **err)
William Lallemand03c331c2020-05-13 10:10:01 +0200910{
911 struct ckch_store *ckchs;
912
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200913 ckchs = ckch_store_new(path);
William Lallemand03c331c2020-05-13 10:10:01 +0200914 if (!ckchs) {
915 memprintf(err, "%sunable to allocate memory.\n", err && *err ? *err : "");
916 goto end;
917 }
William Lallemand03c331c2020-05-13 10:10:01 +0200918
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200919 if (ssl_sock_load_files_into_ckch(path, ckchs->ckch, err) == 1)
920 goto end;
William Lallemand03c331c2020-05-13 10:10:01 +0200921
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200922 /* insert into the ckchs tree */
923 memcpy(ckchs->path, path, strlen(path) + 1);
924 ebst_insert(&ckchs_tree, &ckchs->node);
William Lallemand03c331c2020-05-13 10:10:01 +0200925 return ckchs;
926
927end:
928 ckch_store_free(ckchs);
929
930 return NULL;
931}
932
William Lallemandfa1d8b42020-05-13 15:46:10 +0200933
934/******************** ckch_inst functions ******************************/
935
936/* unlink a ckch_inst, free all SNIs, free the ckch_inst */
937/* The caller must use the lock of the bind_conf if used with inserted SNIs */
938void ckch_inst_free(struct ckch_inst *inst)
939{
940 struct sni_ctx *sni, *sni_s;
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +0100941 struct ckch_inst_link_ref *link_ref, *link_ref_s;
William Lallemandfa1d8b42020-05-13 15:46:10 +0200942
943 if (inst == NULL)
944 return;
945
946 list_for_each_entry_safe(sni, sni_s, &inst->sni_ctx, by_ckch_inst) {
947 SSL_CTX_free(sni->ctx);
Willy Tarreau2b718102021-04-21 07:32:39 +0200948 LIST_DELETE(&sni->by_ckch_inst);
William Lallemandfa1d8b42020-05-13 15:46:10 +0200949 ebmb_delete(&sni->name);
950 free(sni);
951 }
Remi Tricot-Le Bretonf3eedfe2021-01-25 17:19:44 +0100952 SSL_CTX_free(inst->ctx);
953 inst->ctx = NULL;
Willy Tarreau2b718102021-04-21 07:32:39 +0200954 LIST_DELETE(&inst->by_ckchs);
955 LIST_DELETE(&inst->by_crtlist_entry);
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +0100956
957 list_for_each_entry_safe(link_ref, link_ref_s, &inst->cafile_link_refs, list) {
958 LIST_DELETE(&link_ref->link->list);
959 LIST_DELETE(&link_ref->list);
960 free(link_ref);
961 }
962
William Lallemandfa1d8b42020-05-13 15:46:10 +0200963 free(inst);
964}
965
966/* Alloc and init a ckch_inst */
967struct ckch_inst *ckch_inst_new()
968{
969 struct ckch_inst *ckch_inst;
970
971 ckch_inst = calloc(1, sizeof *ckch_inst);
972 if (!ckch_inst)
973 return NULL;
974
975 LIST_INIT(&ckch_inst->sni_ctx);
976 LIST_INIT(&ckch_inst->by_ckchs);
977 LIST_INIT(&ckch_inst->by_crtlist_entry);
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +0100978 LIST_INIT(&ckch_inst->cafile_link_refs);
William Lallemandfa1d8b42020-05-13 15:46:10 +0200979
980 return ckch_inst;
981}
982
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +0200983
984/******************** ssl_store functions ******************************/
Remi Tricot-Le Breton9f0c9362021-02-19 15:06:28 +0100985struct eb_root cafile_tree = EB_ROOT;
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +0200986
Remi Tricot-Le Breton9f0c9362021-02-19 15:06:28 +0100987/*
988 * Returns the cafile_entry found in the cafile_tree indexed by the path 'path'.
989 * If 'oldest_entry' is 1, returns the "original" cafile_entry (since
990 * during a set cafile/commit cafile cycle there might be two entries for any
991 * given path, the original one and the new one set via the CLI but not
992 * committed yet).
993 */
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +0100994struct cafile_entry *ssl_store_get_cafile_entry(char *path, int oldest_entry)
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +0200995{
Remi Tricot-Le Breton9f0c9362021-02-19 15:06:28 +0100996 struct cafile_entry *ca_e = NULL;
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +0200997 struct ebmb_node *eb;
998
999 eb = ebst_lookup(&cafile_tree, path);
Remi Tricot-Le Breton9f0c9362021-02-19 15:06:28 +01001000 while (eb) {
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001001 ca_e = ebmb_entry(eb, struct cafile_entry, node);
Remi Tricot-Le Breton9f0c9362021-02-19 15:06:28 +01001002 /* The ebst_lookup in a tree that has duplicates returns the
1003 * oldest entry first. If we want the latest entry, we need to
1004 * iterate over all the duplicates until we find the last one
1005 * (in our case there should never be more than two entries for
1006 * any given path). */
1007 if (oldest_entry)
1008 return ca_e;
1009 eb = ebmb_next_dup(eb);
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001010 }
Remi Tricot-Le Breton9f0c9362021-02-19 15:06:28 +01001011 return ca_e;
1012}
1013
Remi Tricot-Le Breton38c999b2021-02-23 16:28:43 +01001014int ssl_store_add_uncommitted_cafile_entry(struct cafile_entry *entry)
1015{
1016 return (ebst_insert(&cafile_tree, &entry->node) != &entry->node);
1017}
1018
Remi Tricot-Le Breton9f0c9362021-02-19 15:06:28 +01001019X509_STORE* ssl_store_get0_locations_file(char *path)
1020{
1021 struct cafile_entry *ca_e = ssl_store_get_cafile_entry(path, 0);
1022
1023 if (ca_e)
1024 return ca_e->ca_store;
1025
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001026 return NULL;
1027}
1028
Remi Tricot-Le Breton5daff3c2021-02-22 15:54:55 +01001029/* Create a cafile_entry object, without adding it to the cafile_tree. */
Remi Tricot-Le Breton0bb48242021-04-16 17:59:23 +02001030struct cafile_entry *ssl_store_create_cafile_entry(char *path, X509_STORE *store, enum cafile_type type)
Remi Tricot-Le Breton5daff3c2021-02-22 15:54:55 +01001031{
1032 struct cafile_entry *ca_e;
1033 int pathlen;
1034
1035 pathlen = strlen(path);
1036
1037 ca_e = calloc(1, sizeof(*ca_e) + pathlen + 1);
1038 if (ca_e) {
1039 memcpy(ca_e->path, path, pathlen + 1);
1040 ca_e->ca_store = store;
Remi Tricot-Le Breton0bb48242021-04-16 17:59:23 +02001041 ca_e->type = type;
Remi Tricot-Le Breton5daff3c2021-02-22 15:54:55 +01001042 LIST_INIT(&ca_e->ckch_inst_link);
1043 }
1044 return ca_e;
1045}
1046
1047/* Delete a cafile_entry. The caller is responsible from removing this entry
1048 * from the cafile_tree first if is was previously added into it. */
1049void ssl_store_delete_cafile_entry(struct cafile_entry *ca_e)
1050{
1051 struct ckch_inst_link *link, *link_s;
1052 if (!ca_e)
1053 return;
1054
1055 X509_STORE_free(ca_e->ca_store);
1056
1057 list_for_each_entry_safe(link, link_s, &ca_e->ckch_inst_link, list) {
1058 struct ckch_inst *inst = link->ckch_inst;
1059 struct ckch_inst_link_ref *link_ref, *link_ref_s;
1060 list_for_each_entry_safe(link_ref, link_ref_s, &inst->cafile_link_refs, list) {
1061 if (link_ref->link == link) {
1062 LIST_DELETE(&link_ref->list);
1063 free(link_ref);
1064 break;
1065 }
1066 }
1067 LIST_DELETE(&link->list);
1068 free(link);
1069 }
1070
1071 free(ca_e);
1072}
1073
Remi Tricot-Le Breton383fb142021-02-22 18:26:14 +01001074/*
1075 * Build a cafile_entry out of a buffer instead of out of a file.
1076 * This function is used when the "commit ssl ca-file" cli command is used.
1077 * It can parse CERTIFICATE sections as well as CRL ones.
1078 * Returns 0 in case of success, 1 otherwise.
1079 */
1080int ssl_store_load_ca_from_buf(struct cafile_entry *ca_e, char *cert_buf)
1081{
1082 int retval = 0;
1083
1084 if (!ca_e)
1085 return 1;
1086
1087 if (!ca_e->ca_store) {
1088 ca_e->ca_store = X509_STORE_new();
1089 if (ca_e->ca_store) {
1090 BIO *bio = BIO_new_mem_buf(cert_buf, strlen(cert_buf));
1091 if (bio) {
1092 X509_INFO *info;
1093 int i;
1094 STACK_OF(X509_INFO) *infos = PEM_X509_INFO_read_bio(bio, NULL, NULL, NULL);
1095 if (!infos)
1096 {
1097 BIO_free(bio);
1098 return 1;
1099 }
1100
1101 for (i = 0; i < sk_X509_INFO_num(infos) && !retval; i++) {
1102 info = sk_X509_INFO_value(infos, i);
1103 /* X509_STORE_add_cert and X509_STORE_add_crl return 1 on success */
1104 if (info->x509) {
1105 retval = !X509_STORE_add_cert(ca_e->ca_store, info->x509);
1106 }
1107 if (!retval && info->crl) {
1108 retval = !X509_STORE_add_crl(ca_e->ca_store, info->crl);
1109 }
1110 }
1111 retval = retval || (i != sk_X509_INFO_num(infos));
1112
1113 /* Cleanup */
1114 sk_X509_INFO_pop_free(infos, X509_INFO_free);
1115 BIO_free(bio);
1116 }
1117 }
1118 }
1119
1120 return retval;
1121}
1122
Remi Tricot-Le Breton0bb48242021-04-16 17:59:23 +02001123int ssl_store_load_locations_file(char *path, int create_if_none, enum cafile_type type)
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001124{
1125 X509_STORE *store = ssl_store_get0_locations_file(path);
1126
1127 /* If this function is called by the CLI, we should not call the
1128 * X509_STORE_load_locations function because it performs forbidden disk
1129 * accesses. */
1130 if (!store && create_if_none) {
William Lallemand87fd9942022-04-01 20:12:03 +02001131 STACK_OF(X509_OBJECT) *objs;
1132 int cert_count = 0;
1133 struct stat buf;
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001134 struct cafile_entry *ca_e;
William Lallemandc6b17632022-04-01 23:39:37 +02001135 const char *file = NULL;
1136 const char *dir = NULL;
William Lallemand87fd9942022-04-01 20:12:03 +02001137
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001138 store = X509_STORE_new();
William Lallemand87fd9942022-04-01 20:12:03 +02001139
William Lallemandc6b17632022-04-01 23:39:37 +02001140 if (strcmp(path, "@system-ca") == 0) {
1141 dir = X509_get_default_cert_dir();
William Lallemand87fd9942022-04-01 20:12:03 +02001142
William Lallemandc6b17632022-04-01 23:39:37 +02001143 } else {
1144
1145 if (stat(path, &buf))
1146 goto err;
1147
1148 if (S_ISDIR(buf.st_mode))
1149 dir = path;
1150 else
1151 file = path;
1152 }
William Lallemand87fd9942022-04-01 20:12:03 +02001153
1154 if (file) {
1155 if (!X509_STORE_load_locations(store, file, NULL)) {
1156 goto err;
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001157 }
William Lallemand80296b42022-04-05 10:19:30 +02001158 } else if (dir) {
William Lallemand87fd9942022-04-01 20:12:03 +02001159 int n, i;
1160 struct dirent **de_list;
1161
1162 n = scandir(dir, &de_list, 0, alphasort);
1163 if (n < 0)
1164 goto err;
1165
1166 for (i= 0; i < n; i++) {
1167 char *end;
1168 struct dirent *de = de_list[i];
1169 BIO *in = NULL;
1170 X509 *ca = NULL;;
1171
1172 /* we try to load the files that would have
1173 * been loaded in an hashed directory loaded by
1174 * X509_LOOKUP_hash_dir, so according to "man 1
1175 * c_rehash", we should load ".pem", ".crt",
1176 * ".cer", or ".crl"
1177 */
1178 end = strrchr(de->d_name, '.');
1179 if (!end || (strcmp(end, ".pem") != 0 &&
1180 strcmp(end, ".crt") != 0 &&
1181 strcmp(end, ".cer") != 0 &&
1182 strcmp(end, ".crl") != 0)) {
1183 free(de);
1184 continue;
1185 }
1186 in = BIO_new(BIO_s_file());
1187 if (in == NULL)
1188 goto scandir_err;
1189
William Lallemandc6b17632022-04-01 23:39:37 +02001190 chunk_printf(&trash, "%s/%s", dir, de->d_name);
William Lallemand87fd9942022-04-01 20:12:03 +02001191
1192 if (BIO_read_filename(in, trash.area) == 0)
1193 goto scandir_err;
1194
1195 if (PEM_read_bio_X509_AUX(in, &ca, NULL, NULL) == NULL)
1196 goto scandir_err;
1197
1198 if (X509_STORE_add_cert(store, ca) == 0)
1199 goto scandir_err;
1200
William Lallemand4cfbf3c2022-04-26 15:57:33 +02001201 X509_free(ca);
William Lallemand87fd9942022-04-01 20:12:03 +02001202 BIO_free(in);
1203 free(de);
1204 continue;
1205
1206scandir_err:
William Lallemand4cfbf3c2022-04-26 15:57:33 +02001207 X509_free(ca);
William Lallemand87fd9942022-04-01 20:12:03 +02001208 BIO_free(in);
1209 free(de);
1210 ha_warning("ca-file: '%s' couldn't load '%s'\n", path, trash.area);
William Lallemand87fd9942022-04-01 20:12:03 +02001211
1212 }
1213 free(de_list);
William Lallemand80296b42022-04-05 10:19:30 +02001214 } else {
1215 ha_alert("ca-file: couldn't load '%s'\n", path);
1216 goto err;
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001217 }
William Lallemand87fd9942022-04-01 20:12:03 +02001218
1219 objs = X509_STORE_get0_objects(store);
1220 cert_count = sk_X509_OBJECT_num(objs);
1221 if (cert_count == 0)
1222 ha_warning("ca-file: 0 CA were loaded from '%s'\n", path);
1223
1224 ca_e = ssl_store_create_cafile_entry(path, store, type);
1225 if (!ca_e)
1226 goto err;
1227 ebst_insert(&cafile_tree, &ca_e->node);
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001228 }
1229 return (store != NULL);
William Lallemand87fd9942022-04-01 20:12:03 +02001230
1231err:
1232 X509_STORE_free(store);
1233 store = NULL;
1234 return 0;
1235
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001236}
1237
1238
William Lallemandda8584c2020-05-14 10:14:37 +02001239/*************************** CLI commands ***********************/
1240
1241/* Type of SSL payloads that can be updated over the CLI */
1242
William Lallemandff8bf982022-03-29 10:44:23 +02001243struct cert_exts cert_exts[] = {
1244 { "", CERT_TYPE_PEM, &ssl_sock_load_pem_into_ckch }, /* default mode, no extensions */
William Lallemand26654e72022-03-30 12:01:32 +02001245 { "crt", CERT_TYPE_CRT, &ssl_sock_load_pem_into_ckch },
William Lallemandff8bf982022-03-29 10:44:23 +02001246 { "key", CERT_TYPE_KEY, &ssl_sock_load_key_into_ckch },
William Lallemandda8584c2020-05-14 10:14:37 +02001247#if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) || defined OPENSSL_IS_BORINGSSL)
William Lallemandff8bf982022-03-29 10:44:23 +02001248 { "ocsp", CERT_TYPE_OCSP, &ssl_sock_load_ocsp_response_from_file },
William Lallemandda8584c2020-05-14 10:14:37 +02001249#endif
Ilya Shipitsinc47d6762021-02-13 11:45:33 +05001250#ifdef HAVE_SSL_SCTL
William Lallemandff8bf982022-03-29 10:44:23 +02001251 { "sctl", CERT_TYPE_SCTL, &ssl_sock_load_sctl_from_file },
William Lallemandda8584c2020-05-14 10:14:37 +02001252#endif
William Lallemandff8bf982022-03-29 10:44:23 +02001253 { "issuer", CERT_TYPE_ISSUER, &ssl_sock_load_issuer_file_into_ckch },
1254 { NULL, CERT_TYPE_MAX, NULL },
William Lallemandda8584c2020-05-14 10:14:37 +02001255};
1256
1257
1258/* release function of the `show ssl cert' command */
1259static void cli_release_show_cert(struct appctx *appctx)
1260{
1261 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1262}
1263
Willy Tarreau4fd9b4d2022-05-04 16:11:50 +02001264/* IO handler of "show ssl cert <filename>".
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001265 * It makes use of a show_cert_ctx context, and ckchs_transaction in read-only.
Willy Tarreau4fd9b4d2022-05-04 16:11:50 +02001266 */
William Lallemandda8584c2020-05-14 10:14:37 +02001267static int cli_io_handler_show_cert(struct appctx *appctx)
1268{
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001269 struct show_cert_ctx *ctx = appctx->svcctx;
William Lallemandda8584c2020-05-14 10:14:37 +02001270 struct buffer *trash = alloc_trash_chunk();
1271 struct ebmb_node *node;
Christopher Faulet908628c2022-03-25 16:43:49 +01001272 struct conn_stream *cs = appctx->owner;
William Lallemandda8584c2020-05-14 10:14:37 +02001273 struct ckch_store *ckchs;
1274
1275 if (trash == NULL)
1276 return 1;
1277
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001278 if (!ctx->old_ckchs) {
William Lallemandda8584c2020-05-14 10:14:37 +02001279 if (ckchs_transaction.old_ckchs) {
1280 ckchs = ckchs_transaction.old_ckchs;
1281 chunk_appendf(trash, "# transaction\n");
William Lallemand5685ccf2020-09-16 16:12:25 +02001282 chunk_appendf(trash, "*%s\n", ckchs->path);
William Lallemandda8584c2020-05-14 10:14:37 +02001283 }
1284 }
1285
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001286 if (!ctx->cur_ckchs) {
William Lallemandda8584c2020-05-14 10:14:37 +02001287 chunk_appendf(trash, "# filename\n");
1288 node = ebmb_first(&ckchs_tree);
1289 } else {
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001290 node = &ctx->cur_ckchs->node;
William Lallemandda8584c2020-05-14 10:14:37 +02001291 }
1292 while (node) {
1293 ckchs = ebmb_entry(node, struct ckch_store, node);
William Lallemand5685ccf2020-09-16 16:12:25 +02001294 chunk_appendf(trash, "%s\n", ckchs->path);
William Lallemandda8584c2020-05-14 10:14:37 +02001295
1296 node = ebmb_next(node);
Christopher Faulet908628c2022-03-25 16:43:49 +01001297 if (ci_putchk(cs_ic(cs), trash) == -1) {
Christopher Fauleta0bdec32022-04-04 07:51:21 +02001298 cs_rx_room_blk(cs);
William Lallemandda8584c2020-05-14 10:14:37 +02001299 goto yield;
1300 }
1301 }
1302
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001303 ctx->cur_ckchs = NULL;
William Lallemandda8584c2020-05-14 10:14:37 +02001304 free_trash_chunk(trash);
1305 return 1;
1306yield:
1307
1308 free_trash_chunk(trash);
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001309 ctx->cur_ckchs = ckchs;
William Lallemandda8584c2020-05-14 10:14:37 +02001310 return 0; /* should come back */
1311}
1312
1313/*
1314 * Extract and format the DNS SAN extensions and copy result into a chuink
1315 * Return 0;
1316 */
1317#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
1318static int ssl_sock_get_san_oneline(X509 *cert, struct buffer *out)
1319{
1320 int i;
1321 char *str;
1322 STACK_OF(GENERAL_NAME) *names = NULL;
1323
1324 names = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
1325 if (names) {
1326 for (i = 0; i < sk_GENERAL_NAME_num(names); i++) {
1327 GENERAL_NAME *name = sk_GENERAL_NAME_value(names, i);
1328 if (i > 0)
1329 chunk_appendf(out, ", ");
1330 if (name->type == GEN_DNS) {
1331 if (ASN1_STRING_to_UTF8((unsigned char **)&str, name->d.dNSName) >= 0) {
1332 chunk_appendf(out, "DNS:%s", str);
1333 OPENSSL_free(str);
1334 }
1335 }
1336 }
1337 sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free);
1338 }
1339 return 0;
1340}
1341#endif
1342
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001343/*
1344 * Build the ckch_inst_link that will be chained in the CA file entry and the
1345 * corresponding ckch_inst_link_ref that will be chained in the ckch instance.
1346 * Return 0 in case of success.
1347 */
1348static int do_chain_inst_and_cafile(struct cafile_entry *cafile_entry, struct ckch_inst *ckch_inst)
1349{
1350 struct ckch_inst_link *new_link;
1351 if (!LIST_ISEMPTY(&cafile_entry->ckch_inst_link)) {
1352 struct ckch_inst_link *link = LIST_ELEM(cafile_entry->ckch_inst_link.n,
1353 typeof(link), list);
1354 /* Do not add multiple references to the same
1355 * instance in a cafile_entry */
1356 if (link->ckch_inst == ckch_inst) {
1357 return 1;
1358 }
1359 }
1360
1361 new_link = calloc(1, sizeof(*new_link));
1362 if (new_link) {
1363 struct ckch_inst_link_ref *new_link_ref = calloc(1, sizeof(*new_link_ref));
1364 if (!new_link_ref) {
1365 free(new_link);
1366 return 1;
1367 }
1368
1369 new_link->ckch_inst = ckch_inst;
1370 new_link_ref->link = new_link;
1371 LIST_INIT(&new_link->list);
1372 LIST_INIT(&new_link_ref->list);
1373
1374 LIST_APPEND(&cafile_entry->ckch_inst_link, &new_link->list);
1375 LIST_APPEND(&ckch_inst->cafile_link_refs, &new_link_ref->list);
1376 }
1377
1378 return 0;
1379}
1380
1381
1382/*
1383 * Link a CA file tree entry to the ckch instance that uses it.
1384 * To determine if and which CA file tree entries need to be linked to the
1385 * instance, we follow the same logic performed in ssl_sock_prepare_ctx when
1386 * processing the verify option.
1387 * This function works for a frontend as well as for a backend, depending on the
1388 * configuration parameters given (bind_conf or server).
1389 */
1390void ckch_inst_add_cafile_link(struct ckch_inst *ckch_inst, struct bind_conf *bind_conf,
1391 struct ssl_bind_conf *ssl_conf, const struct server *srv)
1392{
1393 int verify = SSL_VERIFY_NONE;
1394
1395 if (srv) {
1396
1397 if (global.ssl_server_verify == SSL_SERVER_VERIFY_REQUIRED)
1398 verify = SSL_VERIFY_PEER;
1399 switch (srv->ssl_ctx.verify) {
1400 case SSL_SOCK_VERIFY_NONE:
1401 verify = SSL_VERIFY_NONE;
1402 break;
1403 case SSL_SOCK_VERIFY_REQUIRED:
1404 verify = SSL_VERIFY_PEER;
1405 break;
1406 }
1407 }
1408 else {
1409 switch ((ssl_conf && ssl_conf->verify) ? ssl_conf->verify : bind_conf->ssl_conf.verify) {
1410 case SSL_SOCK_VERIFY_NONE:
1411 verify = SSL_VERIFY_NONE;
1412 break;
1413 case SSL_SOCK_VERIFY_OPTIONAL:
1414 verify = SSL_VERIFY_PEER;
1415 break;
1416 case SSL_SOCK_VERIFY_REQUIRED:
1417 verify = SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
1418 break;
1419 }
1420 }
1421
1422 if (verify & SSL_VERIFY_PEER) {
1423 struct cafile_entry *ca_file_entry = NULL;
1424 struct cafile_entry *ca_verify_file_entry = NULL;
Remi Tricot-Le Bretonf81c70c2021-04-20 16:54:21 +02001425 struct cafile_entry *crl_file_entry = NULL;
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001426 if (srv) {
1427 if (srv->ssl_ctx.ca_file) {
1428 ca_file_entry = ssl_store_get_cafile_entry(srv->ssl_ctx.ca_file, 0);
1429
1430 }
Remi Tricot-Le Bretonf81c70c2021-04-20 16:54:21 +02001431 if (srv->ssl_ctx.crl_file) {
1432 crl_file_entry = ssl_store_get_cafile_entry(srv->ssl_ctx.crl_file, 0);
1433 }
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001434 }
1435 else {
1436 char *ca_file = (ssl_conf && ssl_conf->ca_file) ? ssl_conf->ca_file : bind_conf->ssl_conf.ca_file;
1437 char *ca_verify_file = (ssl_conf && ssl_conf->ca_verify_file) ? ssl_conf->ca_verify_file : bind_conf->ssl_conf.ca_verify_file;
Remi Tricot-Le Bretonf81c70c2021-04-20 16:54:21 +02001438 char *crl_file = (ssl_conf && ssl_conf->crl_file) ? ssl_conf->crl_file : bind_conf->ssl_conf.crl_file;
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001439
1440 if (ca_file)
1441 ca_file_entry = ssl_store_get_cafile_entry(ca_file, 0);
1442 if (ca_verify_file)
1443 ca_verify_file_entry = ssl_store_get_cafile_entry(ca_verify_file, 0);
Remi Tricot-Le Bretonf81c70c2021-04-20 16:54:21 +02001444 if (crl_file)
1445 crl_file_entry = ssl_store_get_cafile_entry(crl_file, 0);
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001446 }
1447
1448 if (ca_file_entry) {
1449 /* If we have a ckch instance that is not already in the
1450 * cafile_entry's list, add it to it. */
1451 if (do_chain_inst_and_cafile(ca_file_entry, ckch_inst))
1452 return;
1453
1454 }
1455 if (ca_verify_file_entry && (ca_file_entry != ca_verify_file_entry)) {
1456 /* If we have a ckch instance that is not already in the
1457 * cafile_entry's list, add it to it. */
1458 if (do_chain_inst_and_cafile(ca_verify_file_entry, ckch_inst))
1459 return;
1460 }
Remi Tricot-Le Bretonf81c70c2021-04-20 16:54:21 +02001461 if (crl_file_entry) {
1462 /* If we have a ckch instance that is not already in the
1463 * cafile_entry's list, add it to it. */
1464 if (do_chain_inst_and_cafile(crl_file_entry, ckch_inst))
1465 return;
1466 }
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001467 }
1468}
1469
William Lallemandda8584c2020-05-14 10:14:37 +02001470
1471
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001472static int show_cert_detail(X509 *cert, STACK_OF(X509) *chain, struct buffer *out)
William Lallemandda8584c2020-05-14 10:14:37 +02001473{
William Lallemandda8584c2020-05-14 10:14:37 +02001474 BIO *bio = NULL;
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001475 struct buffer *tmp = alloc_trash_chunk();
William Lallemandda8584c2020-05-14 10:14:37 +02001476 int i;
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001477 int write = -1;
1478 unsigned int len = 0;
1479 X509_NAME *name = NULL;
William Lallemandda8584c2020-05-14 10:14:37 +02001480
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001481 if (!tmp)
1482 return -1;
William Lallemandda8584c2020-05-14 10:14:37 +02001483
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001484 if (!cert)
William Lallemand5685ccf2020-09-16 16:12:25 +02001485 goto end;
William Lallemandda8584c2020-05-14 10:14:37 +02001486
William Lallemand5685ccf2020-09-16 16:12:25 +02001487 if (chain == NULL) {
1488 struct issuer_chain *issuer;
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001489 issuer = ssl_get0_issuer_chain(cert);
William Lallemand5685ccf2020-09-16 16:12:25 +02001490 if (issuer) {
1491 chain = issuer->chain;
1492 chunk_appendf(out, "Chain Filename: ");
1493 chunk_appendf(out, "%s\n", issuer->path);
William Lallemandda8584c2020-05-14 10:14:37 +02001494 }
William Lallemand5685ccf2020-09-16 16:12:25 +02001495 }
1496 chunk_appendf(out, "Serial: ");
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001497 if (ssl_sock_get_serial(cert, tmp) == -1)
William Lallemand5685ccf2020-09-16 16:12:25 +02001498 goto end;
1499 dump_binary(out, tmp->area, tmp->data);
1500 chunk_appendf(out, "\n");
William Lallemandda8584c2020-05-14 10:14:37 +02001501
William Lallemand5685ccf2020-09-16 16:12:25 +02001502 chunk_appendf(out, "notBefore: ");
1503 chunk_reset(tmp);
1504 if ((bio = BIO_new(BIO_s_mem())) == NULL)
1505 goto end;
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001506 if (ASN1_TIME_print(bio, X509_getm_notBefore(cert)) == 0)
William Lallemand5685ccf2020-09-16 16:12:25 +02001507 goto end;
1508 write = BIO_read(bio, tmp->area, tmp->size-1);
1509 tmp->area[write] = '\0';
1510 BIO_free(bio);
1511 bio = NULL;
1512 chunk_appendf(out, "%s\n", tmp->area);
William Lallemandda8584c2020-05-14 10:14:37 +02001513
William Lallemand5685ccf2020-09-16 16:12:25 +02001514 chunk_appendf(out, "notAfter: ");
1515 chunk_reset(tmp);
1516 if ((bio = BIO_new(BIO_s_mem())) == NULL)
1517 goto end;
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001518 if (ASN1_TIME_print(bio, X509_getm_notAfter(cert)) == 0)
William Lallemand5685ccf2020-09-16 16:12:25 +02001519 goto end;
1520 if ((write = BIO_read(bio, tmp->area, tmp->size-1)) <= 0)
1521 goto end;
1522 tmp->area[write] = '\0';
1523 BIO_free(bio);
1524 bio = NULL;
1525 chunk_appendf(out, "%s\n", tmp->area);
William Lallemandda8584c2020-05-14 10:14:37 +02001526
1527#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
William Lallemand5685ccf2020-09-16 16:12:25 +02001528 chunk_appendf(out, "Subject Alternative Name: ");
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001529 if (ssl_sock_get_san_oneline(cert, out) == -1)
William Lallemand5685ccf2020-09-16 16:12:25 +02001530 goto end;
1531 *(out->area + out->data) = '\0';
1532 chunk_appendf(out, "\n");
William Lallemandda8584c2020-05-14 10:14:37 +02001533#endif
William Lallemand5685ccf2020-09-16 16:12:25 +02001534 chunk_reset(tmp);
1535 chunk_appendf(out, "Algorithm: ");
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001536 if (cert_get_pkey_algo(cert, tmp) == 0)
William Lallemand5685ccf2020-09-16 16:12:25 +02001537 goto end;
1538 chunk_appendf(out, "%s\n", tmp->area);
William Lallemandda8584c2020-05-14 10:14:37 +02001539
William Lallemand5685ccf2020-09-16 16:12:25 +02001540 chunk_reset(tmp);
1541 chunk_appendf(out, "SHA1 FingerPrint: ");
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001542 if (X509_digest(cert, EVP_sha1(), (unsigned char *) tmp->area, &len) == 0)
William Lallemand5685ccf2020-09-16 16:12:25 +02001543 goto end;
1544 tmp->data = len;
1545 dump_binary(out, tmp->area, tmp->data);
1546 chunk_appendf(out, "\n");
William Lallemandda8584c2020-05-14 10:14:37 +02001547
William Lallemand5685ccf2020-09-16 16:12:25 +02001548 chunk_appendf(out, "Subject: ");
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001549 if ((name = X509_get_subject_name(cert)) == NULL)
William Lallemand5685ccf2020-09-16 16:12:25 +02001550 goto end;
1551 if ((ssl_sock_get_dn_oneline(name, tmp)) == -1)
1552 goto end;
1553 *(tmp->area + tmp->data) = '\0';
1554 chunk_appendf(out, "%s\n", tmp->area);
1555
1556 chunk_appendf(out, "Issuer: ");
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001557 if ((name = X509_get_issuer_name(cert)) == NULL)
William Lallemand5685ccf2020-09-16 16:12:25 +02001558 goto end;
1559 if ((ssl_sock_get_dn_oneline(name, tmp)) == -1)
1560 goto end;
1561 *(tmp->area + tmp->data) = '\0';
1562 chunk_appendf(out, "%s\n", tmp->area);
1563
1564 /* Displays subject of each certificate in the chain */
1565 for (i = 0; i < sk_X509_num(chain); i++) {
1566 X509 *ca = sk_X509_value(chain, i);
1567
1568 chunk_appendf(out, "Chain Subject: ");
1569 if ((name = X509_get_subject_name(ca)) == NULL)
William Lallemandda8584c2020-05-14 10:14:37 +02001570 goto end;
1571 if ((ssl_sock_get_dn_oneline(name, tmp)) == -1)
1572 goto end;
1573 *(tmp->area + tmp->data) = '\0';
1574 chunk_appendf(out, "%s\n", tmp->area);
1575
William Lallemand5685ccf2020-09-16 16:12:25 +02001576 chunk_appendf(out, "Chain Issuer: ");
1577 if ((name = X509_get_issuer_name(ca)) == NULL)
William Lallemandda8584c2020-05-14 10:14:37 +02001578 goto end;
1579 if ((ssl_sock_get_dn_oneline(name, tmp)) == -1)
1580 goto end;
1581 *(tmp->area + tmp->data) = '\0';
1582 chunk_appendf(out, "%s\n", tmp->area);
William Lallemandda8584c2020-05-14 10:14:37 +02001583 }
1584
1585end:
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001586 if (bio)
1587 BIO_free(bio);
1588 free_trash_chunk(tmp);
1589
1590 return 0;
1591}
1592
Remi Tricot-Le Breton3faf0cb2021-06-10 18:10:32 +02001593#if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) && !defined OPENSSL_IS_BORINGSSL)
Remi Tricot-Le Bretonda968f62021-06-10 13:51:14 +02001594/*
1595 * Build the OCSP tree entry's key for a given ckch_store.
1596 * Returns a negative value in case of error.
1597 */
1598static int ckch_store_build_certid(struct ckch_store *ckch_store, unsigned char certid[128], unsigned int *key_length)
1599{
1600 OCSP_RESPONSE *resp;
1601 OCSP_BASICRESP *bs = NULL;
1602 OCSP_SINGLERESP *sr;
1603 OCSP_CERTID *id;
1604 unsigned char *p = NULL;
1605
1606 if (!key_length)
1607 return -1;
1608
1609 *key_length = 0;
1610
1611 if (!ckch_store->ckch->ocsp_response)
1612 return 0;
1613
1614 p = (unsigned char *) ckch_store->ckch->ocsp_response->area;
1615
1616 resp = d2i_OCSP_RESPONSE(NULL, (const unsigned char **)&p,
1617 ckch_store->ckch->ocsp_response->data);
1618 if (!resp) {
1619 goto end;
1620 }
1621
1622 bs = OCSP_response_get1_basic(resp);
1623 if (!bs) {
1624 goto end;
1625 }
1626
1627 sr = OCSP_resp_get0(bs, 0);
1628 if (!sr) {
1629 goto end;
1630 }
1631
1632 id = (OCSP_CERTID*)OCSP_SINGLERESP_get0_id(sr);
1633
1634 p = certid;
1635 *key_length = i2d_OCSP_CERTID(id, &p);
1636
1637end:
1638 return *key_length > 0;
1639}
1640#endif
1641
1642/*
1643 * Dump the OCSP certificate key (if it exists) of certificate <ckch> into
1644 * buffer <out>.
1645 * Returns 0 in case of success.
1646 */
1647static int ckch_store_show_ocsp_certid(struct ckch_store *ckch_store, struct buffer *out)
1648{
Remi Tricot-Le Breton3faf0cb2021-06-10 18:10:32 +02001649#if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) && !defined OPENSSL_IS_BORINGSSL)
Remi Tricot-Le Bretonda968f62021-06-10 13:51:14 +02001650 unsigned char key[OCSP_MAX_CERTID_ASN1_LENGTH] = {};
1651 unsigned int key_length = 0;
1652 int i;
1653
1654 if (ckch_store_build_certid(ckch_store, (unsigned char*)key, &key_length) >= 0) {
1655 /* Dump the CERTID info */
1656 chunk_appendf(out, "OCSP Response Key: ");
1657 for (i = 0; i < key_length; ++i) {
1658 chunk_appendf(out, "%02x", key[i]);
1659 }
1660 chunk_appendf(out, "\n");
1661 }
1662#endif
1663
1664 return 0;
1665}
1666
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001667
Willy Tarreau4fd9b4d2022-05-04 16:11:50 +02001668/* IO handler of the details "show ssl cert <filename>".
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001669 * It uses a struct show_cert_ctx and ckchs_transaction in read-only.
Willy Tarreau4fd9b4d2022-05-04 16:11:50 +02001670 */
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001671static int cli_io_handler_show_cert_detail(struct appctx *appctx)
1672{
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001673 struct show_cert_ctx *ctx = appctx->svcctx;
Christopher Faulet908628c2022-03-25 16:43:49 +01001674 struct conn_stream *cs = appctx->owner;
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001675 struct ckch_store *ckchs = ctx->cur_ckchs;
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001676 struct buffer *out = alloc_trash_chunk();
1677 int retval = 0;
1678
1679 if (!out)
1680 goto end_no_putchk;
1681
1682 chunk_appendf(out, "Filename: ");
1683 if (ckchs == ckchs_transaction.new_ckchs)
1684 chunk_appendf(out, "*");
1685 chunk_appendf(out, "%s\n", ckchs->path);
1686
1687 chunk_appendf(out, "Status: ");
1688 if (ckchs->ckch->cert == NULL)
1689 chunk_appendf(out, "Empty\n");
1690 else if (LIST_ISEMPTY(&ckchs->ckch_inst))
1691 chunk_appendf(out, "Unused\n");
1692 else
1693 chunk_appendf(out, "Used\n");
1694
1695 retval = show_cert_detail(ckchs->ckch->cert, ckchs->ckch->chain, out);
1696 if (retval < 0)
1697 goto end_no_putchk;
1698 else if (retval)
1699 goto end;
1700
Remi Tricot-Le Bretonda968f62021-06-10 13:51:14 +02001701 ckch_store_show_ocsp_certid(ckchs, out);
1702
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001703end:
Christopher Faulet908628c2022-03-25 16:43:49 +01001704 if (ci_putchk(cs_ic(cs), out) == -1) {
Christopher Fauleta0bdec32022-04-04 07:51:21 +02001705 cs_rx_room_blk(cs);
William Lallemandda8584c2020-05-14 10:14:37 +02001706 goto yield;
1707 }
1708
1709end_no_putchk:
William Lallemandda8584c2020-05-14 10:14:37 +02001710 free_trash_chunk(out);
1711 return 1;
1712yield:
William Lallemandda8584c2020-05-14 10:14:37 +02001713 free_trash_chunk(out);
1714 return 0; /* should come back */
1715}
1716
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001717
Willy Tarreau4fd9b4d2022-05-04 16:11:50 +02001718/* IO handler of the details "show ssl cert <filename.ocsp>".
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001719 * It uses a show_cert_ctx.
Willy Tarreau4fd9b4d2022-05-04 16:11:50 +02001720 */
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001721static int cli_io_handler_show_cert_ocsp_detail(struct appctx *appctx)
1722{
Remi Tricot-Le Breton3faf0cb2021-06-10 18:10:32 +02001723#if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) && !defined OPENSSL_IS_BORINGSSL)
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001724 struct show_cert_ctx *ctx = appctx->svcctx;
Christopher Faulet908628c2022-03-25 16:43:49 +01001725 struct conn_stream *cs = appctx->owner;
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001726 struct ckch_store *ckchs = ctx->cur_ckchs;
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001727 struct buffer *out = alloc_trash_chunk();
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001728 int from_transaction = ctx->transaction;
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001729
1730 if (!out)
1731 goto end_no_putchk;
1732
1733 /* If we try to display an ongoing transaction's OCSP response, we
1734 * need to dump the ckch's ocsp_response buffer directly.
1735 * Otherwise, we must rebuild the certificate's certid in order to
1736 * look for the current OCSP response in the tree. */
1737 if (from_transaction && ckchs->ckch->ocsp_response) {
Remi Tricot-Le Bretona9a591a2022-02-16 14:42:22 +01001738 if (ssl_ocsp_response_print(ckchs->ckch->ocsp_response, out))
1739 goto end_no_putchk;
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001740 }
1741 else {
1742 unsigned char key[OCSP_MAX_CERTID_ASN1_LENGTH] = {};
1743 unsigned int key_length = 0;
1744
1745 if (ckch_store_build_certid(ckchs, (unsigned char*)key, &key_length) < 0)
1746 goto end_no_putchk;
1747
Remi Tricot-Le Bretona9a591a2022-02-16 14:42:22 +01001748 if (ssl_get_ocspresponse_detail(key, out))
1749 goto end_no_putchk;
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001750 }
1751
Christopher Faulet908628c2022-03-25 16:43:49 +01001752 if (ci_putchk(cs_ic(cs), out) == -1) {
Christopher Fauleta0bdec32022-04-04 07:51:21 +02001753 cs_rx_room_blk(cs);
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001754 goto yield;
1755 }
1756
1757end_no_putchk:
1758 free_trash_chunk(out);
1759 return 1;
1760yield:
1761 free_trash_chunk(out);
1762 return 0; /* should come back */
1763#else
1764 return cli_err(appctx, "HAProxy was compiled against a version of OpenSSL that doesn't support OCSP stapling.\n");
1765#endif
1766}
1767
William Lallemandda8584c2020-05-14 10:14:37 +02001768/* parsing function for 'show ssl cert [certfile]' */
1769static int cli_parse_show_cert(char **args, char *payload, struct appctx *appctx, void *private)
1770{
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001771 struct show_cert_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
William Lallemandda8584c2020-05-14 10:14:37 +02001772 struct ckch_store *ckchs;
1773
1774 if (!cli_has_level(appctx, ACCESS_LVL_OPER))
1775 return cli_err(appctx, "Can't allocate memory!\n");
1776
1777 /* The operations on the CKCH architecture are locked so we can
1778 * manipulate ckch_store and ckch_inst */
1779 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
1780 return cli_err(appctx, "Can't show!\nOperations on certificates are currently locked!\n");
1781
1782 /* check if there is a certificate to lookup */
1783 if (*args[3]) {
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001784 int show_ocsp_detail = 0;
1785 int from_transaction = 0;
1786 char *end;
1787
1788 /* We manage the special case "certname.ocsp" through which we
1789 * can show the details of an OCSP response. */
1790 end = strrchr(args[3], '.');
1791 if (end && strcmp(end+1, "ocsp") == 0) {
1792 *end = '\0';
1793 show_ocsp_detail = 1;
1794 }
1795
William Lallemandda8584c2020-05-14 10:14:37 +02001796 if (*args[3] == '*') {
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001797 from_transaction = 1;
William Lallemandda8584c2020-05-14 10:14:37 +02001798 if (!ckchs_transaction.new_ckchs)
1799 goto error;
1800
1801 ckchs = ckchs_transaction.new_ckchs;
1802
Tim Duesterhuse5ff1412021-01-02 22:31:53 +01001803 if (strcmp(args[3] + 1, ckchs->path) != 0)
William Lallemandda8584c2020-05-14 10:14:37 +02001804 goto error;
1805
1806 } else {
1807 if ((ckchs = ckchs_lookup(args[3])) == NULL)
1808 goto error;
1809
1810 }
1811
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001812 ctx->cur_ckchs = ckchs;
William Lallemandda8584c2020-05-14 10:14:37 +02001813 /* use the IO handler that shows details */
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001814 if (show_ocsp_detail) {
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001815 ctx->transaction = from_transaction;
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001816 appctx->io_handler = cli_io_handler_show_cert_ocsp_detail;
1817 }
1818 else
1819 appctx->io_handler = cli_io_handler_show_cert_detail;
William Lallemandda8584c2020-05-14 10:14:37 +02001820 }
1821
1822 return 0;
1823
1824error:
1825 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1826 return cli_err(appctx, "Can't display the certificate: Not found or the certificate is a bundle!\n");
1827}
1828
1829/* release function of the `set ssl cert' command, free things and unlock the spinlock */
1830static void cli_release_commit_cert(struct appctx *appctx)
1831{
Willy Tarreaua645b6a2022-05-04 19:58:00 +02001832 struct commit_cert_ctx *ctx = appctx->svcctx;
William Lallemandda8584c2020-05-14 10:14:37 +02001833 struct ckch_store *new_ckchs;
1834
1835 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1836
1837 if (appctx->st2 != SETCERT_ST_FIN) {
1838 /* free every new sni_ctx and the new store, which are not in the trees so no spinlock there */
Willy Tarreaua645b6a2022-05-04 19:58:00 +02001839 new_ckchs = ctx->new_ckchs;
William Lallemandda8584c2020-05-14 10:14:37 +02001840
1841 /* if the allocation failed, we need to free everything from the temporary list */
1842 ckch_store_free(new_ckchs);
1843 }
1844}
1845
Remi Tricot-Le Bretonbfadc022021-02-24 12:20:48 +01001846
1847/*
1848 * Rebuild a new instance 'new_inst' based on an old instance 'ckchi' and a
1849 * specific ckch_store.
1850 * Returns 0 in case of success, 1 otherwise.
1851 */
William Lallemande60c7d62022-03-30 11:26:15 +02001852int ckch_inst_rebuild(struct ckch_store *ckch_store, struct ckch_inst *ckchi,
1853 struct ckch_inst **new_inst, char **err)
Remi Tricot-Le Bretonbfadc022021-02-24 12:20:48 +01001854{
1855 int retval = 0;
1856 int errcode = 0;
1857 struct sni_ctx *sc0, *sc0s;
1858 char **sni_filter = NULL;
1859 int fcount = 0;
1860
1861 if (ckchi->crtlist_entry) {
1862 sni_filter = ckchi->crtlist_entry->filters;
1863 fcount = ckchi->crtlist_entry->fcount;
1864 }
1865
1866 if (ckchi->is_server_instance)
1867 errcode |= ckch_inst_new_load_srv_store(ckch_store->path, ckch_store, new_inst, err);
1868 else
1869 errcode |= ckch_inst_new_load_store(ckch_store->path, ckch_store, ckchi->bind_conf, ckchi->ssl_conf, sni_filter, fcount, new_inst, err);
1870
1871 if (errcode & ERR_CODE)
1872 return 1;
1873
1874 /* if the previous ckchi was used as the default */
1875 if (ckchi->is_default)
1876 (*new_inst)->is_default = 1;
1877
1878 (*new_inst)->is_server_instance = ckchi->is_server_instance;
1879 (*new_inst)->server = ckchi->server;
1880 /* Create a new SSL_CTX and link it to the new instance. */
1881 if ((*new_inst)->is_server_instance) {
1882 retval = ssl_sock_prep_srv_ctx_and_inst(ckchi->server, (*new_inst)->ctx, (*new_inst));
1883 if (retval)
1884 return 1;
1885 }
1886
1887 /* create the link to the crtlist_entry */
1888 (*new_inst)->crtlist_entry = ckchi->crtlist_entry;
1889
1890 /* we need to initialize the SSL_CTX generated */
1891 /* this iterate on the newly generated SNIs in the new instance to prepare their SSL_CTX */
1892 list_for_each_entry_safe(sc0, sc0s, &(*new_inst)->sni_ctx, by_ckch_inst) {
1893 if (!sc0->order) { /* we initialized only the first SSL_CTX because it's the same in the other sni_ctx's */
1894 errcode |= ssl_sock_prep_ctx_and_inst(ckchi->bind_conf, ckchi->ssl_conf, sc0->ctx, *new_inst, err);
1895 if (errcode & ERR_CODE)
1896 return 1;
1897 }
1898 }
1899
1900 return 0;
1901}
1902
1903/*
1904 * Load all the new SNIs of a newly built ckch instance in the trees, or replace
1905 * a server's main ckch instance.
1906 */
1907static void __ssl_sock_load_new_ckch_instance(struct ckch_inst *ckchi)
1908{
1909 /* The bind_conf will be null on server ckch_instances. */
1910 if (ckchi->is_server_instance) {
1911 int i;
1912 /* a lock is needed here since we have to free the SSL cache */
1913 HA_RWLOCK_WRLOCK(SSL_SERVER_LOCK, &ckchi->server->ssl_ctx.lock);
1914 /* free the server current SSL_CTX */
1915 SSL_CTX_free(ckchi->server->ssl_ctx.ctx);
1916 /* Actual ssl context update */
1917 SSL_CTX_up_ref(ckchi->ctx);
1918 ckchi->server->ssl_ctx.ctx = ckchi->ctx;
1919 ckchi->server->ssl_ctx.inst = ckchi;
1920
1921 /* flush the session cache of the server */
1922 for (i = 0; i < global.nbthread; i++) {
William Lallemandce990332021-11-23 15:15:09 +01001923 ha_free(&ckchi->server->ssl_ctx.reused_sess[i].sni);
Remi Tricot-Le Bretonbfadc022021-02-24 12:20:48 +01001924 ha_free(&ckchi->server->ssl_ctx.reused_sess[i].ptr);
1925 }
1926 HA_RWLOCK_WRUNLOCK(SSL_SERVER_LOCK, &ckchi->server->ssl_ctx.lock);
1927
1928 } else {
1929 HA_RWLOCK_WRLOCK(SNI_LOCK, &ckchi->bind_conf->sni_lock);
1930 ssl_sock_load_cert_sni(ckchi, ckchi->bind_conf);
1931 HA_RWLOCK_WRUNLOCK(SNI_LOCK, &ckchi->bind_conf->sni_lock);
1932 }
1933}
1934
1935/*
1936 * Delete a ckch instance that was replaced after a CLI command.
1937 */
1938static void __ckch_inst_free_locked(struct ckch_inst *ckchi)
1939{
1940 if (ckchi->is_server_instance) {
1941 /* no lock for servers */
1942 ckch_inst_free(ckchi);
1943 } else {
1944 struct bind_conf __maybe_unused *bind_conf = ckchi->bind_conf;
1945
1946 HA_RWLOCK_WRLOCK(SNI_LOCK, &bind_conf->sni_lock);
1947 ckch_inst_free(ckchi);
1948 HA_RWLOCK_WRUNLOCK(SNI_LOCK, &bind_conf->sni_lock);
1949 }
1950}
1951
William Lallemand3b5a3a62022-03-29 14:29:31 +02001952/* Replace a ckch_store in the ckch tree and insert the whole dependencies,
1953* then free the previous dependencies and store.
1954* Used in the case of a certificate update.
1955*
1956* Every dependencies must allocated before using this function.
1957*
1958* This function can't fail as it only update pointers, and does not alloc anything.
1959*
1960* /!\ This function must be used under the ckch lock. /!\
1961*
1962* - Insert every dependencies (SNI, crtlist_entry, ckch_inst, etc)
1963* - Delete the old ckch_store from the tree
1964* - Insert the new ckch_store
1965* - Free the old dependencies and the old ckch_store
1966*/
1967void ckch_store_replace(struct ckch_store *old_ckchs, struct ckch_store *new_ckchs)
1968{
1969 struct crtlist_entry *entry;
1970 struct ckch_inst *ckchi, *ckchis;
1971
1972 LIST_SPLICE(&new_ckchs->crtlist_entry, &old_ckchs->crtlist_entry);
1973 list_for_each_entry(entry, &new_ckchs->crtlist_entry, by_ckch_store) {
1974 ebpt_delete(&entry->node);
1975 /* change the ptr and reinsert the node */
1976 entry->node.key = new_ckchs;
1977 ebpt_insert(&entry->crtlist->entries, &entry->node);
1978 }
1979 /* insert the new ckch_insts in the crtlist_entry */
1980 list_for_each_entry(ckchi, &new_ckchs->ckch_inst, by_ckchs) {
1981 if (ckchi->crtlist_entry)
1982 LIST_INSERT(&ckchi->crtlist_entry->ckch_inst, &ckchi->by_crtlist_entry);
1983 }
1984 /* First, we insert every new SNIs in the trees, also replace the default_ctx */
1985 list_for_each_entry_safe(ckchi, ckchis, &new_ckchs->ckch_inst, by_ckchs) {
1986 __ssl_sock_load_new_ckch_instance(ckchi);
1987 }
1988 /* delete the old sni_ctx, the old ckch_insts and the ckch_store */
1989 list_for_each_entry_safe(ckchi, ckchis, &old_ckchs->ckch_inst, by_ckchs) {
1990 __ckch_inst_free_locked(ckchi);
1991 }
1992
1993 ckch_store_free(old_ckchs);
1994 ebst_insert(&ckchs_tree, &new_ckchs->node);
1995}
1996
Remi Tricot-Le Bretonbfadc022021-02-24 12:20:48 +01001997
William Lallemandda8584c2020-05-14 10:14:37 +02001998/*
1999 * This function tries to create the new ckch_inst and their SNIs
William Lallemand30fcca12022-03-30 12:03:12 +02002000 *
2001 * /!\ don't forget to update __hlua_ckch_commit() if you changes things there. /!\
William Lallemandda8584c2020-05-14 10:14:37 +02002002 */
2003static int cli_io_handler_commit_cert(struct appctx *appctx)
2004{
Willy Tarreaua645b6a2022-05-04 19:58:00 +02002005 struct commit_cert_ctx *ctx = appctx->svcctx;
Christopher Faulet908628c2022-03-25 16:43:49 +01002006 struct conn_stream *cs = appctx->owner;
William Lallemandda8584c2020-05-14 10:14:37 +02002007 int y = 0;
2008 char *err = NULL;
William Lallemandda8584c2020-05-14 10:14:37 +02002009 struct ckch_store *old_ckchs, *new_ckchs = NULL;
William Lallemand3b5a3a62022-03-29 14:29:31 +02002010 struct ckch_inst *ckchi;
William Lallemandda8584c2020-05-14 10:14:37 +02002011 struct buffer *trash = alloc_trash_chunk();
William Lallemandda8584c2020-05-14 10:14:37 +02002012
2013 if (trash == NULL)
2014 goto error;
2015
Christopher Faulet908628c2022-03-25 16:43:49 +01002016 if (unlikely(cs_ic(cs)->flags & (CF_WRITE_ERROR|CF_SHUTW)))
William Lallemandda8584c2020-05-14 10:14:37 +02002017 goto error;
2018
2019 while (1) {
2020 switch (appctx->st2) {
2021 case SETCERT_ST_INIT:
2022 /* This state just print the update message */
2023 chunk_printf(trash, "Committing %s", ckchs_transaction.path);
Christopher Faulet908628c2022-03-25 16:43:49 +01002024 if (ci_putchk(cs_ic(cs), trash) == -1) {
Christopher Fauleta0bdec32022-04-04 07:51:21 +02002025 cs_rx_room_blk(cs);
William Lallemandda8584c2020-05-14 10:14:37 +02002026 goto yield;
2027 }
2028 appctx->st2 = SETCERT_ST_GEN;
2029 /* fallthrough */
2030 case SETCERT_ST_GEN:
2031 /*
2032 * This state generates the ckch instances with their
2033 * sni_ctxs and SSL_CTX.
2034 *
2035 * Since the SSL_CTX generation can be CPU consumer, we
2036 * yield every 10 instances.
2037 */
2038
Willy Tarreaua645b6a2022-05-04 19:58:00 +02002039 old_ckchs = ctx->old_ckchs;
2040 new_ckchs = ctx->new_ckchs;
William Lallemandda8584c2020-05-14 10:14:37 +02002041
2042 if (!new_ckchs)
2043 continue;
2044
2045 /* get the next ckchi to regenerate */
Willy Tarreaua645b6a2022-05-04 19:58:00 +02002046 ckchi = ctx->next_ckchi;
William Lallemandda8584c2020-05-14 10:14:37 +02002047 /* we didn't start yet, set it to the first elem */
2048 if (ckchi == NULL)
2049 ckchi = LIST_ELEM(old_ckchs->ckch_inst.n, typeof(ckchi), by_ckchs);
2050
2051 /* walk through the old ckch_inst and creates new ckch_inst using the updated ckchs */
2052 list_for_each_entry_from(ckchi, &old_ckchs->ckch_inst, by_ckchs) {
2053 struct ckch_inst *new_inst;
William Lallemandda8584c2020-05-14 10:14:37 +02002054
2055 /* it takes a lot of CPU to creates SSL_CTXs, so we yield every 10 CKCH instances */
2056 if (y >= 10) {
2057 /* save the next ckchi to compute */
Willy Tarreaua645b6a2022-05-04 19:58:00 +02002058 ctx->next_ckchi = ckchi;
William Lallemandda8584c2020-05-14 10:14:37 +02002059 goto yield;
2060 }
2061
Remi Tricot-Le Bretonbfadc022021-02-24 12:20:48 +01002062 if (ckch_inst_rebuild(new_ckchs, ckchi, &new_inst, &err))
William Lallemandda8584c2020-05-14 10:14:37 +02002063 goto error;
2064
William Lallemandda8584c2020-05-14 10:14:37 +02002065 /* display one dot per new instance */
2066 chunk_appendf(trash, ".");
2067 /* link the new ckch_inst to the duplicate */
Willy Tarreau2b718102021-04-21 07:32:39 +02002068 LIST_APPEND(&new_ckchs->ckch_inst, &new_inst->by_ckchs);
William Lallemandda8584c2020-05-14 10:14:37 +02002069 y++;
2070 }
2071 appctx->st2 = SETCERT_ST_INSERT;
2072 /* fallthrough */
2073 case SETCERT_ST_INSERT:
2074 /* The generation is finished, we can insert everything */
2075
Willy Tarreaua645b6a2022-05-04 19:58:00 +02002076 old_ckchs = ctx->old_ckchs;
2077 new_ckchs = ctx->new_ckchs;
William Lallemandda8584c2020-05-14 10:14:37 +02002078
2079 if (!new_ckchs)
2080 continue;
2081
William Lallemand3b5a3a62022-03-29 14:29:31 +02002082 /* insert everything and remove the previous objects */
2083 ckch_store_replace(old_ckchs, new_ckchs);
William Lallemandda8584c2020-05-14 10:14:37 +02002084
William Lallemandda8584c2020-05-14 10:14:37 +02002085 appctx->st2 = SETCERT_ST_FIN;
2086 /* fallthrough */
2087 case SETCERT_ST_FIN:
2088 /* we achieved the transaction, we can set everything to NULL */
Willy Tarreau61cfdf42021-02-20 10:46:51 +01002089 ha_free(&ckchs_transaction.path);
William Lallemandda8584c2020-05-14 10:14:37 +02002090 ckchs_transaction.new_ckchs = NULL;
2091 ckchs_transaction.old_ckchs = NULL;
2092 goto end;
2093 }
2094 }
2095end:
2096
2097 chunk_appendf(trash, "\n");
William Lallemandda8584c2020-05-14 10:14:37 +02002098 chunk_appendf(trash, "Success!\n");
Christopher Faulet908628c2022-03-25 16:43:49 +01002099 if (ci_putchk(cs_ic(cs), trash) == -1)
Christopher Fauleta0bdec32022-04-04 07:51:21 +02002100 cs_rx_room_blk(cs);
William Lallemandda8584c2020-05-14 10:14:37 +02002101 free_trash_chunk(trash);
2102 /* success: call the release function and don't come back */
2103 return 1;
2104yield:
2105 /* store the state */
Christopher Faulet908628c2022-03-25 16:43:49 +01002106 if (ci_putchk(cs_ic(cs), trash) == -1)
Christopher Fauleta0bdec32022-04-04 07:51:21 +02002107 cs_rx_room_blk(cs);
William Lallemandda8584c2020-05-14 10:14:37 +02002108 free_trash_chunk(trash);
Christopher Fauleta0bdec32022-04-04 07:51:21 +02002109 cs_rx_endp_more(cs); /* let's come back later */
William Lallemandda8584c2020-05-14 10:14:37 +02002110 return 0; /* should come back */
2111
2112error:
2113 /* spin unlock and free are done in the release function */
2114 if (trash) {
2115 chunk_appendf(trash, "\n%sFailed!\n", err);
Christopher Faulet908628c2022-03-25 16:43:49 +01002116 if (ci_putchk(cs_ic(cs), trash) == -1)
Christopher Fauleta0bdec32022-04-04 07:51:21 +02002117 cs_rx_room_blk(cs);
William Lallemandda8584c2020-05-14 10:14:37 +02002118 free_trash_chunk(trash);
2119 }
2120 /* error: call the release function and don't come back */
2121 return 1;
2122}
2123
2124/*
2125 * Parsing function of 'commit ssl cert'
2126 */
2127static int cli_parse_commit_cert(char **args, char *payload, struct appctx *appctx, void *private)
2128{
Willy Tarreaua645b6a2022-05-04 19:58:00 +02002129 struct commit_cert_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
William Lallemandda8584c2020-05-14 10:14:37 +02002130 char *err = NULL;
2131
2132 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2133 return 1;
2134
2135 if (!*args[3])
2136 return cli_err(appctx, "'commit ssl cert expects a filename\n");
2137
2138 /* The operations on the CKCH architecture are locked so we can
2139 * manipulate ckch_store and ckch_inst */
2140 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2141 return cli_err(appctx, "Can't commit the certificate!\nOperations on certificates are currently locked!\n");
2142
2143 if (!ckchs_transaction.path) {
2144 memprintf(&err, "No ongoing transaction! !\n");
2145 goto error;
2146 }
2147
2148 if (strcmp(ckchs_transaction.path, args[3]) != 0) {
2149 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", ckchs_transaction.path, args[3]);
2150 goto error;
2151 }
2152
William Lallemand5685ccf2020-09-16 16:12:25 +02002153 /* if a certificate is here, a private key must be here too */
2154 if (ckchs_transaction.new_ckchs->ckch->cert && !ckchs_transaction.new_ckchs->ckch->key) {
2155 memprintf(&err, "The transaction must contain at least a certificate and a private key!\n");
2156 goto error;
2157 }
William Lallemanda9419522020-06-24 16:26:41 +02002158
William Lallemand5685ccf2020-09-16 16:12:25 +02002159 if (!X509_check_private_key(ckchs_transaction.new_ckchs->ckch->cert, ckchs_transaction.new_ckchs->ckch->key)) {
2160 memprintf(&err, "inconsistencies between private key and certificate loaded '%s'.\n", ckchs_transaction.path);
2161 goto error;
William Lallemandda8584c2020-05-14 10:14:37 +02002162 }
2163
2164 /* init the appctx structure */
2165 appctx->st2 = SETCERT_ST_INIT;
Willy Tarreaua645b6a2022-05-04 19:58:00 +02002166 ctx->next_ckchi = NULL;
2167 ctx->new_ckchs = ckchs_transaction.new_ckchs;
2168 ctx->old_ckchs = ckchs_transaction.old_ckchs;
William Lallemandda8584c2020-05-14 10:14:37 +02002169
2170 /* we don't unlock there, it will be unlock after the IO handler, in the release handler */
2171 return 0;
2172
2173error:
2174
2175 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2176 err = memprintf(&err, "%sCan't commit %s!\n", err ? err : "", args[3]);
2177
2178 return cli_dynerr(appctx, err);
2179}
2180
2181
2182
2183
2184/*
2185 * Parsing function of `set ssl cert`, it updates or creates a temporary ckch.
2186 */
2187static int cli_parse_set_cert(char **args, char *payload, struct appctx *appctx, void *private)
2188{
2189 struct ckch_store *new_ckchs = NULL;
2190 struct ckch_store *old_ckchs = NULL;
2191 char *err = NULL;
2192 int i;
William Lallemandda8584c2020-05-14 10:14:37 +02002193 int errcode = 0;
2194 char *end;
William Lallemandff8bf982022-03-29 10:44:23 +02002195 struct cert_exts *cert_ext = &cert_exts[0]; /* default one, PEM */
William Lallemandda8584c2020-05-14 10:14:37 +02002196 struct cert_key_and_chain *ckch;
2197 struct buffer *buf;
2198
2199 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2200 return 1;
2201
William Lallemandda8584c2020-05-14 10:14:37 +02002202 if (!*args[3] || !payload)
2203 return cli_err(appctx, "'set ssl cert expects a filename and a certificate as a payload\n");
2204
2205 /* The operations on the CKCH architecture are locked so we can
2206 * manipulate ckch_store and ckch_inst */
2207 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2208 return cli_err(appctx, "Can't update the certificate!\nOperations on certificates are currently locked!\n");
2209
William Lallemand5ba80d62021-05-04 16:17:27 +02002210 if ((buf = alloc_trash_chunk()) == NULL) {
2211 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2212 errcode |= ERR_ALERT | ERR_FATAL;
2213 goto end;
2214 }
William Lallemande5ff4ad2020-06-08 09:40:37 +02002215
William Lallemandda8584c2020-05-14 10:14:37 +02002216 if (!chunk_strcpy(buf, args[3])) {
2217 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2218 errcode |= ERR_ALERT | ERR_FATAL;
2219 goto end;
2220 }
2221
2222 /* check which type of file we want to update */
William Lallemandff8bf982022-03-29 10:44:23 +02002223 for (i = 0; cert_exts[i].ext != NULL; i++) {
William Lallemandda8584c2020-05-14 10:14:37 +02002224 end = strrchr(buf->area, '.');
Tim Duesterhuse5ff1412021-01-02 22:31:53 +01002225 if (end && *cert_exts[i].ext && (strcmp(end + 1, cert_exts[i].ext) == 0)) {
William Lallemandda8584c2020-05-14 10:14:37 +02002226 *end = '\0';
William Lallemand089c1382020-10-23 17:35:12 +02002227 buf->data = strlen(buf->area);
William Lallemandff8bf982022-03-29 10:44:23 +02002228 cert_ext = &cert_exts[i];
William Lallemandda8584c2020-05-14 10:14:37 +02002229 break;
2230 }
2231 }
2232
2233 appctx->ctx.ssl.old_ckchs = NULL;
2234 appctx->ctx.ssl.new_ckchs = NULL;
2235
2236 /* if there is an ongoing transaction */
2237 if (ckchs_transaction.path) {
William Lallemandda8584c2020-05-14 10:14:37 +02002238 /* if there is an ongoing transaction, check if this is the same file */
2239 if (strcmp(ckchs_transaction.path, buf->area) != 0) {
William Lallemand089c1382020-10-23 17:35:12 +02002240 /* we didn't find the transaction, must try more cases below */
2241
2242 /* if the del-ext option is activated we should try to take a look at a ".crt" too. */
William Lallemandff8bf982022-03-29 10:44:23 +02002243 if (cert_ext->type != CERT_TYPE_PEM && global_ssl.extra_files_noext) {
William Lallemand089c1382020-10-23 17:35:12 +02002244 if (!chunk_strcat(buf, ".crt")) {
2245 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2246 errcode |= ERR_ALERT | ERR_FATAL;
2247 goto end;
2248 }
2249
2250 if (strcmp(ckchs_transaction.path, buf->area) != 0) {
2251 /* remove .crt of the error message */
2252 *(b_orig(buf) + b_data(buf) + strlen(".crt")) = '\0';
2253 b_sub(buf, strlen(".crt"));
2254
2255 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", ckchs_transaction.path, buf->area);
2256 errcode |= ERR_ALERT | ERR_FATAL;
2257 goto end;
2258 }
2259 }
William Lallemandda8584c2020-05-14 10:14:37 +02002260 }
2261
2262 appctx->ctx.ssl.old_ckchs = ckchs_transaction.new_ckchs;
2263
2264 } else {
William Lallemandda8584c2020-05-14 10:14:37 +02002265
William Lallemand95fefa12020-09-09 12:01:33 +02002266 /* lookup for the certificate in the tree */
2267 appctx->ctx.ssl.old_ckchs = ckchs_lookup(buf->area);
William Lallemand089c1382020-10-23 17:35:12 +02002268
2269 if (!appctx->ctx.ssl.old_ckchs) {
2270 /* if the del-ext option is activated we should try to take a look at a ".crt" too. */
William Lallemandff8bf982022-03-29 10:44:23 +02002271 if (cert_ext->type != CERT_TYPE_PEM && global_ssl.extra_files_noext) {
William Lallemand089c1382020-10-23 17:35:12 +02002272 if (!chunk_strcat(buf, ".crt")) {
2273 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2274 errcode |= ERR_ALERT | ERR_FATAL;
2275 goto end;
2276 }
2277 appctx->ctx.ssl.old_ckchs = ckchs_lookup(buf->area);
2278 }
2279 }
William Lallemandda8584c2020-05-14 10:14:37 +02002280 }
2281
2282 if (!appctx->ctx.ssl.old_ckchs) {
2283 memprintf(&err, "%sCan't replace a certificate which is not referenced by the configuration!\n",
2284 err ? err : "");
2285 errcode |= ERR_ALERT | ERR_FATAL;
2286 goto end;
2287 }
2288
2289 if (!appctx->ctx.ssl.path) {
2290 /* this is a new transaction, set the path of the transaction */
2291 appctx->ctx.ssl.path = strdup(appctx->ctx.ssl.old_ckchs->path);
2292 if (!appctx->ctx.ssl.path) {
2293 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2294 errcode |= ERR_ALERT | ERR_FATAL;
2295 goto end;
2296 }
2297 }
2298
2299 old_ckchs = appctx->ctx.ssl.old_ckchs;
2300
2301 /* duplicate the ckch store */
2302 new_ckchs = ckchs_dup(old_ckchs);
2303 if (!new_ckchs) {
2304 memprintf(&err, "%sCannot allocate memory!\n",
2305 err ? err : "");
2306 errcode |= ERR_ALERT | ERR_FATAL;
2307 goto end;
2308 }
2309
William Lallemand95fefa12020-09-09 12:01:33 +02002310 ckch = new_ckchs->ckch;
William Lallemandda8584c2020-05-14 10:14:37 +02002311
2312 /* appply the change on the duplicate */
William Lallemandff8bf982022-03-29 10:44:23 +02002313 if (cert_ext->load(buf->area, payload, ckch, &err) != 0) {
William Lallemandda8584c2020-05-14 10:14:37 +02002314 memprintf(&err, "%sCan't load the payload\n", err ? err : "");
2315 errcode |= ERR_ALERT | ERR_FATAL;
2316 goto end;
2317 }
2318
2319 appctx->ctx.ssl.new_ckchs = new_ckchs;
2320
2321 /* we succeed, we can save the ckchs in the transaction */
2322
2323 /* if there wasn't a transaction, update the old ckchs */
2324 if (!ckchs_transaction.old_ckchs) {
2325 ckchs_transaction.old_ckchs = appctx->ctx.ssl.old_ckchs;
2326 ckchs_transaction.path = appctx->ctx.ssl.path;
2327 err = memprintf(&err, "Transaction created for certificate %s!\n", ckchs_transaction.path);
2328 } else {
2329 err = memprintf(&err, "Transaction updated for certificate %s!\n", ckchs_transaction.path);
2330
2331 }
2332
2333 /* free the previous ckchs if there was a transaction */
2334 ckch_store_free(ckchs_transaction.new_ckchs);
2335
2336 ckchs_transaction.new_ckchs = appctx->ctx.ssl.new_ckchs;
2337
2338
2339 /* creates the SNI ctxs later in the IO handler */
2340
2341end:
2342 free_trash_chunk(buf);
2343
2344 if (errcode & ERR_CODE) {
2345
2346 ckch_store_free(appctx->ctx.ssl.new_ckchs);
2347 appctx->ctx.ssl.new_ckchs = NULL;
2348
2349 appctx->ctx.ssl.old_ckchs = NULL;
2350
Willy Tarreau61cfdf42021-02-20 10:46:51 +01002351 ha_free(&appctx->ctx.ssl.path);
William Lallemandda8584c2020-05-14 10:14:37 +02002352
2353 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2354 return cli_dynerr(appctx, memprintf(&err, "%sCan't update %s!\n", err ? err : "", args[3]));
2355 } else {
2356
2357 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2358 return cli_dynmsg(appctx, LOG_NOTICE, err);
2359 }
2360 /* TODO: handle the ERR_WARN which are not handled because of the io_handler */
2361}
2362
2363/* parsing function of 'abort ssl cert' */
2364static int cli_parse_abort_cert(char **args, char *payload, struct appctx *appctx, void *private)
2365{
2366 char *err = NULL;
2367
2368 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2369 return 1;
2370
2371 if (!*args[3])
2372 return cli_err(appctx, "'abort ssl cert' expects a filename\n");
2373
2374 /* The operations on the CKCH architecture are locked so we can
2375 * manipulate ckch_store and ckch_inst */
2376 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2377 return cli_err(appctx, "Can't abort!\nOperations on certificates are currently locked!\n");
2378
2379 if (!ckchs_transaction.path) {
2380 memprintf(&err, "No ongoing transaction!\n");
2381 goto error;
2382 }
2383
2384 if (strcmp(ckchs_transaction.path, args[3]) != 0) {
2385 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to abort a transaction for '%s'\n", ckchs_transaction.path, args[3]);
2386 goto error;
2387 }
2388
2389 /* Only free the ckchs there, because the SNI and instances were not generated yet */
2390 ckch_store_free(ckchs_transaction.new_ckchs);
2391 ckchs_transaction.new_ckchs = NULL;
William Lallemandda8584c2020-05-14 10:14:37 +02002392 ckchs_transaction.old_ckchs = NULL;
Willy Tarreau61cfdf42021-02-20 10:46:51 +01002393 ha_free(&ckchs_transaction.path);
William Lallemandda8584c2020-05-14 10:14:37 +02002394
2395 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2396
2397 err = memprintf(&err, "Transaction aborted for certificate '%s'!\n", args[3]);
2398 return cli_dynmsg(appctx, LOG_NOTICE, err);
2399
2400error:
2401 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2402
2403 return cli_dynerr(appctx, err);
2404}
2405
2406/* parsing function of 'new ssl cert' */
2407static int cli_parse_new_cert(char **args, char *payload, struct appctx *appctx, void *private)
2408{
2409 struct ckch_store *store;
2410 char *err = NULL;
2411 char *path;
2412
2413 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2414 return 1;
2415
2416 if (!*args[3])
2417 return cli_err(appctx, "'new ssl cert' expects a filename\n");
2418
2419 path = args[3];
2420
2421 /* The operations on the CKCH architecture are locked so we can
2422 * manipulate ckch_store and ckch_inst */
2423 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2424 return cli_err(appctx, "Can't create a certificate!\nOperations on certificates are currently locked!\n");
2425
2426 store = ckchs_lookup(path);
2427 if (store != NULL) {
2428 memprintf(&err, "Certificate '%s' already exists!\n", path);
2429 store = NULL; /* we don't want to free it */
2430 goto error;
2431 }
2432 /* we won't support multi-certificate bundle here */
William Lallemandbd8e6ed2020-09-16 16:08:08 +02002433 store = ckch_store_new(path);
William Lallemandda8584c2020-05-14 10:14:37 +02002434 if (!store) {
2435 memprintf(&err, "unable to allocate memory.\n");
2436 goto error;
2437 }
2438
2439 /* insert into the ckchs tree */
2440 ebst_insert(&ckchs_tree, &store->node);
2441 memprintf(&err, "New empty certificate store '%s'!\n", args[3]);
2442
2443 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2444 return cli_dynmsg(appctx, LOG_NOTICE, err);
2445error:
2446 free(store);
2447 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2448 return cli_dynerr(appctx, err);
2449}
2450
2451/* parsing function of 'del ssl cert' */
2452static int cli_parse_del_cert(char **args, char *payload, struct appctx *appctx, void *private)
2453{
2454 struct ckch_store *store;
2455 char *err = NULL;
2456 char *filename;
2457
2458 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2459 return 1;
2460
2461 if (!*args[3])
2462 return cli_err(appctx, "'del ssl cert' expects a certificate name\n");
2463
2464 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2465 return cli_err(appctx, "Can't delete the certificate!\nOperations on certificates are currently locked!\n");
2466
2467 filename = args[3];
2468
2469 store = ckchs_lookup(filename);
2470 if (store == NULL) {
2471 memprintf(&err, "certificate '%s' doesn't exist!\n", filename);
2472 goto error;
2473 }
2474 if (!LIST_ISEMPTY(&store->ckch_inst)) {
2475 memprintf(&err, "certificate '%s' in use, can't be deleted!\n", filename);
2476 goto error;
2477 }
2478
2479 ebmb_delete(&store->node);
2480 ckch_store_free(store);
2481
2482 memprintf(&err, "Certificate '%s' deleted!\n", filename);
2483
2484 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2485 return cli_dynmsg(appctx, LOG_NOTICE, err);
2486
2487error:
2488 memprintf(&err, "Can't remove the certificate: %s\n", err ? err : "");
2489 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2490 return cli_dynerr(appctx, err);
2491}
2492
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002493
Remi Tricot-Le Breton9f40fe02021-03-16 16:21:27 +01002494
2495/* parsing function of 'new ssl ca-file' */
2496static int cli_parse_new_cafile(char **args, char *payload, struct appctx *appctx, void *private)
2497{
2498 struct cafile_entry *cafile_entry;
2499 char *err = NULL;
2500 char *path;
2501
2502 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2503 return 1;
2504
2505 if (!*args[3])
2506 return cli_err(appctx, "'new ssl ca-file' expects a filename\n");
2507
2508 path = args[3];
2509
2510 /* The operations on the CKCH architecture are locked so we can
2511 * manipulate ckch_store and ckch_inst */
2512 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2513 return cli_err(appctx, "Can't create a CA file!\nOperations on certificates are currently locked!\n");
2514
2515 cafile_entry = ssl_store_get_cafile_entry(path, 0);
2516 if (cafile_entry) {
2517 memprintf(&err, "CA file '%s' already exists!\n", path);
2518 goto error;
2519 }
2520
2521 cafile_entry = ssl_store_create_cafile_entry(path, NULL, CAFILE_CERT);
2522 if (!cafile_entry) {
2523 memprintf(&err, "%sCannot allocate memory!\n",
2524 err ? err : "");
2525 goto error;
2526 }
2527
2528 /* Add the newly created cafile_entry to the tree so that
2529 * any new ckch instance created from now can use it. */
2530 if (ssl_store_add_uncommitted_cafile_entry(cafile_entry))
2531 goto error;
2532
2533 memprintf(&err, "New CA file created '%s'!\n", path);
2534
2535 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2536 return cli_dynmsg(appctx, LOG_NOTICE, err);
2537error:
2538 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2539 return cli_dynerr(appctx, err);
2540}
2541
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002542/*
2543 * Parsing function of `set ssl ca-file`
2544 */
2545static int cli_parse_set_cafile(char **args, char *payload, struct appctx *appctx, void *private)
2546{
2547 char *err = NULL;
2548 int errcode = 0;
2549 struct buffer *buf;
2550
2551 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2552 return 1;
2553
2554 if (!*args[3] || !payload)
2555 return cli_err(appctx, "'set ssl ca-file expects a filename and CAs as a payload\n");
2556
2557 /* The operations on the CKCH architecture are locked so we can
2558 * manipulate ckch_store and ckch_inst */
2559 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2560 return cli_err(appctx, "Can't update the CA file!\nOperations on certificates are currently locked!\n");
2561
2562 if ((buf = alloc_trash_chunk()) == NULL) {
2563 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2564 errcode |= ERR_ALERT | ERR_FATAL;
2565 goto end;
2566 }
2567
2568 if (!chunk_strcpy(buf, args[3])) {
2569 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2570 errcode |= ERR_ALERT | ERR_FATAL;
2571 goto end;
2572 }
2573
2574 appctx->ctx.ssl.old_cafile_entry = NULL;
2575 appctx->ctx.ssl.new_cafile_entry = NULL;
2576
2577 /* if there is an ongoing transaction */
2578 if (cafile_transaction.path) {
2579 /* if there is an ongoing transaction, check if this is the same file */
2580 if (strcmp(cafile_transaction.path, buf->area) != 0) {
2581 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", cafile_transaction.path, buf->area);
2582 errcode |= ERR_ALERT | ERR_FATAL;
2583 goto end;
2584 }
2585 appctx->ctx.ssl.old_cafile_entry = cafile_transaction.old_cafile_entry;
2586 }
2587 else {
2588 /* lookup for the certificate in the tree */
2589 appctx->ctx.ssl.old_cafile_entry = ssl_store_get_cafile_entry(buf->area, 0);
2590 }
2591
2592 if (!appctx->ctx.ssl.old_cafile_entry) {
2593 memprintf(&err, "%sCan't replace a CA file which is not referenced by the configuration!\n",
2594 err ? err : "");
2595 errcode |= ERR_ALERT | ERR_FATAL;
2596 goto end;
2597 }
2598
2599 if (!appctx->ctx.ssl.path) {
2600 /* this is a new transaction, set the path of the transaction */
2601 appctx->ctx.ssl.path = strdup(appctx->ctx.ssl.old_cafile_entry->path);
2602 if (!appctx->ctx.ssl.path) {
2603 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2604 errcode |= ERR_ALERT | ERR_FATAL;
2605 goto end;
2606 }
2607 }
2608
2609 if (appctx->ctx.ssl.new_cafile_entry)
2610 ssl_store_delete_cafile_entry(appctx->ctx.ssl.new_cafile_entry);
2611
2612 /* Create a new cafile_entry without adding it to the cafile tree. */
Remi Tricot-Le Breton0bb48242021-04-16 17:59:23 +02002613 appctx->ctx.ssl.new_cafile_entry = ssl_store_create_cafile_entry(appctx->ctx.ssl.path, NULL, CAFILE_CERT);
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002614 if (!appctx->ctx.ssl.new_cafile_entry) {
2615 memprintf(&err, "%sCannot allocate memory!\n",
2616 err ? err : "");
2617 errcode |= ERR_ALERT | ERR_FATAL;
2618 goto end;
2619 }
2620
2621 /* Fill the new entry with the new CAs. */
2622 if (ssl_store_load_ca_from_buf(appctx->ctx.ssl.new_cafile_entry, payload)) {
2623 memprintf(&err, "%sInvalid payload\n", err ? err : "");
2624 errcode |= ERR_ALERT | ERR_FATAL;
2625 goto end;
2626 }
2627
2628 /* we succeed, we can save the ca in the transaction */
2629
2630 /* if there wasn't a transaction, update the old CA */
2631 if (!cafile_transaction.old_cafile_entry) {
2632 cafile_transaction.old_cafile_entry = appctx->ctx.ssl.old_cafile_entry;
2633 cafile_transaction.path = appctx->ctx.ssl.path;
2634 err = memprintf(&err, "transaction created for CA %s!\n", cafile_transaction.path);
2635 } else {
2636 err = memprintf(&err, "transaction updated for CA %s!\n", cafile_transaction.path);
2637 }
2638
2639 /* free the previous CA if there was a transaction */
2640 ssl_store_delete_cafile_entry(cafile_transaction.new_cafile_entry);
2641
2642 cafile_transaction.new_cafile_entry = appctx->ctx.ssl.new_cafile_entry;
2643
2644 /* creates the SNI ctxs later in the IO handler */
2645
2646end:
2647 free_trash_chunk(buf);
2648
2649 if (errcode & ERR_CODE) {
2650 ssl_store_delete_cafile_entry(appctx->ctx.ssl.new_cafile_entry);
2651 appctx->ctx.ssl.new_cafile_entry = NULL;
2652 appctx->ctx.ssl.old_cafile_entry = NULL;
2653
Tim Duesterhus025b93e2021-11-04 21:03:52 +01002654 ha_free(&appctx->ctx.ssl.path);
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002655
2656 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2657 return cli_dynerr(appctx, memprintf(&err, "%sCan't update %s!\n", err ? err : "", args[3]));
2658 } else {
2659
2660 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2661 return cli_dynmsg(appctx, LOG_NOTICE, err);
2662 }
2663}
2664
2665
2666/*
2667 * Parsing function of 'commit ssl ca-file'
2668 */
2669static int cli_parse_commit_cafile(char **args, char *payload, struct appctx *appctx, void *private)
2670{
2671 char *err = NULL;
2672
2673 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2674 return 1;
2675
2676 if (!*args[3])
2677 return cli_err(appctx, "'commit ssl ca-file expects a filename\n");
2678
2679 /* The operations on the CKCH architecture are locked so we can
2680 * manipulate ckch_store and ckch_inst */
2681 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2682 return cli_err(appctx, "Can't commit the CA file!\nOperations on certificates are currently locked!\n");
2683
2684 if (!cafile_transaction.path) {
2685 memprintf(&err, "No ongoing transaction! !\n");
2686 goto error;
2687 }
2688
2689 if (strcmp(cafile_transaction.path, args[3]) != 0) {
2690 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", cafile_transaction.path, args[3]);
2691 goto error;
2692 }
2693 /* init the appctx structure */
2694 appctx->st2 = SETCERT_ST_INIT;
2695 appctx->ctx.ssl.next_ckchi_link = NULL;
2696 appctx->ctx.ssl.old_cafile_entry = cafile_transaction.old_cafile_entry;
2697 appctx->ctx.ssl.new_cafile_entry = cafile_transaction.new_cafile_entry;
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002698 appctx->ctx.ssl.cafile_type = CAFILE_CERT;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002699
2700 return 0;
2701
2702error:
2703
2704 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2705 err = memprintf(&err, "%sCan't commit %s!\n", err ? err : "", args[3]);
2706
2707 return cli_dynerr(appctx, err);
2708}
2709
2710enum {
2711 CREATE_NEW_INST_OK = 0,
2712 CREATE_NEW_INST_YIELD = -1,
2713 CREATE_NEW_INST_ERR = -2
2714};
2715
2716static inline int __create_new_instance(struct appctx *appctx, struct ckch_inst *ckchi, int *count,
Remi Tricot-Le Bretond75b99e2021-05-17 11:45:55 +02002717 struct buffer *trash, char **err)
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002718{
2719 struct ckch_inst *new_inst;
2720
2721 /* it takes a lot of CPU to creates SSL_CTXs, so we yield every 10 CKCH instances */
2722 if (*count >= 10) {
2723 /* save the next ckchi to compute */
2724 appctx->ctx.ssl.next_ckchi = ckchi;
2725 return CREATE_NEW_INST_YIELD;
2726 }
2727
2728 /* Rebuild a new ckch instance that uses the same ckch_store
2729 * than a reference ckchi instance but will use a new CA file. */
Remi Tricot-Le Bretond75b99e2021-05-17 11:45:55 +02002730 if (ckch_inst_rebuild(ckchi->ckch_store, ckchi, &new_inst, err))
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002731 return CREATE_NEW_INST_ERR;
2732
2733 /* display one dot per new instance */
2734 chunk_appendf(trash, ".");
2735 ++(*count);
2736
2737 return CREATE_NEW_INST_OK;
2738}
2739
2740/*
2741 * This function tries to create new ckch instances and their SNIs using a newly
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002742 * set certificate authority (CA file) or a newly set Certificate Revocation
2743 * List (CRL), depending on the command being called.
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002744 */
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002745static int cli_io_handler_commit_cafile_crlfile(struct appctx *appctx)
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002746{
Christopher Faulet908628c2022-03-25 16:43:49 +01002747 struct conn_stream *cs = appctx->owner;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002748 int y = 0;
2749 char *err = NULL;
Remi Tricot-Le Bretona6b27842021-05-18 10:06:00 +02002750 struct cafile_entry *old_cafile_entry = NULL, *new_cafile_entry = NULL;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002751 struct ckch_inst_link *ckchi_link;
2752 struct buffer *trash = alloc_trash_chunk();
2753
2754 if (trash == NULL)
2755 goto error;
2756
Christopher Faulet908628c2022-03-25 16:43:49 +01002757 if (unlikely(cs_ic(cs)->flags & (CF_WRITE_ERROR|CF_SHUTW)))
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002758 goto error;
2759
2760 while (1) {
2761 switch (appctx->st2) {
2762 case SETCERT_ST_INIT:
2763 /* This state just print the update message */
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002764 switch (appctx->ctx.ssl.cafile_type) {
2765 case CAFILE_CERT:
2766 chunk_printf(trash, "Committing %s", cafile_transaction.path);
2767 break;
2768 case CAFILE_CRL:
2769 chunk_printf(trash, "Committing %s", crlfile_transaction.path);
2770 break;
2771 default:
2772 goto error;
2773 }
Christopher Faulet908628c2022-03-25 16:43:49 +01002774 if (ci_putchk(cs_ic(cs), trash) == -1) {
Christopher Fauleta0bdec32022-04-04 07:51:21 +02002775 cs_rx_room_blk(cs);
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002776 goto yield;
2777 }
2778 appctx->st2 = SETCERT_ST_GEN;
2779 /* fallthrough */
2780 case SETCERT_ST_GEN:
2781 /*
2782 * This state generates the ckch instances with their
2783 * sni_ctxs and SSL_CTX.
2784 *
2785 * Since the SSL_CTX generation can be CPU consumer, we
2786 * yield every 10 instances.
2787 */
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002788 switch (appctx->ctx.ssl.cafile_type) {
2789 case CAFILE_CERT:
2790 old_cafile_entry = appctx->ctx.ssl.old_cafile_entry;
2791 new_cafile_entry = appctx->ctx.ssl.new_cafile_entry;
2792 break;
2793 case CAFILE_CRL:
2794 old_cafile_entry = appctx->ctx.ssl.old_crlfile_entry;
2795 new_cafile_entry = appctx->ctx.ssl.new_crlfile_entry;
2796 break;
2797 }
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002798 if (!new_cafile_entry)
2799 continue;
2800
2801 /* get the next ckchi to regenerate */
2802 ckchi_link = appctx->ctx.ssl.next_ckchi_link;
2803 /* we didn't start yet, set it to the first elem */
2804 if (ckchi_link == NULL) {
2805 ckchi_link = LIST_ELEM(old_cafile_entry->ckch_inst_link.n, typeof(ckchi_link), list);
2806 /* Add the newly created cafile_entry to the tree so that
2807 * any new ckch instance created from now can use it. */
2808 if (ssl_store_add_uncommitted_cafile_entry(new_cafile_entry))
2809 goto error;
2810 }
2811
2812 list_for_each_entry_from(ckchi_link, &old_cafile_entry->ckch_inst_link, list) {
Remi Tricot-Le Bretond75b99e2021-05-17 11:45:55 +02002813 switch (__create_new_instance(appctx, ckchi_link->ckch_inst, &y, trash, &err)) {
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002814 case CREATE_NEW_INST_YIELD:
2815 appctx->ctx.ssl.next_ckchi_link = ckchi_link;
2816 goto yield;
2817 case CREATE_NEW_INST_ERR:
2818 goto error;
2819 default: break;
2820 }
2821 }
2822
2823 appctx->st2 = SETCERT_ST_INSERT;
2824 /* fallthrough */
2825 case SETCERT_ST_INSERT:
2826 /* The generation is finished, we can insert everything */
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002827 switch (appctx->ctx.ssl.cafile_type) {
2828 case CAFILE_CERT:
2829 old_cafile_entry = appctx->ctx.ssl.old_cafile_entry;
2830 new_cafile_entry = appctx->ctx.ssl.new_cafile_entry;
2831 break;
2832 case CAFILE_CRL:
2833 old_cafile_entry = appctx->ctx.ssl.old_crlfile_entry;
2834 new_cafile_entry = appctx->ctx.ssl.new_crlfile_entry;
2835 break;
2836 }
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002837 if (!new_cafile_entry)
2838 continue;
2839
2840 /* insert the new ckch_insts in the crtlist_entry */
2841 list_for_each_entry(ckchi_link, &new_cafile_entry->ckch_inst_link, list) {
2842 if (ckchi_link->ckch_inst->crtlist_entry)
2843 LIST_INSERT(&ckchi_link->ckch_inst->crtlist_entry->ckch_inst,
2844 &ckchi_link->ckch_inst->by_crtlist_entry);
2845 }
2846
2847 /* First, we insert every new SNIs in the trees, also replace the default_ctx */
2848 list_for_each_entry(ckchi_link, &new_cafile_entry->ckch_inst_link, list) {
2849 __ssl_sock_load_new_ckch_instance(ckchi_link->ckch_inst);
2850 }
2851
2852 /* delete the old sni_ctx, the old ckch_insts and the ckch_store */
2853 list_for_each_entry(ckchi_link, &old_cafile_entry->ckch_inst_link, list) {
2854 __ckch_inst_free_locked(ckchi_link->ckch_inst);
2855 }
2856
2857
2858 /* Remove the old cafile entry from the tree */
2859 ebmb_delete(&old_cafile_entry->node);
2860 ssl_store_delete_cafile_entry(old_cafile_entry);
2861
2862 appctx->st2 = SETCERT_ST_FIN;
2863 /* fallthrough */
2864 case SETCERT_ST_FIN:
2865 /* we achieved the transaction, we can set everything to NULL */
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002866 switch (appctx->ctx.ssl.cafile_type) {
2867 case CAFILE_CERT:
2868 ha_free(&cafile_transaction.path);
2869 cafile_transaction.old_cafile_entry = NULL;
2870 cafile_transaction.new_cafile_entry = NULL;
2871 break;
2872 case CAFILE_CRL:
2873 ha_free(&crlfile_transaction.path);
2874 crlfile_transaction.old_crlfile_entry = NULL;
2875 crlfile_transaction.new_crlfile_entry = NULL;
2876 break;
2877 }
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002878 goto end;
2879 }
2880 }
2881end:
2882
2883 chunk_appendf(trash, "\n");
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002884 chunk_appendf(trash, "Success!\n");
Christopher Faulet908628c2022-03-25 16:43:49 +01002885 if (ci_putchk(cs_ic(cs), trash) == -1)
Christopher Fauleta0bdec32022-04-04 07:51:21 +02002886 cs_rx_room_blk(cs);
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002887 free_trash_chunk(trash);
2888 /* success: call the release function and don't come back */
2889 return 1;
2890yield:
2891 /* store the state */
Christopher Faulet908628c2022-03-25 16:43:49 +01002892 if (ci_putchk(cs_ic(cs), trash) == -1)
Christopher Fauleta0bdec32022-04-04 07:51:21 +02002893 cs_rx_room_blk(cs);
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002894 free_trash_chunk(trash);
Christopher Fauleta0bdec32022-04-04 07:51:21 +02002895 cs_rx_endp_more(cs); /* let's come back later */
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002896 return 0; /* should come back */
2897
2898error:
2899 /* spin unlock and free are done in the release function */
2900 if (trash) {
2901 chunk_appendf(trash, "\n%sFailed!\n", err);
Christopher Faulet908628c2022-03-25 16:43:49 +01002902 if (ci_putchk(cs_ic(cs), trash) == -1)
Christopher Fauleta0bdec32022-04-04 07:51:21 +02002903 cs_rx_room_blk(cs);
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002904 free_trash_chunk(trash);
2905 }
2906 /* error: call the release function and don't come back */
2907 return 1;
2908}
2909
Remi Tricot-Le Bretond5fd09d2021-03-11 10:22:52 +01002910
2911/* parsing function of 'abort ssl ca-file' */
2912static int cli_parse_abort_cafile(char **args, char *payload, struct appctx *appctx, void *private)
2913{
2914 char *err = NULL;
2915
2916 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2917 return 1;
2918
2919 if (!*args[3])
2920 return cli_err(appctx, "'abort ssl ca-file' expects a filename\n");
2921
2922 /* The operations on the CKCH architecture are locked so we can
2923 * manipulate ckch_store and ckch_inst */
2924 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2925 return cli_err(appctx, "Can't abort!\nOperations on certificates are currently locked!\n");
2926
2927 if (!cafile_transaction.path) {
2928 memprintf(&err, "No ongoing transaction!\n");
2929 goto error;
2930 }
2931
2932 if (strcmp(cafile_transaction.path, args[3]) != 0) {
2933 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to abort a transaction for '%s'\n", cafile_transaction.path, args[3]);
2934 goto error;
2935 }
2936
2937 /* Only free the uncommitted cafile_entry here, because the SNI and instances were not generated yet */
2938 ssl_store_delete_cafile_entry(cafile_transaction.new_cafile_entry);
2939 cafile_transaction.new_cafile_entry = NULL;
2940 cafile_transaction.old_cafile_entry = NULL;
2941 ha_free(&cafile_transaction.path);
2942
2943 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2944
2945 err = memprintf(&err, "Transaction aborted for certificate '%s'!\n", args[3]);
2946 return cli_dynmsg(appctx, LOG_NOTICE, err);
2947
2948error:
2949 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2950
2951 return cli_dynerr(appctx, err);
2952}
2953
Willy Tarreau821c3b02022-05-04 15:47:39 +02002954/* release function of the `commit ssl ca-file' command, free things and unlock the spinlock.
2955 * It uses ctx.ssl.new_cafile_entry.
2956 */
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002957static void cli_release_commit_cafile(struct appctx *appctx)
2958{
2959 if (appctx->st2 != SETCERT_ST_FIN) {
2960 struct cafile_entry *new_cafile_entry = appctx->ctx.ssl.new_cafile_entry;
2961
2962 /* Remove the uncommitted cafile_entry from the tree. */
2963 ebmb_delete(&new_cafile_entry->node);
2964 ssl_store_delete_cafile_entry(new_cafile_entry);
2965 }
2966 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2967}
2968
2969
Willy Tarreau821c3b02022-05-04 15:47:39 +02002970/* IO handler of details "show ssl ca-file <filename[:index]>".
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02002971 * It uses a show_cafile_ctx context, and the global
2972 * cafile_transaction.new_cafile_entry in read-only.
Willy Tarreau821c3b02022-05-04 15:47:39 +02002973 */
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01002974static int cli_io_handler_show_cafile_detail(struct appctx *appctx)
2975{
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02002976 struct show_cafile_ctx *ctx = appctx->svcctx;
Christopher Faulet908628c2022-03-25 16:43:49 +01002977 struct conn_stream *cs = appctx->owner;
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02002978 struct cafile_entry *cafile_entry = ctx->cur_cafile_entry;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01002979 struct buffer *out = alloc_trash_chunk();
William Lallemand03a32e52022-04-26 18:17:15 +02002980 int i = 0;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01002981 X509 *cert;
2982 STACK_OF(X509_OBJECT) *objs;
2983 int retval = 0;
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02002984 int ca_index = ctx->ca_index;
2985 int show_all = ctx->show_all;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01002986
2987 if (!out)
2988 goto end_no_putchk;
2989
2990 chunk_appendf(out, "Filename: ");
2991 if (cafile_entry == cafile_transaction.new_cafile_entry)
2992 chunk_appendf(out, "*");
2993 chunk_appendf(out, "%s\n", cafile_entry->path);
2994
2995 chunk_appendf(out, "Status: ");
2996 if (!cafile_entry->ca_store)
2997 chunk_appendf(out, "Empty\n");
2998 else if (LIST_ISEMPTY(&cafile_entry->ckch_inst_link))
2999 chunk_appendf(out, "Unused\n");
3000 else
3001 chunk_appendf(out, "Used\n");
3002
3003 if (!cafile_entry->ca_store)
3004 goto end;
3005
3006 objs = X509_STORE_get0_objects(cafile_entry->ca_store);
William Lallemand03a32e52022-04-26 18:17:15 +02003007 for (i = ca_index; i < sk_X509_OBJECT_num(objs); i++) {
3008
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003009 cert = X509_OBJECT_get0_X509(sk_X509_OBJECT_value(objs, i));
3010 if (!cert)
3011 continue;
3012
William Lallemand03a32e52022-04-26 18:17:15 +02003013 /* file starts at line 1 */
Remi Tricot-Le Bretone8041fe2022-04-05 16:44:21 +02003014 chunk_appendf(out, " \nCertificate #%d:\n", i+1);
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003015 retval = show_cert_detail(cert, NULL, out);
3016 if (retval < 0)
3017 goto end_no_putchk;
William Lallemand03a32e52022-04-26 18:17:15 +02003018 else if (retval)
3019 goto yield;
3020
3021 if (ci_putchk(cs_ic(cs), out) == -1) {
3022 cs_rx_room_blk(cs);
3023 goto yield;
3024 }
3025
3026 if (!show_all) /* only need to dump one certificate */
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003027 goto end;
3028 }
3029
3030end:
William Lallemand03a32e52022-04-26 18:17:15 +02003031 free_trash_chunk(out);
3032 return 1; /* end, don't come back */
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003033
3034end_no_putchk:
3035 free_trash_chunk(out);
3036 return 1;
3037yield:
William Lallemand03a32e52022-04-26 18:17:15 +02003038 /* save the current state */
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003039 ctx->ca_index = i;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003040 free_trash_chunk(out);
3041 return 0; /* should come back */
3042}
3043
3044
Willy Tarreau06305792022-05-04 15:57:30 +02003045/* parsing function for 'show ssl ca-file [cafile[:index]]'.
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003046 * It prepares a show_cafile_ctx context, and checks the global
3047 * cafile_transaction under the ckch_lock (read only).
Willy Tarreau06305792022-05-04 15:57:30 +02003048 */
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003049static int cli_parse_show_cafile(char **args, char *payload, struct appctx *appctx, void *private)
3050{
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003051 struct show_cafile_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003052 struct cafile_entry *cafile_entry;
William Lallemand03a32e52022-04-26 18:17:15 +02003053 int ca_index = 0;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003054 char *colons;
3055 char *err = NULL;
3056
3057 if (!cli_has_level(appctx, ACCESS_LVL_OPER))
3058 return cli_err(appctx, "Can't allocate memory!\n");
3059
3060 /* The operations on the CKCH architecture are locked so we can
3061 * manipulate ckch_store and ckch_inst */
3062 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3063 return cli_err(appctx, "Can't show!\nOperations on certificates are currently locked!\n");
3064
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003065 ctx->show_all = 1; /* show all certificates */
3066 ctx->ca_index = 0;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003067 /* check if there is a certificate to lookup */
3068 if (*args[3]) {
3069
3070 /* Look for an optional CA index after the CA file name */
3071 colons = strchr(args[3], ':');
3072 if (colons) {
3073 char *endptr;
3074
3075 ca_index = strtol(colons + 1, &endptr, 10);
3076 /* Indexes start at 1 */
3077 if (colons + 1 == endptr || *endptr != '\0' || ca_index <= 0) {
3078 memprintf(&err, "wrong CA index after colons in '%s'!", args[3]);
3079 goto error;
3080 }
3081 *colons = '\0';
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003082 ctx->ca_index = ca_index - 1; /* we start counting at 0 in the ca_store, but at 1 on the CLI */
3083 ctx->show_all = 0; /* show only one certificate */
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003084 }
3085
3086 if (*args[3] == '*') {
3087 if (!cafile_transaction.new_cafile_entry)
3088 goto error;
3089
3090 cafile_entry = cafile_transaction.new_cafile_entry;
3091
3092 if (strcmp(args[3] + 1, cafile_entry->path) != 0)
3093 goto error;
3094
3095 } else {
3096 /* Get the "original" cafile_entry and not the
3097 * uncommitted one if it exists. */
3098 if ((cafile_entry = ssl_store_get_cafile_entry(args[3], 1)) == NULL || cafile_entry->type != CAFILE_CERT)
3099 goto error;
3100 }
3101
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003102 ctx->cur_cafile_entry = cafile_entry;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003103 /* use the IO handler that shows details */
3104 appctx->io_handler = cli_io_handler_show_cafile_detail;
3105 }
3106
3107 return 0;
3108
3109error:
3110 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3111 if (err)
3112 return cli_dynerr(appctx, err);
3113 return cli_err(appctx, "Can't display the CA file : Not found!\n");
3114}
3115
3116
3117/* release function of the 'show ssl ca-file' command */
3118static void cli_release_show_cafile(struct appctx *appctx)
3119{
3120 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3121}
3122
3123
3124/* This function returns the number of certificates in a cafile_entry. */
3125static int get_certificate_count(struct cafile_entry *cafile_entry)
3126{
3127 int cert_count = 0;
3128 STACK_OF(X509_OBJECT) *objs;
3129
3130 if (cafile_entry && cafile_entry->ca_store) {
3131 objs = X509_STORE_get0_objects(cafile_entry->ca_store);
3132 if (objs)
3133 cert_count = sk_X509_OBJECT_num(objs);
3134 }
3135 return cert_count;
3136}
3137
3138/* IO handler of "show ssl ca-file". The command taking a specific CA file name
Willy Tarreau821c3b02022-05-04 15:47:39 +02003139 * is managed in cli_io_handler_show_cafile_detail.
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003140 * It uses a show_cafile_ctx and the global cafile_transaction.new_cafile_entry
3141 * in read-only.
Willy Tarreau821c3b02022-05-04 15:47:39 +02003142 */
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003143static int cli_io_handler_show_cafile(struct appctx *appctx)
3144{
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003145 struct show_cafile_ctx *ctx = appctx->svcctx;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003146 struct buffer *trash = alloc_trash_chunk();
3147 struct ebmb_node *node;
Christopher Faulet908628c2022-03-25 16:43:49 +01003148 struct conn_stream *cs = appctx->owner;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003149 struct cafile_entry *cafile_entry;
3150
3151 if (trash == NULL)
3152 return 1;
3153
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003154 if (!ctx->old_cafile_entry) {
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003155 if (cafile_transaction.old_cafile_entry) {
3156 chunk_appendf(trash, "# transaction\n");
3157 chunk_appendf(trash, "*%s", cafile_transaction.old_cafile_entry->path);
3158
3159 chunk_appendf(trash, " - %d certificate(s)\n", get_certificate_count(cafile_transaction.new_cafile_entry));
3160 }
3161 }
3162
3163 /* First time in this io_handler. */
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003164 if (!ctx->cur_cafile_entry) {
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003165 chunk_appendf(trash, "# filename\n");
3166 node = ebmb_first(&cafile_tree);
3167 } else {
3168 /* We yielded during a previous call. */
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003169 node = &ctx->cur_cafile_entry->node;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003170 }
3171
3172 while (node) {
3173 cafile_entry = ebmb_entry(node, struct cafile_entry, node);
3174 if (cafile_entry->type == CAFILE_CERT) {
3175 chunk_appendf(trash, "%s", cafile_entry->path);
3176
3177 chunk_appendf(trash, " - %d certificate(s)\n", get_certificate_count(cafile_entry));
3178 }
3179
3180 node = ebmb_next(node);
Christopher Faulet908628c2022-03-25 16:43:49 +01003181 if (ci_putchk(cs_ic(cs), trash) == -1) {
Christopher Fauleta0bdec32022-04-04 07:51:21 +02003182 cs_rx_room_blk(cs);
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003183 goto yield;
3184 }
3185 }
3186
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003187 ctx->cur_cafile_entry = NULL;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003188 free_trash_chunk(trash);
3189 return 1;
3190yield:
3191
3192 free_trash_chunk(trash);
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003193 ctx->cur_cafile_entry = cafile_entry;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003194 return 0; /* should come back */
3195}
3196
Remi Tricot-Le Bretonc3a84772021-03-25 18:13:57 +01003197/* parsing function of 'del ssl ca-file' */
3198static int cli_parse_del_cafile(char **args, char *payload, struct appctx *appctx, void *private)
3199{
3200 struct cafile_entry *cafile_entry;
3201 char *err = NULL;
3202 char *filename;
3203
3204 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
3205 return 1;
3206
3207 if (!*args[3])
3208 return cli_err(appctx, "'del ssl ca-file' expects a CA file name\n");
3209
3210 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3211 return cli_err(appctx, "Can't delete the CA file!\nOperations on certificates are currently locked!\n");
3212
3213 filename = args[3];
3214
3215 cafile_entry = ssl_store_get_cafile_entry(filename, 0);
3216 if (!cafile_entry) {
3217 memprintf(&err, "CA file '%s' doesn't exist!\n", filename);
3218 goto error;
3219 }
3220
3221 if (!LIST_ISEMPTY(&cafile_entry->ckch_inst_link)) {
3222 memprintf(&err, "CA file '%s' in use, can't be deleted!\n", filename);
3223 goto error;
3224 }
3225
3226 /* Remove the cafile_entry from the tree */
3227 ebmb_delete(&cafile_entry->node);
3228 ssl_store_delete_cafile_entry(cafile_entry);
3229
3230 memprintf(&err, "CA file '%s' deleted!\n", filename);
3231
3232 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3233 return cli_dynmsg(appctx, LOG_NOTICE, err);
3234
3235error:
3236 memprintf(&err, "Can't remove the CA file: %s\n", err ? err : "");
3237 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3238 return cli_dynerr(appctx, err);
3239}
3240
Remi Tricot-Le Breton720e3b92021-04-26 11:00:42 +02003241/* parsing function of 'new ssl crl-file' */
3242static int cli_parse_new_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
3243{
3244 struct cafile_entry *cafile_entry;
3245 char *err = NULL;
3246 char *path;
3247
3248 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
3249 return 1;
3250
3251 if (!*args[3])
3252 return cli_err(appctx, "'new ssl crl-file' expects a filename\n");
3253
3254 path = args[3];
3255
3256 /* The operations on the CKCH architecture are locked so we can
3257 * manipulate ckch_store and ckch_inst */
3258 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3259 return cli_err(appctx, "Can't create a CA file!\nOperations on certificates are currently locked!\n");
3260
3261 cafile_entry = ssl_store_get_cafile_entry(path, 0);
3262 if (cafile_entry) {
3263 memprintf(&err, "CRL file '%s' already exists!\n", path);
3264 goto error;
3265 }
3266
3267 cafile_entry = ssl_store_create_cafile_entry(path, NULL, CAFILE_CRL);
3268 if (!cafile_entry) {
3269 memprintf(&err, "%sCannot allocate memory!\n", err ? err : "");
3270 goto error;
3271 }
3272
3273 /* Add the newly created cafile_entry to the tree so that
3274 * any new ckch instance created from now can use it. */
3275 if (ssl_store_add_uncommitted_cafile_entry(cafile_entry))
3276 goto error;
3277
3278 memprintf(&err, "New CRL file created '%s'!\n", path);
3279
3280 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3281 return cli_dynmsg(appctx, LOG_NOTICE, err);
3282error:
3283 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3284 return cli_dynerr(appctx, err);
3285}
3286
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003287/* Parsing function of `set ssl crl-file` */
3288static int cli_parse_set_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
3289{
3290 char *err = NULL;
3291 int errcode = 0;
3292 struct buffer *buf;
3293
3294 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
3295 return 1;
3296
3297 if (!*args[3] || !payload)
3298 return cli_err(appctx, "'set ssl crl-file expects a filename and CAs as a payload\n");
3299
3300 /* The operations on the CKCH architecture are locked so we can
3301 * manipulate ckch_store and ckch_inst */
3302 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3303 return cli_err(appctx, "Can't update the CRL file!\nOperations on certificates are currently locked!\n");
3304
3305 if ((buf = alloc_trash_chunk()) == NULL) {
3306 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
3307 errcode |= ERR_ALERT | ERR_FATAL;
3308 goto end;
3309 }
3310
3311 if (!chunk_strcpy(buf, args[3])) {
3312 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
3313 errcode |= ERR_ALERT | ERR_FATAL;
3314 goto end;
3315 }
3316
3317 appctx->ctx.ssl.old_crlfile_entry = NULL;
3318 appctx->ctx.ssl.new_crlfile_entry = NULL;
3319
3320 /* if there is an ongoing transaction */
3321 if (crlfile_transaction.path) {
3322 /* if there is an ongoing transaction, check if this is the same file */
3323 if (strcmp(crlfile_transaction.path, buf->area) != 0) {
3324 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", crlfile_transaction.path, buf->area);
3325 errcode |= ERR_ALERT | ERR_FATAL;
3326 goto end;
3327 }
3328 appctx->ctx.ssl.old_crlfile_entry = crlfile_transaction.old_crlfile_entry;
3329 }
3330 else {
3331 /* lookup for the certificate in the tree */
3332 appctx->ctx.ssl.old_crlfile_entry = ssl_store_get_cafile_entry(buf->area, 0);
3333 }
3334
3335 if (!appctx->ctx.ssl.old_crlfile_entry) {
3336 memprintf(&err, "%sCan't replace a CRL file which is not referenced by the configuration!\n",
3337 err ? err : "");
3338 errcode |= ERR_ALERT | ERR_FATAL;
3339 goto end;
3340 }
3341
3342 if (!appctx->ctx.ssl.path) {
3343 /* this is a new transaction, set the path of the transaction */
3344 appctx->ctx.ssl.path = strdup(appctx->ctx.ssl.old_crlfile_entry->path);
3345 if (!appctx->ctx.ssl.path) {
3346 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
3347 errcode |= ERR_ALERT | ERR_FATAL;
3348 goto end;
3349 }
3350 }
3351
3352 if (appctx->ctx.ssl.new_crlfile_entry)
3353 ssl_store_delete_cafile_entry(appctx->ctx.ssl.new_crlfile_entry);
3354
3355 /* Create a new cafile_entry without adding it to the cafile tree. */
3356 appctx->ctx.ssl.new_crlfile_entry = ssl_store_create_cafile_entry(appctx->ctx.ssl.path, NULL, CAFILE_CRL);
3357 if (!appctx->ctx.ssl.new_crlfile_entry) {
3358 memprintf(&err, "%sCannot allocate memory!\n", err ? err : "");
3359 errcode |= ERR_ALERT | ERR_FATAL;
3360 goto end;
3361 }
3362
3363 /* Fill the new entry with the new CRL. */
3364 if (ssl_store_load_ca_from_buf(appctx->ctx.ssl.new_crlfile_entry, payload)) {
3365 memprintf(&err, "%sInvalid payload\n", err ? err : "");
3366 errcode |= ERR_ALERT | ERR_FATAL;
3367 goto end;
3368 }
3369
3370 /* we succeed, we can save the crl in the transaction */
3371
3372 /* if there wasn't a transaction, update the old CA */
3373 if (!crlfile_transaction.old_crlfile_entry) {
3374 crlfile_transaction.old_crlfile_entry = appctx->ctx.ssl.old_crlfile_entry;
3375 crlfile_transaction.path = appctx->ctx.ssl.path;
3376 err = memprintf(&err, "transaction created for CA %s!\n", crlfile_transaction.path);
3377 } else {
3378 err = memprintf(&err, "transaction updated for CA %s!\n", crlfile_transaction.path);
3379 }
3380
3381 /* free the previous CRL file if there was a transaction */
3382 ssl_store_delete_cafile_entry(crlfile_transaction.new_crlfile_entry);
3383
3384 crlfile_transaction.new_crlfile_entry = appctx->ctx.ssl.new_crlfile_entry;
3385
3386 /* creates the SNI ctxs later in the IO handler */
3387
3388end:
3389 free_trash_chunk(buf);
3390
3391 if (errcode & ERR_CODE) {
3392 ssl_store_delete_cafile_entry(appctx->ctx.ssl.new_crlfile_entry);
3393 appctx->ctx.ssl.new_crlfile_entry = NULL;
3394 appctx->ctx.ssl.old_crlfile_entry = NULL;
3395
Tim Duesterhus025b93e2021-11-04 21:03:52 +01003396 ha_free(&appctx->ctx.ssl.path);
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003397
3398 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3399 return cli_dynerr(appctx, memprintf(&err, "%sCan't update %s!\n", err ? err : "", args[3]));
3400 } else {
3401
3402 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3403 return cli_dynmsg(appctx, LOG_NOTICE, err);
3404 }
3405}
3406
3407/* Parsing function of 'commit ssl crl-file' */
3408static int cli_parse_commit_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
3409{
3410 char *err = NULL;
3411
3412 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
3413 return 1;
3414
3415 if (!*args[3])
3416 return cli_err(appctx, "'commit ssl ca-file expects a filename\n");
3417
3418 /* The operations on the CKCH architecture are locked so we can
3419 * manipulate ckch_store and ckch_inst */
3420 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3421 return cli_err(appctx, "Can't commit the CRL file!\nOperations on certificates are currently locked!\n");
3422
3423 if (!crlfile_transaction.path) {
3424 memprintf(&err, "No ongoing transaction! !\n");
3425 goto error;
3426 }
3427
3428 if (strcmp(crlfile_transaction.path, args[3]) != 0) {
3429 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", crlfile_transaction.path, args[3]);
3430 goto error;
3431 }
3432 /* init the appctx structure */
3433 appctx->st2 = SETCERT_ST_INIT;
3434 appctx->ctx.ssl.next_ckchi = NULL;
3435 appctx->ctx.ssl.old_crlfile_entry = crlfile_transaction.old_crlfile_entry;
3436 appctx->ctx.ssl.new_crlfile_entry = crlfile_transaction.new_crlfile_entry;
3437 appctx->ctx.ssl.cafile_type = CAFILE_CRL;
3438
3439 return 0;
3440
3441error:
3442
3443 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3444 err = memprintf(&err, "%sCan't commit %s!\n", err ? err : "", args[3]);
3445
3446 return cli_dynerr(appctx, err);
3447}
3448
3449
3450/* release function of the `commit ssl crl-file' command, free things and unlock the spinlock */
3451static void cli_release_commit_crlfile(struct appctx *appctx)
3452{
3453 if (appctx->st2 != SETCERT_ST_FIN) {
3454 struct cafile_entry *new_crlfile_entry = appctx->ctx.ssl.new_crlfile_entry;
3455
3456 /* Remove the uncommitted cafile_entry from the tree. */
3457 ebmb_delete(&new_crlfile_entry->node);
3458 ssl_store_delete_cafile_entry(new_crlfile_entry);
3459 }
3460 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3461}
3462
Remi Tricot-Le Breton720e3b92021-04-26 11:00:42 +02003463/* parsing function of 'del ssl crl-file' */
3464static int cli_parse_del_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
3465{
3466 struct cafile_entry *cafile_entry;
3467 char *err = NULL;
3468 char *filename;
3469
3470 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
3471 return 1;
3472
3473 if (!*args[3])
3474 return cli_err(appctx, "'del ssl crl-file' expects a CRL file name\n");
3475
3476 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3477 return cli_err(appctx, "Can't delete the CRL file!\nOperations on certificates are currently locked!\n");
3478
3479 filename = args[3];
3480
3481 cafile_entry = ssl_store_get_cafile_entry(filename, 0);
3482 if (!cafile_entry) {
3483 memprintf(&err, "CRL file '%s' doesn't exist!\n", filename);
3484 goto error;
3485 }
3486 if (cafile_entry->type != CAFILE_CRL) {
3487 memprintf(&err, "'del ssl crl-file' does not work on CA files!\n");
3488 goto error;
3489 }
3490
3491 if (!LIST_ISEMPTY(&cafile_entry->ckch_inst_link)) {
3492 memprintf(&err, "CRL file '%s' in use, can't be deleted!\n", filename);
3493 goto error;
3494 }
3495
3496 /* Remove the cafile_entry from the tree */
3497 ebmb_delete(&cafile_entry->node);
3498 ssl_store_delete_cafile_entry(cafile_entry);
3499
3500 memprintf(&err, "CRL file '%s' deleted!\n", filename);
3501
3502 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3503 return cli_dynmsg(appctx, LOG_NOTICE, err);
3504
3505error:
3506 memprintf(&err, "Can't remove the CRL file: %s\n", err ? err : "");
3507 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3508 return cli_dynerr(appctx, err);
3509}
3510
Remi Tricot-Le Bretoneef8e7b2021-04-20 17:42:02 +02003511/* parsing function of 'abort ssl crl-file' */
3512static int cli_parse_abort_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
3513{
3514 char *err = NULL;
3515
3516 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
3517 return 1;
3518
3519 if (!*args[3])
3520 return cli_err(appctx, "'abort ssl crl-file' expects a filename\n");
3521
3522 /* The operations on the CKCH architecture are locked so we can
3523 * manipulate ckch_store and ckch_inst */
3524 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3525 return cli_err(appctx, "Can't abort!\nOperations on certificates are currently locked!\n");
3526
3527 if (!crlfile_transaction.path) {
3528 memprintf(&err, "No ongoing transaction!\n");
3529 goto error;
3530 }
3531
3532 if (strcmp(crlfile_transaction.path, args[3]) != 0) {
3533 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to abort a transaction for '%s'\n", crlfile_transaction.path, args[3]);
3534 goto error;
3535 }
3536
3537 /* Only free the uncommitted cafile_entry here, because the SNI and instances were not generated yet */
3538 ssl_store_delete_cafile_entry(crlfile_transaction.new_crlfile_entry);
3539 crlfile_transaction.new_crlfile_entry = NULL;
3540 crlfile_transaction.old_crlfile_entry = NULL;
3541 ha_free(&crlfile_transaction.path);
3542
3543 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3544
3545 err = memprintf(&err, "Transaction aborted for certificate '%s'!\n", args[3]);
3546 return cli_dynmsg(appctx, LOG_NOTICE, err);
3547
3548error:
3549 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3550
3551 return cli_dynerr(appctx, err);
3552}
3553
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003554
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003555/*
3556 * Display a Certificate Resignation List's information.
3557 * The information displayed is inspired by the output of 'openssl crl -in
3558 * crl.pem -text'.
3559 * Returns 0 in case of success.
3560 */
3561static int show_crl_detail(X509_CRL *crl, struct buffer *out)
3562{
3563 BIO *bio = NULL;
3564 struct buffer *tmp = alloc_trash_chunk();
3565 long version;
3566 X509_NAME *issuer;
3567 int write = -1;
3568 STACK_OF(X509_REVOKED) *rev = NULL;
3569 X509_REVOKED *rev_entry = NULL;
3570 int i;
3571
3572 if (!tmp)
3573 return -1;
3574
3575 if ((bio = BIO_new(BIO_s_mem())) == NULL)
3576 goto end;
3577
3578 /* Version (as displayed by 'openssl crl') */
3579 version = X509_CRL_get_version(crl);
3580 chunk_appendf(out, "Version %ld\n", version + 1);
3581
3582 /* Signature Algorithm */
3583 chunk_appendf(out, "Signature Algorithm: %s\n", OBJ_nid2ln(X509_CRL_get_signature_nid(crl)));
3584
3585 /* Issuer */
3586 chunk_appendf(out, "Issuer: ");
3587 if ((issuer = X509_CRL_get_issuer(crl)) == NULL)
3588 goto end;
3589 if ((ssl_sock_get_dn_oneline(issuer, tmp)) == -1)
3590 goto end;
3591 *(tmp->area + tmp->data) = '\0';
3592 chunk_appendf(out, "%s\n", tmp->area);
3593
3594 /* Last Update */
3595 chunk_appendf(out, "Last Update: ");
3596 chunk_reset(tmp);
Remi Tricot-Le Bretond75b99e2021-05-17 11:45:55 +02003597 if (BIO_reset(bio) == -1)
3598 goto end;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003599 if (ASN1_TIME_print(bio, X509_CRL_get0_lastUpdate(crl)) == 0)
3600 goto end;
3601 write = BIO_read(bio, tmp->area, tmp->size-1);
3602 tmp->area[write] = '\0';
3603 chunk_appendf(out, "%s\n", tmp->area);
3604
3605
3606 /* Next Update */
3607 chunk_appendf(out, "Next Update: ");
3608 chunk_reset(tmp);
Remi Tricot-Le Bretond75b99e2021-05-17 11:45:55 +02003609 if (BIO_reset(bio) == -1)
3610 goto end;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003611 if (ASN1_TIME_print(bio, X509_CRL_get0_nextUpdate(crl)) == 0)
3612 goto end;
3613 write = BIO_read(bio, tmp->area, tmp->size-1);
3614 tmp->area[write] = '\0';
3615 chunk_appendf(out, "%s\n", tmp->area);
3616
3617
3618 /* Revoked Certificates */
3619 rev = X509_CRL_get_REVOKED(crl);
3620 if (sk_X509_REVOKED_num(rev) > 0)
3621 chunk_appendf(out, "Revoked Certificates:\n");
3622 else
3623 chunk_appendf(out, "No Revoked Certificates.\n");
3624
3625 for (i = 0; i < sk_X509_REVOKED_num(rev); i++) {
3626 rev_entry = sk_X509_REVOKED_value(rev, i);
3627
3628 /* Serial Number and Revocation Date */
Remi Tricot-Le Bretond75b99e2021-05-17 11:45:55 +02003629 if (BIO_reset(bio) == -1)
3630 goto end;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003631 BIO_printf(bio , " Serial Number: ");
Remi Tricot-Le Breton18c7d832021-05-17 18:38:34 +02003632 i2a_ASN1_INTEGER(bio, (ASN1_INTEGER*)X509_REVOKED_get0_serialNumber(rev_entry));
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003633 BIO_printf(bio, "\n Revocation Date: ");
Remi Tricot-Le Bretona6b27842021-05-18 10:06:00 +02003634 if (ASN1_TIME_print(bio, X509_REVOKED_get0_revocationDate(rev_entry)) == 0)
3635 goto end;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003636 BIO_printf(bio, "\n");
3637
3638 write = BIO_read(bio, tmp->area, tmp->size-1);
3639 tmp->area[write] = '\0';
3640 chunk_appendf(out, "%s", tmp->area);
3641 }
3642
3643end:
3644 free_trash_chunk(tmp);
3645 if (bio)
3646 BIO_free(bio);
3647
3648 return 0;
3649}
3650
Willy Tarreau821c3b02022-05-04 15:47:39 +02003651/* IO handler of details "show ssl crl-file <filename[:index]>".
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003652 * It uses show_crlfile_ctx and the global
3653 * crlfile_transaction.new_cafile_entry in read-only.
Willy Tarreau821c3b02022-05-04 15:47:39 +02003654 */
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003655static int cli_io_handler_show_crlfile_detail(struct appctx *appctx)
3656{
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003657 struct show_crlfile_ctx *ctx = appctx->svcctx;
Christopher Faulet908628c2022-03-25 16:43:49 +01003658 struct conn_stream *cs = appctx->owner;
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003659 struct cafile_entry *cafile_entry = ctx->cafile_entry;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003660 struct buffer *out = alloc_trash_chunk();
3661 int i;
3662 X509_CRL *crl;
3663 STACK_OF(X509_OBJECT) *objs;
3664 int retval = 0;
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003665 int index = ctx->index;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003666
3667 if (!out)
3668 goto end_no_putchk;
3669
3670 chunk_appendf(out, "Filename: ");
3671 if (cafile_entry == crlfile_transaction.new_crlfile_entry)
3672 chunk_appendf(out, "*");
3673 chunk_appendf(out, "%s\n", cafile_entry->path);
3674
3675 chunk_appendf(out, "Status: ");
3676 if (!cafile_entry->ca_store)
3677 chunk_appendf(out, "Empty\n");
3678 else if (LIST_ISEMPTY(&cafile_entry->ckch_inst_link))
3679 chunk_appendf(out, "Unused\n");
3680 else
3681 chunk_appendf(out, "Used\n");
3682
3683 if (!cafile_entry->ca_store)
3684 goto end;
3685
3686 objs = X509_STORE_get0_objects(cafile_entry->ca_store);
3687 for (i = 0; i < sk_X509_OBJECT_num(objs); i++) {
3688 crl = X509_OBJECT_get0_X509_CRL(sk_X509_OBJECT_value(objs, i));
3689 if (!crl)
3690 continue;
3691
3692 /* CRL indexes start at 1 on the CLI output. */
3693 if (index && index-1 != i)
3694 continue;
3695
Remi Tricot-Le Bretone8041fe2022-04-05 16:44:21 +02003696 chunk_appendf(out, " \nCertificate Revocation List #%d:\n", i+1);
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003697 retval = show_crl_detail(crl, out);
3698 if (retval < 0)
3699 goto end_no_putchk;
3700 else if (retval || index)
3701 goto end;
3702 }
3703
3704end:
Christopher Faulet908628c2022-03-25 16:43:49 +01003705 if (ci_putchk(cs_ic(cs), out) == -1) {
Christopher Fauleta0bdec32022-04-04 07:51:21 +02003706 cs_rx_room_blk(cs);
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003707 goto yield;
3708 }
3709
3710end_no_putchk:
3711 free_trash_chunk(out);
3712 return 1;
3713yield:
3714 free_trash_chunk(out);
3715 return 0; /* should come back */
3716}
3717
Willy Tarreau821c3b02022-05-04 15:47:39 +02003718/* parsing function for 'show ssl crl-file [crlfile[:index]]'.
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003719 * It sets the context to a show_crlfile_ctx, and the global
Willy Tarreau821c3b02022-05-04 15:47:39 +02003720 * cafile_transaction.new_crlfile_entry under the ckch_lock.
3721 */
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003722static int cli_parse_show_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
3723{
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003724 struct show_crlfile_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003725 struct cafile_entry *cafile_entry;
3726 long index = 0;
3727 char *colons;
3728 char *err = NULL;
3729
3730 if (!cli_has_level(appctx, ACCESS_LVL_OPER))
3731 return cli_err(appctx, "Can't allocate memory!\n");
3732
3733 /* The operations on the CKCH architecture are locked so we can
3734 * manipulate ckch_store and ckch_inst */
3735 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3736 return cli_err(appctx, "Can't show!\nOperations on certificates are currently locked!\n");
3737
3738 /* check if there is a certificate to lookup */
3739 if (*args[3]) {
3740
3741 /* Look for an optional index after the CRL file name */
3742 colons = strchr(args[3], ':');
3743 if (colons) {
3744 char *endptr;
3745
3746 index = strtol(colons + 1, &endptr, 10);
3747 /* Indexes start at 1 */
3748 if (colons + 1 == endptr || *endptr != '\0' || index <= 0) {
3749 memprintf(&err, "wrong CRL index after colons in '%s'!", args[3]);
3750 goto error;
3751 }
3752 *colons = '\0';
3753 }
3754
3755 if (*args[3] == '*') {
3756 if (!crlfile_transaction.new_crlfile_entry)
3757 goto error;
3758
3759 cafile_entry = crlfile_transaction.new_crlfile_entry;
3760
3761 if (strcmp(args[3] + 1, cafile_entry->path) != 0)
3762 goto error;
3763
3764 } else {
3765 /* Get the "original" cafile_entry and not the
3766 * uncommitted one if it exists. */
3767 if ((cafile_entry = ssl_store_get_cafile_entry(args[3], 1)) == NULL || cafile_entry->type != CAFILE_CRL)
3768 goto error;
3769 }
3770
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003771 ctx->cafile_entry = cafile_entry;
3772 ctx->index = index;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003773 /* use the IO handler that shows details */
3774 appctx->io_handler = cli_io_handler_show_crlfile_detail;
3775 }
3776
3777 return 0;
3778
3779error:
3780 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3781 if (err)
3782 return cli_dynerr(appctx, err);
3783 return cli_err(appctx, "Can't display the CA file : Not found!\n");
3784}
3785
3786/* IO handler of "show ssl crl-file". The command taking a specific CRL file name
3787 * is managed in cli_io_handler_show_crlfile_detail. */
3788static int cli_io_handler_show_crlfile(struct appctx *appctx)
3789{
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003790 struct show_crlfile_ctx *ctx = appctx->svcctx;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003791 struct buffer *trash = alloc_trash_chunk();
3792 struct ebmb_node *node;
Christopher Faulet908628c2022-03-25 16:43:49 +01003793 struct conn_stream *cs = appctx->owner;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003794 struct cafile_entry *cafile_entry;
3795
3796 if (trash == NULL)
3797 return 1;
3798
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003799 if (!ctx->old_crlfile_entry) {
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003800 if (crlfile_transaction.old_crlfile_entry) {
3801 chunk_appendf(trash, "# transaction\n");
3802 chunk_appendf(trash, "*%s\n", crlfile_transaction.old_crlfile_entry->path);
3803 }
3804 }
3805
3806 /* First time in this io_handler. */
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003807 if (!ctx->cafile_entry) {
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003808 chunk_appendf(trash, "# filename\n");
3809 node = ebmb_first(&cafile_tree);
3810 } else {
3811 /* We yielded during a previous call. */
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003812 node = &ctx->cafile_entry->node;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003813 }
3814
3815 while (node) {
3816 cafile_entry = ebmb_entry(node, struct cafile_entry, node);
3817 if (cafile_entry->type == CAFILE_CRL) {
3818 chunk_appendf(trash, "%s\n", cafile_entry->path);
3819 }
3820
3821 node = ebmb_next(node);
Christopher Faulet908628c2022-03-25 16:43:49 +01003822 if (ci_putchk(cs_ic(cs), trash) == -1) {
Christopher Fauleta0bdec32022-04-04 07:51:21 +02003823 cs_rx_room_blk(cs);
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003824 goto yield;
3825 }
3826 }
3827
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003828 ctx->cafile_entry = NULL;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003829 free_trash_chunk(trash);
3830 return 1;
3831yield:
3832
3833 free_trash_chunk(trash);
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003834 ctx->cafile_entry = cafile_entry;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003835 return 0; /* should come back */
3836}
3837
3838
3839/* release function of the 'show ssl crl-file' command */
3840static void cli_release_show_crlfile(struct appctx *appctx)
3841{
3842 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3843}
3844
3845
William Lallemandee8530c2020-06-23 18:19:42 +02003846void ckch_deinit()
3847{
3848 struct eb_node *node, *next;
3849 struct ckch_store *store;
William Lallemandb0c48272022-04-26 15:44:53 +02003850 struct ebmb_node *canode;
William Lallemandee8530c2020-06-23 18:19:42 +02003851
William Lallemandb0c48272022-04-26 15:44:53 +02003852 /* deinit the ckch stores */
William Lallemandee8530c2020-06-23 18:19:42 +02003853 node = eb_first(&ckchs_tree);
3854 while (node) {
3855 next = eb_next(node);
3856 store = ebmb_entry(node, struct ckch_store, node);
3857 ckch_store_free(store);
3858 node = next;
3859 }
William Lallemandb0c48272022-04-26 15:44:53 +02003860
3861 /* deinit the ca-file store */
3862 canode = ebmb_first(&cafile_tree);
3863 while (canode) {
3864 struct cafile_entry *entry = NULL;
3865
3866 entry = ebmb_entry(canode, struct cafile_entry, node);
3867 canode = ebmb_next(canode);
3868 ssl_store_delete_cafile_entry(entry);
3869 }
William Lallemandee8530c2020-06-23 18:19:42 +02003870}
William Lallemandda8584c2020-05-14 10:14:37 +02003871
3872/* register cli keywords */
3873static struct cli_kw_list cli_kws = {{ },{
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01003874 { { "new", "ssl", "cert", NULL }, "new ssl cert <certfile> : create a new certificate file to be used in a crt-list or a directory", cli_parse_new_cert, NULL, NULL },
3875 { { "set", "ssl", "cert", NULL }, "set ssl cert <certfile> <payload> : replace a certificate file", cli_parse_set_cert, NULL, NULL },
3876 { { "commit", "ssl", "cert", NULL }, "commit ssl cert <certfile> : commit a certificate file", cli_parse_commit_cert, cli_io_handler_commit_cert, cli_release_commit_cert },
3877 { { "abort", "ssl", "cert", NULL }, "abort ssl cert <certfile> : abort a transaction for a certificate file", cli_parse_abort_cert, NULL, NULL },
3878 { { "del", "ssl", "cert", NULL }, "del ssl cert <certfile> : delete an unused certificate file", cli_parse_del_cert, NULL, NULL },
3879 { { "show", "ssl", "cert", NULL }, "show ssl cert [<certfile>] : display the SSL certificates used in memory, or the details of a file", cli_parse_show_cert, cli_io_handler_show_cert, cli_release_show_cert },
3880
Amaury Denoyelleb11ad9e2021-05-21 11:01:10 +02003881 { { "new", "ssl", "ca-file", NULL }, "new ssl ca-file <cafile> : create a new CA file to be used in a crt-list", cli_parse_new_cafile, NULL, NULL },
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01003882 { { "set", "ssl", "ca-file", NULL }, "set ssl ca-file <cafile> <payload> : replace a CA file", cli_parse_set_cafile, NULL, NULL },
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003883 { { "commit", "ssl", "ca-file", NULL }, "commit ssl ca-file <cafile> : commit a CA file", cli_parse_commit_cafile, cli_io_handler_commit_cafile_crlfile, cli_release_commit_cafile },
Remi Tricot-Le Bretond5fd09d2021-03-11 10:22:52 +01003884 { { "abort", "ssl", "ca-file", NULL }, "abort ssl ca-file <cafile> : abort a transaction for a CA file", cli_parse_abort_cafile, NULL, NULL },
Remi Tricot-Le Bretonc3a84772021-03-25 18:13:57 +01003885 { { "del", "ssl", "ca-file", NULL }, "del ssl ca-file <cafile> : delete an unused CA file", cli_parse_del_cafile, NULL, NULL },
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003886 { { "show", "ssl", "ca-file", NULL }, "show ssl ca-file [<cafile>[:<index>]] : display the SSL CA files used in memory, or the details of a <cafile>, or a single certificate of index <index> of a CA file <cafile>", cli_parse_show_cafile, cli_io_handler_show_cafile, cli_release_show_cafile },
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003887
Remi Tricot-Le Breton720e3b92021-04-26 11:00:42 +02003888 { { "new", "ssl", "crl-file", NULL }, "new ssl crlfile <crlfile> : create a new CRL file to be used in a crt-list", cli_parse_new_crlfile, NULL, NULL },
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003889 { { "set", "ssl", "crl-file", NULL }, "set ssl crl-file <crlfile> <payload> : replace a CRL file", cli_parse_set_crlfile, NULL, NULL },
3890 { { "commit", "ssl", "crl-file", NULL },"commit ssl crl-file <crlfile> : commit a CRL file", cli_parse_commit_crlfile, cli_io_handler_commit_cafile_crlfile, cli_release_commit_crlfile },
Remi Tricot-Le Bretoneef8e7b2021-04-20 17:42:02 +02003891 { { "abort", "ssl", "crl-file", NULL }, "abort ssl crl-file <crlfile> : abort a transaction for a CRL file", cli_parse_abort_crlfile, NULL, NULL },
Remi Tricot-Le Breton720e3b92021-04-26 11:00:42 +02003892 { { "del", "ssl", "crl-file", NULL }, "del ssl crl-file <crlfile> : delete an unused CRL file", cli_parse_del_crlfile, NULL, NULL },
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003893 { { "show", "ssl", "crl-file", NULL }, "show ssl crl-file [<crlfile[:<index>>]] : display the SSL CRL files used in memory, or the details of a <crlfile>, or a single CRL of index <index> of CRL file <crlfile>", cli_parse_show_crlfile, cli_io_handler_show_crlfile, cli_release_show_crlfile },
William Lallemandda8584c2020-05-14 10:14:37 +02003894 { { NULL }, NULL, NULL, NULL }
3895}};
3896
3897INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws);
3898