blob: aff35ebcd79fe736e852954b06f137c941c80f27 [file] [log] [blame]
William Lallemand03c331c2020-05-13 10:10:01 +02001/*
2 *
3 * Copyright (C) 2020 HAProxy Technologies, William Lallemand <wlallemand@haproxy.com>
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version
8 * 2 of the License, or (at your option) any later version.
9 *
10 */
11
12#define _GNU_SOURCE
13#include <ctype.h>
William Lallemand87fd9942022-04-01 20:12:03 +020014#include <dirent.h>
William Lallemand03c331c2020-05-13 10:10:01 +020015#include <errno.h>
16#include <fcntl.h>
17#include <stdio.h>
18#include <stdlib.h>
19#include <string.h>
Willy Tarreauaeed4a82020-06-04 22:01:04 +020020#include <syslog.h>
William Lallemand03c331c2020-05-13 10:10:01 +020021#include <unistd.h>
22
23#include <sys/stat.h>
24#include <sys/types.h>
25
Willy Tarreau74f24562021-10-06 17:54:12 +020026#include <import/ebpttree.h>
Willy Tarreaub2551052020-06-09 09:07:15 +020027#include <import/ebsttree.h>
28
Willy Tarreau8d366972020-05-27 16:10:29 +020029#include <haproxy/base64.h>
Willy Tarreauf1d32c42020-06-04 21:07:02 +020030#include <haproxy/channel.h>
Willy Tarreau83487a82020-06-04 20:19:54 +020031#include <haproxy/cli.h>
Christopher Faulet908628c2022-03-25 16:43:49 +010032#include <haproxy/conn_stream.h>
33#include <haproxy/cs_utils.h>
Willy Tarreau8d366972020-05-27 16:10:29 +020034#include <haproxy/errors.h>
Willy Tarreau47d7f902020-06-04 14:25:47 +020035#include <haproxy/ssl_ckch.h>
Willy Tarreau209108d2020-06-04 20:30:20 +020036#include <haproxy/ssl_sock.h>
Willy Tarreaub2bd8652020-06-04 14:21:22 +020037#include <haproxy/ssl_utils.h>
Willy Tarreau48fbcae2020-06-03 18:09:46 +020038#include <haproxy/tools.h>
William Lallemand03c331c2020-05-13 10:10:01 +020039
William Lallemandda8584c2020-05-14 10:14:37 +020040/* Uncommitted CKCH transaction */
41
42static struct {
43 struct ckch_store *new_ckchs;
44 struct ckch_store *old_ckchs;
45 char *path;
46} ckchs_transaction;
47
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +010048/* Uncommitted CA file transaction */
49
50static struct {
51 struct cafile_entry *old_cafile_entry;
52 struct cafile_entry *new_cafile_entry;
53 char *path;
54} cafile_transaction;
William Lallemandda8584c2020-05-14 10:14:37 +020055
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +020056/* Uncommitted CRL file transaction */
57
58static struct {
59 struct cafile_entry *old_crlfile_entry;
60 struct cafile_entry *new_crlfile_entry;
61 char *path;
62} crlfile_transaction;
63
William Lallemand03c331c2020-05-13 10:10:01 +020064
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +010065
William Lallemand03c331c2020-05-13 10:10:01 +020066/******************** cert_key_and_chain functions *************************
67 * These are the functions that fills a cert_key_and_chain structure. For the
68 * functions filling a SSL_CTX from a cert_key_and_chain, see ssl_sock.c
69 */
70
71/*
72 * Try to parse Signed Certificate Timestamp List structure. This function
73 * makes only basic test if the data seems like SCTL. No signature validation
74 * is performed.
75 */
76static int ssl_sock_parse_sctl(struct buffer *sctl)
77{
78 int ret = 1;
79 int len, pos, sct_len;
80 unsigned char *data;
81
82 if (sctl->data < 2)
83 goto out;
84
85 data = (unsigned char *) sctl->area;
86 len = (data[0] << 8) | data[1];
87
88 if (len + 2 != sctl->data)
89 goto out;
90
91 data = data + 2;
92 pos = 0;
93 while (pos < len) {
94 if (len - pos < 2)
95 goto out;
96
97 sct_len = (data[pos] << 8) | data[pos + 1];
98 if (pos + sct_len + 2 > len)
99 goto out;
100
101 pos += sct_len + 2;
102 }
103
104 ret = 0;
105
106out:
107 return ret;
108}
109
110/* Try to load a sctl from a buffer <buf> if not NULL, or read the file <sctl_path>
111 * It fills the ckch->sctl buffer
112 * return 0 on success or != 0 on failure */
113int ssl_sock_load_sctl_from_file(const char *sctl_path, char *buf, struct cert_key_and_chain *ckch, char **err)
114{
115 int fd = -1;
116 int r = 0;
117 int ret = 1;
118 struct buffer tmp;
119 struct buffer *src;
120 struct buffer *sctl;
121
122 if (buf) {
William Lallemand8d673942021-01-27 14:58:51 +0100123 chunk_initstr(&tmp, buf);
William Lallemand03c331c2020-05-13 10:10:01 +0200124 src = &tmp;
125 } else {
126 fd = open(sctl_path, O_RDONLY);
127 if (fd == -1)
128 goto end;
129
130 trash.data = 0;
131 while (trash.data < trash.size) {
132 r = read(fd, trash.area + trash.data, trash.size - trash.data);
133 if (r < 0) {
134 if (errno == EINTR)
135 continue;
136 goto end;
137 }
138 else if (r == 0) {
139 break;
140 }
141 trash.data += r;
142 }
143 src = &trash;
144 }
145
146 ret = ssl_sock_parse_sctl(src);
147 if (ret)
148 goto end;
149
150 sctl = calloc(1, sizeof(*sctl));
151 if (!chunk_dup(sctl, src)) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100152 ha_free(&sctl);
William Lallemand03c331c2020-05-13 10:10:01 +0200153 goto end;
154 }
155 /* no error, fill ckch with new context, old context must be free */
156 if (ckch->sctl) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100157 ha_free(&ckch->sctl->area);
William Lallemand03c331c2020-05-13 10:10:01 +0200158 free(ckch->sctl);
159 }
160 ckch->sctl = sctl;
161 ret = 0;
162end:
163 if (fd != -1)
164 close(fd);
165
166 return ret;
167}
168
169#if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) || defined OPENSSL_IS_BORINGSSL)
170/*
Ilya Shipitsin46a030c2020-07-05 16:36:08 +0500171 * This function load the OCSP Response in DER format contained in file at
William Lallemand03c331c2020-05-13 10:10:01 +0200172 * path 'ocsp_path' or base64 in a buffer <buf>
173 *
174 * Returns 0 on success, 1 in error case.
175 */
176int ssl_sock_load_ocsp_response_from_file(const char *ocsp_path, char *buf, struct cert_key_and_chain *ckch, char **err)
177{
178 int fd = -1;
179 int r = 0;
180 int ret = 1;
181 struct buffer *ocsp_response;
182 struct buffer *src = NULL;
183
184 if (buf) {
185 int i, j;
186 /* if it's from a buffer it will be base64 */
187
188 /* remove \r and \n from the payload */
189 for (i = 0, j = 0; buf[i]; i++) {
190 if (buf[i] == '\r' || buf[i] == '\n')
191 continue;
192 buf[j++] = buf[i];
193 }
194 buf[j] = 0;
195
196 ret = base64dec(buf, j, trash.area, trash.size);
197 if (ret < 0) {
198 memprintf(err, "Error reading OCSP response in base64 format");
199 goto end;
200 }
201 trash.data = ret;
202 src = &trash;
203 } else {
204 fd = open(ocsp_path, O_RDONLY);
205 if (fd == -1) {
206 memprintf(err, "Error opening OCSP response file");
207 goto end;
208 }
209
210 trash.data = 0;
211 while (trash.data < trash.size) {
212 r = read(fd, trash.area + trash.data, trash.size - trash.data);
213 if (r < 0) {
214 if (errno == EINTR)
215 continue;
216
217 memprintf(err, "Error reading OCSP response from file");
218 goto end;
219 }
220 else if (r == 0) {
221 break;
222 }
223 trash.data += r;
224 }
225 close(fd);
226 fd = -1;
227 src = &trash;
228 }
229
230 ocsp_response = calloc(1, sizeof(*ocsp_response));
231 if (!chunk_dup(ocsp_response, src)) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100232 ha_free(&ocsp_response);
William Lallemand03c331c2020-05-13 10:10:01 +0200233 goto end;
234 }
235 /* no error, fill ckch with new context, old context must be free */
236 if (ckch->ocsp_response) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100237 ha_free(&ckch->ocsp_response->area);
William Lallemand03c331c2020-05-13 10:10:01 +0200238 free(ckch->ocsp_response);
239 }
240 ckch->ocsp_response = ocsp_response;
241 ret = 0;
242end:
243 if (fd != -1)
244 close(fd);
245
246 return ret;
247}
248#endif
249
250/*
251 * Try to load in a ckch every files related to a ckch.
252 * (PEM, sctl, ocsp, issuer etc.)
253 *
254 * This function is only used to load files during the configuration parsing,
255 * it is not used with the CLI.
256 *
257 * This allows us to carry the contents of the file without having to read the
258 * file multiple times. The caller must call
259 * ssl_sock_free_cert_key_and_chain_contents.
260 *
261 * returns:
262 * 0 on Success
263 * 1 on SSL Failure
264 */
265int ssl_sock_load_files_into_ckch(const char *path, struct cert_key_and_chain *ckch, char **err)
266{
William Lallemand8e8581e2020-10-20 17:36:46 +0200267 struct buffer *fp = NULL;
William Lallemand03c331c2020-05-13 10:10:01 +0200268 int ret = 1;
269
270 /* try to load the PEM */
271 if (ssl_sock_load_pem_into_ckch(path, NULL, ckch , err) != 0) {
272 goto end;
273 }
274
William Lallemand8e8581e2020-10-20 17:36:46 +0200275 fp = alloc_trash_chunk();
276 if (!fp) {
277 memprintf(err, "%sCan't allocate memory\n", err && *err ? *err : "");
278 goto end;
279 }
280
281 if (!chunk_strcpy(fp, path) || (b_data(fp) > MAXPATHLEN)) {
282 memprintf(err, "%s '%s' filename too long'.\n",
283 err && *err ? *err : "", fp->area);
284 ret = 1;
285 goto end;
286 }
287
William Lallemand089c1382020-10-23 17:35:12 +0200288 /* remove the ".crt" extension */
William Lallemand8e8581e2020-10-20 17:36:46 +0200289 if (global_ssl.extra_files_noext) {
290 char *ext;
291
292 /* look for the extension */
293 if ((ext = strrchr(fp->area, '.'))) {
William Lallemand8e8581e2020-10-20 17:36:46 +0200294
Tim Duesterhuse5ff1412021-01-02 22:31:53 +0100295 if (strcmp(ext, ".crt") == 0) {
William Lallemand8e8581e2020-10-20 17:36:46 +0200296 *ext = '\0';
William Lallemand089c1382020-10-23 17:35:12 +0200297 fp->data = strlen(fp->area);
298 }
William Lallemand8e8581e2020-10-20 17:36:46 +0200299 }
300
301 }
302
William Lallemand03c331c2020-05-13 10:10:01 +0200303 /* try to load an external private key if it wasn't in the PEM */
304 if ((ckch->key == NULL) && (global_ssl.extra_files & SSL_GF_KEY)) {
William Lallemand03c331c2020-05-13 10:10:01 +0200305 struct stat st;
306
William Lallemand8e8581e2020-10-20 17:36:46 +0200307
308 if (!chunk_strcat(fp, ".key") || (b_data(fp) > MAXPATHLEN)) {
309 memprintf(err, "%s '%s' filename too long'.\n",
310 err && *err ? *err : "", fp->area);
311 ret = 1;
312 goto end;
313 }
314
315 if (stat(fp->area, &st) == 0) {
316 if (ssl_sock_load_key_into_ckch(fp->area, NULL, ckch, err)) {
William Lallemand03c331c2020-05-13 10:10:01 +0200317 memprintf(err, "%s '%s' is present but cannot be read or parsed'.\n",
William Lallemand8e8581e2020-10-20 17:36:46 +0200318 err && *err ? *err : "", fp->area);
William Lallemand03c331c2020-05-13 10:10:01 +0200319 goto end;
320 }
321 }
William Lallemand03c331c2020-05-13 10:10:01 +0200322
William Lallemand8e8581e2020-10-20 17:36:46 +0200323 if (ckch->key == NULL) {
324 memprintf(err, "%sNo Private Key found in '%s'.\n", err && *err ? *err : "", fp->area);
325 goto end;
326 }
327 /* remove the added extension */
328 *(fp->area + fp->data - strlen(".key")) = '\0';
329 b_sub(fp, strlen(".key"));
William Lallemand03c331c2020-05-13 10:10:01 +0200330 }
331
332 if (!X509_check_private_key(ckch->cert, ckch->key)) {
333 memprintf(err, "%sinconsistencies between private key and certificate loaded '%s'.\n",
334 err && *err ? *err : "", path);
335 goto end;
336 }
337
Ilya Shipitsinc47d6762021-02-13 11:45:33 +0500338#ifdef HAVE_SSL_SCTL
William Lallemand03c331c2020-05-13 10:10:01 +0200339 /* try to load the sctl file */
340 if (global_ssl.extra_files & SSL_GF_SCTL) {
William Lallemand03c331c2020-05-13 10:10:01 +0200341 struct stat st;
342
William Lallemand8e8581e2020-10-20 17:36:46 +0200343 if (!chunk_strcat(fp, ".sctl") || b_data(fp) > MAXPATHLEN) {
344 memprintf(err, "%s '%s' filename too long'.\n",
345 err && *err ? *err : "", fp->area);
346 ret = 1;
347 goto end;
348 }
349
350 if (stat(fp->area, &st) == 0) {
351 if (ssl_sock_load_sctl_from_file(fp->area, NULL, ckch, err)) {
William Lallemand03c331c2020-05-13 10:10:01 +0200352 memprintf(err, "%s '%s.sctl' is present but cannot be read or parsed'.\n",
William Lallemand8e8581e2020-10-20 17:36:46 +0200353 err && *err ? *err : "", fp->area);
William Lallemand03c331c2020-05-13 10:10:01 +0200354 ret = 1;
355 goto end;
356 }
357 }
William Lallemand8e8581e2020-10-20 17:36:46 +0200358 /* remove the added extension */
359 *(fp->area + fp->data - strlen(".sctl")) = '\0';
360 b_sub(fp, strlen(".sctl"));
William Lallemand03c331c2020-05-13 10:10:01 +0200361 }
362#endif
363
364 /* try to load an ocsp response file */
365 if (global_ssl.extra_files & SSL_GF_OCSP) {
William Lallemand03c331c2020-05-13 10:10:01 +0200366 struct stat st;
367
William Lallemand8e8581e2020-10-20 17:36:46 +0200368 if (!chunk_strcat(fp, ".ocsp") || b_data(fp) > MAXPATHLEN) {
369 memprintf(err, "%s '%s' filename too long'.\n",
370 err && *err ? *err : "", fp->area);
371 ret = 1;
372 goto end;
373 }
374
375 if (stat(fp->area, &st) == 0) {
376 if (ssl_sock_load_ocsp_response_from_file(fp->area, NULL, ckch, err)) {
William Lallemand03c331c2020-05-13 10:10:01 +0200377 ret = 1;
378 goto end;
379 }
380 }
William Lallemand8e8581e2020-10-20 17:36:46 +0200381 /* remove the added extension */
382 *(fp->area + fp->data - strlen(".ocsp")) = '\0';
383 b_sub(fp, strlen(".ocsp"));
William Lallemand03c331c2020-05-13 10:10:01 +0200384 }
385
386#ifndef OPENSSL_IS_BORINGSSL /* Useless for BoringSSL */
387 if (ckch->ocsp_response && (global_ssl.extra_files & SSL_GF_OCSP_ISSUER)) {
388 /* if no issuer was found, try to load an issuer from the .issuer */
389 if (!ckch->ocsp_issuer) {
390 struct stat st;
William Lallemand8e8581e2020-10-20 17:36:46 +0200391
392 if (!chunk_strcat(fp, ".issuer") || b_data(fp) > MAXPATHLEN) {
393 memprintf(err, "%s '%s' filename too long'.\n",
394 err && *err ? *err : "", fp->area);
395 ret = 1;
396 goto end;
397 }
William Lallemand03c331c2020-05-13 10:10:01 +0200398
William Lallemand8e8581e2020-10-20 17:36:46 +0200399 if (stat(fp->area, &st) == 0) {
400 if (ssl_sock_load_issuer_file_into_ckch(fp->area, NULL, ckch, err)) {
William Lallemand03c331c2020-05-13 10:10:01 +0200401 ret = 1;
402 goto end;
403 }
404
405 if (X509_check_issued(ckch->ocsp_issuer, ckch->cert) != X509_V_OK) {
406 memprintf(err, "%s '%s' is not an issuer'.\n",
William Lallemand8e8581e2020-10-20 17:36:46 +0200407 err && *err ? *err : "", fp->area);
William Lallemand03c331c2020-05-13 10:10:01 +0200408 ret = 1;
409 goto end;
410 }
411 }
William Lallemand8e8581e2020-10-20 17:36:46 +0200412 /* remove the added extension */
413 *(fp->area + fp->data - strlen(".issuer")) = '\0';
414 b_sub(fp, strlen(".issuer"));
William Lallemand03c331c2020-05-13 10:10:01 +0200415 }
416 }
417#endif
418
419 ret = 0;
420
421end:
422
423 ERR_clear_error();
424
425 /* Something went wrong in one of the reads */
426 if (ret != 0)
427 ssl_sock_free_cert_key_and_chain_contents(ckch);
428
William Lallemand8e8581e2020-10-20 17:36:46 +0200429 free_trash_chunk(fp);
430
William Lallemand03c331c2020-05-13 10:10:01 +0200431 return ret;
432}
433
434/*
435 * Try to load a private key file from a <path> or a buffer <buf>
436 *
437 * If it failed you should not attempt to use the ckch but free it.
438 *
439 * Return 0 on success or != 0 on failure
440 */
441int ssl_sock_load_key_into_ckch(const char *path, char *buf, struct cert_key_and_chain *ckch , char **err)
442{
443 BIO *in = NULL;
444 int ret = 1;
445 EVP_PKEY *key = NULL;
446
447 if (buf) {
448 /* reading from a buffer */
449 in = BIO_new_mem_buf(buf, -1);
450 if (in == NULL) {
451 memprintf(err, "%sCan't allocate memory\n", err && *err ? *err : "");
452 goto end;
453 }
454
455 } else {
456 /* reading from a file */
457 in = BIO_new(BIO_s_file());
458 if (in == NULL)
459 goto end;
460
461 if (BIO_read_filename(in, path) <= 0)
462 goto end;
463 }
464
465 /* Read Private Key */
466 key = PEM_read_bio_PrivateKey(in, NULL, NULL, NULL);
467 if (key == NULL) {
468 memprintf(err, "%sunable to load private key from file '%s'.\n",
469 err && *err ? *err : "", path);
470 goto end;
471 }
472
473 ret = 0;
474
475 SWAP(ckch->key, key);
476
477end:
478
479 ERR_clear_error();
480 if (in)
481 BIO_free(in);
482 if (key)
483 EVP_PKEY_free(key);
484
485 return ret;
486}
487
488/*
489 * Try to load a PEM file from a <path> or a buffer <buf>
490 * The PEM must contain at least a Certificate,
491 * It could contain a DH, a certificate chain and a PrivateKey.
492 *
493 * If it failed you should not attempt to use the ckch but free it.
494 *
495 * Return 0 on success or != 0 on failure
496 */
497int ssl_sock_load_pem_into_ckch(const char *path, char *buf, struct cert_key_and_chain *ckch , char **err)
498{
499 BIO *in = NULL;
500 int ret = 1;
501 X509 *ca;
502 X509 *cert = NULL;
503 EVP_PKEY *key = NULL;
Remi Tricot-Le Bretonc76c3c42022-02-11 12:04:55 +0100504 HASSL_DH *dh = NULL;
William Lallemand03c331c2020-05-13 10:10:01 +0200505 STACK_OF(X509) *chain = NULL;
506
507 if (buf) {
508 /* reading from a buffer */
509 in = BIO_new_mem_buf(buf, -1);
510 if (in == NULL) {
511 memprintf(err, "%sCan't allocate memory\n", err && *err ? *err : "");
512 goto end;
513 }
514
515 } else {
516 /* reading from a file */
517 in = BIO_new(BIO_s_file());
518 if (in == NULL) {
519 memprintf(err, "%sCan't allocate memory\n", err && *err ? *err : "");
520 goto end;
521 }
522
523 if (BIO_read_filename(in, path) <= 0) {
524 memprintf(err, "%scannot open the file '%s'.\n",
525 err && *err ? *err : "", path);
526 goto end;
527 }
528 }
529
530 /* Read Private Key */
531 key = PEM_read_bio_PrivateKey(in, NULL, NULL, NULL);
532 /* no need to check for errors here, because the private key could be loaded later */
533
534#ifndef OPENSSL_NO_DH
535 /* Seek back to beginning of file */
536 if (BIO_reset(in) == -1) {
537 memprintf(err, "%san error occurred while reading the file '%s'.\n",
538 err && *err ? *err : "", path);
539 goto end;
540 }
541
Remi Tricot-Le Bretonc76c3c42022-02-11 12:04:55 +0100542 dh = ssl_sock_get_dh_from_bio(in);
543 ERR_clear_error();
William Lallemand03c331c2020-05-13 10:10:01 +0200544 /* no need to return an error there, dh is not mandatory */
545#endif
546
547 /* Seek back to beginning of file */
548 if (BIO_reset(in) == -1) {
549 memprintf(err, "%san error occurred while reading the file '%s'.\n",
550 err && *err ? *err : "", path);
551 goto end;
552 }
553
554 /* Read Certificate */
555 cert = PEM_read_bio_X509_AUX(in, NULL, NULL, NULL);
556 if (cert == NULL) {
557 memprintf(err, "%sunable to load certificate from file '%s'.\n",
558 err && *err ? *err : "", path);
559 goto end;
560 }
561
562 /* Look for a Certificate Chain */
563 while ((ca = PEM_read_bio_X509(in, NULL, NULL, NULL))) {
564 if (chain == NULL)
565 chain = sk_X509_new_null();
566 if (!sk_X509_push(chain, ca)) {
567 X509_free(ca);
568 goto end;
569 }
570 }
571
572 ret = ERR_get_error();
573 if (ret && (ERR_GET_LIB(ret) != ERR_LIB_PEM && ERR_GET_REASON(ret) != PEM_R_NO_START_LINE)) {
574 memprintf(err, "%sunable to load certificate chain from file '%s'.\n",
575 err && *err ? *err : "", path);
576 goto end;
577 }
578
579 /* once it loaded the PEM, it should remove everything else in the ckch */
580 if (ckch->ocsp_response) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100581 ha_free(&ckch->ocsp_response->area);
582 ha_free(&ckch->ocsp_response);
William Lallemand03c331c2020-05-13 10:10:01 +0200583 }
584
585 if (ckch->sctl) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100586 ha_free(&ckch->sctl->area);
587 ha_free(&ckch->sctl);
William Lallemand03c331c2020-05-13 10:10:01 +0200588 }
589
590 if (ckch->ocsp_issuer) {
591 X509_free(ckch->ocsp_issuer);
592 ckch->ocsp_issuer = NULL;
593 }
594
595 /* no error, fill ckch with new context, old context will be free at end: */
596 SWAP(ckch->key, key);
597 SWAP(ckch->dh, dh);
598 SWAP(ckch->cert, cert);
599 SWAP(ckch->chain, chain);
600
601 ret = 0;
602
603end:
604
605 ERR_clear_error();
606 if (in)
607 BIO_free(in);
608 if (key)
609 EVP_PKEY_free(key);
610 if (dh)
Remi Tricot-Le Bretonc76c3c42022-02-11 12:04:55 +0100611 HASSL_DH_free(dh);
William Lallemand03c331c2020-05-13 10:10:01 +0200612 if (cert)
613 X509_free(cert);
614 if (chain)
615 sk_X509_pop_free(chain, X509_free);
616
617 return ret;
618}
619
620/* Frees the contents of a cert_key_and_chain
621 */
622void ssl_sock_free_cert_key_and_chain_contents(struct cert_key_and_chain *ckch)
623{
624 if (!ckch)
625 return;
626
627 /* Free the certificate and set pointer to NULL */
628 if (ckch->cert)
629 X509_free(ckch->cert);
630 ckch->cert = NULL;
631
632 /* Free the key and set pointer to NULL */
633 if (ckch->key)
634 EVP_PKEY_free(ckch->key);
635 ckch->key = NULL;
636
637 /* Free each certificate in the chain */
638 if (ckch->chain)
639 sk_X509_pop_free(ckch->chain, X509_free);
640 ckch->chain = NULL;
641
642 if (ckch->dh)
Remi Tricot-Le Bretonc76c3c42022-02-11 12:04:55 +0100643 HASSL_DH_free(ckch->dh);
William Lallemand03c331c2020-05-13 10:10:01 +0200644 ckch->dh = NULL;
645
646 if (ckch->sctl) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100647 ha_free(&ckch->sctl->area);
648 ha_free(&ckch->sctl);
William Lallemand03c331c2020-05-13 10:10:01 +0200649 }
650
651 if (ckch->ocsp_response) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100652 ha_free(&ckch->ocsp_response->area);
653 ha_free(&ckch->ocsp_response);
William Lallemand03c331c2020-05-13 10:10:01 +0200654 }
655
656 if (ckch->ocsp_issuer)
657 X509_free(ckch->ocsp_issuer);
658 ckch->ocsp_issuer = NULL;
659}
660
661/*
662 *
663 * This function copy a cert_key_and_chain in memory
664 *
665 * It's used to try to apply changes on a ckch before committing them, because
666 * most of the time it's not possible to revert those changes
667 *
668 * Return a the dst or NULL
669 */
670struct cert_key_and_chain *ssl_sock_copy_cert_key_and_chain(struct cert_key_and_chain *src,
671 struct cert_key_and_chain *dst)
672{
William Lallemand6c096142021-02-23 14:45:45 +0100673 if (!src || !dst)
674 return NULL;
675
William Lallemand03c331c2020-05-13 10:10:01 +0200676 if (src->cert) {
677 dst->cert = src->cert;
678 X509_up_ref(src->cert);
679 }
680
681 if (src->key) {
682 dst->key = src->key;
683 EVP_PKEY_up_ref(src->key);
684 }
685
686 if (src->chain) {
687 dst->chain = X509_chain_up_ref(src->chain);
688 }
689
690 if (src->dh) {
Remi Tricot-Le Bretonc76c3c42022-02-11 12:04:55 +0100691 HASSL_DH_up_ref(src->dh);
William Lallemand03c331c2020-05-13 10:10:01 +0200692 dst->dh = src->dh;
693 }
694
695 if (src->sctl) {
696 struct buffer *sctl;
697
698 sctl = calloc(1, sizeof(*sctl));
699 if (!chunk_dup(sctl, src->sctl)) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100700 ha_free(&sctl);
William Lallemand03c331c2020-05-13 10:10:01 +0200701 goto error;
702 }
703 dst->sctl = sctl;
704 }
705
706 if (src->ocsp_response) {
707 struct buffer *ocsp_response;
708
709 ocsp_response = calloc(1, sizeof(*ocsp_response));
710 if (!chunk_dup(ocsp_response, src->ocsp_response)) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100711 ha_free(&ocsp_response);
William Lallemand03c331c2020-05-13 10:10:01 +0200712 goto error;
713 }
714 dst->ocsp_response = ocsp_response;
715 }
716
717 if (src->ocsp_issuer) {
718 X509_up_ref(src->ocsp_issuer);
719 dst->ocsp_issuer = src->ocsp_issuer;
720 }
721
722 return dst;
723
724error:
725
726 /* free everything */
727 ssl_sock_free_cert_key_and_chain_contents(dst);
728
729 return NULL;
730}
731
732/*
733 * return 0 on success or != 0 on failure
734 */
735int ssl_sock_load_issuer_file_into_ckch(const char *path, char *buf, struct cert_key_and_chain *ckch, char **err)
736{
737 int ret = 1;
738 BIO *in = NULL;
739 X509 *issuer;
740
741 if (buf) {
742 /* reading from a buffer */
743 in = BIO_new_mem_buf(buf, -1);
744 if (in == NULL) {
745 memprintf(err, "%sCan't allocate memory\n", err && *err ? *err : "");
746 goto end;
747 }
748
749 } else {
750 /* reading from a file */
751 in = BIO_new(BIO_s_file());
752 if (in == NULL)
753 goto end;
754
755 if (BIO_read_filename(in, path) <= 0)
756 goto end;
757 }
758
759 issuer = PEM_read_bio_X509_AUX(in, NULL, NULL, NULL);
760 if (!issuer) {
761 memprintf(err, "%s'%s' cannot be read or parsed'.\n",
762 err && *err ? *err : "", path);
763 goto end;
764 }
765 /* no error, fill ckch with new context, old context must be free */
766 if (ckch->ocsp_issuer)
767 X509_free(ckch->ocsp_issuer);
768 ckch->ocsp_issuer = issuer;
769 ret = 0;
770
771end:
772
773 ERR_clear_error();
774 if (in)
775 BIO_free(in);
776
777 return ret;
778}
779
780/******************** ckch_store functions ***********************************
781 * The ckch_store is a structure used to cache and index the SSL files used in
782 * configuration
783 */
784
785/*
786 * Free a ckch_store, its ckch, its instances and remove it from the ebtree
787 */
788void ckch_store_free(struct ckch_store *store)
789{
790 struct ckch_inst *inst, *inst_s;
791
792 if (!store)
793 return;
794
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200795 ssl_sock_free_cert_key_and_chain_contents(store->ckch);
William Lallemand03c331c2020-05-13 10:10:01 +0200796
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100797 ha_free(&store->ckch);
William Lallemand03c331c2020-05-13 10:10:01 +0200798
799 list_for_each_entry_safe(inst, inst_s, &store->ckch_inst, by_ckchs) {
800 ckch_inst_free(inst);
801 }
802 ebmb_delete(&store->node);
803 free(store);
804}
805
806/*
807 * create and initialize a ckch_store
808 * <path> is the key name
809 * <nmemb> is the number of store->ckch objects to allocate
810 *
811 * Return a ckch_store or NULL upon failure.
812 */
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200813struct ckch_store *ckch_store_new(const char *filename)
William Lallemand03c331c2020-05-13 10:10:01 +0200814{
815 struct ckch_store *store;
816 int pathlen;
817
818 pathlen = strlen(filename);
819 store = calloc(1, sizeof(*store) + pathlen + 1);
820 if (!store)
821 return NULL;
822
William Lallemand03c331c2020-05-13 10:10:01 +0200823 memcpy(store->path, filename, pathlen + 1);
824
825 LIST_INIT(&store->ckch_inst);
826 LIST_INIT(&store->crtlist_entry);
827
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200828 store->ckch = calloc(1, sizeof(*store->ckch));
William Lallemand03c331c2020-05-13 10:10:01 +0200829 if (!store->ckch)
830 goto error;
831
832 return store;
833error:
834 ckch_store_free(store);
835 return NULL;
836}
837
838/* allocate and duplicate a ckch_store
839 * Return a new ckch_store or NULL */
840struct ckch_store *ckchs_dup(const struct ckch_store *src)
841{
842 struct ckch_store *dst;
843
William Lallemand6c096142021-02-23 14:45:45 +0100844 if (!src)
845 return NULL;
846
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200847 dst = ckch_store_new(src->path);
Eric Salama6ac61e32021-02-23 16:50:57 +0100848 if (!dst)
849 return NULL;
William Lallemand03c331c2020-05-13 10:10:01 +0200850
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200851 if (!ssl_sock_copy_cert_key_and_chain(src->ckch, dst->ckch))
852 goto error;
William Lallemand03c331c2020-05-13 10:10:01 +0200853
854 return dst;
855
856error:
857 ckch_store_free(dst);
858
859 return NULL;
860}
861
862/*
863 * lookup a path into the ckchs tree.
864 */
865struct ckch_store *ckchs_lookup(char *path)
866{
867 struct ebmb_node *eb;
868
869 eb = ebst_lookup(&ckchs_tree, path);
870 if (!eb)
871 return NULL;
872
873 return ebmb_entry(eb, struct ckch_store, node);
874}
875
876/*
877 * This function allocate a ckch_store and populate it with certificates from files.
878 */
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200879struct ckch_store *ckchs_load_cert_file(char *path, char **err)
William Lallemand03c331c2020-05-13 10:10:01 +0200880{
881 struct ckch_store *ckchs;
882
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200883 ckchs = ckch_store_new(path);
William Lallemand03c331c2020-05-13 10:10:01 +0200884 if (!ckchs) {
885 memprintf(err, "%sunable to allocate memory.\n", err && *err ? *err : "");
886 goto end;
887 }
William Lallemand03c331c2020-05-13 10:10:01 +0200888
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200889 if (ssl_sock_load_files_into_ckch(path, ckchs->ckch, err) == 1)
890 goto end;
William Lallemand03c331c2020-05-13 10:10:01 +0200891
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200892 /* insert into the ckchs tree */
893 memcpy(ckchs->path, path, strlen(path) + 1);
894 ebst_insert(&ckchs_tree, &ckchs->node);
William Lallemand03c331c2020-05-13 10:10:01 +0200895 return ckchs;
896
897end:
898 ckch_store_free(ckchs);
899
900 return NULL;
901}
902
William Lallemandfa1d8b42020-05-13 15:46:10 +0200903
904/******************** ckch_inst functions ******************************/
905
906/* unlink a ckch_inst, free all SNIs, free the ckch_inst */
907/* The caller must use the lock of the bind_conf if used with inserted SNIs */
908void ckch_inst_free(struct ckch_inst *inst)
909{
910 struct sni_ctx *sni, *sni_s;
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +0100911 struct ckch_inst_link_ref *link_ref, *link_ref_s;
William Lallemandfa1d8b42020-05-13 15:46:10 +0200912
913 if (inst == NULL)
914 return;
915
916 list_for_each_entry_safe(sni, sni_s, &inst->sni_ctx, by_ckch_inst) {
917 SSL_CTX_free(sni->ctx);
Willy Tarreau2b718102021-04-21 07:32:39 +0200918 LIST_DELETE(&sni->by_ckch_inst);
William Lallemandfa1d8b42020-05-13 15:46:10 +0200919 ebmb_delete(&sni->name);
920 free(sni);
921 }
Remi Tricot-Le Bretonf3eedfe2021-01-25 17:19:44 +0100922 SSL_CTX_free(inst->ctx);
923 inst->ctx = NULL;
Willy Tarreau2b718102021-04-21 07:32:39 +0200924 LIST_DELETE(&inst->by_ckchs);
925 LIST_DELETE(&inst->by_crtlist_entry);
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +0100926
927 list_for_each_entry_safe(link_ref, link_ref_s, &inst->cafile_link_refs, list) {
928 LIST_DELETE(&link_ref->link->list);
929 LIST_DELETE(&link_ref->list);
930 free(link_ref);
931 }
932
William Lallemandfa1d8b42020-05-13 15:46:10 +0200933 free(inst);
934}
935
936/* Alloc and init a ckch_inst */
937struct ckch_inst *ckch_inst_new()
938{
939 struct ckch_inst *ckch_inst;
940
941 ckch_inst = calloc(1, sizeof *ckch_inst);
942 if (!ckch_inst)
943 return NULL;
944
945 LIST_INIT(&ckch_inst->sni_ctx);
946 LIST_INIT(&ckch_inst->by_ckchs);
947 LIST_INIT(&ckch_inst->by_crtlist_entry);
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +0100948 LIST_INIT(&ckch_inst->cafile_link_refs);
William Lallemandfa1d8b42020-05-13 15:46:10 +0200949
950 return ckch_inst;
951}
952
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +0200953
954/******************** ssl_store functions ******************************/
Remi Tricot-Le Breton9f0c9362021-02-19 15:06:28 +0100955struct eb_root cafile_tree = EB_ROOT;
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +0200956
Remi Tricot-Le Breton9f0c9362021-02-19 15:06:28 +0100957/*
958 * Returns the cafile_entry found in the cafile_tree indexed by the path 'path'.
959 * If 'oldest_entry' is 1, returns the "original" cafile_entry (since
960 * during a set cafile/commit cafile cycle there might be two entries for any
961 * given path, the original one and the new one set via the CLI but not
962 * committed yet).
963 */
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +0100964struct cafile_entry *ssl_store_get_cafile_entry(char *path, int oldest_entry)
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +0200965{
Remi Tricot-Le Breton9f0c9362021-02-19 15:06:28 +0100966 struct cafile_entry *ca_e = NULL;
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +0200967 struct ebmb_node *eb;
968
969 eb = ebst_lookup(&cafile_tree, path);
Remi Tricot-Le Breton9f0c9362021-02-19 15:06:28 +0100970 while (eb) {
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +0200971 ca_e = ebmb_entry(eb, struct cafile_entry, node);
Remi Tricot-Le Breton9f0c9362021-02-19 15:06:28 +0100972 /* The ebst_lookup in a tree that has duplicates returns the
973 * oldest entry first. If we want the latest entry, we need to
974 * iterate over all the duplicates until we find the last one
975 * (in our case there should never be more than two entries for
976 * any given path). */
977 if (oldest_entry)
978 return ca_e;
979 eb = ebmb_next_dup(eb);
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +0200980 }
Remi Tricot-Le Breton9f0c9362021-02-19 15:06:28 +0100981 return ca_e;
982}
983
Remi Tricot-Le Breton38c999b2021-02-23 16:28:43 +0100984int ssl_store_add_uncommitted_cafile_entry(struct cafile_entry *entry)
985{
986 return (ebst_insert(&cafile_tree, &entry->node) != &entry->node);
987}
988
Remi Tricot-Le Breton9f0c9362021-02-19 15:06:28 +0100989X509_STORE* ssl_store_get0_locations_file(char *path)
990{
991 struct cafile_entry *ca_e = ssl_store_get_cafile_entry(path, 0);
992
993 if (ca_e)
994 return ca_e->ca_store;
995
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +0200996 return NULL;
997}
998
Remi Tricot-Le Breton5daff3c2021-02-22 15:54:55 +0100999/* Create a cafile_entry object, without adding it to the cafile_tree. */
Remi Tricot-Le Breton0bb48242021-04-16 17:59:23 +02001000struct 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 +01001001{
1002 struct cafile_entry *ca_e;
1003 int pathlen;
1004
1005 pathlen = strlen(path);
1006
1007 ca_e = calloc(1, sizeof(*ca_e) + pathlen + 1);
1008 if (ca_e) {
1009 memcpy(ca_e->path, path, pathlen + 1);
1010 ca_e->ca_store = store;
Remi Tricot-Le Breton0bb48242021-04-16 17:59:23 +02001011 ca_e->type = type;
Remi Tricot-Le Breton5daff3c2021-02-22 15:54:55 +01001012 LIST_INIT(&ca_e->ckch_inst_link);
1013 }
1014 return ca_e;
1015}
1016
1017/* Delete a cafile_entry. The caller is responsible from removing this entry
1018 * from the cafile_tree first if is was previously added into it. */
1019void ssl_store_delete_cafile_entry(struct cafile_entry *ca_e)
1020{
1021 struct ckch_inst_link *link, *link_s;
1022 if (!ca_e)
1023 return;
1024
1025 X509_STORE_free(ca_e->ca_store);
1026
1027 list_for_each_entry_safe(link, link_s, &ca_e->ckch_inst_link, list) {
1028 struct ckch_inst *inst = link->ckch_inst;
1029 struct ckch_inst_link_ref *link_ref, *link_ref_s;
1030 list_for_each_entry_safe(link_ref, link_ref_s, &inst->cafile_link_refs, list) {
1031 if (link_ref->link == link) {
1032 LIST_DELETE(&link_ref->list);
1033 free(link_ref);
1034 break;
1035 }
1036 }
1037 LIST_DELETE(&link->list);
1038 free(link);
1039 }
1040
1041 free(ca_e);
1042}
1043
Remi Tricot-Le Breton383fb142021-02-22 18:26:14 +01001044/*
1045 * Build a cafile_entry out of a buffer instead of out of a file.
1046 * This function is used when the "commit ssl ca-file" cli command is used.
1047 * It can parse CERTIFICATE sections as well as CRL ones.
1048 * Returns 0 in case of success, 1 otherwise.
1049 */
1050int ssl_store_load_ca_from_buf(struct cafile_entry *ca_e, char *cert_buf)
1051{
1052 int retval = 0;
1053
1054 if (!ca_e)
1055 return 1;
1056
1057 if (!ca_e->ca_store) {
1058 ca_e->ca_store = X509_STORE_new();
1059 if (ca_e->ca_store) {
1060 BIO *bio = BIO_new_mem_buf(cert_buf, strlen(cert_buf));
1061 if (bio) {
1062 X509_INFO *info;
1063 int i;
1064 STACK_OF(X509_INFO) *infos = PEM_X509_INFO_read_bio(bio, NULL, NULL, NULL);
1065 if (!infos)
1066 {
1067 BIO_free(bio);
1068 return 1;
1069 }
1070
1071 for (i = 0; i < sk_X509_INFO_num(infos) && !retval; i++) {
1072 info = sk_X509_INFO_value(infos, i);
1073 /* X509_STORE_add_cert and X509_STORE_add_crl return 1 on success */
1074 if (info->x509) {
1075 retval = !X509_STORE_add_cert(ca_e->ca_store, info->x509);
1076 }
1077 if (!retval && info->crl) {
1078 retval = !X509_STORE_add_crl(ca_e->ca_store, info->crl);
1079 }
1080 }
1081 retval = retval || (i != sk_X509_INFO_num(infos));
1082
1083 /* Cleanup */
1084 sk_X509_INFO_pop_free(infos, X509_INFO_free);
1085 BIO_free(bio);
1086 }
1087 }
1088 }
1089
1090 return retval;
1091}
1092
Remi Tricot-Le Breton0bb48242021-04-16 17:59:23 +02001093int ssl_store_load_locations_file(char *path, int create_if_none, enum cafile_type type)
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001094{
1095 X509_STORE *store = ssl_store_get0_locations_file(path);
1096
1097 /* If this function is called by the CLI, we should not call the
1098 * X509_STORE_load_locations function because it performs forbidden disk
1099 * accesses. */
1100 if (!store && create_if_none) {
William Lallemand87fd9942022-04-01 20:12:03 +02001101 STACK_OF(X509_OBJECT) *objs;
1102 int cert_count = 0;
1103 struct stat buf;
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001104 struct cafile_entry *ca_e;
William Lallemandc6b17632022-04-01 23:39:37 +02001105 const char *file = NULL;
1106 const char *dir = NULL;
William Lallemand87fd9942022-04-01 20:12:03 +02001107
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001108 store = X509_STORE_new();
William Lallemand87fd9942022-04-01 20:12:03 +02001109
William Lallemandc6b17632022-04-01 23:39:37 +02001110 if (strcmp(path, "@system-ca") == 0) {
1111 dir = X509_get_default_cert_dir();
William Lallemand87fd9942022-04-01 20:12:03 +02001112
William Lallemandc6b17632022-04-01 23:39:37 +02001113 } else {
1114
1115 if (stat(path, &buf))
1116 goto err;
1117
1118 if (S_ISDIR(buf.st_mode))
1119 dir = path;
1120 else
1121 file = path;
1122 }
William Lallemand87fd9942022-04-01 20:12:03 +02001123
1124 if (file) {
1125 if (!X509_STORE_load_locations(store, file, NULL)) {
1126 goto err;
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001127 }
William Lallemand80296b42022-04-05 10:19:30 +02001128 } else if (dir) {
William Lallemand87fd9942022-04-01 20:12:03 +02001129 int n, i;
1130 struct dirent **de_list;
1131
1132 n = scandir(dir, &de_list, 0, alphasort);
1133 if (n < 0)
1134 goto err;
1135
1136 for (i= 0; i < n; i++) {
1137 char *end;
1138 struct dirent *de = de_list[i];
1139 BIO *in = NULL;
1140 X509 *ca = NULL;;
1141
1142 /* we try to load the files that would have
1143 * been loaded in an hashed directory loaded by
1144 * X509_LOOKUP_hash_dir, so according to "man 1
1145 * c_rehash", we should load ".pem", ".crt",
1146 * ".cer", or ".crl"
1147 */
1148 end = strrchr(de->d_name, '.');
1149 if (!end || (strcmp(end, ".pem") != 0 &&
1150 strcmp(end, ".crt") != 0 &&
1151 strcmp(end, ".cer") != 0 &&
1152 strcmp(end, ".crl") != 0)) {
1153 free(de);
1154 continue;
1155 }
1156 in = BIO_new(BIO_s_file());
1157 if (in == NULL)
1158 goto scandir_err;
1159
William Lallemandc6b17632022-04-01 23:39:37 +02001160 chunk_printf(&trash, "%s/%s", dir, de->d_name);
William Lallemand87fd9942022-04-01 20:12:03 +02001161
1162 if (BIO_read_filename(in, trash.area) == 0)
1163 goto scandir_err;
1164
1165 if (PEM_read_bio_X509_AUX(in, &ca, NULL, NULL) == NULL)
1166 goto scandir_err;
1167
1168 if (X509_STORE_add_cert(store, ca) == 0)
1169 goto scandir_err;
1170
William Lallemand4cfbf3c2022-04-26 15:57:33 +02001171 X509_free(ca);
William Lallemand87fd9942022-04-01 20:12:03 +02001172 BIO_free(in);
1173 free(de);
1174 continue;
1175
1176scandir_err:
William Lallemand4cfbf3c2022-04-26 15:57:33 +02001177 X509_free(ca);
William Lallemand87fd9942022-04-01 20:12:03 +02001178 BIO_free(in);
1179 free(de);
1180 ha_warning("ca-file: '%s' couldn't load '%s'\n", path, trash.area);
William Lallemand87fd9942022-04-01 20:12:03 +02001181
1182 }
1183 free(de_list);
William Lallemand80296b42022-04-05 10:19:30 +02001184 } else {
1185 ha_alert("ca-file: couldn't load '%s'\n", path);
1186 goto err;
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001187 }
William Lallemand87fd9942022-04-01 20:12:03 +02001188
1189 objs = X509_STORE_get0_objects(store);
1190 cert_count = sk_X509_OBJECT_num(objs);
1191 if (cert_count == 0)
1192 ha_warning("ca-file: 0 CA were loaded from '%s'\n", path);
1193
1194 ca_e = ssl_store_create_cafile_entry(path, store, type);
1195 if (!ca_e)
1196 goto err;
1197 ebst_insert(&cafile_tree, &ca_e->node);
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001198 }
1199 return (store != NULL);
William Lallemand87fd9942022-04-01 20:12:03 +02001200
1201err:
1202 X509_STORE_free(store);
1203 store = NULL;
1204 return 0;
1205
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001206}
1207
1208
William Lallemandda8584c2020-05-14 10:14:37 +02001209/*************************** CLI commands ***********************/
1210
1211/* Type of SSL payloads that can be updated over the CLI */
1212
William Lallemandff8bf982022-03-29 10:44:23 +02001213struct cert_exts cert_exts[] = {
1214 { "", CERT_TYPE_PEM, &ssl_sock_load_pem_into_ckch }, /* default mode, no extensions */
William Lallemand26654e72022-03-30 12:01:32 +02001215 { "crt", CERT_TYPE_CRT, &ssl_sock_load_pem_into_ckch },
William Lallemandff8bf982022-03-29 10:44:23 +02001216 { "key", CERT_TYPE_KEY, &ssl_sock_load_key_into_ckch },
William Lallemandda8584c2020-05-14 10:14:37 +02001217#if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) || defined OPENSSL_IS_BORINGSSL)
William Lallemandff8bf982022-03-29 10:44:23 +02001218 { "ocsp", CERT_TYPE_OCSP, &ssl_sock_load_ocsp_response_from_file },
William Lallemandda8584c2020-05-14 10:14:37 +02001219#endif
Ilya Shipitsinc47d6762021-02-13 11:45:33 +05001220#ifdef HAVE_SSL_SCTL
William Lallemandff8bf982022-03-29 10:44:23 +02001221 { "sctl", CERT_TYPE_SCTL, &ssl_sock_load_sctl_from_file },
William Lallemandda8584c2020-05-14 10:14:37 +02001222#endif
William Lallemandff8bf982022-03-29 10:44:23 +02001223 { "issuer", CERT_TYPE_ISSUER, &ssl_sock_load_issuer_file_into_ckch },
1224 { NULL, CERT_TYPE_MAX, NULL },
William Lallemandda8584c2020-05-14 10:14:37 +02001225};
1226
1227
1228/* release function of the `show ssl cert' command */
1229static void cli_release_show_cert(struct appctx *appctx)
1230{
1231 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1232}
1233
1234/* IO handler of "show ssl cert <filename>" */
1235static int cli_io_handler_show_cert(struct appctx *appctx)
1236{
1237 struct buffer *trash = alloc_trash_chunk();
1238 struct ebmb_node *node;
Christopher Faulet908628c2022-03-25 16:43:49 +01001239 struct conn_stream *cs = appctx->owner;
William Lallemandda8584c2020-05-14 10:14:37 +02001240 struct ckch_store *ckchs;
1241
1242 if (trash == NULL)
1243 return 1;
1244
1245 if (!appctx->ctx.ssl.old_ckchs) {
1246 if (ckchs_transaction.old_ckchs) {
1247 ckchs = ckchs_transaction.old_ckchs;
1248 chunk_appendf(trash, "# transaction\n");
William Lallemand5685ccf2020-09-16 16:12:25 +02001249 chunk_appendf(trash, "*%s\n", ckchs->path);
William Lallemandda8584c2020-05-14 10:14:37 +02001250 }
1251 }
1252
1253 if (!appctx->ctx.cli.p0) {
1254 chunk_appendf(trash, "# filename\n");
1255 node = ebmb_first(&ckchs_tree);
1256 } else {
1257 node = &((struct ckch_store *)appctx->ctx.cli.p0)->node;
1258 }
1259 while (node) {
1260 ckchs = ebmb_entry(node, struct ckch_store, node);
William Lallemand5685ccf2020-09-16 16:12:25 +02001261 chunk_appendf(trash, "%s\n", ckchs->path);
William Lallemandda8584c2020-05-14 10:14:37 +02001262
1263 node = ebmb_next(node);
Christopher Faulet908628c2022-03-25 16:43:49 +01001264 if (ci_putchk(cs_ic(cs), trash) == -1) {
Christopher Fauleta0bdec32022-04-04 07:51:21 +02001265 cs_rx_room_blk(cs);
William Lallemandda8584c2020-05-14 10:14:37 +02001266 goto yield;
1267 }
1268 }
1269
1270 appctx->ctx.cli.p0 = NULL;
1271 free_trash_chunk(trash);
1272 return 1;
1273yield:
1274
1275 free_trash_chunk(trash);
1276 appctx->ctx.cli.p0 = ckchs;
1277 return 0; /* should come back */
1278}
1279
1280/*
1281 * Extract and format the DNS SAN extensions and copy result into a chuink
1282 * Return 0;
1283 */
1284#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
1285static int ssl_sock_get_san_oneline(X509 *cert, struct buffer *out)
1286{
1287 int i;
1288 char *str;
1289 STACK_OF(GENERAL_NAME) *names = NULL;
1290
1291 names = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
1292 if (names) {
1293 for (i = 0; i < sk_GENERAL_NAME_num(names); i++) {
1294 GENERAL_NAME *name = sk_GENERAL_NAME_value(names, i);
1295 if (i > 0)
1296 chunk_appendf(out, ", ");
1297 if (name->type == GEN_DNS) {
1298 if (ASN1_STRING_to_UTF8((unsigned char **)&str, name->d.dNSName) >= 0) {
1299 chunk_appendf(out, "DNS:%s", str);
1300 OPENSSL_free(str);
1301 }
1302 }
1303 }
1304 sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free);
1305 }
1306 return 0;
1307}
1308#endif
1309
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001310/*
1311 * Build the ckch_inst_link that will be chained in the CA file entry and the
1312 * corresponding ckch_inst_link_ref that will be chained in the ckch instance.
1313 * Return 0 in case of success.
1314 */
1315static int do_chain_inst_and_cafile(struct cafile_entry *cafile_entry, struct ckch_inst *ckch_inst)
1316{
1317 struct ckch_inst_link *new_link;
1318 if (!LIST_ISEMPTY(&cafile_entry->ckch_inst_link)) {
1319 struct ckch_inst_link *link = LIST_ELEM(cafile_entry->ckch_inst_link.n,
1320 typeof(link), list);
1321 /* Do not add multiple references to the same
1322 * instance in a cafile_entry */
1323 if (link->ckch_inst == ckch_inst) {
1324 return 1;
1325 }
1326 }
1327
1328 new_link = calloc(1, sizeof(*new_link));
1329 if (new_link) {
1330 struct ckch_inst_link_ref *new_link_ref = calloc(1, sizeof(*new_link_ref));
1331 if (!new_link_ref) {
1332 free(new_link);
1333 return 1;
1334 }
1335
1336 new_link->ckch_inst = ckch_inst;
1337 new_link_ref->link = new_link;
1338 LIST_INIT(&new_link->list);
1339 LIST_INIT(&new_link_ref->list);
1340
1341 LIST_APPEND(&cafile_entry->ckch_inst_link, &new_link->list);
1342 LIST_APPEND(&ckch_inst->cafile_link_refs, &new_link_ref->list);
1343 }
1344
1345 return 0;
1346}
1347
1348
1349/*
1350 * Link a CA file tree entry to the ckch instance that uses it.
1351 * To determine if and which CA file tree entries need to be linked to the
1352 * instance, we follow the same logic performed in ssl_sock_prepare_ctx when
1353 * processing the verify option.
1354 * This function works for a frontend as well as for a backend, depending on the
1355 * configuration parameters given (bind_conf or server).
1356 */
1357void ckch_inst_add_cafile_link(struct ckch_inst *ckch_inst, struct bind_conf *bind_conf,
1358 struct ssl_bind_conf *ssl_conf, const struct server *srv)
1359{
1360 int verify = SSL_VERIFY_NONE;
1361
1362 if (srv) {
1363
1364 if (global.ssl_server_verify == SSL_SERVER_VERIFY_REQUIRED)
1365 verify = SSL_VERIFY_PEER;
1366 switch (srv->ssl_ctx.verify) {
1367 case SSL_SOCK_VERIFY_NONE:
1368 verify = SSL_VERIFY_NONE;
1369 break;
1370 case SSL_SOCK_VERIFY_REQUIRED:
1371 verify = SSL_VERIFY_PEER;
1372 break;
1373 }
1374 }
1375 else {
1376 switch ((ssl_conf && ssl_conf->verify) ? ssl_conf->verify : bind_conf->ssl_conf.verify) {
1377 case SSL_SOCK_VERIFY_NONE:
1378 verify = SSL_VERIFY_NONE;
1379 break;
1380 case SSL_SOCK_VERIFY_OPTIONAL:
1381 verify = SSL_VERIFY_PEER;
1382 break;
1383 case SSL_SOCK_VERIFY_REQUIRED:
1384 verify = SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
1385 break;
1386 }
1387 }
1388
1389 if (verify & SSL_VERIFY_PEER) {
1390 struct cafile_entry *ca_file_entry = NULL;
1391 struct cafile_entry *ca_verify_file_entry = NULL;
Remi Tricot-Le Bretonf81c70c2021-04-20 16:54:21 +02001392 struct cafile_entry *crl_file_entry = NULL;
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001393 if (srv) {
1394 if (srv->ssl_ctx.ca_file) {
1395 ca_file_entry = ssl_store_get_cafile_entry(srv->ssl_ctx.ca_file, 0);
1396
1397 }
Remi Tricot-Le Bretonf81c70c2021-04-20 16:54:21 +02001398 if (srv->ssl_ctx.crl_file) {
1399 crl_file_entry = ssl_store_get_cafile_entry(srv->ssl_ctx.crl_file, 0);
1400 }
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001401 }
1402 else {
1403 char *ca_file = (ssl_conf && ssl_conf->ca_file) ? ssl_conf->ca_file : bind_conf->ssl_conf.ca_file;
1404 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 +02001405 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 +01001406
1407 if (ca_file)
1408 ca_file_entry = ssl_store_get_cafile_entry(ca_file, 0);
1409 if (ca_verify_file)
1410 ca_verify_file_entry = ssl_store_get_cafile_entry(ca_verify_file, 0);
Remi Tricot-Le Bretonf81c70c2021-04-20 16:54:21 +02001411 if (crl_file)
1412 crl_file_entry = ssl_store_get_cafile_entry(crl_file, 0);
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001413 }
1414
1415 if (ca_file_entry) {
1416 /* If we have a ckch instance that is not already in the
1417 * cafile_entry's list, add it to it. */
1418 if (do_chain_inst_and_cafile(ca_file_entry, ckch_inst))
1419 return;
1420
1421 }
1422 if (ca_verify_file_entry && (ca_file_entry != ca_verify_file_entry)) {
1423 /* If we have a ckch instance that is not already in the
1424 * cafile_entry's list, add it to it. */
1425 if (do_chain_inst_and_cafile(ca_verify_file_entry, ckch_inst))
1426 return;
1427 }
Remi Tricot-Le Bretonf81c70c2021-04-20 16:54:21 +02001428 if (crl_file_entry) {
1429 /* If we have a ckch instance that is not already in the
1430 * cafile_entry's list, add it to it. */
1431 if (do_chain_inst_and_cafile(crl_file_entry, ckch_inst))
1432 return;
1433 }
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001434 }
1435}
1436
William Lallemandda8584c2020-05-14 10:14:37 +02001437
1438
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001439static int show_cert_detail(X509 *cert, STACK_OF(X509) *chain, struct buffer *out)
William Lallemandda8584c2020-05-14 10:14:37 +02001440{
William Lallemandda8584c2020-05-14 10:14:37 +02001441 BIO *bio = NULL;
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001442 struct buffer *tmp = alloc_trash_chunk();
William Lallemandda8584c2020-05-14 10:14:37 +02001443 int i;
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001444 int write = -1;
1445 unsigned int len = 0;
1446 X509_NAME *name = NULL;
William Lallemandda8584c2020-05-14 10:14:37 +02001447
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001448 if (!tmp)
1449 return -1;
William Lallemandda8584c2020-05-14 10:14:37 +02001450
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001451 if (!cert)
William Lallemand5685ccf2020-09-16 16:12:25 +02001452 goto end;
William Lallemandda8584c2020-05-14 10:14:37 +02001453
William Lallemand5685ccf2020-09-16 16:12:25 +02001454 if (chain == NULL) {
1455 struct issuer_chain *issuer;
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001456 issuer = ssl_get0_issuer_chain(cert);
William Lallemand5685ccf2020-09-16 16:12:25 +02001457 if (issuer) {
1458 chain = issuer->chain;
1459 chunk_appendf(out, "Chain Filename: ");
1460 chunk_appendf(out, "%s\n", issuer->path);
William Lallemandda8584c2020-05-14 10:14:37 +02001461 }
William Lallemand5685ccf2020-09-16 16:12:25 +02001462 }
1463 chunk_appendf(out, "Serial: ");
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001464 if (ssl_sock_get_serial(cert, tmp) == -1)
William Lallemand5685ccf2020-09-16 16:12:25 +02001465 goto end;
1466 dump_binary(out, tmp->area, tmp->data);
1467 chunk_appendf(out, "\n");
William Lallemandda8584c2020-05-14 10:14:37 +02001468
William Lallemand5685ccf2020-09-16 16:12:25 +02001469 chunk_appendf(out, "notBefore: ");
1470 chunk_reset(tmp);
1471 if ((bio = BIO_new(BIO_s_mem())) == NULL)
1472 goto end;
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001473 if (ASN1_TIME_print(bio, X509_getm_notBefore(cert)) == 0)
William Lallemand5685ccf2020-09-16 16:12:25 +02001474 goto end;
1475 write = BIO_read(bio, tmp->area, tmp->size-1);
1476 tmp->area[write] = '\0';
1477 BIO_free(bio);
1478 bio = NULL;
1479 chunk_appendf(out, "%s\n", tmp->area);
William Lallemandda8584c2020-05-14 10:14:37 +02001480
William Lallemand5685ccf2020-09-16 16:12:25 +02001481 chunk_appendf(out, "notAfter: ");
1482 chunk_reset(tmp);
1483 if ((bio = BIO_new(BIO_s_mem())) == NULL)
1484 goto end;
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001485 if (ASN1_TIME_print(bio, X509_getm_notAfter(cert)) == 0)
William Lallemand5685ccf2020-09-16 16:12:25 +02001486 goto end;
1487 if ((write = BIO_read(bio, tmp->area, tmp->size-1)) <= 0)
1488 goto end;
1489 tmp->area[write] = '\0';
1490 BIO_free(bio);
1491 bio = NULL;
1492 chunk_appendf(out, "%s\n", tmp->area);
William Lallemandda8584c2020-05-14 10:14:37 +02001493
1494#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
William Lallemand5685ccf2020-09-16 16:12:25 +02001495 chunk_appendf(out, "Subject Alternative Name: ");
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001496 if (ssl_sock_get_san_oneline(cert, out) == -1)
William Lallemand5685ccf2020-09-16 16:12:25 +02001497 goto end;
1498 *(out->area + out->data) = '\0';
1499 chunk_appendf(out, "\n");
William Lallemandda8584c2020-05-14 10:14:37 +02001500#endif
William Lallemand5685ccf2020-09-16 16:12:25 +02001501 chunk_reset(tmp);
1502 chunk_appendf(out, "Algorithm: ");
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001503 if (cert_get_pkey_algo(cert, tmp) == 0)
William Lallemand5685ccf2020-09-16 16:12:25 +02001504 goto end;
1505 chunk_appendf(out, "%s\n", tmp->area);
William Lallemandda8584c2020-05-14 10:14:37 +02001506
William Lallemand5685ccf2020-09-16 16:12:25 +02001507 chunk_reset(tmp);
1508 chunk_appendf(out, "SHA1 FingerPrint: ");
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001509 if (X509_digest(cert, EVP_sha1(), (unsigned char *) tmp->area, &len) == 0)
William Lallemand5685ccf2020-09-16 16:12:25 +02001510 goto end;
1511 tmp->data = len;
1512 dump_binary(out, tmp->area, tmp->data);
1513 chunk_appendf(out, "\n");
William Lallemandda8584c2020-05-14 10:14:37 +02001514
William Lallemand5685ccf2020-09-16 16:12:25 +02001515 chunk_appendf(out, "Subject: ");
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001516 if ((name = X509_get_subject_name(cert)) == NULL)
William Lallemand5685ccf2020-09-16 16:12:25 +02001517 goto end;
1518 if ((ssl_sock_get_dn_oneline(name, tmp)) == -1)
1519 goto end;
1520 *(tmp->area + tmp->data) = '\0';
1521 chunk_appendf(out, "%s\n", tmp->area);
1522
1523 chunk_appendf(out, "Issuer: ");
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001524 if ((name = X509_get_issuer_name(cert)) == NULL)
William Lallemand5685ccf2020-09-16 16:12:25 +02001525 goto end;
1526 if ((ssl_sock_get_dn_oneline(name, tmp)) == -1)
1527 goto end;
1528 *(tmp->area + tmp->data) = '\0';
1529 chunk_appendf(out, "%s\n", tmp->area);
1530
1531 /* Displays subject of each certificate in the chain */
1532 for (i = 0; i < sk_X509_num(chain); i++) {
1533 X509 *ca = sk_X509_value(chain, i);
1534
1535 chunk_appendf(out, "Chain Subject: ");
1536 if ((name = X509_get_subject_name(ca)) == NULL)
William Lallemandda8584c2020-05-14 10:14:37 +02001537 goto end;
1538 if ((ssl_sock_get_dn_oneline(name, tmp)) == -1)
1539 goto end;
1540 *(tmp->area + tmp->data) = '\0';
1541 chunk_appendf(out, "%s\n", tmp->area);
1542
William Lallemand5685ccf2020-09-16 16:12:25 +02001543 chunk_appendf(out, "Chain Issuer: ");
1544 if ((name = X509_get_issuer_name(ca)) == NULL)
William Lallemandda8584c2020-05-14 10:14:37 +02001545 goto end;
1546 if ((ssl_sock_get_dn_oneline(name, tmp)) == -1)
1547 goto end;
1548 *(tmp->area + tmp->data) = '\0';
1549 chunk_appendf(out, "%s\n", tmp->area);
William Lallemandda8584c2020-05-14 10:14:37 +02001550 }
1551
1552end:
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001553 if (bio)
1554 BIO_free(bio);
1555 free_trash_chunk(tmp);
1556
1557 return 0;
1558}
1559
Remi Tricot-Le Breton3faf0cb2021-06-10 18:10:32 +02001560#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 +02001561/*
1562 * Build the OCSP tree entry's key for a given ckch_store.
1563 * Returns a negative value in case of error.
1564 */
1565static int ckch_store_build_certid(struct ckch_store *ckch_store, unsigned char certid[128], unsigned int *key_length)
1566{
1567 OCSP_RESPONSE *resp;
1568 OCSP_BASICRESP *bs = NULL;
1569 OCSP_SINGLERESP *sr;
1570 OCSP_CERTID *id;
1571 unsigned char *p = NULL;
1572
1573 if (!key_length)
1574 return -1;
1575
1576 *key_length = 0;
1577
1578 if (!ckch_store->ckch->ocsp_response)
1579 return 0;
1580
1581 p = (unsigned char *) ckch_store->ckch->ocsp_response->area;
1582
1583 resp = d2i_OCSP_RESPONSE(NULL, (const unsigned char **)&p,
1584 ckch_store->ckch->ocsp_response->data);
1585 if (!resp) {
1586 goto end;
1587 }
1588
1589 bs = OCSP_response_get1_basic(resp);
1590 if (!bs) {
1591 goto end;
1592 }
1593
1594 sr = OCSP_resp_get0(bs, 0);
1595 if (!sr) {
1596 goto end;
1597 }
1598
1599 id = (OCSP_CERTID*)OCSP_SINGLERESP_get0_id(sr);
1600
1601 p = certid;
1602 *key_length = i2d_OCSP_CERTID(id, &p);
1603
1604end:
1605 return *key_length > 0;
1606}
1607#endif
1608
1609/*
1610 * Dump the OCSP certificate key (if it exists) of certificate <ckch> into
1611 * buffer <out>.
1612 * Returns 0 in case of success.
1613 */
1614static int ckch_store_show_ocsp_certid(struct ckch_store *ckch_store, struct buffer *out)
1615{
Remi Tricot-Le Breton3faf0cb2021-06-10 18:10:32 +02001616#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 +02001617 unsigned char key[OCSP_MAX_CERTID_ASN1_LENGTH] = {};
1618 unsigned int key_length = 0;
1619 int i;
1620
1621 if (ckch_store_build_certid(ckch_store, (unsigned char*)key, &key_length) >= 0) {
1622 /* Dump the CERTID info */
1623 chunk_appendf(out, "OCSP Response Key: ");
1624 for (i = 0; i < key_length; ++i) {
1625 chunk_appendf(out, "%02x", key[i]);
1626 }
1627 chunk_appendf(out, "\n");
1628 }
1629#endif
1630
1631 return 0;
1632}
1633
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001634
1635/* IO handler of the details "show ssl cert <filename>" */
1636static int cli_io_handler_show_cert_detail(struct appctx *appctx)
1637{
Christopher Faulet908628c2022-03-25 16:43:49 +01001638 struct conn_stream *cs = appctx->owner;
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001639 struct ckch_store *ckchs = appctx->ctx.cli.p0;
1640 struct buffer *out = alloc_trash_chunk();
1641 int retval = 0;
1642
1643 if (!out)
1644 goto end_no_putchk;
1645
1646 chunk_appendf(out, "Filename: ");
1647 if (ckchs == ckchs_transaction.new_ckchs)
1648 chunk_appendf(out, "*");
1649 chunk_appendf(out, "%s\n", ckchs->path);
1650
1651 chunk_appendf(out, "Status: ");
1652 if (ckchs->ckch->cert == NULL)
1653 chunk_appendf(out, "Empty\n");
1654 else if (LIST_ISEMPTY(&ckchs->ckch_inst))
1655 chunk_appendf(out, "Unused\n");
1656 else
1657 chunk_appendf(out, "Used\n");
1658
1659 retval = show_cert_detail(ckchs->ckch->cert, ckchs->ckch->chain, out);
1660 if (retval < 0)
1661 goto end_no_putchk;
1662 else if (retval)
1663 goto end;
1664
Remi Tricot-Le Bretonda968f62021-06-10 13:51:14 +02001665 ckch_store_show_ocsp_certid(ckchs, out);
1666
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001667end:
Christopher Faulet908628c2022-03-25 16:43:49 +01001668 if (ci_putchk(cs_ic(cs), out) == -1) {
Christopher Fauleta0bdec32022-04-04 07:51:21 +02001669 cs_rx_room_blk(cs);
William Lallemandda8584c2020-05-14 10:14:37 +02001670 goto yield;
1671 }
1672
1673end_no_putchk:
William Lallemandda8584c2020-05-14 10:14:37 +02001674 free_trash_chunk(out);
1675 return 1;
1676yield:
William Lallemandda8584c2020-05-14 10:14:37 +02001677 free_trash_chunk(out);
1678 return 0; /* should come back */
1679}
1680
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001681
1682/* IO handler of the details "show ssl cert <filename.ocsp>" */
1683static int cli_io_handler_show_cert_ocsp_detail(struct appctx *appctx)
1684{
Remi Tricot-Le Breton3faf0cb2021-06-10 18:10:32 +02001685#if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) && !defined OPENSSL_IS_BORINGSSL)
Christopher Faulet908628c2022-03-25 16:43:49 +01001686 struct conn_stream *cs = appctx->owner;
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001687 struct ckch_store *ckchs = appctx->ctx.cli.p0;
1688 struct buffer *out = alloc_trash_chunk();
1689 int from_transaction = appctx->ctx.cli.i0;
1690
1691 if (!out)
1692 goto end_no_putchk;
1693
1694 /* If we try to display an ongoing transaction's OCSP response, we
1695 * need to dump the ckch's ocsp_response buffer directly.
1696 * Otherwise, we must rebuild the certificate's certid in order to
1697 * look for the current OCSP response in the tree. */
1698 if (from_transaction && ckchs->ckch->ocsp_response) {
Remi Tricot-Le Bretona9a591a2022-02-16 14:42:22 +01001699 if (ssl_ocsp_response_print(ckchs->ckch->ocsp_response, out))
1700 goto end_no_putchk;
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001701 }
1702 else {
1703 unsigned char key[OCSP_MAX_CERTID_ASN1_LENGTH] = {};
1704 unsigned int key_length = 0;
1705
1706 if (ckch_store_build_certid(ckchs, (unsigned char*)key, &key_length) < 0)
1707 goto end_no_putchk;
1708
Remi Tricot-Le Bretona9a591a2022-02-16 14:42:22 +01001709 if (ssl_get_ocspresponse_detail(key, out))
1710 goto end_no_putchk;
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001711 }
1712
Christopher Faulet908628c2022-03-25 16:43:49 +01001713 if (ci_putchk(cs_ic(cs), out) == -1) {
Christopher Fauleta0bdec32022-04-04 07:51:21 +02001714 cs_rx_room_blk(cs);
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001715 goto yield;
1716 }
1717
1718end_no_putchk:
1719 free_trash_chunk(out);
1720 return 1;
1721yield:
1722 free_trash_chunk(out);
1723 return 0; /* should come back */
1724#else
1725 return cli_err(appctx, "HAProxy was compiled against a version of OpenSSL that doesn't support OCSP stapling.\n");
1726#endif
1727}
1728
William Lallemandda8584c2020-05-14 10:14:37 +02001729/* parsing function for 'show ssl cert [certfile]' */
1730static int cli_parse_show_cert(char **args, char *payload, struct appctx *appctx, void *private)
1731{
1732 struct ckch_store *ckchs;
1733
1734 if (!cli_has_level(appctx, ACCESS_LVL_OPER))
1735 return cli_err(appctx, "Can't allocate memory!\n");
1736
1737 /* The operations on the CKCH architecture are locked so we can
1738 * manipulate ckch_store and ckch_inst */
1739 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
1740 return cli_err(appctx, "Can't show!\nOperations on certificates are currently locked!\n");
1741
1742 /* check if there is a certificate to lookup */
1743 if (*args[3]) {
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001744 int show_ocsp_detail = 0;
1745 int from_transaction = 0;
1746 char *end;
1747
1748 /* We manage the special case "certname.ocsp" through which we
1749 * can show the details of an OCSP response. */
1750 end = strrchr(args[3], '.');
1751 if (end && strcmp(end+1, "ocsp") == 0) {
1752 *end = '\0';
1753 show_ocsp_detail = 1;
1754 }
1755
William Lallemandda8584c2020-05-14 10:14:37 +02001756 if (*args[3] == '*') {
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001757 from_transaction = 1;
William Lallemandda8584c2020-05-14 10:14:37 +02001758 if (!ckchs_transaction.new_ckchs)
1759 goto error;
1760
1761 ckchs = ckchs_transaction.new_ckchs;
1762
Tim Duesterhuse5ff1412021-01-02 22:31:53 +01001763 if (strcmp(args[3] + 1, ckchs->path) != 0)
William Lallemandda8584c2020-05-14 10:14:37 +02001764 goto error;
1765
1766 } else {
1767 if ((ckchs = ckchs_lookup(args[3])) == NULL)
1768 goto error;
1769
1770 }
1771
William Lallemandda8584c2020-05-14 10:14:37 +02001772 appctx->ctx.cli.p0 = ckchs;
1773 /* use the IO handler that shows details */
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001774 if (show_ocsp_detail) {
1775 appctx->ctx.cli.i0 = from_transaction;
1776 appctx->io_handler = cli_io_handler_show_cert_ocsp_detail;
1777 }
1778 else
1779 appctx->io_handler = cli_io_handler_show_cert_detail;
William Lallemandda8584c2020-05-14 10:14:37 +02001780 }
1781
1782 return 0;
1783
1784error:
1785 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1786 return cli_err(appctx, "Can't display the certificate: Not found or the certificate is a bundle!\n");
1787}
1788
1789/* release function of the `set ssl cert' command, free things and unlock the spinlock */
1790static void cli_release_commit_cert(struct appctx *appctx)
1791{
1792 struct ckch_store *new_ckchs;
1793
1794 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1795
1796 if (appctx->st2 != SETCERT_ST_FIN) {
1797 /* free every new sni_ctx and the new store, which are not in the trees so no spinlock there */
1798 new_ckchs = appctx->ctx.ssl.new_ckchs;
1799
1800 /* if the allocation failed, we need to free everything from the temporary list */
1801 ckch_store_free(new_ckchs);
1802 }
1803}
1804
Remi Tricot-Le Bretonbfadc022021-02-24 12:20:48 +01001805
1806/*
1807 * Rebuild a new instance 'new_inst' based on an old instance 'ckchi' and a
1808 * specific ckch_store.
1809 * Returns 0 in case of success, 1 otherwise.
1810 */
William Lallemande60c7d62022-03-30 11:26:15 +02001811int ckch_inst_rebuild(struct ckch_store *ckch_store, struct ckch_inst *ckchi,
1812 struct ckch_inst **new_inst, char **err)
Remi Tricot-Le Bretonbfadc022021-02-24 12:20:48 +01001813{
1814 int retval = 0;
1815 int errcode = 0;
1816 struct sni_ctx *sc0, *sc0s;
1817 char **sni_filter = NULL;
1818 int fcount = 0;
1819
1820 if (ckchi->crtlist_entry) {
1821 sni_filter = ckchi->crtlist_entry->filters;
1822 fcount = ckchi->crtlist_entry->fcount;
1823 }
1824
1825 if (ckchi->is_server_instance)
1826 errcode |= ckch_inst_new_load_srv_store(ckch_store->path, ckch_store, new_inst, err);
1827 else
1828 errcode |= ckch_inst_new_load_store(ckch_store->path, ckch_store, ckchi->bind_conf, ckchi->ssl_conf, sni_filter, fcount, new_inst, err);
1829
1830 if (errcode & ERR_CODE)
1831 return 1;
1832
1833 /* if the previous ckchi was used as the default */
1834 if (ckchi->is_default)
1835 (*new_inst)->is_default = 1;
1836
1837 (*new_inst)->is_server_instance = ckchi->is_server_instance;
1838 (*new_inst)->server = ckchi->server;
1839 /* Create a new SSL_CTX and link it to the new instance. */
1840 if ((*new_inst)->is_server_instance) {
1841 retval = ssl_sock_prep_srv_ctx_and_inst(ckchi->server, (*new_inst)->ctx, (*new_inst));
1842 if (retval)
1843 return 1;
1844 }
1845
1846 /* create the link to the crtlist_entry */
1847 (*new_inst)->crtlist_entry = ckchi->crtlist_entry;
1848
1849 /* we need to initialize the SSL_CTX generated */
1850 /* this iterate on the newly generated SNIs in the new instance to prepare their SSL_CTX */
1851 list_for_each_entry_safe(sc0, sc0s, &(*new_inst)->sni_ctx, by_ckch_inst) {
1852 if (!sc0->order) { /* we initialized only the first SSL_CTX because it's the same in the other sni_ctx's */
1853 errcode |= ssl_sock_prep_ctx_and_inst(ckchi->bind_conf, ckchi->ssl_conf, sc0->ctx, *new_inst, err);
1854 if (errcode & ERR_CODE)
1855 return 1;
1856 }
1857 }
1858
1859 return 0;
1860}
1861
1862/*
1863 * Load all the new SNIs of a newly built ckch instance in the trees, or replace
1864 * a server's main ckch instance.
1865 */
1866static void __ssl_sock_load_new_ckch_instance(struct ckch_inst *ckchi)
1867{
1868 /* The bind_conf will be null on server ckch_instances. */
1869 if (ckchi->is_server_instance) {
1870 int i;
1871 /* a lock is needed here since we have to free the SSL cache */
1872 HA_RWLOCK_WRLOCK(SSL_SERVER_LOCK, &ckchi->server->ssl_ctx.lock);
1873 /* free the server current SSL_CTX */
1874 SSL_CTX_free(ckchi->server->ssl_ctx.ctx);
1875 /* Actual ssl context update */
1876 SSL_CTX_up_ref(ckchi->ctx);
1877 ckchi->server->ssl_ctx.ctx = ckchi->ctx;
1878 ckchi->server->ssl_ctx.inst = ckchi;
1879
1880 /* flush the session cache of the server */
1881 for (i = 0; i < global.nbthread; i++) {
William Lallemandce990332021-11-23 15:15:09 +01001882 ha_free(&ckchi->server->ssl_ctx.reused_sess[i].sni);
Remi Tricot-Le Bretonbfadc022021-02-24 12:20:48 +01001883 ha_free(&ckchi->server->ssl_ctx.reused_sess[i].ptr);
1884 }
1885 HA_RWLOCK_WRUNLOCK(SSL_SERVER_LOCK, &ckchi->server->ssl_ctx.lock);
1886
1887 } else {
1888 HA_RWLOCK_WRLOCK(SNI_LOCK, &ckchi->bind_conf->sni_lock);
1889 ssl_sock_load_cert_sni(ckchi, ckchi->bind_conf);
1890 HA_RWLOCK_WRUNLOCK(SNI_LOCK, &ckchi->bind_conf->sni_lock);
1891 }
1892}
1893
1894/*
1895 * Delete a ckch instance that was replaced after a CLI command.
1896 */
1897static void __ckch_inst_free_locked(struct ckch_inst *ckchi)
1898{
1899 if (ckchi->is_server_instance) {
1900 /* no lock for servers */
1901 ckch_inst_free(ckchi);
1902 } else {
1903 struct bind_conf __maybe_unused *bind_conf = ckchi->bind_conf;
1904
1905 HA_RWLOCK_WRLOCK(SNI_LOCK, &bind_conf->sni_lock);
1906 ckch_inst_free(ckchi);
1907 HA_RWLOCK_WRUNLOCK(SNI_LOCK, &bind_conf->sni_lock);
1908 }
1909}
1910
William Lallemand3b5a3a62022-03-29 14:29:31 +02001911/* Replace a ckch_store in the ckch tree and insert the whole dependencies,
1912* then free the previous dependencies and store.
1913* Used in the case of a certificate update.
1914*
1915* Every dependencies must allocated before using this function.
1916*
1917* This function can't fail as it only update pointers, and does not alloc anything.
1918*
1919* /!\ This function must be used under the ckch lock. /!\
1920*
1921* - Insert every dependencies (SNI, crtlist_entry, ckch_inst, etc)
1922* - Delete the old ckch_store from the tree
1923* - Insert the new ckch_store
1924* - Free the old dependencies and the old ckch_store
1925*/
1926void ckch_store_replace(struct ckch_store *old_ckchs, struct ckch_store *new_ckchs)
1927{
1928 struct crtlist_entry *entry;
1929 struct ckch_inst *ckchi, *ckchis;
1930
1931 LIST_SPLICE(&new_ckchs->crtlist_entry, &old_ckchs->crtlist_entry);
1932 list_for_each_entry(entry, &new_ckchs->crtlist_entry, by_ckch_store) {
1933 ebpt_delete(&entry->node);
1934 /* change the ptr and reinsert the node */
1935 entry->node.key = new_ckchs;
1936 ebpt_insert(&entry->crtlist->entries, &entry->node);
1937 }
1938 /* insert the new ckch_insts in the crtlist_entry */
1939 list_for_each_entry(ckchi, &new_ckchs->ckch_inst, by_ckchs) {
1940 if (ckchi->crtlist_entry)
1941 LIST_INSERT(&ckchi->crtlist_entry->ckch_inst, &ckchi->by_crtlist_entry);
1942 }
1943 /* First, we insert every new SNIs in the trees, also replace the default_ctx */
1944 list_for_each_entry_safe(ckchi, ckchis, &new_ckchs->ckch_inst, by_ckchs) {
1945 __ssl_sock_load_new_ckch_instance(ckchi);
1946 }
1947 /* delete the old sni_ctx, the old ckch_insts and the ckch_store */
1948 list_for_each_entry_safe(ckchi, ckchis, &old_ckchs->ckch_inst, by_ckchs) {
1949 __ckch_inst_free_locked(ckchi);
1950 }
1951
1952 ckch_store_free(old_ckchs);
1953 ebst_insert(&ckchs_tree, &new_ckchs->node);
1954}
1955
Remi Tricot-Le Bretonbfadc022021-02-24 12:20:48 +01001956
William Lallemandda8584c2020-05-14 10:14:37 +02001957/*
1958 * This function tries to create the new ckch_inst and their SNIs
William Lallemand30fcca12022-03-30 12:03:12 +02001959 *
1960 * /!\ don't forget to update __hlua_ckch_commit() if you changes things there. /!\
William Lallemandda8584c2020-05-14 10:14:37 +02001961 */
1962static int cli_io_handler_commit_cert(struct appctx *appctx)
1963{
Christopher Faulet908628c2022-03-25 16:43:49 +01001964 struct conn_stream *cs = appctx->owner;
William Lallemandda8584c2020-05-14 10:14:37 +02001965 int y = 0;
1966 char *err = NULL;
William Lallemandda8584c2020-05-14 10:14:37 +02001967 struct ckch_store *old_ckchs, *new_ckchs = NULL;
William Lallemand3b5a3a62022-03-29 14:29:31 +02001968 struct ckch_inst *ckchi;
William Lallemandda8584c2020-05-14 10:14:37 +02001969 struct buffer *trash = alloc_trash_chunk();
William Lallemandda8584c2020-05-14 10:14:37 +02001970
1971 if (trash == NULL)
1972 goto error;
1973
Christopher Faulet908628c2022-03-25 16:43:49 +01001974 if (unlikely(cs_ic(cs)->flags & (CF_WRITE_ERROR|CF_SHUTW)))
William Lallemandda8584c2020-05-14 10:14:37 +02001975 goto error;
1976
1977 while (1) {
1978 switch (appctx->st2) {
1979 case SETCERT_ST_INIT:
1980 /* This state just print the update message */
1981 chunk_printf(trash, "Committing %s", ckchs_transaction.path);
Christopher Faulet908628c2022-03-25 16:43:49 +01001982 if (ci_putchk(cs_ic(cs), trash) == -1) {
Christopher Fauleta0bdec32022-04-04 07:51:21 +02001983 cs_rx_room_blk(cs);
William Lallemandda8584c2020-05-14 10:14:37 +02001984 goto yield;
1985 }
1986 appctx->st2 = SETCERT_ST_GEN;
1987 /* fallthrough */
1988 case SETCERT_ST_GEN:
1989 /*
1990 * This state generates the ckch instances with their
1991 * sni_ctxs and SSL_CTX.
1992 *
1993 * Since the SSL_CTX generation can be CPU consumer, we
1994 * yield every 10 instances.
1995 */
1996
1997 old_ckchs = appctx->ctx.ssl.old_ckchs;
1998 new_ckchs = appctx->ctx.ssl.new_ckchs;
1999
2000 if (!new_ckchs)
2001 continue;
2002
2003 /* get the next ckchi to regenerate */
2004 ckchi = appctx->ctx.ssl.next_ckchi;
2005 /* we didn't start yet, set it to the first elem */
2006 if (ckchi == NULL)
2007 ckchi = LIST_ELEM(old_ckchs->ckch_inst.n, typeof(ckchi), by_ckchs);
2008
2009 /* walk through the old ckch_inst and creates new ckch_inst using the updated ckchs */
2010 list_for_each_entry_from(ckchi, &old_ckchs->ckch_inst, by_ckchs) {
2011 struct ckch_inst *new_inst;
William Lallemandda8584c2020-05-14 10:14:37 +02002012
2013 /* it takes a lot of CPU to creates SSL_CTXs, so we yield every 10 CKCH instances */
2014 if (y >= 10) {
2015 /* save the next ckchi to compute */
2016 appctx->ctx.ssl.next_ckchi = ckchi;
2017 goto yield;
2018 }
2019
Remi Tricot-Le Bretonbfadc022021-02-24 12:20:48 +01002020 if (ckch_inst_rebuild(new_ckchs, ckchi, &new_inst, &err))
William Lallemandda8584c2020-05-14 10:14:37 +02002021 goto error;
2022
William Lallemandda8584c2020-05-14 10:14:37 +02002023 /* display one dot per new instance */
2024 chunk_appendf(trash, ".");
2025 /* link the new ckch_inst to the duplicate */
Willy Tarreau2b718102021-04-21 07:32:39 +02002026 LIST_APPEND(&new_ckchs->ckch_inst, &new_inst->by_ckchs);
William Lallemandda8584c2020-05-14 10:14:37 +02002027 y++;
2028 }
2029 appctx->st2 = SETCERT_ST_INSERT;
2030 /* fallthrough */
2031 case SETCERT_ST_INSERT:
2032 /* The generation is finished, we can insert everything */
2033
2034 old_ckchs = appctx->ctx.ssl.old_ckchs;
2035 new_ckchs = appctx->ctx.ssl.new_ckchs;
2036
2037 if (!new_ckchs)
2038 continue;
2039
William Lallemand3b5a3a62022-03-29 14:29:31 +02002040 /* insert everything and remove the previous objects */
2041 ckch_store_replace(old_ckchs, new_ckchs);
William Lallemandda8584c2020-05-14 10:14:37 +02002042
William Lallemandda8584c2020-05-14 10:14:37 +02002043 appctx->st2 = SETCERT_ST_FIN;
2044 /* fallthrough */
2045 case SETCERT_ST_FIN:
2046 /* we achieved the transaction, we can set everything to NULL */
Willy Tarreau61cfdf42021-02-20 10:46:51 +01002047 ha_free(&ckchs_transaction.path);
William Lallemandda8584c2020-05-14 10:14:37 +02002048 ckchs_transaction.new_ckchs = NULL;
2049 ckchs_transaction.old_ckchs = NULL;
2050 goto end;
2051 }
2052 }
2053end:
2054
2055 chunk_appendf(trash, "\n");
William Lallemandda8584c2020-05-14 10:14:37 +02002056 chunk_appendf(trash, "Success!\n");
Christopher Faulet908628c2022-03-25 16:43:49 +01002057 if (ci_putchk(cs_ic(cs), trash) == -1)
Christopher Fauleta0bdec32022-04-04 07:51:21 +02002058 cs_rx_room_blk(cs);
William Lallemandda8584c2020-05-14 10:14:37 +02002059 free_trash_chunk(trash);
2060 /* success: call the release function and don't come back */
2061 return 1;
2062yield:
2063 /* store the state */
Christopher Faulet908628c2022-03-25 16:43:49 +01002064 if (ci_putchk(cs_ic(cs), trash) == -1)
Christopher Fauleta0bdec32022-04-04 07:51:21 +02002065 cs_rx_room_blk(cs);
William Lallemandda8584c2020-05-14 10:14:37 +02002066 free_trash_chunk(trash);
Christopher Fauleta0bdec32022-04-04 07:51:21 +02002067 cs_rx_endp_more(cs); /* let's come back later */
William Lallemandda8584c2020-05-14 10:14:37 +02002068 return 0; /* should come back */
2069
2070error:
2071 /* spin unlock and free are done in the release function */
2072 if (trash) {
2073 chunk_appendf(trash, "\n%sFailed!\n", err);
Christopher Faulet908628c2022-03-25 16:43:49 +01002074 if (ci_putchk(cs_ic(cs), trash) == -1)
Christopher Fauleta0bdec32022-04-04 07:51:21 +02002075 cs_rx_room_blk(cs);
William Lallemandda8584c2020-05-14 10:14:37 +02002076 free_trash_chunk(trash);
2077 }
2078 /* error: call the release function and don't come back */
2079 return 1;
2080}
2081
2082/*
2083 * Parsing function of 'commit ssl cert'
2084 */
2085static int cli_parse_commit_cert(char **args, char *payload, struct appctx *appctx, void *private)
2086{
2087 char *err = NULL;
2088
2089 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2090 return 1;
2091
2092 if (!*args[3])
2093 return cli_err(appctx, "'commit ssl cert expects a filename\n");
2094
2095 /* The operations on the CKCH architecture are locked so we can
2096 * manipulate ckch_store and ckch_inst */
2097 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2098 return cli_err(appctx, "Can't commit the certificate!\nOperations on certificates are currently locked!\n");
2099
2100 if (!ckchs_transaction.path) {
2101 memprintf(&err, "No ongoing transaction! !\n");
2102 goto error;
2103 }
2104
2105 if (strcmp(ckchs_transaction.path, args[3]) != 0) {
2106 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", ckchs_transaction.path, args[3]);
2107 goto error;
2108 }
2109
William Lallemand5685ccf2020-09-16 16:12:25 +02002110 /* if a certificate is here, a private key must be here too */
2111 if (ckchs_transaction.new_ckchs->ckch->cert && !ckchs_transaction.new_ckchs->ckch->key) {
2112 memprintf(&err, "The transaction must contain at least a certificate and a private key!\n");
2113 goto error;
2114 }
William Lallemanda9419522020-06-24 16:26:41 +02002115
William Lallemand5685ccf2020-09-16 16:12:25 +02002116 if (!X509_check_private_key(ckchs_transaction.new_ckchs->ckch->cert, ckchs_transaction.new_ckchs->ckch->key)) {
2117 memprintf(&err, "inconsistencies between private key and certificate loaded '%s'.\n", ckchs_transaction.path);
2118 goto error;
William Lallemandda8584c2020-05-14 10:14:37 +02002119 }
2120
2121 /* init the appctx structure */
2122 appctx->st2 = SETCERT_ST_INIT;
2123 appctx->ctx.ssl.next_ckchi = NULL;
2124 appctx->ctx.ssl.new_ckchs = ckchs_transaction.new_ckchs;
2125 appctx->ctx.ssl.old_ckchs = ckchs_transaction.old_ckchs;
2126
2127 /* we don't unlock there, it will be unlock after the IO handler, in the release handler */
2128 return 0;
2129
2130error:
2131
2132 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2133 err = memprintf(&err, "%sCan't commit %s!\n", err ? err : "", args[3]);
2134
2135 return cli_dynerr(appctx, err);
2136}
2137
2138
2139
2140
2141/*
2142 * Parsing function of `set ssl cert`, it updates or creates a temporary ckch.
2143 */
2144static int cli_parse_set_cert(char **args, char *payload, struct appctx *appctx, void *private)
2145{
2146 struct ckch_store *new_ckchs = NULL;
2147 struct ckch_store *old_ckchs = NULL;
2148 char *err = NULL;
2149 int i;
William Lallemandda8584c2020-05-14 10:14:37 +02002150 int errcode = 0;
2151 char *end;
William Lallemandff8bf982022-03-29 10:44:23 +02002152 struct cert_exts *cert_ext = &cert_exts[0]; /* default one, PEM */
William Lallemandda8584c2020-05-14 10:14:37 +02002153 struct cert_key_and_chain *ckch;
2154 struct buffer *buf;
2155
2156 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2157 return 1;
2158
William Lallemandda8584c2020-05-14 10:14:37 +02002159 if (!*args[3] || !payload)
2160 return cli_err(appctx, "'set ssl cert expects a filename and a certificate as a payload\n");
2161
2162 /* The operations on the CKCH architecture are locked so we can
2163 * manipulate ckch_store and ckch_inst */
2164 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2165 return cli_err(appctx, "Can't update the certificate!\nOperations on certificates are currently locked!\n");
2166
William Lallemand5ba80d62021-05-04 16:17:27 +02002167 if ((buf = alloc_trash_chunk()) == NULL) {
2168 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2169 errcode |= ERR_ALERT | ERR_FATAL;
2170 goto end;
2171 }
William Lallemande5ff4ad2020-06-08 09:40:37 +02002172
William Lallemandda8584c2020-05-14 10:14:37 +02002173 if (!chunk_strcpy(buf, args[3])) {
2174 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2175 errcode |= ERR_ALERT | ERR_FATAL;
2176 goto end;
2177 }
2178
2179 /* check which type of file we want to update */
William Lallemandff8bf982022-03-29 10:44:23 +02002180 for (i = 0; cert_exts[i].ext != NULL; i++) {
William Lallemandda8584c2020-05-14 10:14:37 +02002181 end = strrchr(buf->area, '.');
Tim Duesterhuse5ff1412021-01-02 22:31:53 +01002182 if (end && *cert_exts[i].ext && (strcmp(end + 1, cert_exts[i].ext) == 0)) {
William Lallemandda8584c2020-05-14 10:14:37 +02002183 *end = '\0';
William Lallemand089c1382020-10-23 17:35:12 +02002184 buf->data = strlen(buf->area);
William Lallemandff8bf982022-03-29 10:44:23 +02002185 cert_ext = &cert_exts[i];
William Lallemandda8584c2020-05-14 10:14:37 +02002186 break;
2187 }
2188 }
2189
2190 appctx->ctx.ssl.old_ckchs = NULL;
2191 appctx->ctx.ssl.new_ckchs = NULL;
2192
2193 /* if there is an ongoing transaction */
2194 if (ckchs_transaction.path) {
William Lallemandda8584c2020-05-14 10:14:37 +02002195 /* if there is an ongoing transaction, check if this is the same file */
2196 if (strcmp(ckchs_transaction.path, buf->area) != 0) {
William Lallemand089c1382020-10-23 17:35:12 +02002197 /* we didn't find the transaction, must try more cases below */
2198
2199 /* if the del-ext option is activated we should try to take a look at a ".crt" too. */
William Lallemandff8bf982022-03-29 10:44:23 +02002200 if (cert_ext->type != CERT_TYPE_PEM && global_ssl.extra_files_noext) {
William Lallemand089c1382020-10-23 17:35:12 +02002201 if (!chunk_strcat(buf, ".crt")) {
2202 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2203 errcode |= ERR_ALERT | ERR_FATAL;
2204 goto end;
2205 }
2206
2207 if (strcmp(ckchs_transaction.path, buf->area) != 0) {
2208 /* remove .crt of the error message */
2209 *(b_orig(buf) + b_data(buf) + strlen(".crt")) = '\0';
2210 b_sub(buf, strlen(".crt"));
2211
2212 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", ckchs_transaction.path, buf->area);
2213 errcode |= ERR_ALERT | ERR_FATAL;
2214 goto end;
2215 }
2216 }
William Lallemandda8584c2020-05-14 10:14:37 +02002217 }
2218
2219 appctx->ctx.ssl.old_ckchs = ckchs_transaction.new_ckchs;
2220
2221 } else {
William Lallemandda8584c2020-05-14 10:14:37 +02002222
William Lallemand95fefa12020-09-09 12:01:33 +02002223 /* lookup for the certificate in the tree */
2224 appctx->ctx.ssl.old_ckchs = ckchs_lookup(buf->area);
William Lallemand089c1382020-10-23 17:35:12 +02002225
2226 if (!appctx->ctx.ssl.old_ckchs) {
2227 /* if the del-ext option is activated we should try to take a look at a ".crt" too. */
William Lallemandff8bf982022-03-29 10:44:23 +02002228 if (cert_ext->type != CERT_TYPE_PEM && global_ssl.extra_files_noext) {
William Lallemand089c1382020-10-23 17:35:12 +02002229 if (!chunk_strcat(buf, ".crt")) {
2230 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2231 errcode |= ERR_ALERT | ERR_FATAL;
2232 goto end;
2233 }
2234 appctx->ctx.ssl.old_ckchs = ckchs_lookup(buf->area);
2235 }
2236 }
William Lallemandda8584c2020-05-14 10:14:37 +02002237 }
2238
2239 if (!appctx->ctx.ssl.old_ckchs) {
2240 memprintf(&err, "%sCan't replace a certificate which is not referenced by the configuration!\n",
2241 err ? err : "");
2242 errcode |= ERR_ALERT | ERR_FATAL;
2243 goto end;
2244 }
2245
2246 if (!appctx->ctx.ssl.path) {
2247 /* this is a new transaction, set the path of the transaction */
2248 appctx->ctx.ssl.path = strdup(appctx->ctx.ssl.old_ckchs->path);
2249 if (!appctx->ctx.ssl.path) {
2250 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2251 errcode |= ERR_ALERT | ERR_FATAL;
2252 goto end;
2253 }
2254 }
2255
2256 old_ckchs = appctx->ctx.ssl.old_ckchs;
2257
2258 /* duplicate the ckch store */
2259 new_ckchs = ckchs_dup(old_ckchs);
2260 if (!new_ckchs) {
2261 memprintf(&err, "%sCannot allocate memory!\n",
2262 err ? err : "");
2263 errcode |= ERR_ALERT | ERR_FATAL;
2264 goto end;
2265 }
2266
William Lallemand95fefa12020-09-09 12:01:33 +02002267 ckch = new_ckchs->ckch;
William Lallemandda8584c2020-05-14 10:14:37 +02002268
2269 /* appply the change on the duplicate */
William Lallemandff8bf982022-03-29 10:44:23 +02002270 if (cert_ext->load(buf->area, payload, ckch, &err) != 0) {
William Lallemandda8584c2020-05-14 10:14:37 +02002271 memprintf(&err, "%sCan't load the payload\n", err ? err : "");
2272 errcode |= ERR_ALERT | ERR_FATAL;
2273 goto end;
2274 }
2275
2276 appctx->ctx.ssl.new_ckchs = new_ckchs;
2277
2278 /* we succeed, we can save the ckchs in the transaction */
2279
2280 /* if there wasn't a transaction, update the old ckchs */
2281 if (!ckchs_transaction.old_ckchs) {
2282 ckchs_transaction.old_ckchs = appctx->ctx.ssl.old_ckchs;
2283 ckchs_transaction.path = appctx->ctx.ssl.path;
2284 err = memprintf(&err, "Transaction created for certificate %s!\n", ckchs_transaction.path);
2285 } else {
2286 err = memprintf(&err, "Transaction updated for certificate %s!\n", ckchs_transaction.path);
2287
2288 }
2289
2290 /* free the previous ckchs if there was a transaction */
2291 ckch_store_free(ckchs_transaction.new_ckchs);
2292
2293 ckchs_transaction.new_ckchs = appctx->ctx.ssl.new_ckchs;
2294
2295
2296 /* creates the SNI ctxs later in the IO handler */
2297
2298end:
2299 free_trash_chunk(buf);
2300
2301 if (errcode & ERR_CODE) {
2302
2303 ckch_store_free(appctx->ctx.ssl.new_ckchs);
2304 appctx->ctx.ssl.new_ckchs = NULL;
2305
2306 appctx->ctx.ssl.old_ckchs = NULL;
2307
Willy Tarreau61cfdf42021-02-20 10:46:51 +01002308 ha_free(&appctx->ctx.ssl.path);
William Lallemandda8584c2020-05-14 10:14:37 +02002309
2310 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2311 return cli_dynerr(appctx, memprintf(&err, "%sCan't update %s!\n", err ? err : "", args[3]));
2312 } else {
2313
2314 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2315 return cli_dynmsg(appctx, LOG_NOTICE, err);
2316 }
2317 /* TODO: handle the ERR_WARN which are not handled because of the io_handler */
2318}
2319
2320/* parsing function of 'abort ssl cert' */
2321static int cli_parse_abort_cert(char **args, char *payload, struct appctx *appctx, void *private)
2322{
2323 char *err = NULL;
2324
2325 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2326 return 1;
2327
2328 if (!*args[3])
2329 return cli_err(appctx, "'abort ssl cert' expects a filename\n");
2330
2331 /* The operations on the CKCH architecture are locked so we can
2332 * manipulate ckch_store and ckch_inst */
2333 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2334 return cli_err(appctx, "Can't abort!\nOperations on certificates are currently locked!\n");
2335
2336 if (!ckchs_transaction.path) {
2337 memprintf(&err, "No ongoing transaction!\n");
2338 goto error;
2339 }
2340
2341 if (strcmp(ckchs_transaction.path, args[3]) != 0) {
2342 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to abort a transaction for '%s'\n", ckchs_transaction.path, args[3]);
2343 goto error;
2344 }
2345
2346 /* Only free the ckchs there, because the SNI and instances were not generated yet */
2347 ckch_store_free(ckchs_transaction.new_ckchs);
2348 ckchs_transaction.new_ckchs = NULL;
William Lallemandda8584c2020-05-14 10:14:37 +02002349 ckchs_transaction.old_ckchs = NULL;
Willy Tarreau61cfdf42021-02-20 10:46:51 +01002350 ha_free(&ckchs_transaction.path);
William Lallemandda8584c2020-05-14 10:14:37 +02002351
2352 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2353
2354 err = memprintf(&err, "Transaction aborted for certificate '%s'!\n", args[3]);
2355 return cli_dynmsg(appctx, LOG_NOTICE, err);
2356
2357error:
2358 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2359
2360 return cli_dynerr(appctx, err);
2361}
2362
2363/* parsing function of 'new ssl cert' */
2364static int cli_parse_new_cert(char **args, char *payload, struct appctx *appctx, void *private)
2365{
2366 struct ckch_store *store;
2367 char *err = NULL;
2368 char *path;
2369
2370 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2371 return 1;
2372
2373 if (!*args[3])
2374 return cli_err(appctx, "'new ssl cert' expects a filename\n");
2375
2376 path = args[3];
2377
2378 /* The operations on the CKCH architecture are locked so we can
2379 * manipulate ckch_store and ckch_inst */
2380 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2381 return cli_err(appctx, "Can't create a certificate!\nOperations on certificates are currently locked!\n");
2382
2383 store = ckchs_lookup(path);
2384 if (store != NULL) {
2385 memprintf(&err, "Certificate '%s' already exists!\n", path);
2386 store = NULL; /* we don't want to free it */
2387 goto error;
2388 }
2389 /* we won't support multi-certificate bundle here */
William Lallemandbd8e6ed2020-09-16 16:08:08 +02002390 store = ckch_store_new(path);
William Lallemandda8584c2020-05-14 10:14:37 +02002391 if (!store) {
2392 memprintf(&err, "unable to allocate memory.\n");
2393 goto error;
2394 }
2395
2396 /* insert into the ckchs tree */
2397 ebst_insert(&ckchs_tree, &store->node);
2398 memprintf(&err, "New empty certificate store '%s'!\n", args[3]);
2399
2400 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2401 return cli_dynmsg(appctx, LOG_NOTICE, err);
2402error:
2403 free(store);
2404 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2405 return cli_dynerr(appctx, err);
2406}
2407
2408/* parsing function of 'del ssl cert' */
2409static int cli_parse_del_cert(char **args, char *payload, struct appctx *appctx, void *private)
2410{
2411 struct ckch_store *store;
2412 char *err = NULL;
2413 char *filename;
2414
2415 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2416 return 1;
2417
2418 if (!*args[3])
2419 return cli_err(appctx, "'del ssl cert' expects a certificate name\n");
2420
2421 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2422 return cli_err(appctx, "Can't delete the certificate!\nOperations on certificates are currently locked!\n");
2423
2424 filename = args[3];
2425
2426 store = ckchs_lookup(filename);
2427 if (store == NULL) {
2428 memprintf(&err, "certificate '%s' doesn't exist!\n", filename);
2429 goto error;
2430 }
2431 if (!LIST_ISEMPTY(&store->ckch_inst)) {
2432 memprintf(&err, "certificate '%s' in use, can't be deleted!\n", filename);
2433 goto error;
2434 }
2435
2436 ebmb_delete(&store->node);
2437 ckch_store_free(store);
2438
2439 memprintf(&err, "Certificate '%s' deleted!\n", filename);
2440
2441 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2442 return cli_dynmsg(appctx, LOG_NOTICE, err);
2443
2444error:
2445 memprintf(&err, "Can't remove the certificate: %s\n", err ? err : "");
2446 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2447 return cli_dynerr(appctx, err);
2448}
2449
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002450
Remi Tricot-Le Breton9f40fe02021-03-16 16:21:27 +01002451
2452/* parsing function of 'new ssl ca-file' */
2453static int cli_parse_new_cafile(char **args, char *payload, struct appctx *appctx, void *private)
2454{
2455 struct cafile_entry *cafile_entry;
2456 char *err = NULL;
2457 char *path;
2458
2459 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2460 return 1;
2461
2462 if (!*args[3])
2463 return cli_err(appctx, "'new ssl ca-file' expects a filename\n");
2464
2465 path = args[3];
2466
2467 /* The operations on the CKCH architecture are locked so we can
2468 * manipulate ckch_store and ckch_inst */
2469 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2470 return cli_err(appctx, "Can't create a CA file!\nOperations on certificates are currently locked!\n");
2471
2472 cafile_entry = ssl_store_get_cafile_entry(path, 0);
2473 if (cafile_entry) {
2474 memprintf(&err, "CA file '%s' already exists!\n", path);
2475 goto error;
2476 }
2477
2478 cafile_entry = ssl_store_create_cafile_entry(path, NULL, CAFILE_CERT);
2479 if (!cafile_entry) {
2480 memprintf(&err, "%sCannot allocate memory!\n",
2481 err ? err : "");
2482 goto error;
2483 }
2484
2485 /* Add the newly created cafile_entry to the tree so that
2486 * any new ckch instance created from now can use it. */
2487 if (ssl_store_add_uncommitted_cafile_entry(cafile_entry))
2488 goto error;
2489
2490 memprintf(&err, "New CA file created '%s'!\n", path);
2491
2492 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2493 return cli_dynmsg(appctx, LOG_NOTICE, err);
2494error:
2495 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2496 return cli_dynerr(appctx, err);
2497}
2498
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002499/*
2500 * Parsing function of `set ssl ca-file`
2501 */
2502static int cli_parse_set_cafile(char **args, char *payload, struct appctx *appctx, void *private)
2503{
2504 char *err = NULL;
2505 int errcode = 0;
2506 struct buffer *buf;
2507
2508 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2509 return 1;
2510
2511 if (!*args[3] || !payload)
2512 return cli_err(appctx, "'set ssl ca-file expects a filename and CAs as a payload\n");
2513
2514 /* The operations on the CKCH architecture are locked so we can
2515 * manipulate ckch_store and ckch_inst */
2516 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2517 return cli_err(appctx, "Can't update the CA file!\nOperations on certificates are currently locked!\n");
2518
2519 if ((buf = alloc_trash_chunk()) == NULL) {
2520 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2521 errcode |= ERR_ALERT | ERR_FATAL;
2522 goto end;
2523 }
2524
2525 if (!chunk_strcpy(buf, args[3])) {
2526 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2527 errcode |= ERR_ALERT | ERR_FATAL;
2528 goto end;
2529 }
2530
2531 appctx->ctx.ssl.old_cafile_entry = NULL;
2532 appctx->ctx.ssl.new_cafile_entry = NULL;
2533
2534 /* if there is an ongoing transaction */
2535 if (cafile_transaction.path) {
2536 /* if there is an ongoing transaction, check if this is the same file */
2537 if (strcmp(cafile_transaction.path, buf->area) != 0) {
2538 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", cafile_transaction.path, buf->area);
2539 errcode |= ERR_ALERT | ERR_FATAL;
2540 goto end;
2541 }
2542 appctx->ctx.ssl.old_cafile_entry = cafile_transaction.old_cafile_entry;
2543 }
2544 else {
2545 /* lookup for the certificate in the tree */
2546 appctx->ctx.ssl.old_cafile_entry = ssl_store_get_cafile_entry(buf->area, 0);
2547 }
2548
2549 if (!appctx->ctx.ssl.old_cafile_entry) {
2550 memprintf(&err, "%sCan't replace a CA file which is not referenced by the configuration!\n",
2551 err ? err : "");
2552 errcode |= ERR_ALERT | ERR_FATAL;
2553 goto end;
2554 }
2555
2556 if (!appctx->ctx.ssl.path) {
2557 /* this is a new transaction, set the path of the transaction */
2558 appctx->ctx.ssl.path = strdup(appctx->ctx.ssl.old_cafile_entry->path);
2559 if (!appctx->ctx.ssl.path) {
2560 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2561 errcode |= ERR_ALERT | ERR_FATAL;
2562 goto end;
2563 }
2564 }
2565
2566 if (appctx->ctx.ssl.new_cafile_entry)
2567 ssl_store_delete_cafile_entry(appctx->ctx.ssl.new_cafile_entry);
2568
2569 /* Create a new cafile_entry without adding it to the cafile tree. */
Remi Tricot-Le Breton0bb48242021-04-16 17:59:23 +02002570 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 +01002571 if (!appctx->ctx.ssl.new_cafile_entry) {
2572 memprintf(&err, "%sCannot allocate memory!\n",
2573 err ? err : "");
2574 errcode |= ERR_ALERT | ERR_FATAL;
2575 goto end;
2576 }
2577
2578 /* Fill the new entry with the new CAs. */
2579 if (ssl_store_load_ca_from_buf(appctx->ctx.ssl.new_cafile_entry, payload)) {
2580 memprintf(&err, "%sInvalid payload\n", err ? err : "");
2581 errcode |= ERR_ALERT | ERR_FATAL;
2582 goto end;
2583 }
2584
2585 /* we succeed, we can save the ca in the transaction */
2586
2587 /* if there wasn't a transaction, update the old CA */
2588 if (!cafile_transaction.old_cafile_entry) {
2589 cafile_transaction.old_cafile_entry = appctx->ctx.ssl.old_cafile_entry;
2590 cafile_transaction.path = appctx->ctx.ssl.path;
2591 err = memprintf(&err, "transaction created for CA %s!\n", cafile_transaction.path);
2592 } else {
2593 err = memprintf(&err, "transaction updated for CA %s!\n", cafile_transaction.path);
2594 }
2595
2596 /* free the previous CA if there was a transaction */
2597 ssl_store_delete_cafile_entry(cafile_transaction.new_cafile_entry);
2598
2599 cafile_transaction.new_cafile_entry = appctx->ctx.ssl.new_cafile_entry;
2600
2601 /* creates the SNI ctxs later in the IO handler */
2602
2603end:
2604 free_trash_chunk(buf);
2605
2606 if (errcode & ERR_CODE) {
2607 ssl_store_delete_cafile_entry(appctx->ctx.ssl.new_cafile_entry);
2608 appctx->ctx.ssl.new_cafile_entry = NULL;
2609 appctx->ctx.ssl.old_cafile_entry = NULL;
2610
Tim Duesterhus025b93e2021-11-04 21:03:52 +01002611 ha_free(&appctx->ctx.ssl.path);
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002612
2613 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2614 return cli_dynerr(appctx, memprintf(&err, "%sCan't update %s!\n", err ? err : "", args[3]));
2615 } else {
2616
2617 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2618 return cli_dynmsg(appctx, LOG_NOTICE, err);
2619 }
2620}
2621
2622
2623/*
2624 * Parsing function of 'commit ssl ca-file'
2625 */
2626static int cli_parse_commit_cafile(char **args, char *payload, struct appctx *appctx, void *private)
2627{
2628 char *err = NULL;
2629
2630 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2631 return 1;
2632
2633 if (!*args[3])
2634 return cli_err(appctx, "'commit ssl ca-file expects a filename\n");
2635
2636 /* The operations on the CKCH architecture are locked so we can
2637 * manipulate ckch_store and ckch_inst */
2638 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2639 return cli_err(appctx, "Can't commit the CA file!\nOperations on certificates are currently locked!\n");
2640
2641 if (!cafile_transaction.path) {
2642 memprintf(&err, "No ongoing transaction! !\n");
2643 goto error;
2644 }
2645
2646 if (strcmp(cafile_transaction.path, args[3]) != 0) {
2647 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", cafile_transaction.path, args[3]);
2648 goto error;
2649 }
2650 /* init the appctx structure */
2651 appctx->st2 = SETCERT_ST_INIT;
2652 appctx->ctx.ssl.next_ckchi_link = NULL;
2653 appctx->ctx.ssl.old_cafile_entry = cafile_transaction.old_cafile_entry;
2654 appctx->ctx.ssl.new_cafile_entry = cafile_transaction.new_cafile_entry;
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002655 appctx->ctx.ssl.cafile_type = CAFILE_CERT;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002656
2657 return 0;
2658
2659error:
2660
2661 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2662 err = memprintf(&err, "%sCan't commit %s!\n", err ? err : "", args[3]);
2663
2664 return cli_dynerr(appctx, err);
2665}
2666
2667enum {
2668 CREATE_NEW_INST_OK = 0,
2669 CREATE_NEW_INST_YIELD = -1,
2670 CREATE_NEW_INST_ERR = -2
2671};
2672
2673static inline int __create_new_instance(struct appctx *appctx, struct ckch_inst *ckchi, int *count,
Remi Tricot-Le Bretond75b99e2021-05-17 11:45:55 +02002674 struct buffer *trash, char **err)
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002675{
2676 struct ckch_inst *new_inst;
2677
2678 /* it takes a lot of CPU to creates SSL_CTXs, so we yield every 10 CKCH instances */
2679 if (*count >= 10) {
2680 /* save the next ckchi to compute */
2681 appctx->ctx.ssl.next_ckchi = ckchi;
2682 return CREATE_NEW_INST_YIELD;
2683 }
2684
2685 /* Rebuild a new ckch instance that uses the same ckch_store
2686 * than a reference ckchi instance but will use a new CA file. */
Remi Tricot-Le Bretond75b99e2021-05-17 11:45:55 +02002687 if (ckch_inst_rebuild(ckchi->ckch_store, ckchi, &new_inst, err))
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002688 return CREATE_NEW_INST_ERR;
2689
2690 /* display one dot per new instance */
2691 chunk_appendf(trash, ".");
2692 ++(*count);
2693
2694 return CREATE_NEW_INST_OK;
2695}
2696
2697/*
2698 * This function tries to create new ckch instances and their SNIs using a newly
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002699 * set certificate authority (CA file) or a newly set Certificate Revocation
2700 * List (CRL), depending on the command being called.
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002701 */
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002702static int cli_io_handler_commit_cafile_crlfile(struct appctx *appctx)
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002703{
Christopher Faulet908628c2022-03-25 16:43:49 +01002704 struct conn_stream *cs = appctx->owner;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002705 int y = 0;
2706 char *err = NULL;
Remi Tricot-Le Bretona6b27842021-05-18 10:06:00 +02002707 struct cafile_entry *old_cafile_entry = NULL, *new_cafile_entry = NULL;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002708 struct ckch_inst_link *ckchi_link;
2709 struct buffer *trash = alloc_trash_chunk();
2710
2711 if (trash == NULL)
2712 goto error;
2713
Christopher Faulet908628c2022-03-25 16:43:49 +01002714 if (unlikely(cs_ic(cs)->flags & (CF_WRITE_ERROR|CF_SHUTW)))
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002715 goto error;
2716
2717 while (1) {
2718 switch (appctx->st2) {
2719 case SETCERT_ST_INIT:
2720 /* This state just print the update message */
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002721 switch (appctx->ctx.ssl.cafile_type) {
2722 case CAFILE_CERT:
2723 chunk_printf(trash, "Committing %s", cafile_transaction.path);
2724 break;
2725 case CAFILE_CRL:
2726 chunk_printf(trash, "Committing %s", crlfile_transaction.path);
2727 break;
2728 default:
2729 goto error;
2730 }
Christopher Faulet908628c2022-03-25 16:43:49 +01002731 if (ci_putchk(cs_ic(cs), trash) == -1) {
Christopher Fauleta0bdec32022-04-04 07:51:21 +02002732 cs_rx_room_blk(cs);
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002733 goto yield;
2734 }
2735 appctx->st2 = SETCERT_ST_GEN;
2736 /* fallthrough */
2737 case SETCERT_ST_GEN:
2738 /*
2739 * This state generates the ckch instances with their
2740 * sni_ctxs and SSL_CTX.
2741 *
2742 * Since the SSL_CTX generation can be CPU consumer, we
2743 * yield every 10 instances.
2744 */
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002745 switch (appctx->ctx.ssl.cafile_type) {
2746 case CAFILE_CERT:
2747 old_cafile_entry = appctx->ctx.ssl.old_cafile_entry;
2748 new_cafile_entry = appctx->ctx.ssl.new_cafile_entry;
2749 break;
2750 case CAFILE_CRL:
2751 old_cafile_entry = appctx->ctx.ssl.old_crlfile_entry;
2752 new_cafile_entry = appctx->ctx.ssl.new_crlfile_entry;
2753 break;
2754 }
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002755 if (!new_cafile_entry)
2756 continue;
2757
2758 /* get the next ckchi to regenerate */
2759 ckchi_link = appctx->ctx.ssl.next_ckchi_link;
2760 /* we didn't start yet, set it to the first elem */
2761 if (ckchi_link == NULL) {
2762 ckchi_link = LIST_ELEM(old_cafile_entry->ckch_inst_link.n, typeof(ckchi_link), list);
2763 /* Add the newly created cafile_entry to the tree so that
2764 * any new ckch instance created from now can use it. */
2765 if (ssl_store_add_uncommitted_cafile_entry(new_cafile_entry))
2766 goto error;
2767 }
2768
2769 list_for_each_entry_from(ckchi_link, &old_cafile_entry->ckch_inst_link, list) {
Remi Tricot-Le Bretond75b99e2021-05-17 11:45:55 +02002770 switch (__create_new_instance(appctx, ckchi_link->ckch_inst, &y, trash, &err)) {
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002771 case CREATE_NEW_INST_YIELD:
2772 appctx->ctx.ssl.next_ckchi_link = ckchi_link;
2773 goto yield;
2774 case CREATE_NEW_INST_ERR:
2775 goto error;
2776 default: break;
2777 }
2778 }
2779
2780 appctx->st2 = SETCERT_ST_INSERT;
2781 /* fallthrough */
2782 case SETCERT_ST_INSERT:
2783 /* The generation is finished, we can insert everything */
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002784 switch (appctx->ctx.ssl.cafile_type) {
2785 case CAFILE_CERT:
2786 old_cafile_entry = appctx->ctx.ssl.old_cafile_entry;
2787 new_cafile_entry = appctx->ctx.ssl.new_cafile_entry;
2788 break;
2789 case CAFILE_CRL:
2790 old_cafile_entry = appctx->ctx.ssl.old_crlfile_entry;
2791 new_cafile_entry = appctx->ctx.ssl.new_crlfile_entry;
2792 break;
2793 }
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002794 if (!new_cafile_entry)
2795 continue;
2796
2797 /* insert the new ckch_insts in the crtlist_entry */
2798 list_for_each_entry(ckchi_link, &new_cafile_entry->ckch_inst_link, list) {
2799 if (ckchi_link->ckch_inst->crtlist_entry)
2800 LIST_INSERT(&ckchi_link->ckch_inst->crtlist_entry->ckch_inst,
2801 &ckchi_link->ckch_inst->by_crtlist_entry);
2802 }
2803
2804 /* First, we insert every new SNIs in the trees, also replace the default_ctx */
2805 list_for_each_entry(ckchi_link, &new_cafile_entry->ckch_inst_link, list) {
2806 __ssl_sock_load_new_ckch_instance(ckchi_link->ckch_inst);
2807 }
2808
2809 /* delete the old sni_ctx, the old ckch_insts and the ckch_store */
2810 list_for_each_entry(ckchi_link, &old_cafile_entry->ckch_inst_link, list) {
2811 __ckch_inst_free_locked(ckchi_link->ckch_inst);
2812 }
2813
2814
2815 /* Remove the old cafile entry from the tree */
2816 ebmb_delete(&old_cafile_entry->node);
2817 ssl_store_delete_cafile_entry(old_cafile_entry);
2818
2819 appctx->st2 = SETCERT_ST_FIN;
2820 /* fallthrough */
2821 case SETCERT_ST_FIN:
2822 /* we achieved the transaction, we can set everything to NULL */
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002823 switch (appctx->ctx.ssl.cafile_type) {
2824 case CAFILE_CERT:
2825 ha_free(&cafile_transaction.path);
2826 cafile_transaction.old_cafile_entry = NULL;
2827 cafile_transaction.new_cafile_entry = NULL;
2828 break;
2829 case CAFILE_CRL:
2830 ha_free(&crlfile_transaction.path);
2831 crlfile_transaction.old_crlfile_entry = NULL;
2832 crlfile_transaction.new_crlfile_entry = NULL;
2833 break;
2834 }
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002835 goto end;
2836 }
2837 }
2838end:
2839
2840 chunk_appendf(trash, "\n");
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002841 chunk_appendf(trash, "Success!\n");
Christopher Faulet908628c2022-03-25 16:43:49 +01002842 if (ci_putchk(cs_ic(cs), trash) == -1)
Christopher Fauleta0bdec32022-04-04 07:51:21 +02002843 cs_rx_room_blk(cs);
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002844 free_trash_chunk(trash);
2845 /* success: call the release function and don't come back */
2846 return 1;
2847yield:
2848 /* store the state */
Christopher Faulet908628c2022-03-25 16:43:49 +01002849 if (ci_putchk(cs_ic(cs), trash) == -1)
Christopher Fauleta0bdec32022-04-04 07:51:21 +02002850 cs_rx_room_blk(cs);
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002851 free_trash_chunk(trash);
Christopher Fauleta0bdec32022-04-04 07:51:21 +02002852 cs_rx_endp_more(cs); /* let's come back later */
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002853 return 0; /* should come back */
2854
2855error:
2856 /* spin unlock and free are done in the release function */
2857 if (trash) {
2858 chunk_appendf(trash, "\n%sFailed!\n", err);
Christopher Faulet908628c2022-03-25 16:43:49 +01002859 if (ci_putchk(cs_ic(cs), trash) == -1)
Christopher Fauleta0bdec32022-04-04 07:51:21 +02002860 cs_rx_room_blk(cs);
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002861 free_trash_chunk(trash);
2862 }
2863 /* error: call the release function and don't come back */
2864 return 1;
2865}
2866
Remi Tricot-Le Bretond5fd09d2021-03-11 10:22:52 +01002867
2868/* parsing function of 'abort ssl ca-file' */
2869static int cli_parse_abort_cafile(char **args, char *payload, struct appctx *appctx, void *private)
2870{
2871 char *err = NULL;
2872
2873 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2874 return 1;
2875
2876 if (!*args[3])
2877 return cli_err(appctx, "'abort ssl ca-file' expects a filename\n");
2878
2879 /* The operations on the CKCH architecture are locked so we can
2880 * manipulate ckch_store and ckch_inst */
2881 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2882 return cli_err(appctx, "Can't abort!\nOperations on certificates are currently locked!\n");
2883
2884 if (!cafile_transaction.path) {
2885 memprintf(&err, "No ongoing transaction!\n");
2886 goto error;
2887 }
2888
2889 if (strcmp(cafile_transaction.path, args[3]) != 0) {
2890 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to abort a transaction for '%s'\n", cafile_transaction.path, args[3]);
2891 goto error;
2892 }
2893
2894 /* Only free the uncommitted cafile_entry here, because the SNI and instances were not generated yet */
2895 ssl_store_delete_cafile_entry(cafile_transaction.new_cafile_entry);
2896 cafile_transaction.new_cafile_entry = NULL;
2897 cafile_transaction.old_cafile_entry = NULL;
2898 ha_free(&cafile_transaction.path);
2899
2900 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2901
2902 err = memprintf(&err, "Transaction aborted for certificate '%s'!\n", args[3]);
2903 return cli_dynmsg(appctx, LOG_NOTICE, err);
2904
2905error:
2906 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2907
2908 return cli_dynerr(appctx, err);
2909}
2910
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002911/* release function of the `commit ssl ca-file' command, free things and unlock the spinlock */
2912static void cli_release_commit_cafile(struct appctx *appctx)
2913{
2914 if (appctx->st2 != SETCERT_ST_FIN) {
2915 struct cafile_entry *new_cafile_entry = appctx->ctx.ssl.new_cafile_entry;
2916
2917 /* Remove the uncommitted cafile_entry from the tree. */
2918 ebmb_delete(&new_cafile_entry->node);
2919 ssl_store_delete_cafile_entry(new_cafile_entry);
2920 }
2921 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2922}
2923
2924
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01002925/* IO handler of details "show ssl ca-file <filename[:index]>" */
2926static int cli_io_handler_show_cafile_detail(struct appctx *appctx)
2927{
Christopher Faulet908628c2022-03-25 16:43:49 +01002928 struct conn_stream *cs = appctx->owner;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01002929 struct cafile_entry *cafile_entry = appctx->ctx.cli.p0;
2930 struct buffer *out = alloc_trash_chunk();
William Lallemand03a32e52022-04-26 18:17:15 +02002931 int i = 0;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01002932 X509 *cert;
2933 STACK_OF(X509_OBJECT) *objs;
2934 int retval = 0;
William Lallemand03a32e52022-04-26 18:17:15 +02002935 int ca_index = appctx->ctx.cli.i0;
2936 int show_all = appctx->ctx.cli.i1;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01002937
2938 if (!out)
2939 goto end_no_putchk;
2940
2941 chunk_appendf(out, "Filename: ");
2942 if (cafile_entry == cafile_transaction.new_cafile_entry)
2943 chunk_appendf(out, "*");
2944 chunk_appendf(out, "%s\n", cafile_entry->path);
2945
2946 chunk_appendf(out, "Status: ");
2947 if (!cafile_entry->ca_store)
2948 chunk_appendf(out, "Empty\n");
2949 else if (LIST_ISEMPTY(&cafile_entry->ckch_inst_link))
2950 chunk_appendf(out, "Unused\n");
2951 else
2952 chunk_appendf(out, "Used\n");
2953
2954 if (!cafile_entry->ca_store)
2955 goto end;
2956
2957 objs = X509_STORE_get0_objects(cafile_entry->ca_store);
William Lallemand03a32e52022-04-26 18:17:15 +02002958 for (i = ca_index; i < sk_X509_OBJECT_num(objs); i++) {
2959
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01002960 cert = X509_OBJECT_get0_X509(sk_X509_OBJECT_value(objs, i));
2961 if (!cert)
2962 continue;
2963
William Lallemand03a32e52022-04-26 18:17:15 +02002964 /* file starts at line 1 */
Remi Tricot-Le Bretone8041fe2022-04-05 16:44:21 +02002965 chunk_appendf(out, " \nCertificate #%d:\n", i+1);
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01002966 retval = show_cert_detail(cert, NULL, out);
2967 if (retval < 0)
2968 goto end_no_putchk;
William Lallemand03a32e52022-04-26 18:17:15 +02002969 else if (retval)
2970 goto yield;
2971
2972 if (ci_putchk(cs_ic(cs), out) == -1) {
2973 cs_rx_room_blk(cs);
2974 goto yield;
2975 }
2976
2977 if (!show_all) /* only need to dump one certificate */
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01002978 goto end;
2979 }
2980
2981end:
William Lallemand03a32e52022-04-26 18:17:15 +02002982 free_trash_chunk(out);
2983 return 1; /* end, don't come back */
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01002984
2985end_no_putchk:
2986 free_trash_chunk(out);
2987 return 1;
2988yield:
William Lallemand03a32e52022-04-26 18:17:15 +02002989 /* save the current state */
2990 appctx->ctx.cli.i0 = i;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01002991 free_trash_chunk(out);
2992 return 0; /* should come back */
2993}
2994
2995
2996/* parsing function for 'show ssl ca-file [cafile[:index]]' */
2997static int cli_parse_show_cafile(char **args, char *payload, struct appctx *appctx, void *private)
2998{
2999 struct cafile_entry *cafile_entry;
William Lallemand03a32e52022-04-26 18:17:15 +02003000 int ca_index = 0;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003001 char *colons;
3002 char *err = NULL;
3003
3004 if (!cli_has_level(appctx, ACCESS_LVL_OPER))
3005 return cli_err(appctx, "Can't allocate memory!\n");
3006
3007 /* The operations on the CKCH architecture are locked so we can
3008 * manipulate ckch_store and ckch_inst */
3009 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3010 return cli_err(appctx, "Can't show!\nOperations on certificates are currently locked!\n");
3011
William Lallemand03a32e52022-04-26 18:17:15 +02003012 appctx->ctx.cli.i1 = 1; /* show all certificates */
3013 appctx->ctx.cli.i0 = 0;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003014 /* check if there is a certificate to lookup */
3015 if (*args[3]) {
3016
3017 /* Look for an optional CA index after the CA file name */
3018 colons = strchr(args[3], ':');
3019 if (colons) {
3020 char *endptr;
3021
3022 ca_index = strtol(colons + 1, &endptr, 10);
3023 /* Indexes start at 1 */
3024 if (colons + 1 == endptr || *endptr != '\0' || ca_index <= 0) {
3025 memprintf(&err, "wrong CA index after colons in '%s'!", args[3]);
3026 goto error;
3027 }
3028 *colons = '\0';
William Lallemand03a32e52022-04-26 18:17:15 +02003029 appctx->ctx.cli.i0 = ca_index - 1; /* we start counting at 0 in the ca_store, but at 1 on the CLI */
3030 appctx->ctx.cli.i1 = 0; /* show only one certificate */
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003031 }
3032
3033 if (*args[3] == '*') {
3034 if (!cafile_transaction.new_cafile_entry)
3035 goto error;
3036
3037 cafile_entry = cafile_transaction.new_cafile_entry;
3038
3039 if (strcmp(args[3] + 1, cafile_entry->path) != 0)
3040 goto error;
3041
3042 } else {
3043 /* Get the "original" cafile_entry and not the
3044 * uncommitted one if it exists. */
3045 if ((cafile_entry = ssl_store_get_cafile_entry(args[3], 1)) == NULL || cafile_entry->type != CAFILE_CERT)
3046 goto error;
3047 }
3048
3049 appctx->ctx.cli.p0 = cafile_entry;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003050 /* use the IO handler that shows details */
3051 appctx->io_handler = cli_io_handler_show_cafile_detail;
3052 }
3053
3054 return 0;
3055
3056error:
3057 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3058 if (err)
3059 return cli_dynerr(appctx, err);
3060 return cli_err(appctx, "Can't display the CA file : Not found!\n");
3061}
3062
3063
3064/* release function of the 'show ssl ca-file' command */
3065static void cli_release_show_cafile(struct appctx *appctx)
3066{
3067 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3068}
3069
3070
3071/* This function returns the number of certificates in a cafile_entry. */
3072static int get_certificate_count(struct cafile_entry *cafile_entry)
3073{
3074 int cert_count = 0;
3075 STACK_OF(X509_OBJECT) *objs;
3076
3077 if (cafile_entry && cafile_entry->ca_store) {
3078 objs = X509_STORE_get0_objects(cafile_entry->ca_store);
3079 if (objs)
3080 cert_count = sk_X509_OBJECT_num(objs);
3081 }
3082 return cert_count;
3083}
3084
3085/* IO handler of "show ssl ca-file". The command taking a specific CA file name
3086 * is managed in cli_io_handler_show_cafile_detail. */
3087static int cli_io_handler_show_cafile(struct appctx *appctx)
3088{
3089 struct buffer *trash = alloc_trash_chunk();
3090 struct ebmb_node *node;
Christopher Faulet908628c2022-03-25 16:43:49 +01003091 struct conn_stream *cs = appctx->owner;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003092 struct cafile_entry *cafile_entry;
3093
3094 if (trash == NULL)
3095 return 1;
3096
3097 if (!appctx->ctx.ssl.old_cafile_entry) {
3098 if (cafile_transaction.old_cafile_entry) {
3099 chunk_appendf(trash, "# transaction\n");
3100 chunk_appendf(trash, "*%s", cafile_transaction.old_cafile_entry->path);
3101
3102 chunk_appendf(trash, " - %d certificate(s)\n", get_certificate_count(cafile_transaction.new_cafile_entry));
3103 }
3104 }
3105
3106 /* First time in this io_handler. */
3107 if (!appctx->ctx.cli.p0) {
3108 chunk_appendf(trash, "# filename\n");
3109 node = ebmb_first(&cafile_tree);
3110 } else {
3111 /* We yielded during a previous call. */
3112 node = &((struct cafile_entry*)appctx->ctx.cli.p0)->node;
3113 }
3114
3115 while (node) {
3116 cafile_entry = ebmb_entry(node, struct cafile_entry, node);
3117 if (cafile_entry->type == CAFILE_CERT) {
3118 chunk_appendf(trash, "%s", cafile_entry->path);
3119
3120 chunk_appendf(trash, " - %d certificate(s)\n", get_certificate_count(cafile_entry));
3121 }
3122
3123 node = ebmb_next(node);
Christopher Faulet908628c2022-03-25 16:43:49 +01003124 if (ci_putchk(cs_ic(cs), trash) == -1) {
Christopher Fauleta0bdec32022-04-04 07:51:21 +02003125 cs_rx_room_blk(cs);
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003126 goto yield;
3127 }
3128 }
3129
3130 appctx->ctx.cli.p0 = NULL;
3131 free_trash_chunk(trash);
3132 return 1;
3133yield:
3134
3135 free_trash_chunk(trash);
3136 appctx->ctx.cli.p0 = cafile_entry;
3137 return 0; /* should come back */
3138}
3139
Remi Tricot-Le Bretonc3a84772021-03-25 18:13:57 +01003140/* parsing function of 'del ssl ca-file' */
3141static int cli_parse_del_cafile(char **args, char *payload, struct appctx *appctx, void *private)
3142{
3143 struct cafile_entry *cafile_entry;
3144 char *err = NULL;
3145 char *filename;
3146
3147 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
3148 return 1;
3149
3150 if (!*args[3])
3151 return cli_err(appctx, "'del ssl ca-file' expects a CA file name\n");
3152
3153 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3154 return cli_err(appctx, "Can't delete the CA file!\nOperations on certificates are currently locked!\n");
3155
3156 filename = args[3];
3157
3158 cafile_entry = ssl_store_get_cafile_entry(filename, 0);
3159 if (!cafile_entry) {
3160 memprintf(&err, "CA file '%s' doesn't exist!\n", filename);
3161 goto error;
3162 }
3163
3164 if (!LIST_ISEMPTY(&cafile_entry->ckch_inst_link)) {
3165 memprintf(&err, "CA file '%s' in use, can't be deleted!\n", filename);
3166 goto error;
3167 }
3168
3169 /* Remove the cafile_entry from the tree */
3170 ebmb_delete(&cafile_entry->node);
3171 ssl_store_delete_cafile_entry(cafile_entry);
3172
3173 memprintf(&err, "CA file '%s' deleted!\n", filename);
3174
3175 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3176 return cli_dynmsg(appctx, LOG_NOTICE, err);
3177
3178error:
3179 memprintf(&err, "Can't remove the CA file: %s\n", err ? err : "");
3180 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3181 return cli_dynerr(appctx, err);
3182}
3183
Remi Tricot-Le Breton720e3b92021-04-26 11:00:42 +02003184/* parsing function of 'new ssl crl-file' */
3185static int cli_parse_new_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
3186{
3187 struct cafile_entry *cafile_entry;
3188 char *err = NULL;
3189 char *path;
3190
3191 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
3192 return 1;
3193
3194 if (!*args[3])
3195 return cli_err(appctx, "'new ssl crl-file' expects a filename\n");
3196
3197 path = args[3];
3198
3199 /* The operations on the CKCH architecture are locked so we can
3200 * manipulate ckch_store and ckch_inst */
3201 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3202 return cli_err(appctx, "Can't create a CA file!\nOperations on certificates are currently locked!\n");
3203
3204 cafile_entry = ssl_store_get_cafile_entry(path, 0);
3205 if (cafile_entry) {
3206 memprintf(&err, "CRL file '%s' already exists!\n", path);
3207 goto error;
3208 }
3209
3210 cafile_entry = ssl_store_create_cafile_entry(path, NULL, CAFILE_CRL);
3211 if (!cafile_entry) {
3212 memprintf(&err, "%sCannot allocate memory!\n", err ? err : "");
3213 goto error;
3214 }
3215
3216 /* Add the newly created cafile_entry to the tree so that
3217 * any new ckch instance created from now can use it. */
3218 if (ssl_store_add_uncommitted_cafile_entry(cafile_entry))
3219 goto error;
3220
3221 memprintf(&err, "New CRL file created '%s'!\n", path);
3222
3223 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3224 return cli_dynmsg(appctx, LOG_NOTICE, err);
3225error:
3226 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3227 return cli_dynerr(appctx, err);
3228}
3229
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003230/* Parsing function of `set ssl crl-file` */
3231static int cli_parse_set_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
3232{
3233 char *err = NULL;
3234 int errcode = 0;
3235 struct buffer *buf;
3236
3237 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
3238 return 1;
3239
3240 if (!*args[3] || !payload)
3241 return cli_err(appctx, "'set ssl crl-file expects a filename and CAs as a payload\n");
3242
3243 /* The operations on the CKCH architecture are locked so we can
3244 * manipulate ckch_store and ckch_inst */
3245 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3246 return cli_err(appctx, "Can't update the CRL file!\nOperations on certificates are currently locked!\n");
3247
3248 if ((buf = alloc_trash_chunk()) == NULL) {
3249 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
3250 errcode |= ERR_ALERT | ERR_FATAL;
3251 goto end;
3252 }
3253
3254 if (!chunk_strcpy(buf, args[3])) {
3255 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
3256 errcode |= ERR_ALERT | ERR_FATAL;
3257 goto end;
3258 }
3259
3260 appctx->ctx.ssl.old_crlfile_entry = NULL;
3261 appctx->ctx.ssl.new_crlfile_entry = NULL;
3262
3263 /* if there is an ongoing transaction */
3264 if (crlfile_transaction.path) {
3265 /* if there is an ongoing transaction, check if this is the same file */
3266 if (strcmp(crlfile_transaction.path, buf->area) != 0) {
3267 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", crlfile_transaction.path, buf->area);
3268 errcode |= ERR_ALERT | ERR_FATAL;
3269 goto end;
3270 }
3271 appctx->ctx.ssl.old_crlfile_entry = crlfile_transaction.old_crlfile_entry;
3272 }
3273 else {
3274 /* lookup for the certificate in the tree */
3275 appctx->ctx.ssl.old_crlfile_entry = ssl_store_get_cafile_entry(buf->area, 0);
3276 }
3277
3278 if (!appctx->ctx.ssl.old_crlfile_entry) {
3279 memprintf(&err, "%sCan't replace a CRL file which is not referenced by the configuration!\n",
3280 err ? err : "");
3281 errcode |= ERR_ALERT | ERR_FATAL;
3282 goto end;
3283 }
3284
3285 if (!appctx->ctx.ssl.path) {
3286 /* this is a new transaction, set the path of the transaction */
3287 appctx->ctx.ssl.path = strdup(appctx->ctx.ssl.old_crlfile_entry->path);
3288 if (!appctx->ctx.ssl.path) {
3289 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
3290 errcode |= ERR_ALERT | ERR_FATAL;
3291 goto end;
3292 }
3293 }
3294
3295 if (appctx->ctx.ssl.new_crlfile_entry)
3296 ssl_store_delete_cafile_entry(appctx->ctx.ssl.new_crlfile_entry);
3297
3298 /* Create a new cafile_entry without adding it to the cafile tree. */
3299 appctx->ctx.ssl.new_crlfile_entry = ssl_store_create_cafile_entry(appctx->ctx.ssl.path, NULL, CAFILE_CRL);
3300 if (!appctx->ctx.ssl.new_crlfile_entry) {
3301 memprintf(&err, "%sCannot allocate memory!\n", err ? err : "");
3302 errcode |= ERR_ALERT | ERR_FATAL;
3303 goto end;
3304 }
3305
3306 /* Fill the new entry with the new CRL. */
3307 if (ssl_store_load_ca_from_buf(appctx->ctx.ssl.new_crlfile_entry, payload)) {
3308 memprintf(&err, "%sInvalid payload\n", err ? err : "");
3309 errcode |= ERR_ALERT | ERR_FATAL;
3310 goto end;
3311 }
3312
3313 /* we succeed, we can save the crl in the transaction */
3314
3315 /* if there wasn't a transaction, update the old CA */
3316 if (!crlfile_transaction.old_crlfile_entry) {
3317 crlfile_transaction.old_crlfile_entry = appctx->ctx.ssl.old_crlfile_entry;
3318 crlfile_transaction.path = appctx->ctx.ssl.path;
3319 err = memprintf(&err, "transaction created for CA %s!\n", crlfile_transaction.path);
3320 } else {
3321 err = memprintf(&err, "transaction updated for CA %s!\n", crlfile_transaction.path);
3322 }
3323
3324 /* free the previous CRL file if there was a transaction */
3325 ssl_store_delete_cafile_entry(crlfile_transaction.new_crlfile_entry);
3326
3327 crlfile_transaction.new_crlfile_entry = appctx->ctx.ssl.new_crlfile_entry;
3328
3329 /* creates the SNI ctxs later in the IO handler */
3330
3331end:
3332 free_trash_chunk(buf);
3333
3334 if (errcode & ERR_CODE) {
3335 ssl_store_delete_cafile_entry(appctx->ctx.ssl.new_crlfile_entry);
3336 appctx->ctx.ssl.new_crlfile_entry = NULL;
3337 appctx->ctx.ssl.old_crlfile_entry = NULL;
3338
Tim Duesterhus025b93e2021-11-04 21:03:52 +01003339 ha_free(&appctx->ctx.ssl.path);
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003340
3341 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3342 return cli_dynerr(appctx, memprintf(&err, "%sCan't update %s!\n", err ? err : "", args[3]));
3343 } else {
3344
3345 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3346 return cli_dynmsg(appctx, LOG_NOTICE, err);
3347 }
3348}
3349
3350/* Parsing function of 'commit ssl crl-file' */
3351static int cli_parse_commit_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
3352{
3353 char *err = NULL;
3354
3355 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
3356 return 1;
3357
3358 if (!*args[3])
3359 return cli_err(appctx, "'commit ssl ca-file expects a filename\n");
3360
3361 /* The operations on the CKCH architecture are locked so we can
3362 * manipulate ckch_store and ckch_inst */
3363 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3364 return cli_err(appctx, "Can't commit the CRL file!\nOperations on certificates are currently locked!\n");
3365
3366 if (!crlfile_transaction.path) {
3367 memprintf(&err, "No ongoing transaction! !\n");
3368 goto error;
3369 }
3370
3371 if (strcmp(crlfile_transaction.path, args[3]) != 0) {
3372 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", crlfile_transaction.path, args[3]);
3373 goto error;
3374 }
3375 /* init the appctx structure */
3376 appctx->st2 = SETCERT_ST_INIT;
3377 appctx->ctx.ssl.next_ckchi = NULL;
3378 appctx->ctx.ssl.old_crlfile_entry = crlfile_transaction.old_crlfile_entry;
3379 appctx->ctx.ssl.new_crlfile_entry = crlfile_transaction.new_crlfile_entry;
3380 appctx->ctx.ssl.cafile_type = CAFILE_CRL;
3381
3382 return 0;
3383
3384error:
3385
3386 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3387 err = memprintf(&err, "%sCan't commit %s!\n", err ? err : "", args[3]);
3388
3389 return cli_dynerr(appctx, err);
3390}
3391
3392
3393/* release function of the `commit ssl crl-file' command, free things and unlock the spinlock */
3394static void cli_release_commit_crlfile(struct appctx *appctx)
3395{
3396 if (appctx->st2 != SETCERT_ST_FIN) {
3397 struct cafile_entry *new_crlfile_entry = appctx->ctx.ssl.new_crlfile_entry;
3398
3399 /* Remove the uncommitted cafile_entry from the tree. */
3400 ebmb_delete(&new_crlfile_entry->node);
3401 ssl_store_delete_cafile_entry(new_crlfile_entry);
3402 }
3403 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3404}
3405
Remi Tricot-Le Breton720e3b92021-04-26 11:00:42 +02003406/* parsing function of 'del ssl crl-file' */
3407static int cli_parse_del_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
3408{
3409 struct cafile_entry *cafile_entry;
3410 char *err = NULL;
3411 char *filename;
3412
3413 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
3414 return 1;
3415
3416 if (!*args[3])
3417 return cli_err(appctx, "'del ssl crl-file' expects a CRL file name\n");
3418
3419 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3420 return cli_err(appctx, "Can't delete the CRL file!\nOperations on certificates are currently locked!\n");
3421
3422 filename = args[3];
3423
3424 cafile_entry = ssl_store_get_cafile_entry(filename, 0);
3425 if (!cafile_entry) {
3426 memprintf(&err, "CRL file '%s' doesn't exist!\n", filename);
3427 goto error;
3428 }
3429 if (cafile_entry->type != CAFILE_CRL) {
3430 memprintf(&err, "'del ssl crl-file' does not work on CA files!\n");
3431 goto error;
3432 }
3433
3434 if (!LIST_ISEMPTY(&cafile_entry->ckch_inst_link)) {
3435 memprintf(&err, "CRL file '%s' in use, can't be deleted!\n", filename);
3436 goto error;
3437 }
3438
3439 /* Remove the cafile_entry from the tree */
3440 ebmb_delete(&cafile_entry->node);
3441 ssl_store_delete_cafile_entry(cafile_entry);
3442
3443 memprintf(&err, "CRL file '%s' deleted!\n", filename);
3444
3445 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3446 return cli_dynmsg(appctx, LOG_NOTICE, err);
3447
3448error:
3449 memprintf(&err, "Can't remove the CRL file: %s\n", err ? err : "");
3450 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3451 return cli_dynerr(appctx, err);
3452}
3453
Remi Tricot-Le Bretoneef8e7b2021-04-20 17:42:02 +02003454/* parsing function of 'abort ssl crl-file' */
3455static int cli_parse_abort_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
3456{
3457 char *err = NULL;
3458
3459 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
3460 return 1;
3461
3462 if (!*args[3])
3463 return cli_err(appctx, "'abort ssl crl-file' expects a filename\n");
3464
3465 /* The operations on the CKCH architecture are locked so we can
3466 * manipulate ckch_store and ckch_inst */
3467 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3468 return cli_err(appctx, "Can't abort!\nOperations on certificates are currently locked!\n");
3469
3470 if (!crlfile_transaction.path) {
3471 memprintf(&err, "No ongoing transaction!\n");
3472 goto error;
3473 }
3474
3475 if (strcmp(crlfile_transaction.path, args[3]) != 0) {
3476 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to abort a transaction for '%s'\n", crlfile_transaction.path, args[3]);
3477 goto error;
3478 }
3479
3480 /* Only free the uncommitted cafile_entry here, because the SNI and instances were not generated yet */
3481 ssl_store_delete_cafile_entry(crlfile_transaction.new_crlfile_entry);
3482 crlfile_transaction.new_crlfile_entry = NULL;
3483 crlfile_transaction.old_crlfile_entry = NULL;
3484 ha_free(&crlfile_transaction.path);
3485
3486 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3487
3488 err = memprintf(&err, "Transaction aborted for certificate '%s'!\n", args[3]);
3489 return cli_dynmsg(appctx, LOG_NOTICE, err);
3490
3491error:
3492 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3493
3494 return cli_dynerr(appctx, err);
3495}
3496
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003497
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003498/*
3499 * Display a Certificate Resignation List's information.
3500 * The information displayed is inspired by the output of 'openssl crl -in
3501 * crl.pem -text'.
3502 * Returns 0 in case of success.
3503 */
3504static int show_crl_detail(X509_CRL *crl, struct buffer *out)
3505{
3506 BIO *bio = NULL;
3507 struct buffer *tmp = alloc_trash_chunk();
3508 long version;
3509 X509_NAME *issuer;
3510 int write = -1;
3511 STACK_OF(X509_REVOKED) *rev = NULL;
3512 X509_REVOKED *rev_entry = NULL;
3513 int i;
3514
3515 if (!tmp)
3516 return -1;
3517
3518 if ((bio = BIO_new(BIO_s_mem())) == NULL)
3519 goto end;
3520
3521 /* Version (as displayed by 'openssl crl') */
3522 version = X509_CRL_get_version(crl);
3523 chunk_appendf(out, "Version %ld\n", version + 1);
3524
3525 /* Signature Algorithm */
3526 chunk_appendf(out, "Signature Algorithm: %s\n", OBJ_nid2ln(X509_CRL_get_signature_nid(crl)));
3527
3528 /* Issuer */
3529 chunk_appendf(out, "Issuer: ");
3530 if ((issuer = X509_CRL_get_issuer(crl)) == NULL)
3531 goto end;
3532 if ((ssl_sock_get_dn_oneline(issuer, tmp)) == -1)
3533 goto end;
3534 *(tmp->area + tmp->data) = '\0';
3535 chunk_appendf(out, "%s\n", tmp->area);
3536
3537 /* Last Update */
3538 chunk_appendf(out, "Last Update: ");
3539 chunk_reset(tmp);
Remi Tricot-Le Bretond75b99e2021-05-17 11:45:55 +02003540 if (BIO_reset(bio) == -1)
3541 goto end;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003542 if (ASN1_TIME_print(bio, X509_CRL_get0_lastUpdate(crl)) == 0)
3543 goto end;
3544 write = BIO_read(bio, tmp->area, tmp->size-1);
3545 tmp->area[write] = '\0';
3546 chunk_appendf(out, "%s\n", tmp->area);
3547
3548
3549 /* Next Update */
3550 chunk_appendf(out, "Next Update: ");
3551 chunk_reset(tmp);
Remi Tricot-Le Bretond75b99e2021-05-17 11:45:55 +02003552 if (BIO_reset(bio) == -1)
3553 goto end;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003554 if (ASN1_TIME_print(bio, X509_CRL_get0_nextUpdate(crl)) == 0)
3555 goto end;
3556 write = BIO_read(bio, tmp->area, tmp->size-1);
3557 tmp->area[write] = '\0';
3558 chunk_appendf(out, "%s\n", tmp->area);
3559
3560
3561 /* Revoked Certificates */
3562 rev = X509_CRL_get_REVOKED(crl);
3563 if (sk_X509_REVOKED_num(rev) > 0)
3564 chunk_appendf(out, "Revoked Certificates:\n");
3565 else
3566 chunk_appendf(out, "No Revoked Certificates.\n");
3567
3568 for (i = 0; i < sk_X509_REVOKED_num(rev); i++) {
3569 rev_entry = sk_X509_REVOKED_value(rev, i);
3570
3571 /* Serial Number and Revocation Date */
Remi Tricot-Le Bretond75b99e2021-05-17 11:45:55 +02003572 if (BIO_reset(bio) == -1)
3573 goto end;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003574 BIO_printf(bio , " Serial Number: ");
Remi Tricot-Le Breton18c7d832021-05-17 18:38:34 +02003575 i2a_ASN1_INTEGER(bio, (ASN1_INTEGER*)X509_REVOKED_get0_serialNumber(rev_entry));
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003576 BIO_printf(bio, "\n Revocation Date: ");
Remi Tricot-Le Bretona6b27842021-05-18 10:06:00 +02003577 if (ASN1_TIME_print(bio, X509_REVOKED_get0_revocationDate(rev_entry)) == 0)
3578 goto end;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003579 BIO_printf(bio, "\n");
3580
3581 write = BIO_read(bio, tmp->area, tmp->size-1);
3582 tmp->area[write] = '\0';
3583 chunk_appendf(out, "%s", tmp->area);
3584 }
3585
3586end:
3587 free_trash_chunk(tmp);
3588 if (bio)
3589 BIO_free(bio);
3590
3591 return 0;
3592}
3593
3594/* IO handler of details "show ssl crl-file <filename[:index]>" */
3595static int cli_io_handler_show_crlfile_detail(struct appctx *appctx)
3596{
Christopher Faulet908628c2022-03-25 16:43:49 +01003597 struct conn_stream *cs = appctx->owner;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003598 struct cafile_entry *cafile_entry = appctx->ctx.cli.p0;
3599 struct buffer *out = alloc_trash_chunk();
3600 int i;
3601 X509_CRL *crl;
3602 STACK_OF(X509_OBJECT) *objs;
3603 int retval = 0;
3604 long index = (long)appctx->ctx.cli.p1;
3605
3606 if (!out)
3607 goto end_no_putchk;
3608
3609 chunk_appendf(out, "Filename: ");
3610 if (cafile_entry == crlfile_transaction.new_crlfile_entry)
3611 chunk_appendf(out, "*");
3612 chunk_appendf(out, "%s\n", cafile_entry->path);
3613
3614 chunk_appendf(out, "Status: ");
3615 if (!cafile_entry->ca_store)
3616 chunk_appendf(out, "Empty\n");
3617 else if (LIST_ISEMPTY(&cafile_entry->ckch_inst_link))
3618 chunk_appendf(out, "Unused\n");
3619 else
3620 chunk_appendf(out, "Used\n");
3621
3622 if (!cafile_entry->ca_store)
3623 goto end;
3624
3625 objs = X509_STORE_get0_objects(cafile_entry->ca_store);
3626 for (i = 0; i < sk_X509_OBJECT_num(objs); i++) {
3627 crl = X509_OBJECT_get0_X509_CRL(sk_X509_OBJECT_value(objs, i));
3628 if (!crl)
3629 continue;
3630
3631 /* CRL indexes start at 1 on the CLI output. */
3632 if (index && index-1 != i)
3633 continue;
3634
Remi Tricot-Le Bretone8041fe2022-04-05 16:44:21 +02003635 chunk_appendf(out, " \nCertificate Revocation List #%d:\n", i+1);
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003636 retval = show_crl_detail(crl, out);
3637 if (retval < 0)
3638 goto end_no_putchk;
3639 else if (retval || index)
3640 goto end;
3641 }
3642
3643end:
Christopher Faulet908628c2022-03-25 16:43:49 +01003644 if (ci_putchk(cs_ic(cs), out) == -1) {
Christopher Fauleta0bdec32022-04-04 07:51:21 +02003645 cs_rx_room_blk(cs);
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003646 goto yield;
3647 }
3648
3649end_no_putchk:
3650 free_trash_chunk(out);
3651 return 1;
3652yield:
3653 free_trash_chunk(out);
3654 return 0; /* should come back */
3655}
3656
3657/* parsing function for 'show ssl crl-file [crlfile[:index]]' */
3658static int cli_parse_show_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
3659{
3660 struct cafile_entry *cafile_entry;
3661 long index = 0;
3662 char *colons;
3663 char *err = NULL;
3664
3665 if (!cli_has_level(appctx, ACCESS_LVL_OPER))
3666 return cli_err(appctx, "Can't allocate memory!\n");
3667
3668 /* The operations on the CKCH architecture are locked so we can
3669 * manipulate ckch_store and ckch_inst */
3670 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3671 return cli_err(appctx, "Can't show!\nOperations on certificates are currently locked!\n");
3672
3673 /* check if there is a certificate to lookup */
3674 if (*args[3]) {
3675
3676 /* Look for an optional index after the CRL file name */
3677 colons = strchr(args[3], ':');
3678 if (colons) {
3679 char *endptr;
3680
3681 index = strtol(colons + 1, &endptr, 10);
3682 /* Indexes start at 1 */
3683 if (colons + 1 == endptr || *endptr != '\0' || index <= 0) {
3684 memprintf(&err, "wrong CRL index after colons in '%s'!", args[3]);
3685 goto error;
3686 }
3687 *colons = '\0';
3688 }
3689
3690 if (*args[3] == '*') {
3691 if (!crlfile_transaction.new_crlfile_entry)
3692 goto error;
3693
3694 cafile_entry = crlfile_transaction.new_crlfile_entry;
3695
3696 if (strcmp(args[3] + 1, cafile_entry->path) != 0)
3697 goto error;
3698
3699 } else {
3700 /* Get the "original" cafile_entry and not the
3701 * uncommitted one if it exists. */
3702 if ((cafile_entry = ssl_store_get_cafile_entry(args[3], 1)) == NULL || cafile_entry->type != CAFILE_CRL)
3703 goto error;
3704 }
3705
3706 appctx->ctx.cli.p0 = cafile_entry;
3707 appctx->ctx.cli.p1 = (void*)index;
3708 /* use the IO handler that shows details */
3709 appctx->io_handler = cli_io_handler_show_crlfile_detail;
3710 }
3711
3712 return 0;
3713
3714error:
3715 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3716 if (err)
3717 return cli_dynerr(appctx, err);
3718 return cli_err(appctx, "Can't display the CA file : Not found!\n");
3719}
3720
3721/* IO handler of "show ssl crl-file". The command taking a specific CRL file name
3722 * is managed in cli_io_handler_show_crlfile_detail. */
3723static int cli_io_handler_show_crlfile(struct appctx *appctx)
3724{
3725 struct buffer *trash = alloc_trash_chunk();
3726 struct ebmb_node *node;
Christopher Faulet908628c2022-03-25 16:43:49 +01003727 struct conn_stream *cs = appctx->owner;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003728 struct cafile_entry *cafile_entry;
3729
3730 if (trash == NULL)
3731 return 1;
3732
3733 if (!appctx->ctx.ssl.old_crlfile_entry) {
3734 if (crlfile_transaction.old_crlfile_entry) {
3735 chunk_appendf(trash, "# transaction\n");
3736 chunk_appendf(trash, "*%s\n", crlfile_transaction.old_crlfile_entry->path);
3737 }
3738 }
3739
3740 /* First time in this io_handler. */
3741 if (!appctx->ctx.cli.p0) {
3742 chunk_appendf(trash, "# filename\n");
3743 node = ebmb_first(&cafile_tree);
3744 } else {
3745 /* We yielded during a previous call. */
3746 node = &((struct cafile_entry*)appctx->ctx.cli.p0)->node;
3747 }
3748
3749 while (node) {
3750 cafile_entry = ebmb_entry(node, struct cafile_entry, node);
3751 if (cafile_entry->type == CAFILE_CRL) {
3752 chunk_appendf(trash, "%s\n", cafile_entry->path);
3753 }
3754
3755 node = ebmb_next(node);
Christopher Faulet908628c2022-03-25 16:43:49 +01003756 if (ci_putchk(cs_ic(cs), trash) == -1) {
Christopher Fauleta0bdec32022-04-04 07:51:21 +02003757 cs_rx_room_blk(cs);
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003758 goto yield;
3759 }
3760 }
3761
3762 appctx->ctx.cli.p0 = NULL;
3763 free_trash_chunk(trash);
3764 return 1;
3765yield:
3766
3767 free_trash_chunk(trash);
3768 appctx->ctx.cli.p0 = cafile_entry;
3769 return 0; /* should come back */
3770}
3771
3772
3773/* release function of the 'show ssl crl-file' command */
3774static void cli_release_show_crlfile(struct appctx *appctx)
3775{
3776 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3777}
3778
3779
William Lallemandee8530c2020-06-23 18:19:42 +02003780void ckch_deinit()
3781{
3782 struct eb_node *node, *next;
3783 struct ckch_store *store;
William Lallemandb0c48272022-04-26 15:44:53 +02003784 struct ebmb_node *canode;
William Lallemandee8530c2020-06-23 18:19:42 +02003785
William Lallemandb0c48272022-04-26 15:44:53 +02003786 /* deinit the ckch stores */
William Lallemandee8530c2020-06-23 18:19:42 +02003787 node = eb_first(&ckchs_tree);
3788 while (node) {
3789 next = eb_next(node);
3790 store = ebmb_entry(node, struct ckch_store, node);
3791 ckch_store_free(store);
3792 node = next;
3793 }
William Lallemandb0c48272022-04-26 15:44:53 +02003794
3795 /* deinit the ca-file store */
3796 canode = ebmb_first(&cafile_tree);
3797 while (canode) {
3798 struct cafile_entry *entry = NULL;
3799
3800 entry = ebmb_entry(canode, struct cafile_entry, node);
3801 canode = ebmb_next(canode);
3802 ssl_store_delete_cafile_entry(entry);
3803 }
William Lallemandee8530c2020-06-23 18:19:42 +02003804}
William Lallemandda8584c2020-05-14 10:14:37 +02003805
3806/* register cli keywords */
3807static struct cli_kw_list cli_kws = {{ },{
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01003808 { { "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 },
3809 { { "set", "ssl", "cert", NULL }, "set ssl cert <certfile> <payload> : replace a certificate file", cli_parse_set_cert, NULL, NULL },
3810 { { "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 },
3811 { { "abort", "ssl", "cert", NULL }, "abort ssl cert <certfile> : abort a transaction for a certificate file", cli_parse_abort_cert, NULL, NULL },
3812 { { "del", "ssl", "cert", NULL }, "del ssl cert <certfile> : delete an unused certificate file", cli_parse_del_cert, NULL, NULL },
3813 { { "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 },
3814
Amaury Denoyelleb11ad9e2021-05-21 11:01:10 +02003815 { { "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 +01003816 { { "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 +02003817 { { "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 +01003818 { { "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 +01003819 { { "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 +01003820 { { "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 +02003821
Remi Tricot-Le Breton720e3b92021-04-26 11:00:42 +02003822 { { "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 +02003823 { { "set", "ssl", "crl-file", NULL }, "set ssl crl-file <crlfile> <payload> : replace a CRL file", cli_parse_set_crlfile, NULL, NULL },
3824 { { "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 +02003825 { { "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 +02003826 { { "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 +02003827 { { "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 +02003828 { { NULL }, NULL, NULL, NULL }
3829}};
3830
3831INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws);
3832