blob: f574fc46afb46467431ce60ec2e44032fcd7138e [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>
Willy Tarreau8d366972020-05-27 16:10:29 +020033#include <haproxy/errors.h>
Willy Tarreau5edca2f2022-05-27 09:25:10 +020034#include <haproxy/sc_strm.h>
Willy Tarreau47d7f902020-06-04 14:25:47 +020035#include <haproxy/ssl_ckch.h>
Willy Tarreau209108d2020-06-04 20:30:20 +020036#include <haproxy/ssl_sock.h>
Willy Tarreaub2bd8652020-06-04 14:21:22 +020037#include <haproxy/ssl_utils.h>
Willy Tarreaucb086c62022-05-27 09:47:12 +020038#include <haproxy/stconn.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;
Remi Tricot-Le Breton9bf3a1f2022-05-09 11:07:13 +0200342 struct stat st;
William Lallemand03c331c2020-05-13 10:10:01 +0200343
344 /* try to load the PEM */
345 if (ssl_sock_load_pem_into_ckch(path, NULL, ckch , err) != 0) {
346 goto end;
347 }
348
William Lallemand8e8581e2020-10-20 17:36:46 +0200349 fp = alloc_trash_chunk();
350 if (!fp) {
351 memprintf(err, "%sCan't allocate memory\n", err && *err ? *err : "");
352 goto end;
353 }
354
355 if (!chunk_strcpy(fp, path) || (b_data(fp) > MAXPATHLEN)) {
356 memprintf(err, "%s '%s' filename too long'.\n",
357 err && *err ? *err : "", fp->area);
358 ret = 1;
359 goto end;
360 }
361
William Lallemand089c1382020-10-23 17:35:12 +0200362 /* remove the ".crt" extension */
William Lallemand8e8581e2020-10-20 17:36:46 +0200363 if (global_ssl.extra_files_noext) {
364 char *ext;
365
366 /* look for the extension */
367 if ((ext = strrchr(fp->area, '.'))) {
William Lallemand8e8581e2020-10-20 17:36:46 +0200368
Tim Duesterhuse5ff1412021-01-02 22:31:53 +0100369 if (strcmp(ext, ".crt") == 0) {
William Lallemand8e8581e2020-10-20 17:36:46 +0200370 *ext = '\0';
William Lallemand089c1382020-10-23 17:35:12 +0200371 fp->data = strlen(fp->area);
372 }
William Lallemand8e8581e2020-10-20 17:36:46 +0200373 }
374
375 }
376
Remi Tricot-Le Breton9bf3a1f2022-05-09 11:07:13 +0200377 /* If no private key was found yet and we cannot look for it in extra
378 * files, raise an error.
379 */
380 if ((ckch->key == NULL) && !(global_ssl.extra_files & SSL_GF_KEY)) {
381 memprintf(err, "%sNo Private Key found in '%s'.\n", err && *err ? *err : "", fp->area);
382 goto end;
383 }
William Lallemand03c331c2020-05-13 10:10:01 +0200384
Remi Tricot-Le Breton9bf3a1f2022-05-09 11:07:13 +0200385 /* try to load an external private key if it wasn't in the PEM */
386 if (!chunk_strcat(fp, ".key") || (b_data(fp) > MAXPATHLEN)) {
387 memprintf(err, "%s '%s' filename too long'.\n",
388 err && *err ? *err : "", fp->area);
389 ret = 1;
390 goto end;
391 }
William Lallemand8e8581e2020-10-20 17:36:46 +0200392
Remi Tricot-Le Breton9bf3a1f2022-05-09 11:07:13 +0200393 if (stat(fp->area, &st) == 0) {
394 if (ssl_sock_load_key_into_ckch(fp->area, NULL, ckch, err)) {
395 memprintf(err, "%s '%s' is present but cannot be read or parsed'.\n",
396 err && *err ? *err : "", fp->area);
William Lallemand8e8581e2020-10-20 17:36:46 +0200397 goto end;
398 }
Remi Tricot-Le Breton9bf3a1f2022-05-09 11:07:13 +0200399 }
William Lallemand03c331c2020-05-13 10:10:01 +0200400
Remi Tricot-Le Breton9bf3a1f2022-05-09 11:07:13 +0200401 if (ckch->key == NULL) {
402 memprintf(err, "%sNo Private Key found in '%s'.\n", err && *err ? *err : "", fp->area);
403 goto end;
William Lallemand03c331c2020-05-13 10:10:01 +0200404 }
Remi Tricot-Le Breton9bf3a1f2022-05-09 11:07:13 +0200405 /* remove the added extension */
406 *(fp->area + fp->data - strlen(".key")) = '\0';
407 b_sub(fp, strlen(".key"));
408
William Lallemand03c331c2020-05-13 10:10:01 +0200409
410 if (!X509_check_private_key(ckch->cert, ckch->key)) {
411 memprintf(err, "%sinconsistencies between private key and certificate loaded '%s'.\n",
412 err && *err ? *err : "", path);
413 goto end;
414 }
415
Ilya Shipitsinc47d6762021-02-13 11:45:33 +0500416#ifdef HAVE_SSL_SCTL
William Lallemand03c331c2020-05-13 10:10:01 +0200417 /* try to load the sctl file */
418 if (global_ssl.extra_files & SSL_GF_SCTL) {
William Lallemand03c331c2020-05-13 10:10:01 +0200419 struct stat st;
420
William Lallemand8e8581e2020-10-20 17:36:46 +0200421 if (!chunk_strcat(fp, ".sctl") || b_data(fp) > MAXPATHLEN) {
422 memprintf(err, "%s '%s' filename too long'.\n",
423 err && *err ? *err : "", fp->area);
424 ret = 1;
425 goto end;
426 }
427
428 if (stat(fp->area, &st) == 0) {
429 if (ssl_sock_load_sctl_from_file(fp->area, NULL, ckch, err)) {
William Lallemand03c331c2020-05-13 10:10:01 +0200430 memprintf(err, "%s '%s.sctl' is present but cannot be read or parsed'.\n",
William Lallemand8e8581e2020-10-20 17:36:46 +0200431 err && *err ? *err : "", fp->area);
William Lallemand03c331c2020-05-13 10:10:01 +0200432 ret = 1;
433 goto end;
434 }
435 }
William Lallemand8e8581e2020-10-20 17:36:46 +0200436 /* remove the added extension */
437 *(fp->area + fp->data - strlen(".sctl")) = '\0';
438 b_sub(fp, strlen(".sctl"));
William Lallemand03c331c2020-05-13 10:10:01 +0200439 }
440#endif
441
442 /* try to load an ocsp response file */
443 if (global_ssl.extra_files & SSL_GF_OCSP) {
William Lallemand03c331c2020-05-13 10:10:01 +0200444 struct stat st;
445
William Lallemand8e8581e2020-10-20 17:36:46 +0200446 if (!chunk_strcat(fp, ".ocsp") || b_data(fp) > MAXPATHLEN) {
447 memprintf(err, "%s '%s' filename too long'.\n",
448 err && *err ? *err : "", fp->area);
449 ret = 1;
450 goto end;
451 }
452
453 if (stat(fp->area, &st) == 0) {
454 if (ssl_sock_load_ocsp_response_from_file(fp->area, NULL, ckch, err)) {
William Lallemand03c331c2020-05-13 10:10:01 +0200455 ret = 1;
456 goto end;
457 }
458 }
William Lallemand8e8581e2020-10-20 17:36:46 +0200459 /* remove the added extension */
460 *(fp->area + fp->data - strlen(".ocsp")) = '\0';
461 b_sub(fp, strlen(".ocsp"));
William Lallemand03c331c2020-05-13 10:10:01 +0200462 }
463
464#ifndef OPENSSL_IS_BORINGSSL /* Useless for BoringSSL */
465 if (ckch->ocsp_response && (global_ssl.extra_files & SSL_GF_OCSP_ISSUER)) {
466 /* if no issuer was found, try to load an issuer from the .issuer */
467 if (!ckch->ocsp_issuer) {
468 struct stat st;
William Lallemand8e8581e2020-10-20 17:36:46 +0200469
470 if (!chunk_strcat(fp, ".issuer") || b_data(fp) > MAXPATHLEN) {
471 memprintf(err, "%s '%s' filename too long'.\n",
472 err && *err ? *err : "", fp->area);
473 ret = 1;
474 goto end;
475 }
William Lallemand03c331c2020-05-13 10:10:01 +0200476
William Lallemand8e8581e2020-10-20 17:36:46 +0200477 if (stat(fp->area, &st) == 0) {
478 if (ssl_sock_load_issuer_file_into_ckch(fp->area, NULL, ckch, err)) {
William Lallemand03c331c2020-05-13 10:10:01 +0200479 ret = 1;
480 goto end;
481 }
482
483 if (X509_check_issued(ckch->ocsp_issuer, ckch->cert) != X509_V_OK) {
484 memprintf(err, "%s '%s' is not an issuer'.\n",
William Lallemand8e8581e2020-10-20 17:36:46 +0200485 err && *err ? *err : "", fp->area);
William Lallemand03c331c2020-05-13 10:10:01 +0200486 ret = 1;
487 goto end;
488 }
489 }
William Lallemand8e8581e2020-10-20 17:36:46 +0200490 /* remove the added extension */
491 *(fp->area + fp->data - strlen(".issuer")) = '\0';
492 b_sub(fp, strlen(".issuer"));
William Lallemand03c331c2020-05-13 10:10:01 +0200493 }
494 }
495#endif
496
497 ret = 0;
498
499end:
500
501 ERR_clear_error();
502
503 /* Something went wrong in one of the reads */
504 if (ret != 0)
505 ssl_sock_free_cert_key_and_chain_contents(ckch);
506
William Lallemand8e8581e2020-10-20 17:36:46 +0200507 free_trash_chunk(fp);
508
William Lallemand03c331c2020-05-13 10:10:01 +0200509 return ret;
510}
511
512/*
513 * Try to load a private key file from a <path> or a buffer <buf>
514 *
515 * If it failed you should not attempt to use the ckch but free it.
516 *
517 * Return 0 on success or != 0 on failure
518 */
519int ssl_sock_load_key_into_ckch(const char *path, char *buf, struct cert_key_and_chain *ckch , char **err)
520{
521 BIO *in = NULL;
522 int ret = 1;
523 EVP_PKEY *key = NULL;
524
525 if (buf) {
526 /* reading from a buffer */
527 in = BIO_new_mem_buf(buf, -1);
528 if (in == NULL) {
529 memprintf(err, "%sCan't allocate memory\n", err && *err ? *err : "");
530 goto end;
531 }
532
533 } else {
534 /* reading from a file */
535 in = BIO_new(BIO_s_file());
536 if (in == NULL)
537 goto end;
538
539 if (BIO_read_filename(in, path) <= 0)
540 goto end;
541 }
542
543 /* Read Private Key */
544 key = PEM_read_bio_PrivateKey(in, NULL, NULL, NULL);
545 if (key == NULL) {
546 memprintf(err, "%sunable to load private key from file '%s'.\n",
547 err && *err ? *err : "", path);
548 goto end;
549 }
550
551 ret = 0;
552
553 SWAP(ckch->key, key);
554
555end:
556
557 ERR_clear_error();
558 if (in)
559 BIO_free(in);
560 if (key)
561 EVP_PKEY_free(key);
562
563 return ret;
564}
565
566/*
567 * Try to load a PEM file from a <path> or a buffer <buf>
568 * The PEM must contain at least a Certificate,
569 * It could contain a DH, a certificate chain and a PrivateKey.
570 *
571 * If it failed you should not attempt to use the ckch but free it.
572 *
573 * Return 0 on success or != 0 on failure
574 */
575int ssl_sock_load_pem_into_ckch(const char *path, char *buf, struct cert_key_and_chain *ckch , char **err)
576{
577 BIO *in = NULL;
578 int ret = 1;
579 X509 *ca;
580 X509 *cert = NULL;
581 EVP_PKEY *key = NULL;
Remi Tricot-Le Bretonc76c3c42022-02-11 12:04:55 +0100582 HASSL_DH *dh = NULL;
William Lallemand03c331c2020-05-13 10:10:01 +0200583 STACK_OF(X509) *chain = NULL;
584
585 if (buf) {
586 /* reading from a buffer */
587 in = BIO_new_mem_buf(buf, -1);
588 if (in == NULL) {
589 memprintf(err, "%sCan't allocate memory\n", err && *err ? *err : "");
590 goto end;
591 }
592
593 } else {
594 /* reading from a file */
595 in = BIO_new(BIO_s_file());
596 if (in == NULL) {
597 memprintf(err, "%sCan't allocate memory\n", err && *err ? *err : "");
598 goto end;
599 }
600
601 if (BIO_read_filename(in, path) <= 0) {
602 memprintf(err, "%scannot open the file '%s'.\n",
603 err && *err ? *err : "", path);
604 goto end;
605 }
606 }
607
608 /* Read Private Key */
609 key = PEM_read_bio_PrivateKey(in, NULL, NULL, NULL);
610 /* no need to check for errors here, because the private key could be loaded later */
611
612#ifndef OPENSSL_NO_DH
613 /* Seek back to beginning of file */
614 if (BIO_reset(in) == -1) {
615 memprintf(err, "%san error occurred while reading the file '%s'.\n",
616 err && *err ? *err : "", path);
617 goto end;
618 }
619
Remi Tricot-Le Bretonc76c3c42022-02-11 12:04:55 +0100620 dh = ssl_sock_get_dh_from_bio(in);
621 ERR_clear_error();
William Lallemand03c331c2020-05-13 10:10:01 +0200622 /* no need to return an error there, dh is not mandatory */
623#endif
624
625 /* Seek back to beginning of file */
626 if (BIO_reset(in) == -1) {
627 memprintf(err, "%san error occurred while reading the file '%s'.\n",
628 err && *err ? *err : "", path);
629 goto end;
630 }
631
632 /* Read Certificate */
633 cert = PEM_read_bio_X509_AUX(in, NULL, NULL, NULL);
634 if (cert == NULL) {
635 memprintf(err, "%sunable to load certificate from file '%s'.\n",
636 err && *err ? *err : "", path);
637 goto end;
638 }
639
640 /* Look for a Certificate Chain */
641 while ((ca = PEM_read_bio_X509(in, NULL, NULL, NULL))) {
642 if (chain == NULL)
643 chain = sk_X509_new_null();
644 if (!sk_X509_push(chain, ca)) {
645 X509_free(ca);
646 goto end;
647 }
648 }
649
650 ret = ERR_get_error();
651 if (ret && (ERR_GET_LIB(ret) != ERR_LIB_PEM && ERR_GET_REASON(ret) != PEM_R_NO_START_LINE)) {
652 memprintf(err, "%sunable to load certificate chain from file '%s'.\n",
653 err && *err ? *err : "", path);
654 goto end;
655 }
656
657 /* once it loaded the PEM, it should remove everything else in the ckch */
658 if (ckch->ocsp_response) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100659 ha_free(&ckch->ocsp_response->area);
660 ha_free(&ckch->ocsp_response);
William Lallemand03c331c2020-05-13 10:10:01 +0200661 }
662
663 if (ckch->sctl) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100664 ha_free(&ckch->sctl->area);
665 ha_free(&ckch->sctl);
William Lallemand03c331c2020-05-13 10:10:01 +0200666 }
667
668 if (ckch->ocsp_issuer) {
669 X509_free(ckch->ocsp_issuer);
670 ckch->ocsp_issuer = NULL;
671 }
672
673 /* no error, fill ckch with new context, old context will be free at end: */
674 SWAP(ckch->key, key);
675 SWAP(ckch->dh, dh);
676 SWAP(ckch->cert, cert);
677 SWAP(ckch->chain, chain);
678
679 ret = 0;
680
681end:
682
683 ERR_clear_error();
684 if (in)
685 BIO_free(in);
686 if (key)
687 EVP_PKEY_free(key);
688 if (dh)
Remi Tricot-Le Bretonc76c3c42022-02-11 12:04:55 +0100689 HASSL_DH_free(dh);
William Lallemand03c331c2020-05-13 10:10:01 +0200690 if (cert)
691 X509_free(cert);
692 if (chain)
693 sk_X509_pop_free(chain, X509_free);
694
695 return ret;
696}
697
698/* Frees the contents of a cert_key_and_chain
699 */
700void ssl_sock_free_cert_key_and_chain_contents(struct cert_key_and_chain *ckch)
701{
702 if (!ckch)
703 return;
704
705 /* Free the certificate and set pointer to NULL */
706 if (ckch->cert)
707 X509_free(ckch->cert);
708 ckch->cert = NULL;
709
710 /* Free the key and set pointer to NULL */
711 if (ckch->key)
712 EVP_PKEY_free(ckch->key);
713 ckch->key = NULL;
714
715 /* Free each certificate in the chain */
716 if (ckch->chain)
717 sk_X509_pop_free(ckch->chain, X509_free);
718 ckch->chain = NULL;
719
720 if (ckch->dh)
Remi Tricot-Le Bretonc76c3c42022-02-11 12:04:55 +0100721 HASSL_DH_free(ckch->dh);
William Lallemand03c331c2020-05-13 10:10:01 +0200722 ckch->dh = NULL;
723
724 if (ckch->sctl) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100725 ha_free(&ckch->sctl->area);
726 ha_free(&ckch->sctl);
William Lallemand03c331c2020-05-13 10:10:01 +0200727 }
728
729 if (ckch->ocsp_response) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100730 ha_free(&ckch->ocsp_response->area);
731 ha_free(&ckch->ocsp_response);
William Lallemand03c331c2020-05-13 10:10:01 +0200732 }
733
734 if (ckch->ocsp_issuer)
735 X509_free(ckch->ocsp_issuer);
736 ckch->ocsp_issuer = NULL;
737}
738
739/*
740 *
741 * This function copy a cert_key_and_chain in memory
742 *
743 * It's used to try to apply changes on a ckch before committing them, because
744 * most of the time it's not possible to revert those changes
745 *
746 * Return a the dst or NULL
747 */
748struct cert_key_and_chain *ssl_sock_copy_cert_key_and_chain(struct cert_key_and_chain *src,
749 struct cert_key_and_chain *dst)
750{
William Lallemand6c096142021-02-23 14:45:45 +0100751 if (!src || !dst)
752 return NULL;
753
William Lallemand03c331c2020-05-13 10:10:01 +0200754 if (src->cert) {
755 dst->cert = src->cert;
756 X509_up_ref(src->cert);
757 }
758
759 if (src->key) {
760 dst->key = src->key;
761 EVP_PKEY_up_ref(src->key);
762 }
763
764 if (src->chain) {
765 dst->chain = X509_chain_up_ref(src->chain);
766 }
767
768 if (src->dh) {
Remi Tricot-Le Bretonc76c3c42022-02-11 12:04:55 +0100769 HASSL_DH_up_ref(src->dh);
William Lallemand03c331c2020-05-13 10:10:01 +0200770 dst->dh = src->dh;
771 }
772
773 if (src->sctl) {
774 struct buffer *sctl;
775
776 sctl = calloc(1, sizeof(*sctl));
777 if (!chunk_dup(sctl, src->sctl)) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100778 ha_free(&sctl);
William Lallemand03c331c2020-05-13 10:10:01 +0200779 goto error;
780 }
781 dst->sctl = sctl;
782 }
783
784 if (src->ocsp_response) {
785 struct buffer *ocsp_response;
786
787 ocsp_response = calloc(1, sizeof(*ocsp_response));
788 if (!chunk_dup(ocsp_response, src->ocsp_response)) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100789 ha_free(&ocsp_response);
William Lallemand03c331c2020-05-13 10:10:01 +0200790 goto error;
791 }
792 dst->ocsp_response = ocsp_response;
793 }
794
795 if (src->ocsp_issuer) {
796 X509_up_ref(src->ocsp_issuer);
797 dst->ocsp_issuer = src->ocsp_issuer;
798 }
799
800 return dst;
801
802error:
803
804 /* free everything */
805 ssl_sock_free_cert_key_and_chain_contents(dst);
806
807 return NULL;
808}
809
810/*
811 * return 0 on success or != 0 on failure
812 */
813int ssl_sock_load_issuer_file_into_ckch(const char *path, char *buf, struct cert_key_and_chain *ckch, char **err)
814{
815 int ret = 1;
816 BIO *in = NULL;
817 X509 *issuer;
818
819 if (buf) {
820 /* reading from a buffer */
821 in = BIO_new_mem_buf(buf, -1);
822 if (in == NULL) {
823 memprintf(err, "%sCan't allocate memory\n", err && *err ? *err : "");
824 goto end;
825 }
826
827 } else {
828 /* reading from a file */
829 in = BIO_new(BIO_s_file());
830 if (in == NULL)
831 goto end;
832
833 if (BIO_read_filename(in, path) <= 0)
834 goto end;
835 }
836
837 issuer = PEM_read_bio_X509_AUX(in, NULL, NULL, NULL);
838 if (!issuer) {
839 memprintf(err, "%s'%s' cannot be read or parsed'.\n",
840 err && *err ? *err : "", path);
841 goto end;
842 }
843 /* no error, fill ckch with new context, old context must be free */
844 if (ckch->ocsp_issuer)
845 X509_free(ckch->ocsp_issuer);
846 ckch->ocsp_issuer = issuer;
847 ret = 0;
848
849end:
850
851 ERR_clear_error();
852 if (in)
853 BIO_free(in);
854
855 return ret;
856}
857
858/******************** ckch_store functions ***********************************
859 * The ckch_store is a structure used to cache and index the SSL files used in
860 * configuration
861 */
862
863/*
864 * Free a ckch_store, its ckch, its instances and remove it from the ebtree
865 */
866void ckch_store_free(struct ckch_store *store)
867{
868 struct ckch_inst *inst, *inst_s;
869
870 if (!store)
871 return;
872
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200873 ssl_sock_free_cert_key_and_chain_contents(store->ckch);
William Lallemand03c331c2020-05-13 10:10:01 +0200874
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100875 ha_free(&store->ckch);
William Lallemand03c331c2020-05-13 10:10:01 +0200876
877 list_for_each_entry_safe(inst, inst_s, &store->ckch_inst, by_ckchs) {
878 ckch_inst_free(inst);
879 }
880 ebmb_delete(&store->node);
881 free(store);
882}
883
884/*
885 * create and initialize a ckch_store
886 * <path> is the key name
887 * <nmemb> is the number of store->ckch objects to allocate
888 *
889 * Return a ckch_store or NULL upon failure.
890 */
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200891struct ckch_store *ckch_store_new(const char *filename)
William Lallemand03c331c2020-05-13 10:10:01 +0200892{
893 struct ckch_store *store;
894 int pathlen;
895
896 pathlen = strlen(filename);
897 store = calloc(1, sizeof(*store) + pathlen + 1);
898 if (!store)
899 return NULL;
900
William Lallemand03c331c2020-05-13 10:10:01 +0200901 memcpy(store->path, filename, pathlen + 1);
902
903 LIST_INIT(&store->ckch_inst);
904 LIST_INIT(&store->crtlist_entry);
905
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200906 store->ckch = calloc(1, sizeof(*store->ckch));
William Lallemand03c331c2020-05-13 10:10:01 +0200907 if (!store->ckch)
908 goto error;
909
910 return store;
911error:
912 ckch_store_free(store);
913 return NULL;
914}
915
916/* allocate and duplicate a ckch_store
917 * Return a new ckch_store or NULL */
918struct ckch_store *ckchs_dup(const struct ckch_store *src)
919{
920 struct ckch_store *dst;
921
William Lallemand6c096142021-02-23 14:45:45 +0100922 if (!src)
923 return NULL;
924
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200925 dst = ckch_store_new(src->path);
Eric Salama6ac61e32021-02-23 16:50:57 +0100926 if (!dst)
927 return NULL;
William Lallemand03c331c2020-05-13 10:10:01 +0200928
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200929 if (!ssl_sock_copy_cert_key_and_chain(src->ckch, dst->ckch))
930 goto error;
William Lallemand03c331c2020-05-13 10:10:01 +0200931
932 return dst;
933
934error:
935 ckch_store_free(dst);
936
937 return NULL;
938}
939
940/*
941 * lookup a path into the ckchs tree.
942 */
943struct ckch_store *ckchs_lookup(char *path)
944{
945 struct ebmb_node *eb;
946
947 eb = ebst_lookup(&ckchs_tree, path);
948 if (!eb)
949 return NULL;
950
951 return ebmb_entry(eb, struct ckch_store, node);
952}
953
954/*
955 * This function allocate a ckch_store and populate it with certificates from files.
956 */
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200957struct ckch_store *ckchs_load_cert_file(char *path, char **err)
William Lallemand03c331c2020-05-13 10:10:01 +0200958{
959 struct ckch_store *ckchs;
960
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200961 ckchs = ckch_store_new(path);
William Lallemand03c331c2020-05-13 10:10:01 +0200962 if (!ckchs) {
963 memprintf(err, "%sunable to allocate memory.\n", err && *err ? *err : "");
964 goto end;
965 }
William Lallemand03c331c2020-05-13 10:10:01 +0200966
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200967 if (ssl_sock_load_files_into_ckch(path, ckchs->ckch, err) == 1)
968 goto end;
William Lallemand03c331c2020-05-13 10:10:01 +0200969
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200970 /* insert into the ckchs tree */
971 memcpy(ckchs->path, path, strlen(path) + 1);
972 ebst_insert(&ckchs_tree, &ckchs->node);
William Lallemand03c331c2020-05-13 10:10:01 +0200973 return ckchs;
974
975end:
976 ckch_store_free(ckchs);
977
978 return NULL;
979}
980
William Lallemandfa1d8b42020-05-13 15:46:10 +0200981
982/******************** ckch_inst functions ******************************/
983
984/* unlink a ckch_inst, free all SNIs, free the ckch_inst */
985/* The caller must use the lock of the bind_conf if used with inserted SNIs */
986void ckch_inst_free(struct ckch_inst *inst)
987{
988 struct sni_ctx *sni, *sni_s;
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +0100989 struct ckch_inst_link_ref *link_ref, *link_ref_s;
William Lallemandfa1d8b42020-05-13 15:46:10 +0200990
991 if (inst == NULL)
992 return;
993
994 list_for_each_entry_safe(sni, sni_s, &inst->sni_ctx, by_ckch_inst) {
995 SSL_CTX_free(sni->ctx);
Willy Tarreau2b718102021-04-21 07:32:39 +0200996 LIST_DELETE(&sni->by_ckch_inst);
William Lallemandfa1d8b42020-05-13 15:46:10 +0200997 ebmb_delete(&sni->name);
998 free(sni);
999 }
Remi Tricot-Le Bretonf3eedfe2021-01-25 17:19:44 +01001000 SSL_CTX_free(inst->ctx);
1001 inst->ctx = NULL;
Willy Tarreau2b718102021-04-21 07:32:39 +02001002 LIST_DELETE(&inst->by_ckchs);
1003 LIST_DELETE(&inst->by_crtlist_entry);
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001004
1005 list_for_each_entry_safe(link_ref, link_ref_s, &inst->cafile_link_refs, list) {
1006 LIST_DELETE(&link_ref->link->list);
1007 LIST_DELETE(&link_ref->list);
1008 free(link_ref);
1009 }
1010
William Lallemandfa1d8b42020-05-13 15:46:10 +02001011 free(inst);
1012}
1013
1014/* Alloc and init a ckch_inst */
1015struct ckch_inst *ckch_inst_new()
1016{
1017 struct ckch_inst *ckch_inst;
1018
1019 ckch_inst = calloc(1, sizeof *ckch_inst);
1020 if (!ckch_inst)
1021 return NULL;
1022
1023 LIST_INIT(&ckch_inst->sni_ctx);
1024 LIST_INIT(&ckch_inst->by_ckchs);
1025 LIST_INIT(&ckch_inst->by_crtlist_entry);
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001026 LIST_INIT(&ckch_inst->cafile_link_refs);
William Lallemandfa1d8b42020-05-13 15:46:10 +02001027
1028 return ckch_inst;
1029}
1030
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001031
1032/******************** ssl_store functions ******************************/
Remi Tricot-Le Breton9f0c9362021-02-19 15:06:28 +01001033struct eb_root cafile_tree = EB_ROOT;
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001034
Remi Tricot-Le Breton9f0c9362021-02-19 15:06:28 +01001035/*
1036 * Returns the cafile_entry found in the cafile_tree indexed by the path 'path'.
1037 * If 'oldest_entry' is 1, returns the "original" cafile_entry (since
1038 * during a set cafile/commit cafile cycle there might be two entries for any
1039 * given path, the original one and the new one set via the CLI but not
1040 * committed yet).
1041 */
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001042struct cafile_entry *ssl_store_get_cafile_entry(char *path, int oldest_entry)
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001043{
Remi Tricot-Le Breton9f0c9362021-02-19 15:06:28 +01001044 struct cafile_entry *ca_e = NULL;
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001045 struct ebmb_node *eb;
1046
1047 eb = ebst_lookup(&cafile_tree, path);
Remi Tricot-Le Breton9f0c9362021-02-19 15:06:28 +01001048 while (eb) {
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001049 ca_e = ebmb_entry(eb, struct cafile_entry, node);
Remi Tricot-Le Breton9f0c9362021-02-19 15:06:28 +01001050 /* The ebst_lookup in a tree that has duplicates returns the
1051 * oldest entry first. If we want the latest entry, we need to
1052 * iterate over all the duplicates until we find the last one
1053 * (in our case there should never be more than two entries for
1054 * any given path). */
1055 if (oldest_entry)
1056 return ca_e;
1057 eb = ebmb_next_dup(eb);
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001058 }
Remi Tricot-Le Breton9f0c9362021-02-19 15:06:28 +01001059 return ca_e;
1060}
1061
Remi Tricot-Le Breton38c999b2021-02-23 16:28:43 +01001062int ssl_store_add_uncommitted_cafile_entry(struct cafile_entry *entry)
1063{
1064 return (ebst_insert(&cafile_tree, &entry->node) != &entry->node);
1065}
1066
Remi Tricot-Le Breton9f0c9362021-02-19 15:06:28 +01001067X509_STORE* ssl_store_get0_locations_file(char *path)
1068{
1069 struct cafile_entry *ca_e = ssl_store_get_cafile_entry(path, 0);
1070
1071 if (ca_e)
1072 return ca_e->ca_store;
1073
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001074 return NULL;
1075}
1076
Remi Tricot-Le Breton5daff3c2021-02-22 15:54:55 +01001077/* Create a cafile_entry object, without adding it to the cafile_tree. */
Remi Tricot-Le Breton0bb48242021-04-16 17:59:23 +02001078struct 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 +01001079{
1080 struct cafile_entry *ca_e;
1081 int pathlen;
1082
1083 pathlen = strlen(path);
1084
1085 ca_e = calloc(1, sizeof(*ca_e) + pathlen + 1);
1086 if (ca_e) {
1087 memcpy(ca_e->path, path, pathlen + 1);
1088 ca_e->ca_store = store;
Remi Tricot-Le Breton0bb48242021-04-16 17:59:23 +02001089 ca_e->type = type;
Remi Tricot-Le Breton5daff3c2021-02-22 15:54:55 +01001090 LIST_INIT(&ca_e->ckch_inst_link);
1091 }
1092 return ca_e;
1093}
1094
1095/* Delete a cafile_entry. The caller is responsible from removing this entry
1096 * from the cafile_tree first if is was previously added into it. */
1097void ssl_store_delete_cafile_entry(struct cafile_entry *ca_e)
1098{
1099 struct ckch_inst_link *link, *link_s;
1100 if (!ca_e)
1101 return;
1102
1103 X509_STORE_free(ca_e->ca_store);
1104
1105 list_for_each_entry_safe(link, link_s, &ca_e->ckch_inst_link, list) {
1106 struct ckch_inst *inst = link->ckch_inst;
1107 struct ckch_inst_link_ref *link_ref, *link_ref_s;
1108 list_for_each_entry_safe(link_ref, link_ref_s, &inst->cafile_link_refs, list) {
1109 if (link_ref->link == link) {
1110 LIST_DELETE(&link_ref->list);
1111 free(link_ref);
1112 break;
1113 }
1114 }
1115 LIST_DELETE(&link->list);
1116 free(link);
1117 }
1118
1119 free(ca_e);
1120}
1121
Remi Tricot-Le Breton383fb142021-02-22 18:26:14 +01001122/*
1123 * Build a cafile_entry out of a buffer instead of out of a file.
1124 * This function is used when the "commit ssl ca-file" cli command is used.
1125 * It can parse CERTIFICATE sections as well as CRL ones.
1126 * Returns 0 in case of success, 1 otherwise.
1127 */
1128int ssl_store_load_ca_from_buf(struct cafile_entry *ca_e, char *cert_buf)
1129{
1130 int retval = 0;
1131
1132 if (!ca_e)
1133 return 1;
1134
1135 if (!ca_e->ca_store) {
1136 ca_e->ca_store = X509_STORE_new();
1137 if (ca_e->ca_store) {
1138 BIO *bio = BIO_new_mem_buf(cert_buf, strlen(cert_buf));
1139 if (bio) {
1140 X509_INFO *info;
1141 int i;
1142 STACK_OF(X509_INFO) *infos = PEM_X509_INFO_read_bio(bio, NULL, NULL, NULL);
1143 if (!infos)
1144 {
1145 BIO_free(bio);
1146 return 1;
1147 }
1148
1149 for (i = 0; i < sk_X509_INFO_num(infos) && !retval; i++) {
1150 info = sk_X509_INFO_value(infos, i);
1151 /* X509_STORE_add_cert and X509_STORE_add_crl return 1 on success */
1152 if (info->x509) {
1153 retval = !X509_STORE_add_cert(ca_e->ca_store, info->x509);
1154 }
1155 if (!retval && info->crl) {
1156 retval = !X509_STORE_add_crl(ca_e->ca_store, info->crl);
1157 }
1158 }
1159 retval = retval || (i != sk_X509_INFO_num(infos));
1160
1161 /* Cleanup */
1162 sk_X509_INFO_pop_free(infos, X509_INFO_free);
1163 BIO_free(bio);
1164 }
1165 }
1166 }
1167
1168 return retval;
1169}
1170
Remi Tricot-Le Breton0bb48242021-04-16 17:59:23 +02001171int ssl_store_load_locations_file(char *path, int create_if_none, enum cafile_type type)
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001172{
1173 X509_STORE *store = ssl_store_get0_locations_file(path);
1174
1175 /* If this function is called by the CLI, we should not call the
1176 * X509_STORE_load_locations function because it performs forbidden disk
1177 * accesses. */
1178 if (!store && create_if_none) {
William Lallemand87fd9942022-04-01 20:12:03 +02001179 STACK_OF(X509_OBJECT) *objs;
1180 int cert_count = 0;
1181 struct stat buf;
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001182 struct cafile_entry *ca_e;
William Lallemandc6b17632022-04-01 23:39:37 +02001183 const char *file = NULL;
1184 const char *dir = NULL;
William Lallemand87fd9942022-04-01 20:12:03 +02001185
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001186 store = X509_STORE_new();
William Lallemand87fd9942022-04-01 20:12:03 +02001187
William Lallemandc6b17632022-04-01 23:39:37 +02001188 if (strcmp(path, "@system-ca") == 0) {
1189 dir = X509_get_default_cert_dir();
William Lallemand87fd9942022-04-01 20:12:03 +02001190
William Lallemandc6b17632022-04-01 23:39:37 +02001191 } else {
1192
1193 if (stat(path, &buf))
1194 goto err;
1195
1196 if (S_ISDIR(buf.st_mode))
1197 dir = path;
1198 else
1199 file = path;
1200 }
William Lallemand87fd9942022-04-01 20:12:03 +02001201
1202 if (file) {
1203 if (!X509_STORE_load_locations(store, file, NULL)) {
1204 goto err;
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001205 }
William Lallemand80296b42022-04-05 10:19:30 +02001206 } else if (dir) {
William Lallemand87fd9942022-04-01 20:12:03 +02001207 int n, i;
1208 struct dirent **de_list;
1209
1210 n = scandir(dir, &de_list, 0, alphasort);
1211 if (n < 0)
1212 goto err;
1213
1214 for (i= 0; i < n; i++) {
1215 char *end;
1216 struct dirent *de = de_list[i];
1217 BIO *in = NULL;
1218 X509 *ca = NULL;;
1219
1220 /* we try to load the files that would have
1221 * been loaded in an hashed directory loaded by
1222 * X509_LOOKUP_hash_dir, so according to "man 1
1223 * c_rehash", we should load ".pem", ".crt",
William Lallemande4b93eb2022-05-09 09:29:00 +02001224 * ".cer", or ".crl". Files starting with a dot
1225 * are ignored.
William Lallemand87fd9942022-04-01 20:12:03 +02001226 */
1227 end = strrchr(de->d_name, '.');
William Lallemande4b93eb2022-05-09 09:29:00 +02001228 if (!end || de->d_name[0] == '.' ||
1229 (strcmp(end, ".pem") != 0 &&
1230 strcmp(end, ".crt") != 0 &&
1231 strcmp(end, ".cer") != 0 &&
1232 strcmp(end, ".crl") != 0)) {
William Lallemand87fd9942022-04-01 20:12:03 +02001233 free(de);
1234 continue;
1235 }
1236 in = BIO_new(BIO_s_file());
1237 if (in == NULL)
1238 goto scandir_err;
1239
William Lallemandc6b17632022-04-01 23:39:37 +02001240 chunk_printf(&trash, "%s/%s", dir, de->d_name);
William Lallemand87fd9942022-04-01 20:12:03 +02001241
1242 if (BIO_read_filename(in, trash.area) == 0)
1243 goto scandir_err;
1244
1245 if (PEM_read_bio_X509_AUX(in, &ca, NULL, NULL) == NULL)
1246 goto scandir_err;
1247
1248 if (X509_STORE_add_cert(store, ca) == 0)
1249 goto scandir_err;
1250
William Lallemand4cfbf3c2022-04-26 15:57:33 +02001251 X509_free(ca);
William Lallemand87fd9942022-04-01 20:12:03 +02001252 BIO_free(in);
1253 free(de);
1254 continue;
1255
1256scandir_err:
William Lallemand4cfbf3c2022-04-26 15:57:33 +02001257 X509_free(ca);
William Lallemand87fd9942022-04-01 20:12:03 +02001258 BIO_free(in);
1259 free(de);
1260 ha_warning("ca-file: '%s' couldn't load '%s'\n", path, trash.area);
William Lallemand87fd9942022-04-01 20:12:03 +02001261
1262 }
1263 free(de_list);
William Lallemand80296b42022-04-05 10:19:30 +02001264 } else {
1265 ha_alert("ca-file: couldn't load '%s'\n", path);
1266 goto err;
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001267 }
William Lallemand87fd9942022-04-01 20:12:03 +02001268
1269 objs = X509_STORE_get0_objects(store);
1270 cert_count = sk_X509_OBJECT_num(objs);
1271 if (cert_count == 0)
1272 ha_warning("ca-file: 0 CA were loaded from '%s'\n", path);
1273
1274 ca_e = ssl_store_create_cafile_entry(path, store, type);
1275 if (!ca_e)
1276 goto err;
1277 ebst_insert(&cafile_tree, &ca_e->node);
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001278 }
1279 return (store != NULL);
William Lallemand87fd9942022-04-01 20:12:03 +02001280
1281err:
1282 X509_STORE_free(store);
1283 store = NULL;
1284 return 0;
1285
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001286}
1287
1288
William Lallemandda8584c2020-05-14 10:14:37 +02001289/*************************** CLI commands ***********************/
1290
1291/* Type of SSL payloads that can be updated over the CLI */
1292
William Lallemandff8bf982022-03-29 10:44:23 +02001293struct cert_exts cert_exts[] = {
1294 { "", CERT_TYPE_PEM, &ssl_sock_load_pem_into_ckch }, /* default mode, no extensions */
William Lallemand26654e72022-03-30 12:01:32 +02001295 { "crt", CERT_TYPE_CRT, &ssl_sock_load_pem_into_ckch },
William Lallemandff8bf982022-03-29 10:44:23 +02001296 { "key", CERT_TYPE_KEY, &ssl_sock_load_key_into_ckch },
William Lallemandda8584c2020-05-14 10:14:37 +02001297#if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) || defined OPENSSL_IS_BORINGSSL)
William Lallemandff8bf982022-03-29 10:44:23 +02001298 { "ocsp", CERT_TYPE_OCSP, &ssl_sock_load_ocsp_response_from_file },
William Lallemandda8584c2020-05-14 10:14:37 +02001299#endif
Ilya Shipitsinc47d6762021-02-13 11:45:33 +05001300#ifdef HAVE_SSL_SCTL
William Lallemandff8bf982022-03-29 10:44:23 +02001301 { "sctl", CERT_TYPE_SCTL, &ssl_sock_load_sctl_from_file },
William Lallemandda8584c2020-05-14 10:14:37 +02001302#endif
William Lallemandff8bf982022-03-29 10:44:23 +02001303 { "issuer", CERT_TYPE_ISSUER, &ssl_sock_load_issuer_file_into_ckch },
1304 { NULL, CERT_TYPE_MAX, NULL },
William Lallemandda8584c2020-05-14 10:14:37 +02001305};
1306
1307
1308/* release function of the `show ssl cert' command */
1309static void cli_release_show_cert(struct appctx *appctx)
1310{
1311 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1312}
1313
Willy Tarreau4fd9b4d2022-05-04 16:11:50 +02001314/* IO handler of "show ssl cert <filename>".
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001315 * It makes use of a show_cert_ctx context, and ckchs_transaction in read-only.
Willy Tarreau4fd9b4d2022-05-04 16:11:50 +02001316 */
William Lallemandda8584c2020-05-14 10:14:37 +02001317static int cli_io_handler_show_cert(struct appctx *appctx)
1318{
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001319 struct show_cert_ctx *ctx = appctx->svcctx;
William Lallemandda8584c2020-05-14 10:14:37 +02001320 struct buffer *trash = alloc_trash_chunk();
1321 struct ebmb_node *node;
William Lallemandda8584c2020-05-14 10:14:37 +02001322 struct ckch_store *ckchs;
1323
1324 if (trash == NULL)
1325 return 1;
1326
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001327 if (!ctx->old_ckchs) {
William Lallemandda8584c2020-05-14 10:14:37 +02001328 if (ckchs_transaction.old_ckchs) {
1329 ckchs = ckchs_transaction.old_ckchs;
1330 chunk_appendf(trash, "# transaction\n");
William Lallemand5685ccf2020-09-16 16:12:25 +02001331 chunk_appendf(trash, "*%s\n", ckchs->path);
William Lallemandda8584c2020-05-14 10:14:37 +02001332 }
1333 }
1334
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001335 if (!ctx->cur_ckchs) {
William Lallemandda8584c2020-05-14 10:14:37 +02001336 chunk_appendf(trash, "# filename\n");
1337 node = ebmb_first(&ckchs_tree);
1338 } else {
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001339 node = &ctx->cur_ckchs->node;
William Lallemandda8584c2020-05-14 10:14:37 +02001340 }
1341 while (node) {
1342 ckchs = ebmb_entry(node, struct ckch_store, node);
William Lallemand5685ccf2020-09-16 16:12:25 +02001343 chunk_appendf(trash, "%s\n", ckchs->path);
William Lallemandda8584c2020-05-14 10:14:37 +02001344
1345 node = ebmb_next(node);
Willy Tarreaud0a06d52022-05-18 15:07:19 +02001346 if (applet_putchk(appctx, trash) == -1)
William Lallemandda8584c2020-05-14 10:14:37 +02001347 goto yield;
William Lallemandda8584c2020-05-14 10:14:37 +02001348 }
1349
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001350 ctx->cur_ckchs = NULL;
William Lallemandda8584c2020-05-14 10:14:37 +02001351 free_trash_chunk(trash);
1352 return 1;
1353yield:
1354
1355 free_trash_chunk(trash);
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001356 ctx->cur_ckchs = ckchs;
William Lallemandda8584c2020-05-14 10:14:37 +02001357 return 0; /* should come back */
1358}
1359
1360/*
1361 * Extract and format the DNS SAN extensions and copy result into a chuink
1362 * Return 0;
1363 */
1364#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
1365static int ssl_sock_get_san_oneline(X509 *cert, struct buffer *out)
1366{
1367 int i;
1368 char *str;
1369 STACK_OF(GENERAL_NAME) *names = NULL;
1370
1371 names = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
1372 if (names) {
1373 for (i = 0; i < sk_GENERAL_NAME_num(names); i++) {
1374 GENERAL_NAME *name = sk_GENERAL_NAME_value(names, i);
1375 if (i > 0)
1376 chunk_appendf(out, ", ");
1377 if (name->type == GEN_DNS) {
1378 if (ASN1_STRING_to_UTF8((unsigned char **)&str, name->d.dNSName) >= 0) {
1379 chunk_appendf(out, "DNS:%s", str);
1380 OPENSSL_free(str);
1381 }
1382 }
1383 }
1384 sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free);
1385 }
1386 return 0;
1387}
1388#endif
1389
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001390/*
1391 * Build the ckch_inst_link that will be chained in the CA file entry and the
1392 * corresponding ckch_inst_link_ref that will be chained in the ckch instance.
1393 * Return 0 in case of success.
1394 */
1395static int do_chain_inst_and_cafile(struct cafile_entry *cafile_entry, struct ckch_inst *ckch_inst)
1396{
1397 struct ckch_inst_link *new_link;
1398 if (!LIST_ISEMPTY(&cafile_entry->ckch_inst_link)) {
1399 struct ckch_inst_link *link = LIST_ELEM(cafile_entry->ckch_inst_link.n,
1400 typeof(link), list);
1401 /* Do not add multiple references to the same
1402 * instance in a cafile_entry */
1403 if (link->ckch_inst == ckch_inst) {
1404 return 1;
1405 }
1406 }
1407
1408 new_link = calloc(1, sizeof(*new_link));
1409 if (new_link) {
1410 struct ckch_inst_link_ref *new_link_ref = calloc(1, sizeof(*new_link_ref));
1411 if (!new_link_ref) {
1412 free(new_link);
1413 return 1;
1414 }
1415
1416 new_link->ckch_inst = ckch_inst;
1417 new_link_ref->link = new_link;
1418 LIST_INIT(&new_link->list);
1419 LIST_INIT(&new_link_ref->list);
1420
1421 LIST_APPEND(&cafile_entry->ckch_inst_link, &new_link->list);
1422 LIST_APPEND(&ckch_inst->cafile_link_refs, &new_link_ref->list);
1423 }
1424
1425 return 0;
1426}
1427
1428
1429/*
1430 * Link a CA file tree entry to the ckch instance that uses it.
1431 * To determine if and which CA file tree entries need to be linked to the
1432 * instance, we follow the same logic performed in ssl_sock_prepare_ctx when
1433 * processing the verify option.
1434 * This function works for a frontend as well as for a backend, depending on the
1435 * configuration parameters given (bind_conf or server).
1436 */
1437void ckch_inst_add_cafile_link(struct ckch_inst *ckch_inst, struct bind_conf *bind_conf,
1438 struct ssl_bind_conf *ssl_conf, const struct server *srv)
1439{
1440 int verify = SSL_VERIFY_NONE;
1441
1442 if (srv) {
1443
1444 if (global.ssl_server_verify == SSL_SERVER_VERIFY_REQUIRED)
1445 verify = SSL_VERIFY_PEER;
1446 switch (srv->ssl_ctx.verify) {
1447 case SSL_SOCK_VERIFY_NONE:
1448 verify = SSL_VERIFY_NONE;
1449 break;
1450 case SSL_SOCK_VERIFY_REQUIRED:
1451 verify = SSL_VERIFY_PEER;
1452 break;
1453 }
1454 }
1455 else {
1456 switch ((ssl_conf && ssl_conf->verify) ? ssl_conf->verify : bind_conf->ssl_conf.verify) {
1457 case SSL_SOCK_VERIFY_NONE:
1458 verify = SSL_VERIFY_NONE;
1459 break;
1460 case SSL_SOCK_VERIFY_OPTIONAL:
1461 verify = SSL_VERIFY_PEER;
1462 break;
1463 case SSL_SOCK_VERIFY_REQUIRED:
1464 verify = SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
1465 break;
1466 }
1467 }
1468
1469 if (verify & SSL_VERIFY_PEER) {
1470 struct cafile_entry *ca_file_entry = NULL;
1471 struct cafile_entry *ca_verify_file_entry = NULL;
Remi Tricot-Le Bretonf81c70c2021-04-20 16:54:21 +02001472 struct cafile_entry *crl_file_entry = NULL;
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001473 if (srv) {
1474 if (srv->ssl_ctx.ca_file) {
1475 ca_file_entry = ssl_store_get_cafile_entry(srv->ssl_ctx.ca_file, 0);
1476
1477 }
Remi Tricot-Le Bretonf81c70c2021-04-20 16:54:21 +02001478 if (srv->ssl_ctx.crl_file) {
1479 crl_file_entry = ssl_store_get_cafile_entry(srv->ssl_ctx.crl_file, 0);
1480 }
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001481 }
1482 else {
1483 char *ca_file = (ssl_conf && ssl_conf->ca_file) ? ssl_conf->ca_file : bind_conf->ssl_conf.ca_file;
1484 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 +02001485 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 +01001486
1487 if (ca_file)
1488 ca_file_entry = ssl_store_get_cafile_entry(ca_file, 0);
1489 if (ca_verify_file)
1490 ca_verify_file_entry = ssl_store_get_cafile_entry(ca_verify_file, 0);
Remi Tricot-Le Bretonf81c70c2021-04-20 16:54:21 +02001491 if (crl_file)
1492 crl_file_entry = ssl_store_get_cafile_entry(crl_file, 0);
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001493 }
1494
1495 if (ca_file_entry) {
1496 /* If we have a ckch instance that is not already in the
1497 * cafile_entry's list, add it to it. */
1498 if (do_chain_inst_and_cafile(ca_file_entry, ckch_inst))
1499 return;
1500
1501 }
1502 if (ca_verify_file_entry && (ca_file_entry != ca_verify_file_entry)) {
1503 /* If we have a ckch instance that is not already in the
1504 * cafile_entry's list, add it to it. */
1505 if (do_chain_inst_and_cafile(ca_verify_file_entry, ckch_inst))
1506 return;
1507 }
Remi Tricot-Le Bretonf81c70c2021-04-20 16:54:21 +02001508 if (crl_file_entry) {
1509 /* If we have a ckch instance that is not already in the
1510 * cafile_entry's list, add it to it. */
1511 if (do_chain_inst_and_cafile(crl_file_entry, ckch_inst))
1512 return;
1513 }
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001514 }
1515}
1516
William Lallemandda8584c2020-05-14 10:14:37 +02001517
1518
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001519static int show_cert_detail(X509 *cert, STACK_OF(X509) *chain, struct buffer *out)
William Lallemandda8584c2020-05-14 10:14:37 +02001520{
William Lallemandda8584c2020-05-14 10:14:37 +02001521 BIO *bio = NULL;
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001522 struct buffer *tmp = alloc_trash_chunk();
William Lallemandda8584c2020-05-14 10:14:37 +02001523 int i;
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001524 int write = -1;
1525 unsigned int len = 0;
1526 X509_NAME *name = NULL;
William Lallemandda8584c2020-05-14 10:14:37 +02001527
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001528 if (!tmp)
1529 return -1;
William Lallemandda8584c2020-05-14 10:14:37 +02001530
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001531 if (!cert)
William Lallemand5685ccf2020-09-16 16:12:25 +02001532 goto end;
William Lallemandda8584c2020-05-14 10:14:37 +02001533
William Lallemand5685ccf2020-09-16 16:12:25 +02001534 if (chain == NULL) {
1535 struct issuer_chain *issuer;
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001536 issuer = ssl_get0_issuer_chain(cert);
William Lallemand5685ccf2020-09-16 16:12:25 +02001537 if (issuer) {
1538 chain = issuer->chain;
1539 chunk_appendf(out, "Chain Filename: ");
1540 chunk_appendf(out, "%s\n", issuer->path);
William Lallemandda8584c2020-05-14 10:14:37 +02001541 }
William Lallemand5685ccf2020-09-16 16:12:25 +02001542 }
1543 chunk_appendf(out, "Serial: ");
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001544 if (ssl_sock_get_serial(cert, tmp) == -1)
William Lallemand5685ccf2020-09-16 16:12:25 +02001545 goto end;
1546 dump_binary(out, tmp->area, tmp->data);
1547 chunk_appendf(out, "\n");
William Lallemandda8584c2020-05-14 10:14:37 +02001548
William Lallemand5685ccf2020-09-16 16:12:25 +02001549 chunk_appendf(out, "notBefore: ");
1550 chunk_reset(tmp);
1551 if ((bio = BIO_new(BIO_s_mem())) == NULL)
1552 goto end;
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001553 if (ASN1_TIME_print(bio, X509_getm_notBefore(cert)) == 0)
William Lallemand5685ccf2020-09-16 16:12:25 +02001554 goto end;
1555 write = BIO_read(bio, tmp->area, tmp->size-1);
1556 tmp->area[write] = '\0';
1557 BIO_free(bio);
1558 bio = NULL;
1559 chunk_appendf(out, "%s\n", tmp->area);
William Lallemandda8584c2020-05-14 10:14:37 +02001560
William Lallemand5685ccf2020-09-16 16:12:25 +02001561 chunk_appendf(out, "notAfter: ");
1562 chunk_reset(tmp);
1563 if ((bio = BIO_new(BIO_s_mem())) == NULL)
1564 goto end;
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001565 if (ASN1_TIME_print(bio, X509_getm_notAfter(cert)) == 0)
William Lallemand5685ccf2020-09-16 16:12:25 +02001566 goto end;
1567 if ((write = BIO_read(bio, tmp->area, tmp->size-1)) <= 0)
1568 goto end;
1569 tmp->area[write] = '\0';
1570 BIO_free(bio);
1571 bio = NULL;
1572 chunk_appendf(out, "%s\n", tmp->area);
William Lallemandda8584c2020-05-14 10:14:37 +02001573
1574#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
William Lallemand5685ccf2020-09-16 16:12:25 +02001575 chunk_appendf(out, "Subject Alternative Name: ");
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001576 if (ssl_sock_get_san_oneline(cert, out) == -1)
William Lallemand5685ccf2020-09-16 16:12:25 +02001577 goto end;
1578 *(out->area + out->data) = '\0';
1579 chunk_appendf(out, "\n");
William Lallemandda8584c2020-05-14 10:14:37 +02001580#endif
William Lallemand5685ccf2020-09-16 16:12:25 +02001581 chunk_reset(tmp);
1582 chunk_appendf(out, "Algorithm: ");
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001583 if (cert_get_pkey_algo(cert, tmp) == 0)
William Lallemand5685ccf2020-09-16 16:12:25 +02001584 goto end;
1585 chunk_appendf(out, "%s\n", tmp->area);
William Lallemandda8584c2020-05-14 10:14:37 +02001586
William Lallemand5685ccf2020-09-16 16:12:25 +02001587 chunk_reset(tmp);
1588 chunk_appendf(out, "SHA1 FingerPrint: ");
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001589 if (X509_digest(cert, EVP_sha1(), (unsigned char *) tmp->area, &len) == 0)
William Lallemand5685ccf2020-09-16 16:12:25 +02001590 goto end;
1591 tmp->data = len;
1592 dump_binary(out, tmp->area, tmp->data);
1593 chunk_appendf(out, "\n");
William Lallemandda8584c2020-05-14 10:14:37 +02001594
William Lallemand5685ccf2020-09-16 16:12:25 +02001595 chunk_appendf(out, "Subject: ");
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001596 if ((name = X509_get_subject_name(cert)) == NULL)
William Lallemand5685ccf2020-09-16 16:12:25 +02001597 goto end;
1598 if ((ssl_sock_get_dn_oneline(name, tmp)) == -1)
1599 goto end;
1600 *(tmp->area + tmp->data) = '\0';
1601 chunk_appendf(out, "%s\n", tmp->area);
1602
1603 chunk_appendf(out, "Issuer: ");
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001604 if ((name = X509_get_issuer_name(cert)) == NULL)
William Lallemand5685ccf2020-09-16 16:12:25 +02001605 goto end;
1606 if ((ssl_sock_get_dn_oneline(name, tmp)) == -1)
1607 goto end;
1608 *(tmp->area + tmp->data) = '\0';
1609 chunk_appendf(out, "%s\n", tmp->area);
1610
1611 /* Displays subject of each certificate in the chain */
1612 for (i = 0; i < sk_X509_num(chain); i++) {
1613 X509 *ca = sk_X509_value(chain, i);
1614
1615 chunk_appendf(out, "Chain Subject: ");
1616 if ((name = X509_get_subject_name(ca)) == NULL)
William Lallemandda8584c2020-05-14 10:14:37 +02001617 goto end;
1618 if ((ssl_sock_get_dn_oneline(name, tmp)) == -1)
1619 goto end;
1620 *(tmp->area + tmp->data) = '\0';
1621 chunk_appendf(out, "%s\n", tmp->area);
1622
William Lallemand5685ccf2020-09-16 16:12:25 +02001623 chunk_appendf(out, "Chain Issuer: ");
1624 if ((name = X509_get_issuer_name(ca)) == NULL)
William Lallemandda8584c2020-05-14 10:14:37 +02001625 goto end;
1626 if ((ssl_sock_get_dn_oneline(name, tmp)) == -1)
1627 goto end;
1628 *(tmp->area + tmp->data) = '\0';
1629 chunk_appendf(out, "%s\n", tmp->area);
William Lallemandda8584c2020-05-14 10:14:37 +02001630 }
1631
1632end:
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001633 if (bio)
1634 BIO_free(bio);
1635 free_trash_chunk(tmp);
1636
1637 return 0;
1638}
1639
Remi Tricot-Le Breton3faf0cb2021-06-10 18:10:32 +02001640#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 +02001641/*
1642 * Build the OCSP tree entry's key for a given ckch_store.
1643 * Returns a negative value in case of error.
1644 */
1645static int ckch_store_build_certid(struct ckch_store *ckch_store, unsigned char certid[128], unsigned int *key_length)
1646{
1647 OCSP_RESPONSE *resp;
1648 OCSP_BASICRESP *bs = NULL;
1649 OCSP_SINGLERESP *sr;
1650 OCSP_CERTID *id;
1651 unsigned char *p = NULL;
1652
1653 if (!key_length)
1654 return -1;
1655
1656 *key_length = 0;
1657
1658 if (!ckch_store->ckch->ocsp_response)
1659 return 0;
1660
1661 p = (unsigned char *) ckch_store->ckch->ocsp_response->area;
1662
1663 resp = d2i_OCSP_RESPONSE(NULL, (const unsigned char **)&p,
1664 ckch_store->ckch->ocsp_response->data);
1665 if (!resp) {
1666 goto end;
1667 }
1668
1669 bs = OCSP_response_get1_basic(resp);
1670 if (!bs) {
1671 goto end;
1672 }
1673
1674 sr = OCSP_resp_get0(bs, 0);
1675 if (!sr) {
1676 goto end;
1677 }
1678
1679 id = (OCSP_CERTID*)OCSP_SINGLERESP_get0_id(sr);
1680
1681 p = certid;
1682 *key_length = i2d_OCSP_CERTID(id, &p);
1683
1684end:
1685 return *key_length > 0;
1686}
1687#endif
1688
1689/*
1690 * Dump the OCSP certificate key (if it exists) of certificate <ckch> into
1691 * buffer <out>.
1692 * Returns 0 in case of success.
1693 */
1694static int ckch_store_show_ocsp_certid(struct ckch_store *ckch_store, struct buffer *out)
1695{
Remi Tricot-Le Breton3faf0cb2021-06-10 18:10:32 +02001696#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 +02001697 unsigned char key[OCSP_MAX_CERTID_ASN1_LENGTH] = {};
1698 unsigned int key_length = 0;
1699 int i;
1700
1701 if (ckch_store_build_certid(ckch_store, (unsigned char*)key, &key_length) >= 0) {
1702 /* Dump the CERTID info */
1703 chunk_appendf(out, "OCSP Response Key: ");
1704 for (i = 0; i < key_length; ++i) {
1705 chunk_appendf(out, "%02x", key[i]);
1706 }
1707 chunk_appendf(out, "\n");
1708 }
1709#endif
1710
1711 return 0;
1712}
1713
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001714
Willy Tarreau4fd9b4d2022-05-04 16:11:50 +02001715/* IO handler of the details "show ssl cert <filename>".
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001716 * It uses a struct show_cert_ctx and ckchs_transaction in read-only.
Willy Tarreau4fd9b4d2022-05-04 16:11:50 +02001717 */
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001718static int cli_io_handler_show_cert_detail(struct appctx *appctx)
1719{
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001720 struct show_cert_ctx *ctx = appctx->svcctx;
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001721 struct ckch_store *ckchs = ctx->cur_ckchs;
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001722 struct buffer *out = alloc_trash_chunk();
1723 int retval = 0;
1724
1725 if (!out)
1726 goto end_no_putchk;
1727
1728 chunk_appendf(out, "Filename: ");
1729 if (ckchs == ckchs_transaction.new_ckchs)
1730 chunk_appendf(out, "*");
1731 chunk_appendf(out, "%s\n", ckchs->path);
1732
1733 chunk_appendf(out, "Status: ");
1734 if (ckchs->ckch->cert == NULL)
1735 chunk_appendf(out, "Empty\n");
1736 else if (LIST_ISEMPTY(&ckchs->ckch_inst))
1737 chunk_appendf(out, "Unused\n");
1738 else
1739 chunk_appendf(out, "Used\n");
1740
1741 retval = show_cert_detail(ckchs->ckch->cert, ckchs->ckch->chain, out);
1742 if (retval < 0)
1743 goto end_no_putchk;
1744 else if (retval)
1745 goto end;
1746
Remi Tricot-Le Bretonda968f62021-06-10 13:51:14 +02001747 ckch_store_show_ocsp_certid(ckchs, out);
1748
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001749end:
Willy Tarreaud0a06d52022-05-18 15:07:19 +02001750 if (applet_putchk(appctx, out) == -1)
William Lallemandda8584c2020-05-14 10:14:37 +02001751 goto yield;
William Lallemandda8584c2020-05-14 10:14:37 +02001752
1753end_no_putchk:
William Lallemandda8584c2020-05-14 10:14:37 +02001754 free_trash_chunk(out);
1755 return 1;
1756yield:
William Lallemandda8584c2020-05-14 10:14:37 +02001757 free_trash_chunk(out);
1758 return 0; /* should come back */
1759}
1760
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001761
Willy Tarreau4fd9b4d2022-05-04 16:11:50 +02001762/* IO handler of the details "show ssl cert <filename.ocsp>".
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001763 * It uses a show_cert_ctx.
Willy Tarreau4fd9b4d2022-05-04 16:11:50 +02001764 */
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001765static int cli_io_handler_show_cert_ocsp_detail(struct appctx *appctx)
1766{
Remi Tricot-Le Breton3faf0cb2021-06-10 18:10:32 +02001767#if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) && !defined OPENSSL_IS_BORINGSSL)
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001768 struct show_cert_ctx *ctx = appctx->svcctx;
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001769 struct ckch_store *ckchs = ctx->cur_ckchs;
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001770 struct buffer *out = alloc_trash_chunk();
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001771 int from_transaction = ctx->transaction;
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001772
1773 if (!out)
1774 goto end_no_putchk;
1775
1776 /* If we try to display an ongoing transaction's OCSP response, we
1777 * need to dump the ckch's ocsp_response buffer directly.
1778 * Otherwise, we must rebuild the certificate's certid in order to
1779 * look for the current OCSP response in the tree. */
1780 if (from_transaction && ckchs->ckch->ocsp_response) {
Remi Tricot-Le Bretona9a591a2022-02-16 14:42:22 +01001781 if (ssl_ocsp_response_print(ckchs->ckch->ocsp_response, out))
1782 goto end_no_putchk;
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001783 }
1784 else {
1785 unsigned char key[OCSP_MAX_CERTID_ASN1_LENGTH] = {};
1786 unsigned int key_length = 0;
1787
1788 if (ckch_store_build_certid(ckchs, (unsigned char*)key, &key_length) < 0)
1789 goto end_no_putchk;
1790
Remi Tricot-Le Bretona9a591a2022-02-16 14:42:22 +01001791 if (ssl_get_ocspresponse_detail(key, out))
1792 goto end_no_putchk;
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001793 }
1794
Willy Tarreaud0a06d52022-05-18 15:07:19 +02001795 if (applet_putchk(appctx, out) == -1)
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001796 goto yield;
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001797
1798end_no_putchk:
1799 free_trash_chunk(out);
1800 return 1;
1801yield:
1802 free_trash_chunk(out);
1803 return 0; /* should come back */
1804#else
1805 return cli_err(appctx, "HAProxy was compiled against a version of OpenSSL that doesn't support OCSP stapling.\n");
1806#endif
1807}
1808
William Lallemandda8584c2020-05-14 10:14:37 +02001809/* parsing function for 'show ssl cert [certfile]' */
1810static int cli_parse_show_cert(char **args, char *payload, struct appctx *appctx, void *private)
1811{
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001812 struct show_cert_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
William Lallemandda8584c2020-05-14 10:14:37 +02001813 struct ckch_store *ckchs;
1814
1815 if (!cli_has_level(appctx, ACCESS_LVL_OPER))
1816 return cli_err(appctx, "Can't allocate memory!\n");
1817
1818 /* The operations on the CKCH architecture are locked so we can
1819 * manipulate ckch_store and ckch_inst */
1820 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
1821 return cli_err(appctx, "Can't show!\nOperations on certificates are currently locked!\n");
1822
1823 /* check if there is a certificate to lookup */
1824 if (*args[3]) {
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001825 int show_ocsp_detail = 0;
1826 int from_transaction = 0;
1827 char *end;
1828
1829 /* We manage the special case "certname.ocsp" through which we
1830 * can show the details of an OCSP response. */
1831 end = strrchr(args[3], '.');
1832 if (end && strcmp(end+1, "ocsp") == 0) {
1833 *end = '\0';
1834 show_ocsp_detail = 1;
1835 }
1836
William Lallemandda8584c2020-05-14 10:14:37 +02001837 if (*args[3] == '*') {
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001838 from_transaction = 1;
William Lallemandda8584c2020-05-14 10:14:37 +02001839 if (!ckchs_transaction.new_ckchs)
1840 goto error;
1841
1842 ckchs = ckchs_transaction.new_ckchs;
1843
Tim Duesterhuse5ff1412021-01-02 22:31:53 +01001844 if (strcmp(args[3] + 1, ckchs->path) != 0)
William Lallemandda8584c2020-05-14 10:14:37 +02001845 goto error;
1846
1847 } else {
1848 if ((ckchs = ckchs_lookup(args[3])) == NULL)
1849 goto error;
1850
1851 }
1852
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001853 ctx->cur_ckchs = ckchs;
William Lallemandda8584c2020-05-14 10:14:37 +02001854 /* use the IO handler that shows details */
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001855 if (show_ocsp_detail) {
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001856 ctx->transaction = from_transaction;
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001857 appctx->io_handler = cli_io_handler_show_cert_ocsp_detail;
1858 }
1859 else
1860 appctx->io_handler = cli_io_handler_show_cert_detail;
William Lallemandda8584c2020-05-14 10:14:37 +02001861 }
1862
1863 return 0;
1864
1865error:
1866 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1867 return cli_err(appctx, "Can't display the certificate: Not found or the certificate is a bundle!\n");
1868}
1869
1870/* release function of the `set ssl cert' command, free things and unlock the spinlock */
1871static void cli_release_commit_cert(struct appctx *appctx)
1872{
Willy Tarreaua645b6a2022-05-04 19:58:00 +02001873 struct commit_cert_ctx *ctx = appctx->svcctx;
William Lallemandda8584c2020-05-14 10:14:37 +02001874 struct ckch_store *new_ckchs;
1875
1876 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1877
Willy Tarreaucb1b4ed2022-05-05 08:15:27 +02001878 if (ctx->state != CERT_ST_FIN) {
William Lallemandda8584c2020-05-14 10:14:37 +02001879 /* 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 +02001880 new_ckchs = ctx->new_ckchs;
William Lallemandda8584c2020-05-14 10:14:37 +02001881
1882 /* if the allocation failed, we need to free everything from the temporary list */
1883 ckch_store_free(new_ckchs);
1884 }
1885}
1886
Remi Tricot-Le Bretonbfadc022021-02-24 12:20:48 +01001887
1888/*
1889 * Rebuild a new instance 'new_inst' based on an old instance 'ckchi' and a
1890 * specific ckch_store.
1891 * Returns 0 in case of success, 1 otherwise.
1892 */
William Lallemande60c7d62022-03-30 11:26:15 +02001893int ckch_inst_rebuild(struct ckch_store *ckch_store, struct ckch_inst *ckchi,
1894 struct ckch_inst **new_inst, char **err)
Remi Tricot-Le Bretonbfadc022021-02-24 12:20:48 +01001895{
1896 int retval = 0;
1897 int errcode = 0;
1898 struct sni_ctx *sc0, *sc0s;
1899 char **sni_filter = NULL;
1900 int fcount = 0;
1901
1902 if (ckchi->crtlist_entry) {
1903 sni_filter = ckchi->crtlist_entry->filters;
1904 fcount = ckchi->crtlist_entry->fcount;
1905 }
1906
1907 if (ckchi->is_server_instance)
1908 errcode |= ckch_inst_new_load_srv_store(ckch_store->path, ckch_store, new_inst, err);
1909 else
1910 errcode |= ckch_inst_new_load_store(ckch_store->path, ckch_store, ckchi->bind_conf, ckchi->ssl_conf, sni_filter, fcount, new_inst, err);
1911
1912 if (errcode & ERR_CODE)
1913 return 1;
1914
1915 /* if the previous ckchi was used as the default */
1916 if (ckchi->is_default)
1917 (*new_inst)->is_default = 1;
1918
1919 (*new_inst)->is_server_instance = ckchi->is_server_instance;
1920 (*new_inst)->server = ckchi->server;
1921 /* Create a new SSL_CTX and link it to the new instance. */
1922 if ((*new_inst)->is_server_instance) {
1923 retval = ssl_sock_prep_srv_ctx_and_inst(ckchi->server, (*new_inst)->ctx, (*new_inst));
1924 if (retval)
1925 return 1;
1926 }
1927
1928 /* create the link to the crtlist_entry */
1929 (*new_inst)->crtlist_entry = ckchi->crtlist_entry;
1930
1931 /* we need to initialize the SSL_CTX generated */
1932 /* this iterate on the newly generated SNIs in the new instance to prepare their SSL_CTX */
1933 list_for_each_entry_safe(sc0, sc0s, &(*new_inst)->sni_ctx, by_ckch_inst) {
1934 if (!sc0->order) { /* we initialized only the first SSL_CTX because it's the same in the other sni_ctx's */
1935 errcode |= ssl_sock_prep_ctx_and_inst(ckchi->bind_conf, ckchi->ssl_conf, sc0->ctx, *new_inst, err);
1936 if (errcode & ERR_CODE)
1937 return 1;
1938 }
1939 }
1940
1941 return 0;
1942}
1943
1944/*
1945 * Load all the new SNIs of a newly built ckch instance in the trees, or replace
1946 * a server's main ckch instance.
1947 */
1948static void __ssl_sock_load_new_ckch_instance(struct ckch_inst *ckchi)
1949{
1950 /* The bind_conf will be null on server ckch_instances. */
1951 if (ckchi->is_server_instance) {
1952 int i;
1953 /* a lock is needed here since we have to free the SSL cache */
1954 HA_RWLOCK_WRLOCK(SSL_SERVER_LOCK, &ckchi->server->ssl_ctx.lock);
1955 /* free the server current SSL_CTX */
1956 SSL_CTX_free(ckchi->server->ssl_ctx.ctx);
1957 /* Actual ssl context update */
1958 SSL_CTX_up_ref(ckchi->ctx);
1959 ckchi->server->ssl_ctx.ctx = ckchi->ctx;
1960 ckchi->server->ssl_ctx.inst = ckchi;
1961
1962 /* flush the session cache of the server */
1963 for (i = 0; i < global.nbthread; i++) {
William Lallemandce990332021-11-23 15:15:09 +01001964 ha_free(&ckchi->server->ssl_ctx.reused_sess[i].sni);
Remi Tricot-Le Bretonbfadc022021-02-24 12:20:48 +01001965 ha_free(&ckchi->server->ssl_ctx.reused_sess[i].ptr);
1966 }
1967 HA_RWLOCK_WRUNLOCK(SSL_SERVER_LOCK, &ckchi->server->ssl_ctx.lock);
1968
1969 } else {
1970 HA_RWLOCK_WRLOCK(SNI_LOCK, &ckchi->bind_conf->sni_lock);
1971 ssl_sock_load_cert_sni(ckchi, ckchi->bind_conf);
1972 HA_RWLOCK_WRUNLOCK(SNI_LOCK, &ckchi->bind_conf->sni_lock);
1973 }
1974}
1975
1976/*
1977 * Delete a ckch instance that was replaced after a CLI command.
1978 */
1979static void __ckch_inst_free_locked(struct ckch_inst *ckchi)
1980{
1981 if (ckchi->is_server_instance) {
1982 /* no lock for servers */
1983 ckch_inst_free(ckchi);
1984 } else {
1985 struct bind_conf __maybe_unused *bind_conf = ckchi->bind_conf;
1986
1987 HA_RWLOCK_WRLOCK(SNI_LOCK, &bind_conf->sni_lock);
1988 ckch_inst_free(ckchi);
1989 HA_RWLOCK_WRUNLOCK(SNI_LOCK, &bind_conf->sni_lock);
1990 }
1991}
1992
William Lallemand3b5a3a62022-03-29 14:29:31 +02001993/* Replace a ckch_store in the ckch tree and insert the whole dependencies,
1994* then free the previous dependencies and store.
1995* Used in the case of a certificate update.
1996*
1997* Every dependencies must allocated before using this function.
1998*
1999* This function can't fail as it only update pointers, and does not alloc anything.
2000*
2001* /!\ This function must be used under the ckch lock. /!\
2002*
2003* - Insert every dependencies (SNI, crtlist_entry, ckch_inst, etc)
2004* - Delete the old ckch_store from the tree
2005* - Insert the new ckch_store
2006* - Free the old dependencies and the old ckch_store
2007*/
2008void ckch_store_replace(struct ckch_store *old_ckchs, struct ckch_store *new_ckchs)
2009{
2010 struct crtlist_entry *entry;
2011 struct ckch_inst *ckchi, *ckchis;
2012
2013 LIST_SPLICE(&new_ckchs->crtlist_entry, &old_ckchs->crtlist_entry);
2014 list_for_each_entry(entry, &new_ckchs->crtlist_entry, by_ckch_store) {
2015 ebpt_delete(&entry->node);
2016 /* change the ptr and reinsert the node */
2017 entry->node.key = new_ckchs;
2018 ebpt_insert(&entry->crtlist->entries, &entry->node);
2019 }
2020 /* insert the new ckch_insts in the crtlist_entry */
2021 list_for_each_entry(ckchi, &new_ckchs->ckch_inst, by_ckchs) {
2022 if (ckchi->crtlist_entry)
2023 LIST_INSERT(&ckchi->crtlist_entry->ckch_inst, &ckchi->by_crtlist_entry);
2024 }
2025 /* First, we insert every new SNIs in the trees, also replace the default_ctx */
2026 list_for_each_entry_safe(ckchi, ckchis, &new_ckchs->ckch_inst, by_ckchs) {
2027 __ssl_sock_load_new_ckch_instance(ckchi);
2028 }
2029 /* delete the old sni_ctx, the old ckch_insts and the ckch_store */
2030 list_for_each_entry_safe(ckchi, ckchis, &old_ckchs->ckch_inst, by_ckchs) {
2031 __ckch_inst_free_locked(ckchi);
2032 }
2033
2034 ckch_store_free(old_ckchs);
2035 ebst_insert(&ckchs_tree, &new_ckchs->node);
2036}
2037
Remi Tricot-Le Bretonbfadc022021-02-24 12:20:48 +01002038
William Lallemandda8584c2020-05-14 10:14:37 +02002039/*
2040 * This function tries to create the new ckch_inst and their SNIs
William Lallemand30fcca12022-03-30 12:03:12 +02002041 *
2042 * /!\ don't forget to update __hlua_ckch_commit() if you changes things there. /!\
William Lallemandda8584c2020-05-14 10:14:37 +02002043 */
2044static int cli_io_handler_commit_cert(struct appctx *appctx)
2045{
Willy Tarreaua645b6a2022-05-04 19:58:00 +02002046 struct commit_cert_ctx *ctx = appctx->svcctx;
Willy Tarreauc12b3212022-05-27 11:08:15 +02002047 struct stconn *sc = appctx_sc(appctx);
William Lallemandda8584c2020-05-14 10:14:37 +02002048 int y = 0;
2049 char *err = NULL;
William Lallemandda8584c2020-05-14 10:14:37 +02002050 struct ckch_store *old_ckchs, *new_ckchs = NULL;
William Lallemand3b5a3a62022-03-29 14:29:31 +02002051 struct ckch_inst *ckchi;
William Lallemandda8584c2020-05-14 10:14:37 +02002052 struct buffer *trash = alloc_trash_chunk();
William Lallemandda8584c2020-05-14 10:14:37 +02002053
2054 if (trash == NULL)
2055 goto error;
2056
Willy Tarreau475e4632022-05-27 10:26:46 +02002057 if (unlikely(sc_ic(sc)->flags & (CF_WRITE_ERROR|CF_SHUTW)))
William Lallemandda8584c2020-05-14 10:14:37 +02002058 goto error;
2059
2060 while (1) {
Willy Tarreaucb1b4ed2022-05-05 08:15:27 +02002061 switch (ctx->state) {
2062 case CERT_ST_INIT:
William Lallemandda8584c2020-05-14 10:14:37 +02002063 /* This state just print the update message */
2064 chunk_printf(trash, "Committing %s", ckchs_transaction.path);
Willy Tarreaud0a06d52022-05-18 15:07:19 +02002065 if (applet_putchk(appctx, trash) == -1)
William Lallemandda8584c2020-05-14 10:14:37 +02002066 goto yield;
Willy Tarreaud0a06d52022-05-18 15:07:19 +02002067
Willy Tarreaucb1b4ed2022-05-05 08:15:27 +02002068 ctx->state = CERT_ST_GEN;
William Lallemandda8584c2020-05-14 10:14:37 +02002069 /* fallthrough */
Willy Tarreaucb1b4ed2022-05-05 08:15:27 +02002070 case CERT_ST_GEN:
William Lallemandda8584c2020-05-14 10:14:37 +02002071 /*
2072 * This state generates the ckch instances with their
2073 * sni_ctxs and SSL_CTX.
2074 *
2075 * Since the SSL_CTX generation can be CPU consumer, we
2076 * yield every 10 instances.
2077 */
2078
Willy Tarreaua645b6a2022-05-04 19:58:00 +02002079 old_ckchs = ctx->old_ckchs;
2080 new_ckchs = ctx->new_ckchs;
William Lallemandda8584c2020-05-14 10:14:37 +02002081
2082 if (!new_ckchs)
2083 continue;
2084
2085 /* get the next ckchi to regenerate */
Willy Tarreaua645b6a2022-05-04 19:58:00 +02002086 ckchi = ctx->next_ckchi;
William Lallemandda8584c2020-05-14 10:14:37 +02002087 /* we didn't start yet, set it to the first elem */
2088 if (ckchi == NULL)
2089 ckchi = LIST_ELEM(old_ckchs->ckch_inst.n, typeof(ckchi), by_ckchs);
2090
2091 /* walk through the old ckch_inst and creates new ckch_inst using the updated ckchs */
2092 list_for_each_entry_from(ckchi, &old_ckchs->ckch_inst, by_ckchs) {
2093 struct ckch_inst *new_inst;
William Lallemandda8584c2020-05-14 10:14:37 +02002094
2095 /* it takes a lot of CPU to creates SSL_CTXs, so we yield every 10 CKCH instances */
2096 if (y >= 10) {
2097 /* save the next ckchi to compute */
Willy Tarreaua645b6a2022-05-04 19:58:00 +02002098 ctx->next_ckchi = ckchi;
William Lallemandda8584c2020-05-14 10:14:37 +02002099 goto yield;
2100 }
2101
Remi Tricot-Le Bretonbfadc022021-02-24 12:20:48 +01002102 if (ckch_inst_rebuild(new_ckchs, ckchi, &new_inst, &err))
William Lallemandda8584c2020-05-14 10:14:37 +02002103 goto error;
2104
William Lallemandda8584c2020-05-14 10:14:37 +02002105 /* display one dot per new instance */
2106 chunk_appendf(trash, ".");
2107 /* link the new ckch_inst to the duplicate */
Willy Tarreau2b718102021-04-21 07:32:39 +02002108 LIST_APPEND(&new_ckchs->ckch_inst, &new_inst->by_ckchs);
William Lallemandda8584c2020-05-14 10:14:37 +02002109 y++;
2110 }
Willy Tarreaucb1b4ed2022-05-05 08:15:27 +02002111 ctx->state = CERT_ST_INSERT;
William Lallemandda8584c2020-05-14 10:14:37 +02002112 /* fallthrough */
Willy Tarreaucb1b4ed2022-05-05 08:15:27 +02002113 case CERT_ST_INSERT:
William Lallemandda8584c2020-05-14 10:14:37 +02002114 /* The generation is finished, we can insert everything */
2115
Willy Tarreaua645b6a2022-05-04 19:58:00 +02002116 old_ckchs = ctx->old_ckchs;
2117 new_ckchs = ctx->new_ckchs;
William Lallemandda8584c2020-05-14 10:14:37 +02002118
2119 if (!new_ckchs)
2120 continue;
2121
William Lallemand3b5a3a62022-03-29 14:29:31 +02002122 /* insert everything and remove the previous objects */
2123 ckch_store_replace(old_ckchs, new_ckchs);
William Lallemandda8584c2020-05-14 10:14:37 +02002124
Willy Tarreaucb1b4ed2022-05-05 08:15:27 +02002125 ctx->state = CERT_ST_FIN;
William Lallemandda8584c2020-05-14 10:14:37 +02002126 /* fallthrough */
Willy Tarreaucb1b4ed2022-05-05 08:15:27 +02002127 case CERT_ST_FIN:
William Lallemandda8584c2020-05-14 10:14:37 +02002128 /* we achieved the transaction, we can set everything to NULL */
Willy Tarreau61cfdf42021-02-20 10:46:51 +01002129 ha_free(&ckchs_transaction.path);
William Lallemandda8584c2020-05-14 10:14:37 +02002130 ckchs_transaction.new_ckchs = NULL;
2131 ckchs_transaction.old_ckchs = NULL;
2132 goto end;
2133 }
2134 }
2135end:
2136
2137 chunk_appendf(trash, "\n");
William Lallemandda8584c2020-05-14 10:14:37 +02002138 chunk_appendf(trash, "Success!\n");
Willy Tarreaud0a06d52022-05-18 15:07:19 +02002139 applet_putchk(appctx, trash);
William Lallemandda8584c2020-05-14 10:14:37 +02002140 free_trash_chunk(trash);
2141 /* success: call the release function and don't come back */
2142 return 1;
2143yield:
2144 /* store the state */
Willy Tarreaud0a06d52022-05-18 15:07:19 +02002145 applet_putchk(appctx, trash);
William Lallemandda8584c2020-05-14 10:14:37 +02002146 free_trash_chunk(trash);
Willy Tarreau4164eb92022-05-25 15:42:03 +02002147 applet_have_more_data(appctx); /* let's come back later */
William Lallemandda8584c2020-05-14 10:14:37 +02002148 return 0; /* should come back */
2149
2150error:
2151 /* spin unlock and free are done in the release function */
2152 if (trash) {
2153 chunk_appendf(trash, "\n%sFailed!\n", err);
Willy Tarreaud0a06d52022-05-18 15:07:19 +02002154 applet_putchk(appctx, trash);
William Lallemandda8584c2020-05-14 10:14:37 +02002155 free_trash_chunk(trash);
2156 }
2157 /* error: call the release function and don't come back */
2158 return 1;
2159}
2160
2161/*
2162 * Parsing function of 'commit ssl cert'
2163 */
2164static int cli_parse_commit_cert(char **args, char *payload, struct appctx *appctx, void *private)
2165{
Willy Tarreaua645b6a2022-05-04 19:58:00 +02002166 struct commit_cert_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
William Lallemandda8584c2020-05-14 10:14:37 +02002167 char *err = NULL;
2168
2169 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2170 return 1;
2171
2172 if (!*args[3])
2173 return cli_err(appctx, "'commit ssl cert expects a filename\n");
2174
2175 /* The operations on the CKCH architecture are locked so we can
2176 * manipulate ckch_store and ckch_inst */
2177 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2178 return cli_err(appctx, "Can't commit the certificate!\nOperations on certificates are currently locked!\n");
2179
2180 if (!ckchs_transaction.path) {
2181 memprintf(&err, "No ongoing transaction! !\n");
2182 goto error;
2183 }
2184
2185 if (strcmp(ckchs_transaction.path, args[3]) != 0) {
2186 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", ckchs_transaction.path, args[3]);
2187 goto error;
2188 }
2189
William Lallemand5685ccf2020-09-16 16:12:25 +02002190 /* if a certificate is here, a private key must be here too */
2191 if (ckchs_transaction.new_ckchs->ckch->cert && !ckchs_transaction.new_ckchs->ckch->key) {
2192 memprintf(&err, "The transaction must contain at least a certificate and a private key!\n");
2193 goto error;
2194 }
William Lallemanda9419522020-06-24 16:26:41 +02002195
William Lallemand5685ccf2020-09-16 16:12:25 +02002196 if (!X509_check_private_key(ckchs_transaction.new_ckchs->ckch->cert, ckchs_transaction.new_ckchs->ckch->key)) {
2197 memprintf(&err, "inconsistencies between private key and certificate loaded '%s'.\n", ckchs_transaction.path);
2198 goto error;
William Lallemandda8584c2020-05-14 10:14:37 +02002199 }
2200
2201 /* init the appctx structure */
Willy Tarreaucb1b4ed2022-05-05 08:15:27 +02002202 ctx->state = CERT_ST_INIT;
Willy Tarreaua645b6a2022-05-04 19:58:00 +02002203 ctx->next_ckchi = NULL;
2204 ctx->new_ckchs = ckchs_transaction.new_ckchs;
2205 ctx->old_ckchs = ckchs_transaction.old_ckchs;
William Lallemandda8584c2020-05-14 10:14:37 +02002206
2207 /* we don't unlock there, it will be unlock after the IO handler, in the release handler */
2208 return 0;
2209
2210error:
2211
2212 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2213 err = memprintf(&err, "%sCan't commit %s!\n", err ? err : "", args[3]);
2214
2215 return cli_dynerr(appctx, err);
2216}
2217
2218
2219
2220
2221/*
2222 * Parsing function of `set ssl cert`, it updates or creates a temporary ckch.
Willy Tarreau329f4b42022-05-04 20:05:55 +02002223 * It uses a set_cert_ctx context, and ckchs_transaction under a lock.
William Lallemandda8584c2020-05-14 10:14:37 +02002224 */
2225static int cli_parse_set_cert(char **args, char *payload, struct appctx *appctx, void *private)
2226{
Willy Tarreau329f4b42022-05-04 20:05:55 +02002227 struct set_cert_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
William Lallemandda8584c2020-05-14 10:14:37 +02002228 struct ckch_store *new_ckchs = NULL;
2229 struct ckch_store *old_ckchs = NULL;
2230 char *err = NULL;
2231 int i;
William Lallemandda8584c2020-05-14 10:14:37 +02002232 int errcode = 0;
2233 char *end;
William Lallemandff8bf982022-03-29 10:44:23 +02002234 struct cert_exts *cert_ext = &cert_exts[0]; /* default one, PEM */
William Lallemandda8584c2020-05-14 10:14:37 +02002235 struct cert_key_and_chain *ckch;
2236 struct buffer *buf;
2237
2238 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2239 return 1;
2240
William Lallemandda8584c2020-05-14 10:14:37 +02002241 if (!*args[3] || !payload)
2242 return cli_err(appctx, "'set ssl cert expects a filename and a certificate as a payload\n");
2243
2244 /* The operations on the CKCH architecture are locked so we can
2245 * manipulate ckch_store and ckch_inst */
2246 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2247 return cli_err(appctx, "Can't update the certificate!\nOperations on certificates are currently locked!\n");
2248
William Lallemand5ba80d62021-05-04 16:17:27 +02002249 if ((buf = alloc_trash_chunk()) == NULL) {
2250 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2251 errcode |= ERR_ALERT | ERR_FATAL;
2252 goto end;
2253 }
William Lallemande5ff4ad2020-06-08 09:40:37 +02002254
William Lallemandda8584c2020-05-14 10:14:37 +02002255 if (!chunk_strcpy(buf, args[3])) {
2256 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2257 errcode |= ERR_ALERT | ERR_FATAL;
2258 goto end;
2259 }
2260
2261 /* check which type of file we want to update */
William Lallemandff8bf982022-03-29 10:44:23 +02002262 for (i = 0; cert_exts[i].ext != NULL; i++) {
William Lallemandda8584c2020-05-14 10:14:37 +02002263 end = strrchr(buf->area, '.');
Tim Duesterhuse5ff1412021-01-02 22:31:53 +01002264 if (end && *cert_exts[i].ext && (strcmp(end + 1, cert_exts[i].ext) == 0)) {
William Lallemandda8584c2020-05-14 10:14:37 +02002265 *end = '\0';
William Lallemand089c1382020-10-23 17:35:12 +02002266 buf->data = strlen(buf->area);
William Lallemandff8bf982022-03-29 10:44:23 +02002267 cert_ext = &cert_exts[i];
William Lallemandda8584c2020-05-14 10:14:37 +02002268 break;
2269 }
2270 }
2271
Willy Tarreau329f4b42022-05-04 20:05:55 +02002272 ctx->old_ckchs = NULL;
2273 ctx->new_ckchs = NULL;
William Lallemandda8584c2020-05-14 10:14:37 +02002274
2275 /* if there is an ongoing transaction */
2276 if (ckchs_transaction.path) {
William Lallemandda8584c2020-05-14 10:14:37 +02002277 /* if there is an ongoing transaction, check if this is the same file */
2278 if (strcmp(ckchs_transaction.path, buf->area) != 0) {
William Lallemand089c1382020-10-23 17:35:12 +02002279 /* we didn't find the transaction, must try more cases below */
2280
2281 /* 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 +02002282 if (cert_ext->type != CERT_TYPE_PEM && global_ssl.extra_files_noext) {
William Lallemand089c1382020-10-23 17:35:12 +02002283 if (!chunk_strcat(buf, ".crt")) {
2284 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2285 errcode |= ERR_ALERT | ERR_FATAL;
2286 goto end;
2287 }
2288
2289 if (strcmp(ckchs_transaction.path, buf->area) != 0) {
2290 /* remove .crt of the error message */
2291 *(b_orig(buf) + b_data(buf) + strlen(".crt")) = '\0';
2292 b_sub(buf, strlen(".crt"));
2293
2294 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", ckchs_transaction.path, buf->area);
2295 errcode |= ERR_ALERT | ERR_FATAL;
2296 goto end;
2297 }
2298 }
William Lallemandda8584c2020-05-14 10:14:37 +02002299 }
2300
Willy Tarreau329f4b42022-05-04 20:05:55 +02002301 ctx->old_ckchs = ckchs_transaction.new_ckchs;
William Lallemandda8584c2020-05-14 10:14:37 +02002302
2303 } else {
William Lallemandda8584c2020-05-14 10:14:37 +02002304
William Lallemand95fefa12020-09-09 12:01:33 +02002305 /* lookup for the certificate in the tree */
Willy Tarreau329f4b42022-05-04 20:05:55 +02002306 ctx->old_ckchs = ckchs_lookup(buf->area);
William Lallemand089c1382020-10-23 17:35:12 +02002307
Willy Tarreau329f4b42022-05-04 20:05:55 +02002308 if (!ctx->old_ckchs) {
William Lallemand089c1382020-10-23 17:35:12 +02002309 /* 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 +02002310 if (cert_ext->type != CERT_TYPE_PEM && global_ssl.extra_files_noext) {
William Lallemand089c1382020-10-23 17:35:12 +02002311 if (!chunk_strcat(buf, ".crt")) {
2312 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2313 errcode |= ERR_ALERT | ERR_FATAL;
2314 goto end;
2315 }
Willy Tarreau329f4b42022-05-04 20:05:55 +02002316 ctx->old_ckchs = ckchs_lookup(buf->area);
William Lallemand089c1382020-10-23 17:35:12 +02002317 }
2318 }
William Lallemandda8584c2020-05-14 10:14:37 +02002319 }
2320
Willy Tarreau329f4b42022-05-04 20:05:55 +02002321 if (!ctx->old_ckchs) {
William Lallemandda8584c2020-05-14 10:14:37 +02002322 memprintf(&err, "%sCan't replace a certificate which is not referenced by the configuration!\n",
2323 err ? err : "");
2324 errcode |= ERR_ALERT | ERR_FATAL;
2325 goto end;
2326 }
2327
Willy Tarreau329f4b42022-05-04 20:05:55 +02002328 if (!ctx->path) {
William Lallemandda8584c2020-05-14 10:14:37 +02002329 /* this is a new transaction, set the path of the transaction */
Willy Tarreau329f4b42022-05-04 20:05:55 +02002330 ctx->path = strdup(ctx->old_ckchs->path);
2331 if (!ctx->path) {
William Lallemandda8584c2020-05-14 10:14:37 +02002332 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2333 errcode |= ERR_ALERT | ERR_FATAL;
2334 goto end;
2335 }
2336 }
2337
Willy Tarreau329f4b42022-05-04 20:05:55 +02002338 old_ckchs = ctx->old_ckchs;
William Lallemandda8584c2020-05-14 10:14:37 +02002339
2340 /* duplicate the ckch store */
2341 new_ckchs = ckchs_dup(old_ckchs);
2342 if (!new_ckchs) {
2343 memprintf(&err, "%sCannot allocate memory!\n",
2344 err ? err : "");
2345 errcode |= ERR_ALERT | ERR_FATAL;
2346 goto end;
2347 }
2348
William Lallemand95fefa12020-09-09 12:01:33 +02002349 ckch = new_ckchs->ckch;
William Lallemandda8584c2020-05-14 10:14:37 +02002350
2351 /* appply the change on the duplicate */
William Lallemandff8bf982022-03-29 10:44:23 +02002352 if (cert_ext->load(buf->area, payload, ckch, &err) != 0) {
William Lallemandda8584c2020-05-14 10:14:37 +02002353 memprintf(&err, "%sCan't load the payload\n", err ? err : "");
2354 errcode |= ERR_ALERT | ERR_FATAL;
2355 goto end;
2356 }
2357
Willy Tarreau329f4b42022-05-04 20:05:55 +02002358 ctx->new_ckchs = new_ckchs;
William Lallemandda8584c2020-05-14 10:14:37 +02002359
2360 /* we succeed, we can save the ckchs in the transaction */
2361
2362 /* if there wasn't a transaction, update the old ckchs */
2363 if (!ckchs_transaction.old_ckchs) {
Willy Tarreau329f4b42022-05-04 20:05:55 +02002364 ckchs_transaction.old_ckchs = ctx->old_ckchs;
2365 ckchs_transaction.path = ctx->path;
William Lallemandda8584c2020-05-14 10:14:37 +02002366 err = memprintf(&err, "Transaction created for certificate %s!\n", ckchs_transaction.path);
2367 } else {
2368 err = memprintf(&err, "Transaction updated for certificate %s!\n", ckchs_transaction.path);
2369
2370 }
2371
2372 /* free the previous ckchs if there was a transaction */
2373 ckch_store_free(ckchs_transaction.new_ckchs);
2374
Willy Tarreau329f4b42022-05-04 20:05:55 +02002375 ckchs_transaction.new_ckchs = ctx->new_ckchs;
William Lallemandda8584c2020-05-14 10:14:37 +02002376
2377
2378 /* creates the SNI ctxs later in the IO handler */
2379
2380end:
2381 free_trash_chunk(buf);
2382
2383 if (errcode & ERR_CODE) {
Willy Tarreau329f4b42022-05-04 20:05:55 +02002384 ckch_store_free(ctx->new_ckchs);
2385 ctx->new_ckchs = NULL;
2386 ctx->old_ckchs = NULL;
2387 ha_free(&ctx->path);
William Lallemandda8584c2020-05-14 10:14:37 +02002388
2389 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2390 return cli_dynerr(appctx, memprintf(&err, "%sCan't update %s!\n", err ? err : "", args[3]));
2391 } else {
William Lallemandda8584c2020-05-14 10:14:37 +02002392 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2393 return cli_dynmsg(appctx, LOG_NOTICE, err);
2394 }
2395 /* TODO: handle the ERR_WARN which are not handled because of the io_handler */
2396}
2397
2398/* parsing function of 'abort ssl cert' */
2399static int cli_parse_abort_cert(char **args, char *payload, struct appctx *appctx, void *private)
2400{
2401 char *err = NULL;
2402
2403 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2404 return 1;
2405
2406 if (!*args[3])
2407 return cli_err(appctx, "'abort ssl cert' expects a filename\n");
2408
2409 /* The operations on the CKCH architecture are locked so we can
2410 * manipulate ckch_store and ckch_inst */
2411 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2412 return cli_err(appctx, "Can't abort!\nOperations on certificates are currently locked!\n");
2413
2414 if (!ckchs_transaction.path) {
2415 memprintf(&err, "No ongoing transaction!\n");
2416 goto error;
2417 }
2418
2419 if (strcmp(ckchs_transaction.path, args[3]) != 0) {
2420 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to abort a transaction for '%s'\n", ckchs_transaction.path, args[3]);
2421 goto error;
2422 }
2423
2424 /* Only free the ckchs there, because the SNI and instances were not generated yet */
2425 ckch_store_free(ckchs_transaction.new_ckchs);
2426 ckchs_transaction.new_ckchs = NULL;
William Lallemandda8584c2020-05-14 10:14:37 +02002427 ckchs_transaction.old_ckchs = NULL;
Willy Tarreau61cfdf42021-02-20 10:46:51 +01002428 ha_free(&ckchs_transaction.path);
William Lallemandda8584c2020-05-14 10:14:37 +02002429
2430 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2431
2432 err = memprintf(&err, "Transaction aborted for certificate '%s'!\n", args[3]);
2433 return cli_dynmsg(appctx, LOG_NOTICE, err);
2434
2435error:
2436 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2437
2438 return cli_dynerr(appctx, err);
2439}
2440
2441/* parsing function of 'new ssl cert' */
2442static int cli_parse_new_cert(char **args, char *payload, struct appctx *appctx, void *private)
2443{
2444 struct ckch_store *store;
2445 char *err = NULL;
2446 char *path;
2447
2448 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2449 return 1;
2450
2451 if (!*args[3])
2452 return cli_err(appctx, "'new ssl cert' expects a filename\n");
2453
2454 path = args[3];
2455
2456 /* The operations on the CKCH architecture are locked so we can
2457 * manipulate ckch_store and ckch_inst */
2458 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2459 return cli_err(appctx, "Can't create a certificate!\nOperations on certificates are currently locked!\n");
2460
2461 store = ckchs_lookup(path);
2462 if (store != NULL) {
2463 memprintf(&err, "Certificate '%s' already exists!\n", path);
2464 store = NULL; /* we don't want to free it */
2465 goto error;
2466 }
2467 /* we won't support multi-certificate bundle here */
William Lallemandbd8e6ed2020-09-16 16:08:08 +02002468 store = ckch_store_new(path);
William Lallemandda8584c2020-05-14 10:14:37 +02002469 if (!store) {
2470 memprintf(&err, "unable to allocate memory.\n");
2471 goto error;
2472 }
2473
2474 /* insert into the ckchs tree */
2475 ebst_insert(&ckchs_tree, &store->node);
2476 memprintf(&err, "New empty certificate store '%s'!\n", args[3]);
2477
2478 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2479 return cli_dynmsg(appctx, LOG_NOTICE, err);
2480error:
2481 free(store);
2482 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2483 return cli_dynerr(appctx, err);
2484}
2485
2486/* parsing function of 'del ssl cert' */
2487static int cli_parse_del_cert(char **args, char *payload, struct appctx *appctx, void *private)
2488{
2489 struct ckch_store *store;
2490 char *err = NULL;
2491 char *filename;
2492
2493 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2494 return 1;
2495
2496 if (!*args[3])
2497 return cli_err(appctx, "'del ssl cert' expects a certificate name\n");
2498
2499 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2500 return cli_err(appctx, "Can't delete the certificate!\nOperations on certificates are currently locked!\n");
2501
2502 filename = args[3];
2503
2504 store = ckchs_lookup(filename);
2505 if (store == NULL) {
2506 memprintf(&err, "certificate '%s' doesn't exist!\n", filename);
2507 goto error;
2508 }
2509 if (!LIST_ISEMPTY(&store->ckch_inst)) {
2510 memprintf(&err, "certificate '%s' in use, can't be deleted!\n", filename);
2511 goto error;
2512 }
2513
2514 ebmb_delete(&store->node);
2515 ckch_store_free(store);
2516
2517 memprintf(&err, "Certificate '%s' deleted!\n", filename);
2518
2519 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2520 return cli_dynmsg(appctx, LOG_NOTICE, err);
2521
2522error:
2523 memprintf(&err, "Can't remove the certificate: %s\n", err ? err : "");
2524 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2525 return cli_dynerr(appctx, err);
2526}
2527
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002528
Remi Tricot-Le Breton9f40fe02021-03-16 16:21:27 +01002529
2530/* parsing function of 'new ssl ca-file' */
2531static int cli_parse_new_cafile(char **args, char *payload, struct appctx *appctx, void *private)
2532{
2533 struct cafile_entry *cafile_entry;
2534 char *err = NULL;
2535 char *path;
2536
2537 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2538 return 1;
2539
2540 if (!*args[3])
2541 return cli_err(appctx, "'new ssl ca-file' expects a filename\n");
2542
2543 path = args[3];
2544
2545 /* The operations on the CKCH architecture are locked so we can
2546 * manipulate ckch_store and ckch_inst */
2547 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2548 return cli_err(appctx, "Can't create a CA file!\nOperations on certificates are currently locked!\n");
2549
2550 cafile_entry = ssl_store_get_cafile_entry(path, 0);
2551 if (cafile_entry) {
2552 memprintf(&err, "CA file '%s' already exists!\n", path);
2553 goto error;
2554 }
2555
2556 cafile_entry = ssl_store_create_cafile_entry(path, NULL, CAFILE_CERT);
2557 if (!cafile_entry) {
2558 memprintf(&err, "%sCannot allocate memory!\n",
2559 err ? err : "");
2560 goto error;
2561 }
2562
2563 /* Add the newly created cafile_entry to the tree so that
2564 * any new ckch instance created from now can use it. */
2565 if (ssl_store_add_uncommitted_cafile_entry(cafile_entry))
2566 goto error;
2567
2568 memprintf(&err, "New CA file created '%s'!\n", path);
2569
2570 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2571 return cli_dynmsg(appctx, LOG_NOTICE, err);
2572error:
2573 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2574 return cli_dynerr(appctx, err);
2575}
2576
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002577/*
2578 * Parsing function of `set ssl ca-file`
2579 */
2580static int cli_parse_set_cafile(char **args, char *payload, struct appctx *appctx, void *private)
2581{
Willy Tarreaua37693f2022-05-04 20:12:55 +02002582 struct set_cafile_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002583 char *err = NULL;
2584 int errcode = 0;
2585 struct buffer *buf;
2586
2587 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2588 return 1;
2589
2590 if (!*args[3] || !payload)
2591 return cli_err(appctx, "'set ssl ca-file expects a filename and CAs as a payload\n");
2592
2593 /* The operations on the CKCH architecture are locked so we can
2594 * manipulate ckch_store and ckch_inst */
2595 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2596 return cli_err(appctx, "Can't update the CA file!\nOperations on certificates are currently locked!\n");
2597
2598 if ((buf = alloc_trash_chunk()) == NULL) {
2599 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2600 errcode |= ERR_ALERT | ERR_FATAL;
2601 goto end;
2602 }
2603
2604 if (!chunk_strcpy(buf, args[3])) {
2605 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2606 errcode |= ERR_ALERT | ERR_FATAL;
2607 goto end;
2608 }
2609
Willy Tarreaua37693f2022-05-04 20:12:55 +02002610 ctx->old_cafile_entry = NULL;
2611 ctx->new_cafile_entry = NULL;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002612
2613 /* if there is an ongoing transaction */
2614 if (cafile_transaction.path) {
2615 /* if there is an ongoing transaction, check if this is the same file */
2616 if (strcmp(cafile_transaction.path, buf->area) != 0) {
2617 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", cafile_transaction.path, buf->area);
2618 errcode |= ERR_ALERT | ERR_FATAL;
2619 goto end;
2620 }
Willy Tarreaua37693f2022-05-04 20:12:55 +02002621 ctx->old_cafile_entry = cafile_transaction.old_cafile_entry;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002622 }
2623 else {
2624 /* lookup for the certificate in the tree */
Willy Tarreaua37693f2022-05-04 20:12:55 +02002625 ctx->old_cafile_entry = ssl_store_get_cafile_entry(buf->area, 0);
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002626 }
2627
Willy Tarreaua37693f2022-05-04 20:12:55 +02002628 if (!ctx->old_cafile_entry) {
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002629 memprintf(&err, "%sCan't replace a CA file which is not referenced by the configuration!\n",
2630 err ? err : "");
2631 errcode |= ERR_ALERT | ERR_FATAL;
2632 goto end;
2633 }
2634
Willy Tarreaua37693f2022-05-04 20:12:55 +02002635 if (!ctx->path) {
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002636 /* this is a new transaction, set the path of the transaction */
Willy Tarreaua37693f2022-05-04 20:12:55 +02002637 ctx->path = strdup(ctx->old_cafile_entry->path);
2638 if (!ctx->path) {
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002639 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2640 errcode |= ERR_ALERT | ERR_FATAL;
2641 goto end;
2642 }
2643 }
2644
Willy Tarreaua37693f2022-05-04 20:12:55 +02002645 if (ctx->new_cafile_entry)
2646 ssl_store_delete_cafile_entry(ctx->new_cafile_entry);
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002647
2648 /* Create a new cafile_entry without adding it to the cafile tree. */
Willy Tarreaua37693f2022-05-04 20:12:55 +02002649 ctx->new_cafile_entry = ssl_store_create_cafile_entry(ctx->path, NULL, CAFILE_CERT);
2650 if (!ctx->new_cafile_entry) {
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002651 memprintf(&err, "%sCannot allocate memory!\n",
2652 err ? err : "");
2653 errcode |= ERR_ALERT | ERR_FATAL;
2654 goto end;
2655 }
2656
2657 /* Fill the new entry with the new CAs. */
Willy Tarreaua37693f2022-05-04 20:12:55 +02002658 if (ssl_store_load_ca_from_buf(ctx->new_cafile_entry, payload)) {
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002659 memprintf(&err, "%sInvalid payload\n", err ? err : "");
2660 errcode |= ERR_ALERT | ERR_FATAL;
2661 goto end;
2662 }
2663
2664 /* we succeed, we can save the ca in the transaction */
2665
2666 /* if there wasn't a transaction, update the old CA */
2667 if (!cafile_transaction.old_cafile_entry) {
Willy Tarreaua37693f2022-05-04 20:12:55 +02002668 cafile_transaction.old_cafile_entry = ctx->old_cafile_entry;
2669 cafile_transaction.path = ctx->path;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002670 err = memprintf(&err, "transaction created for CA %s!\n", cafile_transaction.path);
2671 } else {
2672 err = memprintf(&err, "transaction updated for CA %s!\n", cafile_transaction.path);
2673 }
2674
2675 /* free the previous CA if there was a transaction */
2676 ssl_store_delete_cafile_entry(cafile_transaction.new_cafile_entry);
2677
Willy Tarreaua37693f2022-05-04 20:12:55 +02002678 cafile_transaction.new_cafile_entry = ctx->new_cafile_entry;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002679
2680 /* creates the SNI ctxs later in the IO handler */
2681
2682end:
2683 free_trash_chunk(buf);
2684
2685 if (errcode & ERR_CODE) {
Willy Tarreaua37693f2022-05-04 20:12:55 +02002686 ssl_store_delete_cafile_entry(ctx->new_cafile_entry);
2687 ctx->new_cafile_entry = NULL;
2688 ctx->old_cafile_entry = NULL;
2689 ha_free(&ctx->path);
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002690 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2691 return cli_dynerr(appctx, memprintf(&err, "%sCan't update %s!\n", err ? err : "", args[3]));
2692 } else {
2693
2694 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2695 return cli_dynmsg(appctx, LOG_NOTICE, err);
2696 }
2697}
2698
2699
2700/*
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002701 * Parsing function of 'commit ssl ca-file'.
2702 * It uses a commit_cacrlfile_ctx that's also shared with "commit ssl crl-file".
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002703 */
2704static int cli_parse_commit_cafile(char **args, char *payload, struct appctx *appctx, void *private)
2705{
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002706 struct commit_cacrlfile_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002707 char *err = NULL;
2708
2709 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2710 return 1;
2711
2712 if (!*args[3])
2713 return cli_err(appctx, "'commit ssl ca-file expects a filename\n");
2714
2715 /* The operations on the CKCH architecture are locked so we can
2716 * manipulate ckch_store and ckch_inst */
2717 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2718 return cli_err(appctx, "Can't commit the CA file!\nOperations on certificates are currently locked!\n");
2719
2720 if (!cafile_transaction.path) {
2721 memprintf(&err, "No ongoing transaction! !\n");
2722 goto error;
2723 }
2724
2725 if (strcmp(cafile_transaction.path, args[3]) != 0) {
2726 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", cafile_transaction.path, args[3]);
2727 goto error;
2728 }
2729 /* init the appctx structure */
Willy Tarreau1d6dd802022-05-05 08:17:29 +02002730 ctx->state = CACRL_ST_INIT;
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002731 ctx->next_ckchi_link = NULL;
2732 ctx->old_cafile_entry = cafile_transaction.old_cafile_entry;
2733 ctx->new_cafile_entry = cafile_transaction.new_cafile_entry;
2734 ctx->cafile_type = CAFILE_CERT;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002735
2736 return 0;
2737
2738error:
2739
2740 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2741 err = memprintf(&err, "%sCan't commit %s!\n", err ? err : "", args[3]);
2742
2743 return cli_dynerr(appctx, err);
2744}
2745
2746enum {
2747 CREATE_NEW_INST_OK = 0,
2748 CREATE_NEW_INST_YIELD = -1,
2749 CREATE_NEW_INST_ERR = -2
2750};
2751
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002752/* this is called by the I/O handler for "commit cafile"/"commit crlfile", and
2753 * it uses a context from cmomit_cacrlfile_ctx.
2754 */
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002755static inline int __create_new_instance(struct appctx *appctx, struct ckch_inst *ckchi, int *count,
Remi Tricot-Le Bretond75b99e2021-05-17 11:45:55 +02002756 struct buffer *trash, char **err)
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002757{
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002758 struct commit_cacrlfile_ctx *ctx = appctx->svcctx;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002759 struct ckch_inst *new_inst;
2760
2761 /* it takes a lot of CPU to creates SSL_CTXs, so we yield every 10 CKCH instances */
2762 if (*count >= 10) {
2763 /* save the next ckchi to compute */
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002764 ctx->next_ckchi = ckchi;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002765 return CREATE_NEW_INST_YIELD;
2766 }
2767
2768 /* Rebuild a new ckch instance that uses the same ckch_store
2769 * than a reference ckchi instance but will use a new CA file. */
Remi Tricot-Le Bretond75b99e2021-05-17 11:45:55 +02002770 if (ckch_inst_rebuild(ckchi->ckch_store, ckchi, &new_inst, err))
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002771 return CREATE_NEW_INST_ERR;
2772
2773 /* display one dot per new instance */
2774 chunk_appendf(trash, ".");
2775 ++(*count);
2776
2777 return CREATE_NEW_INST_OK;
2778}
2779
2780/*
2781 * This function tries to create new ckch instances and their SNIs using a newly
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002782 * set certificate authority (CA file) or a newly set Certificate Revocation
2783 * List (CRL), depending on the command being called.
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002784 */
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002785static int cli_io_handler_commit_cafile_crlfile(struct appctx *appctx)
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002786{
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002787 struct commit_cacrlfile_ctx *ctx = appctx->svcctx;
Willy Tarreauc12b3212022-05-27 11:08:15 +02002788 struct stconn *sc = appctx_sc(appctx);
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002789 int y = 0;
2790 char *err = NULL;
Remi Tricot-Le Bretona6b27842021-05-18 10:06:00 +02002791 struct cafile_entry *old_cafile_entry = NULL, *new_cafile_entry = NULL;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002792 struct ckch_inst_link *ckchi_link;
2793 struct buffer *trash = alloc_trash_chunk();
2794
2795 if (trash == NULL)
2796 goto error;
2797
Willy Tarreau475e4632022-05-27 10:26:46 +02002798 if (unlikely(sc_ic(sc)->flags & (CF_WRITE_ERROR|CF_SHUTW)))
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002799 goto error;
2800
2801 while (1) {
Willy Tarreau1d6dd802022-05-05 08:17:29 +02002802 switch (ctx->state) {
2803 case CACRL_ST_INIT:
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002804 /* This state just print the update message */
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002805 switch (ctx->cafile_type) {
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002806 case CAFILE_CERT:
2807 chunk_printf(trash, "Committing %s", cafile_transaction.path);
2808 break;
2809 case CAFILE_CRL:
2810 chunk_printf(trash, "Committing %s", crlfile_transaction.path);
2811 break;
2812 default:
2813 goto error;
2814 }
Willy Tarreaud0a06d52022-05-18 15:07:19 +02002815 if (applet_putchk(appctx, trash) == -1)
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002816 goto yield;
Willy Tarreaud0a06d52022-05-18 15:07:19 +02002817
Willy Tarreau1d6dd802022-05-05 08:17:29 +02002818 ctx->state = CACRL_ST_GEN;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002819 /* fallthrough */
Willy Tarreau1d6dd802022-05-05 08:17:29 +02002820 case CACRL_ST_GEN:
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002821 /*
2822 * This state generates the ckch instances with their
2823 * sni_ctxs and SSL_CTX.
2824 *
2825 * Since the SSL_CTX generation can be CPU consumer, we
2826 * yield every 10 instances.
2827 */
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002828 switch (ctx->cafile_type) {
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002829 case CAFILE_CERT:
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002830 old_cafile_entry = ctx->old_cafile_entry;
2831 new_cafile_entry = ctx->new_cafile_entry;
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002832 break;
2833 case CAFILE_CRL:
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002834 old_cafile_entry = ctx->old_crlfile_entry;
2835 new_cafile_entry = ctx->new_crlfile_entry;
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002836 break;
2837 }
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002838 if (!new_cafile_entry)
2839 continue;
2840
2841 /* get the next ckchi to regenerate */
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002842 ckchi_link = ctx->next_ckchi_link;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002843 /* we didn't start yet, set it to the first elem */
2844 if (ckchi_link == NULL) {
2845 ckchi_link = LIST_ELEM(old_cafile_entry->ckch_inst_link.n, typeof(ckchi_link), list);
2846 /* Add the newly created cafile_entry to the tree so that
2847 * any new ckch instance created from now can use it. */
2848 if (ssl_store_add_uncommitted_cafile_entry(new_cafile_entry))
2849 goto error;
2850 }
2851
2852 list_for_each_entry_from(ckchi_link, &old_cafile_entry->ckch_inst_link, list) {
Remi Tricot-Le Bretond75b99e2021-05-17 11:45:55 +02002853 switch (__create_new_instance(appctx, ckchi_link->ckch_inst, &y, trash, &err)) {
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002854 case CREATE_NEW_INST_YIELD:
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002855 ctx->next_ckchi_link = ckchi_link;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002856 goto yield;
2857 case CREATE_NEW_INST_ERR:
2858 goto error;
2859 default: break;
2860 }
2861 }
2862
Willy Tarreau1d6dd802022-05-05 08:17:29 +02002863 ctx->state = CACRL_ST_INSERT;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002864 /* fallthrough */
Willy Tarreau1d6dd802022-05-05 08:17:29 +02002865 case CACRL_ST_INSERT:
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002866 /* The generation is finished, we can insert everything */
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002867 switch (ctx->cafile_type) {
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002868 case CAFILE_CERT:
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002869 old_cafile_entry = ctx->old_cafile_entry;
2870 new_cafile_entry = ctx->new_cafile_entry;
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002871 break;
2872 case CAFILE_CRL:
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002873 old_cafile_entry = ctx->old_crlfile_entry;
2874 new_cafile_entry = ctx->new_crlfile_entry;
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002875 break;
2876 }
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002877 if (!new_cafile_entry)
2878 continue;
2879
2880 /* insert the new ckch_insts in the crtlist_entry */
2881 list_for_each_entry(ckchi_link, &new_cafile_entry->ckch_inst_link, list) {
2882 if (ckchi_link->ckch_inst->crtlist_entry)
2883 LIST_INSERT(&ckchi_link->ckch_inst->crtlist_entry->ckch_inst,
2884 &ckchi_link->ckch_inst->by_crtlist_entry);
2885 }
2886
2887 /* First, we insert every new SNIs in the trees, also replace the default_ctx */
2888 list_for_each_entry(ckchi_link, &new_cafile_entry->ckch_inst_link, list) {
2889 __ssl_sock_load_new_ckch_instance(ckchi_link->ckch_inst);
2890 }
2891
2892 /* delete the old sni_ctx, the old ckch_insts and the ckch_store */
2893 list_for_each_entry(ckchi_link, &old_cafile_entry->ckch_inst_link, list) {
2894 __ckch_inst_free_locked(ckchi_link->ckch_inst);
2895 }
2896
2897
2898 /* Remove the old cafile entry from the tree */
2899 ebmb_delete(&old_cafile_entry->node);
2900 ssl_store_delete_cafile_entry(old_cafile_entry);
2901
Willy Tarreau1d6dd802022-05-05 08:17:29 +02002902 ctx->state = CACRL_ST_FIN;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002903 /* fallthrough */
Willy Tarreau1d6dd802022-05-05 08:17:29 +02002904 case CACRL_ST_FIN:
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002905 /* we achieved the transaction, we can set everything to NULL */
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002906 switch (ctx->cafile_type) {
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002907 case CAFILE_CERT:
2908 ha_free(&cafile_transaction.path);
2909 cafile_transaction.old_cafile_entry = NULL;
2910 cafile_transaction.new_cafile_entry = NULL;
2911 break;
2912 case CAFILE_CRL:
2913 ha_free(&crlfile_transaction.path);
2914 crlfile_transaction.old_crlfile_entry = NULL;
2915 crlfile_transaction.new_crlfile_entry = NULL;
2916 break;
2917 }
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002918 goto end;
2919 }
2920 }
2921end:
2922
2923 chunk_appendf(trash, "\n");
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002924 chunk_appendf(trash, "Success!\n");
Willy Tarreaud0a06d52022-05-18 15:07:19 +02002925 applet_putchk(appctx, trash);
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002926 free_trash_chunk(trash);
2927 /* success: call the release function and don't come back */
2928 return 1;
2929yield:
2930 /* store the state */
Willy Tarreaud0a06d52022-05-18 15:07:19 +02002931 applet_putchk(appctx, trash);
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002932 free_trash_chunk(trash);
Willy Tarreau4164eb92022-05-25 15:42:03 +02002933 applet_have_more_data(appctx); /* let's come back later */
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002934 return 0; /* should come back */
2935
2936error:
2937 /* spin unlock and free are done in the release function */
2938 if (trash) {
2939 chunk_appendf(trash, "\n%sFailed!\n", err);
Willy Tarreaud0a06d52022-05-18 15:07:19 +02002940 applet_putchk(appctx, trash);
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002941 free_trash_chunk(trash);
2942 }
2943 /* error: call the release function and don't come back */
2944 return 1;
2945}
2946
Remi Tricot-Le Bretond5fd09d2021-03-11 10:22:52 +01002947
2948/* parsing function of 'abort ssl ca-file' */
2949static int cli_parse_abort_cafile(char **args, char *payload, struct appctx *appctx, void *private)
2950{
2951 char *err = NULL;
2952
2953 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2954 return 1;
2955
2956 if (!*args[3])
2957 return cli_err(appctx, "'abort ssl ca-file' expects a filename\n");
2958
2959 /* The operations on the CKCH architecture are locked so we can
2960 * manipulate ckch_store and ckch_inst */
2961 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2962 return cli_err(appctx, "Can't abort!\nOperations on certificates are currently locked!\n");
2963
2964 if (!cafile_transaction.path) {
2965 memprintf(&err, "No ongoing transaction!\n");
2966 goto error;
2967 }
2968
2969 if (strcmp(cafile_transaction.path, args[3]) != 0) {
2970 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to abort a transaction for '%s'\n", cafile_transaction.path, args[3]);
2971 goto error;
2972 }
2973
2974 /* Only free the uncommitted cafile_entry here, because the SNI and instances were not generated yet */
2975 ssl_store_delete_cafile_entry(cafile_transaction.new_cafile_entry);
2976 cafile_transaction.new_cafile_entry = NULL;
2977 cafile_transaction.old_cafile_entry = NULL;
2978 ha_free(&cafile_transaction.path);
2979
2980 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2981
2982 err = memprintf(&err, "Transaction aborted for certificate '%s'!\n", args[3]);
2983 return cli_dynmsg(appctx, LOG_NOTICE, err);
2984
2985error:
2986 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2987
2988 return cli_dynerr(appctx, err);
2989}
2990
Willy Tarreau821c3b02022-05-04 15:47:39 +02002991/* release function of the `commit ssl ca-file' command, free things and unlock the spinlock.
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002992 * It uses a commit_cacrlfile_ctx context.
Willy Tarreau821c3b02022-05-04 15:47:39 +02002993 */
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002994static void cli_release_commit_cafile(struct appctx *appctx)
2995{
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002996 struct commit_cacrlfile_ctx *ctx = appctx->svcctx;
2997
Willy Tarreau1d6dd802022-05-05 08:17:29 +02002998 if (ctx->state != CACRL_ST_FIN) {
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002999 struct cafile_entry *new_cafile_entry = ctx->new_cafile_entry;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01003000
3001 /* Remove the uncommitted cafile_entry from the tree. */
3002 ebmb_delete(&new_cafile_entry->node);
3003 ssl_store_delete_cafile_entry(new_cafile_entry);
3004 }
3005 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3006}
3007
3008
Willy Tarreau821c3b02022-05-04 15:47:39 +02003009/* IO handler of details "show ssl ca-file <filename[:index]>".
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003010 * It uses a show_cafile_ctx context, and the global
3011 * cafile_transaction.new_cafile_entry in read-only.
Willy Tarreau821c3b02022-05-04 15:47:39 +02003012 */
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003013static int cli_io_handler_show_cafile_detail(struct appctx *appctx)
3014{
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003015 struct show_cafile_ctx *ctx = appctx->svcctx;
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003016 struct cafile_entry *cafile_entry = ctx->cur_cafile_entry;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003017 struct buffer *out = alloc_trash_chunk();
William Lallemand03a32e52022-04-26 18:17:15 +02003018 int i = 0;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003019 X509 *cert;
3020 STACK_OF(X509_OBJECT) *objs;
3021 int retval = 0;
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003022 int ca_index = ctx->ca_index;
3023 int show_all = ctx->show_all;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003024
3025 if (!out)
3026 goto end_no_putchk;
3027
3028 chunk_appendf(out, "Filename: ");
3029 if (cafile_entry == cafile_transaction.new_cafile_entry)
3030 chunk_appendf(out, "*");
3031 chunk_appendf(out, "%s\n", cafile_entry->path);
3032
3033 chunk_appendf(out, "Status: ");
3034 if (!cafile_entry->ca_store)
3035 chunk_appendf(out, "Empty\n");
3036 else if (LIST_ISEMPTY(&cafile_entry->ckch_inst_link))
3037 chunk_appendf(out, "Unused\n");
3038 else
3039 chunk_appendf(out, "Used\n");
3040
3041 if (!cafile_entry->ca_store)
3042 goto end;
3043
3044 objs = X509_STORE_get0_objects(cafile_entry->ca_store);
William Lallemand03a32e52022-04-26 18:17:15 +02003045 for (i = ca_index; i < sk_X509_OBJECT_num(objs); i++) {
3046
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003047 cert = X509_OBJECT_get0_X509(sk_X509_OBJECT_value(objs, i));
3048 if (!cert)
3049 continue;
3050
William Lallemand03a32e52022-04-26 18:17:15 +02003051 /* file starts at line 1 */
Remi Tricot-Le Bretone8041fe2022-04-05 16:44:21 +02003052 chunk_appendf(out, " \nCertificate #%d:\n", i+1);
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003053 retval = show_cert_detail(cert, NULL, out);
3054 if (retval < 0)
3055 goto end_no_putchk;
William Lallemand03a32e52022-04-26 18:17:15 +02003056 else if (retval)
3057 goto yield;
3058
Willy Tarreaud0a06d52022-05-18 15:07:19 +02003059 if (applet_putchk(appctx, out) == -1)
William Lallemand03a32e52022-04-26 18:17:15 +02003060 goto yield;
William Lallemand03a32e52022-04-26 18:17:15 +02003061
3062 if (!show_all) /* only need to dump one certificate */
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003063 goto end;
3064 }
3065
3066end:
William Lallemand03a32e52022-04-26 18:17:15 +02003067 free_trash_chunk(out);
3068 return 1; /* end, don't come back */
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003069
3070end_no_putchk:
3071 free_trash_chunk(out);
3072 return 1;
3073yield:
William Lallemand03a32e52022-04-26 18:17:15 +02003074 /* save the current state */
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003075 ctx->ca_index = i;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003076 free_trash_chunk(out);
3077 return 0; /* should come back */
3078}
3079
3080
Willy Tarreau06305792022-05-04 15:57:30 +02003081/* parsing function for 'show ssl ca-file [cafile[:index]]'.
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003082 * It prepares a show_cafile_ctx context, and checks the global
3083 * cafile_transaction under the ckch_lock (read only).
Willy Tarreau06305792022-05-04 15:57:30 +02003084 */
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003085static int cli_parse_show_cafile(char **args, char *payload, struct appctx *appctx, void *private)
3086{
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003087 struct show_cafile_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003088 struct cafile_entry *cafile_entry;
William Lallemand03a32e52022-04-26 18:17:15 +02003089 int ca_index = 0;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003090 char *colons;
3091 char *err = NULL;
3092
3093 if (!cli_has_level(appctx, ACCESS_LVL_OPER))
3094 return cli_err(appctx, "Can't allocate memory!\n");
3095
3096 /* The operations on the CKCH architecture are locked so we can
3097 * manipulate ckch_store and ckch_inst */
3098 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3099 return cli_err(appctx, "Can't show!\nOperations on certificates are currently locked!\n");
3100
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003101 ctx->show_all = 1; /* show all certificates */
3102 ctx->ca_index = 0;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003103 /* check if there is a certificate to lookup */
3104 if (*args[3]) {
3105
3106 /* Look for an optional CA index after the CA file name */
3107 colons = strchr(args[3], ':');
3108 if (colons) {
3109 char *endptr;
3110
3111 ca_index = strtol(colons + 1, &endptr, 10);
3112 /* Indexes start at 1 */
3113 if (colons + 1 == endptr || *endptr != '\0' || ca_index <= 0) {
3114 memprintf(&err, "wrong CA index after colons in '%s'!", args[3]);
3115 goto error;
3116 }
3117 *colons = '\0';
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003118 ctx->ca_index = ca_index - 1; /* we start counting at 0 in the ca_store, but at 1 on the CLI */
3119 ctx->show_all = 0; /* show only one certificate */
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003120 }
3121
3122 if (*args[3] == '*') {
3123 if (!cafile_transaction.new_cafile_entry)
3124 goto error;
3125
3126 cafile_entry = cafile_transaction.new_cafile_entry;
3127
3128 if (strcmp(args[3] + 1, cafile_entry->path) != 0)
3129 goto error;
3130
3131 } else {
3132 /* Get the "original" cafile_entry and not the
3133 * uncommitted one if it exists. */
3134 if ((cafile_entry = ssl_store_get_cafile_entry(args[3], 1)) == NULL || cafile_entry->type != CAFILE_CERT)
3135 goto error;
3136 }
3137
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003138 ctx->cur_cafile_entry = cafile_entry;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003139 /* use the IO handler that shows details */
3140 appctx->io_handler = cli_io_handler_show_cafile_detail;
3141 }
3142
3143 return 0;
3144
3145error:
3146 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3147 if (err)
3148 return cli_dynerr(appctx, err);
3149 return cli_err(appctx, "Can't display the CA file : Not found!\n");
3150}
3151
3152
3153/* release function of the 'show ssl ca-file' command */
3154static void cli_release_show_cafile(struct appctx *appctx)
3155{
3156 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3157}
3158
3159
3160/* This function returns the number of certificates in a cafile_entry. */
3161static int get_certificate_count(struct cafile_entry *cafile_entry)
3162{
3163 int cert_count = 0;
3164 STACK_OF(X509_OBJECT) *objs;
3165
3166 if (cafile_entry && cafile_entry->ca_store) {
3167 objs = X509_STORE_get0_objects(cafile_entry->ca_store);
3168 if (objs)
3169 cert_count = sk_X509_OBJECT_num(objs);
3170 }
3171 return cert_count;
3172}
3173
3174/* IO handler of "show ssl ca-file". The command taking a specific CA file name
Willy Tarreau821c3b02022-05-04 15:47:39 +02003175 * is managed in cli_io_handler_show_cafile_detail.
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003176 * It uses a show_cafile_ctx and the global cafile_transaction.new_cafile_entry
3177 * in read-only.
Willy Tarreau821c3b02022-05-04 15:47:39 +02003178 */
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003179static int cli_io_handler_show_cafile(struct appctx *appctx)
3180{
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003181 struct show_cafile_ctx *ctx = appctx->svcctx;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003182 struct buffer *trash = alloc_trash_chunk();
3183 struct ebmb_node *node;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003184 struct cafile_entry *cafile_entry;
3185
3186 if (trash == NULL)
3187 return 1;
3188
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003189 if (!ctx->old_cafile_entry) {
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003190 if (cafile_transaction.old_cafile_entry) {
3191 chunk_appendf(trash, "# transaction\n");
3192 chunk_appendf(trash, "*%s", cafile_transaction.old_cafile_entry->path);
3193
3194 chunk_appendf(trash, " - %d certificate(s)\n", get_certificate_count(cafile_transaction.new_cafile_entry));
3195 }
3196 }
3197
3198 /* First time in this io_handler. */
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003199 if (!ctx->cur_cafile_entry) {
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003200 chunk_appendf(trash, "# filename\n");
3201 node = ebmb_first(&cafile_tree);
3202 } else {
3203 /* We yielded during a previous call. */
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003204 node = &ctx->cur_cafile_entry->node;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003205 }
3206
3207 while (node) {
3208 cafile_entry = ebmb_entry(node, struct cafile_entry, node);
3209 if (cafile_entry->type == CAFILE_CERT) {
3210 chunk_appendf(trash, "%s", cafile_entry->path);
3211
3212 chunk_appendf(trash, " - %d certificate(s)\n", get_certificate_count(cafile_entry));
3213 }
3214
3215 node = ebmb_next(node);
Willy Tarreaud0a06d52022-05-18 15:07:19 +02003216 if (applet_putchk(appctx, trash) == -1)
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003217 goto yield;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003218 }
3219
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003220 ctx->cur_cafile_entry = NULL;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003221 free_trash_chunk(trash);
3222 return 1;
3223yield:
3224
3225 free_trash_chunk(trash);
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003226 ctx->cur_cafile_entry = cafile_entry;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003227 return 0; /* should come back */
3228}
3229
Remi Tricot-Le Bretonc3a84772021-03-25 18:13:57 +01003230/* parsing function of 'del ssl ca-file' */
3231static int cli_parse_del_cafile(char **args, char *payload, struct appctx *appctx, void *private)
3232{
3233 struct cafile_entry *cafile_entry;
3234 char *err = NULL;
3235 char *filename;
3236
3237 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
3238 return 1;
3239
3240 if (!*args[3])
3241 return cli_err(appctx, "'del ssl ca-file' expects a CA file name\n");
3242
3243 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3244 return cli_err(appctx, "Can't delete the CA file!\nOperations on certificates are currently locked!\n");
3245
3246 filename = args[3];
3247
3248 cafile_entry = ssl_store_get_cafile_entry(filename, 0);
3249 if (!cafile_entry) {
3250 memprintf(&err, "CA file '%s' doesn't exist!\n", filename);
3251 goto error;
3252 }
3253
3254 if (!LIST_ISEMPTY(&cafile_entry->ckch_inst_link)) {
3255 memprintf(&err, "CA file '%s' in use, can't be deleted!\n", filename);
3256 goto error;
3257 }
3258
3259 /* Remove the cafile_entry from the tree */
3260 ebmb_delete(&cafile_entry->node);
3261 ssl_store_delete_cafile_entry(cafile_entry);
3262
3263 memprintf(&err, "CA file '%s' deleted!\n", filename);
3264
3265 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3266 return cli_dynmsg(appctx, LOG_NOTICE, err);
3267
3268error:
3269 memprintf(&err, "Can't remove the CA file: %s\n", err ? err : "");
3270 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3271 return cli_dynerr(appctx, err);
3272}
3273
Remi Tricot-Le Breton720e3b92021-04-26 11:00:42 +02003274/* parsing function of 'new ssl crl-file' */
3275static int cli_parse_new_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
3276{
3277 struct cafile_entry *cafile_entry;
3278 char *err = NULL;
3279 char *path;
3280
3281 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
3282 return 1;
3283
3284 if (!*args[3])
3285 return cli_err(appctx, "'new ssl crl-file' expects a filename\n");
3286
3287 path = args[3];
3288
3289 /* The operations on the CKCH architecture are locked so we can
3290 * manipulate ckch_store and ckch_inst */
3291 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
Remi Tricot-Le Breton444d7022022-05-05 17:18:40 +02003292 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 +02003293
3294 cafile_entry = ssl_store_get_cafile_entry(path, 0);
3295 if (cafile_entry) {
3296 memprintf(&err, "CRL file '%s' already exists!\n", path);
3297 goto error;
3298 }
3299
3300 cafile_entry = ssl_store_create_cafile_entry(path, NULL, CAFILE_CRL);
3301 if (!cafile_entry) {
3302 memprintf(&err, "%sCannot allocate memory!\n", err ? err : "");
3303 goto error;
3304 }
3305
3306 /* Add the newly created cafile_entry to the tree so that
3307 * any new ckch instance created from now can use it. */
3308 if (ssl_store_add_uncommitted_cafile_entry(cafile_entry))
3309 goto error;
3310
3311 memprintf(&err, "New CRL file created '%s'!\n", path);
3312
3313 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3314 return cli_dynmsg(appctx, LOG_NOTICE, err);
3315error:
3316 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3317 return cli_dynerr(appctx, err);
3318}
3319
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003320/* Parsing function of `set ssl crl-file` */
3321static int cli_parse_set_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
3322{
Willy Tarreaua06b9a52022-05-04 20:33:03 +02003323 struct set_crlfile_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003324 char *err = NULL;
3325 int errcode = 0;
3326 struct buffer *buf;
3327
3328 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
3329 return 1;
3330
3331 if (!*args[3] || !payload)
Remi Tricot-Le Breton444d7022022-05-05 17:18:40 +02003332 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 +02003333
3334 /* The operations on the CKCH architecture are locked so we can
3335 * manipulate ckch_store and ckch_inst */
3336 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3337 return cli_err(appctx, "Can't update the CRL file!\nOperations on certificates are currently locked!\n");
3338
3339 if ((buf = alloc_trash_chunk()) == NULL) {
3340 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
3341 errcode |= ERR_ALERT | ERR_FATAL;
3342 goto end;
3343 }
3344
3345 if (!chunk_strcpy(buf, args[3])) {
3346 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
3347 errcode |= ERR_ALERT | ERR_FATAL;
3348 goto end;
3349 }
3350
Willy Tarreaua06b9a52022-05-04 20:33:03 +02003351 ctx->old_crlfile_entry = NULL;
3352 ctx->new_crlfile_entry = NULL;
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003353
3354 /* if there is an ongoing transaction */
3355 if (crlfile_transaction.path) {
3356 /* if there is an ongoing transaction, check if this is the same file */
3357 if (strcmp(crlfile_transaction.path, buf->area) != 0) {
3358 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", crlfile_transaction.path, buf->area);
3359 errcode |= ERR_ALERT | ERR_FATAL;
3360 goto end;
3361 }
Willy Tarreaua06b9a52022-05-04 20:33:03 +02003362 ctx->old_crlfile_entry = crlfile_transaction.old_crlfile_entry;
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003363 }
3364 else {
3365 /* lookup for the certificate in the tree */
Willy Tarreaua06b9a52022-05-04 20:33:03 +02003366 ctx->old_crlfile_entry = ssl_store_get_cafile_entry(buf->area, 0);
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003367 }
3368
Willy Tarreaua06b9a52022-05-04 20:33:03 +02003369 if (!ctx->old_crlfile_entry) {
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003370 memprintf(&err, "%sCan't replace a CRL file which is not referenced by the configuration!\n",
3371 err ? err : "");
3372 errcode |= ERR_ALERT | ERR_FATAL;
3373 goto end;
3374 }
3375
Willy Tarreaua06b9a52022-05-04 20:33:03 +02003376 if (!ctx->path) {
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003377 /* this is a new transaction, set the path of the transaction */
Willy Tarreaua06b9a52022-05-04 20:33:03 +02003378 ctx->path = strdup(ctx->old_crlfile_entry->path);
3379 if (!ctx->path) {
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003380 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
3381 errcode |= ERR_ALERT | ERR_FATAL;
3382 goto end;
3383 }
3384 }
3385
Willy Tarreaua06b9a52022-05-04 20:33:03 +02003386 if (ctx->new_crlfile_entry)
3387 ssl_store_delete_cafile_entry(ctx->new_crlfile_entry);
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003388
3389 /* Create a new cafile_entry without adding it to the cafile tree. */
Willy Tarreaua06b9a52022-05-04 20:33:03 +02003390 ctx->new_crlfile_entry = ssl_store_create_cafile_entry(ctx->path, NULL, CAFILE_CRL);
3391 if (!ctx->new_crlfile_entry) {
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003392 memprintf(&err, "%sCannot allocate memory!\n", err ? err : "");
3393 errcode |= ERR_ALERT | ERR_FATAL;
3394 goto end;
3395 }
3396
3397 /* Fill the new entry with the new CRL. */
Willy Tarreaua06b9a52022-05-04 20:33:03 +02003398 if (ssl_store_load_ca_from_buf(ctx->new_crlfile_entry, payload)) {
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003399 memprintf(&err, "%sInvalid payload\n", err ? err : "");
3400 errcode |= ERR_ALERT | ERR_FATAL;
3401 goto end;
3402 }
3403
3404 /* we succeed, we can save the crl in the transaction */
3405
Remi Tricot-Le Breton444d7022022-05-05 17:18:40 +02003406 /* if there wasn't a transaction, update the old CRL */
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003407 if (!crlfile_transaction.old_crlfile_entry) {
Willy Tarreaua06b9a52022-05-04 20:33:03 +02003408 crlfile_transaction.old_crlfile_entry = ctx->old_crlfile_entry;
3409 crlfile_transaction.path = ctx->path;
Remi Tricot-Le Breton444d7022022-05-05 17:18:40 +02003410 err = memprintf(&err, "transaction created for CRL %s!\n", crlfile_transaction.path);
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003411 } else {
Remi Tricot-Le Breton444d7022022-05-05 17:18:40 +02003412 err = memprintf(&err, "transaction updated for CRL %s!\n", crlfile_transaction.path);
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003413 }
3414
3415 /* free the previous CRL file if there was a transaction */
3416 ssl_store_delete_cafile_entry(crlfile_transaction.new_crlfile_entry);
3417
Willy Tarreaua06b9a52022-05-04 20:33:03 +02003418 crlfile_transaction.new_crlfile_entry = ctx->new_crlfile_entry;
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003419
3420 /* creates the SNI ctxs later in the IO handler */
3421
3422end:
3423 free_trash_chunk(buf);
3424
3425 if (errcode & ERR_CODE) {
Willy Tarreaua06b9a52022-05-04 20:33:03 +02003426 ssl_store_delete_cafile_entry(ctx->new_crlfile_entry);
3427 ctx->new_crlfile_entry = NULL;
3428 ctx->old_crlfile_entry = NULL;
3429 ha_free(&ctx->path);
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003430 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3431 return cli_dynerr(appctx, memprintf(&err, "%sCan't update %s!\n", err ? err : "", args[3]));
3432 } else {
3433
3434 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3435 return cli_dynmsg(appctx, LOG_NOTICE, err);
3436 }
3437}
3438
Willy Tarreaudec23dc2022-05-04 20:25:05 +02003439/* Parsing function of 'commit ssl crl-file'.
3440 * It uses a commit_cacrlfile_ctx that's also shared with "commit ssl ca-file".
3441 */
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003442static int cli_parse_commit_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
3443{
Willy Tarreaudec23dc2022-05-04 20:25:05 +02003444 struct commit_cacrlfile_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003445 char *err = NULL;
3446
3447 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
3448 return 1;
3449
3450 if (!*args[3])
3451 return cli_err(appctx, "'commit ssl ca-file expects a filename\n");
3452
3453 /* The operations on the CKCH architecture are locked so we can
3454 * manipulate ckch_store and ckch_inst */
3455 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3456 return cli_err(appctx, "Can't commit the CRL file!\nOperations on certificates are currently locked!\n");
3457
3458 if (!crlfile_transaction.path) {
3459 memprintf(&err, "No ongoing transaction! !\n");
3460 goto error;
3461 }
3462
3463 if (strcmp(crlfile_transaction.path, args[3]) != 0) {
3464 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", crlfile_transaction.path, args[3]);
3465 goto error;
3466 }
3467 /* init the appctx structure */
Willy Tarreau1d6dd802022-05-05 08:17:29 +02003468 ctx->state = CACRL_ST_INIT;
Willy Tarreaudec23dc2022-05-04 20:25:05 +02003469 ctx->next_ckchi = NULL;
3470 ctx->old_crlfile_entry = crlfile_transaction.old_crlfile_entry;
3471 ctx->new_crlfile_entry = crlfile_transaction.new_crlfile_entry;
3472 ctx->cafile_type = CAFILE_CRL;
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003473
3474 return 0;
3475
3476error:
3477
3478 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3479 err = memprintf(&err, "%sCan't commit %s!\n", err ? err : "", args[3]);
3480
3481 return cli_dynerr(appctx, err);
3482}
3483
3484
Willy Tarreaudec23dc2022-05-04 20:25:05 +02003485/* release function of the `commit ssl crl-file' command, free things and unlock the spinlock.
3486 * it uses a commit_cacrlfile_ctx that's the same as for "commit ssl ca-file".
3487 */
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003488static void cli_release_commit_crlfile(struct appctx *appctx)
3489{
Willy Tarreaudec23dc2022-05-04 20:25:05 +02003490 struct commit_cacrlfile_ctx *ctx = appctx->svcctx;
3491
Willy Tarreau1d6dd802022-05-05 08:17:29 +02003492 if (ctx->state != CACRL_ST_FIN) {
Willy Tarreaudec23dc2022-05-04 20:25:05 +02003493 struct cafile_entry *new_crlfile_entry = ctx->new_crlfile_entry;
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003494
3495 /* Remove the uncommitted cafile_entry from the tree. */
3496 ebmb_delete(&new_crlfile_entry->node);
3497 ssl_store_delete_cafile_entry(new_crlfile_entry);
3498 }
3499 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3500}
3501
Remi Tricot-Le Breton720e3b92021-04-26 11:00:42 +02003502/* parsing function of 'del ssl crl-file' */
3503static int cli_parse_del_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
3504{
3505 struct cafile_entry *cafile_entry;
3506 char *err = NULL;
3507 char *filename;
3508
3509 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
3510 return 1;
3511
3512 if (!*args[3])
3513 return cli_err(appctx, "'del ssl crl-file' expects a CRL file name\n");
3514
3515 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3516 return cli_err(appctx, "Can't delete the CRL file!\nOperations on certificates are currently locked!\n");
3517
3518 filename = args[3];
3519
3520 cafile_entry = ssl_store_get_cafile_entry(filename, 0);
3521 if (!cafile_entry) {
3522 memprintf(&err, "CRL file '%s' doesn't exist!\n", filename);
3523 goto error;
3524 }
3525 if (cafile_entry->type != CAFILE_CRL) {
3526 memprintf(&err, "'del ssl crl-file' does not work on CA files!\n");
3527 goto error;
3528 }
3529
3530 if (!LIST_ISEMPTY(&cafile_entry->ckch_inst_link)) {
3531 memprintf(&err, "CRL file '%s' in use, can't be deleted!\n", filename);
3532 goto error;
3533 }
3534
3535 /* Remove the cafile_entry from the tree */
3536 ebmb_delete(&cafile_entry->node);
3537 ssl_store_delete_cafile_entry(cafile_entry);
3538
3539 memprintf(&err, "CRL file '%s' deleted!\n", filename);
3540
3541 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3542 return cli_dynmsg(appctx, LOG_NOTICE, err);
3543
3544error:
3545 memprintf(&err, "Can't remove the CRL file: %s\n", err ? err : "");
3546 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3547 return cli_dynerr(appctx, err);
3548}
3549
Remi Tricot-Le Bretoneef8e7b2021-04-20 17:42:02 +02003550/* parsing function of 'abort ssl crl-file' */
3551static int cli_parse_abort_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
3552{
3553 char *err = NULL;
3554
3555 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
3556 return 1;
3557
3558 if (!*args[3])
3559 return cli_err(appctx, "'abort ssl crl-file' expects a filename\n");
3560
3561 /* The operations on the CKCH architecture are locked so we can
3562 * manipulate ckch_store and ckch_inst */
3563 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3564 return cli_err(appctx, "Can't abort!\nOperations on certificates are currently locked!\n");
3565
3566 if (!crlfile_transaction.path) {
3567 memprintf(&err, "No ongoing transaction!\n");
3568 goto error;
3569 }
3570
3571 if (strcmp(crlfile_transaction.path, args[3]) != 0) {
3572 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to abort a transaction for '%s'\n", crlfile_transaction.path, args[3]);
3573 goto error;
3574 }
3575
3576 /* Only free the uncommitted cafile_entry here, because the SNI and instances were not generated yet */
3577 ssl_store_delete_cafile_entry(crlfile_transaction.new_crlfile_entry);
3578 crlfile_transaction.new_crlfile_entry = NULL;
3579 crlfile_transaction.old_crlfile_entry = NULL;
3580 ha_free(&crlfile_transaction.path);
3581
3582 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3583
3584 err = memprintf(&err, "Transaction aborted for certificate '%s'!\n", args[3]);
3585 return cli_dynmsg(appctx, LOG_NOTICE, err);
3586
3587error:
3588 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3589
3590 return cli_dynerr(appctx, err);
3591}
3592
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003593
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003594/*
3595 * Display a Certificate Resignation List's information.
3596 * The information displayed is inspired by the output of 'openssl crl -in
3597 * crl.pem -text'.
3598 * Returns 0 in case of success.
3599 */
3600static int show_crl_detail(X509_CRL *crl, struct buffer *out)
3601{
3602 BIO *bio = NULL;
3603 struct buffer *tmp = alloc_trash_chunk();
3604 long version;
3605 X509_NAME *issuer;
3606 int write = -1;
3607 STACK_OF(X509_REVOKED) *rev = NULL;
3608 X509_REVOKED *rev_entry = NULL;
3609 int i;
3610
3611 if (!tmp)
3612 return -1;
3613
3614 if ((bio = BIO_new(BIO_s_mem())) == NULL)
3615 goto end;
3616
3617 /* Version (as displayed by 'openssl crl') */
3618 version = X509_CRL_get_version(crl);
3619 chunk_appendf(out, "Version %ld\n", version + 1);
3620
3621 /* Signature Algorithm */
3622 chunk_appendf(out, "Signature Algorithm: %s\n", OBJ_nid2ln(X509_CRL_get_signature_nid(crl)));
3623
3624 /* Issuer */
3625 chunk_appendf(out, "Issuer: ");
3626 if ((issuer = X509_CRL_get_issuer(crl)) == NULL)
3627 goto end;
3628 if ((ssl_sock_get_dn_oneline(issuer, tmp)) == -1)
3629 goto end;
3630 *(tmp->area + tmp->data) = '\0';
3631 chunk_appendf(out, "%s\n", tmp->area);
3632
3633 /* Last Update */
3634 chunk_appendf(out, "Last Update: ");
3635 chunk_reset(tmp);
Remi Tricot-Le Bretond75b99e2021-05-17 11:45:55 +02003636 if (BIO_reset(bio) == -1)
3637 goto end;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003638 if (ASN1_TIME_print(bio, X509_CRL_get0_lastUpdate(crl)) == 0)
3639 goto end;
3640 write = BIO_read(bio, tmp->area, tmp->size-1);
3641 tmp->area[write] = '\0';
3642 chunk_appendf(out, "%s\n", tmp->area);
3643
3644
3645 /* Next Update */
3646 chunk_appendf(out, "Next Update: ");
3647 chunk_reset(tmp);
Remi Tricot-Le Bretond75b99e2021-05-17 11:45:55 +02003648 if (BIO_reset(bio) == -1)
3649 goto end;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003650 if (ASN1_TIME_print(bio, X509_CRL_get0_nextUpdate(crl)) == 0)
3651 goto end;
3652 write = BIO_read(bio, tmp->area, tmp->size-1);
3653 tmp->area[write] = '\0';
3654 chunk_appendf(out, "%s\n", tmp->area);
3655
3656
3657 /* Revoked Certificates */
3658 rev = X509_CRL_get_REVOKED(crl);
3659 if (sk_X509_REVOKED_num(rev) > 0)
3660 chunk_appendf(out, "Revoked Certificates:\n");
3661 else
3662 chunk_appendf(out, "No Revoked Certificates.\n");
3663
3664 for (i = 0; i < sk_X509_REVOKED_num(rev); i++) {
3665 rev_entry = sk_X509_REVOKED_value(rev, i);
3666
3667 /* Serial Number and Revocation Date */
Remi Tricot-Le Bretond75b99e2021-05-17 11:45:55 +02003668 if (BIO_reset(bio) == -1)
3669 goto end;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003670 BIO_printf(bio , " Serial Number: ");
Remi Tricot-Le Breton18c7d832021-05-17 18:38:34 +02003671 i2a_ASN1_INTEGER(bio, (ASN1_INTEGER*)X509_REVOKED_get0_serialNumber(rev_entry));
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003672 BIO_printf(bio, "\n Revocation Date: ");
Remi Tricot-Le Bretona6b27842021-05-18 10:06:00 +02003673 if (ASN1_TIME_print(bio, X509_REVOKED_get0_revocationDate(rev_entry)) == 0)
3674 goto end;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003675 BIO_printf(bio, "\n");
3676
3677 write = BIO_read(bio, tmp->area, tmp->size-1);
3678 tmp->area[write] = '\0';
3679 chunk_appendf(out, "%s", tmp->area);
3680 }
3681
3682end:
3683 free_trash_chunk(tmp);
3684 if (bio)
3685 BIO_free(bio);
3686
3687 return 0;
3688}
3689
Willy Tarreau821c3b02022-05-04 15:47:39 +02003690/* IO handler of details "show ssl crl-file <filename[:index]>".
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003691 * It uses show_crlfile_ctx and the global
3692 * crlfile_transaction.new_cafile_entry in read-only.
Willy Tarreau821c3b02022-05-04 15:47:39 +02003693 */
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003694static int cli_io_handler_show_crlfile_detail(struct appctx *appctx)
3695{
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003696 struct show_crlfile_ctx *ctx = appctx->svcctx;
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003697 struct cafile_entry *cafile_entry = ctx->cafile_entry;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003698 struct buffer *out = alloc_trash_chunk();
3699 int i;
3700 X509_CRL *crl;
3701 STACK_OF(X509_OBJECT) *objs;
3702 int retval = 0;
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003703 int index = ctx->index;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003704
3705 if (!out)
3706 goto end_no_putchk;
3707
3708 chunk_appendf(out, "Filename: ");
3709 if (cafile_entry == crlfile_transaction.new_crlfile_entry)
3710 chunk_appendf(out, "*");
3711 chunk_appendf(out, "%s\n", cafile_entry->path);
3712
3713 chunk_appendf(out, "Status: ");
3714 if (!cafile_entry->ca_store)
3715 chunk_appendf(out, "Empty\n");
3716 else if (LIST_ISEMPTY(&cafile_entry->ckch_inst_link))
3717 chunk_appendf(out, "Unused\n");
3718 else
3719 chunk_appendf(out, "Used\n");
3720
3721 if (!cafile_entry->ca_store)
3722 goto end;
3723
3724 objs = X509_STORE_get0_objects(cafile_entry->ca_store);
3725 for (i = 0; i < sk_X509_OBJECT_num(objs); i++) {
3726 crl = X509_OBJECT_get0_X509_CRL(sk_X509_OBJECT_value(objs, i));
3727 if (!crl)
3728 continue;
3729
3730 /* CRL indexes start at 1 on the CLI output. */
3731 if (index && index-1 != i)
3732 continue;
3733
Remi Tricot-Le Bretone8041fe2022-04-05 16:44:21 +02003734 chunk_appendf(out, " \nCertificate Revocation List #%d:\n", i+1);
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003735 retval = show_crl_detail(crl, out);
3736 if (retval < 0)
3737 goto end_no_putchk;
3738 else if (retval || index)
3739 goto end;
3740 }
3741
3742end:
Willy Tarreaud0a06d52022-05-18 15:07:19 +02003743 if (applet_putchk(appctx, out) == -1)
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003744 goto yield;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003745
3746end_no_putchk:
3747 free_trash_chunk(out);
3748 return 1;
3749yield:
3750 free_trash_chunk(out);
3751 return 0; /* should come back */
3752}
3753
Willy Tarreau821c3b02022-05-04 15:47:39 +02003754/* parsing function for 'show ssl crl-file [crlfile[:index]]'.
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003755 * It sets the context to a show_crlfile_ctx, and the global
Willy Tarreau821c3b02022-05-04 15:47:39 +02003756 * cafile_transaction.new_crlfile_entry under the ckch_lock.
3757 */
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003758static int cli_parse_show_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
3759{
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003760 struct show_crlfile_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003761 struct cafile_entry *cafile_entry;
3762 long index = 0;
3763 char *colons;
3764 char *err = NULL;
3765
3766 if (!cli_has_level(appctx, ACCESS_LVL_OPER))
3767 return cli_err(appctx, "Can't allocate memory!\n");
3768
3769 /* The operations on the CKCH architecture are locked so we can
3770 * manipulate ckch_store and ckch_inst */
3771 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3772 return cli_err(appctx, "Can't show!\nOperations on certificates are currently locked!\n");
3773
3774 /* check if there is a certificate to lookup */
3775 if (*args[3]) {
3776
3777 /* Look for an optional index after the CRL file name */
3778 colons = strchr(args[3], ':');
3779 if (colons) {
3780 char *endptr;
3781
3782 index = strtol(colons + 1, &endptr, 10);
3783 /* Indexes start at 1 */
3784 if (colons + 1 == endptr || *endptr != '\0' || index <= 0) {
3785 memprintf(&err, "wrong CRL index after colons in '%s'!", args[3]);
3786 goto error;
3787 }
3788 *colons = '\0';
3789 }
3790
3791 if (*args[3] == '*') {
3792 if (!crlfile_transaction.new_crlfile_entry)
3793 goto error;
3794
3795 cafile_entry = crlfile_transaction.new_crlfile_entry;
3796
3797 if (strcmp(args[3] + 1, cafile_entry->path) != 0)
3798 goto error;
3799
3800 } else {
3801 /* Get the "original" cafile_entry and not the
3802 * uncommitted one if it exists. */
3803 if ((cafile_entry = ssl_store_get_cafile_entry(args[3], 1)) == NULL || cafile_entry->type != CAFILE_CRL)
3804 goto error;
3805 }
3806
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003807 ctx->cafile_entry = cafile_entry;
3808 ctx->index = index;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003809 /* use the IO handler that shows details */
3810 appctx->io_handler = cli_io_handler_show_crlfile_detail;
3811 }
3812
3813 return 0;
3814
3815error:
3816 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3817 if (err)
3818 return cli_dynerr(appctx, err);
Remi Tricot-Le Breton444d7022022-05-05 17:18:40 +02003819 return cli_err(appctx, "Can't display the CRL file : Not found!\n");
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003820}
3821
3822/* IO handler of "show ssl crl-file". The command taking a specific CRL file name
3823 * is managed in cli_io_handler_show_crlfile_detail. */
3824static int cli_io_handler_show_crlfile(struct appctx *appctx)
3825{
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003826 struct show_crlfile_ctx *ctx = appctx->svcctx;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003827 struct buffer *trash = alloc_trash_chunk();
3828 struct ebmb_node *node;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003829 struct cafile_entry *cafile_entry;
3830
3831 if (trash == NULL)
3832 return 1;
3833
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003834 if (!ctx->old_crlfile_entry) {
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003835 if (crlfile_transaction.old_crlfile_entry) {
3836 chunk_appendf(trash, "# transaction\n");
3837 chunk_appendf(trash, "*%s\n", crlfile_transaction.old_crlfile_entry->path);
3838 }
3839 }
3840
3841 /* First time in this io_handler. */
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003842 if (!ctx->cafile_entry) {
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003843 chunk_appendf(trash, "# filename\n");
3844 node = ebmb_first(&cafile_tree);
3845 } else {
3846 /* We yielded during a previous call. */
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003847 node = &ctx->cafile_entry->node;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003848 }
3849
3850 while (node) {
3851 cafile_entry = ebmb_entry(node, struct cafile_entry, node);
3852 if (cafile_entry->type == CAFILE_CRL) {
3853 chunk_appendf(trash, "%s\n", cafile_entry->path);
3854 }
3855
3856 node = ebmb_next(node);
Willy Tarreaud0a06d52022-05-18 15:07:19 +02003857 if (applet_putchk(appctx, trash) == -1)
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003858 goto yield;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003859 }
3860
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003861 ctx->cafile_entry = NULL;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003862 free_trash_chunk(trash);
3863 return 1;
3864yield:
3865
3866 free_trash_chunk(trash);
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003867 ctx->cafile_entry = cafile_entry;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003868 return 0; /* should come back */
3869}
3870
3871
3872/* release function of the 'show ssl crl-file' command */
3873static void cli_release_show_crlfile(struct appctx *appctx)
3874{
3875 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3876}
3877
3878
William Lallemandee8530c2020-06-23 18:19:42 +02003879void ckch_deinit()
3880{
3881 struct eb_node *node, *next;
3882 struct ckch_store *store;
William Lallemandb0c48272022-04-26 15:44:53 +02003883 struct ebmb_node *canode;
William Lallemandee8530c2020-06-23 18:19:42 +02003884
William Lallemandb0c48272022-04-26 15:44:53 +02003885 /* deinit the ckch stores */
William Lallemandee8530c2020-06-23 18:19:42 +02003886 node = eb_first(&ckchs_tree);
3887 while (node) {
3888 next = eb_next(node);
3889 store = ebmb_entry(node, struct ckch_store, node);
3890 ckch_store_free(store);
3891 node = next;
3892 }
William Lallemandb0c48272022-04-26 15:44:53 +02003893
3894 /* deinit the ca-file store */
3895 canode = ebmb_first(&cafile_tree);
3896 while (canode) {
3897 struct cafile_entry *entry = NULL;
3898
3899 entry = ebmb_entry(canode, struct cafile_entry, node);
3900 canode = ebmb_next(canode);
3901 ssl_store_delete_cafile_entry(entry);
3902 }
William Lallemandee8530c2020-06-23 18:19:42 +02003903}
William Lallemandda8584c2020-05-14 10:14:37 +02003904
3905/* register cli keywords */
3906static struct cli_kw_list cli_kws = {{ },{
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01003907 { { "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 },
3908 { { "set", "ssl", "cert", NULL }, "set ssl cert <certfile> <payload> : replace a certificate file", cli_parse_set_cert, NULL, NULL },
3909 { { "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 },
3910 { { "abort", "ssl", "cert", NULL }, "abort ssl cert <certfile> : abort a transaction for a certificate file", cli_parse_abort_cert, NULL, NULL },
3911 { { "del", "ssl", "cert", NULL }, "del ssl cert <certfile> : delete an unused certificate file", cli_parse_del_cert, NULL, NULL },
3912 { { "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 },
3913
Amaury Denoyelleb11ad9e2021-05-21 11:01:10 +02003914 { { "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 +01003915 { { "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 +02003916 { { "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 +01003917 { { "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 +01003918 { { "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 +01003919 { { "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 +02003920
Remi Tricot-Le Breton720e3b92021-04-26 11:00:42 +02003921 { { "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 +02003922 { { "set", "ssl", "crl-file", NULL }, "set ssl crl-file <crlfile> <payload> : replace a CRL file", cli_parse_set_crlfile, NULL, NULL },
3923 { { "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 +02003924 { { "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 +02003925 { { "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 +02003926 { { "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 +02003927 { { NULL }, NULL, NULL, NULL }
3928}};
3929
3930INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws);
3931