blob: b00cc51cef2cd26d28e7cdfab83a291af6b38ad4 [file] [log] [blame]
William Lallemand03c331c2020-05-13 10:10:01 +02001/*
2 *
3 * Copyright (C) 2020 HAProxy Technologies, William Lallemand <wlallemand@haproxy.com>
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version
8 * 2 of the License, or (at your option) any later version.
9 *
10 */
11
12#define _GNU_SOURCE
13#include <ctype.h>
William Lallemand87fd9942022-04-01 20:12:03 +020014#include <dirent.h>
William Lallemand03c331c2020-05-13 10:10:01 +020015#include <errno.h>
16#include <fcntl.h>
17#include <stdio.h>
18#include <stdlib.h>
19#include <string.h>
Willy Tarreauaeed4a82020-06-04 22:01:04 +020020#include <syslog.h>
William Lallemand03c331c2020-05-13 10:10:01 +020021#include <unistd.h>
22
23#include <sys/stat.h>
24#include <sys/types.h>
25
Willy Tarreau74f24562021-10-06 17:54:12 +020026#include <import/ebpttree.h>
Willy Tarreaub2551052020-06-09 09:07:15 +020027#include <import/ebsttree.h>
28
Willy Tarreau50c2f1e2022-05-04 19:26:59 +020029#include <haproxy/applet.h>
Willy Tarreau8d366972020-05-27 16:10:29 +020030#include <haproxy/base64.h>
Willy Tarreauf1d32c42020-06-04 21:07:02 +020031#include <haproxy/channel.h>
Willy Tarreau83487a82020-06-04 20:19:54 +020032#include <haproxy/cli.h>
Christopher Faulet908628c2022-03-25 16:43:49 +010033#include <haproxy/conn_stream.h>
34#include <haproxy/cs_utils.h>
Willy Tarreau8d366972020-05-27 16:10:29 +020035#include <haproxy/errors.h>
Willy Tarreau47d7f902020-06-04 14:25:47 +020036#include <haproxy/ssl_ckch.h>
Willy Tarreau209108d2020-06-04 20:30:20 +020037#include <haproxy/ssl_sock.h>
Willy Tarreaub2bd8652020-06-04 14:21:22 +020038#include <haproxy/ssl_utils.h>
Willy Tarreau48fbcae2020-06-03 18:09:46 +020039#include <haproxy/tools.h>
William Lallemand03c331c2020-05-13 10:10:01 +020040
William Lallemandda8584c2020-05-14 10:14:37 +020041/* Uncommitted CKCH transaction */
42
43static struct {
44 struct ckch_store *new_ckchs;
45 struct ckch_store *old_ckchs;
46 char *path;
47} ckchs_transaction;
48
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +010049/* Uncommitted CA file transaction */
50
51static struct {
52 struct cafile_entry *old_cafile_entry;
53 struct cafile_entry *new_cafile_entry;
54 char *path;
55} cafile_transaction;
William Lallemandda8584c2020-05-14 10:14:37 +020056
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +020057/* Uncommitted CRL file transaction */
58
59static struct {
60 struct cafile_entry *old_crlfile_entry;
61 struct cafile_entry *new_crlfile_entry;
62 char *path;
63} crlfile_transaction;
64
Willy Tarreau50c2f1e2022-05-04 19:26:59 +020065/* CLI context used by "show cafile" */
66struct show_cafile_ctx {
67 struct cafile_entry *cur_cafile_entry;
68 struct cafile_entry *old_cafile_entry;
69 int ca_index;
70 int show_all;
71};
William Lallemand03c331c2020-05-13 10:10:01 +020072
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +020073/* CLI context used by "show crlfile" */
74struct show_crlfile_ctx {
75 struct cafile_entry *cafile_entry;
76 struct crlfile_entry *old_crlfile_entry;
77 int index;
78};
79
Willy Tarreau96c9a6c2022-05-04 19:51:37 +020080/* CLI context used by "show cert" */
81struct show_cert_ctx {
82 struct ckch_store *old_ckchs;
83 struct ckch_store *cur_ckchs;
84 int transaction;
85};
86
Willy Tarreaua645b6a2022-05-04 19:58:00 +020087/* CLI context used by "commit cert" */
88struct commit_cert_ctx {
89 struct ckch_store *old_ckchs;
90 struct ckch_store *new_ckchs;
91 struct ckch_inst *next_ckchi;
Willy Tarreaucb1b4ed2022-05-05 08:15:27 +020092 enum {
93 CERT_ST_INIT = 0,
94 CERT_ST_GEN,
95 CERT_ST_INSERT,
96 CERT_ST_FIN,
97 } state;
Willy Tarreaua645b6a2022-05-04 19:58:00 +020098};
99
Willy Tarreau329f4b42022-05-04 20:05:55 +0200100/* CLI context used by "set cert" */
101struct set_cert_ctx {
102 struct ckch_store *old_ckchs;
103 struct ckch_store *new_ckchs;
104 char *path;
105};
Willy Tarreau96c9a6c2022-05-04 19:51:37 +0200106
Willy Tarreaua37693f2022-05-04 20:12:55 +0200107/* CLI context used by "set ca-file" */
108struct set_cafile_ctx {
109 struct cafile_entry *old_cafile_entry;
110 struct cafile_entry *new_cafile_entry;
111 char *path;
112};
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +0100113
Willy Tarreaua06b9a52022-05-04 20:33:03 +0200114/* CLI context used by "set crl-file" */
115struct set_crlfile_ctx {
116 struct cafile_entry *old_crlfile_entry;
117 struct cafile_entry *new_crlfile_entry;
118 char *path;
119};
Willy Tarreau329f4b42022-05-04 20:05:55 +0200120
Willy Tarreaudec23dc2022-05-04 20:25:05 +0200121/* CLI context used by "commit cafile" and "commit crlfile" */
122struct commit_cacrlfile_ctx {
123 struct cafile_entry *old_cafile_entry;
124 struct cafile_entry *new_cafile_entry;
125 struct cafile_entry *old_crlfile_entry;
126 struct cafile_entry *new_crlfile_entry;
127 struct ckch_inst_link *next_ckchi_link;
128 struct ckch_inst *next_ckchi;
129 int cafile_type; /* either CA or CRL, depending on the current command */
Willy Tarreau1d6dd802022-05-05 08:17:29 +0200130 enum {
131 CACRL_ST_INIT = 0,
132 CACRL_ST_GEN,
133 CACRL_ST_INSERT,
134 CACRL_ST_FIN,
135 } state;
Willy Tarreaudec23dc2022-05-04 20:25:05 +0200136};
137
Willy Tarreaua37693f2022-05-04 20:12:55 +0200138
William Lallemand03c331c2020-05-13 10:10:01 +0200139/******************** cert_key_and_chain functions *************************
140 * These are the functions that fills a cert_key_and_chain structure. For the
141 * functions filling a SSL_CTX from a cert_key_and_chain, see ssl_sock.c
142 */
143
144/*
145 * Try to parse Signed Certificate Timestamp List structure. This function
146 * makes only basic test if the data seems like SCTL. No signature validation
147 * is performed.
148 */
149static int ssl_sock_parse_sctl(struct buffer *sctl)
150{
151 int ret = 1;
152 int len, pos, sct_len;
153 unsigned char *data;
154
155 if (sctl->data < 2)
156 goto out;
157
158 data = (unsigned char *) sctl->area;
159 len = (data[0] << 8) | data[1];
160
161 if (len + 2 != sctl->data)
162 goto out;
163
164 data = data + 2;
165 pos = 0;
166 while (pos < len) {
167 if (len - pos < 2)
168 goto out;
169
170 sct_len = (data[pos] << 8) | data[pos + 1];
171 if (pos + sct_len + 2 > len)
172 goto out;
173
174 pos += sct_len + 2;
175 }
176
177 ret = 0;
178
179out:
180 return ret;
181}
182
183/* Try to load a sctl from a buffer <buf> if not NULL, or read the file <sctl_path>
184 * It fills the ckch->sctl buffer
185 * return 0 on success or != 0 on failure */
186int ssl_sock_load_sctl_from_file(const char *sctl_path, char *buf, struct cert_key_and_chain *ckch, char **err)
187{
188 int fd = -1;
189 int r = 0;
190 int ret = 1;
191 struct buffer tmp;
192 struct buffer *src;
193 struct buffer *sctl;
194
195 if (buf) {
William Lallemand8d673942021-01-27 14:58:51 +0100196 chunk_initstr(&tmp, buf);
William Lallemand03c331c2020-05-13 10:10:01 +0200197 src = &tmp;
198 } else {
199 fd = open(sctl_path, O_RDONLY);
200 if (fd == -1)
201 goto end;
202
203 trash.data = 0;
204 while (trash.data < trash.size) {
205 r = read(fd, trash.area + trash.data, trash.size - trash.data);
206 if (r < 0) {
207 if (errno == EINTR)
208 continue;
209 goto end;
210 }
211 else if (r == 0) {
212 break;
213 }
214 trash.data += r;
215 }
216 src = &trash;
217 }
218
219 ret = ssl_sock_parse_sctl(src);
220 if (ret)
221 goto end;
222
223 sctl = calloc(1, sizeof(*sctl));
224 if (!chunk_dup(sctl, src)) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100225 ha_free(&sctl);
William Lallemand03c331c2020-05-13 10:10:01 +0200226 goto end;
227 }
228 /* no error, fill ckch with new context, old context must be free */
229 if (ckch->sctl) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100230 ha_free(&ckch->sctl->area);
William Lallemand03c331c2020-05-13 10:10:01 +0200231 free(ckch->sctl);
232 }
233 ckch->sctl = sctl;
234 ret = 0;
235end:
236 if (fd != -1)
237 close(fd);
238
239 return ret;
240}
241
242#if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) || defined OPENSSL_IS_BORINGSSL)
243/*
Ilya Shipitsin46a030c2020-07-05 16:36:08 +0500244 * This function load the OCSP Response in DER format contained in file at
William Lallemand03c331c2020-05-13 10:10:01 +0200245 * path 'ocsp_path' or base64 in a buffer <buf>
246 *
247 * Returns 0 on success, 1 in error case.
248 */
249int ssl_sock_load_ocsp_response_from_file(const char *ocsp_path, char *buf, struct cert_key_and_chain *ckch, char **err)
250{
251 int fd = -1;
252 int r = 0;
253 int ret = 1;
254 struct buffer *ocsp_response;
255 struct buffer *src = NULL;
256
257 if (buf) {
258 int i, j;
259 /* if it's from a buffer it will be base64 */
260
261 /* remove \r and \n from the payload */
262 for (i = 0, j = 0; buf[i]; i++) {
263 if (buf[i] == '\r' || buf[i] == '\n')
264 continue;
265 buf[j++] = buf[i];
266 }
267 buf[j] = 0;
268
269 ret = base64dec(buf, j, trash.area, trash.size);
270 if (ret < 0) {
271 memprintf(err, "Error reading OCSP response in base64 format");
272 goto end;
273 }
274 trash.data = ret;
275 src = &trash;
276 } else {
277 fd = open(ocsp_path, O_RDONLY);
278 if (fd == -1) {
279 memprintf(err, "Error opening OCSP response file");
280 goto end;
281 }
282
283 trash.data = 0;
284 while (trash.data < trash.size) {
285 r = read(fd, trash.area + trash.data, trash.size - trash.data);
286 if (r < 0) {
287 if (errno == EINTR)
288 continue;
289
290 memprintf(err, "Error reading OCSP response from file");
291 goto end;
292 }
293 else if (r == 0) {
294 break;
295 }
296 trash.data += r;
297 }
298 close(fd);
299 fd = -1;
300 src = &trash;
301 }
302
303 ocsp_response = calloc(1, sizeof(*ocsp_response));
304 if (!chunk_dup(ocsp_response, src)) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100305 ha_free(&ocsp_response);
William Lallemand03c331c2020-05-13 10:10:01 +0200306 goto end;
307 }
308 /* no error, fill ckch with new context, old context must be free */
309 if (ckch->ocsp_response) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100310 ha_free(&ckch->ocsp_response->area);
William Lallemand03c331c2020-05-13 10:10:01 +0200311 free(ckch->ocsp_response);
312 }
313 ckch->ocsp_response = ocsp_response;
314 ret = 0;
315end:
316 if (fd != -1)
317 close(fd);
318
319 return ret;
320}
321#endif
322
323/*
324 * Try to load in a ckch every files related to a ckch.
325 * (PEM, sctl, ocsp, issuer etc.)
326 *
327 * This function is only used to load files during the configuration parsing,
328 * it is not used with the CLI.
329 *
330 * This allows us to carry the contents of the file without having to read the
331 * file multiple times. The caller must call
332 * ssl_sock_free_cert_key_and_chain_contents.
333 *
334 * returns:
335 * 0 on Success
336 * 1 on SSL Failure
337 */
338int ssl_sock_load_files_into_ckch(const char *path, struct cert_key_and_chain *ckch, char **err)
339{
William Lallemand8e8581e2020-10-20 17:36:46 +0200340 struct buffer *fp = NULL;
William Lallemand03c331c2020-05-13 10:10:01 +0200341 int ret = 1;
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;
Willy Tarreau4596fe22022-05-17 19:07:51 +02001322 struct stconn *cs = appctx_cs(appctx);
William Lallemandda8584c2020-05-14 10:14:37 +02001323 struct ckch_store *ckchs;
1324
1325 if (trash == NULL)
1326 return 1;
1327
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001328 if (!ctx->old_ckchs) {
William Lallemandda8584c2020-05-14 10:14:37 +02001329 if (ckchs_transaction.old_ckchs) {
1330 ckchs = ckchs_transaction.old_ckchs;
1331 chunk_appendf(trash, "# transaction\n");
William Lallemand5685ccf2020-09-16 16:12:25 +02001332 chunk_appendf(trash, "*%s\n", ckchs->path);
William Lallemandda8584c2020-05-14 10:14:37 +02001333 }
1334 }
1335
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001336 if (!ctx->cur_ckchs) {
William Lallemandda8584c2020-05-14 10:14:37 +02001337 chunk_appendf(trash, "# filename\n");
1338 node = ebmb_first(&ckchs_tree);
1339 } else {
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001340 node = &ctx->cur_ckchs->node;
William Lallemandda8584c2020-05-14 10:14:37 +02001341 }
1342 while (node) {
1343 ckchs = ebmb_entry(node, struct ckch_store, node);
William Lallemand5685ccf2020-09-16 16:12:25 +02001344 chunk_appendf(trash, "%s\n", ckchs->path);
William Lallemandda8584c2020-05-14 10:14:37 +02001345
1346 node = ebmb_next(node);
Christopher Faulet908628c2022-03-25 16:43:49 +01001347 if (ci_putchk(cs_ic(cs), trash) == -1) {
Christopher Fauleta0bdec32022-04-04 07:51:21 +02001348 cs_rx_room_blk(cs);
William Lallemandda8584c2020-05-14 10:14:37 +02001349 goto yield;
1350 }
1351 }
1352
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001353 ctx->cur_ckchs = NULL;
William Lallemandda8584c2020-05-14 10:14:37 +02001354 free_trash_chunk(trash);
1355 return 1;
1356yield:
1357
1358 free_trash_chunk(trash);
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001359 ctx->cur_ckchs = ckchs;
William Lallemandda8584c2020-05-14 10:14:37 +02001360 return 0; /* should come back */
1361}
1362
1363/*
1364 * Extract and format the DNS SAN extensions and copy result into a chuink
1365 * Return 0;
1366 */
1367#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
1368static int ssl_sock_get_san_oneline(X509 *cert, struct buffer *out)
1369{
1370 int i;
1371 char *str;
1372 STACK_OF(GENERAL_NAME) *names = NULL;
1373
1374 names = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
1375 if (names) {
1376 for (i = 0; i < sk_GENERAL_NAME_num(names); i++) {
1377 GENERAL_NAME *name = sk_GENERAL_NAME_value(names, i);
1378 if (i > 0)
1379 chunk_appendf(out, ", ");
1380 if (name->type == GEN_DNS) {
1381 if (ASN1_STRING_to_UTF8((unsigned char **)&str, name->d.dNSName) >= 0) {
1382 chunk_appendf(out, "DNS:%s", str);
1383 OPENSSL_free(str);
1384 }
1385 }
1386 }
1387 sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free);
1388 }
1389 return 0;
1390}
1391#endif
1392
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001393/*
1394 * Build the ckch_inst_link that will be chained in the CA file entry and the
1395 * corresponding ckch_inst_link_ref that will be chained in the ckch instance.
1396 * Return 0 in case of success.
1397 */
1398static int do_chain_inst_and_cafile(struct cafile_entry *cafile_entry, struct ckch_inst *ckch_inst)
1399{
1400 struct ckch_inst_link *new_link;
1401 if (!LIST_ISEMPTY(&cafile_entry->ckch_inst_link)) {
1402 struct ckch_inst_link *link = LIST_ELEM(cafile_entry->ckch_inst_link.n,
1403 typeof(link), list);
1404 /* Do not add multiple references to the same
1405 * instance in a cafile_entry */
1406 if (link->ckch_inst == ckch_inst) {
1407 return 1;
1408 }
1409 }
1410
1411 new_link = calloc(1, sizeof(*new_link));
1412 if (new_link) {
1413 struct ckch_inst_link_ref *new_link_ref = calloc(1, sizeof(*new_link_ref));
1414 if (!new_link_ref) {
1415 free(new_link);
1416 return 1;
1417 }
1418
1419 new_link->ckch_inst = ckch_inst;
1420 new_link_ref->link = new_link;
1421 LIST_INIT(&new_link->list);
1422 LIST_INIT(&new_link_ref->list);
1423
1424 LIST_APPEND(&cafile_entry->ckch_inst_link, &new_link->list);
1425 LIST_APPEND(&ckch_inst->cafile_link_refs, &new_link_ref->list);
1426 }
1427
1428 return 0;
1429}
1430
1431
1432/*
1433 * Link a CA file tree entry to the ckch instance that uses it.
1434 * To determine if and which CA file tree entries need to be linked to the
1435 * instance, we follow the same logic performed in ssl_sock_prepare_ctx when
1436 * processing the verify option.
1437 * This function works for a frontend as well as for a backend, depending on the
1438 * configuration parameters given (bind_conf or server).
1439 */
1440void ckch_inst_add_cafile_link(struct ckch_inst *ckch_inst, struct bind_conf *bind_conf,
1441 struct ssl_bind_conf *ssl_conf, const struct server *srv)
1442{
1443 int verify = SSL_VERIFY_NONE;
1444
1445 if (srv) {
1446
1447 if (global.ssl_server_verify == SSL_SERVER_VERIFY_REQUIRED)
1448 verify = SSL_VERIFY_PEER;
1449 switch (srv->ssl_ctx.verify) {
1450 case SSL_SOCK_VERIFY_NONE:
1451 verify = SSL_VERIFY_NONE;
1452 break;
1453 case SSL_SOCK_VERIFY_REQUIRED:
1454 verify = SSL_VERIFY_PEER;
1455 break;
1456 }
1457 }
1458 else {
1459 switch ((ssl_conf && ssl_conf->verify) ? ssl_conf->verify : bind_conf->ssl_conf.verify) {
1460 case SSL_SOCK_VERIFY_NONE:
1461 verify = SSL_VERIFY_NONE;
1462 break;
1463 case SSL_SOCK_VERIFY_OPTIONAL:
1464 verify = SSL_VERIFY_PEER;
1465 break;
1466 case SSL_SOCK_VERIFY_REQUIRED:
1467 verify = SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
1468 break;
1469 }
1470 }
1471
1472 if (verify & SSL_VERIFY_PEER) {
1473 struct cafile_entry *ca_file_entry = NULL;
1474 struct cafile_entry *ca_verify_file_entry = NULL;
Remi Tricot-Le Bretonf81c70c2021-04-20 16:54:21 +02001475 struct cafile_entry *crl_file_entry = NULL;
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001476 if (srv) {
1477 if (srv->ssl_ctx.ca_file) {
1478 ca_file_entry = ssl_store_get_cafile_entry(srv->ssl_ctx.ca_file, 0);
1479
1480 }
Remi Tricot-Le Bretonf81c70c2021-04-20 16:54:21 +02001481 if (srv->ssl_ctx.crl_file) {
1482 crl_file_entry = ssl_store_get_cafile_entry(srv->ssl_ctx.crl_file, 0);
1483 }
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001484 }
1485 else {
1486 char *ca_file = (ssl_conf && ssl_conf->ca_file) ? ssl_conf->ca_file : bind_conf->ssl_conf.ca_file;
1487 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 +02001488 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 +01001489
1490 if (ca_file)
1491 ca_file_entry = ssl_store_get_cafile_entry(ca_file, 0);
1492 if (ca_verify_file)
1493 ca_verify_file_entry = ssl_store_get_cafile_entry(ca_verify_file, 0);
Remi Tricot-Le Bretonf81c70c2021-04-20 16:54:21 +02001494 if (crl_file)
1495 crl_file_entry = ssl_store_get_cafile_entry(crl_file, 0);
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001496 }
1497
1498 if (ca_file_entry) {
1499 /* If we have a ckch instance that is not already in the
1500 * cafile_entry's list, add it to it. */
1501 if (do_chain_inst_and_cafile(ca_file_entry, ckch_inst))
1502 return;
1503
1504 }
1505 if (ca_verify_file_entry && (ca_file_entry != ca_verify_file_entry)) {
1506 /* If we have a ckch instance that is not already in the
1507 * cafile_entry's list, add it to it. */
1508 if (do_chain_inst_and_cafile(ca_verify_file_entry, ckch_inst))
1509 return;
1510 }
Remi Tricot-Le Bretonf81c70c2021-04-20 16:54:21 +02001511 if (crl_file_entry) {
1512 /* If we have a ckch instance that is not already in the
1513 * cafile_entry's list, add it to it. */
1514 if (do_chain_inst_and_cafile(crl_file_entry, ckch_inst))
1515 return;
1516 }
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001517 }
1518}
1519
William Lallemandda8584c2020-05-14 10:14:37 +02001520
1521
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001522static int show_cert_detail(X509 *cert, STACK_OF(X509) *chain, struct buffer *out)
William Lallemandda8584c2020-05-14 10:14:37 +02001523{
William Lallemandda8584c2020-05-14 10:14:37 +02001524 BIO *bio = NULL;
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001525 struct buffer *tmp = alloc_trash_chunk();
William Lallemandda8584c2020-05-14 10:14:37 +02001526 int i;
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001527 int write = -1;
1528 unsigned int len = 0;
1529 X509_NAME *name = NULL;
William Lallemandda8584c2020-05-14 10:14:37 +02001530
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001531 if (!tmp)
1532 return -1;
William Lallemandda8584c2020-05-14 10:14:37 +02001533
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001534 if (!cert)
William Lallemand5685ccf2020-09-16 16:12:25 +02001535 goto end;
William Lallemandda8584c2020-05-14 10:14:37 +02001536
William Lallemand5685ccf2020-09-16 16:12:25 +02001537 if (chain == NULL) {
1538 struct issuer_chain *issuer;
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001539 issuer = ssl_get0_issuer_chain(cert);
William Lallemand5685ccf2020-09-16 16:12:25 +02001540 if (issuer) {
1541 chain = issuer->chain;
1542 chunk_appendf(out, "Chain Filename: ");
1543 chunk_appendf(out, "%s\n", issuer->path);
William Lallemandda8584c2020-05-14 10:14:37 +02001544 }
William Lallemand5685ccf2020-09-16 16:12:25 +02001545 }
1546 chunk_appendf(out, "Serial: ");
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001547 if (ssl_sock_get_serial(cert, tmp) == -1)
William Lallemand5685ccf2020-09-16 16:12:25 +02001548 goto end;
1549 dump_binary(out, tmp->area, tmp->data);
1550 chunk_appendf(out, "\n");
William Lallemandda8584c2020-05-14 10:14:37 +02001551
William Lallemand5685ccf2020-09-16 16:12:25 +02001552 chunk_appendf(out, "notBefore: ");
1553 chunk_reset(tmp);
1554 if ((bio = BIO_new(BIO_s_mem())) == NULL)
1555 goto end;
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001556 if (ASN1_TIME_print(bio, X509_getm_notBefore(cert)) == 0)
William Lallemand5685ccf2020-09-16 16:12:25 +02001557 goto end;
1558 write = BIO_read(bio, tmp->area, tmp->size-1);
1559 tmp->area[write] = '\0';
1560 BIO_free(bio);
1561 bio = NULL;
1562 chunk_appendf(out, "%s\n", tmp->area);
William Lallemandda8584c2020-05-14 10:14:37 +02001563
William Lallemand5685ccf2020-09-16 16:12:25 +02001564 chunk_appendf(out, "notAfter: ");
1565 chunk_reset(tmp);
1566 if ((bio = BIO_new(BIO_s_mem())) == NULL)
1567 goto end;
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001568 if (ASN1_TIME_print(bio, X509_getm_notAfter(cert)) == 0)
William Lallemand5685ccf2020-09-16 16:12:25 +02001569 goto end;
1570 if ((write = BIO_read(bio, tmp->area, tmp->size-1)) <= 0)
1571 goto end;
1572 tmp->area[write] = '\0';
1573 BIO_free(bio);
1574 bio = NULL;
1575 chunk_appendf(out, "%s\n", tmp->area);
William Lallemandda8584c2020-05-14 10:14:37 +02001576
1577#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
William Lallemand5685ccf2020-09-16 16:12:25 +02001578 chunk_appendf(out, "Subject Alternative Name: ");
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001579 if (ssl_sock_get_san_oneline(cert, out) == -1)
William Lallemand5685ccf2020-09-16 16:12:25 +02001580 goto end;
1581 *(out->area + out->data) = '\0';
1582 chunk_appendf(out, "\n");
William Lallemandda8584c2020-05-14 10:14:37 +02001583#endif
William Lallemand5685ccf2020-09-16 16:12:25 +02001584 chunk_reset(tmp);
1585 chunk_appendf(out, "Algorithm: ");
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001586 if (cert_get_pkey_algo(cert, tmp) == 0)
William Lallemand5685ccf2020-09-16 16:12:25 +02001587 goto end;
1588 chunk_appendf(out, "%s\n", tmp->area);
William Lallemandda8584c2020-05-14 10:14:37 +02001589
William Lallemand5685ccf2020-09-16 16:12:25 +02001590 chunk_reset(tmp);
1591 chunk_appendf(out, "SHA1 FingerPrint: ");
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001592 if (X509_digest(cert, EVP_sha1(), (unsigned char *) tmp->area, &len) == 0)
William Lallemand5685ccf2020-09-16 16:12:25 +02001593 goto end;
1594 tmp->data = len;
1595 dump_binary(out, tmp->area, tmp->data);
1596 chunk_appendf(out, "\n");
William Lallemandda8584c2020-05-14 10:14:37 +02001597
William Lallemand5685ccf2020-09-16 16:12:25 +02001598 chunk_appendf(out, "Subject: ");
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001599 if ((name = X509_get_subject_name(cert)) == NULL)
William Lallemand5685ccf2020-09-16 16:12:25 +02001600 goto end;
1601 if ((ssl_sock_get_dn_oneline(name, tmp)) == -1)
1602 goto end;
1603 *(tmp->area + tmp->data) = '\0';
1604 chunk_appendf(out, "%s\n", tmp->area);
1605
1606 chunk_appendf(out, "Issuer: ");
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001607 if ((name = X509_get_issuer_name(cert)) == NULL)
William Lallemand5685ccf2020-09-16 16:12:25 +02001608 goto end;
1609 if ((ssl_sock_get_dn_oneline(name, tmp)) == -1)
1610 goto end;
1611 *(tmp->area + tmp->data) = '\0';
1612 chunk_appendf(out, "%s\n", tmp->area);
1613
1614 /* Displays subject of each certificate in the chain */
1615 for (i = 0; i < sk_X509_num(chain); i++) {
1616 X509 *ca = sk_X509_value(chain, i);
1617
1618 chunk_appendf(out, "Chain Subject: ");
1619 if ((name = X509_get_subject_name(ca)) == NULL)
William Lallemandda8584c2020-05-14 10:14:37 +02001620 goto end;
1621 if ((ssl_sock_get_dn_oneline(name, tmp)) == -1)
1622 goto end;
1623 *(tmp->area + tmp->data) = '\0';
1624 chunk_appendf(out, "%s\n", tmp->area);
1625
William Lallemand5685ccf2020-09-16 16:12:25 +02001626 chunk_appendf(out, "Chain Issuer: ");
1627 if ((name = X509_get_issuer_name(ca)) == NULL)
William Lallemandda8584c2020-05-14 10:14:37 +02001628 goto end;
1629 if ((ssl_sock_get_dn_oneline(name, tmp)) == -1)
1630 goto end;
1631 *(tmp->area + tmp->data) = '\0';
1632 chunk_appendf(out, "%s\n", tmp->area);
William Lallemandda8584c2020-05-14 10:14:37 +02001633 }
1634
1635end:
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001636 if (bio)
1637 BIO_free(bio);
1638 free_trash_chunk(tmp);
1639
1640 return 0;
1641}
1642
Remi Tricot-Le Breton3faf0cb2021-06-10 18:10:32 +02001643#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 +02001644/*
1645 * Build the OCSP tree entry's key for a given ckch_store.
1646 * Returns a negative value in case of error.
1647 */
1648static int ckch_store_build_certid(struct ckch_store *ckch_store, unsigned char certid[128], unsigned int *key_length)
1649{
1650 OCSP_RESPONSE *resp;
1651 OCSP_BASICRESP *bs = NULL;
1652 OCSP_SINGLERESP *sr;
1653 OCSP_CERTID *id;
1654 unsigned char *p = NULL;
1655
1656 if (!key_length)
1657 return -1;
1658
1659 *key_length = 0;
1660
1661 if (!ckch_store->ckch->ocsp_response)
1662 return 0;
1663
1664 p = (unsigned char *) ckch_store->ckch->ocsp_response->area;
1665
1666 resp = d2i_OCSP_RESPONSE(NULL, (const unsigned char **)&p,
1667 ckch_store->ckch->ocsp_response->data);
1668 if (!resp) {
1669 goto end;
1670 }
1671
1672 bs = OCSP_response_get1_basic(resp);
1673 if (!bs) {
1674 goto end;
1675 }
1676
1677 sr = OCSP_resp_get0(bs, 0);
1678 if (!sr) {
1679 goto end;
1680 }
1681
1682 id = (OCSP_CERTID*)OCSP_SINGLERESP_get0_id(sr);
1683
1684 p = certid;
1685 *key_length = i2d_OCSP_CERTID(id, &p);
1686
1687end:
1688 return *key_length > 0;
1689}
1690#endif
1691
1692/*
1693 * Dump the OCSP certificate key (if it exists) of certificate <ckch> into
1694 * buffer <out>.
1695 * Returns 0 in case of success.
1696 */
1697static int ckch_store_show_ocsp_certid(struct ckch_store *ckch_store, struct buffer *out)
1698{
Remi Tricot-Le Breton3faf0cb2021-06-10 18:10:32 +02001699#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 +02001700 unsigned char key[OCSP_MAX_CERTID_ASN1_LENGTH] = {};
1701 unsigned int key_length = 0;
1702 int i;
1703
1704 if (ckch_store_build_certid(ckch_store, (unsigned char*)key, &key_length) >= 0) {
1705 /* Dump the CERTID info */
1706 chunk_appendf(out, "OCSP Response Key: ");
1707 for (i = 0; i < key_length; ++i) {
1708 chunk_appendf(out, "%02x", key[i]);
1709 }
1710 chunk_appendf(out, "\n");
1711 }
1712#endif
1713
1714 return 0;
1715}
1716
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001717
Willy Tarreau4fd9b4d2022-05-04 16:11:50 +02001718/* IO handler of the details "show ssl cert <filename>".
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001719 * It uses a struct show_cert_ctx and ckchs_transaction in read-only.
Willy Tarreau4fd9b4d2022-05-04 16:11:50 +02001720 */
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001721static int cli_io_handler_show_cert_detail(struct appctx *appctx)
1722{
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001723 struct show_cert_ctx *ctx = appctx->svcctx;
Willy Tarreau4596fe22022-05-17 19:07:51 +02001724 struct stconn *cs = appctx_cs(appctx);
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001725 struct ckch_store *ckchs = ctx->cur_ckchs;
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001726 struct buffer *out = alloc_trash_chunk();
1727 int retval = 0;
1728
1729 if (!out)
1730 goto end_no_putchk;
1731
1732 chunk_appendf(out, "Filename: ");
1733 if (ckchs == ckchs_transaction.new_ckchs)
1734 chunk_appendf(out, "*");
1735 chunk_appendf(out, "%s\n", ckchs->path);
1736
1737 chunk_appendf(out, "Status: ");
1738 if (ckchs->ckch->cert == NULL)
1739 chunk_appendf(out, "Empty\n");
1740 else if (LIST_ISEMPTY(&ckchs->ckch_inst))
1741 chunk_appendf(out, "Unused\n");
1742 else
1743 chunk_appendf(out, "Used\n");
1744
1745 retval = show_cert_detail(ckchs->ckch->cert, ckchs->ckch->chain, out);
1746 if (retval < 0)
1747 goto end_no_putchk;
1748 else if (retval)
1749 goto end;
1750
Remi Tricot-Le Bretonda968f62021-06-10 13:51:14 +02001751 ckch_store_show_ocsp_certid(ckchs, out);
1752
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001753end:
Christopher Faulet908628c2022-03-25 16:43:49 +01001754 if (ci_putchk(cs_ic(cs), out) == -1) {
Christopher Fauleta0bdec32022-04-04 07:51:21 +02001755 cs_rx_room_blk(cs);
William Lallemandda8584c2020-05-14 10:14:37 +02001756 goto yield;
1757 }
1758
1759end_no_putchk:
William Lallemandda8584c2020-05-14 10:14:37 +02001760 free_trash_chunk(out);
1761 return 1;
1762yield:
William Lallemandda8584c2020-05-14 10:14:37 +02001763 free_trash_chunk(out);
1764 return 0; /* should come back */
1765}
1766
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001767
Willy Tarreau4fd9b4d2022-05-04 16:11:50 +02001768/* IO handler of the details "show ssl cert <filename.ocsp>".
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001769 * It uses a show_cert_ctx.
Willy Tarreau4fd9b4d2022-05-04 16:11:50 +02001770 */
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001771static int cli_io_handler_show_cert_ocsp_detail(struct appctx *appctx)
1772{
Remi Tricot-Le Breton3faf0cb2021-06-10 18:10:32 +02001773#if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) && !defined OPENSSL_IS_BORINGSSL)
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001774 struct show_cert_ctx *ctx = appctx->svcctx;
Willy Tarreau4596fe22022-05-17 19:07:51 +02001775 struct stconn *cs = appctx_cs(appctx);
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001776 struct ckch_store *ckchs = ctx->cur_ckchs;
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001777 struct buffer *out = alloc_trash_chunk();
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001778 int from_transaction = ctx->transaction;
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001779
1780 if (!out)
1781 goto end_no_putchk;
1782
1783 /* If we try to display an ongoing transaction's OCSP response, we
1784 * need to dump the ckch's ocsp_response buffer directly.
1785 * Otherwise, we must rebuild the certificate's certid in order to
1786 * look for the current OCSP response in the tree. */
1787 if (from_transaction && ckchs->ckch->ocsp_response) {
Remi Tricot-Le Bretona9a591a2022-02-16 14:42:22 +01001788 if (ssl_ocsp_response_print(ckchs->ckch->ocsp_response, out))
1789 goto end_no_putchk;
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001790 }
1791 else {
1792 unsigned char key[OCSP_MAX_CERTID_ASN1_LENGTH] = {};
1793 unsigned int key_length = 0;
1794
1795 if (ckch_store_build_certid(ckchs, (unsigned char*)key, &key_length) < 0)
1796 goto end_no_putchk;
1797
Remi Tricot-Le Bretona9a591a2022-02-16 14:42:22 +01001798 if (ssl_get_ocspresponse_detail(key, out))
1799 goto end_no_putchk;
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001800 }
1801
Christopher Faulet908628c2022-03-25 16:43:49 +01001802 if (ci_putchk(cs_ic(cs), out) == -1) {
Christopher Fauleta0bdec32022-04-04 07:51:21 +02001803 cs_rx_room_blk(cs);
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001804 goto yield;
1805 }
1806
1807end_no_putchk:
1808 free_trash_chunk(out);
1809 return 1;
1810yield:
1811 free_trash_chunk(out);
1812 return 0; /* should come back */
1813#else
1814 return cli_err(appctx, "HAProxy was compiled against a version of OpenSSL that doesn't support OCSP stapling.\n");
1815#endif
1816}
1817
William Lallemandda8584c2020-05-14 10:14:37 +02001818/* parsing function for 'show ssl cert [certfile]' */
1819static int cli_parse_show_cert(char **args, char *payload, struct appctx *appctx, void *private)
1820{
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001821 struct show_cert_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
William Lallemandda8584c2020-05-14 10:14:37 +02001822 struct ckch_store *ckchs;
1823
1824 if (!cli_has_level(appctx, ACCESS_LVL_OPER))
1825 return cli_err(appctx, "Can't allocate memory!\n");
1826
1827 /* The operations on the CKCH architecture are locked so we can
1828 * manipulate ckch_store and ckch_inst */
1829 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
1830 return cli_err(appctx, "Can't show!\nOperations on certificates are currently locked!\n");
1831
1832 /* check if there is a certificate to lookup */
1833 if (*args[3]) {
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001834 int show_ocsp_detail = 0;
1835 int from_transaction = 0;
1836 char *end;
1837
1838 /* We manage the special case "certname.ocsp" through which we
1839 * can show the details of an OCSP response. */
1840 end = strrchr(args[3], '.');
1841 if (end && strcmp(end+1, "ocsp") == 0) {
1842 *end = '\0';
1843 show_ocsp_detail = 1;
1844 }
1845
William Lallemandda8584c2020-05-14 10:14:37 +02001846 if (*args[3] == '*') {
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001847 from_transaction = 1;
William Lallemandda8584c2020-05-14 10:14:37 +02001848 if (!ckchs_transaction.new_ckchs)
1849 goto error;
1850
1851 ckchs = ckchs_transaction.new_ckchs;
1852
Tim Duesterhuse5ff1412021-01-02 22:31:53 +01001853 if (strcmp(args[3] + 1, ckchs->path) != 0)
William Lallemandda8584c2020-05-14 10:14:37 +02001854 goto error;
1855
1856 } else {
1857 if ((ckchs = ckchs_lookup(args[3])) == NULL)
1858 goto error;
1859
1860 }
1861
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001862 ctx->cur_ckchs = ckchs;
William Lallemandda8584c2020-05-14 10:14:37 +02001863 /* use the IO handler that shows details */
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001864 if (show_ocsp_detail) {
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001865 ctx->transaction = from_transaction;
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001866 appctx->io_handler = cli_io_handler_show_cert_ocsp_detail;
1867 }
1868 else
1869 appctx->io_handler = cli_io_handler_show_cert_detail;
William Lallemandda8584c2020-05-14 10:14:37 +02001870 }
1871
1872 return 0;
1873
1874error:
1875 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1876 return cli_err(appctx, "Can't display the certificate: Not found or the certificate is a bundle!\n");
1877}
1878
1879/* release function of the `set ssl cert' command, free things and unlock the spinlock */
1880static void cli_release_commit_cert(struct appctx *appctx)
1881{
Willy Tarreaua645b6a2022-05-04 19:58:00 +02001882 struct commit_cert_ctx *ctx = appctx->svcctx;
William Lallemandda8584c2020-05-14 10:14:37 +02001883 struct ckch_store *new_ckchs;
1884
1885 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1886
Willy Tarreaucb1b4ed2022-05-05 08:15:27 +02001887 if (ctx->state != CERT_ST_FIN) {
William Lallemandda8584c2020-05-14 10:14:37 +02001888 /* 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 +02001889 new_ckchs = ctx->new_ckchs;
William Lallemandda8584c2020-05-14 10:14:37 +02001890
1891 /* if the allocation failed, we need to free everything from the temporary list */
1892 ckch_store_free(new_ckchs);
1893 }
1894}
1895
Remi Tricot-Le Bretonbfadc022021-02-24 12:20:48 +01001896
1897/*
1898 * Rebuild a new instance 'new_inst' based on an old instance 'ckchi' and a
1899 * specific ckch_store.
1900 * Returns 0 in case of success, 1 otherwise.
1901 */
William Lallemande60c7d62022-03-30 11:26:15 +02001902int ckch_inst_rebuild(struct ckch_store *ckch_store, struct ckch_inst *ckchi,
1903 struct ckch_inst **new_inst, char **err)
Remi Tricot-Le Bretonbfadc022021-02-24 12:20:48 +01001904{
1905 int retval = 0;
1906 int errcode = 0;
1907 struct sni_ctx *sc0, *sc0s;
1908 char **sni_filter = NULL;
1909 int fcount = 0;
1910
1911 if (ckchi->crtlist_entry) {
1912 sni_filter = ckchi->crtlist_entry->filters;
1913 fcount = ckchi->crtlist_entry->fcount;
1914 }
1915
1916 if (ckchi->is_server_instance)
1917 errcode |= ckch_inst_new_load_srv_store(ckch_store->path, ckch_store, new_inst, err);
1918 else
1919 errcode |= ckch_inst_new_load_store(ckch_store->path, ckch_store, ckchi->bind_conf, ckchi->ssl_conf, sni_filter, fcount, new_inst, err);
1920
1921 if (errcode & ERR_CODE)
1922 return 1;
1923
1924 /* if the previous ckchi was used as the default */
1925 if (ckchi->is_default)
1926 (*new_inst)->is_default = 1;
1927
1928 (*new_inst)->is_server_instance = ckchi->is_server_instance;
1929 (*new_inst)->server = ckchi->server;
1930 /* Create a new SSL_CTX and link it to the new instance. */
1931 if ((*new_inst)->is_server_instance) {
1932 retval = ssl_sock_prep_srv_ctx_and_inst(ckchi->server, (*new_inst)->ctx, (*new_inst));
1933 if (retval)
1934 return 1;
1935 }
1936
1937 /* create the link to the crtlist_entry */
1938 (*new_inst)->crtlist_entry = ckchi->crtlist_entry;
1939
1940 /* we need to initialize the SSL_CTX generated */
1941 /* this iterate on the newly generated SNIs in the new instance to prepare their SSL_CTX */
1942 list_for_each_entry_safe(sc0, sc0s, &(*new_inst)->sni_ctx, by_ckch_inst) {
1943 if (!sc0->order) { /* we initialized only the first SSL_CTX because it's the same in the other sni_ctx's */
1944 errcode |= ssl_sock_prep_ctx_and_inst(ckchi->bind_conf, ckchi->ssl_conf, sc0->ctx, *new_inst, err);
1945 if (errcode & ERR_CODE)
1946 return 1;
1947 }
1948 }
1949
1950 return 0;
1951}
1952
1953/*
1954 * Load all the new SNIs of a newly built ckch instance in the trees, or replace
1955 * a server's main ckch instance.
1956 */
1957static void __ssl_sock_load_new_ckch_instance(struct ckch_inst *ckchi)
1958{
1959 /* The bind_conf will be null on server ckch_instances. */
1960 if (ckchi->is_server_instance) {
1961 int i;
1962 /* a lock is needed here since we have to free the SSL cache */
1963 HA_RWLOCK_WRLOCK(SSL_SERVER_LOCK, &ckchi->server->ssl_ctx.lock);
1964 /* free the server current SSL_CTX */
1965 SSL_CTX_free(ckchi->server->ssl_ctx.ctx);
1966 /* Actual ssl context update */
1967 SSL_CTX_up_ref(ckchi->ctx);
1968 ckchi->server->ssl_ctx.ctx = ckchi->ctx;
1969 ckchi->server->ssl_ctx.inst = ckchi;
1970
1971 /* flush the session cache of the server */
1972 for (i = 0; i < global.nbthread; i++) {
William Lallemandce990332021-11-23 15:15:09 +01001973 ha_free(&ckchi->server->ssl_ctx.reused_sess[i].sni);
Remi Tricot-Le Bretonbfadc022021-02-24 12:20:48 +01001974 ha_free(&ckchi->server->ssl_ctx.reused_sess[i].ptr);
1975 }
1976 HA_RWLOCK_WRUNLOCK(SSL_SERVER_LOCK, &ckchi->server->ssl_ctx.lock);
1977
1978 } else {
1979 HA_RWLOCK_WRLOCK(SNI_LOCK, &ckchi->bind_conf->sni_lock);
1980 ssl_sock_load_cert_sni(ckchi, ckchi->bind_conf);
1981 HA_RWLOCK_WRUNLOCK(SNI_LOCK, &ckchi->bind_conf->sni_lock);
1982 }
1983}
1984
1985/*
1986 * Delete a ckch instance that was replaced after a CLI command.
1987 */
1988static void __ckch_inst_free_locked(struct ckch_inst *ckchi)
1989{
1990 if (ckchi->is_server_instance) {
1991 /* no lock for servers */
1992 ckch_inst_free(ckchi);
1993 } else {
1994 struct bind_conf __maybe_unused *bind_conf = ckchi->bind_conf;
1995
1996 HA_RWLOCK_WRLOCK(SNI_LOCK, &bind_conf->sni_lock);
1997 ckch_inst_free(ckchi);
1998 HA_RWLOCK_WRUNLOCK(SNI_LOCK, &bind_conf->sni_lock);
1999 }
2000}
2001
William Lallemand3b5a3a62022-03-29 14:29:31 +02002002/* Replace a ckch_store in the ckch tree and insert the whole dependencies,
2003* then free the previous dependencies and store.
2004* Used in the case of a certificate update.
2005*
2006* Every dependencies must allocated before using this function.
2007*
2008* This function can't fail as it only update pointers, and does not alloc anything.
2009*
2010* /!\ This function must be used under the ckch lock. /!\
2011*
2012* - Insert every dependencies (SNI, crtlist_entry, ckch_inst, etc)
2013* - Delete the old ckch_store from the tree
2014* - Insert the new ckch_store
2015* - Free the old dependencies and the old ckch_store
2016*/
2017void ckch_store_replace(struct ckch_store *old_ckchs, struct ckch_store *new_ckchs)
2018{
2019 struct crtlist_entry *entry;
2020 struct ckch_inst *ckchi, *ckchis;
2021
2022 LIST_SPLICE(&new_ckchs->crtlist_entry, &old_ckchs->crtlist_entry);
2023 list_for_each_entry(entry, &new_ckchs->crtlist_entry, by_ckch_store) {
2024 ebpt_delete(&entry->node);
2025 /* change the ptr and reinsert the node */
2026 entry->node.key = new_ckchs;
2027 ebpt_insert(&entry->crtlist->entries, &entry->node);
2028 }
2029 /* insert the new ckch_insts in the crtlist_entry */
2030 list_for_each_entry(ckchi, &new_ckchs->ckch_inst, by_ckchs) {
2031 if (ckchi->crtlist_entry)
2032 LIST_INSERT(&ckchi->crtlist_entry->ckch_inst, &ckchi->by_crtlist_entry);
2033 }
2034 /* First, we insert every new SNIs in the trees, also replace the default_ctx */
2035 list_for_each_entry_safe(ckchi, ckchis, &new_ckchs->ckch_inst, by_ckchs) {
2036 __ssl_sock_load_new_ckch_instance(ckchi);
2037 }
2038 /* delete the old sni_ctx, the old ckch_insts and the ckch_store */
2039 list_for_each_entry_safe(ckchi, ckchis, &old_ckchs->ckch_inst, by_ckchs) {
2040 __ckch_inst_free_locked(ckchi);
2041 }
2042
2043 ckch_store_free(old_ckchs);
2044 ebst_insert(&ckchs_tree, &new_ckchs->node);
2045}
2046
Remi Tricot-Le Bretonbfadc022021-02-24 12:20:48 +01002047
William Lallemandda8584c2020-05-14 10:14:37 +02002048/*
2049 * This function tries to create the new ckch_inst and their SNIs
William Lallemand30fcca12022-03-30 12:03:12 +02002050 *
2051 * /!\ don't forget to update __hlua_ckch_commit() if you changes things there. /!\
William Lallemandda8584c2020-05-14 10:14:37 +02002052 */
2053static int cli_io_handler_commit_cert(struct appctx *appctx)
2054{
Willy Tarreaua645b6a2022-05-04 19:58:00 +02002055 struct commit_cert_ctx *ctx = appctx->svcctx;
Willy Tarreau4596fe22022-05-17 19:07:51 +02002056 struct stconn *cs = appctx_cs(appctx);
William Lallemandda8584c2020-05-14 10:14:37 +02002057 int y = 0;
2058 char *err = NULL;
William Lallemandda8584c2020-05-14 10:14:37 +02002059 struct ckch_store *old_ckchs, *new_ckchs = NULL;
William Lallemand3b5a3a62022-03-29 14:29:31 +02002060 struct ckch_inst *ckchi;
William Lallemandda8584c2020-05-14 10:14:37 +02002061 struct buffer *trash = alloc_trash_chunk();
William Lallemandda8584c2020-05-14 10:14:37 +02002062
2063 if (trash == NULL)
2064 goto error;
2065
Christopher Faulet908628c2022-03-25 16:43:49 +01002066 if (unlikely(cs_ic(cs)->flags & (CF_WRITE_ERROR|CF_SHUTW)))
William Lallemandda8584c2020-05-14 10:14:37 +02002067 goto error;
2068
2069 while (1) {
Willy Tarreaucb1b4ed2022-05-05 08:15:27 +02002070 switch (ctx->state) {
2071 case CERT_ST_INIT:
William Lallemandda8584c2020-05-14 10:14:37 +02002072 /* This state just print the update message */
2073 chunk_printf(trash, "Committing %s", ckchs_transaction.path);
Christopher Faulet908628c2022-03-25 16:43:49 +01002074 if (ci_putchk(cs_ic(cs), trash) == -1) {
Christopher Fauleta0bdec32022-04-04 07:51:21 +02002075 cs_rx_room_blk(cs);
William Lallemandda8584c2020-05-14 10:14:37 +02002076 goto yield;
2077 }
Willy Tarreaucb1b4ed2022-05-05 08:15:27 +02002078 ctx->state = CERT_ST_GEN;
William Lallemandda8584c2020-05-14 10:14:37 +02002079 /* fallthrough */
Willy Tarreaucb1b4ed2022-05-05 08:15:27 +02002080 case CERT_ST_GEN:
William Lallemandda8584c2020-05-14 10:14:37 +02002081 /*
2082 * This state generates the ckch instances with their
2083 * sni_ctxs and SSL_CTX.
2084 *
2085 * Since the SSL_CTX generation can be CPU consumer, we
2086 * yield every 10 instances.
2087 */
2088
Willy Tarreaua645b6a2022-05-04 19:58:00 +02002089 old_ckchs = ctx->old_ckchs;
2090 new_ckchs = ctx->new_ckchs;
William Lallemandda8584c2020-05-14 10:14:37 +02002091
2092 if (!new_ckchs)
2093 continue;
2094
2095 /* get the next ckchi to regenerate */
Willy Tarreaua645b6a2022-05-04 19:58:00 +02002096 ckchi = ctx->next_ckchi;
William Lallemandda8584c2020-05-14 10:14:37 +02002097 /* we didn't start yet, set it to the first elem */
2098 if (ckchi == NULL)
2099 ckchi = LIST_ELEM(old_ckchs->ckch_inst.n, typeof(ckchi), by_ckchs);
2100
2101 /* walk through the old ckch_inst and creates new ckch_inst using the updated ckchs */
2102 list_for_each_entry_from(ckchi, &old_ckchs->ckch_inst, by_ckchs) {
2103 struct ckch_inst *new_inst;
William Lallemandda8584c2020-05-14 10:14:37 +02002104
2105 /* it takes a lot of CPU to creates SSL_CTXs, so we yield every 10 CKCH instances */
2106 if (y >= 10) {
2107 /* save the next ckchi to compute */
Willy Tarreaua645b6a2022-05-04 19:58:00 +02002108 ctx->next_ckchi = ckchi;
William Lallemandda8584c2020-05-14 10:14:37 +02002109 goto yield;
2110 }
2111
Remi Tricot-Le Bretonbfadc022021-02-24 12:20:48 +01002112 if (ckch_inst_rebuild(new_ckchs, ckchi, &new_inst, &err))
William Lallemandda8584c2020-05-14 10:14:37 +02002113 goto error;
2114
William Lallemandda8584c2020-05-14 10:14:37 +02002115 /* display one dot per new instance */
2116 chunk_appendf(trash, ".");
2117 /* link the new ckch_inst to the duplicate */
Willy Tarreau2b718102021-04-21 07:32:39 +02002118 LIST_APPEND(&new_ckchs->ckch_inst, &new_inst->by_ckchs);
William Lallemandda8584c2020-05-14 10:14:37 +02002119 y++;
2120 }
Willy Tarreaucb1b4ed2022-05-05 08:15:27 +02002121 ctx->state = CERT_ST_INSERT;
William Lallemandda8584c2020-05-14 10:14:37 +02002122 /* fallthrough */
Willy Tarreaucb1b4ed2022-05-05 08:15:27 +02002123 case CERT_ST_INSERT:
William Lallemandda8584c2020-05-14 10:14:37 +02002124 /* The generation is finished, we can insert everything */
2125
Willy Tarreaua645b6a2022-05-04 19:58:00 +02002126 old_ckchs = ctx->old_ckchs;
2127 new_ckchs = ctx->new_ckchs;
William Lallemandda8584c2020-05-14 10:14:37 +02002128
2129 if (!new_ckchs)
2130 continue;
2131
William Lallemand3b5a3a62022-03-29 14:29:31 +02002132 /* insert everything and remove the previous objects */
2133 ckch_store_replace(old_ckchs, new_ckchs);
William Lallemandda8584c2020-05-14 10:14:37 +02002134
Willy Tarreaucb1b4ed2022-05-05 08:15:27 +02002135 ctx->state = CERT_ST_FIN;
William Lallemandda8584c2020-05-14 10:14:37 +02002136 /* fallthrough */
Willy Tarreaucb1b4ed2022-05-05 08:15:27 +02002137 case CERT_ST_FIN:
William Lallemandda8584c2020-05-14 10:14:37 +02002138 /* we achieved the transaction, we can set everything to NULL */
Willy Tarreau61cfdf42021-02-20 10:46:51 +01002139 ha_free(&ckchs_transaction.path);
William Lallemandda8584c2020-05-14 10:14:37 +02002140 ckchs_transaction.new_ckchs = NULL;
2141 ckchs_transaction.old_ckchs = NULL;
2142 goto end;
2143 }
2144 }
2145end:
2146
2147 chunk_appendf(trash, "\n");
William Lallemandda8584c2020-05-14 10:14:37 +02002148 chunk_appendf(trash, "Success!\n");
Christopher Faulet908628c2022-03-25 16:43:49 +01002149 if (ci_putchk(cs_ic(cs), trash) == -1)
Christopher Fauleta0bdec32022-04-04 07:51:21 +02002150 cs_rx_room_blk(cs);
William Lallemandda8584c2020-05-14 10:14:37 +02002151 free_trash_chunk(trash);
2152 /* success: call the release function and don't come back */
2153 return 1;
2154yield:
2155 /* store the state */
Christopher Faulet908628c2022-03-25 16:43:49 +01002156 if (ci_putchk(cs_ic(cs), trash) == -1)
Christopher Fauleta0bdec32022-04-04 07:51:21 +02002157 cs_rx_room_blk(cs);
William Lallemandda8584c2020-05-14 10:14:37 +02002158 free_trash_chunk(trash);
Christopher Fauleta0bdec32022-04-04 07:51:21 +02002159 cs_rx_endp_more(cs); /* let's come back later */
William Lallemandda8584c2020-05-14 10:14:37 +02002160 return 0; /* should come back */
2161
2162error:
2163 /* spin unlock and free are done in the release function */
2164 if (trash) {
2165 chunk_appendf(trash, "\n%sFailed!\n", err);
Christopher Faulet908628c2022-03-25 16:43:49 +01002166 if (ci_putchk(cs_ic(cs), trash) == -1)
Christopher Fauleta0bdec32022-04-04 07:51:21 +02002167 cs_rx_room_blk(cs);
William Lallemandda8584c2020-05-14 10:14:37 +02002168 free_trash_chunk(trash);
2169 }
2170 /* error: call the release function and don't come back */
2171 return 1;
2172}
2173
2174/*
2175 * Parsing function of 'commit ssl cert'
2176 */
2177static int cli_parse_commit_cert(char **args, char *payload, struct appctx *appctx, void *private)
2178{
Willy Tarreaua645b6a2022-05-04 19:58:00 +02002179 struct commit_cert_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
William Lallemandda8584c2020-05-14 10:14:37 +02002180 char *err = NULL;
2181
2182 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2183 return 1;
2184
2185 if (!*args[3])
2186 return cli_err(appctx, "'commit ssl cert expects a filename\n");
2187
2188 /* The operations on the CKCH architecture are locked so we can
2189 * manipulate ckch_store and ckch_inst */
2190 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2191 return cli_err(appctx, "Can't commit the certificate!\nOperations on certificates are currently locked!\n");
2192
2193 if (!ckchs_transaction.path) {
2194 memprintf(&err, "No ongoing transaction! !\n");
2195 goto error;
2196 }
2197
2198 if (strcmp(ckchs_transaction.path, args[3]) != 0) {
2199 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", ckchs_transaction.path, args[3]);
2200 goto error;
2201 }
2202
William Lallemand5685ccf2020-09-16 16:12:25 +02002203 /* if a certificate is here, a private key must be here too */
2204 if (ckchs_transaction.new_ckchs->ckch->cert && !ckchs_transaction.new_ckchs->ckch->key) {
2205 memprintf(&err, "The transaction must contain at least a certificate and a private key!\n");
2206 goto error;
2207 }
William Lallemanda9419522020-06-24 16:26:41 +02002208
William Lallemand5685ccf2020-09-16 16:12:25 +02002209 if (!X509_check_private_key(ckchs_transaction.new_ckchs->ckch->cert, ckchs_transaction.new_ckchs->ckch->key)) {
2210 memprintf(&err, "inconsistencies between private key and certificate loaded '%s'.\n", ckchs_transaction.path);
2211 goto error;
William Lallemandda8584c2020-05-14 10:14:37 +02002212 }
2213
2214 /* init the appctx structure */
Willy Tarreaucb1b4ed2022-05-05 08:15:27 +02002215 ctx->state = CERT_ST_INIT;
Willy Tarreaua645b6a2022-05-04 19:58:00 +02002216 ctx->next_ckchi = NULL;
2217 ctx->new_ckchs = ckchs_transaction.new_ckchs;
2218 ctx->old_ckchs = ckchs_transaction.old_ckchs;
William Lallemandda8584c2020-05-14 10:14:37 +02002219
2220 /* we don't unlock there, it will be unlock after the IO handler, in the release handler */
2221 return 0;
2222
2223error:
2224
2225 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2226 err = memprintf(&err, "%sCan't commit %s!\n", err ? err : "", args[3]);
2227
2228 return cli_dynerr(appctx, err);
2229}
2230
2231
2232
2233
2234/*
2235 * Parsing function of `set ssl cert`, it updates or creates a temporary ckch.
Willy Tarreau329f4b42022-05-04 20:05:55 +02002236 * It uses a set_cert_ctx context, and ckchs_transaction under a lock.
William Lallemandda8584c2020-05-14 10:14:37 +02002237 */
2238static int cli_parse_set_cert(char **args, char *payload, struct appctx *appctx, void *private)
2239{
Willy Tarreau329f4b42022-05-04 20:05:55 +02002240 struct set_cert_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
William Lallemandda8584c2020-05-14 10:14:37 +02002241 struct ckch_store *new_ckchs = NULL;
2242 struct ckch_store *old_ckchs = NULL;
2243 char *err = NULL;
2244 int i;
William Lallemandda8584c2020-05-14 10:14:37 +02002245 int errcode = 0;
2246 char *end;
William Lallemandff8bf982022-03-29 10:44:23 +02002247 struct cert_exts *cert_ext = &cert_exts[0]; /* default one, PEM */
William Lallemandda8584c2020-05-14 10:14:37 +02002248 struct cert_key_and_chain *ckch;
2249 struct buffer *buf;
2250
2251 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2252 return 1;
2253
William Lallemandda8584c2020-05-14 10:14:37 +02002254 if (!*args[3] || !payload)
2255 return cli_err(appctx, "'set ssl cert expects a filename and a certificate as a payload\n");
2256
2257 /* The operations on the CKCH architecture are locked so we can
2258 * manipulate ckch_store and ckch_inst */
2259 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2260 return cli_err(appctx, "Can't update the certificate!\nOperations on certificates are currently locked!\n");
2261
William Lallemand5ba80d62021-05-04 16:17:27 +02002262 if ((buf = alloc_trash_chunk()) == NULL) {
2263 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2264 errcode |= ERR_ALERT | ERR_FATAL;
2265 goto end;
2266 }
William Lallemande5ff4ad2020-06-08 09:40:37 +02002267
William Lallemandda8584c2020-05-14 10:14:37 +02002268 if (!chunk_strcpy(buf, args[3])) {
2269 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2270 errcode |= ERR_ALERT | ERR_FATAL;
2271 goto end;
2272 }
2273
2274 /* check which type of file we want to update */
William Lallemandff8bf982022-03-29 10:44:23 +02002275 for (i = 0; cert_exts[i].ext != NULL; i++) {
William Lallemandda8584c2020-05-14 10:14:37 +02002276 end = strrchr(buf->area, '.');
Tim Duesterhuse5ff1412021-01-02 22:31:53 +01002277 if (end && *cert_exts[i].ext && (strcmp(end + 1, cert_exts[i].ext) == 0)) {
William Lallemandda8584c2020-05-14 10:14:37 +02002278 *end = '\0';
William Lallemand089c1382020-10-23 17:35:12 +02002279 buf->data = strlen(buf->area);
William Lallemandff8bf982022-03-29 10:44:23 +02002280 cert_ext = &cert_exts[i];
William Lallemandda8584c2020-05-14 10:14:37 +02002281 break;
2282 }
2283 }
2284
Willy Tarreau329f4b42022-05-04 20:05:55 +02002285 ctx->old_ckchs = NULL;
2286 ctx->new_ckchs = NULL;
William Lallemandda8584c2020-05-14 10:14:37 +02002287
2288 /* if there is an ongoing transaction */
2289 if (ckchs_transaction.path) {
William Lallemandda8584c2020-05-14 10:14:37 +02002290 /* if there is an ongoing transaction, check if this is the same file */
2291 if (strcmp(ckchs_transaction.path, buf->area) != 0) {
William Lallemand089c1382020-10-23 17:35:12 +02002292 /* we didn't find the transaction, must try more cases below */
2293
2294 /* 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 +02002295 if (cert_ext->type != CERT_TYPE_PEM && global_ssl.extra_files_noext) {
William Lallemand089c1382020-10-23 17:35:12 +02002296 if (!chunk_strcat(buf, ".crt")) {
2297 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2298 errcode |= ERR_ALERT | ERR_FATAL;
2299 goto end;
2300 }
2301
2302 if (strcmp(ckchs_transaction.path, buf->area) != 0) {
2303 /* remove .crt of the error message */
2304 *(b_orig(buf) + b_data(buf) + strlen(".crt")) = '\0';
2305 b_sub(buf, strlen(".crt"));
2306
2307 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", ckchs_transaction.path, buf->area);
2308 errcode |= ERR_ALERT | ERR_FATAL;
2309 goto end;
2310 }
2311 }
William Lallemandda8584c2020-05-14 10:14:37 +02002312 }
2313
Willy Tarreau329f4b42022-05-04 20:05:55 +02002314 ctx->old_ckchs = ckchs_transaction.new_ckchs;
William Lallemandda8584c2020-05-14 10:14:37 +02002315
2316 } else {
William Lallemandda8584c2020-05-14 10:14:37 +02002317
William Lallemand95fefa12020-09-09 12:01:33 +02002318 /* lookup for the certificate in the tree */
Willy Tarreau329f4b42022-05-04 20:05:55 +02002319 ctx->old_ckchs = ckchs_lookup(buf->area);
William Lallemand089c1382020-10-23 17:35:12 +02002320
Willy Tarreau329f4b42022-05-04 20:05:55 +02002321 if (!ctx->old_ckchs) {
William Lallemand089c1382020-10-23 17:35:12 +02002322 /* 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 +02002323 if (cert_ext->type != CERT_TYPE_PEM && global_ssl.extra_files_noext) {
William Lallemand089c1382020-10-23 17:35:12 +02002324 if (!chunk_strcat(buf, ".crt")) {
2325 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2326 errcode |= ERR_ALERT | ERR_FATAL;
2327 goto end;
2328 }
Willy Tarreau329f4b42022-05-04 20:05:55 +02002329 ctx->old_ckchs = ckchs_lookup(buf->area);
William Lallemand089c1382020-10-23 17:35:12 +02002330 }
2331 }
William Lallemandda8584c2020-05-14 10:14:37 +02002332 }
2333
Willy Tarreau329f4b42022-05-04 20:05:55 +02002334 if (!ctx->old_ckchs) {
William Lallemandda8584c2020-05-14 10:14:37 +02002335 memprintf(&err, "%sCan't replace a certificate which is not referenced by the configuration!\n",
2336 err ? err : "");
2337 errcode |= ERR_ALERT | ERR_FATAL;
2338 goto end;
2339 }
2340
Willy Tarreau329f4b42022-05-04 20:05:55 +02002341 if (!ctx->path) {
William Lallemandda8584c2020-05-14 10:14:37 +02002342 /* this is a new transaction, set the path of the transaction */
Willy Tarreau329f4b42022-05-04 20:05:55 +02002343 ctx->path = strdup(ctx->old_ckchs->path);
2344 if (!ctx->path) {
William Lallemandda8584c2020-05-14 10:14:37 +02002345 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2346 errcode |= ERR_ALERT | ERR_FATAL;
2347 goto end;
2348 }
2349 }
2350
Willy Tarreau329f4b42022-05-04 20:05:55 +02002351 old_ckchs = ctx->old_ckchs;
William Lallemandda8584c2020-05-14 10:14:37 +02002352
2353 /* duplicate the ckch store */
2354 new_ckchs = ckchs_dup(old_ckchs);
2355 if (!new_ckchs) {
2356 memprintf(&err, "%sCannot allocate memory!\n",
2357 err ? err : "");
2358 errcode |= ERR_ALERT | ERR_FATAL;
2359 goto end;
2360 }
2361
William Lallemand95fefa12020-09-09 12:01:33 +02002362 ckch = new_ckchs->ckch;
William Lallemandda8584c2020-05-14 10:14:37 +02002363
2364 /* appply the change on the duplicate */
William Lallemandff8bf982022-03-29 10:44:23 +02002365 if (cert_ext->load(buf->area, payload, ckch, &err) != 0) {
William Lallemandda8584c2020-05-14 10:14:37 +02002366 memprintf(&err, "%sCan't load the payload\n", err ? err : "");
2367 errcode |= ERR_ALERT | ERR_FATAL;
2368 goto end;
2369 }
2370
Willy Tarreau329f4b42022-05-04 20:05:55 +02002371 ctx->new_ckchs = new_ckchs;
William Lallemandda8584c2020-05-14 10:14:37 +02002372
2373 /* we succeed, we can save the ckchs in the transaction */
2374
2375 /* if there wasn't a transaction, update the old ckchs */
2376 if (!ckchs_transaction.old_ckchs) {
Willy Tarreau329f4b42022-05-04 20:05:55 +02002377 ckchs_transaction.old_ckchs = ctx->old_ckchs;
2378 ckchs_transaction.path = ctx->path;
William Lallemandda8584c2020-05-14 10:14:37 +02002379 err = memprintf(&err, "Transaction created for certificate %s!\n", ckchs_transaction.path);
2380 } else {
2381 err = memprintf(&err, "Transaction updated for certificate %s!\n", ckchs_transaction.path);
2382
2383 }
2384
2385 /* free the previous ckchs if there was a transaction */
2386 ckch_store_free(ckchs_transaction.new_ckchs);
2387
Willy Tarreau329f4b42022-05-04 20:05:55 +02002388 ckchs_transaction.new_ckchs = ctx->new_ckchs;
William Lallemandda8584c2020-05-14 10:14:37 +02002389
2390
2391 /* creates the SNI ctxs later in the IO handler */
2392
2393end:
2394 free_trash_chunk(buf);
2395
2396 if (errcode & ERR_CODE) {
Willy Tarreau329f4b42022-05-04 20:05:55 +02002397 ckch_store_free(ctx->new_ckchs);
2398 ctx->new_ckchs = NULL;
2399 ctx->old_ckchs = NULL;
2400 ha_free(&ctx->path);
William Lallemandda8584c2020-05-14 10:14:37 +02002401
2402 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2403 return cli_dynerr(appctx, memprintf(&err, "%sCan't update %s!\n", err ? err : "", args[3]));
2404 } else {
William Lallemandda8584c2020-05-14 10:14:37 +02002405 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2406 return cli_dynmsg(appctx, LOG_NOTICE, err);
2407 }
2408 /* TODO: handle the ERR_WARN which are not handled because of the io_handler */
2409}
2410
2411/* parsing function of 'abort ssl cert' */
2412static int cli_parse_abort_cert(char **args, char *payload, struct appctx *appctx, void *private)
2413{
2414 char *err = NULL;
2415
2416 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2417 return 1;
2418
2419 if (!*args[3])
2420 return cli_err(appctx, "'abort ssl cert' expects a filename\n");
2421
2422 /* The operations on the CKCH architecture are locked so we can
2423 * manipulate ckch_store and ckch_inst */
2424 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2425 return cli_err(appctx, "Can't abort!\nOperations on certificates are currently locked!\n");
2426
2427 if (!ckchs_transaction.path) {
2428 memprintf(&err, "No ongoing transaction!\n");
2429 goto error;
2430 }
2431
2432 if (strcmp(ckchs_transaction.path, args[3]) != 0) {
2433 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to abort a transaction for '%s'\n", ckchs_transaction.path, args[3]);
2434 goto error;
2435 }
2436
2437 /* Only free the ckchs there, because the SNI and instances were not generated yet */
2438 ckch_store_free(ckchs_transaction.new_ckchs);
2439 ckchs_transaction.new_ckchs = NULL;
William Lallemandda8584c2020-05-14 10:14:37 +02002440 ckchs_transaction.old_ckchs = NULL;
Willy Tarreau61cfdf42021-02-20 10:46:51 +01002441 ha_free(&ckchs_transaction.path);
William Lallemandda8584c2020-05-14 10:14:37 +02002442
2443 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2444
2445 err = memprintf(&err, "Transaction aborted for certificate '%s'!\n", args[3]);
2446 return cli_dynmsg(appctx, LOG_NOTICE, err);
2447
2448error:
2449 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2450
2451 return cli_dynerr(appctx, err);
2452}
2453
2454/* parsing function of 'new ssl cert' */
2455static int cli_parse_new_cert(char **args, char *payload, struct appctx *appctx, void *private)
2456{
2457 struct ckch_store *store;
2458 char *err = NULL;
2459 char *path;
2460
2461 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2462 return 1;
2463
2464 if (!*args[3])
2465 return cli_err(appctx, "'new ssl cert' expects a filename\n");
2466
2467 path = args[3];
2468
2469 /* The operations on the CKCH architecture are locked so we can
2470 * manipulate ckch_store and ckch_inst */
2471 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2472 return cli_err(appctx, "Can't create a certificate!\nOperations on certificates are currently locked!\n");
2473
2474 store = ckchs_lookup(path);
2475 if (store != NULL) {
2476 memprintf(&err, "Certificate '%s' already exists!\n", path);
2477 store = NULL; /* we don't want to free it */
2478 goto error;
2479 }
2480 /* we won't support multi-certificate bundle here */
William Lallemandbd8e6ed2020-09-16 16:08:08 +02002481 store = ckch_store_new(path);
William Lallemandda8584c2020-05-14 10:14:37 +02002482 if (!store) {
2483 memprintf(&err, "unable to allocate memory.\n");
2484 goto error;
2485 }
2486
2487 /* insert into the ckchs tree */
2488 ebst_insert(&ckchs_tree, &store->node);
2489 memprintf(&err, "New empty certificate store '%s'!\n", args[3]);
2490
2491 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2492 return cli_dynmsg(appctx, LOG_NOTICE, err);
2493error:
2494 free(store);
2495 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2496 return cli_dynerr(appctx, err);
2497}
2498
2499/* parsing function of 'del ssl cert' */
2500static int cli_parse_del_cert(char **args, char *payload, struct appctx *appctx, void *private)
2501{
2502 struct ckch_store *store;
2503 char *err = NULL;
2504 char *filename;
2505
2506 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2507 return 1;
2508
2509 if (!*args[3])
2510 return cli_err(appctx, "'del ssl cert' expects a certificate name\n");
2511
2512 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2513 return cli_err(appctx, "Can't delete the certificate!\nOperations on certificates are currently locked!\n");
2514
2515 filename = args[3];
2516
2517 store = ckchs_lookup(filename);
2518 if (store == NULL) {
2519 memprintf(&err, "certificate '%s' doesn't exist!\n", filename);
2520 goto error;
2521 }
2522 if (!LIST_ISEMPTY(&store->ckch_inst)) {
2523 memprintf(&err, "certificate '%s' in use, can't be deleted!\n", filename);
2524 goto error;
2525 }
2526
2527 ebmb_delete(&store->node);
2528 ckch_store_free(store);
2529
2530 memprintf(&err, "Certificate '%s' deleted!\n", filename);
2531
2532 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2533 return cli_dynmsg(appctx, LOG_NOTICE, err);
2534
2535error:
2536 memprintf(&err, "Can't remove the certificate: %s\n", err ? err : "");
2537 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2538 return cli_dynerr(appctx, err);
2539}
2540
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002541
Remi Tricot-Le Breton9f40fe02021-03-16 16:21:27 +01002542
2543/* parsing function of 'new ssl ca-file' */
2544static int cli_parse_new_cafile(char **args, char *payload, struct appctx *appctx, void *private)
2545{
2546 struct cafile_entry *cafile_entry;
2547 char *err = NULL;
2548 char *path;
2549
2550 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2551 return 1;
2552
2553 if (!*args[3])
2554 return cli_err(appctx, "'new ssl ca-file' expects a filename\n");
2555
2556 path = args[3];
2557
2558 /* The operations on the CKCH architecture are locked so we can
2559 * manipulate ckch_store and ckch_inst */
2560 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2561 return cli_err(appctx, "Can't create a CA file!\nOperations on certificates are currently locked!\n");
2562
2563 cafile_entry = ssl_store_get_cafile_entry(path, 0);
2564 if (cafile_entry) {
2565 memprintf(&err, "CA file '%s' already exists!\n", path);
2566 goto error;
2567 }
2568
2569 cafile_entry = ssl_store_create_cafile_entry(path, NULL, CAFILE_CERT);
2570 if (!cafile_entry) {
2571 memprintf(&err, "%sCannot allocate memory!\n",
2572 err ? err : "");
2573 goto error;
2574 }
2575
2576 /* Add the newly created cafile_entry to the tree so that
2577 * any new ckch instance created from now can use it. */
2578 if (ssl_store_add_uncommitted_cafile_entry(cafile_entry))
2579 goto error;
2580
2581 memprintf(&err, "New CA file created '%s'!\n", path);
2582
2583 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2584 return cli_dynmsg(appctx, LOG_NOTICE, err);
2585error:
2586 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2587 return cli_dynerr(appctx, err);
2588}
2589
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002590/*
2591 * Parsing function of `set ssl ca-file`
2592 */
2593static int cli_parse_set_cafile(char **args, char *payload, struct appctx *appctx, void *private)
2594{
Willy Tarreaua37693f2022-05-04 20:12:55 +02002595 struct set_cafile_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002596 char *err = NULL;
2597 int errcode = 0;
2598 struct buffer *buf;
2599
2600 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2601 return 1;
2602
2603 if (!*args[3] || !payload)
2604 return cli_err(appctx, "'set ssl ca-file expects a filename and CAs as a payload\n");
2605
2606 /* The operations on the CKCH architecture are locked so we can
2607 * manipulate ckch_store and ckch_inst */
2608 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2609 return cli_err(appctx, "Can't update the CA file!\nOperations on certificates are currently locked!\n");
2610
2611 if ((buf = alloc_trash_chunk()) == NULL) {
2612 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2613 errcode |= ERR_ALERT | ERR_FATAL;
2614 goto end;
2615 }
2616
2617 if (!chunk_strcpy(buf, args[3])) {
2618 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2619 errcode |= ERR_ALERT | ERR_FATAL;
2620 goto end;
2621 }
2622
Willy Tarreaua37693f2022-05-04 20:12:55 +02002623 ctx->old_cafile_entry = NULL;
2624 ctx->new_cafile_entry = NULL;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002625
2626 /* if there is an ongoing transaction */
2627 if (cafile_transaction.path) {
2628 /* if there is an ongoing transaction, check if this is the same file */
2629 if (strcmp(cafile_transaction.path, buf->area) != 0) {
2630 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", cafile_transaction.path, buf->area);
2631 errcode |= ERR_ALERT | ERR_FATAL;
2632 goto end;
2633 }
Willy Tarreaua37693f2022-05-04 20:12:55 +02002634 ctx->old_cafile_entry = cafile_transaction.old_cafile_entry;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002635 }
2636 else {
2637 /* lookup for the certificate in the tree */
Willy Tarreaua37693f2022-05-04 20:12:55 +02002638 ctx->old_cafile_entry = ssl_store_get_cafile_entry(buf->area, 0);
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002639 }
2640
Willy Tarreaua37693f2022-05-04 20:12:55 +02002641 if (!ctx->old_cafile_entry) {
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002642 memprintf(&err, "%sCan't replace a CA file which is not referenced by the configuration!\n",
2643 err ? err : "");
2644 errcode |= ERR_ALERT | ERR_FATAL;
2645 goto end;
2646 }
2647
Willy Tarreaua37693f2022-05-04 20:12:55 +02002648 if (!ctx->path) {
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002649 /* this is a new transaction, set the path of the transaction */
Willy Tarreaua37693f2022-05-04 20:12:55 +02002650 ctx->path = strdup(ctx->old_cafile_entry->path);
2651 if (!ctx->path) {
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002652 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2653 errcode |= ERR_ALERT | ERR_FATAL;
2654 goto end;
2655 }
2656 }
2657
Willy Tarreaua37693f2022-05-04 20:12:55 +02002658 if (ctx->new_cafile_entry)
2659 ssl_store_delete_cafile_entry(ctx->new_cafile_entry);
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002660
2661 /* Create a new cafile_entry without adding it to the cafile tree. */
Willy Tarreaua37693f2022-05-04 20:12:55 +02002662 ctx->new_cafile_entry = ssl_store_create_cafile_entry(ctx->path, NULL, CAFILE_CERT);
2663 if (!ctx->new_cafile_entry) {
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002664 memprintf(&err, "%sCannot allocate memory!\n",
2665 err ? err : "");
2666 errcode |= ERR_ALERT | ERR_FATAL;
2667 goto end;
2668 }
2669
2670 /* Fill the new entry with the new CAs. */
Willy Tarreaua37693f2022-05-04 20:12:55 +02002671 if (ssl_store_load_ca_from_buf(ctx->new_cafile_entry, payload)) {
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002672 memprintf(&err, "%sInvalid payload\n", err ? err : "");
2673 errcode |= ERR_ALERT | ERR_FATAL;
2674 goto end;
2675 }
2676
2677 /* we succeed, we can save the ca in the transaction */
2678
2679 /* if there wasn't a transaction, update the old CA */
2680 if (!cafile_transaction.old_cafile_entry) {
Willy Tarreaua37693f2022-05-04 20:12:55 +02002681 cafile_transaction.old_cafile_entry = ctx->old_cafile_entry;
2682 cafile_transaction.path = ctx->path;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002683 err = memprintf(&err, "transaction created for CA %s!\n", cafile_transaction.path);
2684 } else {
2685 err = memprintf(&err, "transaction updated for CA %s!\n", cafile_transaction.path);
2686 }
2687
2688 /* free the previous CA if there was a transaction */
2689 ssl_store_delete_cafile_entry(cafile_transaction.new_cafile_entry);
2690
Willy Tarreaua37693f2022-05-04 20:12:55 +02002691 cafile_transaction.new_cafile_entry = ctx->new_cafile_entry;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002692
2693 /* creates the SNI ctxs later in the IO handler */
2694
2695end:
2696 free_trash_chunk(buf);
2697
2698 if (errcode & ERR_CODE) {
Willy Tarreaua37693f2022-05-04 20:12:55 +02002699 ssl_store_delete_cafile_entry(ctx->new_cafile_entry);
2700 ctx->new_cafile_entry = NULL;
2701 ctx->old_cafile_entry = NULL;
2702 ha_free(&ctx->path);
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002703 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2704 return cli_dynerr(appctx, memprintf(&err, "%sCan't update %s!\n", err ? err : "", args[3]));
2705 } else {
2706
2707 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2708 return cli_dynmsg(appctx, LOG_NOTICE, err);
2709 }
2710}
2711
2712
2713/*
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002714 * Parsing function of 'commit ssl ca-file'.
2715 * It uses a commit_cacrlfile_ctx that's also shared with "commit ssl crl-file".
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002716 */
2717static int cli_parse_commit_cafile(char **args, char *payload, struct appctx *appctx, void *private)
2718{
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002719 struct commit_cacrlfile_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002720 char *err = NULL;
2721
2722 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2723 return 1;
2724
2725 if (!*args[3])
2726 return cli_err(appctx, "'commit ssl ca-file expects a filename\n");
2727
2728 /* The operations on the CKCH architecture are locked so we can
2729 * manipulate ckch_store and ckch_inst */
2730 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2731 return cli_err(appctx, "Can't commit the CA file!\nOperations on certificates are currently locked!\n");
2732
2733 if (!cafile_transaction.path) {
2734 memprintf(&err, "No ongoing transaction! !\n");
2735 goto error;
2736 }
2737
2738 if (strcmp(cafile_transaction.path, args[3]) != 0) {
2739 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", cafile_transaction.path, args[3]);
2740 goto error;
2741 }
2742 /* init the appctx structure */
Willy Tarreau1d6dd802022-05-05 08:17:29 +02002743 ctx->state = CACRL_ST_INIT;
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002744 ctx->next_ckchi_link = NULL;
2745 ctx->old_cafile_entry = cafile_transaction.old_cafile_entry;
2746 ctx->new_cafile_entry = cafile_transaction.new_cafile_entry;
2747 ctx->cafile_type = CAFILE_CERT;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002748
2749 return 0;
2750
2751error:
2752
2753 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2754 err = memprintf(&err, "%sCan't commit %s!\n", err ? err : "", args[3]);
2755
2756 return cli_dynerr(appctx, err);
2757}
2758
2759enum {
2760 CREATE_NEW_INST_OK = 0,
2761 CREATE_NEW_INST_YIELD = -1,
2762 CREATE_NEW_INST_ERR = -2
2763};
2764
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002765/* this is called by the I/O handler for "commit cafile"/"commit crlfile", and
2766 * it uses a context from cmomit_cacrlfile_ctx.
2767 */
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002768static inline int __create_new_instance(struct appctx *appctx, struct ckch_inst *ckchi, int *count,
Remi Tricot-Le Bretond75b99e2021-05-17 11:45:55 +02002769 struct buffer *trash, char **err)
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002770{
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002771 struct commit_cacrlfile_ctx *ctx = appctx->svcctx;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002772 struct ckch_inst *new_inst;
2773
2774 /* it takes a lot of CPU to creates SSL_CTXs, so we yield every 10 CKCH instances */
2775 if (*count >= 10) {
2776 /* save the next ckchi to compute */
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002777 ctx->next_ckchi = ckchi;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002778 return CREATE_NEW_INST_YIELD;
2779 }
2780
2781 /* Rebuild a new ckch instance that uses the same ckch_store
2782 * than a reference ckchi instance but will use a new CA file. */
Remi Tricot-Le Bretond75b99e2021-05-17 11:45:55 +02002783 if (ckch_inst_rebuild(ckchi->ckch_store, ckchi, &new_inst, err))
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002784 return CREATE_NEW_INST_ERR;
2785
2786 /* display one dot per new instance */
2787 chunk_appendf(trash, ".");
2788 ++(*count);
2789
2790 return CREATE_NEW_INST_OK;
2791}
2792
2793/*
2794 * This function tries to create new ckch instances and their SNIs using a newly
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002795 * set certificate authority (CA file) or a newly set Certificate Revocation
2796 * List (CRL), depending on the command being called.
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002797 */
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002798static int cli_io_handler_commit_cafile_crlfile(struct appctx *appctx)
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002799{
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002800 struct commit_cacrlfile_ctx *ctx = appctx->svcctx;
Willy Tarreau4596fe22022-05-17 19:07:51 +02002801 struct stconn *cs = appctx_cs(appctx);
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002802 int y = 0;
2803 char *err = NULL;
Remi Tricot-Le Bretona6b27842021-05-18 10:06:00 +02002804 struct cafile_entry *old_cafile_entry = NULL, *new_cafile_entry = NULL;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002805 struct ckch_inst_link *ckchi_link;
2806 struct buffer *trash = alloc_trash_chunk();
2807
2808 if (trash == NULL)
2809 goto error;
2810
Christopher Faulet908628c2022-03-25 16:43:49 +01002811 if (unlikely(cs_ic(cs)->flags & (CF_WRITE_ERROR|CF_SHUTW)))
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002812 goto error;
2813
2814 while (1) {
Willy Tarreau1d6dd802022-05-05 08:17:29 +02002815 switch (ctx->state) {
2816 case CACRL_ST_INIT:
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002817 /* This state just print the update message */
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002818 switch (ctx->cafile_type) {
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002819 case CAFILE_CERT:
2820 chunk_printf(trash, "Committing %s", cafile_transaction.path);
2821 break;
2822 case CAFILE_CRL:
2823 chunk_printf(trash, "Committing %s", crlfile_transaction.path);
2824 break;
2825 default:
2826 goto error;
2827 }
Christopher Faulet908628c2022-03-25 16:43:49 +01002828 if (ci_putchk(cs_ic(cs), trash) == -1) {
Christopher Fauleta0bdec32022-04-04 07:51:21 +02002829 cs_rx_room_blk(cs);
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002830 goto yield;
2831 }
Willy Tarreau1d6dd802022-05-05 08:17:29 +02002832 ctx->state = CACRL_ST_GEN;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002833 /* fallthrough */
Willy Tarreau1d6dd802022-05-05 08:17:29 +02002834 case CACRL_ST_GEN:
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002835 /*
2836 * This state generates the ckch instances with their
2837 * sni_ctxs and SSL_CTX.
2838 *
2839 * Since the SSL_CTX generation can be CPU consumer, we
2840 * yield every 10 instances.
2841 */
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002842 switch (ctx->cafile_type) {
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002843 case CAFILE_CERT:
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002844 old_cafile_entry = ctx->old_cafile_entry;
2845 new_cafile_entry = ctx->new_cafile_entry;
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002846 break;
2847 case CAFILE_CRL:
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002848 old_cafile_entry = ctx->old_crlfile_entry;
2849 new_cafile_entry = ctx->new_crlfile_entry;
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002850 break;
2851 }
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002852 if (!new_cafile_entry)
2853 continue;
2854
2855 /* get the next ckchi to regenerate */
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002856 ckchi_link = ctx->next_ckchi_link;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002857 /* we didn't start yet, set it to the first elem */
2858 if (ckchi_link == NULL) {
2859 ckchi_link = LIST_ELEM(old_cafile_entry->ckch_inst_link.n, typeof(ckchi_link), list);
2860 /* Add the newly created cafile_entry to the tree so that
2861 * any new ckch instance created from now can use it. */
2862 if (ssl_store_add_uncommitted_cafile_entry(new_cafile_entry))
2863 goto error;
2864 }
2865
2866 list_for_each_entry_from(ckchi_link, &old_cafile_entry->ckch_inst_link, list) {
Remi Tricot-Le Bretond75b99e2021-05-17 11:45:55 +02002867 switch (__create_new_instance(appctx, ckchi_link->ckch_inst, &y, trash, &err)) {
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002868 case CREATE_NEW_INST_YIELD:
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002869 ctx->next_ckchi_link = ckchi_link;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002870 goto yield;
2871 case CREATE_NEW_INST_ERR:
2872 goto error;
2873 default: break;
2874 }
2875 }
2876
Willy Tarreau1d6dd802022-05-05 08:17:29 +02002877 ctx->state = CACRL_ST_INSERT;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002878 /* fallthrough */
Willy Tarreau1d6dd802022-05-05 08:17:29 +02002879 case CACRL_ST_INSERT:
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002880 /* The generation is finished, we can insert everything */
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002881 switch (ctx->cafile_type) {
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002882 case CAFILE_CERT:
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002883 old_cafile_entry = ctx->old_cafile_entry;
2884 new_cafile_entry = ctx->new_cafile_entry;
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002885 break;
2886 case CAFILE_CRL:
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002887 old_cafile_entry = ctx->old_crlfile_entry;
2888 new_cafile_entry = ctx->new_crlfile_entry;
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002889 break;
2890 }
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002891 if (!new_cafile_entry)
2892 continue;
2893
2894 /* insert the new ckch_insts in the crtlist_entry */
2895 list_for_each_entry(ckchi_link, &new_cafile_entry->ckch_inst_link, list) {
2896 if (ckchi_link->ckch_inst->crtlist_entry)
2897 LIST_INSERT(&ckchi_link->ckch_inst->crtlist_entry->ckch_inst,
2898 &ckchi_link->ckch_inst->by_crtlist_entry);
2899 }
2900
2901 /* First, we insert every new SNIs in the trees, also replace the default_ctx */
2902 list_for_each_entry(ckchi_link, &new_cafile_entry->ckch_inst_link, list) {
2903 __ssl_sock_load_new_ckch_instance(ckchi_link->ckch_inst);
2904 }
2905
2906 /* delete the old sni_ctx, the old ckch_insts and the ckch_store */
2907 list_for_each_entry(ckchi_link, &old_cafile_entry->ckch_inst_link, list) {
2908 __ckch_inst_free_locked(ckchi_link->ckch_inst);
2909 }
2910
2911
2912 /* Remove the old cafile entry from the tree */
2913 ebmb_delete(&old_cafile_entry->node);
2914 ssl_store_delete_cafile_entry(old_cafile_entry);
2915
Willy Tarreau1d6dd802022-05-05 08:17:29 +02002916 ctx->state = CACRL_ST_FIN;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002917 /* fallthrough */
Willy Tarreau1d6dd802022-05-05 08:17:29 +02002918 case CACRL_ST_FIN:
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002919 /* we achieved the transaction, we can set everything to NULL */
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002920 switch (ctx->cafile_type) {
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002921 case CAFILE_CERT:
2922 ha_free(&cafile_transaction.path);
2923 cafile_transaction.old_cafile_entry = NULL;
2924 cafile_transaction.new_cafile_entry = NULL;
2925 break;
2926 case CAFILE_CRL:
2927 ha_free(&crlfile_transaction.path);
2928 crlfile_transaction.old_crlfile_entry = NULL;
2929 crlfile_transaction.new_crlfile_entry = NULL;
2930 break;
2931 }
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002932 goto end;
2933 }
2934 }
2935end:
2936
2937 chunk_appendf(trash, "\n");
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002938 chunk_appendf(trash, "Success!\n");
Christopher Faulet908628c2022-03-25 16:43:49 +01002939 if (ci_putchk(cs_ic(cs), trash) == -1)
Christopher Fauleta0bdec32022-04-04 07:51:21 +02002940 cs_rx_room_blk(cs);
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002941 free_trash_chunk(trash);
2942 /* success: call the release function and don't come back */
2943 return 1;
2944yield:
2945 /* store the state */
Christopher Faulet908628c2022-03-25 16:43:49 +01002946 if (ci_putchk(cs_ic(cs), trash) == -1)
Christopher Fauleta0bdec32022-04-04 07:51:21 +02002947 cs_rx_room_blk(cs);
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002948 free_trash_chunk(trash);
Christopher Fauleta0bdec32022-04-04 07:51:21 +02002949 cs_rx_endp_more(cs); /* let's come back later */
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002950 return 0; /* should come back */
2951
2952error:
2953 /* spin unlock and free are done in the release function */
2954 if (trash) {
2955 chunk_appendf(trash, "\n%sFailed!\n", err);
Christopher Faulet908628c2022-03-25 16:43:49 +01002956 if (ci_putchk(cs_ic(cs), trash) == -1)
Christopher Fauleta0bdec32022-04-04 07:51:21 +02002957 cs_rx_room_blk(cs);
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002958 free_trash_chunk(trash);
2959 }
2960 /* error: call the release function and don't come back */
2961 return 1;
2962}
2963
Remi Tricot-Le Bretond5fd09d2021-03-11 10:22:52 +01002964
2965/* parsing function of 'abort ssl ca-file' */
2966static int cli_parse_abort_cafile(char **args, char *payload, struct appctx *appctx, void *private)
2967{
2968 char *err = NULL;
2969
2970 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2971 return 1;
2972
2973 if (!*args[3])
2974 return cli_err(appctx, "'abort ssl ca-file' expects a filename\n");
2975
2976 /* The operations on the CKCH architecture are locked so we can
2977 * manipulate ckch_store and ckch_inst */
2978 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2979 return cli_err(appctx, "Can't abort!\nOperations on certificates are currently locked!\n");
2980
2981 if (!cafile_transaction.path) {
2982 memprintf(&err, "No ongoing transaction!\n");
2983 goto error;
2984 }
2985
2986 if (strcmp(cafile_transaction.path, args[3]) != 0) {
2987 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to abort a transaction for '%s'\n", cafile_transaction.path, args[3]);
2988 goto error;
2989 }
2990
2991 /* Only free the uncommitted cafile_entry here, because the SNI and instances were not generated yet */
2992 ssl_store_delete_cafile_entry(cafile_transaction.new_cafile_entry);
2993 cafile_transaction.new_cafile_entry = NULL;
2994 cafile_transaction.old_cafile_entry = NULL;
2995 ha_free(&cafile_transaction.path);
2996
2997 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2998
2999 err = memprintf(&err, "Transaction aborted for certificate '%s'!\n", args[3]);
3000 return cli_dynmsg(appctx, LOG_NOTICE, err);
3001
3002error:
3003 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3004
3005 return cli_dynerr(appctx, err);
3006}
3007
Willy Tarreau821c3b02022-05-04 15:47:39 +02003008/* release function of the `commit ssl ca-file' command, free things and unlock the spinlock.
Willy Tarreaudec23dc2022-05-04 20:25:05 +02003009 * It uses a commit_cacrlfile_ctx context.
Willy Tarreau821c3b02022-05-04 15:47:39 +02003010 */
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01003011static void cli_release_commit_cafile(struct appctx *appctx)
3012{
Willy Tarreaudec23dc2022-05-04 20:25:05 +02003013 struct commit_cacrlfile_ctx *ctx = appctx->svcctx;
3014
Willy Tarreau1d6dd802022-05-05 08:17:29 +02003015 if (ctx->state != CACRL_ST_FIN) {
Willy Tarreaudec23dc2022-05-04 20:25:05 +02003016 struct cafile_entry *new_cafile_entry = ctx->new_cafile_entry;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01003017
3018 /* Remove the uncommitted cafile_entry from the tree. */
3019 ebmb_delete(&new_cafile_entry->node);
3020 ssl_store_delete_cafile_entry(new_cafile_entry);
3021 }
3022 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3023}
3024
3025
Willy Tarreau821c3b02022-05-04 15:47:39 +02003026/* IO handler of details "show ssl ca-file <filename[:index]>".
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003027 * It uses a show_cafile_ctx context, and the global
3028 * cafile_transaction.new_cafile_entry in read-only.
Willy Tarreau821c3b02022-05-04 15:47:39 +02003029 */
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003030static int cli_io_handler_show_cafile_detail(struct appctx *appctx)
3031{
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003032 struct show_cafile_ctx *ctx = appctx->svcctx;
Willy Tarreau4596fe22022-05-17 19:07:51 +02003033 struct stconn *cs = appctx_cs(appctx);
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003034 struct cafile_entry *cafile_entry = ctx->cur_cafile_entry;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003035 struct buffer *out = alloc_trash_chunk();
William Lallemand03a32e52022-04-26 18:17:15 +02003036 int i = 0;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003037 X509 *cert;
3038 STACK_OF(X509_OBJECT) *objs;
3039 int retval = 0;
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003040 int ca_index = ctx->ca_index;
3041 int show_all = ctx->show_all;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003042
3043 if (!out)
3044 goto end_no_putchk;
3045
3046 chunk_appendf(out, "Filename: ");
3047 if (cafile_entry == cafile_transaction.new_cafile_entry)
3048 chunk_appendf(out, "*");
3049 chunk_appendf(out, "%s\n", cafile_entry->path);
3050
3051 chunk_appendf(out, "Status: ");
3052 if (!cafile_entry->ca_store)
3053 chunk_appendf(out, "Empty\n");
3054 else if (LIST_ISEMPTY(&cafile_entry->ckch_inst_link))
3055 chunk_appendf(out, "Unused\n");
3056 else
3057 chunk_appendf(out, "Used\n");
3058
3059 if (!cafile_entry->ca_store)
3060 goto end;
3061
3062 objs = X509_STORE_get0_objects(cafile_entry->ca_store);
William Lallemand03a32e52022-04-26 18:17:15 +02003063 for (i = ca_index; i < sk_X509_OBJECT_num(objs); i++) {
3064
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003065 cert = X509_OBJECT_get0_X509(sk_X509_OBJECT_value(objs, i));
3066 if (!cert)
3067 continue;
3068
William Lallemand03a32e52022-04-26 18:17:15 +02003069 /* file starts at line 1 */
Remi Tricot-Le Bretone8041fe2022-04-05 16:44:21 +02003070 chunk_appendf(out, " \nCertificate #%d:\n", i+1);
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003071 retval = show_cert_detail(cert, NULL, out);
3072 if (retval < 0)
3073 goto end_no_putchk;
William Lallemand03a32e52022-04-26 18:17:15 +02003074 else if (retval)
3075 goto yield;
3076
3077 if (ci_putchk(cs_ic(cs), out) == -1) {
3078 cs_rx_room_blk(cs);
3079 goto yield;
3080 }
3081
3082 if (!show_all) /* only need to dump one certificate */
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003083 goto end;
3084 }
3085
3086end:
William Lallemand03a32e52022-04-26 18:17:15 +02003087 free_trash_chunk(out);
3088 return 1; /* end, don't come back */
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003089
3090end_no_putchk:
3091 free_trash_chunk(out);
3092 return 1;
3093yield:
William Lallemand03a32e52022-04-26 18:17:15 +02003094 /* save the current state */
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003095 ctx->ca_index = i;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003096 free_trash_chunk(out);
3097 return 0; /* should come back */
3098}
3099
3100
Willy Tarreau06305792022-05-04 15:57:30 +02003101/* parsing function for 'show ssl ca-file [cafile[:index]]'.
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003102 * It prepares a show_cafile_ctx context, and checks the global
3103 * cafile_transaction under the ckch_lock (read only).
Willy Tarreau06305792022-05-04 15:57:30 +02003104 */
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003105static int cli_parse_show_cafile(char **args, char *payload, struct appctx *appctx, void *private)
3106{
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003107 struct show_cafile_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003108 struct cafile_entry *cafile_entry;
William Lallemand03a32e52022-04-26 18:17:15 +02003109 int ca_index = 0;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003110 char *colons;
3111 char *err = NULL;
3112
3113 if (!cli_has_level(appctx, ACCESS_LVL_OPER))
3114 return cli_err(appctx, "Can't allocate memory!\n");
3115
3116 /* The operations on the CKCH architecture are locked so we can
3117 * manipulate ckch_store and ckch_inst */
3118 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3119 return cli_err(appctx, "Can't show!\nOperations on certificates are currently locked!\n");
3120
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003121 ctx->show_all = 1; /* show all certificates */
3122 ctx->ca_index = 0;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003123 /* check if there is a certificate to lookup */
3124 if (*args[3]) {
3125
3126 /* Look for an optional CA index after the CA file name */
3127 colons = strchr(args[3], ':');
3128 if (colons) {
3129 char *endptr;
3130
3131 ca_index = strtol(colons + 1, &endptr, 10);
3132 /* Indexes start at 1 */
3133 if (colons + 1 == endptr || *endptr != '\0' || ca_index <= 0) {
3134 memprintf(&err, "wrong CA index after colons in '%s'!", args[3]);
3135 goto error;
3136 }
3137 *colons = '\0';
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003138 ctx->ca_index = ca_index - 1; /* we start counting at 0 in the ca_store, but at 1 on the CLI */
3139 ctx->show_all = 0; /* show only one certificate */
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003140 }
3141
3142 if (*args[3] == '*') {
3143 if (!cafile_transaction.new_cafile_entry)
3144 goto error;
3145
3146 cafile_entry = cafile_transaction.new_cafile_entry;
3147
3148 if (strcmp(args[3] + 1, cafile_entry->path) != 0)
3149 goto error;
3150
3151 } else {
3152 /* Get the "original" cafile_entry and not the
3153 * uncommitted one if it exists. */
3154 if ((cafile_entry = ssl_store_get_cafile_entry(args[3], 1)) == NULL || cafile_entry->type != CAFILE_CERT)
3155 goto error;
3156 }
3157
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003158 ctx->cur_cafile_entry = cafile_entry;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003159 /* use the IO handler that shows details */
3160 appctx->io_handler = cli_io_handler_show_cafile_detail;
3161 }
3162
3163 return 0;
3164
3165error:
3166 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3167 if (err)
3168 return cli_dynerr(appctx, err);
3169 return cli_err(appctx, "Can't display the CA file : Not found!\n");
3170}
3171
3172
3173/* release function of the 'show ssl ca-file' command */
3174static void cli_release_show_cafile(struct appctx *appctx)
3175{
3176 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3177}
3178
3179
3180/* This function returns the number of certificates in a cafile_entry. */
3181static int get_certificate_count(struct cafile_entry *cafile_entry)
3182{
3183 int cert_count = 0;
3184 STACK_OF(X509_OBJECT) *objs;
3185
3186 if (cafile_entry && cafile_entry->ca_store) {
3187 objs = X509_STORE_get0_objects(cafile_entry->ca_store);
3188 if (objs)
3189 cert_count = sk_X509_OBJECT_num(objs);
3190 }
3191 return cert_count;
3192}
3193
3194/* IO handler of "show ssl ca-file". The command taking a specific CA file name
Willy Tarreau821c3b02022-05-04 15:47:39 +02003195 * is managed in cli_io_handler_show_cafile_detail.
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003196 * It uses a show_cafile_ctx and the global cafile_transaction.new_cafile_entry
3197 * in read-only.
Willy Tarreau821c3b02022-05-04 15:47:39 +02003198 */
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003199static int cli_io_handler_show_cafile(struct appctx *appctx)
3200{
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003201 struct show_cafile_ctx *ctx = appctx->svcctx;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003202 struct buffer *trash = alloc_trash_chunk();
3203 struct ebmb_node *node;
Willy Tarreau4596fe22022-05-17 19:07:51 +02003204 struct stconn *cs = appctx_cs(appctx);
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003205 struct cafile_entry *cafile_entry;
3206
3207 if (trash == NULL)
3208 return 1;
3209
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003210 if (!ctx->old_cafile_entry) {
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003211 if (cafile_transaction.old_cafile_entry) {
3212 chunk_appendf(trash, "# transaction\n");
3213 chunk_appendf(trash, "*%s", cafile_transaction.old_cafile_entry->path);
3214
3215 chunk_appendf(trash, " - %d certificate(s)\n", get_certificate_count(cafile_transaction.new_cafile_entry));
3216 }
3217 }
3218
3219 /* First time in this io_handler. */
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003220 if (!ctx->cur_cafile_entry) {
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003221 chunk_appendf(trash, "# filename\n");
3222 node = ebmb_first(&cafile_tree);
3223 } else {
3224 /* We yielded during a previous call. */
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003225 node = &ctx->cur_cafile_entry->node;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003226 }
3227
3228 while (node) {
3229 cafile_entry = ebmb_entry(node, struct cafile_entry, node);
3230 if (cafile_entry->type == CAFILE_CERT) {
3231 chunk_appendf(trash, "%s", cafile_entry->path);
3232
3233 chunk_appendf(trash, " - %d certificate(s)\n", get_certificate_count(cafile_entry));
3234 }
3235
3236 node = ebmb_next(node);
Christopher Faulet908628c2022-03-25 16:43:49 +01003237 if (ci_putchk(cs_ic(cs), trash) == -1) {
Christopher Fauleta0bdec32022-04-04 07:51:21 +02003238 cs_rx_room_blk(cs);
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003239 goto yield;
3240 }
3241 }
3242
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003243 ctx->cur_cafile_entry = NULL;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003244 free_trash_chunk(trash);
3245 return 1;
3246yield:
3247
3248 free_trash_chunk(trash);
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003249 ctx->cur_cafile_entry = cafile_entry;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003250 return 0; /* should come back */
3251}
3252
Remi Tricot-Le Bretonc3a84772021-03-25 18:13:57 +01003253/* parsing function of 'del ssl ca-file' */
3254static int cli_parse_del_cafile(char **args, char *payload, struct appctx *appctx, void *private)
3255{
3256 struct cafile_entry *cafile_entry;
3257 char *err = NULL;
3258 char *filename;
3259
3260 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
3261 return 1;
3262
3263 if (!*args[3])
3264 return cli_err(appctx, "'del ssl ca-file' expects a CA file name\n");
3265
3266 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3267 return cli_err(appctx, "Can't delete the CA file!\nOperations on certificates are currently locked!\n");
3268
3269 filename = args[3];
3270
3271 cafile_entry = ssl_store_get_cafile_entry(filename, 0);
3272 if (!cafile_entry) {
3273 memprintf(&err, "CA file '%s' doesn't exist!\n", filename);
3274 goto error;
3275 }
3276
3277 if (!LIST_ISEMPTY(&cafile_entry->ckch_inst_link)) {
3278 memprintf(&err, "CA file '%s' in use, can't be deleted!\n", filename);
3279 goto error;
3280 }
3281
3282 /* Remove the cafile_entry from the tree */
3283 ebmb_delete(&cafile_entry->node);
3284 ssl_store_delete_cafile_entry(cafile_entry);
3285
3286 memprintf(&err, "CA file '%s' deleted!\n", filename);
3287
3288 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3289 return cli_dynmsg(appctx, LOG_NOTICE, err);
3290
3291error:
3292 memprintf(&err, "Can't remove the CA file: %s\n", err ? err : "");
3293 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3294 return cli_dynerr(appctx, err);
3295}
3296
Remi Tricot-Le Breton720e3b92021-04-26 11:00:42 +02003297/* parsing function of 'new ssl crl-file' */
3298static int cli_parse_new_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
3299{
3300 struct cafile_entry *cafile_entry;
3301 char *err = NULL;
3302 char *path;
3303
3304 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
3305 return 1;
3306
3307 if (!*args[3])
3308 return cli_err(appctx, "'new ssl crl-file' expects a filename\n");
3309
3310 path = args[3];
3311
3312 /* The operations on the CKCH architecture are locked so we can
3313 * manipulate ckch_store and ckch_inst */
3314 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
Remi Tricot-Le Breton444d7022022-05-05 17:18:40 +02003315 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 +02003316
3317 cafile_entry = ssl_store_get_cafile_entry(path, 0);
3318 if (cafile_entry) {
3319 memprintf(&err, "CRL file '%s' already exists!\n", path);
3320 goto error;
3321 }
3322
3323 cafile_entry = ssl_store_create_cafile_entry(path, NULL, CAFILE_CRL);
3324 if (!cafile_entry) {
3325 memprintf(&err, "%sCannot allocate memory!\n", err ? err : "");
3326 goto error;
3327 }
3328
3329 /* Add the newly created cafile_entry to the tree so that
3330 * any new ckch instance created from now can use it. */
3331 if (ssl_store_add_uncommitted_cafile_entry(cafile_entry))
3332 goto error;
3333
3334 memprintf(&err, "New CRL file created '%s'!\n", path);
3335
3336 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3337 return cli_dynmsg(appctx, LOG_NOTICE, err);
3338error:
3339 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3340 return cli_dynerr(appctx, err);
3341}
3342
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003343/* Parsing function of `set ssl crl-file` */
3344static int cli_parse_set_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
3345{
Willy Tarreaua06b9a52022-05-04 20:33:03 +02003346 struct set_crlfile_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003347 char *err = NULL;
3348 int errcode = 0;
3349 struct buffer *buf;
3350
3351 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
3352 return 1;
3353
3354 if (!*args[3] || !payload)
Remi Tricot-Le Breton444d7022022-05-05 17:18:40 +02003355 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 +02003356
3357 /* The operations on the CKCH architecture are locked so we can
3358 * manipulate ckch_store and ckch_inst */
3359 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3360 return cli_err(appctx, "Can't update the CRL file!\nOperations on certificates are currently locked!\n");
3361
3362 if ((buf = alloc_trash_chunk()) == NULL) {
3363 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
3364 errcode |= ERR_ALERT | ERR_FATAL;
3365 goto end;
3366 }
3367
3368 if (!chunk_strcpy(buf, args[3])) {
3369 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
3370 errcode |= ERR_ALERT | ERR_FATAL;
3371 goto end;
3372 }
3373
Willy Tarreaua06b9a52022-05-04 20:33:03 +02003374 ctx->old_crlfile_entry = NULL;
3375 ctx->new_crlfile_entry = NULL;
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003376
3377 /* if there is an ongoing transaction */
3378 if (crlfile_transaction.path) {
3379 /* if there is an ongoing transaction, check if this is the same file */
3380 if (strcmp(crlfile_transaction.path, buf->area) != 0) {
3381 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", crlfile_transaction.path, buf->area);
3382 errcode |= ERR_ALERT | ERR_FATAL;
3383 goto end;
3384 }
Willy Tarreaua06b9a52022-05-04 20:33:03 +02003385 ctx->old_crlfile_entry = crlfile_transaction.old_crlfile_entry;
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003386 }
3387 else {
3388 /* lookup for the certificate in the tree */
Willy Tarreaua06b9a52022-05-04 20:33:03 +02003389 ctx->old_crlfile_entry = ssl_store_get_cafile_entry(buf->area, 0);
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003390 }
3391
Willy Tarreaua06b9a52022-05-04 20:33:03 +02003392 if (!ctx->old_crlfile_entry) {
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003393 memprintf(&err, "%sCan't replace a CRL file which is not referenced by the configuration!\n",
3394 err ? err : "");
3395 errcode |= ERR_ALERT | ERR_FATAL;
3396 goto end;
3397 }
3398
Willy Tarreaua06b9a52022-05-04 20:33:03 +02003399 if (!ctx->path) {
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003400 /* this is a new transaction, set the path of the transaction */
Willy Tarreaua06b9a52022-05-04 20:33:03 +02003401 ctx->path = strdup(ctx->old_crlfile_entry->path);
3402 if (!ctx->path) {
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003403 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
3404 errcode |= ERR_ALERT | ERR_FATAL;
3405 goto end;
3406 }
3407 }
3408
Willy Tarreaua06b9a52022-05-04 20:33:03 +02003409 if (ctx->new_crlfile_entry)
3410 ssl_store_delete_cafile_entry(ctx->new_crlfile_entry);
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003411
3412 /* Create a new cafile_entry without adding it to the cafile tree. */
Willy Tarreaua06b9a52022-05-04 20:33:03 +02003413 ctx->new_crlfile_entry = ssl_store_create_cafile_entry(ctx->path, NULL, CAFILE_CRL);
3414 if (!ctx->new_crlfile_entry) {
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003415 memprintf(&err, "%sCannot allocate memory!\n", err ? err : "");
3416 errcode |= ERR_ALERT | ERR_FATAL;
3417 goto end;
3418 }
3419
3420 /* Fill the new entry with the new CRL. */
Willy Tarreaua06b9a52022-05-04 20:33:03 +02003421 if (ssl_store_load_ca_from_buf(ctx->new_crlfile_entry, payload)) {
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003422 memprintf(&err, "%sInvalid payload\n", err ? err : "");
3423 errcode |= ERR_ALERT | ERR_FATAL;
3424 goto end;
3425 }
3426
3427 /* we succeed, we can save the crl in the transaction */
3428
Remi Tricot-Le Breton444d7022022-05-05 17:18:40 +02003429 /* if there wasn't a transaction, update the old CRL */
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003430 if (!crlfile_transaction.old_crlfile_entry) {
Willy Tarreaua06b9a52022-05-04 20:33:03 +02003431 crlfile_transaction.old_crlfile_entry = ctx->old_crlfile_entry;
3432 crlfile_transaction.path = ctx->path;
Remi Tricot-Le Breton444d7022022-05-05 17:18:40 +02003433 err = memprintf(&err, "transaction created for CRL %s!\n", crlfile_transaction.path);
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003434 } else {
Remi Tricot-Le Breton444d7022022-05-05 17:18:40 +02003435 err = memprintf(&err, "transaction updated for CRL %s!\n", crlfile_transaction.path);
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003436 }
3437
3438 /* free the previous CRL file if there was a transaction */
3439 ssl_store_delete_cafile_entry(crlfile_transaction.new_crlfile_entry);
3440
Willy Tarreaua06b9a52022-05-04 20:33:03 +02003441 crlfile_transaction.new_crlfile_entry = ctx->new_crlfile_entry;
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003442
3443 /* creates the SNI ctxs later in the IO handler */
3444
3445end:
3446 free_trash_chunk(buf);
3447
3448 if (errcode & ERR_CODE) {
Willy Tarreaua06b9a52022-05-04 20:33:03 +02003449 ssl_store_delete_cafile_entry(ctx->new_crlfile_entry);
3450 ctx->new_crlfile_entry = NULL;
3451 ctx->old_crlfile_entry = NULL;
3452 ha_free(&ctx->path);
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003453 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3454 return cli_dynerr(appctx, memprintf(&err, "%sCan't update %s!\n", err ? err : "", args[3]));
3455 } else {
3456
3457 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3458 return cli_dynmsg(appctx, LOG_NOTICE, err);
3459 }
3460}
3461
Willy Tarreaudec23dc2022-05-04 20:25:05 +02003462/* Parsing function of 'commit ssl crl-file'.
3463 * It uses a commit_cacrlfile_ctx that's also shared with "commit ssl ca-file".
3464 */
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003465static int cli_parse_commit_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
3466{
Willy Tarreaudec23dc2022-05-04 20:25:05 +02003467 struct commit_cacrlfile_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003468 char *err = NULL;
3469
3470 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
3471 return 1;
3472
3473 if (!*args[3])
3474 return cli_err(appctx, "'commit ssl ca-file expects a filename\n");
3475
3476 /* The operations on the CKCH architecture are locked so we can
3477 * manipulate ckch_store and ckch_inst */
3478 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3479 return cli_err(appctx, "Can't commit the CRL file!\nOperations on certificates are currently locked!\n");
3480
3481 if (!crlfile_transaction.path) {
3482 memprintf(&err, "No ongoing transaction! !\n");
3483 goto error;
3484 }
3485
3486 if (strcmp(crlfile_transaction.path, args[3]) != 0) {
3487 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", crlfile_transaction.path, args[3]);
3488 goto error;
3489 }
3490 /* init the appctx structure */
Willy Tarreau1d6dd802022-05-05 08:17:29 +02003491 ctx->state = CACRL_ST_INIT;
Willy Tarreaudec23dc2022-05-04 20:25:05 +02003492 ctx->next_ckchi = NULL;
3493 ctx->old_crlfile_entry = crlfile_transaction.old_crlfile_entry;
3494 ctx->new_crlfile_entry = crlfile_transaction.new_crlfile_entry;
3495 ctx->cafile_type = CAFILE_CRL;
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003496
3497 return 0;
3498
3499error:
3500
3501 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3502 err = memprintf(&err, "%sCan't commit %s!\n", err ? err : "", args[3]);
3503
3504 return cli_dynerr(appctx, err);
3505}
3506
3507
Willy Tarreaudec23dc2022-05-04 20:25:05 +02003508/* release function of the `commit ssl crl-file' command, free things and unlock the spinlock.
3509 * it uses a commit_cacrlfile_ctx that's the same as for "commit ssl ca-file".
3510 */
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003511static void cli_release_commit_crlfile(struct appctx *appctx)
3512{
Willy Tarreaudec23dc2022-05-04 20:25:05 +02003513 struct commit_cacrlfile_ctx *ctx = appctx->svcctx;
3514
Willy Tarreau1d6dd802022-05-05 08:17:29 +02003515 if (ctx->state != CACRL_ST_FIN) {
Willy Tarreaudec23dc2022-05-04 20:25:05 +02003516 struct cafile_entry *new_crlfile_entry = ctx->new_crlfile_entry;
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003517
3518 /* Remove the uncommitted cafile_entry from the tree. */
3519 ebmb_delete(&new_crlfile_entry->node);
3520 ssl_store_delete_cafile_entry(new_crlfile_entry);
3521 }
3522 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3523}
3524
Remi Tricot-Le Breton720e3b92021-04-26 11:00:42 +02003525/* parsing function of 'del ssl crl-file' */
3526static int cli_parse_del_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
3527{
3528 struct cafile_entry *cafile_entry;
3529 char *err = NULL;
3530 char *filename;
3531
3532 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
3533 return 1;
3534
3535 if (!*args[3])
3536 return cli_err(appctx, "'del ssl crl-file' expects a CRL file name\n");
3537
3538 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3539 return cli_err(appctx, "Can't delete the CRL file!\nOperations on certificates are currently locked!\n");
3540
3541 filename = args[3];
3542
3543 cafile_entry = ssl_store_get_cafile_entry(filename, 0);
3544 if (!cafile_entry) {
3545 memprintf(&err, "CRL file '%s' doesn't exist!\n", filename);
3546 goto error;
3547 }
3548 if (cafile_entry->type != CAFILE_CRL) {
3549 memprintf(&err, "'del ssl crl-file' does not work on CA files!\n");
3550 goto error;
3551 }
3552
3553 if (!LIST_ISEMPTY(&cafile_entry->ckch_inst_link)) {
3554 memprintf(&err, "CRL file '%s' in use, can't be deleted!\n", filename);
3555 goto error;
3556 }
3557
3558 /* Remove the cafile_entry from the tree */
3559 ebmb_delete(&cafile_entry->node);
3560 ssl_store_delete_cafile_entry(cafile_entry);
3561
3562 memprintf(&err, "CRL file '%s' deleted!\n", filename);
3563
3564 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3565 return cli_dynmsg(appctx, LOG_NOTICE, err);
3566
3567error:
3568 memprintf(&err, "Can't remove the CRL file: %s\n", err ? err : "");
3569 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3570 return cli_dynerr(appctx, err);
3571}
3572
Remi Tricot-Le Bretoneef8e7b2021-04-20 17:42:02 +02003573/* parsing function of 'abort ssl crl-file' */
3574static int cli_parse_abort_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
3575{
3576 char *err = NULL;
3577
3578 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
3579 return 1;
3580
3581 if (!*args[3])
3582 return cli_err(appctx, "'abort ssl crl-file' expects a filename\n");
3583
3584 /* The operations on the CKCH architecture are locked so we can
3585 * manipulate ckch_store and ckch_inst */
3586 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3587 return cli_err(appctx, "Can't abort!\nOperations on certificates are currently locked!\n");
3588
3589 if (!crlfile_transaction.path) {
3590 memprintf(&err, "No ongoing transaction!\n");
3591 goto error;
3592 }
3593
3594 if (strcmp(crlfile_transaction.path, args[3]) != 0) {
3595 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to abort a transaction for '%s'\n", crlfile_transaction.path, args[3]);
3596 goto error;
3597 }
3598
3599 /* Only free the uncommitted cafile_entry here, because the SNI and instances were not generated yet */
3600 ssl_store_delete_cafile_entry(crlfile_transaction.new_crlfile_entry);
3601 crlfile_transaction.new_crlfile_entry = NULL;
3602 crlfile_transaction.old_crlfile_entry = NULL;
3603 ha_free(&crlfile_transaction.path);
3604
3605 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3606
3607 err = memprintf(&err, "Transaction aborted for certificate '%s'!\n", args[3]);
3608 return cli_dynmsg(appctx, LOG_NOTICE, err);
3609
3610error:
3611 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3612
3613 return cli_dynerr(appctx, err);
3614}
3615
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003616
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003617/*
3618 * Display a Certificate Resignation List's information.
3619 * The information displayed is inspired by the output of 'openssl crl -in
3620 * crl.pem -text'.
3621 * Returns 0 in case of success.
3622 */
3623static int show_crl_detail(X509_CRL *crl, struct buffer *out)
3624{
3625 BIO *bio = NULL;
3626 struct buffer *tmp = alloc_trash_chunk();
3627 long version;
3628 X509_NAME *issuer;
3629 int write = -1;
3630 STACK_OF(X509_REVOKED) *rev = NULL;
3631 X509_REVOKED *rev_entry = NULL;
3632 int i;
3633
3634 if (!tmp)
3635 return -1;
3636
3637 if ((bio = BIO_new(BIO_s_mem())) == NULL)
3638 goto end;
3639
3640 /* Version (as displayed by 'openssl crl') */
3641 version = X509_CRL_get_version(crl);
3642 chunk_appendf(out, "Version %ld\n", version + 1);
3643
3644 /* Signature Algorithm */
3645 chunk_appendf(out, "Signature Algorithm: %s\n", OBJ_nid2ln(X509_CRL_get_signature_nid(crl)));
3646
3647 /* Issuer */
3648 chunk_appendf(out, "Issuer: ");
3649 if ((issuer = X509_CRL_get_issuer(crl)) == NULL)
3650 goto end;
3651 if ((ssl_sock_get_dn_oneline(issuer, tmp)) == -1)
3652 goto end;
3653 *(tmp->area + tmp->data) = '\0';
3654 chunk_appendf(out, "%s\n", tmp->area);
3655
3656 /* Last Update */
3657 chunk_appendf(out, "Last Update: ");
3658 chunk_reset(tmp);
Remi Tricot-Le Bretond75b99e2021-05-17 11:45:55 +02003659 if (BIO_reset(bio) == -1)
3660 goto end;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003661 if (ASN1_TIME_print(bio, X509_CRL_get0_lastUpdate(crl)) == 0)
3662 goto end;
3663 write = BIO_read(bio, tmp->area, tmp->size-1);
3664 tmp->area[write] = '\0';
3665 chunk_appendf(out, "%s\n", tmp->area);
3666
3667
3668 /* Next Update */
3669 chunk_appendf(out, "Next Update: ");
3670 chunk_reset(tmp);
Remi Tricot-Le Bretond75b99e2021-05-17 11:45:55 +02003671 if (BIO_reset(bio) == -1)
3672 goto end;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003673 if (ASN1_TIME_print(bio, X509_CRL_get0_nextUpdate(crl)) == 0)
3674 goto end;
3675 write = BIO_read(bio, tmp->area, tmp->size-1);
3676 tmp->area[write] = '\0';
3677 chunk_appendf(out, "%s\n", tmp->area);
3678
3679
3680 /* Revoked Certificates */
3681 rev = X509_CRL_get_REVOKED(crl);
3682 if (sk_X509_REVOKED_num(rev) > 0)
3683 chunk_appendf(out, "Revoked Certificates:\n");
3684 else
3685 chunk_appendf(out, "No Revoked Certificates.\n");
3686
3687 for (i = 0; i < sk_X509_REVOKED_num(rev); i++) {
3688 rev_entry = sk_X509_REVOKED_value(rev, i);
3689
3690 /* Serial Number and Revocation Date */
Remi Tricot-Le Bretond75b99e2021-05-17 11:45:55 +02003691 if (BIO_reset(bio) == -1)
3692 goto end;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003693 BIO_printf(bio , " Serial Number: ");
Remi Tricot-Le Breton18c7d832021-05-17 18:38:34 +02003694 i2a_ASN1_INTEGER(bio, (ASN1_INTEGER*)X509_REVOKED_get0_serialNumber(rev_entry));
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003695 BIO_printf(bio, "\n Revocation Date: ");
Remi Tricot-Le Bretona6b27842021-05-18 10:06:00 +02003696 if (ASN1_TIME_print(bio, X509_REVOKED_get0_revocationDate(rev_entry)) == 0)
3697 goto end;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003698 BIO_printf(bio, "\n");
3699
3700 write = BIO_read(bio, tmp->area, tmp->size-1);
3701 tmp->area[write] = '\0';
3702 chunk_appendf(out, "%s", tmp->area);
3703 }
3704
3705end:
3706 free_trash_chunk(tmp);
3707 if (bio)
3708 BIO_free(bio);
3709
3710 return 0;
3711}
3712
Willy Tarreau821c3b02022-05-04 15:47:39 +02003713/* IO handler of details "show ssl crl-file <filename[:index]>".
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003714 * It uses show_crlfile_ctx and the global
3715 * crlfile_transaction.new_cafile_entry in read-only.
Willy Tarreau821c3b02022-05-04 15:47:39 +02003716 */
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003717static int cli_io_handler_show_crlfile_detail(struct appctx *appctx)
3718{
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003719 struct show_crlfile_ctx *ctx = appctx->svcctx;
Willy Tarreau4596fe22022-05-17 19:07:51 +02003720 struct stconn *cs = appctx_cs(appctx);
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003721 struct cafile_entry *cafile_entry = ctx->cafile_entry;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003722 struct buffer *out = alloc_trash_chunk();
3723 int i;
3724 X509_CRL *crl;
3725 STACK_OF(X509_OBJECT) *objs;
3726 int retval = 0;
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003727 int index = ctx->index;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003728
3729 if (!out)
3730 goto end_no_putchk;
3731
3732 chunk_appendf(out, "Filename: ");
3733 if (cafile_entry == crlfile_transaction.new_crlfile_entry)
3734 chunk_appendf(out, "*");
3735 chunk_appendf(out, "%s\n", cafile_entry->path);
3736
3737 chunk_appendf(out, "Status: ");
3738 if (!cafile_entry->ca_store)
3739 chunk_appendf(out, "Empty\n");
3740 else if (LIST_ISEMPTY(&cafile_entry->ckch_inst_link))
3741 chunk_appendf(out, "Unused\n");
3742 else
3743 chunk_appendf(out, "Used\n");
3744
3745 if (!cafile_entry->ca_store)
3746 goto end;
3747
3748 objs = X509_STORE_get0_objects(cafile_entry->ca_store);
3749 for (i = 0; i < sk_X509_OBJECT_num(objs); i++) {
3750 crl = X509_OBJECT_get0_X509_CRL(sk_X509_OBJECT_value(objs, i));
3751 if (!crl)
3752 continue;
3753
3754 /* CRL indexes start at 1 on the CLI output. */
3755 if (index && index-1 != i)
3756 continue;
3757
Remi Tricot-Le Bretone8041fe2022-04-05 16:44:21 +02003758 chunk_appendf(out, " \nCertificate Revocation List #%d:\n", i+1);
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003759 retval = show_crl_detail(crl, out);
3760 if (retval < 0)
3761 goto end_no_putchk;
3762 else if (retval || index)
3763 goto end;
3764 }
3765
3766end:
Christopher Faulet908628c2022-03-25 16:43:49 +01003767 if (ci_putchk(cs_ic(cs), out) == -1) {
Christopher Fauleta0bdec32022-04-04 07:51:21 +02003768 cs_rx_room_blk(cs);
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003769 goto yield;
3770 }
3771
3772end_no_putchk:
3773 free_trash_chunk(out);
3774 return 1;
3775yield:
3776 free_trash_chunk(out);
3777 return 0; /* should come back */
3778}
3779
Willy Tarreau821c3b02022-05-04 15:47:39 +02003780/* parsing function for 'show ssl crl-file [crlfile[:index]]'.
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003781 * It sets the context to a show_crlfile_ctx, and the global
Willy Tarreau821c3b02022-05-04 15:47:39 +02003782 * cafile_transaction.new_crlfile_entry under the ckch_lock.
3783 */
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003784static int cli_parse_show_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
3785{
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003786 struct show_crlfile_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003787 struct cafile_entry *cafile_entry;
3788 long index = 0;
3789 char *colons;
3790 char *err = NULL;
3791
3792 if (!cli_has_level(appctx, ACCESS_LVL_OPER))
3793 return cli_err(appctx, "Can't allocate memory!\n");
3794
3795 /* The operations on the CKCH architecture are locked so we can
3796 * manipulate ckch_store and ckch_inst */
3797 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3798 return cli_err(appctx, "Can't show!\nOperations on certificates are currently locked!\n");
3799
3800 /* check if there is a certificate to lookup */
3801 if (*args[3]) {
3802
3803 /* Look for an optional index after the CRL file name */
3804 colons = strchr(args[3], ':');
3805 if (colons) {
3806 char *endptr;
3807
3808 index = strtol(colons + 1, &endptr, 10);
3809 /* Indexes start at 1 */
3810 if (colons + 1 == endptr || *endptr != '\0' || index <= 0) {
3811 memprintf(&err, "wrong CRL index after colons in '%s'!", args[3]);
3812 goto error;
3813 }
3814 *colons = '\0';
3815 }
3816
3817 if (*args[3] == '*') {
3818 if (!crlfile_transaction.new_crlfile_entry)
3819 goto error;
3820
3821 cafile_entry = crlfile_transaction.new_crlfile_entry;
3822
3823 if (strcmp(args[3] + 1, cafile_entry->path) != 0)
3824 goto error;
3825
3826 } else {
3827 /* Get the "original" cafile_entry and not the
3828 * uncommitted one if it exists. */
3829 if ((cafile_entry = ssl_store_get_cafile_entry(args[3], 1)) == NULL || cafile_entry->type != CAFILE_CRL)
3830 goto error;
3831 }
3832
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003833 ctx->cafile_entry = cafile_entry;
3834 ctx->index = index;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003835 /* use the IO handler that shows details */
3836 appctx->io_handler = cli_io_handler_show_crlfile_detail;
3837 }
3838
3839 return 0;
3840
3841error:
3842 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3843 if (err)
3844 return cli_dynerr(appctx, err);
Remi Tricot-Le Breton444d7022022-05-05 17:18:40 +02003845 return cli_err(appctx, "Can't display the CRL file : Not found!\n");
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003846}
3847
3848/* IO handler of "show ssl crl-file". The command taking a specific CRL file name
3849 * is managed in cli_io_handler_show_crlfile_detail. */
3850static int cli_io_handler_show_crlfile(struct appctx *appctx)
3851{
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003852 struct show_crlfile_ctx *ctx = appctx->svcctx;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003853 struct buffer *trash = alloc_trash_chunk();
3854 struct ebmb_node *node;
Willy Tarreau4596fe22022-05-17 19:07:51 +02003855 struct stconn *cs = appctx_cs(appctx);
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003856 struct cafile_entry *cafile_entry;
3857
3858 if (trash == NULL)
3859 return 1;
3860
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003861 if (!ctx->old_crlfile_entry) {
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003862 if (crlfile_transaction.old_crlfile_entry) {
3863 chunk_appendf(trash, "# transaction\n");
3864 chunk_appendf(trash, "*%s\n", crlfile_transaction.old_crlfile_entry->path);
3865 }
3866 }
3867
3868 /* First time in this io_handler. */
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003869 if (!ctx->cafile_entry) {
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003870 chunk_appendf(trash, "# filename\n");
3871 node = ebmb_first(&cafile_tree);
3872 } else {
3873 /* We yielded during a previous call. */
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003874 node = &ctx->cafile_entry->node;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003875 }
3876
3877 while (node) {
3878 cafile_entry = ebmb_entry(node, struct cafile_entry, node);
3879 if (cafile_entry->type == CAFILE_CRL) {
3880 chunk_appendf(trash, "%s\n", cafile_entry->path);
3881 }
3882
3883 node = ebmb_next(node);
Christopher Faulet908628c2022-03-25 16:43:49 +01003884 if (ci_putchk(cs_ic(cs), trash) == -1) {
Christopher Fauleta0bdec32022-04-04 07:51:21 +02003885 cs_rx_room_blk(cs);
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003886 goto yield;
3887 }
3888 }
3889
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003890 ctx->cafile_entry = NULL;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003891 free_trash_chunk(trash);
3892 return 1;
3893yield:
3894
3895 free_trash_chunk(trash);
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003896 ctx->cafile_entry = cafile_entry;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003897 return 0; /* should come back */
3898}
3899
3900
3901/* release function of the 'show ssl crl-file' command */
3902static void cli_release_show_crlfile(struct appctx *appctx)
3903{
3904 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3905}
3906
3907
William Lallemandee8530c2020-06-23 18:19:42 +02003908void ckch_deinit()
3909{
3910 struct eb_node *node, *next;
3911 struct ckch_store *store;
William Lallemandb0c48272022-04-26 15:44:53 +02003912 struct ebmb_node *canode;
William Lallemandee8530c2020-06-23 18:19:42 +02003913
William Lallemandb0c48272022-04-26 15:44:53 +02003914 /* deinit the ckch stores */
William Lallemandee8530c2020-06-23 18:19:42 +02003915 node = eb_first(&ckchs_tree);
3916 while (node) {
3917 next = eb_next(node);
3918 store = ebmb_entry(node, struct ckch_store, node);
3919 ckch_store_free(store);
3920 node = next;
3921 }
William Lallemandb0c48272022-04-26 15:44:53 +02003922
3923 /* deinit the ca-file store */
3924 canode = ebmb_first(&cafile_tree);
3925 while (canode) {
3926 struct cafile_entry *entry = NULL;
3927
3928 entry = ebmb_entry(canode, struct cafile_entry, node);
3929 canode = ebmb_next(canode);
3930 ssl_store_delete_cafile_entry(entry);
3931 }
William Lallemandee8530c2020-06-23 18:19:42 +02003932}
William Lallemandda8584c2020-05-14 10:14:37 +02003933
3934/* register cli keywords */
3935static struct cli_kw_list cli_kws = {{ },{
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01003936 { { "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 },
3937 { { "set", "ssl", "cert", NULL }, "set ssl cert <certfile> <payload> : replace a certificate file", cli_parse_set_cert, NULL, NULL },
3938 { { "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 },
3939 { { "abort", "ssl", "cert", NULL }, "abort ssl cert <certfile> : abort a transaction for a certificate file", cli_parse_abort_cert, NULL, NULL },
3940 { { "del", "ssl", "cert", NULL }, "del ssl cert <certfile> : delete an unused certificate file", cli_parse_del_cert, NULL, NULL },
3941 { { "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 },
3942
Amaury Denoyelleb11ad9e2021-05-21 11:01:10 +02003943 { { "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 +01003944 { { "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 +02003945 { { "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 +01003946 { { "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 +01003947 { { "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 +01003948 { { "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 +02003949
Remi Tricot-Le Breton720e3b92021-04-26 11:00:42 +02003950 { { "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 +02003951 { { "set", "ssl", "crl-file", NULL }, "set ssl crl-file <crlfile> <payload> : replace a CRL file", cli_parse_set_crlfile, NULL, NULL },
3952 { { "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 +02003953 { { "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 +02003954 { { "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 +02003955 { { "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 +02003956 { { NULL }, NULL, NULL, NULL }
3957}};
3958
3959INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws);
3960