blob: 08472c68f18cc7df406e5f8b6d36228ad4d84c06 [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>
14#include <errno.h>
15#include <fcntl.h>
16#include <stdio.h>
17#include <stdlib.h>
18#include <string.h>
Willy Tarreauaeed4a82020-06-04 22:01:04 +020019#include <syslog.h>
William Lallemand03c331c2020-05-13 10:10:01 +020020#include <unistd.h>
21
22#include <sys/stat.h>
23#include <sys/types.h>
24
Willy Tarreau74f24562021-10-06 17:54:12 +020025#include <import/ebpttree.h>
Willy Tarreaub2551052020-06-09 09:07:15 +020026#include <import/ebsttree.h>
27
Willy Tarreau8d366972020-05-27 16:10:29 +020028#include <haproxy/base64.h>
Willy Tarreauf1d32c42020-06-04 21:07:02 +020029#include <haproxy/channel.h>
Willy Tarreau83487a82020-06-04 20:19:54 +020030#include <haproxy/cli.h>
Willy Tarreau8d366972020-05-27 16:10:29 +020031#include <haproxy/errors.h>
Willy Tarreau47d7f902020-06-04 14:25:47 +020032#include <haproxy/ssl_ckch.h>
Willy Tarreau209108d2020-06-04 20:30:20 +020033#include <haproxy/ssl_sock.h>
Willy Tarreaub2bd8652020-06-04 14:21:22 +020034#include <haproxy/ssl_utils.h>
Willy Tarreau5e539c92020-06-04 20:45:39 +020035#include <haproxy/stream_interface.h>
Willy Tarreau48fbcae2020-06-03 18:09:46 +020036#include <haproxy/tools.h>
William Lallemand03c331c2020-05-13 10:10:01 +020037
William Lallemandda8584c2020-05-14 10:14:37 +020038/* Uncommitted CKCH transaction */
39
40static struct {
41 struct ckch_store *new_ckchs;
42 struct ckch_store *old_ckchs;
43 char *path;
44} ckchs_transaction;
45
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +010046/* Uncommitted CA file transaction */
47
48static struct {
49 struct cafile_entry *old_cafile_entry;
50 struct cafile_entry *new_cafile_entry;
51 char *path;
52} cafile_transaction;
William Lallemandda8584c2020-05-14 10:14:37 +020053
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +020054/* Uncommitted CRL file transaction */
55
56static struct {
57 struct cafile_entry *old_crlfile_entry;
58 struct cafile_entry *new_crlfile_entry;
59 char *path;
60} crlfile_transaction;
61
William Lallemand03c331c2020-05-13 10:10:01 +020062
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +010063
William Lallemand03c331c2020-05-13 10:10:01 +020064/******************** cert_key_and_chain functions *************************
65 * These are the functions that fills a cert_key_and_chain structure. For the
66 * functions filling a SSL_CTX from a cert_key_and_chain, see ssl_sock.c
67 */
68
69/*
70 * Try to parse Signed Certificate Timestamp List structure. This function
71 * makes only basic test if the data seems like SCTL. No signature validation
72 * is performed.
73 */
74static int ssl_sock_parse_sctl(struct buffer *sctl)
75{
76 int ret = 1;
77 int len, pos, sct_len;
78 unsigned char *data;
79
80 if (sctl->data < 2)
81 goto out;
82
83 data = (unsigned char *) sctl->area;
84 len = (data[0] << 8) | data[1];
85
86 if (len + 2 != sctl->data)
87 goto out;
88
89 data = data + 2;
90 pos = 0;
91 while (pos < len) {
92 if (len - pos < 2)
93 goto out;
94
95 sct_len = (data[pos] << 8) | data[pos + 1];
96 if (pos + sct_len + 2 > len)
97 goto out;
98
99 pos += sct_len + 2;
100 }
101
102 ret = 0;
103
104out:
105 return ret;
106}
107
108/* Try to load a sctl from a buffer <buf> if not NULL, or read the file <sctl_path>
109 * It fills the ckch->sctl buffer
110 * return 0 on success or != 0 on failure */
111int ssl_sock_load_sctl_from_file(const char *sctl_path, char *buf, struct cert_key_and_chain *ckch, char **err)
112{
113 int fd = -1;
114 int r = 0;
115 int ret = 1;
116 struct buffer tmp;
117 struct buffer *src;
118 struct buffer *sctl;
119
120 if (buf) {
William Lallemand8d673942021-01-27 14:58:51 +0100121 chunk_initstr(&tmp, buf);
William Lallemand03c331c2020-05-13 10:10:01 +0200122 src = &tmp;
123 } else {
124 fd = open(sctl_path, O_RDONLY);
125 if (fd == -1)
126 goto end;
127
128 trash.data = 0;
129 while (trash.data < trash.size) {
130 r = read(fd, trash.area + trash.data, trash.size - trash.data);
131 if (r < 0) {
132 if (errno == EINTR)
133 continue;
134 goto end;
135 }
136 else if (r == 0) {
137 break;
138 }
139 trash.data += r;
140 }
141 src = &trash;
142 }
143
144 ret = ssl_sock_parse_sctl(src);
145 if (ret)
146 goto end;
147
148 sctl = calloc(1, sizeof(*sctl));
149 if (!chunk_dup(sctl, src)) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100150 ha_free(&sctl);
William Lallemand03c331c2020-05-13 10:10:01 +0200151 goto end;
152 }
153 /* no error, fill ckch with new context, old context must be free */
154 if (ckch->sctl) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100155 ha_free(&ckch->sctl->area);
William Lallemand03c331c2020-05-13 10:10:01 +0200156 free(ckch->sctl);
157 }
158 ckch->sctl = sctl;
159 ret = 0;
160end:
161 if (fd != -1)
162 close(fd);
163
164 return ret;
165}
166
167#if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) || defined OPENSSL_IS_BORINGSSL)
168/*
Ilya Shipitsin46a030c2020-07-05 16:36:08 +0500169 * This function load the OCSP Response in DER format contained in file at
William Lallemand03c331c2020-05-13 10:10:01 +0200170 * path 'ocsp_path' or base64 in a buffer <buf>
171 *
172 * Returns 0 on success, 1 in error case.
173 */
174int ssl_sock_load_ocsp_response_from_file(const char *ocsp_path, char *buf, struct cert_key_and_chain *ckch, char **err)
175{
176 int fd = -1;
177 int r = 0;
178 int ret = 1;
179 struct buffer *ocsp_response;
180 struct buffer *src = NULL;
181
182 if (buf) {
183 int i, j;
184 /* if it's from a buffer it will be base64 */
185
186 /* remove \r and \n from the payload */
187 for (i = 0, j = 0; buf[i]; i++) {
188 if (buf[i] == '\r' || buf[i] == '\n')
189 continue;
190 buf[j++] = buf[i];
191 }
192 buf[j] = 0;
193
194 ret = base64dec(buf, j, trash.area, trash.size);
195 if (ret < 0) {
196 memprintf(err, "Error reading OCSP response in base64 format");
197 goto end;
198 }
199 trash.data = ret;
200 src = &trash;
201 } else {
202 fd = open(ocsp_path, O_RDONLY);
203 if (fd == -1) {
204 memprintf(err, "Error opening OCSP response file");
205 goto end;
206 }
207
208 trash.data = 0;
209 while (trash.data < trash.size) {
210 r = read(fd, trash.area + trash.data, trash.size - trash.data);
211 if (r < 0) {
212 if (errno == EINTR)
213 continue;
214
215 memprintf(err, "Error reading OCSP response from file");
216 goto end;
217 }
218 else if (r == 0) {
219 break;
220 }
221 trash.data += r;
222 }
223 close(fd);
224 fd = -1;
225 src = &trash;
226 }
227
228 ocsp_response = calloc(1, sizeof(*ocsp_response));
229 if (!chunk_dup(ocsp_response, src)) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100230 ha_free(&ocsp_response);
William Lallemand03c331c2020-05-13 10:10:01 +0200231 goto end;
232 }
233 /* no error, fill ckch with new context, old context must be free */
234 if (ckch->ocsp_response) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100235 ha_free(&ckch->ocsp_response->area);
William Lallemand03c331c2020-05-13 10:10:01 +0200236 free(ckch->ocsp_response);
237 }
238 ckch->ocsp_response = ocsp_response;
239 ret = 0;
240end:
241 if (fd != -1)
242 close(fd);
243
244 return ret;
245}
246#endif
247
248/*
249 * Try to load in a ckch every files related to a ckch.
250 * (PEM, sctl, ocsp, issuer etc.)
251 *
252 * This function is only used to load files during the configuration parsing,
253 * it is not used with the CLI.
254 *
255 * This allows us to carry the contents of the file without having to read the
256 * file multiple times. The caller must call
257 * ssl_sock_free_cert_key_and_chain_contents.
258 *
259 * returns:
260 * 0 on Success
261 * 1 on SSL Failure
262 */
263int ssl_sock_load_files_into_ckch(const char *path, struct cert_key_and_chain *ckch, char **err)
264{
William Lallemand8e8581e2020-10-20 17:36:46 +0200265 struct buffer *fp = NULL;
William Lallemand03c331c2020-05-13 10:10:01 +0200266 int ret = 1;
267
268 /* try to load the PEM */
269 if (ssl_sock_load_pem_into_ckch(path, NULL, ckch , err) != 0) {
270 goto end;
271 }
272
William Lallemand8e8581e2020-10-20 17:36:46 +0200273 fp = alloc_trash_chunk();
274 if (!fp) {
275 memprintf(err, "%sCan't allocate memory\n", err && *err ? *err : "");
276 goto end;
277 }
278
279 if (!chunk_strcpy(fp, path) || (b_data(fp) > MAXPATHLEN)) {
280 memprintf(err, "%s '%s' filename too long'.\n",
281 err && *err ? *err : "", fp->area);
282 ret = 1;
283 goto end;
284 }
285
William Lallemand089c1382020-10-23 17:35:12 +0200286 /* remove the ".crt" extension */
William Lallemand8e8581e2020-10-20 17:36:46 +0200287 if (global_ssl.extra_files_noext) {
288 char *ext;
289
290 /* look for the extension */
291 if ((ext = strrchr(fp->area, '.'))) {
William Lallemand8e8581e2020-10-20 17:36:46 +0200292
Tim Duesterhuse5ff1412021-01-02 22:31:53 +0100293 if (strcmp(ext, ".crt") == 0) {
William Lallemand8e8581e2020-10-20 17:36:46 +0200294 *ext = '\0';
William Lallemand089c1382020-10-23 17:35:12 +0200295 fp->data = strlen(fp->area);
296 }
William Lallemand8e8581e2020-10-20 17:36:46 +0200297 }
298
299 }
300
William Lallemand03c331c2020-05-13 10:10:01 +0200301 /* try to load an external private key if it wasn't in the PEM */
302 if ((ckch->key == NULL) && (global_ssl.extra_files & SSL_GF_KEY)) {
William Lallemand03c331c2020-05-13 10:10:01 +0200303 struct stat st;
304
William Lallemand8e8581e2020-10-20 17:36:46 +0200305
306 if (!chunk_strcat(fp, ".key") || (b_data(fp) > MAXPATHLEN)) {
307 memprintf(err, "%s '%s' filename too long'.\n",
308 err && *err ? *err : "", fp->area);
309 ret = 1;
310 goto end;
311 }
312
313 if (stat(fp->area, &st) == 0) {
314 if (ssl_sock_load_key_into_ckch(fp->area, NULL, ckch, err)) {
William Lallemand03c331c2020-05-13 10:10:01 +0200315 memprintf(err, "%s '%s' is present but cannot be read or parsed'.\n",
William Lallemand8e8581e2020-10-20 17:36:46 +0200316 err && *err ? *err : "", fp->area);
William Lallemand03c331c2020-05-13 10:10:01 +0200317 goto end;
318 }
319 }
William Lallemand03c331c2020-05-13 10:10:01 +0200320
William Lallemand8e8581e2020-10-20 17:36:46 +0200321 if (ckch->key == NULL) {
322 memprintf(err, "%sNo Private Key found in '%s'.\n", err && *err ? *err : "", fp->area);
323 goto end;
324 }
325 /* remove the added extension */
326 *(fp->area + fp->data - strlen(".key")) = '\0';
327 b_sub(fp, strlen(".key"));
William Lallemand03c331c2020-05-13 10:10:01 +0200328 }
329
330 if (!X509_check_private_key(ckch->cert, ckch->key)) {
331 memprintf(err, "%sinconsistencies between private key and certificate loaded '%s'.\n",
332 err && *err ? *err : "", path);
333 goto end;
334 }
335
Ilya Shipitsinc47d6762021-02-13 11:45:33 +0500336#ifdef HAVE_SSL_SCTL
William Lallemand03c331c2020-05-13 10:10:01 +0200337 /* try to load the sctl file */
338 if (global_ssl.extra_files & SSL_GF_SCTL) {
William Lallemand03c331c2020-05-13 10:10:01 +0200339 struct stat st;
340
William Lallemand8e8581e2020-10-20 17:36:46 +0200341 if (!chunk_strcat(fp, ".sctl") || b_data(fp) > MAXPATHLEN) {
342 memprintf(err, "%s '%s' filename too long'.\n",
343 err && *err ? *err : "", fp->area);
344 ret = 1;
345 goto end;
346 }
347
348 if (stat(fp->area, &st) == 0) {
349 if (ssl_sock_load_sctl_from_file(fp->area, NULL, ckch, err)) {
William Lallemand03c331c2020-05-13 10:10:01 +0200350 memprintf(err, "%s '%s.sctl' is present but cannot be read or parsed'.\n",
William Lallemand8e8581e2020-10-20 17:36:46 +0200351 err && *err ? *err : "", fp->area);
William Lallemand03c331c2020-05-13 10:10:01 +0200352 ret = 1;
353 goto end;
354 }
355 }
William Lallemand8e8581e2020-10-20 17:36:46 +0200356 /* remove the added extension */
357 *(fp->area + fp->data - strlen(".sctl")) = '\0';
358 b_sub(fp, strlen(".sctl"));
William Lallemand03c331c2020-05-13 10:10:01 +0200359 }
360#endif
361
362 /* try to load an ocsp response file */
363 if (global_ssl.extra_files & SSL_GF_OCSP) {
William Lallemand03c331c2020-05-13 10:10:01 +0200364 struct stat st;
365
William Lallemand8e8581e2020-10-20 17:36:46 +0200366 if (!chunk_strcat(fp, ".ocsp") || b_data(fp) > MAXPATHLEN) {
367 memprintf(err, "%s '%s' filename too long'.\n",
368 err && *err ? *err : "", fp->area);
369 ret = 1;
370 goto end;
371 }
372
373 if (stat(fp->area, &st) == 0) {
374 if (ssl_sock_load_ocsp_response_from_file(fp->area, NULL, ckch, err)) {
William Lallemand03c331c2020-05-13 10:10:01 +0200375 ret = 1;
376 goto end;
377 }
378 }
William Lallemand8e8581e2020-10-20 17:36:46 +0200379 /* remove the added extension */
380 *(fp->area + fp->data - strlen(".ocsp")) = '\0';
381 b_sub(fp, strlen(".ocsp"));
William Lallemand03c331c2020-05-13 10:10:01 +0200382 }
383
384#ifndef OPENSSL_IS_BORINGSSL /* Useless for BoringSSL */
385 if (ckch->ocsp_response && (global_ssl.extra_files & SSL_GF_OCSP_ISSUER)) {
386 /* if no issuer was found, try to load an issuer from the .issuer */
387 if (!ckch->ocsp_issuer) {
388 struct stat st;
William Lallemand8e8581e2020-10-20 17:36:46 +0200389
390 if (!chunk_strcat(fp, ".issuer") || b_data(fp) > MAXPATHLEN) {
391 memprintf(err, "%s '%s' filename too long'.\n",
392 err && *err ? *err : "", fp->area);
393 ret = 1;
394 goto end;
395 }
William Lallemand03c331c2020-05-13 10:10:01 +0200396
William Lallemand8e8581e2020-10-20 17:36:46 +0200397 if (stat(fp->area, &st) == 0) {
398 if (ssl_sock_load_issuer_file_into_ckch(fp->area, NULL, ckch, err)) {
William Lallemand03c331c2020-05-13 10:10:01 +0200399 ret = 1;
400 goto end;
401 }
402
403 if (X509_check_issued(ckch->ocsp_issuer, ckch->cert) != X509_V_OK) {
404 memprintf(err, "%s '%s' is not an issuer'.\n",
William Lallemand8e8581e2020-10-20 17:36:46 +0200405 err && *err ? *err : "", fp->area);
William Lallemand03c331c2020-05-13 10:10:01 +0200406 ret = 1;
407 goto end;
408 }
409 }
William Lallemand8e8581e2020-10-20 17:36:46 +0200410 /* remove the added extension */
411 *(fp->area + fp->data - strlen(".issuer")) = '\0';
412 b_sub(fp, strlen(".issuer"));
William Lallemand03c331c2020-05-13 10:10:01 +0200413 }
414 }
415#endif
416
417 ret = 0;
418
419end:
420
421 ERR_clear_error();
422
423 /* Something went wrong in one of the reads */
424 if (ret != 0)
425 ssl_sock_free_cert_key_and_chain_contents(ckch);
426
William Lallemand8e8581e2020-10-20 17:36:46 +0200427 free_trash_chunk(fp);
428
William Lallemand03c331c2020-05-13 10:10:01 +0200429 return ret;
430}
431
432/*
433 * Try to load a private key file from a <path> or a buffer <buf>
434 *
435 * If it failed you should not attempt to use the ckch but free it.
436 *
437 * Return 0 on success or != 0 on failure
438 */
439int ssl_sock_load_key_into_ckch(const char *path, char *buf, struct cert_key_and_chain *ckch , char **err)
440{
441 BIO *in = NULL;
442 int ret = 1;
443 EVP_PKEY *key = NULL;
444
445 if (buf) {
446 /* reading from a buffer */
447 in = BIO_new_mem_buf(buf, -1);
448 if (in == NULL) {
449 memprintf(err, "%sCan't allocate memory\n", err && *err ? *err : "");
450 goto end;
451 }
452
453 } else {
454 /* reading from a file */
455 in = BIO_new(BIO_s_file());
456 if (in == NULL)
457 goto end;
458
459 if (BIO_read_filename(in, path) <= 0)
460 goto end;
461 }
462
463 /* Read Private Key */
464 key = PEM_read_bio_PrivateKey(in, NULL, NULL, NULL);
465 if (key == NULL) {
466 memprintf(err, "%sunable to load private key from file '%s'.\n",
467 err && *err ? *err : "", path);
468 goto end;
469 }
470
471 ret = 0;
472
473 SWAP(ckch->key, key);
474
475end:
476
477 ERR_clear_error();
478 if (in)
479 BIO_free(in);
480 if (key)
481 EVP_PKEY_free(key);
482
483 return ret;
484}
485
486/*
487 * Try to load a PEM file from a <path> or a buffer <buf>
488 * The PEM must contain at least a Certificate,
489 * It could contain a DH, a certificate chain and a PrivateKey.
490 *
491 * If it failed you should not attempt to use the ckch but free it.
492 *
493 * Return 0 on success or != 0 on failure
494 */
495int ssl_sock_load_pem_into_ckch(const char *path, char *buf, struct cert_key_and_chain *ckch , char **err)
496{
497 BIO *in = NULL;
498 int ret = 1;
499 X509 *ca;
500 X509 *cert = NULL;
501 EVP_PKEY *key = NULL;
Remi Tricot-Le Bretonc76c3c42022-02-11 12:04:55 +0100502 HASSL_DH *dh = NULL;
William Lallemand03c331c2020-05-13 10:10:01 +0200503 STACK_OF(X509) *chain = NULL;
504
505 if (buf) {
506 /* reading from a buffer */
507 in = BIO_new_mem_buf(buf, -1);
508 if (in == NULL) {
509 memprintf(err, "%sCan't allocate memory\n", err && *err ? *err : "");
510 goto end;
511 }
512
513 } else {
514 /* reading from a file */
515 in = BIO_new(BIO_s_file());
516 if (in == NULL) {
517 memprintf(err, "%sCan't allocate memory\n", err && *err ? *err : "");
518 goto end;
519 }
520
521 if (BIO_read_filename(in, path) <= 0) {
522 memprintf(err, "%scannot open the file '%s'.\n",
523 err && *err ? *err : "", path);
524 goto end;
525 }
526 }
527
528 /* Read Private Key */
529 key = PEM_read_bio_PrivateKey(in, NULL, NULL, NULL);
530 /* no need to check for errors here, because the private key could be loaded later */
531
532#ifndef OPENSSL_NO_DH
533 /* Seek back to beginning of file */
534 if (BIO_reset(in) == -1) {
535 memprintf(err, "%san error occurred while reading the file '%s'.\n",
536 err && *err ? *err : "", path);
537 goto end;
538 }
539
Remi Tricot-Le Bretonc76c3c42022-02-11 12:04:55 +0100540 dh = ssl_sock_get_dh_from_bio(in);
541 ERR_clear_error();
William Lallemand03c331c2020-05-13 10:10:01 +0200542 /* no need to return an error there, dh is not mandatory */
543#endif
544
545 /* Seek back to beginning of file */
546 if (BIO_reset(in) == -1) {
547 memprintf(err, "%san error occurred while reading the file '%s'.\n",
548 err && *err ? *err : "", path);
549 goto end;
550 }
551
552 /* Read Certificate */
553 cert = PEM_read_bio_X509_AUX(in, NULL, NULL, NULL);
554 if (cert == NULL) {
555 memprintf(err, "%sunable to load certificate from file '%s'.\n",
556 err && *err ? *err : "", path);
557 goto end;
558 }
559
560 /* Look for a Certificate Chain */
561 while ((ca = PEM_read_bio_X509(in, NULL, NULL, NULL))) {
562 if (chain == NULL)
563 chain = sk_X509_new_null();
564 if (!sk_X509_push(chain, ca)) {
565 X509_free(ca);
566 goto end;
567 }
568 }
569
570 ret = ERR_get_error();
571 if (ret && (ERR_GET_LIB(ret) != ERR_LIB_PEM && ERR_GET_REASON(ret) != PEM_R_NO_START_LINE)) {
572 memprintf(err, "%sunable to load certificate chain from file '%s'.\n",
573 err && *err ? *err : "", path);
574 goto end;
575 }
576
577 /* once it loaded the PEM, it should remove everything else in the ckch */
578 if (ckch->ocsp_response) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100579 ha_free(&ckch->ocsp_response->area);
580 ha_free(&ckch->ocsp_response);
William Lallemand03c331c2020-05-13 10:10:01 +0200581 }
582
583 if (ckch->sctl) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100584 ha_free(&ckch->sctl->area);
585 ha_free(&ckch->sctl);
William Lallemand03c331c2020-05-13 10:10:01 +0200586 }
587
588 if (ckch->ocsp_issuer) {
589 X509_free(ckch->ocsp_issuer);
590 ckch->ocsp_issuer = NULL;
591 }
592
593 /* no error, fill ckch with new context, old context will be free at end: */
594 SWAP(ckch->key, key);
595 SWAP(ckch->dh, dh);
596 SWAP(ckch->cert, cert);
597 SWAP(ckch->chain, chain);
598
599 ret = 0;
600
601end:
602
603 ERR_clear_error();
604 if (in)
605 BIO_free(in);
606 if (key)
607 EVP_PKEY_free(key);
608 if (dh)
Remi Tricot-Le Bretonc76c3c42022-02-11 12:04:55 +0100609 HASSL_DH_free(dh);
William Lallemand03c331c2020-05-13 10:10:01 +0200610 if (cert)
611 X509_free(cert);
612 if (chain)
613 sk_X509_pop_free(chain, X509_free);
614
615 return ret;
616}
617
618/* Frees the contents of a cert_key_and_chain
619 */
620void ssl_sock_free_cert_key_and_chain_contents(struct cert_key_and_chain *ckch)
621{
622 if (!ckch)
623 return;
624
625 /* Free the certificate and set pointer to NULL */
626 if (ckch->cert)
627 X509_free(ckch->cert);
628 ckch->cert = NULL;
629
630 /* Free the key and set pointer to NULL */
631 if (ckch->key)
632 EVP_PKEY_free(ckch->key);
633 ckch->key = NULL;
634
635 /* Free each certificate in the chain */
636 if (ckch->chain)
637 sk_X509_pop_free(ckch->chain, X509_free);
638 ckch->chain = NULL;
639
640 if (ckch->dh)
Remi Tricot-Le Bretonc76c3c42022-02-11 12:04:55 +0100641 HASSL_DH_free(ckch->dh);
William Lallemand03c331c2020-05-13 10:10:01 +0200642 ckch->dh = NULL;
643
644 if (ckch->sctl) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100645 ha_free(&ckch->sctl->area);
646 ha_free(&ckch->sctl);
William Lallemand03c331c2020-05-13 10:10:01 +0200647 }
648
649 if (ckch->ocsp_response) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100650 ha_free(&ckch->ocsp_response->area);
651 ha_free(&ckch->ocsp_response);
William Lallemand03c331c2020-05-13 10:10:01 +0200652 }
653
654 if (ckch->ocsp_issuer)
655 X509_free(ckch->ocsp_issuer);
656 ckch->ocsp_issuer = NULL;
657}
658
659/*
660 *
661 * This function copy a cert_key_and_chain in memory
662 *
663 * It's used to try to apply changes on a ckch before committing them, because
664 * most of the time it's not possible to revert those changes
665 *
666 * Return a the dst or NULL
667 */
668struct cert_key_and_chain *ssl_sock_copy_cert_key_and_chain(struct cert_key_and_chain *src,
669 struct cert_key_and_chain *dst)
670{
William Lallemand6c096142021-02-23 14:45:45 +0100671 if (!src || !dst)
672 return NULL;
673
William Lallemand03c331c2020-05-13 10:10:01 +0200674 if (src->cert) {
675 dst->cert = src->cert;
676 X509_up_ref(src->cert);
677 }
678
679 if (src->key) {
680 dst->key = src->key;
681 EVP_PKEY_up_ref(src->key);
682 }
683
684 if (src->chain) {
685 dst->chain = X509_chain_up_ref(src->chain);
686 }
687
688 if (src->dh) {
Remi Tricot-Le Bretonc76c3c42022-02-11 12:04:55 +0100689 HASSL_DH_up_ref(src->dh);
William Lallemand03c331c2020-05-13 10:10:01 +0200690 dst->dh = src->dh;
691 }
692
693 if (src->sctl) {
694 struct buffer *sctl;
695
696 sctl = calloc(1, sizeof(*sctl));
697 if (!chunk_dup(sctl, src->sctl)) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100698 ha_free(&sctl);
William Lallemand03c331c2020-05-13 10:10:01 +0200699 goto error;
700 }
701 dst->sctl = sctl;
702 }
703
704 if (src->ocsp_response) {
705 struct buffer *ocsp_response;
706
707 ocsp_response = calloc(1, sizeof(*ocsp_response));
708 if (!chunk_dup(ocsp_response, src->ocsp_response)) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100709 ha_free(&ocsp_response);
William Lallemand03c331c2020-05-13 10:10:01 +0200710 goto error;
711 }
712 dst->ocsp_response = ocsp_response;
713 }
714
715 if (src->ocsp_issuer) {
716 X509_up_ref(src->ocsp_issuer);
717 dst->ocsp_issuer = src->ocsp_issuer;
718 }
719
720 return dst;
721
722error:
723
724 /* free everything */
725 ssl_sock_free_cert_key_and_chain_contents(dst);
726
727 return NULL;
728}
729
730/*
731 * return 0 on success or != 0 on failure
732 */
733int ssl_sock_load_issuer_file_into_ckch(const char *path, char *buf, struct cert_key_and_chain *ckch, char **err)
734{
735 int ret = 1;
736 BIO *in = NULL;
737 X509 *issuer;
738
739 if (buf) {
740 /* reading from a buffer */
741 in = BIO_new_mem_buf(buf, -1);
742 if (in == NULL) {
743 memprintf(err, "%sCan't allocate memory\n", err && *err ? *err : "");
744 goto end;
745 }
746
747 } else {
748 /* reading from a file */
749 in = BIO_new(BIO_s_file());
750 if (in == NULL)
751 goto end;
752
753 if (BIO_read_filename(in, path) <= 0)
754 goto end;
755 }
756
757 issuer = PEM_read_bio_X509_AUX(in, NULL, NULL, NULL);
758 if (!issuer) {
759 memprintf(err, "%s'%s' cannot be read or parsed'.\n",
760 err && *err ? *err : "", path);
761 goto end;
762 }
763 /* no error, fill ckch with new context, old context must be free */
764 if (ckch->ocsp_issuer)
765 X509_free(ckch->ocsp_issuer);
766 ckch->ocsp_issuer = issuer;
767 ret = 0;
768
769end:
770
771 ERR_clear_error();
772 if (in)
773 BIO_free(in);
774
775 return ret;
776}
777
778/******************** ckch_store functions ***********************************
779 * The ckch_store is a structure used to cache and index the SSL files used in
780 * configuration
781 */
782
783/*
784 * Free a ckch_store, its ckch, its instances and remove it from the ebtree
785 */
786void ckch_store_free(struct ckch_store *store)
787{
788 struct ckch_inst *inst, *inst_s;
789
790 if (!store)
791 return;
792
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200793 ssl_sock_free_cert_key_and_chain_contents(store->ckch);
William Lallemand03c331c2020-05-13 10:10:01 +0200794
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100795 ha_free(&store->ckch);
William Lallemand03c331c2020-05-13 10:10:01 +0200796
797 list_for_each_entry_safe(inst, inst_s, &store->ckch_inst, by_ckchs) {
798 ckch_inst_free(inst);
799 }
800 ebmb_delete(&store->node);
801 free(store);
802}
803
804/*
805 * create and initialize a ckch_store
806 * <path> is the key name
807 * <nmemb> is the number of store->ckch objects to allocate
808 *
809 * Return a ckch_store or NULL upon failure.
810 */
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200811struct ckch_store *ckch_store_new(const char *filename)
William Lallemand03c331c2020-05-13 10:10:01 +0200812{
813 struct ckch_store *store;
814 int pathlen;
815
816 pathlen = strlen(filename);
817 store = calloc(1, sizeof(*store) + pathlen + 1);
818 if (!store)
819 return NULL;
820
William Lallemand03c331c2020-05-13 10:10:01 +0200821 memcpy(store->path, filename, pathlen + 1);
822
823 LIST_INIT(&store->ckch_inst);
824 LIST_INIT(&store->crtlist_entry);
825
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200826 store->ckch = calloc(1, sizeof(*store->ckch));
William Lallemand03c331c2020-05-13 10:10:01 +0200827 if (!store->ckch)
828 goto error;
829
830 return store;
831error:
832 ckch_store_free(store);
833 return NULL;
834}
835
836/* allocate and duplicate a ckch_store
837 * Return a new ckch_store or NULL */
838struct ckch_store *ckchs_dup(const struct ckch_store *src)
839{
840 struct ckch_store *dst;
841
William Lallemand6c096142021-02-23 14:45:45 +0100842 if (!src)
843 return NULL;
844
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200845 dst = ckch_store_new(src->path);
Eric Salama6ac61e32021-02-23 16:50:57 +0100846 if (!dst)
847 return NULL;
William Lallemand03c331c2020-05-13 10:10:01 +0200848
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200849 if (!ssl_sock_copy_cert_key_and_chain(src->ckch, dst->ckch))
850 goto error;
William Lallemand03c331c2020-05-13 10:10:01 +0200851
852 return dst;
853
854error:
855 ckch_store_free(dst);
856
857 return NULL;
858}
859
860/*
861 * lookup a path into the ckchs tree.
862 */
863struct ckch_store *ckchs_lookup(char *path)
864{
865 struct ebmb_node *eb;
866
867 eb = ebst_lookup(&ckchs_tree, path);
868 if (!eb)
869 return NULL;
870
871 return ebmb_entry(eb, struct ckch_store, node);
872}
873
874/*
875 * This function allocate a ckch_store and populate it with certificates from files.
876 */
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200877struct ckch_store *ckchs_load_cert_file(char *path, char **err)
William Lallemand03c331c2020-05-13 10:10:01 +0200878{
879 struct ckch_store *ckchs;
880
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200881 ckchs = ckch_store_new(path);
William Lallemand03c331c2020-05-13 10:10:01 +0200882 if (!ckchs) {
883 memprintf(err, "%sunable to allocate memory.\n", err && *err ? *err : "");
884 goto end;
885 }
William Lallemand03c331c2020-05-13 10:10:01 +0200886
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200887 if (ssl_sock_load_files_into_ckch(path, ckchs->ckch, err) == 1)
888 goto end;
William Lallemand03c331c2020-05-13 10:10:01 +0200889
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200890 /* insert into the ckchs tree */
891 memcpy(ckchs->path, path, strlen(path) + 1);
892 ebst_insert(&ckchs_tree, &ckchs->node);
William Lallemand03c331c2020-05-13 10:10:01 +0200893 return ckchs;
894
895end:
896 ckch_store_free(ckchs);
897
898 return NULL;
899}
900
William Lallemandfa1d8b42020-05-13 15:46:10 +0200901
902/******************** ckch_inst functions ******************************/
903
904/* unlink a ckch_inst, free all SNIs, free the ckch_inst */
905/* The caller must use the lock of the bind_conf if used with inserted SNIs */
906void ckch_inst_free(struct ckch_inst *inst)
907{
908 struct sni_ctx *sni, *sni_s;
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +0100909 struct ckch_inst_link_ref *link_ref, *link_ref_s;
William Lallemandfa1d8b42020-05-13 15:46:10 +0200910
911 if (inst == NULL)
912 return;
913
914 list_for_each_entry_safe(sni, sni_s, &inst->sni_ctx, by_ckch_inst) {
915 SSL_CTX_free(sni->ctx);
Willy Tarreau2b718102021-04-21 07:32:39 +0200916 LIST_DELETE(&sni->by_ckch_inst);
William Lallemandfa1d8b42020-05-13 15:46:10 +0200917 ebmb_delete(&sni->name);
918 free(sni);
919 }
Remi Tricot-Le Bretonf3eedfe2021-01-25 17:19:44 +0100920 SSL_CTX_free(inst->ctx);
921 inst->ctx = NULL;
Willy Tarreau2b718102021-04-21 07:32:39 +0200922 LIST_DELETE(&inst->by_ckchs);
923 LIST_DELETE(&inst->by_crtlist_entry);
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +0100924
925 list_for_each_entry_safe(link_ref, link_ref_s, &inst->cafile_link_refs, list) {
926 LIST_DELETE(&link_ref->link->list);
927 LIST_DELETE(&link_ref->list);
928 free(link_ref);
929 }
930
William Lallemandfa1d8b42020-05-13 15:46:10 +0200931 free(inst);
932}
933
934/* Alloc and init a ckch_inst */
935struct ckch_inst *ckch_inst_new()
936{
937 struct ckch_inst *ckch_inst;
938
939 ckch_inst = calloc(1, sizeof *ckch_inst);
940 if (!ckch_inst)
941 return NULL;
942
943 LIST_INIT(&ckch_inst->sni_ctx);
944 LIST_INIT(&ckch_inst->by_ckchs);
945 LIST_INIT(&ckch_inst->by_crtlist_entry);
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +0100946 LIST_INIT(&ckch_inst->cafile_link_refs);
William Lallemandfa1d8b42020-05-13 15:46:10 +0200947
948 return ckch_inst;
949}
950
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +0200951
952/******************** ssl_store functions ******************************/
Remi Tricot-Le Breton9f0c9362021-02-19 15:06:28 +0100953struct eb_root cafile_tree = EB_ROOT;
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +0200954
Remi Tricot-Le Breton9f0c9362021-02-19 15:06:28 +0100955/*
956 * Returns the cafile_entry found in the cafile_tree indexed by the path 'path'.
957 * If 'oldest_entry' is 1, returns the "original" cafile_entry (since
958 * during a set cafile/commit cafile cycle there might be two entries for any
959 * given path, the original one and the new one set via the CLI but not
960 * committed yet).
961 */
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +0100962struct cafile_entry *ssl_store_get_cafile_entry(char *path, int oldest_entry)
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +0200963{
Remi Tricot-Le Breton9f0c9362021-02-19 15:06:28 +0100964 struct cafile_entry *ca_e = NULL;
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +0200965 struct ebmb_node *eb;
966
967 eb = ebst_lookup(&cafile_tree, path);
Remi Tricot-Le Breton9f0c9362021-02-19 15:06:28 +0100968 while (eb) {
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +0200969 ca_e = ebmb_entry(eb, struct cafile_entry, node);
Remi Tricot-Le Breton9f0c9362021-02-19 15:06:28 +0100970 /* The ebst_lookup in a tree that has duplicates returns the
971 * oldest entry first. If we want the latest entry, we need to
972 * iterate over all the duplicates until we find the last one
973 * (in our case there should never be more than two entries for
974 * any given path). */
975 if (oldest_entry)
976 return ca_e;
977 eb = ebmb_next_dup(eb);
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +0200978 }
Remi Tricot-Le Breton9f0c9362021-02-19 15:06:28 +0100979 return ca_e;
980}
981
Remi Tricot-Le Breton38c999b2021-02-23 16:28:43 +0100982int ssl_store_add_uncommitted_cafile_entry(struct cafile_entry *entry)
983{
984 return (ebst_insert(&cafile_tree, &entry->node) != &entry->node);
985}
986
Remi Tricot-Le Breton9f0c9362021-02-19 15:06:28 +0100987X509_STORE* ssl_store_get0_locations_file(char *path)
988{
989 struct cafile_entry *ca_e = ssl_store_get_cafile_entry(path, 0);
990
991 if (ca_e)
992 return ca_e->ca_store;
993
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +0200994 return NULL;
995}
996
Remi Tricot-Le Breton5daff3c2021-02-22 15:54:55 +0100997/* Create a cafile_entry object, without adding it to the cafile_tree. */
Remi Tricot-Le Breton0bb48242021-04-16 17:59:23 +0200998struct 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 +0100999{
1000 struct cafile_entry *ca_e;
1001 int pathlen;
1002
1003 pathlen = strlen(path);
1004
1005 ca_e = calloc(1, sizeof(*ca_e) + pathlen + 1);
1006 if (ca_e) {
1007 memcpy(ca_e->path, path, pathlen + 1);
1008 ca_e->ca_store = store;
Remi Tricot-Le Breton0bb48242021-04-16 17:59:23 +02001009 ca_e->type = type;
Remi Tricot-Le Breton5daff3c2021-02-22 15:54:55 +01001010 LIST_INIT(&ca_e->ckch_inst_link);
1011 }
1012 return ca_e;
1013}
1014
1015/* Delete a cafile_entry. The caller is responsible from removing this entry
1016 * from the cafile_tree first if is was previously added into it. */
1017void ssl_store_delete_cafile_entry(struct cafile_entry *ca_e)
1018{
1019 struct ckch_inst_link *link, *link_s;
1020 if (!ca_e)
1021 return;
1022
1023 X509_STORE_free(ca_e->ca_store);
1024
1025 list_for_each_entry_safe(link, link_s, &ca_e->ckch_inst_link, list) {
1026 struct ckch_inst *inst = link->ckch_inst;
1027 struct ckch_inst_link_ref *link_ref, *link_ref_s;
1028 list_for_each_entry_safe(link_ref, link_ref_s, &inst->cafile_link_refs, list) {
1029 if (link_ref->link == link) {
1030 LIST_DELETE(&link_ref->list);
1031 free(link_ref);
1032 break;
1033 }
1034 }
1035 LIST_DELETE(&link->list);
1036 free(link);
1037 }
1038
1039 free(ca_e);
1040}
1041
Remi Tricot-Le Breton383fb142021-02-22 18:26:14 +01001042/*
1043 * Build a cafile_entry out of a buffer instead of out of a file.
1044 * This function is used when the "commit ssl ca-file" cli command is used.
1045 * It can parse CERTIFICATE sections as well as CRL ones.
1046 * Returns 0 in case of success, 1 otherwise.
1047 */
1048int ssl_store_load_ca_from_buf(struct cafile_entry *ca_e, char *cert_buf)
1049{
1050 int retval = 0;
1051
1052 if (!ca_e)
1053 return 1;
1054
1055 if (!ca_e->ca_store) {
1056 ca_e->ca_store = X509_STORE_new();
1057 if (ca_e->ca_store) {
1058 BIO *bio = BIO_new_mem_buf(cert_buf, strlen(cert_buf));
1059 if (bio) {
1060 X509_INFO *info;
1061 int i;
1062 STACK_OF(X509_INFO) *infos = PEM_X509_INFO_read_bio(bio, NULL, NULL, NULL);
1063 if (!infos)
1064 {
1065 BIO_free(bio);
1066 return 1;
1067 }
1068
1069 for (i = 0; i < sk_X509_INFO_num(infos) && !retval; i++) {
1070 info = sk_X509_INFO_value(infos, i);
1071 /* X509_STORE_add_cert and X509_STORE_add_crl return 1 on success */
1072 if (info->x509) {
1073 retval = !X509_STORE_add_cert(ca_e->ca_store, info->x509);
1074 }
1075 if (!retval && info->crl) {
1076 retval = !X509_STORE_add_crl(ca_e->ca_store, info->crl);
1077 }
1078 }
1079 retval = retval || (i != sk_X509_INFO_num(infos));
1080
1081 /* Cleanup */
1082 sk_X509_INFO_pop_free(infos, X509_INFO_free);
1083 BIO_free(bio);
1084 }
1085 }
1086 }
1087
1088 return retval;
1089}
1090
Remi Tricot-Le Breton0bb48242021-04-16 17:59:23 +02001091int ssl_store_load_locations_file(char *path, int create_if_none, enum cafile_type type)
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001092{
1093 X509_STORE *store = ssl_store_get0_locations_file(path);
1094
1095 /* If this function is called by the CLI, we should not call the
1096 * X509_STORE_load_locations function because it performs forbidden disk
1097 * accesses. */
1098 if (!store && create_if_none) {
1099 struct cafile_entry *ca_e;
1100 store = X509_STORE_new();
1101 if (X509_STORE_load_locations(store, path, NULL)) {
Remi Tricot-Le Breton0bb48242021-04-16 17:59:23 +02001102 ca_e = ssl_store_create_cafile_entry(path, store, type);
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001103 if (ca_e) {
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001104 ebst_insert(&cafile_tree, &ca_e->node);
1105 }
1106 } else {
1107 X509_STORE_free(store);
1108 store = NULL;
1109 }
1110 }
1111 return (store != NULL);
1112}
1113
1114
William Lallemandda8584c2020-05-14 10:14:37 +02001115/*************************** CLI commands ***********************/
1116
1117/* Type of SSL payloads that can be updated over the CLI */
1118
1119enum {
1120 CERT_TYPE_PEM = 0,
1121 CERT_TYPE_KEY,
1122#if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) || defined OPENSSL_IS_BORINGSSL)
1123 CERT_TYPE_OCSP,
1124#endif
1125 CERT_TYPE_ISSUER,
Ilya Shipitsinc47d6762021-02-13 11:45:33 +05001126#ifdef HAVE_SSL_SCTL
William Lallemandda8584c2020-05-14 10:14:37 +02001127 CERT_TYPE_SCTL,
1128#endif
1129 CERT_TYPE_MAX,
1130};
1131
1132struct {
1133 const char *ext;
1134 int type;
1135 int (*load)(const char *path, char *payload, struct cert_key_and_chain *ckch, char **err);
1136 /* add a parsing callback */
1137} cert_exts[CERT_TYPE_MAX+1] = {
1138 [CERT_TYPE_PEM] = { "", CERT_TYPE_PEM, &ssl_sock_load_pem_into_ckch }, /* default mode, no extensions */
1139 [CERT_TYPE_KEY] = { "key", CERT_TYPE_KEY, &ssl_sock_load_key_into_ckch },
1140#if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) || defined OPENSSL_IS_BORINGSSL)
1141 [CERT_TYPE_OCSP] = { "ocsp", CERT_TYPE_OCSP, &ssl_sock_load_ocsp_response_from_file },
1142#endif
Ilya Shipitsinc47d6762021-02-13 11:45:33 +05001143#ifdef HAVE_SSL_SCTL
William Lallemandda8584c2020-05-14 10:14:37 +02001144 [CERT_TYPE_SCTL] = { "sctl", CERT_TYPE_SCTL, &ssl_sock_load_sctl_from_file },
1145#endif
1146 [CERT_TYPE_ISSUER] = { "issuer", CERT_TYPE_ISSUER, &ssl_sock_load_issuer_file_into_ckch },
1147 [CERT_TYPE_MAX] = { NULL, CERT_TYPE_MAX, NULL },
1148};
1149
1150
1151/* release function of the `show ssl cert' command */
1152static void cli_release_show_cert(struct appctx *appctx)
1153{
1154 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1155}
1156
1157/* IO handler of "show ssl cert <filename>" */
1158static int cli_io_handler_show_cert(struct appctx *appctx)
1159{
1160 struct buffer *trash = alloc_trash_chunk();
1161 struct ebmb_node *node;
Christopher Faulet86e1c332021-12-20 17:09:39 +01001162 struct stream_interface *si = cs_si(appctx->owner);
William Lallemandda8584c2020-05-14 10:14:37 +02001163 struct ckch_store *ckchs;
1164
1165 if (trash == NULL)
1166 return 1;
1167
1168 if (!appctx->ctx.ssl.old_ckchs) {
1169 if (ckchs_transaction.old_ckchs) {
1170 ckchs = ckchs_transaction.old_ckchs;
1171 chunk_appendf(trash, "# transaction\n");
William Lallemand5685ccf2020-09-16 16:12:25 +02001172 chunk_appendf(trash, "*%s\n", ckchs->path);
William Lallemandda8584c2020-05-14 10:14:37 +02001173 }
1174 }
1175
1176 if (!appctx->ctx.cli.p0) {
1177 chunk_appendf(trash, "# filename\n");
1178 node = ebmb_first(&ckchs_tree);
1179 } else {
1180 node = &((struct ckch_store *)appctx->ctx.cli.p0)->node;
1181 }
1182 while (node) {
1183 ckchs = ebmb_entry(node, struct ckch_store, node);
William Lallemand5685ccf2020-09-16 16:12:25 +02001184 chunk_appendf(trash, "%s\n", ckchs->path);
William Lallemandda8584c2020-05-14 10:14:37 +02001185
1186 node = ebmb_next(node);
1187 if (ci_putchk(si_ic(si), trash) == -1) {
1188 si_rx_room_blk(si);
1189 goto yield;
1190 }
1191 }
1192
1193 appctx->ctx.cli.p0 = NULL;
1194 free_trash_chunk(trash);
1195 return 1;
1196yield:
1197
1198 free_trash_chunk(trash);
1199 appctx->ctx.cli.p0 = ckchs;
1200 return 0; /* should come back */
1201}
1202
1203/*
1204 * Extract and format the DNS SAN extensions and copy result into a chuink
1205 * Return 0;
1206 */
1207#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
1208static int ssl_sock_get_san_oneline(X509 *cert, struct buffer *out)
1209{
1210 int i;
1211 char *str;
1212 STACK_OF(GENERAL_NAME) *names = NULL;
1213
1214 names = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
1215 if (names) {
1216 for (i = 0; i < sk_GENERAL_NAME_num(names); i++) {
1217 GENERAL_NAME *name = sk_GENERAL_NAME_value(names, i);
1218 if (i > 0)
1219 chunk_appendf(out, ", ");
1220 if (name->type == GEN_DNS) {
1221 if (ASN1_STRING_to_UTF8((unsigned char **)&str, name->d.dNSName) >= 0) {
1222 chunk_appendf(out, "DNS:%s", str);
1223 OPENSSL_free(str);
1224 }
1225 }
1226 }
1227 sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free);
1228 }
1229 return 0;
1230}
1231#endif
1232
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001233/*
1234 * Build the ckch_inst_link that will be chained in the CA file entry and the
1235 * corresponding ckch_inst_link_ref that will be chained in the ckch instance.
1236 * Return 0 in case of success.
1237 */
1238static int do_chain_inst_and_cafile(struct cafile_entry *cafile_entry, struct ckch_inst *ckch_inst)
1239{
1240 struct ckch_inst_link *new_link;
1241 if (!LIST_ISEMPTY(&cafile_entry->ckch_inst_link)) {
1242 struct ckch_inst_link *link = LIST_ELEM(cafile_entry->ckch_inst_link.n,
1243 typeof(link), list);
1244 /* Do not add multiple references to the same
1245 * instance in a cafile_entry */
1246 if (link->ckch_inst == ckch_inst) {
1247 return 1;
1248 }
1249 }
1250
1251 new_link = calloc(1, sizeof(*new_link));
1252 if (new_link) {
1253 struct ckch_inst_link_ref *new_link_ref = calloc(1, sizeof(*new_link_ref));
1254 if (!new_link_ref) {
1255 free(new_link);
1256 return 1;
1257 }
1258
1259 new_link->ckch_inst = ckch_inst;
1260 new_link_ref->link = new_link;
1261 LIST_INIT(&new_link->list);
1262 LIST_INIT(&new_link_ref->list);
1263
1264 LIST_APPEND(&cafile_entry->ckch_inst_link, &new_link->list);
1265 LIST_APPEND(&ckch_inst->cafile_link_refs, &new_link_ref->list);
1266 }
1267
1268 return 0;
1269}
1270
1271
1272/*
1273 * Link a CA file tree entry to the ckch instance that uses it.
1274 * To determine if and which CA file tree entries need to be linked to the
1275 * instance, we follow the same logic performed in ssl_sock_prepare_ctx when
1276 * processing the verify option.
1277 * This function works for a frontend as well as for a backend, depending on the
1278 * configuration parameters given (bind_conf or server).
1279 */
1280void ckch_inst_add_cafile_link(struct ckch_inst *ckch_inst, struct bind_conf *bind_conf,
1281 struct ssl_bind_conf *ssl_conf, const struct server *srv)
1282{
1283 int verify = SSL_VERIFY_NONE;
1284
1285 if (srv) {
1286
1287 if (global.ssl_server_verify == SSL_SERVER_VERIFY_REQUIRED)
1288 verify = SSL_VERIFY_PEER;
1289 switch (srv->ssl_ctx.verify) {
1290 case SSL_SOCK_VERIFY_NONE:
1291 verify = SSL_VERIFY_NONE;
1292 break;
1293 case SSL_SOCK_VERIFY_REQUIRED:
1294 verify = SSL_VERIFY_PEER;
1295 break;
1296 }
1297 }
1298 else {
1299 switch ((ssl_conf && ssl_conf->verify) ? ssl_conf->verify : bind_conf->ssl_conf.verify) {
1300 case SSL_SOCK_VERIFY_NONE:
1301 verify = SSL_VERIFY_NONE;
1302 break;
1303 case SSL_SOCK_VERIFY_OPTIONAL:
1304 verify = SSL_VERIFY_PEER;
1305 break;
1306 case SSL_SOCK_VERIFY_REQUIRED:
1307 verify = SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
1308 break;
1309 }
1310 }
1311
1312 if (verify & SSL_VERIFY_PEER) {
1313 struct cafile_entry *ca_file_entry = NULL;
1314 struct cafile_entry *ca_verify_file_entry = NULL;
Remi Tricot-Le Bretonf81c70c2021-04-20 16:54:21 +02001315 struct cafile_entry *crl_file_entry = NULL;
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001316 if (srv) {
1317 if (srv->ssl_ctx.ca_file) {
1318 ca_file_entry = ssl_store_get_cafile_entry(srv->ssl_ctx.ca_file, 0);
1319
1320 }
Remi Tricot-Le Bretonf81c70c2021-04-20 16:54:21 +02001321 if (srv->ssl_ctx.crl_file) {
1322 crl_file_entry = ssl_store_get_cafile_entry(srv->ssl_ctx.crl_file, 0);
1323 }
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001324 }
1325 else {
1326 char *ca_file = (ssl_conf && ssl_conf->ca_file) ? ssl_conf->ca_file : bind_conf->ssl_conf.ca_file;
1327 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 +02001328 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 +01001329
1330 if (ca_file)
1331 ca_file_entry = ssl_store_get_cafile_entry(ca_file, 0);
1332 if (ca_verify_file)
1333 ca_verify_file_entry = ssl_store_get_cafile_entry(ca_verify_file, 0);
Remi Tricot-Le Bretonf81c70c2021-04-20 16:54:21 +02001334 if (crl_file)
1335 crl_file_entry = ssl_store_get_cafile_entry(crl_file, 0);
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001336 }
1337
1338 if (ca_file_entry) {
1339 /* If we have a ckch instance that is not already in the
1340 * cafile_entry's list, add it to it. */
1341 if (do_chain_inst_and_cafile(ca_file_entry, ckch_inst))
1342 return;
1343
1344 }
1345 if (ca_verify_file_entry && (ca_file_entry != ca_verify_file_entry)) {
1346 /* If we have a ckch instance that is not already in the
1347 * cafile_entry's list, add it to it. */
1348 if (do_chain_inst_and_cafile(ca_verify_file_entry, ckch_inst))
1349 return;
1350 }
Remi Tricot-Le Bretonf81c70c2021-04-20 16:54:21 +02001351 if (crl_file_entry) {
1352 /* If we have a ckch instance that is not already in the
1353 * cafile_entry's list, add it to it. */
1354 if (do_chain_inst_and_cafile(crl_file_entry, ckch_inst))
1355 return;
1356 }
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001357 }
1358}
1359
William Lallemandda8584c2020-05-14 10:14:37 +02001360
1361
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001362static int show_cert_detail(X509 *cert, STACK_OF(X509) *chain, struct buffer *out)
William Lallemandda8584c2020-05-14 10:14:37 +02001363{
William Lallemandda8584c2020-05-14 10:14:37 +02001364 BIO *bio = NULL;
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001365 struct buffer *tmp = alloc_trash_chunk();
William Lallemandda8584c2020-05-14 10:14:37 +02001366 int i;
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001367 int write = -1;
1368 unsigned int len = 0;
1369 X509_NAME *name = NULL;
William Lallemandda8584c2020-05-14 10:14:37 +02001370
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001371 if (!tmp)
1372 return -1;
William Lallemandda8584c2020-05-14 10:14:37 +02001373
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001374 if (!cert)
William Lallemand5685ccf2020-09-16 16:12:25 +02001375 goto end;
William Lallemandda8584c2020-05-14 10:14:37 +02001376
William Lallemand5685ccf2020-09-16 16:12:25 +02001377 if (chain == NULL) {
1378 struct issuer_chain *issuer;
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001379 issuer = ssl_get0_issuer_chain(cert);
William Lallemand5685ccf2020-09-16 16:12:25 +02001380 if (issuer) {
1381 chain = issuer->chain;
1382 chunk_appendf(out, "Chain Filename: ");
1383 chunk_appendf(out, "%s\n", issuer->path);
William Lallemandda8584c2020-05-14 10:14:37 +02001384 }
William Lallemand5685ccf2020-09-16 16:12:25 +02001385 }
1386 chunk_appendf(out, "Serial: ");
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001387 if (ssl_sock_get_serial(cert, tmp) == -1)
William Lallemand5685ccf2020-09-16 16:12:25 +02001388 goto end;
1389 dump_binary(out, tmp->area, tmp->data);
1390 chunk_appendf(out, "\n");
William Lallemandda8584c2020-05-14 10:14:37 +02001391
William Lallemand5685ccf2020-09-16 16:12:25 +02001392 chunk_appendf(out, "notBefore: ");
1393 chunk_reset(tmp);
1394 if ((bio = BIO_new(BIO_s_mem())) == NULL)
1395 goto end;
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001396 if (ASN1_TIME_print(bio, X509_getm_notBefore(cert)) == 0)
William Lallemand5685ccf2020-09-16 16:12:25 +02001397 goto end;
1398 write = BIO_read(bio, tmp->area, tmp->size-1);
1399 tmp->area[write] = '\0';
1400 BIO_free(bio);
1401 bio = NULL;
1402 chunk_appendf(out, "%s\n", tmp->area);
William Lallemandda8584c2020-05-14 10:14:37 +02001403
William Lallemand5685ccf2020-09-16 16:12:25 +02001404 chunk_appendf(out, "notAfter: ");
1405 chunk_reset(tmp);
1406 if ((bio = BIO_new(BIO_s_mem())) == NULL)
1407 goto end;
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001408 if (ASN1_TIME_print(bio, X509_getm_notAfter(cert)) == 0)
William Lallemand5685ccf2020-09-16 16:12:25 +02001409 goto end;
1410 if ((write = BIO_read(bio, tmp->area, tmp->size-1)) <= 0)
1411 goto end;
1412 tmp->area[write] = '\0';
1413 BIO_free(bio);
1414 bio = NULL;
1415 chunk_appendf(out, "%s\n", tmp->area);
William Lallemandda8584c2020-05-14 10:14:37 +02001416
1417#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
William Lallemand5685ccf2020-09-16 16:12:25 +02001418 chunk_appendf(out, "Subject Alternative Name: ");
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001419 if (ssl_sock_get_san_oneline(cert, out) == -1)
William Lallemand5685ccf2020-09-16 16:12:25 +02001420 goto end;
1421 *(out->area + out->data) = '\0';
1422 chunk_appendf(out, "\n");
William Lallemandda8584c2020-05-14 10:14:37 +02001423#endif
William Lallemand5685ccf2020-09-16 16:12:25 +02001424 chunk_reset(tmp);
1425 chunk_appendf(out, "Algorithm: ");
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001426 if (cert_get_pkey_algo(cert, tmp) == 0)
William Lallemand5685ccf2020-09-16 16:12:25 +02001427 goto end;
1428 chunk_appendf(out, "%s\n", tmp->area);
William Lallemandda8584c2020-05-14 10:14:37 +02001429
William Lallemand5685ccf2020-09-16 16:12:25 +02001430 chunk_reset(tmp);
1431 chunk_appendf(out, "SHA1 FingerPrint: ");
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001432 if (X509_digest(cert, EVP_sha1(), (unsigned char *) tmp->area, &len) == 0)
William Lallemand5685ccf2020-09-16 16:12:25 +02001433 goto end;
1434 tmp->data = len;
1435 dump_binary(out, tmp->area, tmp->data);
1436 chunk_appendf(out, "\n");
William Lallemandda8584c2020-05-14 10:14:37 +02001437
William Lallemand5685ccf2020-09-16 16:12:25 +02001438 chunk_appendf(out, "Subject: ");
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001439 if ((name = X509_get_subject_name(cert)) == NULL)
William Lallemand5685ccf2020-09-16 16:12:25 +02001440 goto end;
1441 if ((ssl_sock_get_dn_oneline(name, tmp)) == -1)
1442 goto end;
1443 *(tmp->area + tmp->data) = '\0';
1444 chunk_appendf(out, "%s\n", tmp->area);
1445
1446 chunk_appendf(out, "Issuer: ");
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001447 if ((name = X509_get_issuer_name(cert)) == NULL)
William Lallemand5685ccf2020-09-16 16:12:25 +02001448 goto end;
1449 if ((ssl_sock_get_dn_oneline(name, tmp)) == -1)
1450 goto end;
1451 *(tmp->area + tmp->data) = '\0';
1452 chunk_appendf(out, "%s\n", tmp->area);
1453
1454 /* Displays subject of each certificate in the chain */
1455 for (i = 0; i < sk_X509_num(chain); i++) {
1456 X509 *ca = sk_X509_value(chain, i);
1457
1458 chunk_appendf(out, "Chain Subject: ");
1459 if ((name = X509_get_subject_name(ca)) == NULL)
William Lallemandda8584c2020-05-14 10:14:37 +02001460 goto end;
1461 if ((ssl_sock_get_dn_oneline(name, tmp)) == -1)
1462 goto end;
1463 *(tmp->area + tmp->data) = '\0';
1464 chunk_appendf(out, "%s\n", tmp->area);
1465
William Lallemand5685ccf2020-09-16 16:12:25 +02001466 chunk_appendf(out, "Chain Issuer: ");
1467 if ((name = X509_get_issuer_name(ca)) == NULL)
William Lallemandda8584c2020-05-14 10:14:37 +02001468 goto end;
1469 if ((ssl_sock_get_dn_oneline(name, tmp)) == -1)
1470 goto end;
1471 *(tmp->area + tmp->data) = '\0';
1472 chunk_appendf(out, "%s\n", tmp->area);
William Lallemandda8584c2020-05-14 10:14:37 +02001473 }
1474
1475end:
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001476 if (bio)
1477 BIO_free(bio);
1478 free_trash_chunk(tmp);
1479
1480 return 0;
1481}
1482
Remi Tricot-Le Breton3faf0cb2021-06-10 18:10:32 +02001483#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 +02001484/*
1485 * Build the OCSP tree entry's key for a given ckch_store.
1486 * Returns a negative value in case of error.
1487 */
1488static int ckch_store_build_certid(struct ckch_store *ckch_store, unsigned char certid[128], unsigned int *key_length)
1489{
1490 OCSP_RESPONSE *resp;
1491 OCSP_BASICRESP *bs = NULL;
1492 OCSP_SINGLERESP *sr;
1493 OCSP_CERTID *id;
1494 unsigned char *p = NULL;
1495
1496 if (!key_length)
1497 return -1;
1498
1499 *key_length = 0;
1500
1501 if (!ckch_store->ckch->ocsp_response)
1502 return 0;
1503
1504 p = (unsigned char *) ckch_store->ckch->ocsp_response->area;
1505
1506 resp = d2i_OCSP_RESPONSE(NULL, (const unsigned char **)&p,
1507 ckch_store->ckch->ocsp_response->data);
1508 if (!resp) {
1509 goto end;
1510 }
1511
1512 bs = OCSP_response_get1_basic(resp);
1513 if (!bs) {
1514 goto end;
1515 }
1516
1517 sr = OCSP_resp_get0(bs, 0);
1518 if (!sr) {
1519 goto end;
1520 }
1521
1522 id = (OCSP_CERTID*)OCSP_SINGLERESP_get0_id(sr);
1523
1524 p = certid;
1525 *key_length = i2d_OCSP_CERTID(id, &p);
1526
1527end:
1528 return *key_length > 0;
1529}
1530#endif
1531
1532/*
1533 * Dump the OCSP certificate key (if it exists) of certificate <ckch> into
1534 * buffer <out>.
1535 * Returns 0 in case of success.
1536 */
1537static int ckch_store_show_ocsp_certid(struct ckch_store *ckch_store, struct buffer *out)
1538{
Remi Tricot-Le Breton3faf0cb2021-06-10 18:10:32 +02001539#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 +02001540 unsigned char key[OCSP_MAX_CERTID_ASN1_LENGTH] = {};
1541 unsigned int key_length = 0;
1542 int i;
1543
1544 if (ckch_store_build_certid(ckch_store, (unsigned char*)key, &key_length) >= 0) {
1545 /* Dump the CERTID info */
1546 chunk_appendf(out, "OCSP Response Key: ");
1547 for (i = 0; i < key_length; ++i) {
1548 chunk_appendf(out, "%02x", key[i]);
1549 }
1550 chunk_appendf(out, "\n");
1551 }
1552#endif
1553
1554 return 0;
1555}
1556
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001557
1558/* IO handler of the details "show ssl cert <filename>" */
1559static int cli_io_handler_show_cert_detail(struct appctx *appctx)
1560{
Christopher Faulet86e1c332021-12-20 17:09:39 +01001561 struct stream_interface *si = cs_si(appctx->owner);
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001562 struct ckch_store *ckchs = appctx->ctx.cli.p0;
1563 struct buffer *out = alloc_trash_chunk();
1564 int retval = 0;
1565
1566 if (!out)
1567 goto end_no_putchk;
1568
1569 chunk_appendf(out, "Filename: ");
1570 if (ckchs == ckchs_transaction.new_ckchs)
1571 chunk_appendf(out, "*");
1572 chunk_appendf(out, "%s\n", ckchs->path);
1573
1574 chunk_appendf(out, "Status: ");
1575 if (ckchs->ckch->cert == NULL)
1576 chunk_appendf(out, "Empty\n");
1577 else if (LIST_ISEMPTY(&ckchs->ckch_inst))
1578 chunk_appendf(out, "Unused\n");
1579 else
1580 chunk_appendf(out, "Used\n");
1581
1582 retval = show_cert_detail(ckchs->ckch->cert, ckchs->ckch->chain, out);
1583 if (retval < 0)
1584 goto end_no_putchk;
1585 else if (retval)
1586 goto end;
1587
Remi Tricot-Le Bretonda968f62021-06-10 13:51:14 +02001588 ckch_store_show_ocsp_certid(ckchs, out);
1589
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001590end:
William Lallemandda8584c2020-05-14 10:14:37 +02001591 if (ci_putchk(si_ic(si), out) == -1) {
1592 si_rx_room_blk(si);
1593 goto yield;
1594 }
1595
1596end_no_putchk:
William Lallemandda8584c2020-05-14 10:14:37 +02001597 free_trash_chunk(out);
1598 return 1;
1599yield:
William Lallemandda8584c2020-05-14 10:14:37 +02001600 free_trash_chunk(out);
1601 return 0; /* should come back */
1602}
1603
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001604
1605/* IO handler of the details "show ssl cert <filename.ocsp>" */
1606static int cli_io_handler_show_cert_ocsp_detail(struct appctx *appctx)
1607{
Remi Tricot-Le Breton3faf0cb2021-06-10 18:10:32 +02001608#if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) && !defined OPENSSL_IS_BORINGSSL)
Christopher Faulet86e1c332021-12-20 17:09:39 +01001609 struct stream_interface *si = cs_si(appctx->owner);
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001610 struct ckch_store *ckchs = appctx->ctx.cli.p0;
1611 struct buffer *out = alloc_trash_chunk();
1612 int from_transaction = appctx->ctx.cli.i0;
1613
1614 if (!out)
1615 goto end_no_putchk;
1616
1617 /* If we try to display an ongoing transaction's OCSP response, we
1618 * need to dump the ckch's ocsp_response buffer directly.
1619 * Otherwise, we must rebuild the certificate's certid in order to
1620 * look for the current OCSP response in the tree. */
1621 if (from_transaction && ckchs->ckch->ocsp_response) {
Remi Tricot-Le Bretona9a591a2022-02-16 14:42:22 +01001622 if (ssl_ocsp_response_print(ckchs->ckch->ocsp_response, out))
1623 goto end_no_putchk;
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001624 }
1625 else {
1626 unsigned char key[OCSP_MAX_CERTID_ASN1_LENGTH] = {};
1627 unsigned int key_length = 0;
1628
1629 if (ckch_store_build_certid(ckchs, (unsigned char*)key, &key_length) < 0)
1630 goto end_no_putchk;
1631
Remi Tricot-Le Bretona9a591a2022-02-16 14:42:22 +01001632 if (ssl_get_ocspresponse_detail(key, out))
1633 goto end_no_putchk;
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001634 }
1635
1636 if (ci_putchk(si_ic(si), out) == -1) {
1637 si_rx_room_blk(si);
1638 goto yield;
1639 }
1640
1641end_no_putchk:
1642 free_trash_chunk(out);
1643 return 1;
1644yield:
1645 free_trash_chunk(out);
1646 return 0; /* should come back */
1647#else
1648 return cli_err(appctx, "HAProxy was compiled against a version of OpenSSL that doesn't support OCSP stapling.\n");
1649#endif
1650}
1651
William Lallemandda8584c2020-05-14 10:14:37 +02001652/* parsing function for 'show ssl cert [certfile]' */
1653static int cli_parse_show_cert(char **args, char *payload, struct appctx *appctx, void *private)
1654{
1655 struct ckch_store *ckchs;
1656
1657 if (!cli_has_level(appctx, ACCESS_LVL_OPER))
1658 return cli_err(appctx, "Can't allocate memory!\n");
1659
1660 /* The operations on the CKCH architecture are locked so we can
1661 * manipulate ckch_store and ckch_inst */
1662 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
1663 return cli_err(appctx, "Can't show!\nOperations on certificates are currently locked!\n");
1664
1665 /* check if there is a certificate to lookup */
1666 if (*args[3]) {
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001667 int show_ocsp_detail = 0;
1668 int from_transaction = 0;
1669 char *end;
1670
1671 /* We manage the special case "certname.ocsp" through which we
1672 * can show the details of an OCSP response. */
1673 end = strrchr(args[3], '.');
1674 if (end && strcmp(end+1, "ocsp") == 0) {
1675 *end = '\0';
1676 show_ocsp_detail = 1;
1677 }
1678
William Lallemandda8584c2020-05-14 10:14:37 +02001679 if (*args[3] == '*') {
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001680 from_transaction = 1;
William Lallemandda8584c2020-05-14 10:14:37 +02001681 if (!ckchs_transaction.new_ckchs)
1682 goto error;
1683
1684 ckchs = ckchs_transaction.new_ckchs;
1685
Tim Duesterhuse5ff1412021-01-02 22:31:53 +01001686 if (strcmp(args[3] + 1, ckchs->path) != 0)
William Lallemandda8584c2020-05-14 10:14:37 +02001687 goto error;
1688
1689 } else {
1690 if ((ckchs = ckchs_lookup(args[3])) == NULL)
1691 goto error;
1692
1693 }
1694
William Lallemandda8584c2020-05-14 10:14:37 +02001695 appctx->ctx.cli.p0 = ckchs;
1696 /* use the IO handler that shows details */
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001697 if (show_ocsp_detail) {
1698 appctx->ctx.cli.i0 = from_transaction;
1699 appctx->io_handler = cli_io_handler_show_cert_ocsp_detail;
1700 }
1701 else
1702 appctx->io_handler = cli_io_handler_show_cert_detail;
William Lallemandda8584c2020-05-14 10:14:37 +02001703 }
1704
1705 return 0;
1706
1707error:
1708 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1709 return cli_err(appctx, "Can't display the certificate: Not found or the certificate is a bundle!\n");
1710}
1711
1712/* release function of the `set ssl cert' command, free things and unlock the spinlock */
1713static void cli_release_commit_cert(struct appctx *appctx)
1714{
1715 struct ckch_store *new_ckchs;
1716
1717 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1718
1719 if (appctx->st2 != SETCERT_ST_FIN) {
1720 /* free every new sni_ctx and the new store, which are not in the trees so no spinlock there */
1721 new_ckchs = appctx->ctx.ssl.new_ckchs;
1722
1723 /* if the allocation failed, we need to free everything from the temporary list */
1724 ckch_store_free(new_ckchs);
1725 }
1726}
1727
Remi Tricot-Le Bretonbfadc022021-02-24 12:20:48 +01001728
1729/*
1730 * Rebuild a new instance 'new_inst' based on an old instance 'ckchi' and a
1731 * specific ckch_store.
1732 * Returns 0 in case of success, 1 otherwise.
1733 */
1734static int ckch_inst_rebuild(struct ckch_store *ckch_store, struct ckch_inst *ckchi,
1735 struct ckch_inst **new_inst, char **err)
1736{
1737 int retval = 0;
1738 int errcode = 0;
1739 struct sni_ctx *sc0, *sc0s;
1740 char **sni_filter = NULL;
1741 int fcount = 0;
1742
1743 if (ckchi->crtlist_entry) {
1744 sni_filter = ckchi->crtlist_entry->filters;
1745 fcount = ckchi->crtlist_entry->fcount;
1746 }
1747
1748 if (ckchi->is_server_instance)
1749 errcode |= ckch_inst_new_load_srv_store(ckch_store->path, ckch_store, new_inst, err);
1750 else
1751 errcode |= ckch_inst_new_load_store(ckch_store->path, ckch_store, ckchi->bind_conf, ckchi->ssl_conf, sni_filter, fcount, new_inst, err);
1752
1753 if (errcode & ERR_CODE)
1754 return 1;
1755
1756 /* if the previous ckchi was used as the default */
1757 if (ckchi->is_default)
1758 (*new_inst)->is_default = 1;
1759
1760 (*new_inst)->is_server_instance = ckchi->is_server_instance;
1761 (*new_inst)->server = ckchi->server;
1762 /* Create a new SSL_CTX and link it to the new instance. */
1763 if ((*new_inst)->is_server_instance) {
1764 retval = ssl_sock_prep_srv_ctx_and_inst(ckchi->server, (*new_inst)->ctx, (*new_inst));
1765 if (retval)
1766 return 1;
1767 }
1768
1769 /* create the link to the crtlist_entry */
1770 (*new_inst)->crtlist_entry = ckchi->crtlist_entry;
1771
1772 /* we need to initialize the SSL_CTX generated */
1773 /* this iterate on the newly generated SNIs in the new instance to prepare their SSL_CTX */
1774 list_for_each_entry_safe(sc0, sc0s, &(*new_inst)->sni_ctx, by_ckch_inst) {
1775 if (!sc0->order) { /* we initialized only the first SSL_CTX because it's the same in the other sni_ctx's */
1776 errcode |= ssl_sock_prep_ctx_and_inst(ckchi->bind_conf, ckchi->ssl_conf, sc0->ctx, *new_inst, err);
1777 if (errcode & ERR_CODE)
1778 return 1;
1779 }
1780 }
1781
1782 return 0;
1783}
1784
1785/*
1786 * Load all the new SNIs of a newly built ckch instance in the trees, or replace
1787 * a server's main ckch instance.
1788 */
1789static void __ssl_sock_load_new_ckch_instance(struct ckch_inst *ckchi)
1790{
1791 /* The bind_conf will be null on server ckch_instances. */
1792 if (ckchi->is_server_instance) {
1793 int i;
1794 /* a lock is needed here since we have to free the SSL cache */
1795 HA_RWLOCK_WRLOCK(SSL_SERVER_LOCK, &ckchi->server->ssl_ctx.lock);
1796 /* free the server current SSL_CTX */
1797 SSL_CTX_free(ckchi->server->ssl_ctx.ctx);
1798 /* Actual ssl context update */
1799 SSL_CTX_up_ref(ckchi->ctx);
1800 ckchi->server->ssl_ctx.ctx = ckchi->ctx;
1801 ckchi->server->ssl_ctx.inst = ckchi;
1802
1803 /* flush the session cache of the server */
1804 for (i = 0; i < global.nbthread; i++) {
William Lallemandce990332021-11-23 15:15:09 +01001805 ha_free(&ckchi->server->ssl_ctx.reused_sess[i].sni);
Remi Tricot-Le Bretonbfadc022021-02-24 12:20:48 +01001806 ha_free(&ckchi->server->ssl_ctx.reused_sess[i].ptr);
1807 }
1808 HA_RWLOCK_WRUNLOCK(SSL_SERVER_LOCK, &ckchi->server->ssl_ctx.lock);
1809
1810 } else {
1811 HA_RWLOCK_WRLOCK(SNI_LOCK, &ckchi->bind_conf->sni_lock);
1812 ssl_sock_load_cert_sni(ckchi, ckchi->bind_conf);
1813 HA_RWLOCK_WRUNLOCK(SNI_LOCK, &ckchi->bind_conf->sni_lock);
1814 }
1815}
1816
1817/*
1818 * Delete a ckch instance that was replaced after a CLI command.
1819 */
1820static void __ckch_inst_free_locked(struct ckch_inst *ckchi)
1821{
1822 if (ckchi->is_server_instance) {
1823 /* no lock for servers */
1824 ckch_inst_free(ckchi);
1825 } else {
1826 struct bind_conf __maybe_unused *bind_conf = ckchi->bind_conf;
1827
1828 HA_RWLOCK_WRLOCK(SNI_LOCK, &bind_conf->sni_lock);
1829 ckch_inst_free(ckchi);
1830 HA_RWLOCK_WRUNLOCK(SNI_LOCK, &bind_conf->sni_lock);
1831 }
1832}
1833
1834
William Lallemandda8584c2020-05-14 10:14:37 +02001835/*
1836 * This function tries to create the new ckch_inst and their SNIs
1837 */
1838static int cli_io_handler_commit_cert(struct appctx *appctx)
1839{
Christopher Faulet86e1c332021-12-20 17:09:39 +01001840 struct stream_interface *si = cs_si(appctx->owner);
William Lallemandda8584c2020-05-14 10:14:37 +02001841 int y = 0;
1842 char *err = NULL;
William Lallemandda8584c2020-05-14 10:14:37 +02001843 struct ckch_store *old_ckchs, *new_ckchs = NULL;
1844 struct ckch_inst *ckchi, *ckchis;
1845 struct buffer *trash = alloc_trash_chunk();
William Lallemandda8584c2020-05-14 10:14:37 +02001846 struct crtlist_entry *entry;
1847
1848 if (trash == NULL)
1849 goto error;
1850
1851 if (unlikely(si_ic(si)->flags & (CF_WRITE_ERROR|CF_SHUTW)))
1852 goto error;
1853
1854 while (1) {
1855 switch (appctx->st2) {
1856 case SETCERT_ST_INIT:
1857 /* This state just print the update message */
1858 chunk_printf(trash, "Committing %s", ckchs_transaction.path);
1859 if (ci_putchk(si_ic(si), trash) == -1) {
1860 si_rx_room_blk(si);
1861 goto yield;
1862 }
1863 appctx->st2 = SETCERT_ST_GEN;
1864 /* fallthrough */
1865 case SETCERT_ST_GEN:
1866 /*
1867 * This state generates the ckch instances with their
1868 * sni_ctxs and SSL_CTX.
1869 *
1870 * Since the SSL_CTX generation can be CPU consumer, we
1871 * yield every 10 instances.
1872 */
1873
1874 old_ckchs = appctx->ctx.ssl.old_ckchs;
1875 new_ckchs = appctx->ctx.ssl.new_ckchs;
1876
1877 if (!new_ckchs)
1878 continue;
1879
1880 /* get the next ckchi to regenerate */
1881 ckchi = appctx->ctx.ssl.next_ckchi;
1882 /* we didn't start yet, set it to the first elem */
1883 if (ckchi == NULL)
1884 ckchi = LIST_ELEM(old_ckchs->ckch_inst.n, typeof(ckchi), by_ckchs);
1885
1886 /* walk through the old ckch_inst and creates new ckch_inst using the updated ckchs */
1887 list_for_each_entry_from(ckchi, &old_ckchs->ckch_inst, by_ckchs) {
1888 struct ckch_inst *new_inst;
William Lallemandda8584c2020-05-14 10:14:37 +02001889
1890 /* it takes a lot of CPU to creates SSL_CTXs, so we yield every 10 CKCH instances */
1891 if (y >= 10) {
1892 /* save the next ckchi to compute */
1893 appctx->ctx.ssl.next_ckchi = ckchi;
1894 goto yield;
1895 }
1896
Remi Tricot-Le Bretonbfadc022021-02-24 12:20:48 +01001897 if (ckch_inst_rebuild(new_ckchs, ckchi, &new_inst, &err))
William Lallemandda8584c2020-05-14 10:14:37 +02001898 goto error;
1899
William Lallemandda8584c2020-05-14 10:14:37 +02001900 /* display one dot per new instance */
1901 chunk_appendf(trash, ".");
1902 /* link the new ckch_inst to the duplicate */
Willy Tarreau2b718102021-04-21 07:32:39 +02001903 LIST_APPEND(&new_ckchs->ckch_inst, &new_inst->by_ckchs);
William Lallemandda8584c2020-05-14 10:14:37 +02001904 y++;
1905 }
1906 appctx->st2 = SETCERT_ST_INSERT;
1907 /* fallthrough */
1908 case SETCERT_ST_INSERT:
1909 /* The generation is finished, we can insert everything */
1910
1911 old_ckchs = appctx->ctx.ssl.old_ckchs;
1912 new_ckchs = appctx->ctx.ssl.new_ckchs;
1913
1914 if (!new_ckchs)
1915 continue;
1916
1917 /* get the list of crtlist_entry in the old store, and update the pointers to the store */
1918 LIST_SPLICE(&new_ckchs->crtlist_entry, &old_ckchs->crtlist_entry);
1919 list_for_each_entry(entry, &new_ckchs->crtlist_entry, by_ckch_store) {
1920 ebpt_delete(&entry->node);
1921 /* change the ptr and reinsert the node */
1922 entry->node.key = new_ckchs;
1923 ebpt_insert(&entry->crtlist->entries, &entry->node);
1924 }
1925
William Lallemanda55685b2020-12-15 14:57:46 +01001926 /* insert the new ckch_insts in the crtlist_entry */
1927 list_for_each_entry(ckchi, &new_ckchs->ckch_inst, by_ckchs) {
1928 if (ckchi->crtlist_entry)
Willy Tarreau2b718102021-04-21 07:32:39 +02001929 LIST_INSERT(&ckchi->crtlist_entry->ckch_inst, &ckchi->by_crtlist_entry);
William Lallemanda55685b2020-12-15 14:57:46 +01001930 }
1931
William Lallemandda8584c2020-05-14 10:14:37 +02001932 /* First, we insert every new SNIs in the trees, also replace the default_ctx */
1933 list_for_each_entry_safe(ckchi, ckchis, &new_ckchs->ckch_inst, by_ckchs) {
Remi Tricot-Le Bretonbfadc022021-02-24 12:20:48 +01001934 __ssl_sock_load_new_ckch_instance(ckchi);
William Lallemandda8584c2020-05-14 10:14:37 +02001935 }
1936
1937 /* delete the old sni_ctx, the old ckch_insts and the ckch_store */
1938 list_for_each_entry_safe(ckchi, ckchis, &old_ckchs->ckch_inst, by_ckchs) {
Remi Tricot-Le Bretonbfadc022021-02-24 12:20:48 +01001939 __ckch_inst_free_locked(ckchi);
William Lallemandda8584c2020-05-14 10:14:37 +02001940 }
1941
1942 /* Replace the old ckchs by the new one */
1943 ckch_store_free(old_ckchs);
1944 ebst_insert(&ckchs_tree, &new_ckchs->node);
1945 appctx->st2 = SETCERT_ST_FIN;
1946 /* fallthrough */
1947 case SETCERT_ST_FIN:
1948 /* we achieved the transaction, we can set everything to NULL */
Willy Tarreau61cfdf42021-02-20 10:46:51 +01001949 ha_free(&ckchs_transaction.path);
William Lallemandda8584c2020-05-14 10:14:37 +02001950 ckchs_transaction.new_ckchs = NULL;
1951 ckchs_transaction.old_ckchs = NULL;
1952 goto end;
1953 }
1954 }
1955end:
1956
1957 chunk_appendf(trash, "\n");
William Lallemandda8584c2020-05-14 10:14:37 +02001958 chunk_appendf(trash, "Success!\n");
1959 if (ci_putchk(si_ic(si), trash) == -1)
1960 si_rx_room_blk(si);
1961 free_trash_chunk(trash);
1962 /* success: call the release function and don't come back */
1963 return 1;
1964yield:
1965 /* store the state */
1966 if (ci_putchk(si_ic(si), trash) == -1)
1967 si_rx_room_blk(si);
1968 free_trash_chunk(trash);
1969 si_rx_endp_more(si); /* let's come back later */
1970 return 0; /* should come back */
1971
1972error:
1973 /* spin unlock and free are done in the release function */
1974 if (trash) {
1975 chunk_appendf(trash, "\n%sFailed!\n", err);
1976 if (ci_putchk(si_ic(si), trash) == -1)
1977 si_rx_room_blk(si);
1978 free_trash_chunk(trash);
1979 }
1980 /* error: call the release function and don't come back */
1981 return 1;
1982}
1983
1984/*
1985 * Parsing function of 'commit ssl cert'
1986 */
1987static int cli_parse_commit_cert(char **args, char *payload, struct appctx *appctx, void *private)
1988{
1989 char *err = NULL;
1990
1991 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
1992 return 1;
1993
1994 if (!*args[3])
1995 return cli_err(appctx, "'commit ssl cert expects a filename\n");
1996
1997 /* The operations on the CKCH architecture are locked so we can
1998 * manipulate ckch_store and ckch_inst */
1999 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2000 return cli_err(appctx, "Can't commit the certificate!\nOperations on certificates are currently locked!\n");
2001
2002 if (!ckchs_transaction.path) {
2003 memprintf(&err, "No ongoing transaction! !\n");
2004 goto error;
2005 }
2006
2007 if (strcmp(ckchs_transaction.path, args[3]) != 0) {
2008 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", ckchs_transaction.path, args[3]);
2009 goto error;
2010 }
2011
William Lallemand5685ccf2020-09-16 16:12:25 +02002012 /* if a certificate is here, a private key must be here too */
2013 if (ckchs_transaction.new_ckchs->ckch->cert && !ckchs_transaction.new_ckchs->ckch->key) {
2014 memprintf(&err, "The transaction must contain at least a certificate and a private key!\n");
2015 goto error;
2016 }
William Lallemanda9419522020-06-24 16:26:41 +02002017
William Lallemand5685ccf2020-09-16 16:12:25 +02002018 if (!X509_check_private_key(ckchs_transaction.new_ckchs->ckch->cert, ckchs_transaction.new_ckchs->ckch->key)) {
2019 memprintf(&err, "inconsistencies between private key and certificate loaded '%s'.\n", ckchs_transaction.path);
2020 goto error;
William Lallemandda8584c2020-05-14 10:14:37 +02002021 }
2022
2023 /* init the appctx structure */
2024 appctx->st2 = SETCERT_ST_INIT;
2025 appctx->ctx.ssl.next_ckchi = NULL;
2026 appctx->ctx.ssl.new_ckchs = ckchs_transaction.new_ckchs;
2027 appctx->ctx.ssl.old_ckchs = ckchs_transaction.old_ckchs;
2028
2029 /* we don't unlock there, it will be unlock after the IO handler, in the release handler */
2030 return 0;
2031
2032error:
2033
2034 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2035 err = memprintf(&err, "%sCan't commit %s!\n", err ? err : "", args[3]);
2036
2037 return cli_dynerr(appctx, err);
2038}
2039
2040
2041
2042
2043/*
2044 * Parsing function of `set ssl cert`, it updates or creates a temporary ckch.
2045 */
2046static int cli_parse_set_cert(char **args, char *payload, struct appctx *appctx, void *private)
2047{
2048 struct ckch_store *new_ckchs = NULL;
2049 struct ckch_store *old_ckchs = NULL;
2050 char *err = NULL;
2051 int i;
William Lallemandda8584c2020-05-14 10:14:37 +02002052 int errcode = 0;
2053 char *end;
2054 int type = CERT_TYPE_PEM;
2055 struct cert_key_and_chain *ckch;
2056 struct buffer *buf;
2057
2058 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2059 return 1;
2060
William Lallemandda8584c2020-05-14 10:14:37 +02002061 if (!*args[3] || !payload)
2062 return cli_err(appctx, "'set ssl cert expects a filename and a certificate as a payload\n");
2063
2064 /* The operations on the CKCH architecture are locked so we can
2065 * manipulate ckch_store and ckch_inst */
2066 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2067 return cli_err(appctx, "Can't update the certificate!\nOperations on certificates are currently locked!\n");
2068
William Lallemand5ba80d62021-05-04 16:17:27 +02002069 if ((buf = alloc_trash_chunk()) == NULL) {
2070 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2071 errcode |= ERR_ALERT | ERR_FATAL;
2072 goto end;
2073 }
William Lallemande5ff4ad2020-06-08 09:40:37 +02002074
William Lallemandda8584c2020-05-14 10:14:37 +02002075 if (!chunk_strcpy(buf, args[3])) {
2076 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2077 errcode |= ERR_ALERT | ERR_FATAL;
2078 goto end;
2079 }
2080
2081 /* check which type of file we want to update */
2082 for (i = 0; cert_exts[i].type < CERT_TYPE_MAX; i++) {
2083 end = strrchr(buf->area, '.');
Tim Duesterhuse5ff1412021-01-02 22:31:53 +01002084 if (end && *cert_exts[i].ext && (strcmp(end + 1, cert_exts[i].ext) == 0)) {
William Lallemandda8584c2020-05-14 10:14:37 +02002085 *end = '\0';
William Lallemand089c1382020-10-23 17:35:12 +02002086 buf->data = strlen(buf->area);
William Lallemandda8584c2020-05-14 10:14:37 +02002087 type = cert_exts[i].type;
2088 break;
2089 }
2090 }
2091
2092 appctx->ctx.ssl.old_ckchs = NULL;
2093 appctx->ctx.ssl.new_ckchs = NULL;
2094
2095 /* if there is an ongoing transaction */
2096 if (ckchs_transaction.path) {
William Lallemandda8584c2020-05-14 10:14:37 +02002097 /* if there is an ongoing transaction, check if this is the same file */
2098 if (strcmp(ckchs_transaction.path, buf->area) != 0) {
William Lallemand089c1382020-10-23 17:35:12 +02002099 /* we didn't find the transaction, must try more cases below */
2100
2101 /* if the del-ext option is activated we should try to take a look at a ".crt" too. */
2102 if (type != CERT_TYPE_PEM && global_ssl.extra_files_noext) {
2103 if (!chunk_strcat(buf, ".crt")) {
2104 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2105 errcode |= ERR_ALERT | ERR_FATAL;
2106 goto end;
2107 }
2108
2109 if (strcmp(ckchs_transaction.path, buf->area) != 0) {
2110 /* remove .crt of the error message */
2111 *(b_orig(buf) + b_data(buf) + strlen(".crt")) = '\0';
2112 b_sub(buf, strlen(".crt"));
2113
2114 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", ckchs_transaction.path, buf->area);
2115 errcode |= ERR_ALERT | ERR_FATAL;
2116 goto end;
2117 }
2118 }
William Lallemandda8584c2020-05-14 10:14:37 +02002119 }
2120
2121 appctx->ctx.ssl.old_ckchs = ckchs_transaction.new_ckchs;
2122
2123 } else {
William Lallemandda8584c2020-05-14 10:14:37 +02002124
William Lallemand95fefa12020-09-09 12:01:33 +02002125 /* lookup for the certificate in the tree */
2126 appctx->ctx.ssl.old_ckchs = ckchs_lookup(buf->area);
William Lallemand089c1382020-10-23 17:35:12 +02002127
2128 if (!appctx->ctx.ssl.old_ckchs) {
2129 /* if the del-ext option is activated we should try to take a look at a ".crt" too. */
2130 if (type != CERT_TYPE_PEM && global_ssl.extra_files_noext) {
2131 if (!chunk_strcat(buf, ".crt")) {
2132 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2133 errcode |= ERR_ALERT | ERR_FATAL;
2134 goto end;
2135 }
2136 appctx->ctx.ssl.old_ckchs = ckchs_lookup(buf->area);
2137 }
2138 }
William Lallemandda8584c2020-05-14 10:14:37 +02002139 }
2140
2141 if (!appctx->ctx.ssl.old_ckchs) {
2142 memprintf(&err, "%sCan't replace a certificate which is not referenced by the configuration!\n",
2143 err ? err : "");
2144 errcode |= ERR_ALERT | ERR_FATAL;
2145 goto end;
2146 }
2147
2148 if (!appctx->ctx.ssl.path) {
2149 /* this is a new transaction, set the path of the transaction */
2150 appctx->ctx.ssl.path = strdup(appctx->ctx.ssl.old_ckchs->path);
2151 if (!appctx->ctx.ssl.path) {
2152 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2153 errcode |= ERR_ALERT | ERR_FATAL;
2154 goto end;
2155 }
2156 }
2157
2158 old_ckchs = appctx->ctx.ssl.old_ckchs;
2159
2160 /* duplicate the ckch store */
2161 new_ckchs = ckchs_dup(old_ckchs);
2162 if (!new_ckchs) {
2163 memprintf(&err, "%sCannot allocate memory!\n",
2164 err ? err : "");
2165 errcode |= ERR_ALERT | ERR_FATAL;
2166 goto end;
2167 }
2168
William Lallemand95fefa12020-09-09 12:01:33 +02002169 ckch = new_ckchs->ckch;
William Lallemandda8584c2020-05-14 10:14:37 +02002170
2171 /* appply the change on the duplicate */
2172 if (cert_exts[type].load(buf->area, payload, ckch, &err) != 0) {
2173 memprintf(&err, "%sCan't load the payload\n", err ? err : "");
2174 errcode |= ERR_ALERT | ERR_FATAL;
2175 goto end;
2176 }
2177
2178 appctx->ctx.ssl.new_ckchs = new_ckchs;
2179
2180 /* we succeed, we can save the ckchs in the transaction */
2181
2182 /* if there wasn't a transaction, update the old ckchs */
2183 if (!ckchs_transaction.old_ckchs) {
2184 ckchs_transaction.old_ckchs = appctx->ctx.ssl.old_ckchs;
2185 ckchs_transaction.path = appctx->ctx.ssl.path;
2186 err = memprintf(&err, "Transaction created for certificate %s!\n", ckchs_transaction.path);
2187 } else {
2188 err = memprintf(&err, "Transaction updated for certificate %s!\n", ckchs_transaction.path);
2189
2190 }
2191
2192 /* free the previous ckchs if there was a transaction */
2193 ckch_store_free(ckchs_transaction.new_ckchs);
2194
2195 ckchs_transaction.new_ckchs = appctx->ctx.ssl.new_ckchs;
2196
2197
2198 /* creates the SNI ctxs later in the IO handler */
2199
2200end:
2201 free_trash_chunk(buf);
2202
2203 if (errcode & ERR_CODE) {
2204
2205 ckch_store_free(appctx->ctx.ssl.new_ckchs);
2206 appctx->ctx.ssl.new_ckchs = NULL;
2207
2208 appctx->ctx.ssl.old_ckchs = NULL;
2209
Willy Tarreau61cfdf42021-02-20 10:46:51 +01002210 ha_free(&appctx->ctx.ssl.path);
William Lallemandda8584c2020-05-14 10:14:37 +02002211
2212 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2213 return cli_dynerr(appctx, memprintf(&err, "%sCan't update %s!\n", err ? err : "", args[3]));
2214 } else {
2215
2216 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2217 return cli_dynmsg(appctx, LOG_NOTICE, err);
2218 }
2219 /* TODO: handle the ERR_WARN which are not handled because of the io_handler */
2220}
2221
2222/* parsing function of 'abort ssl cert' */
2223static int cli_parse_abort_cert(char **args, char *payload, struct appctx *appctx, void *private)
2224{
2225 char *err = NULL;
2226
2227 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2228 return 1;
2229
2230 if (!*args[3])
2231 return cli_err(appctx, "'abort ssl cert' expects a filename\n");
2232
2233 /* The operations on the CKCH architecture are locked so we can
2234 * manipulate ckch_store and ckch_inst */
2235 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2236 return cli_err(appctx, "Can't abort!\nOperations on certificates are currently locked!\n");
2237
2238 if (!ckchs_transaction.path) {
2239 memprintf(&err, "No ongoing transaction!\n");
2240 goto error;
2241 }
2242
2243 if (strcmp(ckchs_transaction.path, args[3]) != 0) {
2244 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to abort a transaction for '%s'\n", ckchs_transaction.path, args[3]);
2245 goto error;
2246 }
2247
2248 /* Only free the ckchs there, because the SNI and instances were not generated yet */
2249 ckch_store_free(ckchs_transaction.new_ckchs);
2250 ckchs_transaction.new_ckchs = NULL;
William Lallemandda8584c2020-05-14 10:14:37 +02002251 ckchs_transaction.old_ckchs = NULL;
Willy Tarreau61cfdf42021-02-20 10:46:51 +01002252 ha_free(&ckchs_transaction.path);
William Lallemandda8584c2020-05-14 10:14:37 +02002253
2254 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2255
2256 err = memprintf(&err, "Transaction aborted for certificate '%s'!\n", args[3]);
2257 return cli_dynmsg(appctx, LOG_NOTICE, err);
2258
2259error:
2260 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2261
2262 return cli_dynerr(appctx, err);
2263}
2264
2265/* parsing function of 'new ssl cert' */
2266static int cli_parse_new_cert(char **args, char *payload, struct appctx *appctx, void *private)
2267{
2268 struct ckch_store *store;
2269 char *err = NULL;
2270 char *path;
2271
2272 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2273 return 1;
2274
2275 if (!*args[3])
2276 return cli_err(appctx, "'new ssl cert' expects a filename\n");
2277
2278 path = args[3];
2279
2280 /* The operations on the CKCH architecture are locked so we can
2281 * manipulate ckch_store and ckch_inst */
2282 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2283 return cli_err(appctx, "Can't create a certificate!\nOperations on certificates are currently locked!\n");
2284
2285 store = ckchs_lookup(path);
2286 if (store != NULL) {
2287 memprintf(&err, "Certificate '%s' already exists!\n", path);
2288 store = NULL; /* we don't want to free it */
2289 goto error;
2290 }
2291 /* we won't support multi-certificate bundle here */
William Lallemandbd8e6ed2020-09-16 16:08:08 +02002292 store = ckch_store_new(path);
William Lallemandda8584c2020-05-14 10:14:37 +02002293 if (!store) {
2294 memprintf(&err, "unable to allocate memory.\n");
2295 goto error;
2296 }
2297
2298 /* insert into the ckchs tree */
2299 ebst_insert(&ckchs_tree, &store->node);
2300 memprintf(&err, "New empty certificate store '%s'!\n", args[3]);
2301
2302 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2303 return cli_dynmsg(appctx, LOG_NOTICE, err);
2304error:
2305 free(store);
2306 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2307 return cli_dynerr(appctx, err);
2308}
2309
2310/* parsing function of 'del ssl cert' */
2311static int cli_parse_del_cert(char **args, char *payload, struct appctx *appctx, void *private)
2312{
2313 struct ckch_store *store;
2314 char *err = NULL;
2315 char *filename;
2316
2317 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2318 return 1;
2319
2320 if (!*args[3])
2321 return cli_err(appctx, "'del ssl cert' expects a certificate name\n");
2322
2323 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2324 return cli_err(appctx, "Can't delete the certificate!\nOperations on certificates are currently locked!\n");
2325
2326 filename = args[3];
2327
2328 store = ckchs_lookup(filename);
2329 if (store == NULL) {
2330 memprintf(&err, "certificate '%s' doesn't exist!\n", filename);
2331 goto error;
2332 }
2333 if (!LIST_ISEMPTY(&store->ckch_inst)) {
2334 memprintf(&err, "certificate '%s' in use, can't be deleted!\n", filename);
2335 goto error;
2336 }
2337
2338 ebmb_delete(&store->node);
2339 ckch_store_free(store);
2340
2341 memprintf(&err, "Certificate '%s' deleted!\n", filename);
2342
2343 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2344 return cli_dynmsg(appctx, LOG_NOTICE, err);
2345
2346error:
2347 memprintf(&err, "Can't remove the certificate: %s\n", err ? err : "");
2348 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2349 return cli_dynerr(appctx, err);
2350}
2351
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002352
Remi Tricot-Le Breton9f40fe02021-03-16 16:21:27 +01002353
2354/* parsing function of 'new ssl ca-file' */
2355static int cli_parse_new_cafile(char **args, char *payload, struct appctx *appctx, void *private)
2356{
2357 struct cafile_entry *cafile_entry;
2358 char *err = NULL;
2359 char *path;
2360
2361 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2362 return 1;
2363
2364 if (!*args[3])
2365 return cli_err(appctx, "'new ssl ca-file' expects a filename\n");
2366
2367 path = args[3];
2368
2369 /* The operations on the CKCH architecture are locked so we can
2370 * manipulate ckch_store and ckch_inst */
2371 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2372 return cli_err(appctx, "Can't create a CA file!\nOperations on certificates are currently locked!\n");
2373
2374 cafile_entry = ssl_store_get_cafile_entry(path, 0);
2375 if (cafile_entry) {
2376 memprintf(&err, "CA file '%s' already exists!\n", path);
2377 goto error;
2378 }
2379
2380 cafile_entry = ssl_store_create_cafile_entry(path, NULL, CAFILE_CERT);
2381 if (!cafile_entry) {
2382 memprintf(&err, "%sCannot allocate memory!\n",
2383 err ? err : "");
2384 goto error;
2385 }
2386
2387 /* Add the newly created cafile_entry to the tree so that
2388 * any new ckch instance created from now can use it. */
2389 if (ssl_store_add_uncommitted_cafile_entry(cafile_entry))
2390 goto error;
2391
2392 memprintf(&err, "New CA file created '%s'!\n", path);
2393
2394 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2395 return cli_dynmsg(appctx, LOG_NOTICE, err);
2396error:
2397 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2398 return cli_dynerr(appctx, err);
2399}
2400
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002401/*
2402 * Parsing function of `set ssl ca-file`
2403 */
2404static int cli_parse_set_cafile(char **args, char *payload, struct appctx *appctx, void *private)
2405{
2406 char *err = NULL;
2407 int errcode = 0;
2408 struct buffer *buf;
2409
2410 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2411 return 1;
2412
2413 if (!*args[3] || !payload)
2414 return cli_err(appctx, "'set ssl ca-file expects a filename and CAs as a payload\n");
2415
2416 /* The operations on the CKCH architecture are locked so we can
2417 * manipulate ckch_store and ckch_inst */
2418 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2419 return cli_err(appctx, "Can't update the CA file!\nOperations on certificates are currently locked!\n");
2420
2421 if ((buf = alloc_trash_chunk()) == NULL) {
2422 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2423 errcode |= ERR_ALERT | ERR_FATAL;
2424 goto end;
2425 }
2426
2427 if (!chunk_strcpy(buf, args[3])) {
2428 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2429 errcode |= ERR_ALERT | ERR_FATAL;
2430 goto end;
2431 }
2432
2433 appctx->ctx.ssl.old_cafile_entry = NULL;
2434 appctx->ctx.ssl.new_cafile_entry = NULL;
2435
2436 /* if there is an ongoing transaction */
2437 if (cafile_transaction.path) {
2438 /* if there is an ongoing transaction, check if this is the same file */
2439 if (strcmp(cafile_transaction.path, buf->area) != 0) {
2440 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", cafile_transaction.path, buf->area);
2441 errcode |= ERR_ALERT | ERR_FATAL;
2442 goto end;
2443 }
2444 appctx->ctx.ssl.old_cafile_entry = cafile_transaction.old_cafile_entry;
2445 }
2446 else {
2447 /* lookup for the certificate in the tree */
2448 appctx->ctx.ssl.old_cafile_entry = ssl_store_get_cafile_entry(buf->area, 0);
2449 }
2450
2451 if (!appctx->ctx.ssl.old_cafile_entry) {
2452 memprintf(&err, "%sCan't replace a CA file which is not referenced by the configuration!\n",
2453 err ? err : "");
2454 errcode |= ERR_ALERT | ERR_FATAL;
2455 goto end;
2456 }
2457
2458 if (!appctx->ctx.ssl.path) {
2459 /* this is a new transaction, set the path of the transaction */
2460 appctx->ctx.ssl.path = strdup(appctx->ctx.ssl.old_cafile_entry->path);
2461 if (!appctx->ctx.ssl.path) {
2462 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2463 errcode |= ERR_ALERT | ERR_FATAL;
2464 goto end;
2465 }
2466 }
2467
2468 if (appctx->ctx.ssl.new_cafile_entry)
2469 ssl_store_delete_cafile_entry(appctx->ctx.ssl.new_cafile_entry);
2470
2471 /* Create a new cafile_entry without adding it to the cafile tree. */
Remi Tricot-Le Breton0bb48242021-04-16 17:59:23 +02002472 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 +01002473 if (!appctx->ctx.ssl.new_cafile_entry) {
2474 memprintf(&err, "%sCannot allocate memory!\n",
2475 err ? err : "");
2476 errcode |= ERR_ALERT | ERR_FATAL;
2477 goto end;
2478 }
2479
2480 /* Fill the new entry with the new CAs. */
2481 if (ssl_store_load_ca_from_buf(appctx->ctx.ssl.new_cafile_entry, payload)) {
2482 memprintf(&err, "%sInvalid payload\n", err ? err : "");
2483 errcode |= ERR_ALERT | ERR_FATAL;
2484 goto end;
2485 }
2486
2487 /* we succeed, we can save the ca in the transaction */
2488
2489 /* if there wasn't a transaction, update the old CA */
2490 if (!cafile_transaction.old_cafile_entry) {
2491 cafile_transaction.old_cafile_entry = appctx->ctx.ssl.old_cafile_entry;
2492 cafile_transaction.path = appctx->ctx.ssl.path;
2493 err = memprintf(&err, "transaction created for CA %s!\n", cafile_transaction.path);
2494 } else {
2495 err = memprintf(&err, "transaction updated for CA %s!\n", cafile_transaction.path);
2496 }
2497
2498 /* free the previous CA if there was a transaction */
2499 ssl_store_delete_cafile_entry(cafile_transaction.new_cafile_entry);
2500
2501 cafile_transaction.new_cafile_entry = appctx->ctx.ssl.new_cafile_entry;
2502
2503 /* creates the SNI ctxs later in the IO handler */
2504
2505end:
2506 free_trash_chunk(buf);
2507
2508 if (errcode & ERR_CODE) {
2509 ssl_store_delete_cafile_entry(appctx->ctx.ssl.new_cafile_entry);
2510 appctx->ctx.ssl.new_cafile_entry = NULL;
2511 appctx->ctx.ssl.old_cafile_entry = NULL;
2512
Tim Duesterhus025b93e2021-11-04 21:03:52 +01002513 ha_free(&appctx->ctx.ssl.path);
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002514
2515 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2516 return cli_dynerr(appctx, memprintf(&err, "%sCan't update %s!\n", err ? err : "", args[3]));
2517 } else {
2518
2519 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2520 return cli_dynmsg(appctx, LOG_NOTICE, err);
2521 }
2522}
2523
2524
2525/*
2526 * Parsing function of 'commit ssl ca-file'
2527 */
2528static int cli_parse_commit_cafile(char **args, char *payload, struct appctx *appctx, void *private)
2529{
2530 char *err = NULL;
2531
2532 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2533 return 1;
2534
2535 if (!*args[3])
2536 return cli_err(appctx, "'commit ssl ca-file expects a filename\n");
2537
2538 /* The operations on the CKCH architecture are locked so we can
2539 * manipulate ckch_store and ckch_inst */
2540 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2541 return cli_err(appctx, "Can't commit the CA file!\nOperations on certificates are currently locked!\n");
2542
2543 if (!cafile_transaction.path) {
2544 memprintf(&err, "No ongoing transaction! !\n");
2545 goto error;
2546 }
2547
2548 if (strcmp(cafile_transaction.path, args[3]) != 0) {
2549 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", cafile_transaction.path, args[3]);
2550 goto error;
2551 }
2552 /* init the appctx structure */
2553 appctx->st2 = SETCERT_ST_INIT;
2554 appctx->ctx.ssl.next_ckchi_link = NULL;
2555 appctx->ctx.ssl.old_cafile_entry = cafile_transaction.old_cafile_entry;
2556 appctx->ctx.ssl.new_cafile_entry = cafile_transaction.new_cafile_entry;
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002557 appctx->ctx.ssl.cafile_type = CAFILE_CERT;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002558
2559 return 0;
2560
2561error:
2562
2563 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2564 err = memprintf(&err, "%sCan't commit %s!\n", err ? err : "", args[3]);
2565
2566 return cli_dynerr(appctx, err);
2567}
2568
2569enum {
2570 CREATE_NEW_INST_OK = 0,
2571 CREATE_NEW_INST_YIELD = -1,
2572 CREATE_NEW_INST_ERR = -2
2573};
2574
2575static inline int __create_new_instance(struct appctx *appctx, struct ckch_inst *ckchi, int *count,
Remi Tricot-Le Bretond75b99e2021-05-17 11:45:55 +02002576 struct buffer *trash, char **err)
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002577{
2578 struct ckch_inst *new_inst;
2579
2580 /* it takes a lot of CPU to creates SSL_CTXs, so we yield every 10 CKCH instances */
2581 if (*count >= 10) {
2582 /* save the next ckchi to compute */
2583 appctx->ctx.ssl.next_ckchi = ckchi;
2584 return CREATE_NEW_INST_YIELD;
2585 }
2586
2587 /* Rebuild a new ckch instance that uses the same ckch_store
2588 * than a reference ckchi instance but will use a new CA file. */
Remi Tricot-Le Bretond75b99e2021-05-17 11:45:55 +02002589 if (ckch_inst_rebuild(ckchi->ckch_store, ckchi, &new_inst, err))
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002590 return CREATE_NEW_INST_ERR;
2591
2592 /* display one dot per new instance */
2593 chunk_appendf(trash, ".");
2594 ++(*count);
2595
2596 return CREATE_NEW_INST_OK;
2597}
2598
2599/*
2600 * This function tries to create new ckch instances and their SNIs using a newly
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002601 * set certificate authority (CA file) or a newly set Certificate Revocation
2602 * List (CRL), depending on the command being called.
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002603 */
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002604static int cli_io_handler_commit_cafile_crlfile(struct appctx *appctx)
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002605{
Christopher Faulet86e1c332021-12-20 17:09:39 +01002606 struct stream_interface *si = cs_si(appctx->owner);
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002607 int y = 0;
2608 char *err = NULL;
Remi Tricot-Le Bretona6b27842021-05-18 10:06:00 +02002609 struct cafile_entry *old_cafile_entry = NULL, *new_cafile_entry = NULL;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002610 struct ckch_inst_link *ckchi_link;
2611 struct buffer *trash = alloc_trash_chunk();
2612
2613 if (trash == NULL)
2614 goto error;
2615
2616 if (unlikely(si_ic(si)->flags & (CF_WRITE_ERROR|CF_SHUTW)))
2617 goto error;
2618
2619 while (1) {
2620 switch (appctx->st2) {
2621 case SETCERT_ST_INIT:
2622 /* This state just print the update message */
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002623 switch (appctx->ctx.ssl.cafile_type) {
2624 case CAFILE_CERT:
2625 chunk_printf(trash, "Committing %s", cafile_transaction.path);
2626 break;
2627 case CAFILE_CRL:
2628 chunk_printf(trash, "Committing %s", crlfile_transaction.path);
2629 break;
2630 default:
2631 goto error;
2632 }
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002633 if (ci_putchk(si_ic(si), trash) == -1) {
2634 si_rx_room_blk(si);
2635 goto yield;
2636 }
2637 appctx->st2 = SETCERT_ST_GEN;
2638 /* fallthrough */
2639 case SETCERT_ST_GEN:
2640 /*
2641 * This state generates the ckch instances with their
2642 * sni_ctxs and SSL_CTX.
2643 *
2644 * Since the SSL_CTX generation can be CPU consumer, we
2645 * yield every 10 instances.
2646 */
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002647 switch (appctx->ctx.ssl.cafile_type) {
2648 case CAFILE_CERT:
2649 old_cafile_entry = appctx->ctx.ssl.old_cafile_entry;
2650 new_cafile_entry = appctx->ctx.ssl.new_cafile_entry;
2651 break;
2652 case CAFILE_CRL:
2653 old_cafile_entry = appctx->ctx.ssl.old_crlfile_entry;
2654 new_cafile_entry = appctx->ctx.ssl.new_crlfile_entry;
2655 break;
2656 }
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002657 if (!new_cafile_entry)
2658 continue;
2659
2660 /* get the next ckchi to regenerate */
2661 ckchi_link = appctx->ctx.ssl.next_ckchi_link;
2662 /* we didn't start yet, set it to the first elem */
2663 if (ckchi_link == NULL) {
2664 ckchi_link = LIST_ELEM(old_cafile_entry->ckch_inst_link.n, typeof(ckchi_link), list);
2665 /* Add the newly created cafile_entry to the tree so that
2666 * any new ckch instance created from now can use it. */
2667 if (ssl_store_add_uncommitted_cafile_entry(new_cafile_entry))
2668 goto error;
2669 }
2670
2671 list_for_each_entry_from(ckchi_link, &old_cafile_entry->ckch_inst_link, list) {
Remi Tricot-Le Bretond75b99e2021-05-17 11:45:55 +02002672 switch (__create_new_instance(appctx, ckchi_link->ckch_inst, &y, trash, &err)) {
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002673 case CREATE_NEW_INST_YIELD:
2674 appctx->ctx.ssl.next_ckchi_link = ckchi_link;
2675 goto yield;
2676 case CREATE_NEW_INST_ERR:
2677 goto error;
2678 default: break;
2679 }
2680 }
2681
2682 appctx->st2 = SETCERT_ST_INSERT;
2683 /* fallthrough */
2684 case SETCERT_ST_INSERT:
2685 /* The generation is finished, we can insert everything */
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002686 switch (appctx->ctx.ssl.cafile_type) {
2687 case CAFILE_CERT:
2688 old_cafile_entry = appctx->ctx.ssl.old_cafile_entry;
2689 new_cafile_entry = appctx->ctx.ssl.new_cafile_entry;
2690 break;
2691 case CAFILE_CRL:
2692 old_cafile_entry = appctx->ctx.ssl.old_crlfile_entry;
2693 new_cafile_entry = appctx->ctx.ssl.new_crlfile_entry;
2694 break;
2695 }
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002696 if (!new_cafile_entry)
2697 continue;
2698
2699 /* insert the new ckch_insts in the crtlist_entry */
2700 list_for_each_entry(ckchi_link, &new_cafile_entry->ckch_inst_link, list) {
2701 if (ckchi_link->ckch_inst->crtlist_entry)
2702 LIST_INSERT(&ckchi_link->ckch_inst->crtlist_entry->ckch_inst,
2703 &ckchi_link->ckch_inst->by_crtlist_entry);
2704 }
2705
2706 /* First, we insert every new SNIs in the trees, also replace the default_ctx */
2707 list_for_each_entry(ckchi_link, &new_cafile_entry->ckch_inst_link, list) {
2708 __ssl_sock_load_new_ckch_instance(ckchi_link->ckch_inst);
2709 }
2710
2711 /* delete the old sni_ctx, the old ckch_insts and the ckch_store */
2712 list_for_each_entry(ckchi_link, &old_cafile_entry->ckch_inst_link, list) {
2713 __ckch_inst_free_locked(ckchi_link->ckch_inst);
2714 }
2715
2716
2717 /* Remove the old cafile entry from the tree */
2718 ebmb_delete(&old_cafile_entry->node);
2719 ssl_store_delete_cafile_entry(old_cafile_entry);
2720
2721 appctx->st2 = SETCERT_ST_FIN;
2722 /* fallthrough */
2723 case SETCERT_ST_FIN:
2724 /* we achieved the transaction, we can set everything to NULL */
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002725 switch (appctx->ctx.ssl.cafile_type) {
2726 case CAFILE_CERT:
2727 ha_free(&cafile_transaction.path);
2728 cafile_transaction.old_cafile_entry = NULL;
2729 cafile_transaction.new_cafile_entry = NULL;
2730 break;
2731 case CAFILE_CRL:
2732 ha_free(&crlfile_transaction.path);
2733 crlfile_transaction.old_crlfile_entry = NULL;
2734 crlfile_transaction.new_crlfile_entry = NULL;
2735 break;
2736 }
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002737 goto end;
2738 }
2739 }
2740end:
2741
2742 chunk_appendf(trash, "\n");
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002743 chunk_appendf(trash, "Success!\n");
2744 if (ci_putchk(si_ic(si), trash) == -1)
2745 si_rx_room_blk(si);
2746 free_trash_chunk(trash);
2747 /* success: call the release function and don't come back */
2748 return 1;
2749yield:
2750 /* store the state */
2751 if (ci_putchk(si_ic(si), trash) == -1)
2752 si_rx_room_blk(si);
2753 free_trash_chunk(trash);
2754 si_rx_endp_more(si); /* let's come back later */
2755 return 0; /* should come back */
2756
2757error:
2758 /* spin unlock and free are done in the release function */
2759 if (trash) {
2760 chunk_appendf(trash, "\n%sFailed!\n", err);
2761 if (ci_putchk(si_ic(si), trash) == -1)
2762 si_rx_room_blk(si);
2763 free_trash_chunk(trash);
2764 }
2765 /* error: call the release function and don't come back */
2766 return 1;
2767}
2768
Remi Tricot-Le Bretond5fd09d2021-03-11 10:22:52 +01002769
2770/* parsing function of 'abort ssl ca-file' */
2771static int cli_parse_abort_cafile(char **args, char *payload, struct appctx *appctx, void *private)
2772{
2773 char *err = NULL;
2774
2775 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2776 return 1;
2777
2778 if (!*args[3])
2779 return cli_err(appctx, "'abort ssl ca-file' expects a filename\n");
2780
2781 /* The operations on the CKCH architecture are locked so we can
2782 * manipulate ckch_store and ckch_inst */
2783 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2784 return cli_err(appctx, "Can't abort!\nOperations on certificates are currently locked!\n");
2785
2786 if (!cafile_transaction.path) {
2787 memprintf(&err, "No ongoing transaction!\n");
2788 goto error;
2789 }
2790
2791 if (strcmp(cafile_transaction.path, args[3]) != 0) {
2792 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to abort a transaction for '%s'\n", cafile_transaction.path, args[3]);
2793 goto error;
2794 }
2795
2796 /* Only free the uncommitted cafile_entry here, because the SNI and instances were not generated yet */
2797 ssl_store_delete_cafile_entry(cafile_transaction.new_cafile_entry);
2798 cafile_transaction.new_cafile_entry = NULL;
2799 cafile_transaction.old_cafile_entry = NULL;
2800 ha_free(&cafile_transaction.path);
2801
2802 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2803
2804 err = memprintf(&err, "Transaction aborted for certificate '%s'!\n", args[3]);
2805 return cli_dynmsg(appctx, LOG_NOTICE, err);
2806
2807error:
2808 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2809
2810 return cli_dynerr(appctx, err);
2811}
2812
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002813/* release function of the `commit ssl ca-file' command, free things and unlock the spinlock */
2814static void cli_release_commit_cafile(struct appctx *appctx)
2815{
2816 if (appctx->st2 != SETCERT_ST_FIN) {
2817 struct cafile_entry *new_cafile_entry = appctx->ctx.ssl.new_cafile_entry;
2818
2819 /* Remove the uncommitted cafile_entry from the tree. */
2820 ebmb_delete(&new_cafile_entry->node);
2821 ssl_store_delete_cafile_entry(new_cafile_entry);
2822 }
2823 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2824}
2825
2826
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01002827/* IO handler of details "show ssl ca-file <filename[:index]>" */
2828static int cli_io_handler_show_cafile_detail(struct appctx *appctx)
2829{
Christopher Faulet86e1c332021-12-20 17:09:39 +01002830 struct stream_interface *si = cs_si(appctx->owner);
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01002831 struct cafile_entry *cafile_entry = appctx->ctx.cli.p0;
2832 struct buffer *out = alloc_trash_chunk();
2833 int i;
2834 X509 *cert;
2835 STACK_OF(X509_OBJECT) *objs;
2836 int retval = 0;
2837 long ca_index = (long)appctx->ctx.cli.p1;
2838
2839 if (!out)
2840 goto end_no_putchk;
2841
2842 chunk_appendf(out, "Filename: ");
2843 if (cafile_entry == cafile_transaction.new_cafile_entry)
2844 chunk_appendf(out, "*");
2845 chunk_appendf(out, "%s\n", cafile_entry->path);
2846
2847 chunk_appendf(out, "Status: ");
2848 if (!cafile_entry->ca_store)
2849 chunk_appendf(out, "Empty\n");
2850 else if (LIST_ISEMPTY(&cafile_entry->ckch_inst_link))
2851 chunk_appendf(out, "Unused\n");
2852 else
2853 chunk_appendf(out, "Used\n");
2854
2855 if (!cafile_entry->ca_store)
2856 goto end;
2857
2858 objs = X509_STORE_get0_objects(cafile_entry->ca_store);
2859 for (i = 0; i < sk_X509_OBJECT_num(objs); i++) {
2860 cert = X509_OBJECT_get0_X509(sk_X509_OBJECT_value(objs, i));
2861 if (!cert)
2862 continue;
2863
2864 /* Certificate indexes start at 1 on the CLI output. */
2865 if (ca_index && ca_index-1 != i)
2866 continue;
2867
2868 chunk_appendf(out, "\nCertificate #%d:\n", i+1);
2869 retval = show_cert_detail(cert, NULL, out);
2870 if (retval < 0)
2871 goto end_no_putchk;
2872 else if (retval || ca_index)
2873 goto end;
2874 }
2875
2876end:
2877 if (ci_putchk(si_ic(si), out) == -1) {
2878 si_rx_room_blk(si);
2879 goto yield;
2880 }
2881
2882end_no_putchk:
2883 free_trash_chunk(out);
2884 return 1;
2885yield:
2886 free_trash_chunk(out);
2887 return 0; /* should come back */
2888}
2889
2890
2891/* parsing function for 'show ssl ca-file [cafile[:index]]' */
2892static int cli_parse_show_cafile(char **args, char *payload, struct appctx *appctx, void *private)
2893{
2894 struct cafile_entry *cafile_entry;
2895 long ca_index = 0;
2896 char *colons;
2897 char *err = NULL;
2898
2899 if (!cli_has_level(appctx, ACCESS_LVL_OPER))
2900 return cli_err(appctx, "Can't allocate memory!\n");
2901
2902 /* The operations on the CKCH architecture are locked so we can
2903 * manipulate ckch_store and ckch_inst */
2904 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2905 return cli_err(appctx, "Can't show!\nOperations on certificates are currently locked!\n");
2906
2907 /* check if there is a certificate to lookup */
2908 if (*args[3]) {
2909
2910 /* Look for an optional CA index after the CA file name */
2911 colons = strchr(args[3], ':');
2912 if (colons) {
2913 char *endptr;
2914
2915 ca_index = strtol(colons + 1, &endptr, 10);
2916 /* Indexes start at 1 */
2917 if (colons + 1 == endptr || *endptr != '\0' || ca_index <= 0) {
2918 memprintf(&err, "wrong CA index after colons in '%s'!", args[3]);
2919 goto error;
2920 }
2921 *colons = '\0';
2922 }
2923
2924 if (*args[3] == '*') {
2925 if (!cafile_transaction.new_cafile_entry)
2926 goto error;
2927
2928 cafile_entry = cafile_transaction.new_cafile_entry;
2929
2930 if (strcmp(args[3] + 1, cafile_entry->path) != 0)
2931 goto error;
2932
2933 } else {
2934 /* Get the "original" cafile_entry and not the
2935 * uncommitted one if it exists. */
2936 if ((cafile_entry = ssl_store_get_cafile_entry(args[3], 1)) == NULL || cafile_entry->type != CAFILE_CERT)
2937 goto error;
2938 }
2939
2940 appctx->ctx.cli.p0 = cafile_entry;
2941 appctx->ctx.cli.p1 = (void*)ca_index;
2942 /* use the IO handler that shows details */
2943 appctx->io_handler = cli_io_handler_show_cafile_detail;
2944 }
2945
2946 return 0;
2947
2948error:
2949 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2950 if (err)
2951 return cli_dynerr(appctx, err);
2952 return cli_err(appctx, "Can't display the CA file : Not found!\n");
2953}
2954
2955
2956/* release function of the 'show ssl ca-file' command */
2957static void cli_release_show_cafile(struct appctx *appctx)
2958{
2959 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2960}
2961
2962
2963/* This function returns the number of certificates in a cafile_entry. */
2964static int get_certificate_count(struct cafile_entry *cafile_entry)
2965{
2966 int cert_count = 0;
2967 STACK_OF(X509_OBJECT) *objs;
2968
2969 if (cafile_entry && cafile_entry->ca_store) {
2970 objs = X509_STORE_get0_objects(cafile_entry->ca_store);
2971 if (objs)
2972 cert_count = sk_X509_OBJECT_num(objs);
2973 }
2974 return cert_count;
2975}
2976
2977/* IO handler of "show ssl ca-file". The command taking a specific CA file name
2978 * is managed in cli_io_handler_show_cafile_detail. */
2979static int cli_io_handler_show_cafile(struct appctx *appctx)
2980{
2981 struct buffer *trash = alloc_trash_chunk();
2982 struct ebmb_node *node;
Christopher Faulet86e1c332021-12-20 17:09:39 +01002983 struct stream_interface *si = cs_si(appctx->owner);
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01002984 struct cafile_entry *cafile_entry;
2985
2986 if (trash == NULL)
2987 return 1;
2988
2989 if (!appctx->ctx.ssl.old_cafile_entry) {
2990 if (cafile_transaction.old_cafile_entry) {
2991 chunk_appendf(trash, "# transaction\n");
2992 chunk_appendf(trash, "*%s", cafile_transaction.old_cafile_entry->path);
2993
2994 chunk_appendf(trash, " - %d certificate(s)\n", get_certificate_count(cafile_transaction.new_cafile_entry));
2995 }
2996 }
2997
2998 /* First time in this io_handler. */
2999 if (!appctx->ctx.cli.p0) {
3000 chunk_appendf(trash, "# filename\n");
3001 node = ebmb_first(&cafile_tree);
3002 } else {
3003 /* We yielded during a previous call. */
3004 node = &((struct cafile_entry*)appctx->ctx.cli.p0)->node;
3005 }
3006
3007 while (node) {
3008 cafile_entry = ebmb_entry(node, struct cafile_entry, node);
3009 if (cafile_entry->type == CAFILE_CERT) {
3010 chunk_appendf(trash, "%s", cafile_entry->path);
3011
3012 chunk_appendf(trash, " - %d certificate(s)\n", get_certificate_count(cafile_entry));
3013 }
3014
3015 node = ebmb_next(node);
3016 if (ci_putchk(si_ic(si), trash) == -1) {
3017 si_rx_room_blk(si);
3018 goto yield;
3019 }
3020 }
3021
3022 appctx->ctx.cli.p0 = NULL;
3023 free_trash_chunk(trash);
3024 return 1;
3025yield:
3026
3027 free_trash_chunk(trash);
3028 appctx->ctx.cli.p0 = cafile_entry;
3029 return 0; /* should come back */
3030}
3031
Remi Tricot-Le Bretonc3a84772021-03-25 18:13:57 +01003032/* parsing function of 'del ssl ca-file' */
3033static int cli_parse_del_cafile(char **args, char *payload, struct appctx *appctx, void *private)
3034{
3035 struct cafile_entry *cafile_entry;
3036 char *err = NULL;
3037 char *filename;
3038
3039 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
3040 return 1;
3041
3042 if (!*args[3])
3043 return cli_err(appctx, "'del ssl ca-file' expects a CA file name\n");
3044
3045 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3046 return cli_err(appctx, "Can't delete the CA file!\nOperations on certificates are currently locked!\n");
3047
3048 filename = args[3];
3049
3050 cafile_entry = ssl_store_get_cafile_entry(filename, 0);
3051 if (!cafile_entry) {
3052 memprintf(&err, "CA file '%s' doesn't exist!\n", filename);
3053 goto error;
3054 }
3055
3056 if (!LIST_ISEMPTY(&cafile_entry->ckch_inst_link)) {
3057 memprintf(&err, "CA file '%s' in use, can't be deleted!\n", filename);
3058 goto error;
3059 }
3060
3061 /* Remove the cafile_entry from the tree */
3062 ebmb_delete(&cafile_entry->node);
3063 ssl_store_delete_cafile_entry(cafile_entry);
3064
3065 memprintf(&err, "CA file '%s' deleted!\n", filename);
3066
3067 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3068 return cli_dynmsg(appctx, LOG_NOTICE, err);
3069
3070error:
3071 memprintf(&err, "Can't remove the CA file: %s\n", err ? err : "");
3072 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3073 return cli_dynerr(appctx, err);
3074}
3075
Remi Tricot-Le Breton720e3b92021-04-26 11:00:42 +02003076/* parsing function of 'new ssl crl-file' */
3077static int cli_parse_new_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
3078{
3079 struct cafile_entry *cafile_entry;
3080 char *err = NULL;
3081 char *path;
3082
3083 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
3084 return 1;
3085
3086 if (!*args[3])
3087 return cli_err(appctx, "'new ssl crl-file' expects a filename\n");
3088
3089 path = args[3];
3090
3091 /* The operations on the CKCH architecture are locked so we can
3092 * manipulate ckch_store and ckch_inst */
3093 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3094 return cli_err(appctx, "Can't create a CA file!\nOperations on certificates are currently locked!\n");
3095
3096 cafile_entry = ssl_store_get_cafile_entry(path, 0);
3097 if (cafile_entry) {
3098 memprintf(&err, "CRL file '%s' already exists!\n", path);
3099 goto error;
3100 }
3101
3102 cafile_entry = ssl_store_create_cafile_entry(path, NULL, CAFILE_CRL);
3103 if (!cafile_entry) {
3104 memprintf(&err, "%sCannot allocate memory!\n", err ? err : "");
3105 goto error;
3106 }
3107
3108 /* Add the newly created cafile_entry to the tree so that
3109 * any new ckch instance created from now can use it. */
3110 if (ssl_store_add_uncommitted_cafile_entry(cafile_entry))
3111 goto error;
3112
3113 memprintf(&err, "New CRL file created '%s'!\n", path);
3114
3115 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3116 return cli_dynmsg(appctx, LOG_NOTICE, err);
3117error:
3118 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3119 return cli_dynerr(appctx, err);
3120}
3121
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003122/* Parsing function of `set ssl crl-file` */
3123static int cli_parse_set_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
3124{
3125 char *err = NULL;
3126 int errcode = 0;
3127 struct buffer *buf;
3128
3129 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
3130 return 1;
3131
3132 if (!*args[3] || !payload)
3133 return cli_err(appctx, "'set ssl crl-file expects a filename and CAs as a payload\n");
3134
3135 /* The operations on the CKCH architecture are locked so we can
3136 * manipulate ckch_store and ckch_inst */
3137 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3138 return cli_err(appctx, "Can't update the CRL file!\nOperations on certificates are currently locked!\n");
3139
3140 if ((buf = alloc_trash_chunk()) == NULL) {
3141 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
3142 errcode |= ERR_ALERT | ERR_FATAL;
3143 goto end;
3144 }
3145
3146 if (!chunk_strcpy(buf, args[3])) {
3147 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
3148 errcode |= ERR_ALERT | ERR_FATAL;
3149 goto end;
3150 }
3151
3152 appctx->ctx.ssl.old_crlfile_entry = NULL;
3153 appctx->ctx.ssl.new_crlfile_entry = NULL;
3154
3155 /* if there is an ongoing transaction */
3156 if (crlfile_transaction.path) {
3157 /* if there is an ongoing transaction, check if this is the same file */
3158 if (strcmp(crlfile_transaction.path, buf->area) != 0) {
3159 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", crlfile_transaction.path, buf->area);
3160 errcode |= ERR_ALERT | ERR_FATAL;
3161 goto end;
3162 }
3163 appctx->ctx.ssl.old_crlfile_entry = crlfile_transaction.old_crlfile_entry;
3164 }
3165 else {
3166 /* lookup for the certificate in the tree */
3167 appctx->ctx.ssl.old_crlfile_entry = ssl_store_get_cafile_entry(buf->area, 0);
3168 }
3169
3170 if (!appctx->ctx.ssl.old_crlfile_entry) {
3171 memprintf(&err, "%sCan't replace a CRL file which is not referenced by the configuration!\n",
3172 err ? err : "");
3173 errcode |= ERR_ALERT | ERR_FATAL;
3174 goto end;
3175 }
3176
3177 if (!appctx->ctx.ssl.path) {
3178 /* this is a new transaction, set the path of the transaction */
3179 appctx->ctx.ssl.path = strdup(appctx->ctx.ssl.old_crlfile_entry->path);
3180 if (!appctx->ctx.ssl.path) {
3181 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
3182 errcode |= ERR_ALERT | ERR_FATAL;
3183 goto end;
3184 }
3185 }
3186
3187 if (appctx->ctx.ssl.new_crlfile_entry)
3188 ssl_store_delete_cafile_entry(appctx->ctx.ssl.new_crlfile_entry);
3189
3190 /* Create a new cafile_entry without adding it to the cafile tree. */
3191 appctx->ctx.ssl.new_crlfile_entry = ssl_store_create_cafile_entry(appctx->ctx.ssl.path, NULL, CAFILE_CRL);
3192 if (!appctx->ctx.ssl.new_crlfile_entry) {
3193 memprintf(&err, "%sCannot allocate memory!\n", err ? err : "");
3194 errcode |= ERR_ALERT | ERR_FATAL;
3195 goto end;
3196 }
3197
3198 /* Fill the new entry with the new CRL. */
3199 if (ssl_store_load_ca_from_buf(appctx->ctx.ssl.new_crlfile_entry, payload)) {
3200 memprintf(&err, "%sInvalid payload\n", err ? err : "");
3201 errcode |= ERR_ALERT | ERR_FATAL;
3202 goto end;
3203 }
3204
3205 /* we succeed, we can save the crl in the transaction */
3206
3207 /* if there wasn't a transaction, update the old CA */
3208 if (!crlfile_transaction.old_crlfile_entry) {
3209 crlfile_transaction.old_crlfile_entry = appctx->ctx.ssl.old_crlfile_entry;
3210 crlfile_transaction.path = appctx->ctx.ssl.path;
3211 err = memprintf(&err, "transaction created for CA %s!\n", crlfile_transaction.path);
3212 } else {
3213 err = memprintf(&err, "transaction updated for CA %s!\n", crlfile_transaction.path);
3214 }
3215
3216 /* free the previous CRL file if there was a transaction */
3217 ssl_store_delete_cafile_entry(crlfile_transaction.new_crlfile_entry);
3218
3219 crlfile_transaction.new_crlfile_entry = appctx->ctx.ssl.new_crlfile_entry;
3220
3221 /* creates the SNI ctxs later in the IO handler */
3222
3223end:
3224 free_trash_chunk(buf);
3225
3226 if (errcode & ERR_CODE) {
3227 ssl_store_delete_cafile_entry(appctx->ctx.ssl.new_crlfile_entry);
3228 appctx->ctx.ssl.new_crlfile_entry = NULL;
3229 appctx->ctx.ssl.old_crlfile_entry = NULL;
3230
Tim Duesterhus025b93e2021-11-04 21:03:52 +01003231 ha_free(&appctx->ctx.ssl.path);
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003232
3233 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3234 return cli_dynerr(appctx, memprintf(&err, "%sCan't update %s!\n", err ? err : "", args[3]));
3235 } else {
3236
3237 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3238 return cli_dynmsg(appctx, LOG_NOTICE, err);
3239 }
3240}
3241
3242/* Parsing function of 'commit ssl crl-file' */
3243static int cli_parse_commit_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
3244{
3245 char *err = NULL;
3246
3247 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
3248 return 1;
3249
3250 if (!*args[3])
3251 return cli_err(appctx, "'commit ssl ca-file expects a filename\n");
3252
3253 /* The operations on the CKCH architecture are locked so we can
3254 * manipulate ckch_store and ckch_inst */
3255 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3256 return cli_err(appctx, "Can't commit the CRL file!\nOperations on certificates are currently locked!\n");
3257
3258 if (!crlfile_transaction.path) {
3259 memprintf(&err, "No ongoing transaction! !\n");
3260 goto error;
3261 }
3262
3263 if (strcmp(crlfile_transaction.path, args[3]) != 0) {
3264 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", crlfile_transaction.path, args[3]);
3265 goto error;
3266 }
3267 /* init the appctx structure */
3268 appctx->st2 = SETCERT_ST_INIT;
3269 appctx->ctx.ssl.next_ckchi = NULL;
3270 appctx->ctx.ssl.old_crlfile_entry = crlfile_transaction.old_crlfile_entry;
3271 appctx->ctx.ssl.new_crlfile_entry = crlfile_transaction.new_crlfile_entry;
3272 appctx->ctx.ssl.cafile_type = CAFILE_CRL;
3273
3274 return 0;
3275
3276error:
3277
3278 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3279 err = memprintf(&err, "%sCan't commit %s!\n", err ? err : "", args[3]);
3280
3281 return cli_dynerr(appctx, err);
3282}
3283
3284
3285/* release function of the `commit ssl crl-file' command, free things and unlock the spinlock */
3286static void cli_release_commit_crlfile(struct appctx *appctx)
3287{
3288 if (appctx->st2 != SETCERT_ST_FIN) {
3289 struct cafile_entry *new_crlfile_entry = appctx->ctx.ssl.new_crlfile_entry;
3290
3291 /* Remove the uncommitted cafile_entry from the tree. */
3292 ebmb_delete(&new_crlfile_entry->node);
3293 ssl_store_delete_cafile_entry(new_crlfile_entry);
3294 }
3295 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3296}
3297
Remi Tricot-Le Breton720e3b92021-04-26 11:00:42 +02003298/* parsing function of 'del ssl crl-file' */
3299static int cli_parse_del_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
3300{
3301 struct cafile_entry *cafile_entry;
3302 char *err = NULL;
3303 char *filename;
3304
3305 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
3306 return 1;
3307
3308 if (!*args[3])
3309 return cli_err(appctx, "'del ssl crl-file' expects a CRL file name\n");
3310
3311 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3312 return cli_err(appctx, "Can't delete the CRL file!\nOperations on certificates are currently locked!\n");
3313
3314 filename = args[3];
3315
3316 cafile_entry = ssl_store_get_cafile_entry(filename, 0);
3317 if (!cafile_entry) {
3318 memprintf(&err, "CRL file '%s' doesn't exist!\n", filename);
3319 goto error;
3320 }
3321 if (cafile_entry->type != CAFILE_CRL) {
3322 memprintf(&err, "'del ssl crl-file' does not work on CA files!\n");
3323 goto error;
3324 }
3325
3326 if (!LIST_ISEMPTY(&cafile_entry->ckch_inst_link)) {
3327 memprintf(&err, "CRL file '%s' in use, can't be deleted!\n", filename);
3328 goto error;
3329 }
3330
3331 /* Remove the cafile_entry from the tree */
3332 ebmb_delete(&cafile_entry->node);
3333 ssl_store_delete_cafile_entry(cafile_entry);
3334
3335 memprintf(&err, "CRL file '%s' deleted!\n", filename);
3336
3337 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3338 return cli_dynmsg(appctx, LOG_NOTICE, err);
3339
3340error:
3341 memprintf(&err, "Can't remove the CRL file: %s\n", err ? err : "");
3342 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3343 return cli_dynerr(appctx, err);
3344}
3345
Remi Tricot-Le Bretoneef8e7b2021-04-20 17:42:02 +02003346/* parsing function of 'abort ssl crl-file' */
3347static int cli_parse_abort_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
3348{
3349 char *err = NULL;
3350
3351 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
3352 return 1;
3353
3354 if (!*args[3])
3355 return cli_err(appctx, "'abort ssl crl-file' expects a filename\n");
3356
3357 /* The operations on the CKCH architecture are locked so we can
3358 * manipulate ckch_store and ckch_inst */
3359 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3360 return cli_err(appctx, "Can't abort!\nOperations on certificates are currently locked!\n");
3361
3362 if (!crlfile_transaction.path) {
3363 memprintf(&err, "No ongoing transaction!\n");
3364 goto error;
3365 }
3366
3367 if (strcmp(crlfile_transaction.path, args[3]) != 0) {
3368 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to abort a transaction for '%s'\n", crlfile_transaction.path, args[3]);
3369 goto error;
3370 }
3371
3372 /* Only free the uncommitted cafile_entry here, because the SNI and instances were not generated yet */
3373 ssl_store_delete_cafile_entry(crlfile_transaction.new_crlfile_entry);
3374 crlfile_transaction.new_crlfile_entry = NULL;
3375 crlfile_transaction.old_crlfile_entry = NULL;
3376 ha_free(&crlfile_transaction.path);
3377
3378 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3379
3380 err = memprintf(&err, "Transaction aborted for certificate '%s'!\n", args[3]);
3381 return cli_dynmsg(appctx, LOG_NOTICE, err);
3382
3383error:
3384 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3385
3386 return cli_dynerr(appctx, err);
3387}
3388
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003389
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003390/*
3391 * Display a Certificate Resignation List's information.
3392 * The information displayed is inspired by the output of 'openssl crl -in
3393 * crl.pem -text'.
3394 * Returns 0 in case of success.
3395 */
3396static int show_crl_detail(X509_CRL *crl, struct buffer *out)
3397{
3398 BIO *bio = NULL;
3399 struct buffer *tmp = alloc_trash_chunk();
3400 long version;
3401 X509_NAME *issuer;
3402 int write = -1;
3403 STACK_OF(X509_REVOKED) *rev = NULL;
3404 X509_REVOKED *rev_entry = NULL;
3405 int i;
3406
3407 if (!tmp)
3408 return -1;
3409
3410 if ((bio = BIO_new(BIO_s_mem())) == NULL)
3411 goto end;
3412
3413 /* Version (as displayed by 'openssl crl') */
3414 version = X509_CRL_get_version(crl);
3415 chunk_appendf(out, "Version %ld\n", version + 1);
3416
3417 /* Signature Algorithm */
3418 chunk_appendf(out, "Signature Algorithm: %s\n", OBJ_nid2ln(X509_CRL_get_signature_nid(crl)));
3419
3420 /* Issuer */
3421 chunk_appendf(out, "Issuer: ");
3422 if ((issuer = X509_CRL_get_issuer(crl)) == NULL)
3423 goto end;
3424 if ((ssl_sock_get_dn_oneline(issuer, tmp)) == -1)
3425 goto end;
3426 *(tmp->area + tmp->data) = '\0';
3427 chunk_appendf(out, "%s\n", tmp->area);
3428
3429 /* Last Update */
3430 chunk_appendf(out, "Last Update: ");
3431 chunk_reset(tmp);
Remi Tricot-Le Bretond75b99e2021-05-17 11:45:55 +02003432 if (BIO_reset(bio) == -1)
3433 goto end;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003434 if (ASN1_TIME_print(bio, X509_CRL_get0_lastUpdate(crl)) == 0)
3435 goto end;
3436 write = BIO_read(bio, tmp->area, tmp->size-1);
3437 tmp->area[write] = '\0';
3438 chunk_appendf(out, "%s\n", tmp->area);
3439
3440
3441 /* Next Update */
3442 chunk_appendf(out, "Next Update: ");
3443 chunk_reset(tmp);
Remi Tricot-Le Bretond75b99e2021-05-17 11:45:55 +02003444 if (BIO_reset(bio) == -1)
3445 goto end;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003446 if (ASN1_TIME_print(bio, X509_CRL_get0_nextUpdate(crl)) == 0)
3447 goto end;
3448 write = BIO_read(bio, tmp->area, tmp->size-1);
3449 tmp->area[write] = '\0';
3450 chunk_appendf(out, "%s\n", tmp->area);
3451
3452
3453 /* Revoked Certificates */
3454 rev = X509_CRL_get_REVOKED(crl);
3455 if (sk_X509_REVOKED_num(rev) > 0)
3456 chunk_appendf(out, "Revoked Certificates:\n");
3457 else
3458 chunk_appendf(out, "No Revoked Certificates.\n");
3459
3460 for (i = 0; i < sk_X509_REVOKED_num(rev); i++) {
3461 rev_entry = sk_X509_REVOKED_value(rev, i);
3462
3463 /* Serial Number and Revocation Date */
Remi Tricot-Le Bretond75b99e2021-05-17 11:45:55 +02003464 if (BIO_reset(bio) == -1)
3465 goto end;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003466 BIO_printf(bio , " Serial Number: ");
Remi Tricot-Le Breton18c7d832021-05-17 18:38:34 +02003467 i2a_ASN1_INTEGER(bio, (ASN1_INTEGER*)X509_REVOKED_get0_serialNumber(rev_entry));
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003468 BIO_printf(bio, "\n Revocation Date: ");
Remi Tricot-Le Bretona6b27842021-05-18 10:06:00 +02003469 if (ASN1_TIME_print(bio, X509_REVOKED_get0_revocationDate(rev_entry)) == 0)
3470 goto end;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003471 BIO_printf(bio, "\n");
3472
3473 write = BIO_read(bio, tmp->area, tmp->size-1);
3474 tmp->area[write] = '\0';
3475 chunk_appendf(out, "%s", tmp->area);
3476 }
3477
3478end:
3479 free_trash_chunk(tmp);
3480 if (bio)
3481 BIO_free(bio);
3482
3483 return 0;
3484}
3485
3486/* IO handler of details "show ssl crl-file <filename[:index]>" */
3487static int cli_io_handler_show_crlfile_detail(struct appctx *appctx)
3488{
Christopher Faulet86e1c332021-12-20 17:09:39 +01003489 struct stream_interface *si = cs_si(appctx->owner);
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003490 struct cafile_entry *cafile_entry = appctx->ctx.cli.p0;
3491 struct buffer *out = alloc_trash_chunk();
3492 int i;
3493 X509_CRL *crl;
3494 STACK_OF(X509_OBJECT) *objs;
3495 int retval = 0;
3496 long index = (long)appctx->ctx.cli.p1;
3497
3498 if (!out)
3499 goto end_no_putchk;
3500
3501 chunk_appendf(out, "Filename: ");
3502 if (cafile_entry == crlfile_transaction.new_crlfile_entry)
3503 chunk_appendf(out, "*");
3504 chunk_appendf(out, "%s\n", cafile_entry->path);
3505
3506 chunk_appendf(out, "Status: ");
3507 if (!cafile_entry->ca_store)
3508 chunk_appendf(out, "Empty\n");
3509 else if (LIST_ISEMPTY(&cafile_entry->ckch_inst_link))
3510 chunk_appendf(out, "Unused\n");
3511 else
3512 chunk_appendf(out, "Used\n");
3513
3514 if (!cafile_entry->ca_store)
3515 goto end;
3516
3517 objs = X509_STORE_get0_objects(cafile_entry->ca_store);
3518 for (i = 0; i < sk_X509_OBJECT_num(objs); i++) {
3519 crl = X509_OBJECT_get0_X509_CRL(sk_X509_OBJECT_value(objs, i));
3520 if (!crl)
3521 continue;
3522
3523 /* CRL indexes start at 1 on the CLI output. */
3524 if (index && index-1 != i)
3525 continue;
3526
3527 chunk_appendf(out, "\nCertificate Revocation List #%d:\n", i+1);
3528 retval = show_crl_detail(crl, out);
3529 if (retval < 0)
3530 goto end_no_putchk;
3531 else if (retval || index)
3532 goto end;
3533 }
3534
3535end:
3536 if (ci_putchk(si_ic(si), out) == -1) {
3537 si_rx_room_blk(si);
3538 goto yield;
3539 }
3540
3541end_no_putchk:
3542 free_trash_chunk(out);
3543 return 1;
3544yield:
3545 free_trash_chunk(out);
3546 return 0; /* should come back */
3547}
3548
3549/* parsing function for 'show ssl crl-file [crlfile[:index]]' */
3550static int cli_parse_show_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
3551{
3552 struct cafile_entry *cafile_entry;
3553 long index = 0;
3554 char *colons;
3555 char *err = NULL;
3556
3557 if (!cli_has_level(appctx, ACCESS_LVL_OPER))
3558 return cli_err(appctx, "Can't allocate memory!\n");
3559
3560 /* The operations on the CKCH architecture are locked so we can
3561 * manipulate ckch_store and ckch_inst */
3562 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3563 return cli_err(appctx, "Can't show!\nOperations on certificates are currently locked!\n");
3564
3565 /* check if there is a certificate to lookup */
3566 if (*args[3]) {
3567
3568 /* Look for an optional index after the CRL file name */
3569 colons = strchr(args[3], ':');
3570 if (colons) {
3571 char *endptr;
3572
3573 index = strtol(colons + 1, &endptr, 10);
3574 /* Indexes start at 1 */
3575 if (colons + 1 == endptr || *endptr != '\0' || index <= 0) {
3576 memprintf(&err, "wrong CRL index after colons in '%s'!", args[3]);
3577 goto error;
3578 }
3579 *colons = '\0';
3580 }
3581
3582 if (*args[3] == '*') {
3583 if (!crlfile_transaction.new_crlfile_entry)
3584 goto error;
3585
3586 cafile_entry = crlfile_transaction.new_crlfile_entry;
3587
3588 if (strcmp(args[3] + 1, cafile_entry->path) != 0)
3589 goto error;
3590
3591 } else {
3592 /* Get the "original" cafile_entry and not the
3593 * uncommitted one if it exists. */
3594 if ((cafile_entry = ssl_store_get_cafile_entry(args[3], 1)) == NULL || cafile_entry->type != CAFILE_CRL)
3595 goto error;
3596 }
3597
3598 appctx->ctx.cli.p0 = cafile_entry;
3599 appctx->ctx.cli.p1 = (void*)index;
3600 /* use the IO handler that shows details */
3601 appctx->io_handler = cli_io_handler_show_crlfile_detail;
3602 }
3603
3604 return 0;
3605
3606error:
3607 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3608 if (err)
3609 return cli_dynerr(appctx, err);
3610 return cli_err(appctx, "Can't display the CA file : Not found!\n");
3611}
3612
3613/* IO handler of "show ssl crl-file". The command taking a specific CRL file name
3614 * is managed in cli_io_handler_show_crlfile_detail. */
3615static int cli_io_handler_show_crlfile(struct appctx *appctx)
3616{
3617 struct buffer *trash = alloc_trash_chunk();
3618 struct ebmb_node *node;
Christopher Faulet86e1c332021-12-20 17:09:39 +01003619 struct stream_interface *si = cs_si(appctx->owner);
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003620 struct cafile_entry *cafile_entry;
3621
3622 if (trash == NULL)
3623 return 1;
3624
3625 if (!appctx->ctx.ssl.old_crlfile_entry) {
3626 if (crlfile_transaction.old_crlfile_entry) {
3627 chunk_appendf(trash, "# transaction\n");
3628 chunk_appendf(trash, "*%s\n", crlfile_transaction.old_crlfile_entry->path);
3629 }
3630 }
3631
3632 /* First time in this io_handler. */
3633 if (!appctx->ctx.cli.p0) {
3634 chunk_appendf(trash, "# filename\n");
3635 node = ebmb_first(&cafile_tree);
3636 } else {
3637 /* We yielded during a previous call. */
3638 node = &((struct cafile_entry*)appctx->ctx.cli.p0)->node;
3639 }
3640
3641 while (node) {
3642 cafile_entry = ebmb_entry(node, struct cafile_entry, node);
3643 if (cafile_entry->type == CAFILE_CRL) {
3644 chunk_appendf(trash, "%s\n", cafile_entry->path);
3645 }
3646
3647 node = ebmb_next(node);
3648 if (ci_putchk(si_ic(si), trash) == -1) {
3649 si_rx_room_blk(si);
3650 goto yield;
3651 }
3652 }
3653
3654 appctx->ctx.cli.p0 = NULL;
3655 free_trash_chunk(trash);
3656 return 1;
3657yield:
3658
3659 free_trash_chunk(trash);
3660 appctx->ctx.cli.p0 = cafile_entry;
3661 return 0; /* should come back */
3662}
3663
3664
3665/* release function of the 'show ssl crl-file' command */
3666static void cli_release_show_crlfile(struct appctx *appctx)
3667{
3668 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3669}
3670
3671
William Lallemandee8530c2020-06-23 18:19:42 +02003672void ckch_deinit()
3673{
3674 struct eb_node *node, *next;
3675 struct ckch_store *store;
3676
3677 node = eb_first(&ckchs_tree);
3678 while (node) {
3679 next = eb_next(node);
3680 store = ebmb_entry(node, struct ckch_store, node);
3681 ckch_store_free(store);
3682 node = next;
3683 }
3684}
William Lallemandda8584c2020-05-14 10:14:37 +02003685
3686/* register cli keywords */
3687static struct cli_kw_list cli_kws = {{ },{
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01003688 { { "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 },
3689 { { "set", "ssl", "cert", NULL }, "set ssl cert <certfile> <payload> : replace a certificate file", cli_parse_set_cert, NULL, NULL },
3690 { { "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 },
3691 { { "abort", "ssl", "cert", NULL }, "abort ssl cert <certfile> : abort a transaction for a certificate file", cli_parse_abort_cert, NULL, NULL },
3692 { { "del", "ssl", "cert", NULL }, "del ssl cert <certfile> : delete an unused certificate file", cli_parse_del_cert, NULL, NULL },
3693 { { "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 },
3694
Amaury Denoyelleb11ad9e2021-05-21 11:01:10 +02003695 { { "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 +01003696 { { "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 +02003697 { { "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 +01003698 { { "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 +01003699 { { "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 +01003700 { { "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 +02003701
Remi Tricot-Le Breton720e3b92021-04-26 11:00:42 +02003702 { { "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 +02003703 { { "set", "ssl", "crl-file", NULL }, "set ssl crl-file <crlfile> <payload> : replace a CRL file", cli_parse_set_crlfile, NULL, NULL },
3704 { { "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 +02003705 { { "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 +02003706 { { "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 +02003707 { { "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 +02003708 { { NULL }, NULL, NULL, NULL }
3709}};
3710
3711INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws);
3712