blob: 6238c7e0a0f5ddc885c3096a4b794d2bf61a8233 [file] [log] [blame]
William Lallemand03c331c2020-05-13 10:10:01 +02001/*
2 *
3 * Copyright (C) 2020 HAProxy Technologies, William Lallemand <wlallemand@haproxy.com>
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version
8 * 2 of the License, or (at your option) any later version.
9 *
10 */
11
12#define _GNU_SOURCE
13#include <ctype.h>
William Lallemand87fd9942022-04-01 20:12:03 +020014#include <dirent.h>
William Lallemand03c331c2020-05-13 10:10:01 +020015#include <errno.h>
16#include <fcntl.h>
17#include <stdio.h>
18#include <stdlib.h>
19#include <string.h>
Willy Tarreauaeed4a82020-06-04 22:01:04 +020020#include <syslog.h>
William Lallemand03c331c2020-05-13 10:10:01 +020021#include <unistd.h>
22
23#include <sys/stat.h>
24#include <sys/types.h>
25
Willy Tarreau74f24562021-10-06 17:54:12 +020026#include <import/ebpttree.h>
Willy Tarreaub2551052020-06-09 09:07:15 +020027#include <import/ebsttree.h>
28
Willy Tarreau50c2f1e2022-05-04 19:26:59 +020029#include <haproxy/applet.h>
Willy Tarreau8d366972020-05-27 16:10:29 +020030#include <haproxy/base64.h>
Willy Tarreauf1d32c42020-06-04 21:07:02 +020031#include <haproxy/channel.h>
Willy Tarreau83487a82020-06-04 20:19:54 +020032#include <haproxy/cli.h>
Christopher Faulet908628c2022-03-25 16:43:49 +010033#include <haproxy/conn_stream.h>
34#include <haproxy/cs_utils.h>
Willy Tarreau8d366972020-05-27 16:10:29 +020035#include <haproxy/errors.h>
Willy Tarreau47d7f902020-06-04 14:25:47 +020036#include <haproxy/ssl_ckch.h>
Willy Tarreau209108d2020-06-04 20:30:20 +020037#include <haproxy/ssl_sock.h>
Willy Tarreaub2bd8652020-06-04 14:21:22 +020038#include <haproxy/ssl_utils.h>
Willy Tarreau48fbcae2020-06-03 18:09:46 +020039#include <haproxy/tools.h>
William Lallemand03c331c2020-05-13 10:10:01 +020040
William Lallemandda8584c2020-05-14 10:14:37 +020041/* Uncommitted CKCH transaction */
42
43static struct {
44 struct ckch_store *new_ckchs;
45 struct ckch_store *old_ckchs;
46 char *path;
47} ckchs_transaction;
48
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +010049/* Uncommitted CA file transaction */
50
51static struct {
52 struct cafile_entry *old_cafile_entry;
53 struct cafile_entry *new_cafile_entry;
54 char *path;
55} cafile_transaction;
William Lallemandda8584c2020-05-14 10:14:37 +020056
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +020057/* Uncommitted CRL file transaction */
58
59static struct {
60 struct cafile_entry *old_crlfile_entry;
61 struct cafile_entry *new_crlfile_entry;
62 char *path;
63} crlfile_transaction;
64
Willy Tarreau50c2f1e2022-05-04 19:26:59 +020065/* CLI context used by "show cafile" */
66struct show_cafile_ctx {
67 struct cafile_entry *cur_cafile_entry;
68 struct cafile_entry *old_cafile_entry;
69 int ca_index;
70 int show_all;
71};
William Lallemand03c331c2020-05-13 10:10:01 +020072
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +020073/* CLI context used by "show crlfile" */
74struct show_crlfile_ctx {
75 struct cafile_entry *cafile_entry;
76 struct crlfile_entry *old_crlfile_entry;
77 int index;
78};
79
Willy Tarreau96c9a6c2022-05-04 19:51:37 +020080/* CLI context used by "show cert" */
81struct show_cert_ctx {
82 struct ckch_store *old_ckchs;
83 struct ckch_store *cur_ckchs;
84 int transaction;
85};
86
Willy Tarreaua645b6a2022-05-04 19:58:00 +020087/* CLI context used by "commit cert" */
88struct commit_cert_ctx {
89 struct ckch_store *old_ckchs;
90 struct ckch_store *new_ckchs;
91 struct ckch_inst *next_ckchi;
Willy Tarreaucb1b4ed2022-05-05 08:15:27 +020092 enum {
93 CERT_ST_INIT = 0,
94 CERT_ST_GEN,
95 CERT_ST_INSERT,
96 CERT_ST_FIN,
97 } state;
Willy Tarreaua645b6a2022-05-04 19:58:00 +020098};
99
Willy Tarreau329f4b42022-05-04 20:05:55 +0200100/* CLI context used by "set cert" */
101struct set_cert_ctx {
102 struct ckch_store *old_ckchs;
103 struct ckch_store *new_ckchs;
104 char *path;
105};
Willy Tarreau96c9a6c2022-05-04 19:51:37 +0200106
Willy Tarreaua37693f2022-05-04 20:12:55 +0200107/* CLI context used by "set ca-file" */
108struct set_cafile_ctx {
109 struct cafile_entry *old_cafile_entry;
110 struct cafile_entry *new_cafile_entry;
111 char *path;
112};
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +0100113
Willy Tarreaua06b9a52022-05-04 20:33:03 +0200114/* CLI context used by "set crl-file" */
115struct set_crlfile_ctx {
116 struct cafile_entry *old_crlfile_entry;
117 struct cafile_entry *new_crlfile_entry;
118 char *path;
119};
Willy Tarreau329f4b42022-05-04 20:05:55 +0200120
Willy Tarreaudec23dc2022-05-04 20:25:05 +0200121/* CLI context used by "commit cafile" and "commit crlfile" */
122struct commit_cacrlfile_ctx {
123 struct cafile_entry *old_cafile_entry;
124 struct cafile_entry *new_cafile_entry;
125 struct cafile_entry *old_crlfile_entry;
126 struct cafile_entry *new_crlfile_entry;
127 struct ckch_inst_link *next_ckchi_link;
128 struct ckch_inst *next_ckchi;
129 int cafile_type; /* either CA or CRL, depending on the current command */
Willy Tarreau1d6dd802022-05-05 08:17:29 +0200130 enum {
131 CACRL_ST_INIT = 0,
132 CACRL_ST_GEN,
133 CACRL_ST_INSERT,
134 CACRL_ST_FIN,
135 } state;
Willy Tarreaudec23dc2022-05-04 20:25:05 +0200136};
137
Willy Tarreaua37693f2022-05-04 20:12:55 +0200138
William Lallemand03c331c2020-05-13 10:10:01 +0200139/******************** cert_key_and_chain functions *************************
140 * These are the functions that fills a cert_key_and_chain structure. For the
141 * functions filling a SSL_CTX from a cert_key_and_chain, see ssl_sock.c
142 */
143
144/*
145 * Try to parse Signed Certificate Timestamp List structure. This function
146 * makes only basic test if the data seems like SCTL. No signature validation
147 * is performed.
148 */
149static int ssl_sock_parse_sctl(struct buffer *sctl)
150{
151 int ret = 1;
152 int len, pos, sct_len;
153 unsigned char *data;
154
155 if (sctl->data < 2)
156 goto out;
157
158 data = (unsigned char *) sctl->area;
159 len = (data[0] << 8) | data[1];
160
161 if (len + 2 != sctl->data)
162 goto out;
163
164 data = data + 2;
165 pos = 0;
166 while (pos < len) {
167 if (len - pos < 2)
168 goto out;
169
170 sct_len = (data[pos] << 8) | data[pos + 1];
171 if (pos + sct_len + 2 > len)
172 goto out;
173
174 pos += sct_len + 2;
175 }
176
177 ret = 0;
178
179out:
180 return ret;
181}
182
183/* Try to load a sctl from a buffer <buf> if not NULL, or read the file <sctl_path>
184 * It fills the ckch->sctl buffer
185 * return 0 on success or != 0 on failure */
186int ssl_sock_load_sctl_from_file(const char *sctl_path, char *buf, struct cert_key_and_chain *ckch, char **err)
187{
188 int fd = -1;
189 int r = 0;
190 int ret = 1;
191 struct buffer tmp;
192 struct buffer *src;
193 struct buffer *sctl;
194
195 if (buf) {
William Lallemand8d673942021-01-27 14:58:51 +0100196 chunk_initstr(&tmp, buf);
William Lallemand03c331c2020-05-13 10:10:01 +0200197 src = &tmp;
198 } else {
199 fd = open(sctl_path, O_RDONLY);
200 if (fd == -1)
201 goto end;
202
203 trash.data = 0;
204 while (trash.data < trash.size) {
205 r = read(fd, trash.area + trash.data, trash.size - trash.data);
206 if (r < 0) {
207 if (errno == EINTR)
208 continue;
209 goto end;
210 }
211 else if (r == 0) {
212 break;
213 }
214 trash.data += r;
215 }
216 src = &trash;
217 }
218
219 ret = ssl_sock_parse_sctl(src);
220 if (ret)
221 goto end;
222
223 sctl = calloc(1, sizeof(*sctl));
224 if (!chunk_dup(sctl, src)) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100225 ha_free(&sctl);
William Lallemand03c331c2020-05-13 10:10:01 +0200226 goto end;
227 }
228 /* no error, fill ckch with new context, old context must be free */
229 if (ckch->sctl) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100230 ha_free(&ckch->sctl->area);
William Lallemand03c331c2020-05-13 10:10:01 +0200231 free(ckch->sctl);
232 }
233 ckch->sctl = sctl;
234 ret = 0;
235end:
236 if (fd != -1)
237 close(fd);
238
239 return ret;
240}
241
242#if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) || defined OPENSSL_IS_BORINGSSL)
243/*
Ilya Shipitsin46a030c2020-07-05 16:36:08 +0500244 * This function load the OCSP Response in DER format contained in file at
William Lallemand03c331c2020-05-13 10:10:01 +0200245 * path 'ocsp_path' or base64 in a buffer <buf>
246 *
247 * Returns 0 on success, 1 in error case.
248 */
249int ssl_sock_load_ocsp_response_from_file(const char *ocsp_path, char *buf, struct cert_key_and_chain *ckch, char **err)
250{
251 int fd = -1;
252 int r = 0;
253 int ret = 1;
254 struct buffer *ocsp_response;
255 struct buffer *src = NULL;
256
257 if (buf) {
258 int i, j;
259 /* if it's from a buffer it will be base64 */
260
261 /* remove \r and \n from the payload */
262 for (i = 0, j = 0; buf[i]; i++) {
263 if (buf[i] == '\r' || buf[i] == '\n')
264 continue;
265 buf[j++] = buf[i];
266 }
267 buf[j] = 0;
268
269 ret = base64dec(buf, j, trash.area, trash.size);
270 if (ret < 0) {
271 memprintf(err, "Error reading OCSP response in base64 format");
272 goto end;
273 }
274 trash.data = ret;
275 src = &trash;
276 } else {
277 fd = open(ocsp_path, O_RDONLY);
278 if (fd == -1) {
279 memprintf(err, "Error opening OCSP response file");
280 goto end;
281 }
282
283 trash.data = 0;
284 while (trash.data < trash.size) {
285 r = read(fd, trash.area + trash.data, trash.size - trash.data);
286 if (r < 0) {
287 if (errno == EINTR)
288 continue;
289
290 memprintf(err, "Error reading OCSP response from file");
291 goto end;
292 }
293 else if (r == 0) {
294 break;
295 }
296 trash.data += r;
297 }
298 close(fd);
299 fd = -1;
300 src = &trash;
301 }
302
303 ocsp_response = calloc(1, sizeof(*ocsp_response));
304 if (!chunk_dup(ocsp_response, src)) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100305 ha_free(&ocsp_response);
William Lallemand03c331c2020-05-13 10:10:01 +0200306 goto end;
307 }
308 /* no error, fill ckch with new context, old context must be free */
309 if (ckch->ocsp_response) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100310 ha_free(&ckch->ocsp_response->area);
William Lallemand03c331c2020-05-13 10:10:01 +0200311 free(ckch->ocsp_response);
312 }
313 ckch->ocsp_response = ocsp_response;
314 ret = 0;
315end:
316 if (fd != -1)
317 close(fd);
318
319 return ret;
320}
321#endif
322
323/*
324 * Try to load in a ckch every files related to a ckch.
325 * (PEM, sctl, ocsp, issuer etc.)
326 *
327 * This function is only used to load files during the configuration parsing,
328 * it is not used with the CLI.
329 *
330 * This allows us to carry the contents of the file without having to read the
331 * file multiple times. The caller must call
332 * ssl_sock_free_cert_key_and_chain_contents.
333 *
334 * returns:
335 * 0 on Success
336 * 1 on SSL Failure
337 */
338int ssl_sock_load_files_into_ckch(const char *path, struct cert_key_and_chain *ckch, char **err)
339{
William Lallemand8e8581e2020-10-20 17:36:46 +0200340 struct buffer *fp = NULL;
William Lallemand03c331c2020-05-13 10:10:01 +0200341 int ret = 1;
342
343 /* try to load the PEM */
344 if (ssl_sock_load_pem_into_ckch(path, NULL, ckch , err) != 0) {
345 goto end;
346 }
347
William Lallemand8e8581e2020-10-20 17:36:46 +0200348 fp = alloc_trash_chunk();
349 if (!fp) {
350 memprintf(err, "%sCan't allocate memory\n", err && *err ? *err : "");
351 goto end;
352 }
353
354 if (!chunk_strcpy(fp, path) || (b_data(fp) > MAXPATHLEN)) {
355 memprintf(err, "%s '%s' filename too long'.\n",
356 err && *err ? *err : "", fp->area);
357 ret = 1;
358 goto end;
359 }
360
William Lallemand089c1382020-10-23 17:35:12 +0200361 /* remove the ".crt" extension */
William Lallemand8e8581e2020-10-20 17:36:46 +0200362 if (global_ssl.extra_files_noext) {
363 char *ext;
364
365 /* look for the extension */
366 if ((ext = strrchr(fp->area, '.'))) {
William Lallemand8e8581e2020-10-20 17:36:46 +0200367
Tim Duesterhuse5ff1412021-01-02 22:31:53 +0100368 if (strcmp(ext, ".crt") == 0) {
William Lallemand8e8581e2020-10-20 17:36:46 +0200369 *ext = '\0';
William Lallemand089c1382020-10-23 17:35:12 +0200370 fp->data = strlen(fp->area);
371 }
William Lallemand8e8581e2020-10-20 17:36:46 +0200372 }
373
374 }
375
William Lallemand03c331c2020-05-13 10:10:01 +0200376 /* try to load an external private key if it wasn't in the PEM */
377 if ((ckch->key == NULL) && (global_ssl.extra_files & SSL_GF_KEY)) {
William Lallemand03c331c2020-05-13 10:10:01 +0200378 struct stat st;
379
William Lallemand8e8581e2020-10-20 17:36:46 +0200380
381 if (!chunk_strcat(fp, ".key") || (b_data(fp) > MAXPATHLEN)) {
382 memprintf(err, "%s '%s' filename too long'.\n",
383 err && *err ? *err : "", fp->area);
384 ret = 1;
385 goto end;
386 }
387
388 if (stat(fp->area, &st) == 0) {
389 if (ssl_sock_load_key_into_ckch(fp->area, NULL, ckch, err)) {
William Lallemand03c331c2020-05-13 10:10:01 +0200390 memprintf(err, "%s '%s' is present but cannot be read or parsed'.\n",
William Lallemand8e8581e2020-10-20 17:36:46 +0200391 err && *err ? *err : "", fp->area);
William Lallemand03c331c2020-05-13 10:10:01 +0200392 goto end;
393 }
394 }
William Lallemand03c331c2020-05-13 10:10:01 +0200395
William Lallemand8e8581e2020-10-20 17:36:46 +0200396 if (ckch->key == NULL) {
397 memprintf(err, "%sNo Private Key found in '%s'.\n", err && *err ? *err : "", fp->area);
398 goto end;
399 }
400 /* remove the added extension */
401 *(fp->area + fp->data - strlen(".key")) = '\0';
402 b_sub(fp, strlen(".key"));
William Lallemand03c331c2020-05-13 10:10:01 +0200403 }
404
405 if (!X509_check_private_key(ckch->cert, ckch->key)) {
406 memprintf(err, "%sinconsistencies between private key and certificate loaded '%s'.\n",
407 err && *err ? *err : "", path);
408 goto end;
409 }
410
Ilya Shipitsinc47d6762021-02-13 11:45:33 +0500411#ifdef HAVE_SSL_SCTL
William Lallemand03c331c2020-05-13 10:10:01 +0200412 /* try to load the sctl file */
413 if (global_ssl.extra_files & SSL_GF_SCTL) {
William Lallemand03c331c2020-05-13 10:10:01 +0200414 struct stat st;
415
William Lallemand8e8581e2020-10-20 17:36:46 +0200416 if (!chunk_strcat(fp, ".sctl") || b_data(fp) > MAXPATHLEN) {
417 memprintf(err, "%s '%s' filename too long'.\n",
418 err && *err ? *err : "", fp->area);
419 ret = 1;
420 goto end;
421 }
422
423 if (stat(fp->area, &st) == 0) {
424 if (ssl_sock_load_sctl_from_file(fp->area, NULL, ckch, err)) {
William Lallemand03c331c2020-05-13 10:10:01 +0200425 memprintf(err, "%s '%s.sctl' is present but cannot be read or parsed'.\n",
William Lallemand8e8581e2020-10-20 17:36:46 +0200426 err && *err ? *err : "", fp->area);
William Lallemand03c331c2020-05-13 10:10:01 +0200427 ret = 1;
428 goto end;
429 }
430 }
William Lallemand8e8581e2020-10-20 17:36:46 +0200431 /* remove the added extension */
432 *(fp->area + fp->data - strlen(".sctl")) = '\0';
433 b_sub(fp, strlen(".sctl"));
William Lallemand03c331c2020-05-13 10:10:01 +0200434 }
435#endif
436
437 /* try to load an ocsp response file */
438 if (global_ssl.extra_files & SSL_GF_OCSP) {
William Lallemand03c331c2020-05-13 10:10:01 +0200439 struct stat st;
440
William Lallemand8e8581e2020-10-20 17:36:46 +0200441 if (!chunk_strcat(fp, ".ocsp") || b_data(fp) > MAXPATHLEN) {
442 memprintf(err, "%s '%s' filename too long'.\n",
443 err && *err ? *err : "", fp->area);
444 ret = 1;
445 goto end;
446 }
447
448 if (stat(fp->area, &st) == 0) {
449 if (ssl_sock_load_ocsp_response_from_file(fp->area, NULL, ckch, err)) {
William Lallemand03c331c2020-05-13 10:10:01 +0200450 ret = 1;
451 goto end;
452 }
453 }
William Lallemand8e8581e2020-10-20 17:36:46 +0200454 /* remove the added extension */
455 *(fp->area + fp->data - strlen(".ocsp")) = '\0';
456 b_sub(fp, strlen(".ocsp"));
William Lallemand03c331c2020-05-13 10:10:01 +0200457 }
458
459#ifndef OPENSSL_IS_BORINGSSL /* Useless for BoringSSL */
460 if (ckch->ocsp_response && (global_ssl.extra_files & SSL_GF_OCSP_ISSUER)) {
461 /* if no issuer was found, try to load an issuer from the .issuer */
462 if (!ckch->ocsp_issuer) {
463 struct stat st;
William Lallemand8e8581e2020-10-20 17:36:46 +0200464
465 if (!chunk_strcat(fp, ".issuer") || b_data(fp) > MAXPATHLEN) {
466 memprintf(err, "%s '%s' filename too long'.\n",
467 err && *err ? *err : "", fp->area);
468 ret = 1;
469 goto end;
470 }
William Lallemand03c331c2020-05-13 10:10:01 +0200471
William Lallemand8e8581e2020-10-20 17:36:46 +0200472 if (stat(fp->area, &st) == 0) {
473 if (ssl_sock_load_issuer_file_into_ckch(fp->area, NULL, ckch, err)) {
William Lallemand03c331c2020-05-13 10:10:01 +0200474 ret = 1;
475 goto end;
476 }
477
478 if (X509_check_issued(ckch->ocsp_issuer, ckch->cert) != X509_V_OK) {
479 memprintf(err, "%s '%s' is not an issuer'.\n",
William Lallemand8e8581e2020-10-20 17:36:46 +0200480 err && *err ? *err : "", fp->area);
William Lallemand03c331c2020-05-13 10:10:01 +0200481 ret = 1;
482 goto end;
483 }
484 }
William Lallemand8e8581e2020-10-20 17:36:46 +0200485 /* remove the added extension */
486 *(fp->area + fp->data - strlen(".issuer")) = '\0';
487 b_sub(fp, strlen(".issuer"));
William Lallemand03c331c2020-05-13 10:10:01 +0200488 }
489 }
490#endif
491
492 ret = 0;
493
494end:
495
496 ERR_clear_error();
497
498 /* Something went wrong in one of the reads */
499 if (ret != 0)
500 ssl_sock_free_cert_key_and_chain_contents(ckch);
501
William Lallemand8e8581e2020-10-20 17:36:46 +0200502 free_trash_chunk(fp);
503
William Lallemand03c331c2020-05-13 10:10:01 +0200504 return ret;
505}
506
507/*
508 * Try to load a private key file from a <path> or a buffer <buf>
509 *
510 * If it failed you should not attempt to use the ckch but free it.
511 *
512 * Return 0 on success or != 0 on failure
513 */
514int ssl_sock_load_key_into_ckch(const char *path, char *buf, struct cert_key_and_chain *ckch , char **err)
515{
516 BIO *in = NULL;
517 int ret = 1;
518 EVP_PKEY *key = NULL;
519
520 if (buf) {
521 /* reading from a buffer */
522 in = BIO_new_mem_buf(buf, -1);
523 if (in == NULL) {
524 memprintf(err, "%sCan't allocate memory\n", err && *err ? *err : "");
525 goto end;
526 }
527
528 } else {
529 /* reading from a file */
530 in = BIO_new(BIO_s_file());
531 if (in == NULL)
532 goto end;
533
534 if (BIO_read_filename(in, path) <= 0)
535 goto end;
536 }
537
538 /* Read Private Key */
539 key = PEM_read_bio_PrivateKey(in, NULL, NULL, NULL);
540 if (key == NULL) {
541 memprintf(err, "%sunable to load private key from file '%s'.\n",
542 err && *err ? *err : "", path);
543 goto end;
544 }
545
546 ret = 0;
547
548 SWAP(ckch->key, key);
549
550end:
551
552 ERR_clear_error();
553 if (in)
554 BIO_free(in);
555 if (key)
556 EVP_PKEY_free(key);
557
558 return ret;
559}
560
561/*
562 * Try to load a PEM file from a <path> or a buffer <buf>
563 * The PEM must contain at least a Certificate,
564 * It could contain a DH, a certificate chain and a PrivateKey.
565 *
566 * If it failed you should not attempt to use the ckch but free it.
567 *
568 * Return 0 on success or != 0 on failure
569 */
570int ssl_sock_load_pem_into_ckch(const char *path, char *buf, struct cert_key_and_chain *ckch , char **err)
571{
572 BIO *in = NULL;
573 int ret = 1;
574 X509 *ca;
575 X509 *cert = NULL;
576 EVP_PKEY *key = NULL;
Remi Tricot-Le Bretonc76c3c42022-02-11 12:04:55 +0100577 HASSL_DH *dh = NULL;
William Lallemand03c331c2020-05-13 10:10:01 +0200578 STACK_OF(X509) *chain = NULL;
579
580 if (buf) {
581 /* reading from a buffer */
582 in = BIO_new_mem_buf(buf, -1);
583 if (in == NULL) {
584 memprintf(err, "%sCan't allocate memory\n", err && *err ? *err : "");
585 goto end;
586 }
587
588 } else {
589 /* reading from a file */
590 in = BIO_new(BIO_s_file());
591 if (in == NULL) {
592 memprintf(err, "%sCan't allocate memory\n", err && *err ? *err : "");
593 goto end;
594 }
595
596 if (BIO_read_filename(in, path) <= 0) {
597 memprintf(err, "%scannot open the file '%s'.\n",
598 err && *err ? *err : "", path);
599 goto end;
600 }
601 }
602
603 /* Read Private Key */
604 key = PEM_read_bio_PrivateKey(in, NULL, NULL, NULL);
605 /* no need to check for errors here, because the private key could be loaded later */
606
607#ifndef OPENSSL_NO_DH
608 /* Seek back to beginning of file */
609 if (BIO_reset(in) == -1) {
610 memprintf(err, "%san error occurred while reading the file '%s'.\n",
611 err && *err ? *err : "", path);
612 goto end;
613 }
614
Remi Tricot-Le Bretonc76c3c42022-02-11 12:04:55 +0100615 dh = ssl_sock_get_dh_from_bio(in);
616 ERR_clear_error();
William Lallemand03c331c2020-05-13 10:10:01 +0200617 /* no need to return an error there, dh is not mandatory */
618#endif
619
620 /* Seek back to beginning of file */
621 if (BIO_reset(in) == -1) {
622 memprintf(err, "%san error occurred while reading the file '%s'.\n",
623 err && *err ? *err : "", path);
624 goto end;
625 }
626
627 /* Read Certificate */
628 cert = PEM_read_bio_X509_AUX(in, NULL, NULL, NULL);
629 if (cert == NULL) {
630 memprintf(err, "%sunable to load certificate from file '%s'.\n",
631 err && *err ? *err : "", path);
632 goto end;
633 }
634
635 /* Look for a Certificate Chain */
636 while ((ca = PEM_read_bio_X509(in, NULL, NULL, NULL))) {
637 if (chain == NULL)
638 chain = sk_X509_new_null();
639 if (!sk_X509_push(chain, ca)) {
640 X509_free(ca);
641 goto end;
642 }
643 }
644
645 ret = ERR_get_error();
646 if (ret && (ERR_GET_LIB(ret) != ERR_LIB_PEM && ERR_GET_REASON(ret) != PEM_R_NO_START_LINE)) {
647 memprintf(err, "%sunable to load certificate chain from file '%s'.\n",
648 err && *err ? *err : "", path);
649 goto end;
650 }
651
652 /* once it loaded the PEM, it should remove everything else in the ckch */
653 if (ckch->ocsp_response) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100654 ha_free(&ckch->ocsp_response->area);
655 ha_free(&ckch->ocsp_response);
William Lallemand03c331c2020-05-13 10:10:01 +0200656 }
657
658 if (ckch->sctl) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100659 ha_free(&ckch->sctl->area);
660 ha_free(&ckch->sctl);
William Lallemand03c331c2020-05-13 10:10:01 +0200661 }
662
663 if (ckch->ocsp_issuer) {
664 X509_free(ckch->ocsp_issuer);
665 ckch->ocsp_issuer = NULL;
666 }
667
668 /* no error, fill ckch with new context, old context will be free at end: */
669 SWAP(ckch->key, key);
670 SWAP(ckch->dh, dh);
671 SWAP(ckch->cert, cert);
672 SWAP(ckch->chain, chain);
673
674 ret = 0;
675
676end:
677
678 ERR_clear_error();
679 if (in)
680 BIO_free(in);
681 if (key)
682 EVP_PKEY_free(key);
683 if (dh)
Remi Tricot-Le Bretonc76c3c42022-02-11 12:04:55 +0100684 HASSL_DH_free(dh);
William Lallemand03c331c2020-05-13 10:10:01 +0200685 if (cert)
686 X509_free(cert);
687 if (chain)
688 sk_X509_pop_free(chain, X509_free);
689
690 return ret;
691}
692
693/* Frees the contents of a cert_key_and_chain
694 */
695void ssl_sock_free_cert_key_and_chain_contents(struct cert_key_and_chain *ckch)
696{
697 if (!ckch)
698 return;
699
700 /* Free the certificate and set pointer to NULL */
701 if (ckch->cert)
702 X509_free(ckch->cert);
703 ckch->cert = NULL;
704
705 /* Free the key and set pointer to NULL */
706 if (ckch->key)
707 EVP_PKEY_free(ckch->key);
708 ckch->key = NULL;
709
710 /* Free each certificate in the chain */
711 if (ckch->chain)
712 sk_X509_pop_free(ckch->chain, X509_free);
713 ckch->chain = NULL;
714
715 if (ckch->dh)
Remi Tricot-Le Bretonc76c3c42022-02-11 12:04:55 +0100716 HASSL_DH_free(ckch->dh);
William Lallemand03c331c2020-05-13 10:10:01 +0200717 ckch->dh = NULL;
718
719 if (ckch->sctl) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100720 ha_free(&ckch->sctl->area);
721 ha_free(&ckch->sctl);
William Lallemand03c331c2020-05-13 10:10:01 +0200722 }
723
724 if (ckch->ocsp_response) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100725 ha_free(&ckch->ocsp_response->area);
726 ha_free(&ckch->ocsp_response);
William Lallemand03c331c2020-05-13 10:10:01 +0200727 }
728
729 if (ckch->ocsp_issuer)
730 X509_free(ckch->ocsp_issuer);
731 ckch->ocsp_issuer = NULL;
732}
733
734/*
735 *
736 * This function copy a cert_key_and_chain in memory
737 *
738 * It's used to try to apply changes on a ckch before committing them, because
739 * most of the time it's not possible to revert those changes
740 *
741 * Return a the dst or NULL
742 */
743struct cert_key_and_chain *ssl_sock_copy_cert_key_and_chain(struct cert_key_and_chain *src,
744 struct cert_key_and_chain *dst)
745{
William Lallemand6c096142021-02-23 14:45:45 +0100746 if (!src || !dst)
747 return NULL;
748
William Lallemand03c331c2020-05-13 10:10:01 +0200749 if (src->cert) {
750 dst->cert = src->cert;
751 X509_up_ref(src->cert);
752 }
753
754 if (src->key) {
755 dst->key = src->key;
756 EVP_PKEY_up_ref(src->key);
757 }
758
759 if (src->chain) {
760 dst->chain = X509_chain_up_ref(src->chain);
761 }
762
763 if (src->dh) {
Remi Tricot-Le Bretonc76c3c42022-02-11 12:04:55 +0100764 HASSL_DH_up_ref(src->dh);
William Lallemand03c331c2020-05-13 10:10:01 +0200765 dst->dh = src->dh;
766 }
767
768 if (src->sctl) {
769 struct buffer *sctl;
770
771 sctl = calloc(1, sizeof(*sctl));
772 if (!chunk_dup(sctl, src->sctl)) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100773 ha_free(&sctl);
William Lallemand03c331c2020-05-13 10:10:01 +0200774 goto error;
775 }
776 dst->sctl = sctl;
777 }
778
779 if (src->ocsp_response) {
780 struct buffer *ocsp_response;
781
782 ocsp_response = calloc(1, sizeof(*ocsp_response));
783 if (!chunk_dup(ocsp_response, src->ocsp_response)) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100784 ha_free(&ocsp_response);
William Lallemand03c331c2020-05-13 10:10:01 +0200785 goto error;
786 }
787 dst->ocsp_response = ocsp_response;
788 }
789
790 if (src->ocsp_issuer) {
791 X509_up_ref(src->ocsp_issuer);
792 dst->ocsp_issuer = src->ocsp_issuer;
793 }
794
795 return dst;
796
797error:
798
799 /* free everything */
800 ssl_sock_free_cert_key_and_chain_contents(dst);
801
802 return NULL;
803}
804
805/*
806 * return 0 on success or != 0 on failure
807 */
808int ssl_sock_load_issuer_file_into_ckch(const char *path, char *buf, struct cert_key_and_chain *ckch, char **err)
809{
810 int ret = 1;
811 BIO *in = NULL;
812 X509 *issuer;
813
814 if (buf) {
815 /* reading from a buffer */
816 in = BIO_new_mem_buf(buf, -1);
817 if (in == NULL) {
818 memprintf(err, "%sCan't allocate memory\n", err && *err ? *err : "");
819 goto end;
820 }
821
822 } else {
823 /* reading from a file */
824 in = BIO_new(BIO_s_file());
825 if (in == NULL)
826 goto end;
827
828 if (BIO_read_filename(in, path) <= 0)
829 goto end;
830 }
831
832 issuer = PEM_read_bio_X509_AUX(in, NULL, NULL, NULL);
833 if (!issuer) {
834 memprintf(err, "%s'%s' cannot be read or parsed'.\n",
835 err && *err ? *err : "", path);
836 goto end;
837 }
838 /* no error, fill ckch with new context, old context must be free */
839 if (ckch->ocsp_issuer)
840 X509_free(ckch->ocsp_issuer);
841 ckch->ocsp_issuer = issuer;
842 ret = 0;
843
844end:
845
846 ERR_clear_error();
847 if (in)
848 BIO_free(in);
849
850 return ret;
851}
852
853/******************** ckch_store functions ***********************************
854 * The ckch_store is a structure used to cache and index the SSL files used in
855 * configuration
856 */
857
858/*
859 * Free a ckch_store, its ckch, its instances and remove it from the ebtree
860 */
861void ckch_store_free(struct ckch_store *store)
862{
863 struct ckch_inst *inst, *inst_s;
864
865 if (!store)
866 return;
867
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200868 ssl_sock_free_cert_key_and_chain_contents(store->ckch);
William Lallemand03c331c2020-05-13 10:10:01 +0200869
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100870 ha_free(&store->ckch);
William Lallemand03c331c2020-05-13 10:10:01 +0200871
872 list_for_each_entry_safe(inst, inst_s, &store->ckch_inst, by_ckchs) {
873 ckch_inst_free(inst);
874 }
875 ebmb_delete(&store->node);
876 free(store);
877}
878
879/*
880 * create and initialize a ckch_store
881 * <path> is the key name
882 * <nmemb> is the number of store->ckch objects to allocate
883 *
884 * Return a ckch_store or NULL upon failure.
885 */
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200886struct ckch_store *ckch_store_new(const char *filename)
William Lallemand03c331c2020-05-13 10:10:01 +0200887{
888 struct ckch_store *store;
889 int pathlen;
890
891 pathlen = strlen(filename);
892 store = calloc(1, sizeof(*store) + pathlen + 1);
893 if (!store)
894 return NULL;
895
William Lallemand03c331c2020-05-13 10:10:01 +0200896 memcpy(store->path, filename, pathlen + 1);
897
898 LIST_INIT(&store->ckch_inst);
899 LIST_INIT(&store->crtlist_entry);
900
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200901 store->ckch = calloc(1, sizeof(*store->ckch));
William Lallemand03c331c2020-05-13 10:10:01 +0200902 if (!store->ckch)
903 goto error;
904
905 return store;
906error:
907 ckch_store_free(store);
908 return NULL;
909}
910
911/* allocate and duplicate a ckch_store
912 * Return a new ckch_store or NULL */
913struct ckch_store *ckchs_dup(const struct ckch_store *src)
914{
915 struct ckch_store *dst;
916
William Lallemand6c096142021-02-23 14:45:45 +0100917 if (!src)
918 return NULL;
919
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200920 dst = ckch_store_new(src->path);
Eric Salama6ac61e32021-02-23 16:50:57 +0100921 if (!dst)
922 return NULL;
William Lallemand03c331c2020-05-13 10:10:01 +0200923
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200924 if (!ssl_sock_copy_cert_key_and_chain(src->ckch, dst->ckch))
925 goto error;
William Lallemand03c331c2020-05-13 10:10:01 +0200926
927 return dst;
928
929error:
930 ckch_store_free(dst);
931
932 return NULL;
933}
934
935/*
936 * lookup a path into the ckchs tree.
937 */
938struct ckch_store *ckchs_lookup(char *path)
939{
940 struct ebmb_node *eb;
941
942 eb = ebst_lookup(&ckchs_tree, path);
943 if (!eb)
944 return NULL;
945
946 return ebmb_entry(eb, struct ckch_store, node);
947}
948
949/*
950 * This function allocate a ckch_store and populate it with certificates from files.
951 */
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200952struct ckch_store *ckchs_load_cert_file(char *path, char **err)
William Lallemand03c331c2020-05-13 10:10:01 +0200953{
954 struct ckch_store *ckchs;
955
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200956 ckchs = ckch_store_new(path);
William Lallemand03c331c2020-05-13 10:10:01 +0200957 if (!ckchs) {
958 memprintf(err, "%sunable to allocate memory.\n", err && *err ? *err : "");
959 goto end;
960 }
William Lallemand03c331c2020-05-13 10:10:01 +0200961
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200962 if (ssl_sock_load_files_into_ckch(path, ckchs->ckch, err) == 1)
963 goto end;
William Lallemand03c331c2020-05-13 10:10:01 +0200964
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200965 /* insert into the ckchs tree */
966 memcpy(ckchs->path, path, strlen(path) + 1);
967 ebst_insert(&ckchs_tree, &ckchs->node);
William Lallemand03c331c2020-05-13 10:10:01 +0200968 return ckchs;
969
970end:
971 ckch_store_free(ckchs);
972
973 return NULL;
974}
975
William Lallemandfa1d8b42020-05-13 15:46:10 +0200976
977/******************** ckch_inst functions ******************************/
978
979/* unlink a ckch_inst, free all SNIs, free the ckch_inst */
980/* The caller must use the lock of the bind_conf if used with inserted SNIs */
981void ckch_inst_free(struct ckch_inst *inst)
982{
983 struct sni_ctx *sni, *sni_s;
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +0100984 struct ckch_inst_link_ref *link_ref, *link_ref_s;
William Lallemandfa1d8b42020-05-13 15:46:10 +0200985
986 if (inst == NULL)
987 return;
988
989 list_for_each_entry_safe(sni, sni_s, &inst->sni_ctx, by_ckch_inst) {
990 SSL_CTX_free(sni->ctx);
Willy Tarreau2b718102021-04-21 07:32:39 +0200991 LIST_DELETE(&sni->by_ckch_inst);
William Lallemandfa1d8b42020-05-13 15:46:10 +0200992 ebmb_delete(&sni->name);
993 free(sni);
994 }
Remi Tricot-Le Bretonf3eedfe2021-01-25 17:19:44 +0100995 SSL_CTX_free(inst->ctx);
996 inst->ctx = NULL;
Willy Tarreau2b718102021-04-21 07:32:39 +0200997 LIST_DELETE(&inst->by_ckchs);
998 LIST_DELETE(&inst->by_crtlist_entry);
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +0100999
1000 list_for_each_entry_safe(link_ref, link_ref_s, &inst->cafile_link_refs, list) {
1001 LIST_DELETE(&link_ref->link->list);
1002 LIST_DELETE(&link_ref->list);
1003 free(link_ref);
1004 }
1005
William Lallemandfa1d8b42020-05-13 15:46:10 +02001006 free(inst);
1007}
1008
1009/* Alloc and init a ckch_inst */
1010struct ckch_inst *ckch_inst_new()
1011{
1012 struct ckch_inst *ckch_inst;
1013
1014 ckch_inst = calloc(1, sizeof *ckch_inst);
1015 if (!ckch_inst)
1016 return NULL;
1017
1018 LIST_INIT(&ckch_inst->sni_ctx);
1019 LIST_INIT(&ckch_inst->by_ckchs);
1020 LIST_INIT(&ckch_inst->by_crtlist_entry);
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001021 LIST_INIT(&ckch_inst->cafile_link_refs);
William Lallemandfa1d8b42020-05-13 15:46:10 +02001022
1023 return ckch_inst;
1024}
1025
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001026
1027/******************** ssl_store functions ******************************/
Remi Tricot-Le Breton9f0c9362021-02-19 15:06:28 +01001028struct eb_root cafile_tree = EB_ROOT;
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001029
Remi Tricot-Le Breton9f0c9362021-02-19 15:06:28 +01001030/*
1031 * Returns the cafile_entry found in the cafile_tree indexed by the path 'path'.
1032 * If 'oldest_entry' is 1, returns the "original" cafile_entry (since
1033 * during a set cafile/commit cafile cycle there might be two entries for any
1034 * given path, the original one and the new one set via the CLI but not
1035 * committed yet).
1036 */
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001037struct cafile_entry *ssl_store_get_cafile_entry(char *path, int oldest_entry)
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001038{
Remi Tricot-Le Breton9f0c9362021-02-19 15:06:28 +01001039 struct cafile_entry *ca_e = NULL;
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001040 struct ebmb_node *eb;
1041
1042 eb = ebst_lookup(&cafile_tree, path);
Remi Tricot-Le Breton9f0c9362021-02-19 15:06:28 +01001043 while (eb) {
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001044 ca_e = ebmb_entry(eb, struct cafile_entry, node);
Remi Tricot-Le Breton9f0c9362021-02-19 15:06:28 +01001045 /* The ebst_lookup in a tree that has duplicates returns the
1046 * oldest entry first. If we want the latest entry, we need to
1047 * iterate over all the duplicates until we find the last one
1048 * (in our case there should never be more than two entries for
1049 * any given path). */
1050 if (oldest_entry)
1051 return ca_e;
1052 eb = ebmb_next_dup(eb);
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001053 }
Remi Tricot-Le Breton9f0c9362021-02-19 15:06:28 +01001054 return ca_e;
1055}
1056
Remi Tricot-Le Breton38c999b2021-02-23 16:28:43 +01001057int ssl_store_add_uncommitted_cafile_entry(struct cafile_entry *entry)
1058{
1059 return (ebst_insert(&cafile_tree, &entry->node) != &entry->node);
1060}
1061
Remi Tricot-Le Breton9f0c9362021-02-19 15:06:28 +01001062X509_STORE* ssl_store_get0_locations_file(char *path)
1063{
1064 struct cafile_entry *ca_e = ssl_store_get_cafile_entry(path, 0);
1065
1066 if (ca_e)
1067 return ca_e->ca_store;
1068
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001069 return NULL;
1070}
1071
Remi Tricot-Le Breton5daff3c2021-02-22 15:54:55 +01001072/* Create a cafile_entry object, without adding it to the cafile_tree. */
Remi Tricot-Le Breton0bb48242021-04-16 17:59:23 +02001073struct 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 +01001074{
1075 struct cafile_entry *ca_e;
1076 int pathlen;
1077
1078 pathlen = strlen(path);
1079
1080 ca_e = calloc(1, sizeof(*ca_e) + pathlen + 1);
1081 if (ca_e) {
1082 memcpy(ca_e->path, path, pathlen + 1);
1083 ca_e->ca_store = store;
Remi Tricot-Le Breton0bb48242021-04-16 17:59:23 +02001084 ca_e->type = type;
Remi Tricot-Le Breton5daff3c2021-02-22 15:54:55 +01001085 LIST_INIT(&ca_e->ckch_inst_link);
1086 }
1087 return ca_e;
1088}
1089
1090/* Delete a cafile_entry. The caller is responsible from removing this entry
1091 * from the cafile_tree first if is was previously added into it. */
1092void ssl_store_delete_cafile_entry(struct cafile_entry *ca_e)
1093{
1094 struct ckch_inst_link *link, *link_s;
1095 if (!ca_e)
1096 return;
1097
1098 X509_STORE_free(ca_e->ca_store);
1099
1100 list_for_each_entry_safe(link, link_s, &ca_e->ckch_inst_link, list) {
1101 struct ckch_inst *inst = link->ckch_inst;
1102 struct ckch_inst_link_ref *link_ref, *link_ref_s;
1103 list_for_each_entry_safe(link_ref, link_ref_s, &inst->cafile_link_refs, list) {
1104 if (link_ref->link == link) {
1105 LIST_DELETE(&link_ref->list);
1106 free(link_ref);
1107 break;
1108 }
1109 }
1110 LIST_DELETE(&link->list);
1111 free(link);
1112 }
1113
1114 free(ca_e);
1115}
1116
Remi Tricot-Le Breton383fb142021-02-22 18:26:14 +01001117/*
1118 * Build a cafile_entry out of a buffer instead of out of a file.
1119 * This function is used when the "commit ssl ca-file" cli command is used.
1120 * It can parse CERTIFICATE sections as well as CRL ones.
1121 * Returns 0 in case of success, 1 otherwise.
1122 */
1123int ssl_store_load_ca_from_buf(struct cafile_entry *ca_e, char *cert_buf)
1124{
1125 int retval = 0;
1126
1127 if (!ca_e)
1128 return 1;
1129
1130 if (!ca_e->ca_store) {
1131 ca_e->ca_store = X509_STORE_new();
1132 if (ca_e->ca_store) {
1133 BIO *bio = BIO_new_mem_buf(cert_buf, strlen(cert_buf));
1134 if (bio) {
1135 X509_INFO *info;
1136 int i;
1137 STACK_OF(X509_INFO) *infos = PEM_X509_INFO_read_bio(bio, NULL, NULL, NULL);
1138 if (!infos)
1139 {
1140 BIO_free(bio);
1141 return 1;
1142 }
1143
1144 for (i = 0; i < sk_X509_INFO_num(infos) && !retval; i++) {
1145 info = sk_X509_INFO_value(infos, i);
1146 /* X509_STORE_add_cert and X509_STORE_add_crl return 1 on success */
1147 if (info->x509) {
1148 retval = !X509_STORE_add_cert(ca_e->ca_store, info->x509);
1149 }
1150 if (!retval && info->crl) {
1151 retval = !X509_STORE_add_crl(ca_e->ca_store, info->crl);
1152 }
1153 }
1154 retval = retval || (i != sk_X509_INFO_num(infos));
1155
1156 /* Cleanup */
1157 sk_X509_INFO_pop_free(infos, X509_INFO_free);
1158 BIO_free(bio);
1159 }
1160 }
1161 }
1162
1163 return retval;
1164}
1165
Remi Tricot-Le Breton0bb48242021-04-16 17:59:23 +02001166int ssl_store_load_locations_file(char *path, int create_if_none, enum cafile_type type)
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001167{
1168 X509_STORE *store = ssl_store_get0_locations_file(path);
1169
1170 /* If this function is called by the CLI, we should not call the
1171 * X509_STORE_load_locations function because it performs forbidden disk
1172 * accesses. */
1173 if (!store && create_if_none) {
William Lallemand87fd9942022-04-01 20:12:03 +02001174 STACK_OF(X509_OBJECT) *objs;
1175 int cert_count = 0;
1176 struct stat buf;
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001177 struct cafile_entry *ca_e;
William Lallemandc6b17632022-04-01 23:39:37 +02001178 const char *file = NULL;
1179 const char *dir = NULL;
William Lallemand87fd9942022-04-01 20:12:03 +02001180
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001181 store = X509_STORE_new();
William Lallemand87fd9942022-04-01 20:12:03 +02001182
William Lallemandc6b17632022-04-01 23:39:37 +02001183 if (strcmp(path, "@system-ca") == 0) {
1184 dir = X509_get_default_cert_dir();
William Lallemand87fd9942022-04-01 20:12:03 +02001185
William Lallemandc6b17632022-04-01 23:39:37 +02001186 } else {
1187
1188 if (stat(path, &buf))
1189 goto err;
1190
1191 if (S_ISDIR(buf.st_mode))
1192 dir = path;
1193 else
1194 file = path;
1195 }
William Lallemand87fd9942022-04-01 20:12:03 +02001196
1197 if (file) {
1198 if (!X509_STORE_load_locations(store, file, NULL)) {
1199 goto err;
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001200 }
William Lallemand80296b42022-04-05 10:19:30 +02001201 } else if (dir) {
William Lallemand87fd9942022-04-01 20:12:03 +02001202 int n, i;
1203 struct dirent **de_list;
1204
1205 n = scandir(dir, &de_list, 0, alphasort);
1206 if (n < 0)
1207 goto err;
1208
1209 for (i= 0; i < n; i++) {
1210 char *end;
1211 struct dirent *de = de_list[i];
1212 BIO *in = NULL;
1213 X509 *ca = NULL;;
1214
1215 /* we try to load the files that would have
1216 * been loaded in an hashed directory loaded by
1217 * X509_LOOKUP_hash_dir, so according to "man 1
1218 * c_rehash", we should load ".pem", ".crt",
William Lallemande4b93eb2022-05-09 09:29:00 +02001219 * ".cer", or ".crl". Files starting with a dot
1220 * are ignored.
William Lallemand87fd9942022-04-01 20:12:03 +02001221 */
1222 end = strrchr(de->d_name, '.');
William Lallemande4b93eb2022-05-09 09:29:00 +02001223 if (!end || de->d_name[0] == '.' ||
1224 (strcmp(end, ".pem") != 0 &&
1225 strcmp(end, ".crt") != 0 &&
1226 strcmp(end, ".cer") != 0 &&
1227 strcmp(end, ".crl") != 0)) {
William Lallemand87fd9942022-04-01 20:12:03 +02001228 free(de);
1229 continue;
1230 }
1231 in = BIO_new(BIO_s_file());
1232 if (in == NULL)
1233 goto scandir_err;
1234
William Lallemandc6b17632022-04-01 23:39:37 +02001235 chunk_printf(&trash, "%s/%s", dir, de->d_name);
William Lallemand87fd9942022-04-01 20:12:03 +02001236
1237 if (BIO_read_filename(in, trash.area) == 0)
1238 goto scandir_err;
1239
1240 if (PEM_read_bio_X509_AUX(in, &ca, NULL, NULL) == NULL)
1241 goto scandir_err;
1242
1243 if (X509_STORE_add_cert(store, ca) == 0)
1244 goto scandir_err;
1245
William Lallemand4cfbf3c2022-04-26 15:57:33 +02001246 X509_free(ca);
William Lallemand87fd9942022-04-01 20:12:03 +02001247 BIO_free(in);
1248 free(de);
1249 continue;
1250
1251scandir_err:
William Lallemand4cfbf3c2022-04-26 15:57:33 +02001252 X509_free(ca);
William Lallemand87fd9942022-04-01 20:12:03 +02001253 BIO_free(in);
1254 free(de);
1255 ha_warning("ca-file: '%s' couldn't load '%s'\n", path, trash.area);
William Lallemand87fd9942022-04-01 20:12:03 +02001256
1257 }
1258 free(de_list);
William Lallemand80296b42022-04-05 10:19:30 +02001259 } else {
1260 ha_alert("ca-file: couldn't load '%s'\n", path);
1261 goto err;
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001262 }
William Lallemand87fd9942022-04-01 20:12:03 +02001263
1264 objs = X509_STORE_get0_objects(store);
1265 cert_count = sk_X509_OBJECT_num(objs);
1266 if (cert_count == 0)
1267 ha_warning("ca-file: 0 CA were loaded from '%s'\n", path);
1268
1269 ca_e = ssl_store_create_cafile_entry(path, store, type);
1270 if (!ca_e)
1271 goto err;
1272 ebst_insert(&cafile_tree, &ca_e->node);
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001273 }
1274 return (store != NULL);
William Lallemand87fd9942022-04-01 20:12:03 +02001275
1276err:
1277 X509_STORE_free(store);
1278 store = NULL;
1279 return 0;
1280
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001281}
1282
1283
William Lallemandda8584c2020-05-14 10:14:37 +02001284/*************************** CLI commands ***********************/
1285
1286/* Type of SSL payloads that can be updated over the CLI */
1287
William Lallemandff8bf982022-03-29 10:44:23 +02001288struct cert_exts cert_exts[] = {
1289 { "", CERT_TYPE_PEM, &ssl_sock_load_pem_into_ckch }, /* default mode, no extensions */
William Lallemand26654e72022-03-30 12:01:32 +02001290 { "crt", CERT_TYPE_CRT, &ssl_sock_load_pem_into_ckch },
William Lallemandff8bf982022-03-29 10:44:23 +02001291 { "key", CERT_TYPE_KEY, &ssl_sock_load_key_into_ckch },
William Lallemandda8584c2020-05-14 10:14:37 +02001292#if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) || defined OPENSSL_IS_BORINGSSL)
William Lallemandff8bf982022-03-29 10:44:23 +02001293 { "ocsp", CERT_TYPE_OCSP, &ssl_sock_load_ocsp_response_from_file },
William Lallemandda8584c2020-05-14 10:14:37 +02001294#endif
Ilya Shipitsinc47d6762021-02-13 11:45:33 +05001295#ifdef HAVE_SSL_SCTL
William Lallemandff8bf982022-03-29 10:44:23 +02001296 { "sctl", CERT_TYPE_SCTL, &ssl_sock_load_sctl_from_file },
William Lallemandda8584c2020-05-14 10:14:37 +02001297#endif
William Lallemandff8bf982022-03-29 10:44:23 +02001298 { "issuer", CERT_TYPE_ISSUER, &ssl_sock_load_issuer_file_into_ckch },
1299 { NULL, CERT_TYPE_MAX, NULL },
William Lallemandda8584c2020-05-14 10:14:37 +02001300};
1301
1302
1303/* release function of the `show ssl cert' command */
1304static void cli_release_show_cert(struct appctx *appctx)
1305{
1306 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1307}
1308
Willy Tarreau4fd9b4d2022-05-04 16:11:50 +02001309/* IO handler of "show ssl cert <filename>".
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001310 * It makes use of a show_cert_ctx context, and ckchs_transaction in read-only.
Willy Tarreau4fd9b4d2022-05-04 16:11:50 +02001311 */
William Lallemandda8584c2020-05-14 10:14:37 +02001312static int cli_io_handler_show_cert(struct appctx *appctx)
1313{
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001314 struct show_cert_ctx *ctx = appctx->svcctx;
William Lallemandda8584c2020-05-14 10:14:37 +02001315 struct buffer *trash = alloc_trash_chunk();
1316 struct ebmb_node *node;
Christopher Faulet908628c2022-03-25 16:43:49 +01001317 struct conn_stream *cs = appctx->owner;
William Lallemandda8584c2020-05-14 10:14:37 +02001318 struct ckch_store *ckchs;
1319
1320 if (trash == NULL)
1321 return 1;
1322
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001323 if (!ctx->old_ckchs) {
William Lallemandda8584c2020-05-14 10:14:37 +02001324 if (ckchs_transaction.old_ckchs) {
1325 ckchs = ckchs_transaction.old_ckchs;
1326 chunk_appendf(trash, "# transaction\n");
William Lallemand5685ccf2020-09-16 16:12:25 +02001327 chunk_appendf(trash, "*%s\n", ckchs->path);
William Lallemandda8584c2020-05-14 10:14:37 +02001328 }
1329 }
1330
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001331 if (!ctx->cur_ckchs) {
William Lallemandda8584c2020-05-14 10:14:37 +02001332 chunk_appendf(trash, "# filename\n");
1333 node = ebmb_first(&ckchs_tree);
1334 } else {
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001335 node = &ctx->cur_ckchs->node;
William Lallemandda8584c2020-05-14 10:14:37 +02001336 }
1337 while (node) {
1338 ckchs = ebmb_entry(node, struct ckch_store, node);
William Lallemand5685ccf2020-09-16 16:12:25 +02001339 chunk_appendf(trash, "%s\n", ckchs->path);
William Lallemandda8584c2020-05-14 10:14:37 +02001340
1341 node = ebmb_next(node);
Christopher Faulet908628c2022-03-25 16:43:49 +01001342 if (ci_putchk(cs_ic(cs), trash) == -1) {
Christopher Fauleta0bdec32022-04-04 07:51:21 +02001343 cs_rx_room_blk(cs);
William Lallemandda8584c2020-05-14 10:14:37 +02001344 goto yield;
1345 }
1346 }
1347
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001348 ctx->cur_ckchs = NULL;
William Lallemandda8584c2020-05-14 10:14:37 +02001349 free_trash_chunk(trash);
1350 return 1;
1351yield:
1352
1353 free_trash_chunk(trash);
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001354 ctx->cur_ckchs = ckchs;
William Lallemandda8584c2020-05-14 10:14:37 +02001355 return 0; /* should come back */
1356}
1357
1358/*
1359 * Extract and format the DNS SAN extensions and copy result into a chuink
1360 * Return 0;
1361 */
1362#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
1363static int ssl_sock_get_san_oneline(X509 *cert, struct buffer *out)
1364{
1365 int i;
1366 char *str;
1367 STACK_OF(GENERAL_NAME) *names = NULL;
1368
1369 names = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
1370 if (names) {
1371 for (i = 0; i < sk_GENERAL_NAME_num(names); i++) {
1372 GENERAL_NAME *name = sk_GENERAL_NAME_value(names, i);
1373 if (i > 0)
1374 chunk_appendf(out, ", ");
1375 if (name->type == GEN_DNS) {
1376 if (ASN1_STRING_to_UTF8((unsigned char **)&str, name->d.dNSName) >= 0) {
1377 chunk_appendf(out, "DNS:%s", str);
1378 OPENSSL_free(str);
1379 }
1380 }
1381 }
1382 sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free);
1383 }
1384 return 0;
1385}
1386#endif
1387
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001388/*
1389 * Build the ckch_inst_link that will be chained in the CA file entry and the
1390 * corresponding ckch_inst_link_ref that will be chained in the ckch instance.
1391 * Return 0 in case of success.
1392 */
1393static int do_chain_inst_and_cafile(struct cafile_entry *cafile_entry, struct ckch_inst *ckch_inst)
1394{
1395 struct ckch_inst_link *new_link;
1396 if (!LIST_ISEMPTY(&cafile_entry->ckch_inst_link)) {
1397 struct ckch_inst_link *link = LIST_ELEM(cafile_entry->ckch_inst_link.n,
1398 typeof(link), list);
1399 /* Do not add multiple references to the same
1400 * instance in a cafile_entry */
1401 if (link->ckch_inst == ckch_inst) {
1402 return 1;
1403 }
1404 }
1405
1406 new_link = calloc(1, sizeof(*new_link));
1407 if (new_link) {
1408 struct ckch_inst_link_ref *new_link_ref = calloc(1, sizeof(*new_link_ref));
1409 if (!new_link_ref) {
1410 free(new_link);
1411 return 1;
1412 }
1413
1414 new_link->ckch_inst = ckch_inst;
1415 new_link_ref->link = new_link;
1416 LIST_INIT(&new_link->list);
1417 LIST_INIT(&new_link_ref->list);
1418
1419 LIST_APPEND(&cafile_entry->ckch_inst_link, &new_link->list);
1420 LIST_APPEND(&ckch_inst->cafile_link_refs, &new_link_ref->list);
1421 }
1422
1423 return 0;
1424}
1425
1426
1427/*
1428 * Link a CA file tree entry to the ckch instance that uses it.
1429 * To determine if and which CA file tree entries need to be linked to the
1430 * instance, we follow the same logic performed in ssl_sock_prepare_ctx when
1431 * processing the verify option.
1432 * This function works for a frontend as well as for a backend, depending on the
1433 * configuration parameters given (bind_conf or server).
1434 */
1435void ckch_inst_add_cafile_link(struct ckch_inst *ckch_inst, struct bind_conf *bind_conf,
1436 struct ssl_bind_conf *ssl_conf, const struct server *srv)
1437{
1438 int verify = SSL_VERIFY_NONE;
1439
1440 if (srv) {
1441
1442 if (global.ssl_server_verify == SSL_SERVER_VERIFY_REQUIRED)
1443 verify = SSL_VERIFY_PEER;
1444 switch (srv->ssl_ctx.verify) {
1445 case SSL_SOCK_VERIFY_NONE:
1446 verify = SSL_VERIFY_NONE;
1447 break;
1448 case SSL_SOCK_VERIFY_REQUIRED:
1449 verify = SSL_VERIFY_PEER;
1450 break;
1451 }
1452 }
1453 else {
1454 switch ((ssl_conf && ssl_conf->verify) ? ssl_conf->verify : bind_conf->ssl_conf.verify) {
1455 case SSL_SOCK_VERIFY_NONE:
1456 verify = SSL_VERIFY_NONE;
1457 break;
1458 case SSL_SOCK_VERIFY_OPTIONAL:
1459 verify = SSL_VERIFY_PEER;
1460 break;
1461 case SSL_SOCK_VERIFY_REQUIRED:
1462 verify = SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
1463 break;
1464 }
1465 }
1466
1467 if (verify & SSL_VERIFY_PEER) {
1468 struct cafile_entry *ca_file_entry = NULL;
1469 struct cafile_entry *ca_verify_file_entry = NULL;
Remi Tricot-Le Bretonf81c70c2021-04-20 16:54:21 +02001470 struct cafile_entry *crl_file_entry = NULL;
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001471 if (srv) {
1472 if (srv->ssl_ctx.ca_file) {
1473 ca_file_entry = ssl_store_get_cafile_entry(srv->ssl_ctx.ca_file, 0);
1474
1475 }
Remi Tricot-Le Bretonf81c70c2021-04-20 16:54:21 +02001476 if (srv->ssl_ctx.crl_file) {
1477 crl_file_entry = ssl_store_get_cafile_entry(srv->ssl_ctx.crl_file, 0);
1478 }
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001479 }
1480 else {
1481 char *ca_file = (ssl_conf && ssl_conf->ca_file) ? ssl_conf->ca_file : bind_conf->ssl_conf.ca_file;
1482 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 +02001483 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 +01001484
1485 if (ca_file)
1486 ca_file_entry = ssl_store_get_cafile_entry(ca_file, 0);
1487 if (ca_verify_file)
1488 ca_verify_file_entry = ssl_store_get_cafile_entry(ca_verify_file, 0);
Remi Tricot-Le Bretonf81c70c2021-04-20 16:54:21 +02001489 if (crl_file)
1490 crl_file_entry = ssl_store_get_cafile_entry(crl_file, 0);
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001491 }
1492
1493 if (ca_file_entry) {
1494 /* If we have a ckch instance that is not already in the
1495 * cafile_entry's list, add it to it. */
1496 if (do_chain_inst_and_cafile(ca_file_entry, ckch_inst))
1497 return;
1498
1499 }
1500 if (ca_verify_file_entry && (ca_file_entry != ca_verify_file_entry)) {
1501 /* If we have a ckch instance that is not already in the
1502 * cafile_entry's list, add it to it. */
1503 if (do_chain_inst_and_cafile(ca_verify_file_entry, ckch_inst))
1504 return;
1505 }
Remi Tricot-Le Bretonf81c70c2021-04-20 16:54:21 +02001506 if (crl_file_entry) {
1507 /* If we have a ckch instance that is not already in the
1508 * cafile_entry's list, add it to it. */
1509 if (do_chain_inst_and_cafile(crl_file_entry, ckch_inst))
1510 return;
1511 }
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001512 }
1513}
1514
William Lallemandda8584c2020-05-14 10:14:37 +02001515
1516
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001517static int show_cert_detail(X509 *cert, STACK_OF(X509) *chain, struct buffer *out)
William Lallemandda8584c2020-05-14 10:14:37 +02001518{
William Lallemandda8584c2020-05-14 10:14:37 +02001519 BIO *bio = NULL;
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001520 struct buffer *tmp = alloc_trash_chunk();
William Lallemandda8584c2020-05-14 10:14:37 +02001521 int i;
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001522 int write = -1;
1523 unsigned int len = 0;
1524 X509_NAME *name = NULL;
William Lallemandda8584c2020-05-14 10:14:37 +02001525
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001526 if (!tmp)
1527 return -1;
William Lallemandda8584c2020-05-14 10:14:37 +02001528
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001529 if (!cert)
William Lallemand5685ccf2020-09-16 16:12:25 +02001530 goto end;
William Lallemandda8584c2020-05-14 10:14:37 +02001531
William Lallemand5685ccf2020-09-16 16:12:25 +02001532 if (chain == NULL) {
1533 struct issuer_chain *issuer;
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001534 issuer = ssl_get0_issuer_chain(cert);
William Lallemand5685ccf2020-09-16 16:12:25 +02001535 if (issuer) {
1536 chain = issuer->chain;
1537 chunk_appendf(out, "Chain Filename: ");
1538 chunk_appendf(out, "%s\n", issuer->path);
William Lallemandda8584c2020-05-14 10:14:37 +02001539 }
William Lallemand5685ccf2020-09-16 16:12:25 +02001540 }
1541 chunk_appendf(out, "Serial: ");
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001542 if (ssl_sock_get_serial(cert, tmp) == -1)
William Lallemand5685ccf2020-09-16 16:12:25 +02001543 goto end;
1544 dump_binary(out, tmp->area, tmp->data);
1545 chunk_appendf(out, "\n");
William Lallemandda8584c2020-05-14 10:14:37 +02001546
William Lallemand5685ccf2020-09-16 16:12:25 +02001547 chunk_appendf(out, "notBefore: ");
1548 chunk_reset(tmp);
1549 if ((bio = BIO_new(BIO_s_mem())) == NULL)
1550 goto end;
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001551 if (ASN1_TIME_print(bio, X509_getm_notBefore(cert)) == 0)
William Lallemand5685ccf2020-09-16 16:12:25 +02001552 goto end;
1553 write = BIO_read(bio, tmp->area, tmp->size-1);
1554 tmp->area[write] = '\0';
1555 BIO_free(bio);
1556 bio = NULL;
1557 chunk_appendf(out, "%s\n", tmp->area);
William Lallemandda8584c2020-05-14 10:14:37 +02001558
William Lallemand5685ccf2020-09-16 16:12:25 +02001559 chunk_appendf(out, "notAfter: ");
1560 chunk_reset(tmp);
1561 if ((bio = BIO_new(BIO_s_mem())) == NULL)
1562 goto end;
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001563 if (ASN1_TIME_print(bio, X509_getm_notAfter(cert)) == 0)
William Lallemand5685ccf2020-09-16 16:12:25 +02001564 goto end;
1565 if ((write = BIO_read(bio, tmp->area, tmp->size-1)) <= 0)
1566 goto end;
1567 tmp->area[write] = '\0';
1568 BIO_free(bio);
1569 bio = NULL;
1570 chunk_appendf(out, "%s\n", tmp->area);
William Lallemandda8584c2020-05-14 10:14:37 +02001571
1572#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
William Lallemand5685ccf2020-09-16 16:12:25 +02001573 chunk_appendf(out, "Subject Alternative Name: ");
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001574 if (ssl_sock_get_san_oneline(cert, out) == -1)
William Lallemand5685ccf2020-09-16 16:12:25 +02001575 goto end;
1576 *(out->area + out->data) = '\0';
1577 chunk_appendf(out, "\n");
William Lallemandda8584c2020-05-14 10:14:37 +02001578#endif
William Lallemand5685ccf2020-09-16 16:12:25 +02001579 chunk_reset(tmp);
1580 chunk_appendf(out, "Algorithm: ");
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001581 if (cert_get_pkey_algo(cert, tmp) == 0)
William Lallemand5685ccf2020-09-16 16:12:25 +02001582 goto end;
1583 chunk_appendf(out, "%s\n", tmp->area);
William Lallemandda8584c2020-05-14 10:14:37 +02001584
William Lallemand5685ccf2020-09-16 16:12:25 +02001585 chunk_reset(tmp);
1586 chunk_appendf(out, "SHA1 FingerPrint: ");
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001587 if (X509_digest(cert, EVP_sha1(), (unsigned char *) tmp->area, &len) == 0)
William Lallemand5685ccf2020-09-16 16:12:25 +02001588 goto end;
1589 tmp->data = len;
1590 dump_binary(out, tmp->area, tmp->data);
1591 chunk_appendf(out, "\n");
William Lallemandda8584c2020-05-14 10:14:37 +02001592
William Lallemand5685ccf2020-09-16 16:12:25 +02001593 chunk_appendf(out, "Subject: ");
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001594 if ((name = X509_get_subject_name(cert)) == NULL)
William Lallemand5685ccf2020-09-16 16:12:25 +02001595 goto end;
1596 if ((ssl_sock_get_dn_oneline(name, tmp)) == -1)
1597 goto end;
1598 *(tmp->area + tmp->data) = '\0';
1599 chunk_appendf(out, "%s\n", tmp->area);
1600
1601 chunk_appendf(out, "Issuer: ");
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001602 if ((name = X509_get_issuer_name(cert)) == NULL)
William Lallemand5685ccf2020-09-16 16:12:25 +02001603 goto end;
1604 if ((ssl_sock_get_dn_oneline(name, tmp)) == -1)
1605 goto end;
1606 *(tmp->area + tmp->data) = '\0';
1607 chunk_appendf(out, "%s\n", tmp->area);
1608
1609 /* Displays subject of each certificate in the chain */
1610 for (i = 0; i < sk_X509_num(chain); i++) {
1611 X509 *ca = sk_X509_value(chain, i);
1612
1613 chunk_appendf(out, "Chain Subject: ");
1614 if ((name = X509_get_subject_name(ca)) == NULL)
William Lallemandda8584c2020-05-14 10:14:37 +02001615 goto end;
1616 if ((ssl_sock_get_dn_oneline(name, tmp)) == -1)
1617 goto end;
1618 *(tmp->area + tmp->data) = '\0';
1619 chunk_appendf(out, "%s\n", tmp->area);
1620
William Lallemand5685ccf2020-09-16 16:12:25 +02001621 chunk_appendf(out, "Chain Issuer: ");
1622 if ((name = X509_get_issuer_name(ca)) == NULL)
William Lallemandda8584c2020-05-14 10:14:37 +02001623 goto end;
1624 if ((ssl_sock_get_dn_oneline(name, tmp)) == -1)
1625 goto end;
1626 *(tmp->area + tmp->data) = '\0';
1627 chunk_appendf(out, "%s\n", tmp->area);
William Lallemandda8584c2020-05-14 10:14:37 +02001628 }
1629
1630end:
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001631 if (bio)
1632 BIO_free(bio);
1633 free_trash_chunk(tmp);
1634
1635 return 0;
1636}
1637
Remi Tricot-Le Breton3faf0cb2021-06-10 18:10:32 +02001638#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 +02001639/*
1640 * Build the OCSP tree entry's key for a given ckch_store.
1641 * Returns a negative value in case of error.
1642 */
1643static int ckch_store_build_certid(struct ckch_store *ckch_store, unsigned char certid[128], unsigned int *key_length)
1644{
1645 OCSP_RESPONSE *resp;
1646 OCSP_BASICRESP *bs = NULL;
1647 OCSP_SINGLERESP *sr;
1648 OCSP_CERTID *id;
1649 unsigned char *p = NULL;
1650
1651 if (!key_length)
1652 return -1;
1653
1654 *key_length = 0;
1655
1656 if (!ckch_store->ckch->ocsp_response)
1657 return 0;
1658
1659 p = (unsigned char *) ckch_store->ckch->ocsp_response->area;
1660
1661 resp = d2i_OCSP_RESPONSE(NULL, (const unsigned char **)&p,
1662 ckch_store->ckch->ocsp_response->data);
1663 if (!resp) {
1664 goto end;
1665 }
1666
1667 bs = OCSP_response_get1_basic(resp);
1668 if (!bs) {
1669 goto end;
1670 }
1671
1672 sr = OCSP_resp_get0(bs, 0);
1673 if (!sr) {
1674 goto end;
1675 }
1676
1677 id = (OCSP_CERTID*)OCSP_SINGLERESP_get0_id(sr);
1678
1679 p = certid;
1680 *key_length = i2d_OCSP_CERTID(id, &p);
1681
1682end:
1683 return *key_length > 0;
1684}
1685#endif
1686
1687/*
1688 * Dump the OCSP certificate key (if it exists) of certificate <ckch> into
1689 * buffer <out>.
1690 * Returns 0 in case of success.
1691 */
1692static int ckch_store_show_ocsp_certid(struct ckch_store *ckch_store, struct buffer *out)
1693{
Remi Tricot-Le Breton3faf0cb2021-06-10 18:10:32 +02001694#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 +02001695 unsigned char key[OCSP_MAX_CERTID_ASN1_LENGTH] = {};
1696 unsigned int key_length = 0;
1697 int i;
1698
1699 if (ckch_store_build_certid(ckch_store, (unsigned char*)key, &key_length) >= 0) {
1700 /* Dump the CERTID info */
1701 chunk_appendf(out, "OCSP Response Key: ");
1702 for (i = 0; i < key_length; ++i) {
1703 chunk_appendf(out, "%02x", key[i]);
1704 }
1705 chunk_appendf(out, "\n");
1706 }
1707#endif
1708
1709 return 0;
1710}
1711
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001712
Willy Tarreau4fd9b4d2022-05-04 16:11:50 +02001713/* IO handler of the details "show ssl cert <filename>".
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001714 * It uses a struct show_cert_ctx and ckchs_transaction in read-only.
Willy Tarreau4fd9b4d2022-05-04 16:11:50 +02001715 */
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001716static int cli_io_handler_show_cert_detail(struct appctx *appctx)
1717{
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001718 struct show_cert_ctx *ctx = appctx->svcctx;
Christopher Faulet908628c2022-03-25 16:43:49 +01001719 struct conn_stream *cs = appctx->owner;
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001720 struct ckch_store *ckchs = ctx->cur_ckchs;
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001721 struct buffer *out = alloc_trash_chunk();
1722 int retval = 0;
1723
1724 if (!out)
1725 goto end_no_putchk;
1726
1727 chunk_appendf(out, "Filename: ");
1728 if (ckchs == ckchs_transaction.new_ckchs)
1729 chunk_appendf(out, "*");
1730 chunk_appendf(out, "%s\n", ckchs->path);
1731
1732 chunk_appendf(out, "Status: ");
1733 if (ckchs->ckch->cert == NULL)
1734 chunk_appendf(out, "Empty\n");
1735 else if (LIST_ISEMPTY(&ckchs->ckch_inst))
1736 chunk_appendf(out, "Unused\n");
1737 else
1738 chunk_appendf(out, "Used\n");
1739
1740 retval = show_cert_detail(ckchs->ckch->cert, ckchs->ckch->chain, out);
1741 if (retval < 0)
1742 goto end_no_putchk;
1743 else if (retval)
1744 goto end;
1745
Remi Tricot-Le Bretonda968f62021-06-10 13:51:14 +02001746 ckch_store_show_ocsp_certid(ckchs, out);
1747
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001748end:
Christopher Faulet908628c2022-03-25 16:43:49 +01001749 if (ci_putchk(cs_ic(cs), out) == -1) {
Christopher Fauleta0bdec32022-04-04 07:51:21 +02001750 cs_rx_room_blk(cs);
William Lallemandda8584c2020-05-14 10:14:37 +02001751 goto yield;
1752 }
1753
1754end_no_putchk:
William Lallemandda8584c2020-05-14 10:14:37 +02001755 free_trash_chunk(out);
1756 return 1;
1757yield:
William Lallemandda8584c2020-05-14 10:14:37 +02001758 free_trash_chunk(out);
1759 return 0; /* should come back */
1760}
1761
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001762
Willy Tarreau4fd9b4d2022-05-04 16:11:50 +02001763/* IO handler of the details "show ssl cert <filename.ocsp>".
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001764 * It uses a show_cert_ctx.
Willy Tarreau4fd9b4d2022-05-04 16:11:50 +02001765 */
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001766static int cli_io_handler_show_cert_ocsp_detail(struct appctx *appctx)
1767{
Remi Tricot-Le Breton3faf0cb2021-06-10 18:10:32 +02001768#if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) && !defined OPENSSL_IS_BORINGSSL)
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001769 struct show_cert_ctx *ctx = appctx->svcctx;
Christopher Faulet908628c2022-03-25 16:43:49 +01001770 struct conn_stream *cs = appctx->owner;
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001771 struct ckch_store *ckchs = ctx->cur_ckchs;
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001772 struct buffer *out = alloc_trash_chunk();
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001773 int from_transaction = ctx->transaction;
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001774
1775 if (!out)
1776 goto end_no_putchk;
1777
1778 /* If we try to display an ongoing transaction's OCSP response, we
1779 * need to dump the ckch's ocsp_response buffer directly.
1780 * Otherwise, we must rebuild the certificate's certid in order to
1781 * look for the current OCSP response in the tree. */
1782 if (from_transaction && ckchs->ckch->ocsp_response) {
Remi Tricot-Le Bretona9a591a2022-02-16 14:42:22 +01001783 if (ssl_ocsp_response_print(ckchs->ckch->ocsp_response, out))
1784 goto end_no_putchk;
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001785 }
1786 else {
1787 unsigned char key[OCSP_MAX_CERTID_ASN1_LENGTH] = {};
1788 unsigned int key_length = 0;
1789
1790 if (ckch_store_build_certid(ckchs, (unsigned char*)key, &key_length) < 0)
1791 goto end_no_putchk;
1792
Remi Tricot-Le Bretona9a591a2022-02-16 14:42:22 +01001793 if (ssl_get_ocspresponse_detail(key, out))
1794 goto end_no_putchk;
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001795 }
1796
Christopher Faulet908628c2022-03-25 16:43:49 +01001797 if (ci_putchk(cs_ic(cs), out) == -1) {
Christopher Fauleta0bdec32022-04-04 07:51:21 +02001798 cs_rx_room_blk(cs);
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001799 goto yield;
1800 }
1801
1802end_no_putchk:
1803 free_trash_chunk(out);
1804 return 1;
1805yield:
1806 free_trash_chunk(out);
1807 return 0; /* should come back */
1808#else
1809 return cli_err(appctx, "HAProxy was compiled against a version of OpenSSL that doesn't support OCSP stapling.\n");
1810#endif
1811}
1812
William Lallemandda8584c2020-05-14 10:14:37 +02001813/* parsing function for 'show ssl cert [certfile]' */
1814static int cli_parse_show_cert(char **args, char *payload, struct appctx *appctx, void *private)
1815{
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001816 struct show_cert_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
William Lallemandda8584c2020-05-14 10:14:37 +02001817 struct ckch_store *ckchs;
1818
1819 if (!cli_has_level(appctx, ACCESS_LVL_OPER))
1820 return cli_err(appctx, "Can't allocate memory!\n");
1821
1822 /* The operations on the CKCH architecture are locked so we can
1823 * manipulate ckch_store and ckch_inst */
1824 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
1825 return cli_err(appctx, "Can't show!\nOperations on certificates are currently locked!\n");
1826
1827 /* check if there is a certificate to lookup */
1828 if (*args[3]) {
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001829 int show_ocsp_detail = 0;
1830 int from_transaction = 0;
1831 char *end;
1832
1833 /* We manage the special case "certname.ocsp" through which we
1834 * can show the details of an OCSP response. */
1835 end = strrchr(args[3], '.');
1836 if (end && strcmp(end+1, "ocsp") == 0) {
1837 *end = '\0';
1838 show_ocsp_detail = 1;
1839 }
1840
William Lallemandda8584c2020-05-14 10:14:37 +02001841 if (*args[3] == '*') {
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001842 from_transaction = 1;
William Lallemandda8584c2020-05-14 10:14:37 +02001843 if (!ckchs_transaction.new_ckchs)
1844 goto error;
1845
1846 ckchs = ckchs_transaction.new_ckchs;
1847
Tim Duesterhuse5ff1412021-01-02 22:31:53 +01001848 if (strcmp(args[3] + 1, ckchs->path) != 0)
William Lallemandda8584c2020-05-14 10:14:37 +02001849 goto error;
1850
1851 } else {
1852 if ((ckchs = ckchs_lookup(args[3])) == NULL)
1853 goto error;
1854
1855 }
1856
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001857 ctx->cur_ckchs = ckchs;
William Lallemandda8584c2020-05-14 10:14:37 +02001858 /* use the IO handler that shows details */
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001859 if (show_ocsp_detail) {
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001860 ctx->transaction = from_transaction;
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001861 appctx->io_handler = cli_io_handler_show_cert_ocsp_detail;
1862 }
1863 else
1864 appctx->io_handler = cli_io_handler_show_cert_detail;
William Lallemandda8584c2020-05-14 10:14:37 +02001865 }
1866
1867 return 0;
1868
1869error:
1870 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1871 return cli_err(appctx, "Can't display the certificate: Not found or the certificate is a bundle!\n");
1872}
1873
1874/* release function of the `set ssl cert' command, free things and unlock the spinlock */
1875static void cli_release_commit_cert(struct appctx *appctx)
1876{
Willy Tarreaua645b6a2022-05-04 19:58:00 +02001877 struct commit_cert_ctx *ctx = appctx->svcctx;
William Lallemandda8584c2020-05-14 10:14:37 +02001878 struct ckch_store *new_ckchs;
1879
1880 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1881
Willy Tarreaucb1b4ed2022-05-05 08:15:27 +02001882 if (ctx->state != CERT_ST_FIN) {
William Lallemandda8584c2020-05-14 10:14:37 +02001883 /* free every new sni_ctx and the new store, which are not in the trees so no spinlock there */
Willy Tarreaua645b6a2022-05-04 19:58:00 +02001884 new_ckchs = ctx->new_ckchs;
William Lallemandda8584c2020-05-14 10:14:37 +02001885
1886 /* if the allocation failed, we need to free everything from the temporary list */
1887 ckch_store_free(new_ckchs);
1888 }
1889}
1890
Remi Tricot-Le Bretonbfadc022021-02-24 12:20:48 +01001891
1892/*
1893 * Rebuild a new instance 'new_inst' based on an old instance 'ckchi' and a
1894 * specific ckch_store.
1895 * Returns 0 in case of success, 1 otherwise.
1896 */
William Lallemande60c7d62022-03-30 11:26:15 +02001897int ckch_inst_rebuild(struct ckch_store *ckch_store, struct ckch_inst *ckchi,
1898 struct ckch_inst **new_inst, char **err)
Remi Tricot-Le Bretonbfadc022021-02-24 12:20:48 +01001899{
1900 int retval = 0;
1901 int errcode = 0;
1902 struct sni_ctx *sc0, *sc0s;
1903 char **sni_filter = NULL;
1904 int fcount = 0;
1905
1906 if (ckchi->crtlist_entry) {
1907 sni_filter = ckchi->crtlist_entry->filters;
1908 fcount = ckchi->crtlist_entry->fcount;
1909 }
1910
1911 if (ckchi->is_server_instance)
1912 errcode |= ckch_inst_new_load_srv_store(ckch_store->path, ckch_store, new_inst, err);
1913 else
1914 errcode |= ckch_inst_new_load_store(ckch_store->path, ckch_store, ckchi->bind_conf, ckchi->ssl_conf, sni_filter, fcount, new_inst, err);
1915
1916 if (errcode & ERR_CODE)
1917 return 1;
1918
1919 /* if the previous ckchi was used as the default */
1920 if (ckchi->is_default)
1921 (*new_inst)->is_default = 1;
1922
1923 (*new_inst)->is_server_instance = ckchi->is_server_instance;
1924 (*new_inst)->server = ckchi->server;
1925 /* Create a new SSL_CTX and link it to the new instance. */
1926 if ((*new_inst)->is_server_instance) {
1927 retval = ssl_sock_prep_srv_ctx_and_inst(ckchi->server, (*new_inst)->ctx, (*new_inst));
1928 if (retval)
1929 return 1;
1930 }
1931
1932 /* create the link to the crtlist_entry */
1933 (*new_inst)->crtlist_entry = ckchi->crtlist_entry;
1934
1935 /* we need to initialize the SSL_CTX generated */
1936 /* this iterate on the newly generated SNIs in the new instance to prepare their SSL_CTX */
1937 list_for_each_entry_safe(sc0, sc0s, &(*new_inst)->sni_ctx, by_ckch_inst) {
1938 if (!sc0->order) { /* we initialized only the first SSL_CTX because it's the same in the other sni_ctx's */
1939 errcode |= ssl_sock_prep_ctx_and_inst(ckchi->bind_conf, ckchi->ssl_conf, sc0->ctx, *new_inst, err);
1940 if (errcode & ERR_CODE)
1941 return 1;
1942 }
1943 }
1944
1945 return 0;
1946}
1947
1948/*
1949 * Load all the new SNIs of a newly built ckch instance in the trees, or replace
1950 * a server's main ckch instance.
1951 */
1952static void __ssl_sock_load_new_ckch_instance(struct ckch_inst *ckchi)
1953{
1954 /* The bind_conf will be null on server ckch_instances. */
1955 if (ckchi->is_server_instance) {
1956 int i;
1957 /* a lock is needed here since we have to free the SSL cache */
1958 HA_RWLOCK_WRLOCK(SSL_SERVER_LOCK, &ckchi->server->ssl_ctx.lock);
1959 /* free the server current SSL_CTX */
1960 SSL_CTX_free(ckchi->server->ssl_ctx.ctx);
1961 /* Actual ssl context update */
1962 SSL_CTX_up_ref(ckchi->ctx);
1963 ckchi->server->ssl_ctx.ctx = ckchi->ctx;
1964 ckchi->server->ssl_ctx.inst = ckchi;
1965
1966 /* flush the session cache of the server */
1967 for (i = 0; i < global.nbthread; i++) {
William Lallemandce990332021-11-23 15:15:09 +01001968 ha_free(&ckchi->server->ssl_ctx.reused_sess[i].sni);
Remi Tricot-Le Bretonbfadc022021-02-24 12:20:48 +01001969 ha_free(&ckchi->server->ssl_ctx.reused_sess[i].ptr);
1970 }
1971 HA_RWLOCK_WRUNLOCK(SSL_SERVER_LOCK, &ckchi->server->ssl_ctx.lock);
1972
1973 } else {
1974 HA_RWLOCK_WRLOCK(SNI_LOCK, &ckchi->bind_conf->sni_lock);
1975 ssl_sock_load_cert_sni(ckchi, ckchi->bind_conf);
1976 HA_RWLOCK_WRUNLOCK(SNI_LOCK, &ckchi->bind_conf->sni_lock);
1977 }
1978}
1979
1980/*
1981 * Delete a ckch instance that was replaced after a CLI command.
1982 */
1983static void __ckch_inst_free_locked(struct ckch_inst *ckchi)
1984{
1985 if (ckchi->is_server_instance) {
1986 /* no lock for servers */
1987 ckch_inst_free(ckchi);
1988 } else {
1989 struct bind_conf __maybe_unused *bind_conf = ckchi->bind_conf;
1990
1991 HA_RWLOCK_WRLOCK(SNI_LOCK, &bind_conf->sni_lock);
1992 ckch_inst_free(ckchi);
1993 HA_RWLOCK_WRUNLOCK(SNI_LOCK, &bind_conf->sni_lock);
1994 }
1995}
1996
William Lallemand3b5a3a62022-03-29 14:29:31 +02001997/* Replace a ckch_store in the ckch tree and insert the whole dependencies,
1998* then free the previous dependencies and store.
1999* Used in the case of a certificate update.
2000*
2001* Every dependencies must allocated before using this function.
2002*
2003* This function can't fail as it only update pointers, and does not alloc anything.
2004*
2005* /!\ This function must be used under the ckch lock. /!\
2006*
2007* - Insert every dependencies (SNI, crtlist_entry, ckch_inst, etc)
2008* - Delete the old ckch_store from the tree
2009* - Insert the new ckch_store
2010* - Free the old dependencies and the old ckch_store
2011*/
2012void ckch_store_replace(struct ckch_store *old_ckchs, struct ckch_store *new_ckchs)
2013{
2014 struct crtlist_entry *entry;
2015 struct ckch_inst *ckchi, *ckchis;
2016
2017 LIST_SPLICE(&new_ckchs->crtlist_entry, &old_ckchs->crtlist_entry);
2018 list_for_each_entry(entry, &new_ckchs->crtlist_entry, by_ckch_store) {
2019 ebpt_delete(&entry->node);
2020 /* change the ptr and reinsert the node */
2021 entry->node.key = new_ckchs;
2022 ebpt_insert(&entry->crtlist->entries, &entry->node);
2023 }
2024 /* insert the new ckch_insts in the crtlist_entry */
2025 list_for_each_entry(ckchi, &new_ckchs->ckch_inst, by_ckchs) {
2026 if (ckchi->crtlist_entry)
2027 LIST_INSERT(&ckchi->crtlist_entry->ckch_inst, &ckchi->by_crtlist_entry);
2028 }
2029 /* First, we insert every new SNIs in the trees, also replace the default_ctx */
2030 list_for_each_entry_safe(ckchi, ckchis, &new_ckchs->ckch_inst, by_ckchs) {
2031 __ssl_sock_load_new_ckch_instance(ckchi);
2032 }
2033 /* delete the old sni_ctx, the old ckch_insts and the ckch_store */
2034 list_for_each_entry_safe(ckchi, ckchis, &old_ckchs->ckch_inst, by_ckchs) {
2035 __ckch_inst_free_locked(ckchi);
2036 }
2037
2038 ckch_store_free(old_ckchs);
2039 ebst_insert(&ckchs_tree, &new_ckchs->node);
2040}
2041
Remi Tricot-Le Bretonbfadc022021-02-24 12:20:48 +01002042
William Lallemandda8584c2020-05-14 10:14:37 +02002043/*
2044 * This function tries to create the new ckch_inst and their SNIs
William Lallemand30fcca12022-03-30 12:03:12 +02002045 *
2046 * /!\ don't forget to update __hlua_ckch_commit() if you changes things there. /!\
William Lallemandda8584c2020-05-14 10:14:37 +02002047 */
2048static int cli_io_handler_commit_cert(struct appctx *appctx)
2049{
Willy Tarreaua645b6a2022-05-04 19:58:00 +02002050 struct commit_cert_ctx *ctx = appctx->svcctx;
Christopher Faulet908628c2022-03-25 16:43:49 +01002051 struct conn_stream *cs = appctx->owner;
William Lallemandda8584c2020-05-14 10:14:37 +02002052 int y = 0;
2053 char *err = NULL;
William Lallemandda8584c2020-05-14 10:14:37 +02002054 struct ckch_store *old_ckchs, *new_ckchs = NULL;
William Lallemand3b5a3a62022-03-29 14:29:31 +02002055 struct ckch_inst *ckchi;
William Lallemandda8584c2020-05-14 10:14:37 +02002056 struct buffer *trash = alloc_trash_chunk();
William Lallemandda8584c2020-05-14 10:14:37 +02002057
2058 if (trash == NULL)
2059 goto error;
2060
Christopher Faulet908628c2022-03-25 16:43:49 +01002061 if (unlikely(cs_ic(cs)->flags & (CF_WRITE_ERROR|CF_SHUTW)))
William Lallemandda8584c2020-05-14 10:14:37 +02002062 goto error;
2063
2064 while (1) {
Willy Tarreaucb1b4ed2022-05-05 08:15:27 +02002065 switch (ctx->state) {
2066 case CERT_ST_INIT:
William Lallemandda8584c2020-05-14 10:14:37 +02002067 /* This state just print the update message */
2068 chunk_printf(trash, "Committing %s", ckchs_transaction.path);
Christopher Faulet908628c2022-03-25 16:43:49 +01002069 if (ci_putchk(cs_ic(cs), trash) == -1) {
Christopher Fauleta0bdec32022-04-04 07:51:21 +02002070 cs_rx_room_blk(cs);
William Lallemandda8584c2020-05-14 10:14:37 +02002071 goto yield;
2072 }
Willy Tarreaucb1b4ed2022-05-05 08:15:27 +02002073 ctx->state = CERT_ST_GEN;
William Lallemandda8584c2020-05-14 10:14:37 +02002074 /* fallthrough */
Willy Tarreaucb1b4ed2022-05-05 08:15:27 +02002075 case CERT_ST_GEN:
William Lallemandda8584c2020-05-14 10:14:37 +02002076 /*
2077 * This state generates the ckch instances with their
2078 * sni_ctxs and SSL_CTX.
2079 *
2080 * Since the SSL_CTX generation can be CPU consumer, we
2081 * yield every 10 instances.
2082 */
2083
Willy Tarreaua645b6a2022-05-04 19:58:00 +02002084 old_ckchs = ctx->old_ckchs;
2085 new_ckchs = ctx->new_ckchs;
William Lallemandda8584c2020-05-14 10:14:37 +02002086
2087 if (!new_ckchs)
2088 continue;
2089
2090 /* get the next ckchi to regenerate */
Willy Tarreaua645b6a2022-05-04 19:58:00 +02002091 ckchi = ctx->next_ckchi;
William Lallemandda8584c2020-05-14 10:14:37 +02002092 /* we didn't start yet, set it to the first elem */
2093 if (ckchi == NULL)
2094 ckchi = LIST_ELEM(old_ckchs->ckch_inst.n, typeof(ckchi), by_ckchs);
2095
2096 /* walk through the old ckch_inst and creates new ckch_inst using the updated ckchs */
2097 list_for_each_entry_from(ckchi, &old_ckchs->ckch_inst, by_ckchs) {
2098 struct ckch_inst *new_inst;
William Lallemandda8584c2020-05-14 10:14:37 +02002099
2100 /* it takes a lot of CPU to creates SSL_CTXs, so we yield every 10 CKCH instances */
2101 if (y >= 10) {
2102 /* save the next ckchi to compute */
Willy Tarreaua645b6a2022-05-04 19:58:00 +02002103 ctx->next_ckchi = ckchi;
William Lallemandda8584c2020-05-14 10:14:37 +02002104 goto yield;
2105 }
2106
Remi Tricot-Le Bretonbfadc022021-02-24 12:20:48 +01002107 if (ckch_inst_rebuild(new_ckchs, ckchi, &new_inst, &err))
William Lallemandda8584c2020-05-14 10:14:37 +02002108 goto error;
2109
William Lallemandda8584c2020-05-14 10:14:37 +02002110 /* display one dot per new instance */
2111 chunk_appendf(trash, ".");
2112 /* link the new ckch_inst to the duplicate */
Willy Tarreau2b718102021-04-21 07:32:39 +02002113 LIST_APPEND(&new_ckchs->ckch_inst, &new_inst->by_ckchs);
William Lallemandda8584c2020-05-14 10:14:37 +02002114 y++;
2115 }
Willy Tarreaucb1b4ed2022-05-05 08:15:27 +02002116 ctx->state = CERT_ST_INSERT;
William Lallemandda8584c2020-05-14 10:14:37 +02002117 /* fallthrough */
Willy Tarreaucb1b4ed2022-05-05 08:15:27 +02002118 case CERT_ST_INSERT:
William Lallemandda8584c2020-05-14 10:14:37 +02002119 /* The generation is finished, we can insert everything */
2120
Willy Tarreaua645b6a2022-05-04 19:58:00 +02002121 old_ckchs = ctx->old_ckchs;
2122 new_ckchs = ctx->new_ckchs;
William Lallemandda8584c2020-05-14 10:14:37 +02002123
2124 if (!new_ckchs)
2125 continue;
2126
William Lallemand3b5a3a62022-03-29 14:29:31 +02002127 /* insert everything and remove the previous objects */
2128 ckch_store_replace(old_ckchs, new_ckchs);
William Lallemandda8584c2020-05-14 10:14:37 +02002129
Willy Tarreaucb1b4ed2022-05-05 08:15:27 +02002130 ctx->state = CERT_ST_FIN;
William Lallemandda8584c2020-05-14 10:14:37 +02002131 /* fallthrough */
Willy Tarreaucb1b4ed2022-05-05 08:15:27 +02002132 case CERT_ST_FIN:
William Lallemandda8584c2020-05-14 10:14:37 +02002133 /* we achieved the transaction, we can set everything to NULL */
Willy Tarreau61cfdf42021-02-20 10:46:51 +01002134 ha_free(&ckchs_transaction.path);
William Lallemandda8584c2020-05-14 10:14:37 +02002135 ckchs_transaction.new_ckchs = NULL;
2136 ckchs_transaction.old_ckchs = NULL;
2137 goto end;
2138 }
2139 }
2140end:
2141
2142 chunk_appendf(trash, "\n");
William Lallemandda8584c2020-05-14 10:14:37 +02002143 chunk_appendf(trash, "Success!\n");
Christopher Faulet908628c2022-03-25 16:43:49 +01002144 if (ci_putchk(cs_ic(cs), trash) == -1)
Christopher Fauleta0bdec32022-04-04 07:51:21 +02002145 cs_rx_room_blk(cs);
William Lallemandda8584c2020-05-14 10:14:37 +02002146 free_trash_chunk(trash);
2147 /* success: call the release function and don't come back */
2148 return 1;
2149yield:
2150 /* store the state */
Christopher Faulet908628c2022-03-25 16:43:49 +01002151 if (ci_putchk(cs_ic(cs), trash) == -1)
Christopher Fauleta0bdec32022-04-04 07:51:21 +02002152 cs_rx_room_blk(cs);
William Lallemandda8584c2020-05-14 10:14:37 +02002153 free_trash_chunk(trash);
Christopher Fauleta0bdec32022-04-04 07:51:21 +02002154 cs_rx_endp_more(cs); /* let's come back later */
William Lallemandda8584c2020-05-14 10:14:37 +02002155 return 0; /* should come back */
2156
2157error:
2158 /* spin unlock and free are done in the release function */
2159 if (trash) {
2160 chunk_appendf(trash, "\n%sFailed!\n", err);
Christopher Faulet908628c2022-03-25 16:43:49 +01002161 if (ci_putchk(cs_ic(cs), trash) == -1)
Christopher Fauleta0bdec32022-04-04 07:51:21 +02002162 cs_rx_room_blk(cs);
William Lallemandda8584c2020-05-14 10:14:37 +02002163 free_trash_chunk(trash);
2164 }
2165 /* error: call the release function and don't come back */
2166 return 1;
2167}
2168
2169/*
2170 * Parsing function of 'commit ssl cert'
2171 */
2172static int cli_parse_commit_cert(char **args, char *payload, struct appctx *appctx, void *private)
2173{
Willy Tarreaua645b6a2022-05-04 19:58:00 +02002174 struct commit_cert_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
William Lallemandda8584c2020-05-14 10:14:37 +02002175 char *err = NULL;
2176
2177 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2178 return 1;
2179
2180 if (!*args[3])
2181 return cli_err(appctx, "'commit ssl cert expects a filename\n");
2182
2183 /* The operations on the CKCH architecture are locked so we can
2184 * manipulate ckch_store and ckch_inst */
2185 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2186 return cli_err(appctx, "Can't commit the certificate!\nOperations on certificates are currently locked!\n");
2187
2188 if (!ckchs_transaction.path) {
2189 memprintf(&err, "No ongoing transaction! !\n");
2190 goto error;
2191 }
2192
2193 if (strcmp(ckchs_transaction.path, args[3]) != 0) {
2194 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", ckchs_transaction.path, args[3]);
2195 goto error;
2196 }
2197
William Lallemand5685ccf2020-09-16 16:12:25 +02002198 /* if a certificate is here, a private key must be here too */
2199 if (ckchs_transaction.new_ckchs->ckch->cert && !ckchs_transaction.new_ckchs->ckch->key) {
2200 memprintf(&err, "The transaction must contain at least a certificate and a private key!\n");
2201 goto error;
2202 }
William Lallemanda9419522020-06-24 16:26:41 +02002203
William Lallemand5685ccf2020-09-16 16:12:25 +02002204 if (!X509_check_private_key(ckchs_transaction.new_ckchs->ckch->cert, ckchs_transaction.new_ckchs->ckch->key)) {
2205 memprintf(&err, "inconsistencies between private key and certificate loaded '%s'.\n", ckchs_transaction.path);
2206 goto error;
William Lallemandda8584c2020-05-14 10:14:37 +02002207 }
2208
2209 /* init the appctx structure */
Willy Tarreaucb1b4ed2022-05-05 08:15:27 +02002210 ctx->state = CERT_ST_INIT;
Willy Tarreaua645b6a2022-05-04 19:58:00 +02002211 ctx->next_ckchi = NULL;
2212 ctx->new_ckchs = ckchs_transaction.new_ckchs;
2213 ctx->old_ckchs = ckchs_transaction.old_ckchs;
William Lallemandda8584c2020-05-14 10:14:37 +02002214
2215 /* we don't unlock there, it will be unlock after the IO handler, in the release handler */
2216 return 0;
2217
2218error:
2219
2220 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2221 err = memprintf(&err, "%sCan't commit %s!\n", err ? err : "", args[3]);
2222
2223 return cli_dynerr(appctx, err);
2224}
2225
2226
2227
2228
2229/*
2230 * Parsing function of `set ssl cert`, it updates or creates a temporary ckch.
Willy Tarreau329f4b42022-05-04 20:05:55 +02002231 * It uses a set_cert_ctx context, and ckchs_transaction under a lock.
William Lallemandda8584c2020-05-14 10:14:37 +02002232 */
2233static int cli_parse_set_cert(char **args, char *payload, struct appctx *appctx, void *private)
2234{
Willy Tarreau329f4b42022-05-04 20:05:55 +02002235 struct set_cert_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
William Lallemandda8584c2020-05-14 10:14:37 +02002236 struct ckch_store *new_ckchs = NULL;
2237 struct ckch_store *old_ckchs = NULL;
2238 char *err = NULL;
2239 int i;
William Lallemandda8584c2020-05-14 10:14:37 +02002240 int errcode = 0;
2241 char *end;
William Lallemandff8bf982022-03-29 10:44:23 +02002242 struct cert_exts *cert_ext = &cert_exts[0]; /* default one, PEM */
William Lallemandda8584c2020-05-14 10:14:37 +02002243 struct cert_key_and_chain *ckch;
2244 struct buffer *buf;
2245
2246 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2247 return 1;
2248
William Lallemandda8584c2020-05-14 10:14:37 +02002249 if (!*args[3] || !payload)
2250 return cli_err(appctx, "'set ssl cert expects a filename and a certificate as a payload\n");
2251
2252 /* The operations on the CKCH architecture are locked so we can
2253 * manipulate ckch_store and ckch_inst */
2254 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2255 return cli_err(appctx, "Can't update the certificate!\nOperations on certificates are currently locked!\n");
2256
William Lallemand5ba80d62021-05-04 16:17:27 +02002257 if ((buf = alloc_trash_chunk()) == NULL) {
2258 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2259 errcode |= ERR_ALERT | ERR_FATAL;
2260 goto end;
2261 }
William Lallemande5ff4ad2020-06-08 09:40:37 +02002262
William Lallemandda8584c2020-05-14 10:14:37 +02002263 if (!chunk_strcpy(buf, args[3])) {
2264 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2265 errcode |= ERR_ALERT | ERR_FATAL;
2266 goto end;
2267 }
2268
2269 /* check which type of file we want to update */
William Lallemandff8bf982022-03-29 10:44:23 +02002270 for (i = 0; cert_exts[i].ext != NULL; i++) {
William Lallemandda8584c2020-05-14 10:14:37 +02002271 end = strrchr(buf->area, '.');
Tim Duesterhuse5ff1412021-01-02 22:31:53 +01002272 if (end && *cert_exts[i].ext && (strcmp(end + 1, cert_exts[i].ext) == 0)) {
William Lallemandda8584c2020-05-14 10:14:37 +02002273 *end = '\0';
William Lallemand089c1382020-10-23 17:35:12 +02002274 buf->data = strlen(buf->area);
William Lallemandff8bf982022-03-29 10:44:23 +02002275 cert_ext = &cert_exts[i];
William Lallemandda8584c2020-05-14 10:14:37 +02002276 break;
2277 }
2278 }
2279
Willy Tarreau329f4b42022-05-04 20:05:55 +02002280 ctx->old_ckchs = NULL;
2281 ctx->new_ckchs = NULL;
William Lallemandda8584c2020-05-14 10:14:37 +02002282
2283 /* if there is an ongoing transaction */
2284 if (ckchs_transaction.path) {
William Lallemandda8584c2020-05-14 10:14:37 +02002285 /* if there is an ongoing transaction, check if this is the same file */
2286 if (strcmp(ckchs_transaction.path, buf->area) != 0) {
William Lallemand089c1382020-10-23 17:35:12 +02002287 /* we didn't find the transaction, must try more cases below */
2288
2289 /* 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 +02002290 if (cert_ext->type != CERT_TYPE_PEM && global_ssl.extra_files_noext) {
William Lallemand089c1382020-10-23 17:35:12 +02002291 if (!chunk_strcat(buf, ".crt")) {
2292 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2293 errcode |= ERR_ALERT | ERR_FATAL;
2294 goto end;
2295 }
2296
2297 if (strcmp(ckchs_transaction.path, buf->area) != 0) {
2298 /* remove .crt of the error message */
2299 *(b_orig(buf) + b_data(buf) + strlen(".crt")) = '\0';
2300 b_sub(buf, strlen(".crt"));
2301
2302 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", ckchs_transaction.path, buf->area);
2303 errcode |= ERR_ALERT | ERR_FATAL;
2304 goto end;
2305 }
2306 }
William Lallemandda8584c2020-05-14 10:14:37 +02002307 }
2308
Willy Tarreau329f4b42022-05-04 20:05:55 +02002309 ctx->old_ckchs = ckchs_transaction.new_ckchs;
William Lallemandda8584c2020-05-14 10:14:37 +02002310
2311 } else {
William Lallemandda8584c2020-05-14 10:14:37 +02002312
William Lallemand95fefa12020-09-09 12:01:33 +02002313 /* lookup for the certificate in the tree */
Willy Tarreau329f4b42022-05-04 20:05:55 +02002314 ctx->old_ckchs = ckchs_lookup(buf->area);
William Lallemand089c1382020-10-23 17:35:12 +02002315
Willy Tarreau329f4b42022-05-04 20:05:55 +02002316 if (!ctx->old_ckchs) {
William Lallemand089c1382020-10-23 17:35:12 +02002317 /* 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 +02002318 if (cert_ext->type != CERT_TYPE_PEM && global_ssl.extra_files_noext) {
William Lallemand089c1382020-10-23 17:35:12 +02002319 if (!chunk_strcat(buf, ".crt")) {
2320 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2321 errcode |= ERR_ALERT | ERR_FATAL;
2322 goto end;
2323 }
Willy Tarreau329f4b42022-05-04 20:05:55 +02002324 ctx->old_ckchs = ckchs_lookup(buf->area);
William Lallemand089c1382020-10-23 17:35:12 +02002325 }
2326 }
William Lallemandda8584c2020-05-14 10:14:37 +02002327 }
2328
Willy Tarreau329f4b42022-05-04 20:05:55 +02002329 if (!ctx->old_ckchs) {
William Lallemandda8584c2020-05-14 10:14:37 +02002330 memprintf(&err, "%sCan't replace a certificate which is not referenced by the configuration!\n",
2331 err ? err : "");
2332 errcode |= ERR_ALERT | ERR_FATAL;
2333 goto end;
2334 }
2335
Willy Tarreau329f4b42022-05-04 20:05:55 +02002336 if (!ctx->path) {
William Lallemandda8584c2020-05-14 10:14:37 +02002337 /* this is a new transaction, set the path of the transaction */
Willy Tarreau329f4b42022-05-04 20:05:55 +02002338 ctx->path = strdup(ctx->old_ckchs->path);
2339 if (!ctx->path) {
William Lallemandda8584c2020-05-14 10:14:37 +02002340 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2341 errcode |= ERR_ALERT | ERR_FATAL;
2342 goto end;
2343 }
2344 }
2345
Willy Tarreau329f4b42022-05-04 20:05:55 +02002346 old_ckchs = ctx->old_ckchs;
William Lallemandda8584c2020-05-14 10:14:37 +02002347
2348 /* duplicate the ckch store */
2349 new_ckchs = ckchs_dup(old_ckchs);
2350 if (!new_ckchs) {
2351 memprintf(&err, "%sCannot allocate memory!\n",
2352 err ? err : "");
2353 errcode |= ERR_ALERT | ERR_FATAL;
2354 goto end;
2355 }
2356
William Lallemand95fefa12020-09-09 12:01:33 +02002357 ckch = new_ckchs->ckch;
William Lallemandda8584c2020-05-14 10:14:37 +02002358
2359 /* appply the change on the duplicate */
William Lallemandff8bf982022-03-29 10:44:23 +02002360 if (cert_ext->load(buf->area, payload, ckch, &err) != 0) {
William Lallemandda8584c2020-05-14 10:14:37 +02002361 memprintf(&err, "%sCan't load the payload\n", err ? err : "");
2362 errcode |= ERR_ALERT | ERR_FATAL;
2363 goto end;
2364 }
2365
Willy Tarreau329f4b42022-05-04 20:05:55 +02002366 ctx->new_ckchs = new_ckchs;
William Lallemandda8584c2020-05-14 10:14:37 +02002367
2368 /* we succeed, we can save the ckchs in the transaction */
2369
2370 /* if there wasn't a transaction, update the old ckchs */
2371 if (!ckchs_transaction.old_ckchs) {
Willy Tarreau329f4b42022-05-04 20:05:55 +02002372 ckchs_transaction.old_ckchs = ctx->old_ckchs;
2373 ckchs_transaction.path = ctx->path;
William Lallemandda8584c2020-05-14 10:14:37 +02002374 err = memprintf(&err, "Transaction created for certificate %s!\n", ckchs_transaction.path);
2375 } else {
2376 err = memprintf(&err, "Transaction updated for certificate %s!\n", ckchs_transaction.path);
2377
2378 }
2379
2380 /* free the previous ckchs if there was a transaction */
2381 ckch_store_free(ckchs_transaction.new_ckchs);
2382
Willy Tarreau329f4b42022-05-04 20:05:55 +02002383 ckchs_transaction.new_ckchs = ctx->new_ckchs;
William Lallemandda8584c2020-05-14 10:14:37 +02002384
2385
2386 /* creates the SNI ctxs later in the IO handler */
2387
2388end:
2389 free_trash_chunk(buf);
2390
2391 if (errcode & ERR_CODE) {
Willy Tarreau329f4b42022-05-04 20:05:55 +02002392 ckch_store_free(ctx->new_ckchs);
2393 ctx->new_ckchs = NULL;
2394 ctx->old_ckchs = NULL;
2395 ha_free(&ctx->path);
William Lallemandda8584c2020-05-14 10:14:37 +02002396
2397 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2398 return cli_dynerr(appctx, memprintf(&err, "%sCan't update %s!\n", err ? err : "", args[3]));
2399 } else {
William Lallemandda8584c2020-05-14 10:14:37 +02002400 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2401 return cli_dynmsg(appctx, LOG_NOTICE, err);
2402 }
2403 /* TODO: handle the ERR_WARN which are not handled because of the io_handler */
2404}
2405
2406/* parsing function of 'abort ssl cert' */
2407static int cli_parse_abort_cert(char **args, char *payload, struct appctx *appctx, void *private)
2408{
2409 char *err = NULL;
2410
2411 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2412 return 1;
2413
2414 if (!*args[3])
2415 return cli_err(appctx, "'abort ssl cert' expects a filename\n");
2416
2417 /* The operations on the CKCH architecture are locked so we can
2418 * manipulate ckch_store and ckch_inst */
2419 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2420 return cli_err(appctx, "Can't abort!\nOperations on certificates are currently locked!\n");
2421
2422 if (!ckchs_transaction.path) {
2423 memprintf(&err, "No ongoing transaction!\n");
2424 goto error;
2425 }
2426
2427 if (strcmp(ckchs_transaction.path, args[3]) != 0) {
2428 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to abort a transaction for '%s'\n", ckchs_transaction.path, args[3]);
2429 goto error;
2430 }
2431
2432 /* Only free the ckchs there, because the SNI and instances were not generated yet */
2433 ckch_store_free(ckchs_transaction.new_ckchs);
2434 ckchs_transaction.new_ckchs = NULL;
William Lallemandda8584c2020-05-14 10:14:37 +02002435 ckchs_transaction.old_ckchs = NULL;
Willy Tarreau61cfdf42021-02-20 10:46:51 +01002436 ha_free(&ckchs_transaction.path);
William Lallemandda8584c2020-05-14 10:14:37 +02002437
2438 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2439
2440 err = memprintf(&err, "Transaction aborted for certificate '%s'!\n", args[3]);
2441 return cli_dynmsg(appctx, LOG_NOTICE, err);
2442
2443error:
2444 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2445
2446 return cli_dynerr(appctx, err);
2447}
2448
2449/* parsing function of 'new ssl cert' */
2450static int cli_parse_new_cert(char **args, char *payload, struct appctx *appctx, void *private)
2451{
2452 struct ckch_store *store;
2453 char *err = NULL;
2454 char *path;
2455
2456 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2457 return 1;
2458
2459 if (!*args[3])
2460 return cli_err(appctx, "'new ssl cert' expects a filename\n");
2461
2462 path = args[3];
2463
2464 /* The operations on the CKCH architecture are locked so we can
2465 * manipulate ckch_store and ckch_inst */
2466 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2467 return cli_err(appctx, "Can't create a certificate!\nOperations on certificates are currently locked!\n");
2468
2469 store = ckchs_lookup(path);
2470 if (store != NULL) {
2471 memprintf(&err, "Certificate '%s' already exists!\n", path);
2472 store = NULL; /* we don't want to free it */
2473 goto error;
2474 }
2475 /* we won't support multi-certificate bundle here */
William Lallemandbd8e6ed2020-09-16 16:08:08 +02002476 store = ckch_store_new(path);
William Lallemandda8584c2020-05-14 10:14:37 +02002477 if (!store) {
2478 memprintf(&err, "unable to allocate memory.\n");
2479 goto error;
2480 }
2481
2482 /* insert into the ckchs tree */
2483 ebst_insert(&ckchs_tree, &store->node);
2484 memprintf(&err, "New empty certificate store '%s'!\n", args[3]);
2485
2486 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2487 return cli_dynmsg(appctx, LOG_NOTICE, err);
2488error:
2489 free(store);
2490 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2491 return cli_dynerr(appctx, err);
2492}
2493
2494/* parsing function of 'del ssl cert' */
2495static int cli_parse_del_cert(char **args, char *payload, struct appctx *appctx, void *private)
2496{
2497 struct ckch_store *store;
2498 char *err = NULL;
2499 char *filename;
2500
2501 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2502 return 1;
2503
2504 if (!*args[3])
2505 return cli_err(appctx, "'del ssl cert' expects a certificate name\n");
2506
2507 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2508 return cli_err(appctx, "Can't delete the certificate!\nOperations on certificates are currently locked!\n");
2509
2510 filename = args[3];
2511
2512 store = ckchs_lookup(filename);
2513 if (store == NULL) {
2514 memprintf(&err, "certificate '%s' doesn't exist!\n", filename);
2515 goto error;
2516 }
2517 if (!LIST_ISEMPTY(&store->ckch_inst)) {
2518 memprintf(&err, "certificate '%s' in use, can't be deleted!\n", filename);
2519 goto error;
2520 }
2521
2522 ebmb_delete(&store->node);
2523 ckch_store_free(store);
2524
2525 memprintf(&err, "Certificate '%s' deleted!\n", filename);
2526
2527 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2528 return cli_dynmsg(appctx, LOG_NOTICE, err);
2529
2530error:
2531 memprintf(&err, "Can't remove the certificate: %s\n", err ? err : "");
2532 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2533 return cli_dynerr(appctx, err);
2534}
2535
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002536
Remi Tricot-Le Breton9f40fe02021-03-16 16:21:27 +01002537
2538/* parsing function of 'new ssl ca-file' */
2539static int cli_parse_new_cafile(char **args, char *payload, struct appctx *appctx, void *private)
2540{
2541 struct cafile_entry *cafile_entry;
2542 char *err = NULL;
2543 char *path;
2544
2545 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2546 return 1;
2547
2548 if (!*args[3])
2549 return cli_err(appctx, "'new ssl ca-file' expects a filename\n");
2550
2551 path = args[3];
2552
2553 /* The operations on the CKCH architecture are locked so we can
2554 * manipulate ckch_store and ckch_inst */
2555 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2556 return cli_err(appctx, "Can't create a CA file!\nOperations on certificates are currently locked!\n");
2557
2558 cafile_entry = ssl_store_get_cafile_entry(path, 0);
2559 if (cafile_entry) {
2560 memprintf(&err, "CA file '%s' already exists!\n", path);
2561 goto error;
2562 }
2563
2564 cafile_entry = ssl_store_create_cafile_entry(path, NULL, CAFILE_CERT);
2565 if (!cafile_entry) {
2566 memprintf(&err, "%sCannot allocate memory!\n",
2567 err ? err : "");
2568 goto error;
2569 }
2570
2571 /* Add the newly created cafile_entry to the tree so that
2572 * any new ckch instance created from now can use it. */
2573 if (ssl_store_add_uncommitted_cafile_entry(cafile_entry))
2574 goto error;
2575
2576 memprintf(&err, "New CA file created '%s'!\n", path);
2577
2578 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2579 return cli_dynmsg(appctx, LOG_NOTICE, err);
2580error:
2581 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2582 return cli_dynerr(appctx, err);
2583}
2584
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002585/*
2586 * Parsing function of `set ssl ca-file`
2587 */
2588static int cli_parse_set_cafile(char **args, char *payload, struct appctx *appctx, void *private)
2589{
Willy Tarreaua37693f2022-05-04 20:12:55 +02002590 struct set_cafile_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002591 char *err = NULL;
2592 int errcode = 0;
2593 struct buffer *buf;
2594
2595 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2596 return 1;
2597
2598 if (!*args[3] || !payload)
2599 return cli_err(appctx, "'set ssl ca-file expects a filename and CAs as a payload\n");
2600
2601 /* The operations on the CKCH architecture are locked so we can
2602 * manipulate ckch_store and ckch_inst */
2603 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2604 return cli_err(appctx, "Can't update the CA file!\nOperations on certificates are currently locked!\n");
2605
2606 if ((buf = alloc_trash_chunk()) == NULL) {
2607 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2608 errcode |= ERR_ALERT | ERR_FATAL;
2609 goto end;
2610 }
2611
2612 if (!chunk_strcpy(buf, args[3])) {
2613 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2614 errcode |= ERR_ALERT | ERR_FATAL;
2615 goto end;
2616 }
2617
Willy Tarreaua37693f2022-05-04 20:12:55 +02002618 ctx->old_cafile_entry = NULL;
2619 ctx->new_cafile_entry = NULL;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002620
2621 /* if there is an ongoing transaction */
2622 if (cafile_transaction.path) {
2623 /* if there is an ongoing transaction, check if this is the same file */
2624 if (strcmp(cafile_transaction.path, buf->area) != 0) {
2625 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", cafile_transaction.path, buf->area);
2626 errcode |= ERR_ALERT | ERR_FATAL;
2627 goto end;
2628 }
Willy Tarreaua37693f2022-05-04 20:12:55 +02002629 ctx->old_cafile_entry = cafile_transaction.old_cafile_entry;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002630 }
2631 else {
2632 /* lookup for the certificate in the tree */
Willy Tarreaua37693f2022-05-04 20:12:55 +02002633 ctx->old_cafile_entry = ssl_store_get_cafile_entry(buf->area, 0);
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002634 }
2635
Willy Tarreaua37693f2022-05-04 20:12:55 +02002636 if (!ctx->old_cafile_entry) {
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002637 memprintf(&err, "%sCan't replace a CA file which is not referenced by the configuration!\n",
2638 err ? err : "");
2639 errcode |= ERR_ALERT | ERR_FATAL;
2640 goto end;
2641 }
2642
Willy Tarreaua37693f2022-05-04 20:12:55 +02002643 if (!ctx->path) {
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002644 /* this is a new transaction, set the path of the transaction */
Willy Tarreaua37693f2022-05-04 20:12:55 +02002645 ctx->path = strdup(ctx->old_cafile_entry->path);
2646 if (!ctx->path) {
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002647 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2648 errcode |= ERR_ALERT | ERR_FATAL;
2649 goto end;
2650 }
2651 }
2652
Willy Tarreaua37693f2022-05-04 20:12:55 +02002653 if (ctx->new_cafile_entry)
2654 ssl_store_delete_cafile_entry(ctx->new_cafile_entry);
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002655
2656 /* Create a new cafile_entry without adding it to the cafile tree. */
Willy Tarreaua37693f2022-05-04 20:12:55 +02002657 ctx->new_cafile_entry = ssl_store_create_cafile_entry(ctx->path, NULL, CAFILE_CERT);
2658 if (!ctx->new_cafile_entry) {
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002659 memprintf(&err, "%sCannot allocate memory!\n",
2660 err ? err : "");
2661 errcode |= ERR_ALERT | ERR_FATAL;
2662 goto end;
2663 }
2664
2665 /* Fill the new entry with the new CAs. */
Willy Tarreaua37693f2022-05-04 20:12:55 +02002666 if (ssl_store_load_ca_from_buf(ctx->new_cafile_entry, payload)) {
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002667 memprintf(&err, "%sInvalid payload\n", err ? err : "");
2668 errcode |= ERR_ALERT | ERR_FATAL;
2669 goto end;
2670 }
2671
2672 /* we succeed, we can save the ca in the transaction */
2673
2674 /* if there wasn't a transaction, update the old CA */
2675 if (!cafile_transaction.old_cafile_entry) {
Willy Tarreaua37693f2022-05-04 20:12:55 +02002676 cafile_transaction.old_cafile_entry = ctx->old_cafile_entry;
2677 cafile_transaction.path = ctx->path;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002678 err = memprintf(&err, "transaction created for CA %s!\n", cafile_transaction.path);
2679 } else {
2680 err = memprintf(&err, "transaction updated for CA %s!\n", cafile_transaction.path);
2681 }
2682
2683 /* free the previous CA if there was a transaction */
2684 ssl_store_delete_cafile_entry(cafile_transaction.new_cafile_entry);
2685
Willy Tarreaua37693f2022-05-04 20:12:55 +02002686 cafile_transaction.new_cafile_entry = ctx->new_cafile_entry;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002687
2688 /* creates the SNI ctxs later in the IO handler */
2689
2690end:
2691 free_trash_chunk(buf);
2692
2693 if (errcode & ERR_CODE) {
Willy Tarreaua37693f2022-05-04 20:12:55 +02002694 ssl_store_delete_cafile_entry(ctx->new_cafile_entry);
2695 ctx->new_cafile_entry = NULL;
2696 ctx->old_cafile_entry = NULL;
2697 ha_free(&ctx->path);
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002698 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2699 return cli_dynerr(appctx, memprintf(&err, "%sCan't update %s!\n", err ? err : "", args[3]));
2700 } else {
2701
2702 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2703 return cli_dynmsg(appctx, LOG_NOTICE, err);
2704 }
2705}
2706
2707
2708/*
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002709 * Parsing function of 'commit ssl ca-file'.
2710 * It uses a commit_cacrlfile_ctx that's also shared with "commit ssl crl-file".
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002711 */
2712static int cli_parse_commit_cafile(char **args, char *payload, struct appctx *appctx, void *private)
2713{
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002714 struct commit_cacrlfile_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002715 char *err = NULL;
2716
2717 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2718 return 1;
2719
2720 if (!*args[3])
2721 return cli_err(appctx, "'commit ssl ca-file expects a filename\n");
2722
2723 /* The operations on the CKCH architecture are locked so we can
2724 * manipulate ckch_store and ckch_inst */
2725 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2726 return cli_err(appctx, "Can't commit the CA file!\nOperations on certificates are currently locked!\n");
2727
2728 if (!cafile_transaction.path) {
2729 memprintf(&err, "No ongoing transaction! !\n");
2730 goto error;
2731 }
2732
2733 if (strcmp(cafile_transaction.path, args[3]) != 0) {
2734 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", cafile_transaction.path, args[3]);
2735 goto error;
2736 }
2737 /* init the appctx structure */
Willy Tarreau1d6dd802022-05-05 08:17:29 +02002738 ctx->state = CACRL_ST_INIT;
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002739 ctx->next_ckchi_link = NULL;
2740 ctx->old_cafile_entry = cafile_transaction.old_cafile_entry;
2741 ctx->new_cafile_entry = cafile_transaction.new_cafile_entry;
2742 ctx->cafile_type = CAFILE_CERT;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002743
2744 return 0;
2745
2746error:
2747
2748 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2749 err = memprintf(&err, "%sCan't commit %s!\n", err ? err : "", args[3]);
2750
2751 return cli_dynerr(appctx, err);
2752}
2753
2754enum {
2755 CREATE_NEW_INST_OK = 0,
2756 CREATE_NEW_INST_YIELD = -1,
2757 CREATE_NEW_INST_ERR = -2
2758};
2759
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002760/* this is called by the I/O handler for "commit cafile"/"commit crlfile", and
2761 * it uses a context from cmomit_cacrlfile_ctx.
2762 */
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002763static inline int __create_new_instance(struct appctx *appctx, struct ckch_inst *ckchi, int *count,
Remi Tricot-Le Bretond75b99e2021-05-17 11:45:55 +02002764 struct buffer *trash, char **err)
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002765{
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002766 struct commit_cacrlfile_ctx *ctx = appctx->svcctx;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002767 struct ckch_inst *new_inst;
2768
2769 /* it takes a lot of CPU to creates SSL_CTXs, so we yield every 10 CKCH instances */
2770 if (*count >= 10) {
2771 /* save the next ckchi to compute */
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002772 ctx->next_ckchi = ckchi;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002773 return CREATE_NEW_INST_YIELD;
2774 }
2775
2776 /* Rebuild a new ckch instance that uses the same ckch_store
2777 * than a reference ckchi instance but will use a new CA file. */
Remi Tricot-Le Bretond75b99e2021-05-17 11:45:55 +02002778 if (ckch_inst_rebuild(ckchi->ckch_store, ckchi, &new_inst, err))
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002779 return CREATE_NEW_INST_ERR;
2780
2781 /* display one dot per new instance */
2782 chunk_appendf(trash, ".");
2783 ++(*count);
2784
2785 return CREATE_NEW_INST_OK;
2786}
2787
2788/*
2789 * This function tries to create new ckch instances and their SNIs using a newly
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002790 * set certificate authority (CA file) or a newly set Certificate Revocation
2791 * List (CRL), depending on the command being called.
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002792 */
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002793static int cli_io_handler_commit_cafile_crlfile(struct appctx *appctx)
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002794{
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002795 struct commit_cacrlfile_ctx *ctx = appctx->svcctx;
Christopher Faulet908628c2022-03-25 16:43:49 +01002796 struct conn_stream *cs = appctx->owner;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002797 int y = 0;
2798 char *err = NULL;
Remi Tricot-Le Bretona6b27842021-05-18 10:06:00 +02002799 struct cafile_entry *old_cafile_entry = NULL, *new_cafile_entry = NULL;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002800 struct ckch_inst_link *ckchi_link;
2801 struct buffer *trash = alloc_trash_chunk();
2802
2803 if (trash == NULL)
2804 goto error;
2805
Christopher Faulet908628c2022-03-25 16:43:49 +01002806 if (unlikely(cs_ic(cs)->flags & (CF_WRITE_ERROR|CF_SHUTW)))
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002807 goto error;
2808
2809 while (1) {
Willy Tarreau1d6dd802022-05-05 08:17:29 +02002810 switch (ctx->state) {
2811 case CACRL_ST_INIT:
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002812 /* This state just print the update message */
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002813 switch (ctx->cafile_type) {
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002814 case CAFILE_CERT:
2815 chunk_printf(trash, "Committing %s", cafile_transaction.path);
2816 break;
2817 case CAFILE_CRL:
2818 chunk_printf(trash, "Committing %s", crlfile_transaction.path);
2819 break;
2820 default:
2821 goto error;
2822 }
Christopher Faulet908628c2022-03-25 16:43:49 +01002823 if (ci_putchk(cs_ic(cs), trash) == -1) {
Christopher Fauleta0bdec32022-04-04 07:51:21 +02002824 cs_rx_room_blk(cs);
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002825 goto yield;
2826 }
Willy Tarreau1d6dd802022-05-05 08:17:29 +02002827 ctx->state = CACRL_ST_GEN;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002828 /* fallthrough */
Willy Tarreau1d6dd802022-05-05 08:17:29 +02002829 case CACRL_ST_GEN:
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002830 /*
2831 * This state generates the ckch instances with their
2832 * sni_ctxs and SSL_CTX.
2833 *
2834 * Since the SSL_CTX generation can be CPU consumer, we
2835 * yield every 10 instances.
2836 */
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002837 switch (ctx->cafile_type) {
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002838 case CAFILE_CERT:
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002839 old_cafile_entry = ctx->old_cafile_entry;
2840 new_cafile_entry = ctx->new_cafile_entry;
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002841 break;
2842 case CAFILE_CRL:
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002843 old_cafile_entry = ctx->old_crlfile_entry;
2844 new_cafile_entry = ctx->new_crlfile_entry;
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002845 break;
2846 }
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002847 if (!new_cafile_entry)
2848 continue;
2849
2850 /* get the next ckchi to regenerate */
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002851 ckchi_link = ctx->next_ckchi_link;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002852 /* we didn't start yet, set it to the first elem */
2853 if (ckchi_link == NULL) {
2854 ckchi_link = LIST_ELEM(old_cafile_entry->ckch_inst_link.n, typeof(ckchi_link), list);
2855 /* Add the newly created cafile_entry to the tree so that
2856 * any new ckch instance created from now can use it. */
2857 if (ssl_store_add_uncommitted_cafile_entry(new_cafile_entry))
2858 goto error;
2859 }
2860
2861 list_for_each_entry_from(ckchi_link, &old_cafile_entry->ckch_inst_link, list) {
Remi Tricot-Le Bretond75b99e2021-05-17 11:45:55 +02002862 switch (__create_new_instance(appctx, ckchi_link->ckch_inst, &y, trash, &err)) {
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002863 case CREATE_NEW_INST_YIELD:
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002864 ctx->next_ckchi_link = ckchi_link;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002865 goto yield;
2866 case CREATE_NEW_INST_ERR:
2867 goto error;
2868 default: break;
2869 }
2870 }
2871
Willy Tarreau1d6dd802022-05-05 08:17:29 +02002872 ctx->state = CACRL_ST_INSERT;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002873 /* fallthrough */
Willy Tarreau1d6dd802022-05-05 08:17:29 +02002874 case CACRL_ST_INSERT:
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002875 /* The generation is finished, we can insert everything */
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002876 switch (ctx->cafile_type) {
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002877 case CAFILE_CERT:
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002878 old_cafile_entry = ctx->old_cafile_entry;
2879 new_cafile_entry = ctx->new_cafile_entry;
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002880 break;
2881 case CAFILE_CRL:
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002882 old_cafile_entry = ctx->old_crlfile_entry;
2883 new_cafile_entry = ctx->new_crlfile_entry;
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002884 break;
2885 }
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002886 if (!new_cafile_entry)
2887 continue;
2888
2889 /* insert the new ckch_insts in the crtlist_entry */
2890 list_for_each_entry(ckchi_link, &new_cafile_entry->ckch_inst_link, list) {
2891 if (ckchi_link->ckch_inst->crtlist_entry)
2892 LIST_INSERT(&ckchi_link->ckch_inst->crtlist_entry->ckch_inst,
2893 &ckchi_link->ckch_inst->by_crtlist_entry);
2894 }
2895
2896 /* First, we insert every new SNIs in the trees, also replace the default_ctx */
2897 list_for_each_entry(ckchi_link, &new_cafile_entry->ckch_inst_link, list) {
2898 __ssl_sock_load_new_ckch_instance(ckchi_link->ckch_inst);
2899 }
2900
2901 /* delete the old sni_ctx, the old ckch_insts and the ckch_store */
2902 list_for_each_entry(ckchi_link, &old_cafile_entry->ckch_inst_link, list) {
2903 __ckch_inst_free_locked(ckchi_link->ckch_inst);
2904 }
2905
2906
2907 /* Remove the old cafile entry from the tree */
2908 ebmb_delete(&old_cafile_entry->node);
2909 ssl_store_delete_cafile_entry(old_cafile_entry);
2910
Willy Tarreau1d6dd802022-05-05 08:17:29 +02002911 ctx->state = CACRL_ST_FIN;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002912 /* fallthrough */
Willy Tarreau1d6dd802022-05-05 08:17:29 +02002913 case CACRL_ST_FIN:
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002914 /* we achieved the transaction, we can set everything to NULL */
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002915 switch (ctx->cafile_type) {
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002916 case CAFILE_CERT:
2917 ha_free(&cafile_transaction.path);
2918 cafile_transaction.old_cafile_entry = NULL;
2919 cafile_transaction.new_cafile_entry = NULL;
2920 break;
2921 case CAFILE_CRL:
2922 ha_free(&crlfile_transaction.path);
2923 crlfile_transaction.old_crlfile_entry = NULL;
2924 crlfile_transaction.new_crlfile_entry = NULL;
2925 break;
2926 }
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002927 goto end;
2928 }
2929 }
2930end:
2931
2932 chunk_appendf(trash, "\n");
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002933 chunk_appendf(trash, "Success!\n");
Christopher Faulet908628c2022-03-25 16:43:49 +01002934 if (ci_putchk(cs_ic(cs), trash) == -1)
Christopher Fauleta0bdec32022-04-04 07:51:21 +02002935 cs_rx_room_blk(cs);
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002936 free_trash_chunk(trash);
2937 /* success: call the release function and don't come back */
2938 return 1;
2939yield:
2940 /* store the state */
Christopher Faulet908628c2022-03-25 16:43:49 +01002941 if (ci_putchk(cs_ic(cs), trash) == -1)
Christopher Fauleta0bdec32022-04-04 07:51:21 +02002942 cs_rx_room_blk(cs);
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002943 free_trash_chunk(trash);
Christopher Fauleta0bdec32022-04-04 07:51:21 +02002944 cs_rx_endp_more(cs); /* let's come back later */
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002945 return 0; /* should come back */
2946
2947error:
2948 /* spin unlock and free are done in the release function */
2949 if (trash) {
2950 chunk_appendf(trash, "\n%sFailed!\n", err);
Christopher Faulet908628c2022-03-25 16:43:49 +01002951 if (ci_putchk(cs_ic(cs), trash) == -1)
Christopher Fauleta0bdec32022-04-04 07:51:21 +02002952 cs_rx_room_blk(cs);
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002953 free_trash_chunk(trash);
2954 }
2955 /* error: call the release function and don't come back */
2956 return 1;
2957}
2958
Remi Tricot-Le Bretond5fd09d2021-03-11 10:22:52 +01002959
2960/* parsing function of 'abort ssl ca-file' */
2961static int cli_parse_abort_cafile(char **args, char *payload, struct appctx *appctx, void *private)
2962{
2963 char *err = NULL;
2964
2965 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2966 return 1;
2967
2968 if (!*args[3])
2969 return cli_err(appctx, "'abort ssl ca-file' expects a filename\n");
2970
2971 /* The operations on the CKCH architecture are locked so we can
2972 * manipulate ckch_store and ckch_inst */
2973 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2974 return cli_err(appctx, "Can't abort!\nOperations on certificates are currently locked!\n");
2975
2976 if (!cafile_transaction.path) {
2977 memprintf(&err, "No ongoing transaction!\n");
2978 goto error;
2979 }
2980
2981 if (strcmp(cafile_transaction.path, args[3]) != 0) {
2982 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to abort a transaction for '%s'\n", cafile_transaction.path, args[3]);
2983 goto error;
2984 }
2985
2986 /* Only free the uncommitted cafile_entry here, because the SNI and instances were not generated yet */
2987 ssl_store_delete_cafile_entry(cafile_transaction.new_cafile_entry);
2988 cafile_transaction.new_cafile_entry = NULL;
2989 cafile_transaction.old_cafile_entry = NULL;
2990 ha_free(&cafile_transaction.path);
2991
2992 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2993
2994 err = memprintf(&err, "Transaction aborted for certificate '%s'!\n", args[3]);
2995 return cli_dynmsg(appctx, LOG_NOTICE, err);
2996
2997error:
2998 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2999
3000 return cli_dynerr(appctx, err);
3001}
3002
Willy Tarreau821c3b02022-05-04 15:47:39 +02003003/* release function of the `commit ssl ca-file' command, free things and unlock the spinlock.
Willy Tarreaudec23dc2022-05-04 20:25:05 +02003004 * It uses a commit_cacrlfile_ctx context.
Willy Tarreau821c3b02022-05-04 15:47:39 +02003005 */
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01003006static void cli_release_commit_cafile(struct appctx *appctx)
3007{
Willy Tarreaudec23dc2022-05-04 20:25:05 +02003008 struct commit_cacrlfile_ctx *ctx = appctx->svcctx;
3009
Willy Tarreau1d6dd802022-05-05 08:17:29 +02003010 if (ctx->state != CACRL_ST_FIN) {
Willy Tarreaudec23dc2022-05-04 20:25:05 +02003011 struct cafile_entry *new_cafile_entry = ctx->new_cafile_entry;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01003012
3013 /* Remove the uncommitted cafile_entry from the tree. */
3014 ebmb_delete(&new_cafile_entry->node);
3015 ssl_store_delete_cafile_entry(new_cafile_entry);
3016 }
3017 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3018}
3019
3020
Willy Tarreau821c3b02022-05-04 15:47:39 +02003021/* IO handler of details "show ssl ca-file <filename[:index]>".
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003022 * It uses a show_cafile_ctx context, and the global
3023 * cafile_transaction.new_cafile_entry in read-only.
Willy Tarreau821c3b02022-05-04 15:47:39 +02003024 */
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003025static int cli_io_handler_show_cafile_detail(struct appctx *appctx)
3026{
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003027 struct show_cafile_ctx *ctx = appctx->svcctx;
Christopher Faulet908628c2022-03-25 16:43:49 +01003028 struct conn_stream *cs = appctx->owner;
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003029 struct cafile_entry *cafile_entry = ctx->cur_cafile_entry;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003030 struct buffer *out = alloc_trash_chunk();
William Lallemand03a32e52022-04-26 18:17:15 +02003031 int i = 0;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003032 X509 *cert;
3033 STACK_OF(X509_OBJECT) *objs;
3034 int retval = 0;
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003035 int ca_index = ctx->ca_index;
3036 int show_all = ctx->show_all;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003037
3038 if (!out)
3039 goto end_no_putchk;
3040
3041 chunk_appendf(out, "Filename: ");
3042 if (cafile_entry == cafile_transaction.new_cafile_entry)
3043 chunk_appendf(out, "*");
3044 chunk_appendf(out, "%s\n", cafile_entry->path);
3045
3046 chunk_appendf(out, "Status: ");
3047 if (!cafile_entry->ca_store)
3048 chunk_appendf(out, "Empty\n");
3049 else if (LIST_ISEMPTY(&cafile_entry->ckch_inst_link))
3050 chunk_appendf(out, "Unused\n");
3051 else
3052 chunk_appendf(out, "Used\n");
3053
3054 if (!cafile_entry->ca_store)
3055 goto end;
3056
3057 objs = X509_STORE_get0_objects(cafile_entry->ca_store);
William Lallemand03a32e52022-04-26 18:17:15 +02003058 for (i = ca_index; i < sk_X509_OBJECT_num(objs); i++) {
3059
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003060 cert = X509_OBJECT_get0_X509(sk_X509_OBJECT_value(objs, i));
3061 if (!cert)
3062 continue;
3063
William Lallemand03a32e52022-04-26 18:17:15 +02003064 /* file starts at line 1 */
Remi Tricot-Le Bretone8041fe2022-04-05 16:44:21 +02003065 chunk_appendf(out, " \nCertificate #%d:\n", i+1);
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003066 retval = show_cert_detail(cert, NULL, out);
3067 if (retval < 0)
3068 goto end_no_putchk;
William Lallemand03a32e52022-04-26 18:17:15 +02003069 else if (retval)
3070 goto yield;
3071
3072 if (ci_putchk(cs_ic(cs), out) == -1) {
3073 cs_rx_room_blk(cs);
3074 goto yield;
3075 }
3076
3077 if (!show_all) /* only need to dump one certificate */
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003078 goto end;
3079 }
3080
3081end:
William Lallemand03a32e52022-04-26 18:17:15 +02003082 free_trash_chunk(out);
3083 return 1; /* end, don't come back */
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003084
3085end_no_putchk:
3086 free_trash_chunk(out);
3087 return 1;
3088yield:
William Lallemand03a32e52022-04-26 18:17:15 +02003089 /* save the current state */
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003090 ctx->ca_index = i;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003091 free_trash_chunk(out);
3092 return 0; /* should come back */
3093}
3094
3095
Willy Tarreau06305792022-05-04 15:57:30 +02003096/* parsing function for 'show ssl ca-file [cafile[:index]]'.
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003097 * It prepares a show_cafile_ctx context, and checks the global
3098 * cafile_transaction under the ckch_lock (read only).
Willy Tarreau06305792022-05-04 15:57:30 +02003099 */
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003100static int cli_parse_show_cafile(char **args, char *payload, struct appctx *appctx, void *private)
3101{
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003102 struct show_cafile_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003103 struct cafile_entry *cafile_entry;
William Lallemand03a32e52022-04-26 18:17:15 +02003104 int ca_index = 0;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003105 char *colons;
3106 char *err = NULL;
3107
3108 if (!cli_has_level(appctx, ACCESS_LVL_OPER))
3109 return cli_err(appctx, "Can't allocate memory!\n");
3110
3111 /* The operations on the CKCH architecture are locked so we can
3112 * manipulate ckch_store and ckch_inst */
3113 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3114 return cli_err(appctx, "Can't show!\nOperations on certificates are currently locked!\n");
3115
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003116 ctx->show_all = 1; /* show all certificates */
3117 ctx->ca_index = 0;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003118 /* check if there is a certificate to lookup */
3119 if (*args[3]) {
3120
3121 /* Look for an optional CA index after the CA file name */
3122 colons = strchr(args[3], ':');
3123 if (colons) {
3124 char *endptr;
3125
3126 ca_index = strtol(colons + 1, &endptr, 10);
3127 /* Indexes start at 1 */
3128 if (colons + 1 == endptr || *endptr != '\0' || ca_index <= 0) {
3129 memprintf(&err, "wrong CA index after colons in '%s'!", args[3]);
3130 goto error;
3131 }
3132 *colons = '\0';
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003133 ctx->ca_index = ca_index - 1; /* we start counting at 0 in the ca_store, but at 1 on the CLI */
3134 ctx->show_all = 0; /* show only one certificate */
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003135 }
3136
3137 if (*args[3] == '*') {
3138 if (!cafile_transaction.new_cafile_entry)
3139 goto error;
3140
3141 cafile_entry = cafile_transaction.new_cafile_entry;
3142
3143 if (strcmp(args[3] + 1, cafile_entry->path) != 0)
3144 goto error;
3145
3146 } else {
3147 /* Get the "original" cafile_entry and not the
3148 * uncommitted one if it exists. */
3149 if ((cafile_entry = ssl_store_get_cafile_entry(args[3], 1)) == NULL || cafile_entry->type != CAFILE_CERT)
3150 goto error;
3151 }
3152
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003153 ctx->cur_cafile_entry = cafile_entry;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003154 /* use the IO handler that shows details */
3155 appctx->io_handler = cli_io_handler_show_cafile_detail;
3156 }
3157
3158 return 0;
3159
3160error:
3161 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3162 if (err)
3163 return cli_dynerr(appctx, err);
3164 return cli_err(appctx, "Can't display the CA file : Not found!\n");
3165}
3166
3167
3168/* release function of the 'show ssl ca-file' command */
3169static void cli_release_show_cafile(struct appctx *appctx)
3170{
3171 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3172}
3173
3174
3175/* This function returns the number of certificates in a cafile_entry. */
3176static int get_certificate_count(struct cafile_entry *cafile_entry)
3177{
3178 int cert_count = 0;
3179 STACK_OF(X509_OBJECT) *objs;
3180
3181 if (cafile_entry && cafile_entry->ca_store) {
3182 objs = X509_STORE_get0_objects(cafile_entry->ca_store);
3183 if (objs)
3184 cert_count = sk_X509_OBJECT_num(objs);
3185 }
3186 return cert_count;
3187}
3188
3189/* IO handler of "show ssl ca-file". The command taking a specific CA file name
Willy Tarreau821c3b02022-05-04 15:47:39 +02003190 * is managed in cli_io_handler_show_cafile_detail.
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003191 * It uses a show_cafile_ctx and the global cafile_transaction.new_cafile_entry
3192 * in read-only.
Willy Tarreau821c3b02022-05-04 15:47:39 +02003193 */
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003194static int cli_io_handler_show_cafile(struct appctx *appctx)
3195{
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003196 struct show_cafile_ctx *ctx = appctx->svcctx;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003197 struct buffer *trash = alloc_trash_chunk();
3198 struct ebmb_node *node;
Christopher Faulet908628c2022-03-25 16:43:49 +01003199 struct conn_stream *cs = appctx->owner;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003200 struct cafile_entry *cafile_entry;
3201
3202 if (trash == NULL)
3203 return 1;
3204
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003205 if (!ctx->old_cafile_entry) {
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003206 if (cafile_transaction.old_cafile_entry) {
3207 chunk_appendf(trash, "# transaction\n");
3208 chunk_appendf(trash, "*%s", cafile_transaction.old_cafile_entry->path);
3209
3210 chunk_appendf(trash, " - %d certificate(s)\n", get_certificate_count(cafile_transaction.new_cafile_entry));
3211 }
3212 }
3213
3214 /* First time in this io_handler. */
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003215 if (!ctx->cur_cafile_entry) {
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003216 chunk_appendf(trash, "# filename\n");
3217 node = ebmb_first(&cafile_tree);
3218 } else {
3219 /* We yielded during a previous call. */
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003220 node = &ctx->cur_cafile_entry->node;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003221 }
3222
3223 while (node) {
3224 cafile_entry = ebmb_entry(node, struct cafile_entry, node);
3225 if (cafile_entry->type == CAFILE_CERT) {
3226 chunk_appendf(trash, "%s", cafile_entry->path);
3227
3228 chunk_appendf(trash, " - %d certificate(s)\n", get_certificate_count(cafile_entry));
3229 }
3230
3231 node = ebmb_next(node);
Christopher Faulet908628c2022-03-25 16:43:49 +01003232 if (ci_putchk(cs_ic(cs), trash) == -1) {
Christopher Fauleta0bdec32022-04-04 07:51:21 +02003233 cs_rx_room_blk(cs);
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003234 goto yield;
3235 }
3236 }
3237
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003238 ctx->cur_cafile_entry = NULL;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003239 free_trash_chunk(trash);
3240 return 1;
3241yield:
3242
3243 free_trash_chunk(trash);
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003244 ctx->cur_cafile_entry = cafile_entry;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003245 return 0; /* should come back */
3246}
3247
Remi Tricot-Le Bretonc3a84772021-03-25 18:13:57 +01003248/* parsing function of 'del ssl ca-file' */
3249static int cli_parse_del_cafile(char **args, char *payload, struct appctx *appctx, void *private)
3250{
3251 struct cafile_entry *cafile_entry;
3252 char *err = NULL;
3253 char *filename;
3254
3255 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
3256 return 1;
3257
3258 if (!*args[3])
3259 return cli_err(appctx, "'del ssl ca-file' expects a CA file name\n");
3260
3261 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3262 return cli_err(appctx, "Can't delete the CA file!\nOperations on certificates are currently locked!\n");
3263
3264 filename = args[3];
3265
3266 cafile_entry = ssl_store_get_cafile_entry(filename, 0);
3267 if (!cafile_entry) {
3268 memprintf(&err, "CA file '%s' doesn't exist!\n", filename);
3269 goto error;
3270 }
3271
3272 if (!LIST_ISEMPTY(&cafile_entry->ckch_inst_link)) {
3273 memprintf(&err, "CA file '%s' in use, can't be deleted!\n", filename);
3274 goto error;
3275 }
3276
3277 /* Remove the cafile_entry from the tree */
3278 ebmb_delete(&cafile_entry->node);
3279 ssl_store_delete_cafile_entry(cafile_entry);
3280
3281 memprintf(&err, "CA file '%s' deleted!\n", filename);
3282
3283 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3284 return cli_dynmsg(appctx, LOG_NOTICE, err);
3285
3286error:
3287 memprintf(&err, "Can't remove the CA file: %s\n", err ? err : "");
3288 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3289 return cli_dynerr(appctx, err);
3290}
3291
Remi Tricot-Le Breton720e3b92021-04-26 11:00:42 +02003292/* parsing function of 'new ssl crl-file' */
3293static int cli_parse_new_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
3294{
3295 struct cafile_entry *cafile_entry;
3296 char *err = NULL;
3297 char *path;
3298
3299 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
3300 return 1;
3301
3302 if (!*args[3])
3303 return cli_err(appctx, "'new ssl crl-file' expects a filename\n");
3304
3305 path = args[3];
3306
3307 /* The operations on the CKCH architecture are locked so we can
3308 * manipulate ckch_store and ckch_inst */
3309 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
Remi Tricot-Le Breton444d7022022-05-05 17:18:40 +02003310 return cli_err(appctx, "Can't create a CRL file!\nOperations on certificates are currently locked!\n");
Remi Tricot-Le Breton720e3b92021-04-26 11:00:42 +02003311
3312 cafile_entry = ssl_store_get_cafile_entry(path, 0);
3313 if (cafile_entry) {
3314 memprintf(&err, "CRL file '%s' already exists!\n", path);
3315 goto error;
3316 }
3317
3318 cafile_entry = ssl_store_create_cafile_entry(path, NULL, CAFILE_CRL);
3319 if (!cafile_entry) {
3320 memprintf(&err, "%sCannot allocate memory!\n", err ? err : "");
3321 goto error;
3322 }
3323
3324 /* Add the newly created cafile_entry to the tree so that
3325 * any new ckch instance created from now can use it. */
3326 if (ssl_store_add_uncommitted_cafile_entry(cafile_entry))
3327 goto error;
3328
3329 memprintf(&err, "New CRL file created '%s'!\n", path);
3330
3331 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3332 return cli_dynmsg(appctx, LOG_NOTICE, err);
3333error:
3334 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3335 return cli_dynerr(appctx, err);
3336}
3337
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003338/* Parsing function of `set ssl crl-file` */
3339static int cli_parse_set_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
3340{
Willy Tarreaua06b9a52022-05-04 20:33:03 +02003341 struct set_crlfile_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003342 char *err = NULL;
3343 int errcode = 0;
3344 struct buffer *buf;
3345
3346 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
3347 return 1;
3348
3349 if (!*args[3] || !payload)
Remi Tricot-Le Breton444d7022022-05-05 17:18:40 +02003350 return cli_err(appctx, "'set ssl crl-file expects a filename and CRLs as a payload\n");
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003351
3352 /* The operations on the CKCH architecture are locked so we can
3353 * manipulate ckch_store and ckch_inst */
3354 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3355 return cli_err(appctx, "Can't update the CRL file!\nOperations on certificates are currently locked!\n");
3356
3357 if ((buf = alloc_trash_chunk()) == NULL) {
3358 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
3359 errcode |= ERR_ALERT | ERR_FATAL;
3360 goto end;
3361 }
3362
3363 if (!chunk_strcpy(buf, args[3])) {
3364 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
3365 errcode |= ERR_ALERT | ERR_FATAL;
3366 goto end;
3367 }
3368
Willy Tarreaua06b9a52022-05-04 20:33:03 +02003369 ctx->old_crlfile_entry = NULL;
3370 ctx->new_crlfile_entry = NULL;
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003371
3372 /* if there is an ongoing transaction */
3373 if (crlfile_transaction.path) {
3374 /* if there is an ongoing transaction, check if this is the same file */
3375 if (strcmp(crlfile_transaction.path, buf->area) != 0) {
3376 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", crlfile_transaction.path, buf->area);
3377 errcode |= ERR_ALERT | ERR_FATAL;
3378 goto end;
3379 }
Willy Tarreaua06b9a52022-05-04 20:33:03 +02003380 ctx->old_crlfile_entry = crlfile_transaction.old_crlfile_entry;
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003381 }
3382 else {
3383 /* lookup for the certificate in the tree */
Willy Tarreaua06b9a52022-05-04 20:33:03 +02003384 ctx->old_crlfile_entry = ssl_store_get_cafile_entry(buf->area, 0);
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003385 }
3386
Willy Tarreaua06b9a52022-05-04 20:33:03 +02003387 if (!ctx->old_crlfile_entry) {
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003388 memprintf(&err, "%sCan't replace a CRL file which is not referenced by the configuration!\n",
3389 err ? err : "");
3390 errcode |= ERR_ALERT | ERR_FATAL;
3391 goto end;
3392 }
3393
Willy Tarreaua06b9a52022-05-04 20:33:03 +02003394 if (!ctx->path) {
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003395 /* this is a new transaction, set the path of the transaction */
Willy Tarreaua06b9a52022-05-04 20:33:03 +02003396 ctx->path = strdup(ctx->old_crlfile_entry->path);
3397 if (!ctx->path) {
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003398 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
3399 errcode |= ERR_ALERT | ERR_FATAL;
3400 goto end;
3401 }
3402 }
3403
Willy Tarreaua06b9a52022-05-04 20:33:03 +02003404 if (ctx->new_crlfile_entry)
3405 ssl_store_delete_cafile_entry(ctx->new_crlfile_entry);
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003406
3407 /* Create a new cafile_entry without adding it to the cafile tree. */
Willy Tarreaua06b9a52022-05-04 20:33:03 +02003408 ctx->new_crlfile_entry = ssl_store_create_cafile_entry(ctx->path, NULL, CAFILE_CRL);
3409 if (!ctx->new_crlfile_entry) {
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003410 memprintf(&err, "%sCannot allocate memory!\n", err ? err : "");
3411 errcode |= ERR_ALERT | ERR_FATAL;
3412 goto end;
3413 }
3414
3415 /* Fill the new entry with the new CRL. */
Willy Tarreaua06b9a52022-05-04 20:33:03 +02003416 if (ssl_store_load_ca_from_buf(ctx->new_crlfile_entry, payload)) {
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003417 memprintf(&err, "%sInvalid payload\n", err ? err : "");
3418 errcode |= ERR_ALERT | ERR_FATAL;
3419 goto end;
3420 }
3421
3422 /* we succeed, we can save the crl in the transaction */
3423
Remi Tricot-Le Breton444d7022022-05-05 17:18:40 +02003424 /* if there wasn't a transaction, update the old CRL */
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003425 if (!crlfile_transaction.old_crlfile_entry) {
Willy Tarreaua06b9a52022-05-04 20:33:03 +02003426 crlfile_transaction.old_crlfile_entry = ctx->old_crlfile_entry;
3427 crlfile_transaction.path = ctx->path;
Remi Tricot-Le Breton444d7022022-05-05 17:18:40 +02003428 err = memprintf(&err, "transaction created for CRL %s!\n", crlfile_transaction.path);
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003429 } else {
Remi Tricot-Le Breton444d7022022-05-05 17:18:40 +02003430 err = memprintf(&err, "transaction updated for CRL %s!\n", crlfile_transaction.path);
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003431 }
3432
3433 /* free the previous CRL file if there was a transaction */
3434 ssl_store_delete_cafile_entry(crlfile_transaction.new_crlfile_entry);
3435
Willy Tarreaua06b9a52022-05-04 20:33:03 +02003436 crlfile_transaction.new_crlfile_entry = ctx->new_crlfile_entry;
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003437
3438 /* creates the SNI ctxs later in the IO handler */
3439
3440end:
3441 free_trash_chunk(buf);
3442
3443 if (errcode & ERR_CODE) {
Willy Tarreaua06b9a52022-05-04 20:33:03 +02003444 ssl_store_delete_cafile_entry(ctx->new_crlfile_entry);
3445 ctx->new_crlfile_entry = NULL;
3446 ctx->old_crlfile_entry = NULL;
3447 ha_free(&ctx->path);
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003448 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3449 return cli_dynerr(appctx, memprintf(&err, "%sCan't update %s!\n", err ? err : "", args[3]));
3450 } else {
3451
3452 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3453 return cli_dynmsg(appctx, LOG_NOTICE, err);
3454 }
3455}
3456
Willy Tarreaudec23dc2022-05-04 20:25:05 +02003457/* Parsing function of 'commit ssl crl-file'.
3458 * It uses a commit_cacrlfile_ctx that's also shared with "commit ssl ca-file".
3459 */
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003460static int cli_parse_commit_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
3461{
Willy Tarreaudec23dc2022-05-04 20:25:05 +02003462 struct commit_cacrlfile_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003463 char *err = NULL;
3464
3465 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
3466 return 1;
3467
3468 if (!*args[3])
3469 return cli_err(appctx, "'commit ssl ca-file expects a filename\n");
3470
3471 /* The operations on the CKCH architecture are locked so we can
3472 * manipulate ckch_store and ckch_inst */
3473 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3474 return cli_err(appctx, "Can't commit the CRL file!\nOperations on certificates are currently locked!\n");
3475
3476 if (!crlfile_transaction.path) {
3477 memprintf(&err, "No ongoing transaction! !\n");
3478 goto error;
3479 }
3480
3481 if (strcmp(crlfile_transaction.path, args[3]) != 0) {
3482 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", crlfile_transaction.path, args[3]);
3483 goto error;
3484 }
3485 /* init the appctx structure */
Willy Tarreau1d6dd802022-05-05 08:17:29 +02003486 ctx->state = CACRL_ST_INIT;
Willy Tarreaudec23dc2022-05-04 20:25:05 +02003487 ctx->next_ckchi = NULL;
3488 ctx->old_crlfile_entry = crlfile_transaction.old_crlfile_entry;
3489 ctx->new_crlfile_entry = crlfile_transaction.new_crlfile_entry;
3490 ctx->cafile_type = CAFILE_CRL;
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003491
3492 return 0;
3493
3494error:
3495
3496 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3497 err = memprintf(&err, "%sCan't commit %s!\n", err ? err : "", args[3]);
3498
3499 return cli_dynerr(appctx, err);
3500}
3501
3502
Willy Tarreaudec23dc2022-05-04 20:25:05 +02003503/* release function of the `commit ssl crl-file' command, free things and unlock the spinlock.
3504 * it uses a commit_cacrlfile_ctx that's the same as for "commit ssl ca-file".
3505 */
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003506static void cli_release_commit_crlfile(struct appctx *appctx)
3507{
Willy Tarreaudec23dc2022-05-04 20:25:05 +02003508 struct commit_cacrlfile_ctx *ctx = appctx->svcctx;
3509
Willy Tarreau1d6dd802022-05-05 08:17:29 +02003510 if (ctx->state != CACRL_ST_FIN) {
Willy Tarreaudec23dc2022-05-04 20:25:05 +02003511 struct cafile_entry *new_crlfile_entry = ctx->new_crlfile_entry;
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003512
3513 /* Remove the uncommitted cafile_entry from the tree. */
3514 ebmb_delete(&new_crlfile_entry->node);
3515 ssl_store_delete_cafile_entry(new_crlfile_entry);
3516 }
3517 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3518}
3519
Remi Tricot-Le Breton720e3b92021-04-26 11:00:42 +02003520/* parsing function of 'del ssl crl-file' */
3521static int cli_parse_del_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
3522{
3523 struct cafile_entry *cafile_entry;
3524 char *err = NULL;
3525 char *filename;
3526
3527 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
3528 return 1;
3529
3530 if (!*args[3])
3531 return cli_err(appctx, "'del ssl crl-file' expects a CRL file name\n");
3532
3533 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3534 return cli_err(appctx, "Can't delete the CRL file!\nOperations on certificates are currently locked!\n");
3535
3536 filename = args[3];
3537
3538 cafile_entry = ssl_store_get_cafile_entry(filename, 0);
3539 if (!cafile_entry) {
3540 memprintf(&err, "CRL file '%s' doesn't exist!\n", filename);
3541 goto error;
3542 }
3543 if (cafile_entry->type != CAFILE_CRL) {
3544 memprintf(&err, "'del ssl crl-file' does not work on CA files!\n");
3545 goto error;
3546 }
3547
3548 if (!LIST_ISEMPTY(&cafile_entry->ckch_inst_link)) {
3549 memprintf(&err, "CRL file '%s' in use, can't be deleted!\n", filename);
3550 goto error;
3551 }
3552
3553 /* Remove the cafile_entry from the tree */
3554 ebmb_delete(&cafile_entry->node);
3555 ssl_store_delete_cafile_entry(cafile_entry);
3556
3557 memprintf(&err, "CRL file '%s' deleted!\n", filename);
3558
3559 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3560 return cli_dynmsg(appctx, LOG_NOTICE, err);
3561
3562error:
3563 memprintf(&err, "Can't remove the CRL file: %s\n", err ? err : "");
3564 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3565 return cli_dynerr(appctx, err);
3566}
3567
Remi Tricot-Le Bretoneef8e7b2021-04-20 17:42:02 +02003568/* parsing function of 'abort ssl crl-file' */
3569static int cli_parse_abort_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
3570{
3571 char *err = NULL;
3572
3573 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
3574 return 1;
3575
3576 if (!*args[3])
3577 return cli_err(appctx, "'abort ssl crl-file' expects a filename\n");
3578
3579 /* The operations on the CKCH architecture are locked so we can
3580 * manipulate ckch_store and ckch_inst */
3581 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3582 return cli_err(appctx, "Can't abort!\nOperations on certificates are currently locked!\n");
3583
3584 if (!crlfile_transaction.path) {
3585 memprintf(&err, "No ongoing transaction!\n");
3586 goto error;
3587 }
3588
3589 if (strcmp(crlfile_transaction.path, args[3]) != 0) {
3590 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to abort a transaction for '%s'\n", crlfile_transaction.path, args[3]);
3591 goto error;
3592 }
3593
3594 /* Only free the uncommitted cafile_entry here, because the SNI and instances were not generated yet */
3595 ssl_store_delete_cafile_entry(crlfile_transaction.new_crlfile_entry);
3596 crlfile_transaction.new_crlfile_entry = NULL;
3597 crlfile_transaction.old_crlfile_entry = NULL;
3598 ha_free(&crlfile_transaction.path);
3599
3600 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3601
3602 err = memprintf(&err, "Transaction aborted for certificate '%s'!\n", args[3]);
3603 return cli_dynmsg(appctx, LOG_NOTICE, err);
3604
3605error:
3606 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3607
3608 return cli_dynerr(appctx, err);
3609}
3610
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003611
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003612/*
3613 * Display a Certificate Resignation List's information.
3614 * The information displayed is inspired by the output of 'openssl crl -in
3615 * crl.pem -text'.
3616 * Returns 0 in case of success.
3617 */
3618static int show_crl_detail(X509_CRL *crl, struct buffer *out)
3619{
3620 BIO *bio = NULL;
3621 struct buffer *tmp = alloc_trash_chunk();
3622 long version;
3623 X509_NAME *issuer;
3624 int write = -1;
3625 STACK_OF(X509_REVOKED) *rev = NULL;
3626 X509_REVOKED *rev_entry = NULL;
3627 int i;
3628
3629 if (!tmp)
3630 return -1;
3631
3632 if ((bio = BIO_new(BIO_s_mem())) == NULL)
3633 goto end;
3634
3635 /* Version (as displayed by 'openssl crl') */
3636 version = X509_CRL_get_version(crl);
3637 chunk_appendf(out, "Version %ld\n", version + 1);
3638
3639 /* Signature Algorithm */
3640 chunk_appendf(out, "Signature Algorithm: %s\n", OBJ_nid2ln(X509_CRL_get_signature_nid(crl)));
3641
3642 /* Issuer */
3643 chunk_appendf(out, "Issuer: ");
3644 if ((issuer = X509_CRL_get_issuer(crl)) == NULL)
3645 goto end;
3646 if ((ssl_sock_get_dn_oneline(issuer, tmp)) == -1)
3647 goto end;
3648 *(tmp->area + tmp->data) = '\0';
3649 chunk_appendf(out, "%s\n", tmp->area);
3650
3651 /* Last Update */
3652 chunk_appendf(out, "Last Update: ");
3653 chunk_reset(tmp);
Remi Tricot-Le Bretond75b99e2021-05-17 11:45:55 +02003654 if (BIO_reset(bio) == -1)
3655 goto end;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003656 if (ASN1_TIME_print(bio, X509_CRL_get0_lastUpdate(crl)) == 0)
3657 goto end;
3658 write = BIO_read(bio, tmp->area, tmp->size-1);
3659 tmp->area[write] = '\0';
3660 chunk_appendf(out, "%s\n", tmp->area);
3661
3662
3663 /* Next Update */
3664 chunk_appendf(out, "Next Update: ");
3665 chunk_reset(tmp);
Remi Tricot-Le Bretond75b99e2021-05-17 11:45:55 +02003666 if (BIO_reset(bio) == -1)
3667 goto end;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003668 if (ASN1_TIME_print(bio, X509_CRL_get0_nextUpdate(crl)) == 0)
3669 goto end;
3670 write = BIO_read(bio, tmp->area, tmp->size-1);
3671 tmp->area[write] = '\0';
3672 chunk_appendf(out, "%s\n", tmp->area);
3673
3674
3675 /* Revoked Certificates */
3676 rev = X509_CRL_get_REVOKED(crl);
3677 if (sk_X509_REVOKED_num(rev) > 0)
3678 chunk_appendf(out, "Revoked Certificates:\n");
3679 else
3680 chunk_appendf(out, "No Revoked Certificates.\n");
3681
3682 for (i = 0; i < sk_X509_REVOKED_num(rev); i++) {
3683 rev_entry = sk_X509_REVOKED_value(rev, i);
3684
3685 /* Serial Number and Revocation Date */
Remi Tricot-Le Bretond75b99e2021-05-17 11:45:55 +02003686 if (BIO_reset(bio) == -1)
3687 goto end;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003688 BIO_printf(bio , " Serial Number: ");
Remi Tricot-Le Breton18c7d832021-05-17 18:38:34 +02003689 i2a_ASN1_INTEGER(bio, (ASN1_INTEGER*)X509_REVOKED_get0_serialNumber(rev_entry));
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003690 BIO_printf(bio, "\n Revocation Date: ");
Remi Tricot-Le Bretona6b27842021-05-18 10:06:00 +02003691 if (ASN1_TIME_print(bio, X509_REVOKED_get0_revocationDate(rev_entry)) == 0)
3692 goto end;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003693 BIO_printf(bio, "\n");
3694
3695 write = BIO_read(bio, tmp->area, tmp->size-1);
3696 tmp->area[write] = '\0';
3697 chunk_appendf(out, "%s", tmp->area);
3698 }
3699
3700end:
3701 free_trash_chunk(tmp);
3702 if (bio)
3703 BIO_free(bio);
3704
3705 return 0;
3706}
3707
Willy Tarreau821c3b02022-05-04 15:47:39 +02003708/* IO handler of details "show ssl crl-file <filename[:index]>".
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003709 * It uses show_crlfile_ctx and the global
3710 * crlfile_transaction.new_cafile_entry in read-only.
Willy Tarreau821c3b02022-05-04 15:47:39 +02003711 */
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003712static int cli_io_handler_show_crlfile_detail(struct appctx *appctx)
3713{
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003714 struct show_crlfile_ctx *ctx = appctx->svcctx;
Christopher Faulet908628c2022-03-25 16:43:49 +01003715 struct conn_stream *cs = appctx->owner;
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003716 struct cafile_entry *cafile_entry = ctx->cafile_entry;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003717 struct buffer *out = alloc_trash_chunk();
3718 int i;
3719 X509_CRL *crl;
3720 STACK_OF(X509_OBJECT) *objs;
3721 int retval = 0;
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003722 int index = ctx->index;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003723
3724 if (!out)
3725 goto end_no_putchk;
3726
3727 chunk_appendf(out, "Filename: ");
3728 if (cafile_entry == crlfile_transaction.new_crlfile_entry)
3729 chunk_appendf(out, "*");
3730 chunk_appendf(out, "%s\n", cafile_entry->path);
3731
3732 chunk_appendf(out, "Status: ");
3733 if (!cafile_entry->ca_store)
3734 chunk_appendf(out, "Empty\n");
3735 else if (LIST_ISEMPTY(&cafile_entry->ckch_inst_link))
3736 chunk_appendf(out, "Unused\n");
3737 else
3738 chunk_appendf(out, "Used\n");
3739
3740 if (!cafile_entry->ca_store)
3741 goto end;
3742
3743 objs = X509_STORE_get0_objects(cafile_entry->ca_store);
3744 for (i = 0; i < sk_X509_OBJECT_num(objs); i++) {
3745 crl = X509_OBJECT_get0_X509_CRL(sk_X509_OBJECT_value(objs, i));
3746 if (!crl)
3747 continue;
3748
3749 /* CRL indexes start at 1 on the CLI output. */
3750 if (index && index-1 != i)
3751 continue;
3752
Remi Tricot-Le Bretone8041fe2022-04-05 16:44:21 +02003753 chunk_appendf(out, " \nCertificate Revocation List #%d:\n", i+1);
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003754 retval = show_crl_detail(crl, out);
3755 if (retval < 0)
3756 goto end_no_putchk;
3757 else if (retval || index)
3758 goto end;
3759 }
3760
3761end:
Christopher Faulet908628c2022-03-25 16:43:49 +01003762 if (ci_putchk(cs_ic(cs), out) == -1) {
Christopher Fauleta0bdec32022-04-04 07:51:21 +02003763 cs_rx_room_blk(cs);
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003764 goto yield;
3765 }
3766
3767end_no_putchk:
3768 free_trash_chunk(out);
3769 return 1;
3770yield:
3771 free_trash_chunk(out);
3772 return 0; /* should come back */
3773}
3774
Willy Tarreau821c3b02022-05-04 15:47:39 +02003775/* parsing function for 'show ssl crl-file [crlfile[:index]]'.
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003776 * It sets the context to a show_crlfile_ctx, and the global
Willy Tarreau821c3b02022-05-04 15:47:39 +02003777 * cafile_transaction.new_crlfile_entry under the ckch_lock.
3778 */
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003779static int cli_parse_show_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
3780{
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003781 struct show_crlfile_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003782 struct cafile_entry *cafile_entry;
3783 long index = 0;
3784 char *colons;
3785 char *err = NULL;
3786
3787 if (!cli_has_level(appctx, ACCESS_LVL_OPER))
3788 return cli_err(appctx, "Can't allocate memory!\n");
3789
3790 /* The operations on the CKCH architecture are locked so we can
3791 * manipulate ckch_store and ckch_inst */
3792 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3793 return cli_err(appctx, "Can't show!\nOperations on certificates are currently locked!\n");
3794
3795 /* check if there is a certificate to lookup */
3796 if (*args[3]) {
3797
3798 /* Look for an optional index after the CRL file name */
3799 colons = strchr(args[3], ':');
3800 if (colons) {
3801 char *endptr;
3802
3803 index = strtol(colons + 1, &endptr, 10);
3804 /* Indexes start at 1 */
3805 if (colons + 1 == endptr || *endptr != '\0' || index <= 0) {
3806 memprintf(&err, "wrong CRL index after colons in '%s'!", args[3]);
3807 goto error;
3808 }
3809 *colons = '\0';
3810 }
3811
3812 if (*args[3] == '*') {
3813 if (!crlfile_transaction.new_crlfile_entry)
3814 goto error;
3815
3816 cafile_entry = crlfile_transaction.new_crlfile_entry;
3817
3818 if (strcmp(args[3] + 1, cafile_entry->path) != 0)
3819 goto error;
3820
3821 } else {
3822 /* Get the "original" cafile_entry and not the
3823 * uncommitted one if it exists. */
3824 if ((cafile_entry = ssl_store_get_cafile_entry(args[3], 1)) == NULL || cafile_entry->type != CAFILE_CRL)
3825 goto error;
3826 }
3827
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003828 ctx->cafile_entry = cafile_entry;
3829 ctx->index = index;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003830 /* use the IO handler that shows details */
3831 appctx->io_handler = cli_io_handler_show_crlfile_detail;
3832 }
3833
3834 return 0;
3835
3836error:
3837 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3838 if (err)
3839 return cli_dynerr(appctx, err);
Remi Tricot-Le Breton444d7022022-05-05 17:18:40 +02003840 return cli_err(appctx, "Can't display the CRL file : Not found!\n");
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003841}
3842
3843/* IO handler of "show ssl crl-file". The command taking a specific CRL file name
3844 * is managed in cli_io_handler_show_crlfile_detail. */
3845static int cli_io_handler_show_crlfile(struct appctx *appctx)
3846{
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003847 struct show_crlfile_ctx *ctx = appctx->svcctx;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003848 struct buffer *trash = alloc_trash_chunk();
3849 struct ebmb_node *node;
Christopher Faulet908628c2022-03-25 16:43:49 +01003850 struct conn_stream *cs = appctx->owner;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003851 struct cafile_entry *cafile_entry;
3852
3853 if (trash == NULL)
3854 return 1;
3855
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003856 if (!ctx->old_crlfile_entry) {
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003857 if (crlfile_transaction.old_crlfile_entry) {
3858 chunk_appendf(trash, "# transaction\n");
3859 chunk_appendf(trash, "*%s\n", crlfile_transaction.old_crlfile_entry->path);
3860 }
3861 }
3862
3863 /* First time in this io_handler. */
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003864 if (!ctx->cafile_entry) {
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003865 chunk_appendf(trash, "# filename\n");
3866 node = ebmb_first(&cafile_tree);
3867 } else {
3868 /* We yielded during a previous call. */
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003869 node = &ctx->cafile_entry->node;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003870 }
3871
3872 while (node) {
3873 cafile_entry = ebmb_entry(node, struct cafile_entry, node);
3874 if (cafile_entry->type == CAFILE_CRL) {
3875 chunk_appendf(trash, "%s\n", cafile_entry->path);
3876 }
3877
3878 node = ebmb_next(node);
Christopher Faulet908628c2022-03-25 16:43:49 +01003879 if (ci_putchk(cs_ic(cs), trash) == -1) {
Christopher Fauleta0bdec32022-04-04 07:51:21 +02003880 cs_rx_room_blk(cs);
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003881 goto yield;
3882 }
3883 }
3884
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003885 ctx->cafile_entry = NULL;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003886 free_trash_chunk(trash);
3887 return 1;
3888yield:
3889
3890 free_trash_chunk(trash);
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003891 ctx->cafile_entry = cafile_entry;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003892 return 0; /* should come back */
3893}
3894
3895
3896/* release function of the 'show ssl crl-file' command */
3897static void cli_release_show_crlfile(struct appctx *appctx)
3898{
3899 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3900}
3901
3902
William Lallemandee8530c2020-06-23 18:19:42 +02003903void ckch_deinit()
3904{
3905 struct eb_node *node, *next;
3906 struct ckch_store *store;
William Lallemandb0c48272022-04-26 15:44:53 +02003907 struct ebmb_node *canode;
William Lallemandee8530c2020-06-23 18:19:42 +02003908
William Lallemandb0c48272022-04-26 15:44:53 +02003909 /* deinit the ckch stores */
William Lallemandee8530c2020-06-23 18:19:42 +02003910 node = eb_first(&ckchs_tree);
3911 while (node) {
3912 next = eb_next(node);
3913 store = ebmb_entry(node, struct ckch_store, node);
3914 ckch_store_free(store);
3915 node = next;
3916 }
William Lallemandb0c48272022-04-26 15:44:53 +02003917
3918 /* deinit the ca-file store */
3919 canode = ebmb_first(&cafile_tree);
3920 while (canode) {
3921 struct cafile_entry *entry = NULL;
3922
3923 entry = ebmb_entry(canode, struct cafile_entry, node);
3924 canode = ebmb_next(canode);
3925 ssl_store_delete_cafile_entry(entry);
3926 }
William Lallemandee8530c2020-06-23 18:19:42 +02003927}
William Lallemandda8584c2020-05-14 10:14:37 +02003928
3929/* register cli keywords */
3930static struct cli_kw_list cli_kws = {{ },{
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01003931 { { "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 },
3932 { { "set", "ssl", "cert", NULL }, "set ssl cert <certfile> <payload> : replace a certificate file", cli_parse_set_cert, NULL, NULL },
3933 { { "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 },
3934 { { "abort", "ssl", "cert", NULL }, "abort ssl cert <certfile> : abort a transaction for a certificate file", cli_parse_abort_cert, NULL, NULL },
3935 { { "del", "ssl", "cert", NULL }, "del ssl cert <certfile> : delete an unused certificate file", cli_parse_del_cert, NULL, NULL },
3936 { { "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 },
3937
Amaury Denoyelleb11ad9e2021-05-21 11:01:10 +02003938 { { "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 +01003939 { { "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 +02003940 { { "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 +01003941 { { "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 +01003942 { { "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 +01003943 { { "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 +02003944
Remi Tricot-Le Breton720e3b92021-04-26 11:00:42 +02003945 { { "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 +02003946 { { "set", "ssl", "crl-file", NULL }, "set ssl crl-file <crlfile> <payload> : replace a CRL file", cli_parse_set_crlfile, NULL, NULL },
3947 { { "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 +02003948 { { "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 +02003949 { { "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 +02003950 { { "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 +02003951 { { NULL }, NULL, NULL, NULL }
3952}};
3953
3954INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws);
3955