blob: b9cddf20d7a8a856e0ef17ad9a4f410c1ccbba48 [file] [log] [blame]
William Lallemand03c331c2020-05-13 10:10:01 +02001/*
2 *
3 * Copyright (C) 2020 HAProxy Technologies, William Lallemand <wlallemand@haproxy.com>
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version
8 * 2 of the License, or (at your option) any later version.
9 *
10 */
11
12#define _GNU_SOURCE
13#include <ctype.h>
14#include <errno.h>
15#include <fcntl.h>
16#include <stdio.h>
17#include <stdlib.h>
18#include <string.h>
Willy Tarreauaeed4a82020-06-04 22:01:04 +020019#include <syslog.h>
William Lallemand03c331c2020-05-13 10:10:01 +020020#include <unistd.h>
21
22#include <sys/stat.h>
23#include <sys/types.h>
24
Willy Tarreaub2551052020-06-09 09:07:15 +020025#include <import/ebsttree.h>
26
Willy Tarreau8d366972020-05-27 16:10:29 +020027#include <haproxy/base64.h>
Willy Tarreauf1d32c42020-06-04 21:07:02 +020028#include <haproxy/channel.h>
Willy Tarreau83487a82020-06-04 20:19:54 +020029#include <haproxy/cli.h>
Willy Tarreau8d366972020-05-27 16:10:29 +020030#include <haproxy/errors.h>
Willy Tarreau47d7f902020-06-04 14:25:47 +020031#include <haproxy/ssl_ckch.h>
Willy Tarreau209108d2020-06-04 20:30:20 +020032#include <haproxy/ssl_sock.h>
Willy Tarreaub2bd8652020-06-04 14:21:22 +020033#include <haproxy/ssl_utils.h>
Willy Tarreau5e539c92020-06-04 20:45:39 +020034#include <haproxy/stream_interface.h>
Willy Tarreau48fbcae2020-06-03 18:09:46 +020035#include <haproxy/tools.h>
William Lallemand03c331c2020-05-13 10:10:01 +020036
William Lallemandda8584c2020-05-14 10:14:37 +020037/* Uncommitted CKCH transaction */
38
39static struct {
40 struct ckch_store *new_ckchs;
41 struct ckch_store *old_ckchs;
42 char *path;
43} ckchs_transaction;
44
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +010045/* Uncommitted CA file transaction */
46
47static struct {
48 struct cafile_entry *old_cafile_entry;
49 struct cafile_entry *new_cafile_entry;
50 char *path;
51} cafile_transaction;
William Lallemandda8584c2020-05-14 10:14:37 +020052
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +020053/* Uncommitted CRL file transaction */
54
55static struct {
56 struct cafile_entry *old_crlfile_entry;
57 struct cafile_entry *new_crlfile_entry;
58 char *path;
59} crlfile_transaction;
60
William Lallemand03c331c2020-05-13 10:10:01 +020061
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +010062
William Lallemand03c331c2020-05-13 10:10:01 +020063/******************** cert_key_and_chain functions *************************
64 * These are the functions that fills a cert_key_and_chain structure. For the
65 * functions filling a SSL_CTX from a cert_key_and_chain, see ssl_sock.c
66 */
67
68/*
69 * Try to parse Signed Certificate Timestamp List structure. This function
70 * makes only basic test if the data seems like SCTL. No signature validation
71 * is performed.
72 */
73static int ssl_sock_parse_sctl(struct buffer *sctl)
74{
75 int ret = 1;
76 int len, pos, sct_len;
77 unsigned char *data;
78
79 if (sctl->data < 2)
80 goto out;
81
82 data = (unsigned char *) sctl->area;
83 len = (data[0] << 8) | data[1];
84
85 if (len + 2 != sctl->data)
86 goto out;
87
88 data = data + 2;
89 pos = 0;
90 while (pos < len) {
91 if (len - pos < 2)
92 goto out;
93
94 sct_len = (data[pos] << 8) | data[pos + 1];
95 if (pos + sct_len + 2 > len)
96 goto out;
97
98 pos += sct_len + 2;
99 }
100
101 ret = 0;
102
103out:
104 return ret;
105}
106
107/* Try to load a sctl from a buffer <buf> if not NULL, or read the file <sctl_path>
108 * It fills the ckch->sctl buffer
109 * return 0 on success or != 0 on failure */
110int ssl_sock_load_sctl_from_file(const char *sctl_path, char *buf, struct cert_key_and_chain *ckch, char **err)
111{
112 int fd = -1;
113 int r = 0;
114 int ret = 1;
115 struct buffer tmp;
116 struct buffer *src;
117 struct buffer *sctl;
118
119 if (buf) {
William Lallemand8d673942021-01-27 14:58:51 +0100120 chunk_initstr(&tmp, buf);
William Lallemand03c331c2020-05-13 10:10:01 +0200121 src = &tmp;
122 } else {
123 fd = open(sctl_path, O_RDONLY);
124 if (fd == -1)
125 goto end;
126
127 trash.data = 0;
128 while (trash.data < trash.size) {
129 r = read(fd, trash.area + trash.data, trash.size - trash.data);
130 if (r < 0) {
131 if (errno == EINTR)
132 continue;
133 goto end;
134 }
135 else if (r == 0) {
136 break;
137 }
138 trash.data += r;
139 }
140 src = &trash;
141 }
142
143 ret = ssl_sock_parse_sctl(src);
144 if (ret)
145 goto end;
146
147 sctl = calloc(1, sizeof(*sctl));
148 if (!chunk_dup(sctl, src)) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100149 ha_free(&sctl);
William Lallemand03c331c2020-05-13 10:10:01 +0200150 goto end;
151 }
152 /* no error, fill ckch with new context, old context must be free */
153 if (ckch->sctl) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100154 ha_free(&ckch->sctl->area);
William Lallemand03c331c2020-05-13 10:10:01 +0200155 free(ckch->sctl);
156 }
157 ckch->sctl = sctl;
158 ret = 0;
159end:
160 if (fd != -1)
161 close(fd);
162
163 return ret;
164}
165
166#if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) || defined OPENSSL_IS_BORINGSSL)
167/*
Ilya Shipitsin46a030c2020-07-05 16:36:08 +0500168 * This function load the OCSP Response in DER format contained in file at
William Lallemand03c331c2020-05-13 10:10:01 +0200169 * path 'ocsp_path' or base64 in a buffer <buf>
170 *
171 * Returns 0 on success, 1 in error case.
172 */
173int ssl_sock_load_ocsp_response_from_file(const char *ocsp_path, char *buf, struct cert_key_and_chain *ckch, char **err)
174{
175 int fd = -1;
176 int r = 0;
177 int ret = 1;
178 struct buffer *ocsp_response;
179 struct buffer *src = NULL;
180
181 if (buf) {
182 int i, j;
183 /* if it's from a buffer it will be base64 */
184
185 /* remove \r and \n from the payload */
186 for (i = 0, j = 0; buf[i]; i++) {
187 if (buf[i] == '\r' || buf[i] == '\n')
188 continue;
189 buf[j++] = buf[i];
190 }
191 buf[j] = 0;
192
193 ret = base64dec(buf, j, trash.area, trash.size);
194 if (ret < 0) {
195 memprintf(err, "Error reading OCSP response in base64 format");
196 goto end;
197 }
198 trash.data = ret;
199 src = &trash;
200 } else {
201 fd = open(ocsp_path, O_RDONLY);
202 if (fd == -1) {
203 memprintf(err, "Error opening OCSP response file");
204 goto end;
205 }
206
207 trash.data = 0;
208 while (trash.data < trash.size) {
209 r = read(fd, trash.area + trash.data, trash.size - trash.data);
210 if (r < 0) {
211 if (errno == EINTR)
212 continue;
213
214 memprintf(err, "Error reading OCSP response from file");
215 goto end;
216 }
217 else if (r == 0) {
218 break;
219 }
220 trash.data += r;
221 }
222 close(fd);
223 fd = -1;
224 src = &trash;
225 }
226
227 ocsp_response = calloc(1, sizeof(*ocsp_response));
228 if (!chunk_dup(ocsp_response, src)) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100229 ha_free(&ocsp_response);
William Lallemand03c331c2020-05-13 10:10:01 +0200230 goto end;
231 }
232 /* no error, fill ckch with new context, old context must be free */
233 if (ckch->ocsp_response) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100234 ha_free(&ckch->ocsp_response->area);
William Lallemand03c331c2020-05-13 10:10:01 +0200235 free(ckch->ocsp_response);
236 }
237 ckch->ocsp_response = ocsp_response;
238 ret = 0;
239end:
240 if (fd != -1)
241 close(fd);
242
243 return ret;
244}
245#endif
246
247/*
248 * Try to load in a ckch every files related to a ckch.
249 * (PEM, sctl, ocsp, issuer etc.)
250 *
251 * This function is only used to load files during the configuration parsing,
252 * it is not used with the CLI.
253 *
254 * This allows us to carry the contents of the file without having to read the
255 * file multiple times. The caller must call
256 * ssl_sock_free_cert_key_and_chain_contents.
257 *
258 * returns:
259 * 0 on Success
260 * 1 on SSL Failure
261 */
262int ssl_sock_load_files_into_ckch(const char *path, struct cert_key_and_chain *ckch, char **err)
263{
William Lallemand8e8581e2020-10-20 17:36:46 +0200264 struct buffer *fp = NULL;
William Lallemand03c331c2020-05-13 10:10:01 +0200265 int ret = 1;
266
267 /* try to load the PEM */
268 if (ssl_sock_load_pem_into_ckch(path, NULL, ckch , err) != 0) {
269 goto end;
270 }
271
William Lallemand8e8581e2020-10-20 17:36:46 +0200272 fp = alloc_trash_chunk();
273 if (!fp) {
274 memprintf(err, "%sCan't allocate memory\n", err && *err ? *err : "");
275 goto end;
276 }
277
278 if (!chunk_strcpy(fp, path) || (b_data(fp) > MAXPATHLEN)) {
279 memprintf(err, "%s '%s' filename too long'.\n",
280 err && *err ? *err : "", fp->area);
281 ret = 1;
282 goto end;
283 }
284
William Lallemand089c1382020-10-23 17:35:12 +0200285 /* remove the ".crt" extension */
William Lallemand8e8581e2020-10-20 17:36:46 +0200286 if (global_ssl.extra_files_noext) {
287 char *ext;
288
289 /* look for the extension */
290 if ((ext = strrchr(fp->area, '.'))) {
William Lallemand8e8581e2020-10-20 17:36:46 +0200291
Tim Duesterhuse5ff1412021-01-02 22:31:53 +0100292 if (strcmp(ext, ".crt") == 0) {
William Lallemand8e8581e2020-10-20 17:36:46 +0200293 *ext = '\0';
William Lallemand089c1382020-10-23 17:35:12 +0200294 fp->data = strlen(fp->area);
295 }
William Lallemand8e8581e2020-10-20 17:36:46 +0200296 }
297
298 }
299
William Lallemand03c331c2020-05-13 10:10:01 +0200300 /* try to load an external private key if it wasn't in the PEM */
301 if ((ckch->key == NULL) && (global_ssl.extra_files & SSL_GF_KEY)) {
William Lallemand03c331c2020-05-13 10:10:01 +0200302 struct stat st;
303
William Lallemand8e8581e2020-10-20 17:36:46 +0200304
305 if (!chunk_strcat(fp, ".key") || (b_data(fp) > MAXPATHLEN)) {
306 memprintf(err, "%s '%s' filename too long'.\n",
307 err && *err ? *err : "", fp->area);
308 ret = 1;
309 goto end;
310 }
311
312 if (stat(fp->area, &st) == 0) {
313 if (ssl_sock_load_key_into_ckch(fp->area, NULL, ckch, err)) {
William Lallemand03c331c2020-05-13 10:10:01 +0200314 memprintf(err, "%s '%s' is present but cannot be read or parsed'.\n",
William Lallemand8e8581e2020-10-20 17:36:46 +0200315 err && *err ? *err : "", fp->area);
William Lallemand03c331c2020-05-13 10:10:01 +0200316 goto end;
317 }
318 }
William Lallemand03c331c2020-05-13 10:10:01 +0200319
William Lallemand8e8581e2020-10-20 17:36:46 +0200320 if (ckch->key == NULL) {
321 memprintf(err, "%sNo Private Key found in '%s'.\n", err && *err ? *err : "", fp->area);
322 goto end;
323 }
324 /* remove the added extension */
325 *(fp->area + fp->data - strlen(".key")) = '\0';
326 b_sub(fp, strlen(".key"));
William Lallemand03c331c2020-05-13 10:10:01 +0200327 }
328
329 if (!X509_check_private_key(ckch->cert, ckch->key)) {
330 memprintf(err, "%sinconsistencies between private key and certificate loaded '%s'.\n",
331 err && *err ? *err : "", path);
332 goto end;
333 }
334
Ilya Shipitsinc47d6762021-02-13 11:45:33 +0500335#ifdef HAVE_SSL_SCTL
William Lallemand03c331c2020-05-13 10:10:01 +0200336 /* try to load the sctl file */
337 if (global_ssl.extra_files & SSL_GF_SCTL) {
William Lallemand03c331c2020-05-13 10:10:01 +0200338 struct stat st;
339
William Lallemand8e8581e2020-10-20 17:36:46 +0200340 if (!chunk_strcat(fp, ".sctl") || b_data(fp) > MAXPATHLEN) {
341 memprintf(err, "%s '%s' filename too long'.\n",
342 err && *err ? *err : "", fp->area);
343 ret = 1;
344 goto end;
345 }
346
347 if (stat(fp->area, &st) == 0) {
348 if (ssl_sock_load_sctl_from_file(fp->area, NULL, ckch, err)) {
William Lallemand03c331c2020-05-13 10:10:01 +0200349 memprintf(err, "%s '%s.sctl' is present but cannot be read or parsed'.\n",
William Lallemand8e8581e2020-10-20 17:36:46 +0200350 err && *err ? *err : "", fp->area);
William Lallemand03c331c2020-05-13 10:10:01 +0200351 ret = 1;
352 goto end;
353 }
354 }
William Lallemand8e8581e2020-10-20 17:36:46 +0200355 /* remove the added extension */
356 *(fp->area + fp->data - strlen(".sctl")) = '\0';
357 b_sub(fp, strlen(".sctl"));
William Lallemand03c331c2020-05-13 10:10:01 +0200358 }
359#endif
360
361 /* try to load an ocsp response file */
362 if (global_ssl.extra_files & SSL_GF_OCSP) {
William Lallemand03c331c2020-05-13 10:10:01 +0200363 struct stat st;
364
William Lallemand8e8581e2020-10-20 17:36:46 +0200365 if (!chunk_strcat(fp, ".ocsp") || b_data(fp) > MAXPATHLEN) {
366 memprintf(err, "%s '%s' filename too long'.\n",
367 err && *err ? *err : "", fp->area);
368 ret = 1;
369 goto end;
370 }
371
372 if (stat(fp->area, &st) == 0) {
373 if (ssl_sock_load_ocsp_response_from_file(fp->area, NULL, ckch, err)) {
William Lallemand03c331c2020-05-13 10:10:01 +0200374 ret = 1;
375 goto end;
376 }
377 }
William Lallemand8e8581e2020-10-20 17:36:46 +0200378 /* remove the added extension */
379 *(fp->area + fp->data - strlen(".ocsp")) = '\0';
380 b_sub(fp, strlen(".ocsp"));
William Lallemand03c331c2020-05-13 10:10:01 +0200381 }
382
383#ifndef OPENSSL_IS_BORINGSSL /* Useless for BoringSSL */
384 if (ckch->ocsp_response && (global_ssl.extra_files & SSL_GF_OCSP_ISSUER)) {
385 /* if no issuer was found, try to load an issuer from the .issuer */
386 if (!ckch->ocsp_issuer) {
387 struct stat st;
William Lallemand8e8581e2020-10-20 17:36:46 +0200388
389 if (!chunk_strcat(fp, ".issuer") || b_data(fp) > MAXPATHLEN) {
390 memprintf(err, "%s '%s' filename too long'.\n",
391 err && *err ? *err : "", fp->area);
392 ret = 1;
393 goto end;
394 }
William Lallemand03c331c2020-05-13 10:10:01 +0200395
William Lallemand8e8581e2020-10-20 17:36:46 +0200396 if (stat(fp->area, &st) == 0) {
397 if (ssl_sock_load_issuer_file_into_ckch(fp->area, NULL, ckch, err)) {
William Lallemand03c331c2020-05-13 10:10:01 +0200398 ret = 1;
399 goto end;
400 }
401
402 if (X509_check_issued(ckch->ocsp_issuer, ckch->cert) != X509_V_OK) {
403 memprintf(err, "%s '%s' is not an issuer'.\n",
William Lallemand8e8581e2020-10-20 17:36:46 +0200404 err && *err ? *err : "", fp->area);
William Lallemand03c331c2020-05-13 10:10:01 +0200405 ret = 1;
406 goto end;
407 }
408 }
William Lallemand8e8581e2020-10-20 17:36:46 +0200409 /* remove the added extension */
410 *(fp->area + fp->data - strlen(".issuer")) = '\0';
411 b_sub(fp, strlen(".issuer"));
William Lallemand03c331c2020-05-13 10:10:01 +0200412 }
413 }
414#endif
415
416 ret = 0;
417
418end:
419
420 ERR_clear_error();
421
422 /* Something went wrong in one of the reads */
423 if (ret != 0)
424 ssl_sock_free_cert_key_and_chain_contents(ckch);
425
William Lallemand8e8581e2020-10-20 17:36:46 +0200426 free_trash_chunk(fp);
427
William Lallemand03c331c2020-05-13 10:10:01 +0200428 return ret;
429}
430
431/*
432 * Try to load a private key file from a <path> or a buffer <buf>
433 *
434 * If it failed you should not attempt to use the ckch but free it.
435 *
436 * Return 0 on success or != 0 on failure
437 */
438int ssl_sock_load_key_into_ckch(const char *path, char *buf, struct cert_key_and_chain *ckch , char **err)
439{
440 BIO *in = NULL;
441 int ret = 1;
442 EVP_PKEY *key = NULL;
443
444 if (buf) {
445 /* reading from a buffer */
446 in = BIO_new_mem_buf(buf, -1);
447 if (in == NULL) {
448 memprintf(err, "%sCan't allocate memory\n", err && *err ? *err : "");
449 goto end;
450 }
451
452 } else {
453 /* reading from a file */
454 in = BIO_new(BIO_s_file());
455 if (in == NULL)
456 goto end;
457
458 if (BIO_read_filename(in, path) <= 0)
459 goto end;
460 }
461
462 /* Read Private Key */
463 key = PEM_read_bio_PrivateKey(in, NULL, NULL, NULL);
464 if (key == NULL) {
465 memprintf(err, "%sunable to load private key from file '%s'.\n",
466 err && *err ? *err : "", path);
467 goto end;
468 }
469
470 ret = 0;
471
472 SWAP(ckch->key, key);
473
474end:
475
476 ERR_clear_error();
477 if (in)
478 BIO_free(in);
479 if (key)
480 EVP_PKEY_free(key);
481
482 return ret;
483}
484
485/*
486 * Try to load a PEM file from a <path> or a buffer <buf>
487 * The PEM must contain at least a Certificate,
488 * It could contain a DH, a certificate chain and a PrivateKey.
489 *
490 * If it failed you should not attempt to use the ckch but free it.
491 *
492 * Return 0 on success or != 0 on failure
493 */
494int ssl_sock_load_pem_into_ckch(const char *path, char *buf, struct cert_key_and_chain *ckch , char **err)
495{
496 BIO *in = NULL;
497 int ret = 1;
498 X509 *ca;
499 X509 *cert = NULL;
500 EVP_PKEY *key = NULL;
501 DH *dh = NULL;
502 STACK_OF(X509) *chain = NULL;
503
504 if (buf) {
505 /* reading from a buffer */
506 in = BIO_new_mem_buf(buf, -1);
507 if (in == NULL) {
508 memprintf(err, "%sCan't allocate memory\n", err && *err ? *err : "");
509 goto end;
510 }
511
512 } else {
513 /* reading from a file */
514 in = BIO_new(BIO_s_file());
515 if (in == NULL) {
516 memprintf(err, "%sCan't allocate memory\n", err && *err ? *err : "");
517 goto end;
518 }
519
520 if (BIO_read_filename(in, path) <= 0) {
521 memprintf(err, "%scannot open the file '%s'.\n",
522 err && *err ? *err : "", path);
523 goto end;
524 }
525 }
526
527 /* Read Private Key */
528 key = PEM_read_bio_PrivateKey(in, NULL, NULL, NULL);
529 /* no need to check for errors here, because the private key could be loaded later */
530
531#ifndef OPENSSL_NO_DH
532 /* Seek back to beginning of file */
533 if (BIO_reset(in) == -1) {
534 memprintf(err, "%san error occurred while reading the file '%s'.\n",
535 err && *err ? *err : "", path);
536 goto end;
537 }
538
539 dh = PEM_read_bio_DHparams(in, NULL, NULL, NULL);
540 /* no need to return an error there, dh is not mandatory */
541#endif
542
543 /* Seek back to beginning of file */
544 if (BIO_reset(in) == -1) {
545 memprintf(err, "%san error occurred while reading the file '%s'.\n",
546 err && *err ? *err : "", path);
547 goto end;
548 }
549
550 /* Read Certificate */
551 cert = PEM_read_bio_X509_AUX(in, NULL, NULL, NULL);
552 if (cert == NULL) {
553 memprintf(err, "%sunable to load certificate from file '%s'.\n",
554 err && *err ? *err : "", path);
555 goto end;
556 }
557
558 /* Look for a Certificate Chain */
559 while ((ca = PEM_read_bio_X509(in, NULL, NULL, NULL))) {
560 if (chain == NULL)
561 chain = sk_X509_new_null();
562 if (!sk_X509_push(chain, ca)) {
563 X509_free(ca);
564 goto end;
565 }
566 }
567
568 ret = ERR_get_error();
569 if (ret && (ERR_GET_LIB(ret) != ERR_LIB_PEM && ERR_GET_REASON(ret) != PEM_R_NO_START_LINE)) {
570 memprintf(err, "%sunable to load certificate chain from file '%s'.\n",
571 err && *err ? *err : "", path);
572 goto end;
573 }
574
575 /* once it loaded the PEM, it should remove everything else in the ckch */
576 if (ckch->ocsp_response) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100577 ha_free(&ckch->ocsp_response->area);
578 ha_free(&ckch->ocsp_response);
William Lallemand03c331c2020-05-13 10:10:01 +0200579 }
580
581 if (ckch->sctl) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100582 ha_free(&ckch->sctl->area);
583 ha_free(&ckch->sctl);
William Lallemand03c331c2020-05-13 10:10:01 +0200584 }
585
586 if (ckch->ocsp_issuer) {
587 X509_free(ckch->ocsp_issuer);
588 ckch->ocsp_issuer = NULL;
589 }
590
591 /* no error, fill ckch with new context, old context will be free at end: */
592 SWAP(ckch->key, key);
593 SWAP(ckch->dh, dh);
594 SWAP(ckch->cert, cert);
595 SWAP(ckch->chain, chain);
596
597 ret = 0;
598
599end:
600
601 ERR_clear_error();
602 if (in)
603 BIO_free(in);
604 if (key)
605 EVP_PKEY_free(key);
606 if (dh)
607 DH_free(dh);
608 if (cert)
609 X509_free(cert);
610 if (chain)
611 sk_X509_pop_free(chain, X509_free);
612
613 return ret;
614}
615
616/* Frees the contents of a cert_key_and_chain
617 */
618void ssl_sock_free_cert_key_and_chain_contents(struct cert_key_and_chain *ckch)
619{
620 if (!ckch)
621 return;
622
623 /* Free the certificate and set pointer to NULL */
624 if (ckch->cert)
625 X509_free(ckch->cert);
626 ckch->cert = NULL;
627
628 /* Free the key and set pointer to NULL */
629 if (ckch->key)
630 EVP_PKEY_free(ckch->key);
631 ckch->key = NULL;
632
633 /* Free each certificate in the chain */
634 if (ckch->chain)
635 sk_X509_pop_free(ckch->chain, X509_free);
636 ckch->chain = NULL;
637
638 if (ckch->dh)
639 DH_free(ckch->dh);
640 ckch->dh = NULL;
641
642 if (ckch->sctl) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100643 ha_free(&ckch->sctl->area);
644 ha_free(&ckch->sctl);
William Lallemand03c331c2020-05-13 10:10:01 +0200645 }
646
647 if (ckch->ocsp_response) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100648 ha_free(&ckch->ocsp_response->area);
649 ha_free(&ckch->ocsp_response);
William Lallemand03c331c2020-05-13 10:10:01 +0200650 }
651
652 if (ckch->ocsp_issuer)
653 X509_free(ckch->ocsp_issuer);
654 ckch->ocsp_issuer = NULL;
655}
656
657/*
658 *
659 * This function copy a cert_key_and_chain in memory
660 *
661 * It's used to try to apply changes on a ckch before committing them, because
662 * most of the time it's not possible to revert those changes
663 *
664 * Return a the dst or NULL
665 */
666struct cert_key_and_chain *ssl_sock_copy_cert_key_and_chain(struct cert_key_and_chain *src,
667 struct cert_key_and_chain *dst)
668{
William Lallemand6c096142021-02-23 14:45:45 +0100669 if (!src || !dst)
670 return NULL;
671
William Lallemand03c331c2020-05-13 10:10:01 +0200672 if (src->cert) {
673 dst->cert = src->cert;
674 X509_up_ref(src->cert);
675 }
676
677 if (src->key) {
678 dst->key = src->key;
679 EVP_PKEY_up_ref(src->key);
680 }
681
682 if (src->chain) {
683 dst->chain = X509_chain_up_ref(src->chain);
684 }
685
686 if (src->dh) {
687 DH_up_ref(src->dh);
688 dst->dh = src->dh;
689 }
690
691 if (src->sctl) {
692 struct buffer *sctl;
693
694 sctl = calloc(1, sizeof(*sctl));
695 if (!chunk_dup(sctl, src->sctl)) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100696 ha_free(&sctl);
William Lallemand03c331c2020-05-13 10:10:01 +0200697 goto error;
698 }
699 dst->sctl = sctl;
700 }
701
702 if (src->ocsp_response) {
703 struct buffer *ocsp_response;
704
705 ocsp_response = calloc(1, sizeof(*ocsp_response));
706 if (!chunk_dup(ocsp_response, src->ocsp_response)) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100707 ha_free(&ocsp_response);
William Lallemand03c331c2020-05-13 10:10:01 +0200708 goto error;
709 }
710 dst->ocsp_response = ocsp_response;
711 }
712
713 if (src->ocsp_issuer) {
714 X509_up_ref(src->ocsp_issuer);
715 dst->ocsp_issuer = src->ocsp_issuer;
716 }
717
718 return dst;
719
720error:
721
722 /* free everything */
723 ssl_sock_free_cert_key_and_chain_contents(dst);
724
725 return NULL;
726}
727
728/*
729 * return 0 on success or != 0 on failure
730 */
731int ssl_sock_load_issuer_file_into_ckch(const char *path, char *buf, struct cert_key_and_chain *ckch, char **err)
732{
733 int ret = 1;
734 BIO *in = NULL;
735 X509 *issuer;
736
737 if (buf) {
738 /* reading from a buffer */
739 in = BIO_new_mem_buf(buf, -1);
740 if (in == NULL) {
741 memprintf(err, "%sCan't allocate memory\n", err && *err ? *err : "");
742 goto end;
743 }
744
745 } else {
746 /* reading from a file */
747 in = BIO_new(BIO_s_file());
748 if (in == NULL)
749 goto end;
750
751 if (BIO_read_filename(in, path) <= 0)
752 goto end;
753 }
754
755 issuer = PEM_read_bio_X509_AUX(in, NULL, NULL, NULL);
756 if (!issuer) {
757 memprintf(err, "%s'%s' cannot be read or parsed'.\n",
758 err && *err ? *err : "", path);
759 goto end;
760 }
761 /* no error, fill ckch with new context, old context must be free */
762 if (ckch->ocsp_issuer)
763 X509_free(ckch->ocsp_issuer);
764 ckch->ocsp_issuer = issuer;
765 ret = 0;
766
767end:
768
769 ERR_clear_error();
770 if (in)
771 BIO_free(in);
772
773 return ret;
774}
775
776/******************** ckch_store functions ***********************************
777 * The ckch_store is a structure used to cache and index the SSL files used in
778 * configuration
779 */
780
781/*
782 * Free a ckch_store, its ckch, its instances and remove it from the ebtree
783 */
784void ckch_store_free(struct ckch_store *store)
785{
786 struct ckch_inst *inst, *inst_s;
787
788 if (!store)
789 return;
790
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200791 ssl_sock_free_cert_key_and_chain_contents(store->ckch);
William Lallemand03c331c2020-05-13 10:10:01 +0200792
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100793 ha_free(&store->ckch);
William Lallemand03c331c2020-05-13 10:10:01 +0200794
795 list_for_each_entry_safe(inst, inst_s, &store->ckch_inst, by_ckchs) {
796 ckch_inst_free(inst);
797 }
798 ebmb_delete(&store->node);
799 free(store);
800}
801
802/*
803 * create and initialize a ckch_store
804 * <path> is the key name
805 * <nmemb> is the number of store->ckch objects to allocate
806 *
807 * Return a ckch_store or NULL upon failure.
808 */
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200809struct ckch_store *ckch_store_new(const char *filename)
William Lallemand03c331c2020-05-13 10:10:01 +0200810{
811 struct ckch_store *store;
812 int pathlen;
813
814 pathlen = strlen(filename);
815 store = calloc(1, sizeof(*store) + pathlen + 1);
816 if (!store)
817 return NULL;
818
William Lallemand03c331c2020-05-13 10:10:01 +0200819 memcpy(store->path, filename, pathlen + 1);
820
821 LIST_INIT(&store->ckch_inst);
822 LIST_INIT(&store->crtlist_entry);
823
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200824 store->ckch = calloc(1, sizeof(*store->ckch));
William Lallemand03c331c2020-05-13 10:10:01 +0200825 if (!store->ckch)
826 goto error;
827
828 return store;
829error:
830 ckch_store_free(store);
831 return NULL;
832}
833
834/* allocate and duplicate a ckch_store
835 * Return a new ckch_store or NULL */
836struct ckch_store *ckchs_dup(const struct ckch_store *src)
837{
838 struct ckch_store *dst;
839
William Lallemand6c096142021-02-23 14:45:45 +0100840 if (!src)
841 return NULL;
842
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200843 dst = ckch_store_new(src->path);
Eric Salama6ac61e32021-02-23 16:50:57 +0100844 if (!dst)
845 return NULL;
William Lallemand03c331c2020-05-13 10:10:01 +0200846
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200847 if (!ssl_sock_copy_cert_key_and_chain(src->ckch, dst->ckch))
848 goto error;
William Lallemand03c331c2020-05-13 10:10:01 +0200849
850 return dst;
851
852error:
853 ckch_store_free(dst);
854
855 return NULL;
856}
857
858/*
859 * lookup a path into the ckchs tree.
860 */
861struct ckch_store *ckchs_lookup(char *path)
862{
863 struct ebmb_node *eb;
864
865 eb = ebst_lookup(&ckchs_tree, path);
866 if (!eb)
867 return NULL;
868
869 return ebmb_entry(eb, struct ckch_store, node);
870}
871
872/*
873 * This function allocate a ckch_store and populate it with certificates from files.
874 */
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200875struct ckch_store *ckchs_load_cert_file(char *path, char **err)
William Lallemand03c331c2020-05-13 10:10:01 +0200876{
877 struct ckch_store *ckchs;
878
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200879 ckchs = ckch_store_new(path);
William Lallemand03c331c2020-05-13 10:10:01 +0200880 if (!ckchs) {
881 memprintf(err, "%sunable to allocate memory.\n", err && *err ? *err : "");
882 goto end;
883 }
William Lallemand03c331c2020-05-13 10:10:01 +0200884
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200885 if (ssl_sock_load_files_into_ckch(path, ckchs->ckch, err) == 1)
886 goto end;
William Lallemand03c331c2020-05-13 10:10:01 +0200887
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200888 /* insert into the ckchs tree */
889 memcpy(ckchs->path, path, strlen(path) + 1);
890 ebst_insert(&ckchs_tree, &ckchs->node);
William Lallemand03c331c2020-05-13 10:10:01 +0200891 return ckchs;
892
893end:
894 ckch_store_free(ckchs);
895
896 return NULL;
897}
898
William Lallemandfa1d8b42020-05-13 15:46:10 +0200899
900/******************** ckch_inst functions ******************************/
901
902/* unlink a ckch_inst, free all SNIs, free the ckch_inst */
903/* The caller must use the lock of the bind_conf if used with inserted SNIs */
904void ckch_inst_free(struct ckch_inst *inst)
905{
906 struct sni_ctx *sni, *sni_s;
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +0100907 struct ckch_inst_link_ref *link_ref, *link_ref_s;
William Lallemandfa1d8b42020-05-13 15:46:10 +0200908
909 if (inst == NULL)
910 return;
911
912 list_for_each_entry_safe(sni, sni_s, &inst->sni_ctx, by_ckch_inst) {
913 SSL_CTX_free(sni->ctx);
Willy Tarreau2b718102021-04-21 07:32:39 +0200914 LIST_DELETE(&sni->by_ckch_inst);
William Lallemandfa1d8b42020-05-13 15:46:10 +0200915 ebmb_delete(&sni->name);
916 free(sni);
917 }
Remi Tricot-Le Bretonf3eedfe2021-01-25 17:19:44 +0100918 SSL_CTX_free(inst->ctx);
919 inst->ctx = NULL;
Willy Tarreau2b718102021-04-21 07:32:39 +0200920 LIST_DELETE(&inst->by_ckchs);
921 LIST_DELETE(&inst->by_crtlist_entry);
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +0100922
923 list_for_each_entry_safe(link_ref, link_ref_s, &inst->cafile_link_refs, list) {
924 LIST_DELETE(&link_ref->link->list);
925 LIST_DELETE(&link_ref->list);
926 free(link_ref);
927 }
928
William Lallemandfa1d8b42020-05-13 15:46:10 +0200929 free(inst);
930}
931
932/* Alloc and init a ckch_inst */
933struct ckch_inst *ckch_inst_new()
934{
935 struct ckch_inst *ckch_inst;
936
937 ckch_inst = calloc(1, sizeof *ckch_inst);
938 if (!ckch_inst)
939 return NULL;
940
941 LIST_INIT(&ckch_inst->sni_ctx);
942 LIST_INIT(&ckch_inst->by_ckchs);
943 LIST_INIT(&ckch_inst->by_crtlist_entry);
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +0100944 LIST_INIT(&ckch_inst->cafile_link_refs);
William Lallemandfa1d8b42020-05-13 15:46:10 +0200945
946 return ckch_inst;
947}
948
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +0200949
950/******************** ssl_store functions ******************************/
Remi Tricot-Le Breton9f0c9362021-02-19 15:06:28 +0100951struct eb_root cafile_tree = EB_ROOT;
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +0200952
Remi Tricot-Le Breton9f0c9362021-02-19 15:06:28 +0100953/*
954 * Returns the cafile_entry found in the cafile_tree indexed by the path 'path'.
955 * If 'oldest_entry' is 1, returns the "original" cafile_entry (since
956 * during a set cafile/commit cafile cycle there might be two entries for any
957 * given path, the original one and the new one set via the CLI but not
958 * committed yet).
959 */
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +0100960struct cafile_entry *ssl_store_get_cafile_entry(char *path, int oldest_entry)
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +0200961{
Remi Tricot-Le Breton9f0c9362021-02-19 15:06:28 +0100962 struct cafile_entry *ca_e = NULL;
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +0200963 struct ebmb_node *eb;
964
965 eb = ebst_lookup(&cafile_tree, path);
Remi Tricot-Le Breton9f0c9362021-02-19 15:06:28 +0100966 while (eb) {
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +0200967 ca_e = ebmb_entry(eb, struct cafile_entry, node);
Remi Tricot-Le Breton9f0c9362021-02-19 15:06:28 +0100968 /* The ebst_lookup in a tree that has duplicates returns the
969 * oldest entry first. If we want the latest entry, we need to
970 * iterate over all the duplicates until we find the last one
971 * (in our case there should never be more than two entries for
972 * any given path). */
973 if (oldest_entry)
974 return ca_e;
975 eb = ebmb_next_dup(eb);
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +0200976 }
Remi Tricot-Le Breton9f0c9362021-02-19 15:06:28 +0100977 return ca_e;
978}
979
Remi Tricot-Le Breton38c999b2021-02-23 16:28:43 +0100980int ssl_store_add_uncommitted_cafile_entry(struct cafile_entry *entry)
981{
982 return (ebst_insert(&cafile_tree, &entry->node) != &entry->node);
983}
984
Remi Tricot-Le Breton9f0c9362021-02-19 15:06:28 +0100985X509_STORE* ssl_store_get0_locations_file(char *path)
986{
987 struct cafile_entry *ca_e = ssl_store_get_cafile_entry(path, 0);
988
989 if (ca_e)
990 return ca_e->ca_store;
991
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +0200992 return NULL;
993}
994
Remi Tricot-Le Breton5daff3c2021-02-22 15:54:55 +0100995/* Create a cafile_entry object, without adding it to the cafile_tree. */
Remi Tricot-Le Breton0bb48242021-04-16 17:59:23 +0200996struct 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 +0100997{
998 struct cafile_entry *ca_e;
999 int pathlen;
1000
1001 pathlen = strlen(path);
1002
1003 ca_e = calloc(1, sizeof(*ca_e) + pathlen + 1);
1004 if (ca_e) {
1005 memcpy(ca_e->path, path, pathlen + 1);
1006 ca_e->ca_store = store;
Remi Tricot-Le Breton0bb48242021-04-16 17:59:23 +02001007 ca_e->type = type;
Remi Tricot-Le Breton5daff3c2021-02-22 15:54:55 +01001008 LIST_INIT(&ca_e->ckch_inst_link);
1009 }
1010 return ca_e;
1011}
1012
1013/* Delete a cafile_entry. The caller is responsible from removing this entry
1014 * from the cafile_tree first if is was previously added into it. */
1015void ssl_store_delete_cafile_entry(struct cafile_entry *ca_e)
1016{
1017 struct ckch_inst_link *link, *link_s;
1018 if (!ca_e)
1019 return;
1020
1021 X509_STORE_free(ca_e->ca_store);
1022
1023 list_for_each_entry_safe(link, link_s, &ca_e->ckch_inst_link, list) {
1024 struct ckch_inst *inst = link->ckch_inst;
1025 struct ckch_inst_link_ref *link_ref, *link_ref_s;
1026 list_for_each_entry_safe(link_ref, link_ref_s, &inst->cafile_link_refs, list) {
1027 if (link_ref->link == link) {
1028 LIST_DELETE(&link_ref->list);
1029 free(link_ref);
1030 break;
1031 }
1032 }
1033 LIST_DELETE(&link->list);
1034 free(link);
1035 }
1036
1037 free(ca_e);
1038}
1039
Remi Tricot-Le Breton383fb142021-02-22 18:26:14 +01001040/*
1041 * Build a cafile_entry out of a buffer instead of out of a file.
1042 * This function is used when the "commit ssl ca-file" cli command is used.
1043 * It can parse CERTIFICATE sections as well as CRL ones.
1044 * Returns 0 in case of success, 1 otherwise.
1045 */
1046int ssl_store_load_ca_from_buf(struct cafile_entry *ca_e, char *cert_buf)
1047{
1048 int retval = 0;
1049
1050 if (!ca_e)
1051 return 1;
1052
1053 if (!ca_e->ca_store) {
1054 ca_e->ca_store = X509_STORE_new();
1055 if (ca_e->ca_store) {
1056 BIO *bio = BIO_new_mem_buf(cert_buf, strlen(cert_buf));
1057 if (bio) {
1058 X509_INFO *info;
1059 int i;
1060 STACK_OF(X509_INFO) *infos = PEM_X509_INFO_read_bio(bio, NULL, NULL, NULL);
1061 if (!infos)
1062 {
1063 BIO_free(bio);
1064 return 1;
1065 }
1066
1067 for (i = 0; i < sk_X509_INFO_num(infos) && !retval; i++) {
1068 info = sk_X509_INFO_value(infos, i);
1069 /* X509_STORE_add_cert and X509_STORE_add_crl return 1 on success */
1070 if (info->x509) {
1071 retval = !X509_STORE_add_cert(ca_e->ca_store, info->x509);
1072 }
1073 if (!retval && info->crl) {
1074 retval = !X509_STORE_add_crl(ca_e->ca_store, info->crl);
1075 }
1076 }
1077 retval = retval || (i != sk_X509_INFO_num(infos));
1078
1079 /* Cleanup */
1080 sk_X509_INFO_pop_free(infos, X509_INFO_free);
1081 BIO_free(bio);
1082 }
1083 }
1084 }
1085
1086 return retval;
1087}
1088
Remi Tricot-Le Breton0bb48242021-04-16 17:59:23 +02001089int ssl_store_load_locations_file(char *path, int create_if_none, enum cafile_type type)
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001090{
1091 X509_STORE *store = ssl_store_get0_locations_file(path);
1092
1093 /* If this function is called by the CLI, we should not call the
1094 * X509_STORE_load_locations function because it performs forbidden disk
1095 * accesses. */
1096 if (!store && create_if_none) {
1097 struct cafile_entry *ca_e;
1098 store = X509_STORE_new();
1099 if (X509_STORE_load_locations(store, path, NULL)) {
Remi Tricot-Le Breton0bb48242021-04-16 17:59:23 +02001100 ca_e = ssl_store_create_cafile_entry(path, store, type);
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001101 if (ca_e) {
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001102 ebst_insert(&cafile_tree, &ca_e->node);
1103 }
1104 } else {
1105 X509_STORE_free(store);
1106 store = NULL;
1107 }
1108 }
1109 return (store != NULL);
1110}
1111
1112
William Lallemandda8584c2020-05-14 10:14:37 +02001113/*************************** CLI commands ***********************/
1114
1115/* Type of SSL payloads that can be updated over the CLI */
1116
1117enum {
1118 CERT_TYPE_PEM = 0,
1119 CERT_TYPE_KEY,
1120#if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) || defined OPENSSL_IS_BORINGSSL)
1121 CERT_TYPE_OCSP,
1122#endif
1123 CERT_TYPE_ISSUER,
Ilya Shipitsinc47d6762021-02-13 11:45:33 +05001124#ifdef HAVE_SSL_SCTL
William Lallemandda8584c2020-05-14 10:14:37 +02001125 CERT_TYPE_SCTL,
1126#endif
1127 CERT_TYPE_MAX,
1128};
1129
1130struct {
1131 const char *ext;
1132 int type;
1133 int (*load)(const char *path, char *payload, struct cert_key_and_chain *ckch, char **err);
1134 /* add a parsing callback */
1135} cert_exts[CERT_TYPE_MAX+1] = {
1136 [CERT_TYPE_PEM] = { "", CERT_TYPE_PEM, &ssl_sock_load_pem_into_ckch }, /* default mode, no extensions */
1137 [CERT_TYPE_KEY] = { "key", CERT_TYPE_KEY, &ssl_sock_load_key_into_ckch },
1138#if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) || defined OPENSSL_IS_BORINGSSL)
1139 [CERT_TYPE_OCSP] = { "ocsp", CERT_TYPE_OCSP, &ssl_sock_load_ocsp_response_from_file },
1140#endif
Ilya Shipitsinc47d6762021-02-13 11:45:33 +05001141#ifdef HAVE_SSL_SCTL
William Lallemandda8584c2020-05-14 10:14:37 +02001142 [CERT_TYPE_SCTL] = { "sctl", CERT_TYPE_SCTL, &ssl_sock_load_sctl_from_file },
1143#endif
1144 [CERT_TYPE_ISSUER] = { "issuer", CERT_TYPE_ISSUER, &ssl_sock_load_issuer_file_into_ckch },
1145 [CERT_TYPE_MAX] = { NULL, CERT_TYPE_MAX, NULL },
1146};
1147
1148
1149/* release function of the `show ssl cert' command */
1150static void cli_release_show_cert(struct appctx *appctx)
1151{
1152 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1153}
1154
1155/* IO handler of "show ssl cert <filename>" */
1156static int cli_io_handler_show_cert(struct appctx *appctx)
1157{
1158 struct buffer *trash = alloc_trash_chunk();
1159 struct ebmb_node *node;
1160 struct stream_interface *si = appctx->owner;
1161 struct ckch_store *ckchs;
1162
1163 if (trash == NULL)
1164 return 1;
1165
1166 if (!appctx->ctx.ssl.old_ckchs) {
1167 if (ckchs_transaction.old_ckchs) {
1168 ckchs = ckchs_transaction.old_ckchs;
1169 chunk_appendf(trash, "# transaction\n");
William Lallemand5685ccf2020-09-16 16:12:25 +02001170 chunk_appendf(trash, "*%s\n", ckchs->path);
William Lallemandda8584c2020-05-14 10:14:37 +02001171 }
1172 }
1173
1174 if (!appctx->ctx.cli.p0) {
1175 chunk_appendf(trash, "# filename\n");
1176 node = ebmb_first(&ckchs_tree);
1177 } else {
1178 node = &((struct ckch_store *)appctx->ctx.cli.p0)->node;
1179 }
1180 while (node) {
1181 ckchs = ebmb_entry(node, struct ckch_store, node);
William Lallemand5685ccf2020-09-16 16:12:25 +02001182 chunk_appendf(trash, "%s\n", ckchs->path);
William Lallemandda8584c2020-05-14 10:14:37 +02001183
1184 node = ebmb_next(node);
1185 if (ci_putchk(si_ic(si), trash) == -1) {
1186 si_rx_room_blk(si);
1187 goto yield;
1188 }
1189 }
1190
1191 appctx->ctx.cli.p0 = NULL;
1192 free_trash_chunk(trash);
1193 return 1;
1194yield:
1195
1196 free_trash_chunk(trash);
1197 appctx->ctx.cli.p0 = ckchs;
1198 return 0; /* should come back */
1199}
1200
1201/*
1202 * Extract and format the DNS SAN extensions and copy result into a chuink
1203 * Return 0;
1204 */
1205#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
1206static int ssl_sock_get_san_oneline(X509 *cert, struct buffer *out)
1207{
1208 int i;
1209 char *str;
1210 STACK_OF(GENERAL_NAME) *names = NULL;
1211
1212 names = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
1213 if (names) {
1214 for (i = 0; i < sk_GENERAL_NAME_num(names); i++) {
1215 GENERAL_NAME *name = sk_GENERAL_NAME_value(names, i);
1216 if (i > 0)
1217 chunk_appendf(out, ", ");
1218 if (name->type == GEN_DNS) {
1219 if (ASN1_STRING_to_UTF8((unsigned char **)&str, name->d.dNSName) >= 0) {
1220 chunk_appendf(out, "DNS:%s", str);
1221 OPENSSL_free(str);
1222 }
1223 }
1224 }
1225 sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free);
1226 }
1227 return 0;
1228}
1229#endif
1230
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001231/*
1232 * Build the ckch_inst_link that will be chained in the CA file entry and the
1233 * corresponding ckch_inst_link_ref that will be chained in the ckch instance.
1234 * Return 0 in case of success.
1235 */
1236static int do_chain_inst_and_cafile(struct cafile_entry *cafile_entry, struct ckch_inst *ckch_inst)
1237{
1238 struct ckch_inst_link *new_link;
1239 if (!LIST_ISEMPTY(&cafile_entry->ckch_inst_link)) {
1240 struct ckch_inst_link *link = LIST_ELEM(cafile_entry->ckch_inst_link.n,
1241 typeof(link), list);
1242 /* Do not add multiple references to the same
1243 * instance in a cafile_entry */
1244 if (link->ckch_inst == ckch_inst) {
1245 return 1;
1246 }
1247 }
1248
1249 new_link = calloc(1, sizeof(*new_link));
1250 if (new_link) {
1251 struct ckch_inst_link_ref *new_link_ref = calloc(1, sizeof(*new_link_ref));
1252 if (!new_link_ref) {
1253 free(new_link);
1254 return 1;
1255 }
1256
1257 new_link->ckch_inst = ckch_inst;
1258 new_link_ref->link = new_link;
1259 LIST_INIT(&new_link->list);
1260 LIST_INIT(&new_link_ref->list);
1261
1262 LIST_APPEND(&cafile_entry->ckch_inst_link, &new_link->list);
1263 LIST_APPEND(&ckch_inst->cafile_link_refs, &new_link_ref->list);
1264 }
1265
1266 return 0;
1267}
1268
1269
1270/*
1271 * Link a CA file tree entry to the ckch instance that uses it.
1272 * To determine if and which CA file tree entries need to be linked to the
1273 * instance, we follow the same logic performed in ssl_sock_prepare_ctx when
1274 * processing the verify option.
1275 * This function works for a frontend as well as for a backend, depending on the
1276 * configuration parameters given (bind_conf or server).
1277 */
1278void ckch_inst_add_cafile_link(struct ckch_inst *ckch_inst, struct bind_conf *bind_conf,
1279 struct ssl_bind_conf *ssl_conf, const struct server *srv)
1280{
1281 int verify = SSL_VERIFY_NONE;
1282
1283 if (srv) {
1284
1285 if (global.ssl_server_verify == SSL_SERVER_VERIFY_REQUIRED)
1286 verify = SSL_VERIFY_PEER;
1287 switch (srv->ssl_ctx.verify) {
1288 case SSL_SOCK_VERIFY_NONE:
1289 verify = SSL_VERIFY_NONE;
1290 break;
1291 case SSL_SOCK_VERIFY_REQUIRED:
1292 verify = SSL_VERIFY_PEER;
1293 break;
1294 }
1295 }
1296 else {
1297 switch ((ssl_conf && ssl_conf->verify) ? ssl_conf->verify : bind_conf->ssl_conf.verify) {
1298 case SSL_SOCK_VERIFY_NONE:
1299 verify = SSL_VERIFY_NONE;
1300 break;
1301 case SSL_SOCK_VERIFY_OPTIONAL:
1302 verify = SSL_VERIFY_PEER;
1303 break;
1304 case SSL_SOCK_VERIFY_REQUIRED:
1305 verify = SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
1306 break;
1307 }
1308 }
1309
1310 if (verify & SSL_VERIFY_PEER) {
1311 struct cafile_entry *ca_file_entry = NULL;
1312 struct cafile_entry *ca_verify_file_entry = NULL;
Remi Tricot-Le Bretonf81c70c2021-04-20 16:54:21 +02001313 struct cafile_entry *crl_file_entry = NULL;
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001314 if (srv) {
1315 if (srv->ssl_ctx.ca_file) {
1316 ca_file_entry = ssl_store_get_cafile_entry(srv->ssl_ctx.ca_file, 0);
1317
1318 }
Remi Tricot-Le Bretonf81c70c2021-04-20 16:54:21 +02001319 if (srv->ssl_ctx.crl_file) {
1320 crl_file_entry = ssl_store_get_cafile_entry(srv->ssl_ctx.crl_file, 0);
1321 }
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001322 }
1323 else {
1324 char *ca_file = (ssl_conf && ssl_conf->ca_file) ? ssl_conf->ca_file : bind_conf->ssl_conf.ca_file;
1325 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 +02001326 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 +01001327
1328 if (ca_file)
1329 ca_file_entry = ssl_store_get_cafile_entry(ca_file, 0);
1330 if (ca_verify_file)
1331 ca_verify_file_entry = ssl_store_get_cafile_entry(ca_verify_file, 0);
Remi Tricot-Le Bretonf81c70c2021-04-20 16:54:21 +02001332 if (crl_file)
1333 crl_file_entry = ssl_store_get_cafile_entry(crl_file, 0);
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001334 }
1335
1336 if (ca_file_entry) {
1337 /* If we have a ckch instance that is not already in the
1338 * cafile_entry's list, add it to it. */
1339 if (do_chain_inst_and_cafile(ca_file_entry, ckch_inst))
1340 return;
1341
1342 }
1343 if (ca_verify_file_entry && (ca_file_entry != ca_verify_file_entry)) {
1344 /* If we have a ckch instance that is not already in the
1345 * cafile_entry's list, add it to it. */
1346 if (do_chain_inst_and_cafile(ca_verify_file_entry, ckch_inst))
1347 return;
1348 }
Remi Tricot-Le Bretonf81c70c2021-04-20 16:54:21 +02001349 if (crl_file_entry) {
1350 /* If we have a ckch instance that is not already in the
1351 * cafile_entry's list, add it to it. */
1352 if (do_chain_inst_and_cafile(crl_file_entry, ckch_inst))
1353 return;
1354 }
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001355 }
1356}
1357
William Lallemandda8584c2020-05-14 10:14:37 +02001358
1359
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001360static int show_cert_detail(X509 *cert, STACK_OF(X509) *chain, struct buffer *out)
William Lallemandda8584c2020-05-14 10:14:37 +02001361{
William Lallemandda8584c2020-05-14 10:14:37 +02001362 BIO *bio = NULL;
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001363 struct buffer *tmp = alloc_trash_chunk();
William Lallemandda8584c2020-05-14 10:14:37 +02001364 int i;
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001365 int write = -1;
1366 unsigned int len = 0;
1367 X509_NAME *name = NULL;
William Lallemandda8584c2020-05-14 10:14:37 +02001368
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001369 if (!tmp)
1370 return -1;
William Lallemandda8584c2020-05-14 10:14:37 +02001371
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001372 if (!cert)
William Lallemand5685ccf2020-09-16 16:12:25 +02001373 goto end;
William Lallemandda8584c2020-05-14 10:14:37 +02001374
William Lallemand5685ccf2020-09-16 16:12:25 +02001375 if (chain == NULL) {
1376 struct issuer_chain *issuer;
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001377 issuer = ssl_get0_issuer_chain(cert);
William Lallemand5685ccf2020-09-16 16:12:25 +02001378 if (issuer) {
1379 chain = issuer->chain;
1380 chunk_appendf(out, "Chain Filename: ");
1381 chunk_appendf(out, "%s\n", issuer->path);
William Lallemandda8584c2020-05-14 10:14:37 +02001382 }
William Lallemand5685ccf2020-09-16 16:12:25 +02001383 }
1384 chunk_appendf(out, "Serial: ");
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001385 if (ssl_sock_get_serial(cert, tmp) == -1)
William Lallemand5685ccf2020-09-16 16:12:25 +02001386 goto end;
1387 dump_binary(out, tmp->area, tmp->data);
1388 chunk_appendf(out, "\n");
William Lallemandda8584c2020-05-14 10:14:37 +02001389
William Lallemand5685ccf2020-09-16 16:12:25 +02001390 chunk_appendf(out, "notBefore: ");
1391 chunk_reset(tmp);
1392 if ((bio = BIO_new(BIO_s_mem())) == NULL)
1393 goto end;
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001394 if (ASN1_TIME_print(bio, X509_getm_notBefore(cert)) == 0)
William Lallemand5685ccf2020-09-16 16:12:25 +02001395 goto end;
1396 write = BIO_read(bio, tmp->area, tmp->size-1);
1397 tmp->area[write] = '\0';
1398 BIO_free(bio);
1399 bio = NULL;
1400 chunk_appendf(out, "%s\n", tmp->area);
William Lallemandda8584c2020-05-14 10:14:37 +02001401
William Lallemand5685ccf2020-09-16 16:12:25 +02001402 chunk_appendf(out, "notAfter: ");
1403 chunk_reset(tmp);
1404 if ((bio = BIO_new(BIO_s_mem())) == NULL)
1405 goto end;
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001406 if (ASN1_TIME_print(bio, X509_getm_notAfter(cert)) == 0)
William Lallemand5685ccf2020-09-16 16:12:25 +02001407 goto end;
1408 if ((write = BIO_read(bio, tmp->area, tmp->size-1)) <= 0)
1409 goto end;
1410 tmp->area[write] = '\0';
1411 BIO_free(bio);
1412 bio = NULL;
1413 chunk_appendf(out, "%s\n", tmp->area);
William Lallemandda8584c2020-05-14 10:14:37 +02001414
1415#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
William Lallemand5685ccf2020-09-16 16:12:25 +02001416 chunk_appendf(out, "Subject Alternative Name: ");
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001417 if (ssl_sock_get_san_oneline(cert, out) == -1)
William Lallemand5685ccf2020-09-16 16:12:25 +02001418 goto end;
1419 *(out->area + out->data) = '\0';
1420 chunk_appendf(out, "\n");
William Lallemandda8584c2020-05-14 10:14:37 +02001421#endif
William Lallemand5685ccf2020-09-16 16:12:25 +02001422 chunk_reset(tmp);
1423 chunk_appendf(out, "Algorithm: ");
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001424 if (cert_get_pkey_algo(cert, tmp) == 0)
William Lallemand5685ccf2020-09-16 16:12:25 +02001425 goto end;
1426 chunk_appendf(out, "%s\n", tmp->area);
William Lallemandda8584c2020-05-14 10:14:37 +02001427
William Lallemand5685ccf2020-09-16 16:12:25 +02001428 chunk_reset(tmp);
1429 chunk_appendf(out, "SHA1 FingerPrint: ");
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001430 if (X509_digest(cert, EVP_sha1(), (unsigned char *) tmp->area, &len) == 0)
William Lallemand5685ccf2020-09-16 16:12:25 +02001431 goto end;
1432 tmp->data = len;
1433 dump_binary(out, tmp->area, tmp->data);
1434 chunk_appendf(out, "\n");
William Lallemandda8584c2020-05-14 10:14:37 +02001435
William Lallemand5685ccf2020-09-16 16:12:25 +02001436 chunk_appendf(out, "Subject: ");
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001437 if ((name = X509_get_subject_name(cert)) == NULL)
William Lallemand5685ccf2020-09-16 16:12:25 +02001438 goto end;
1439 if ((ssl_sock_get_dn_oneline(name, tmp)) == -1)
1440 goto end;
1441 *(tmp->area + tmp->data) = '\0';
1442 chunk_appendf(out, "%s\n", tmp->area);
1443
1444 chunk_appendf(out, "Issuer: ");
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001445 if ((name = X509_get_issuer_name(cert)) == NULL)
William Lallemand5685ccf2020-09-16 16:12:25 +02001446 goto end;
1447 if ((ssl_sock_get_dn_oneline(name, tmp)) == -1)
1448 goto end;
1449 *(tmp->area + tmp->data) = '\0';
1450 chunk_appendf(out, "%s\n", tmp->area);
1451
1452 /* Displays subject of each certificate in the chain */
1453 for (i = 0; i < sk_X509_num(chain); i++) {
1454 X509 *ca = sk_X509_value(chain, i);
1455
1456 chunk_appendf(out, "Chain Subject: ");
1457 if ((name = X509_get_subject_name(ca)) == NULL)
William Lallemandda8584c2020-05-14 10:14:37 +02001458 goto end;
1459 if ((ssl_sock_get_dn_oneline(name, tmp)) == -1)
1460 goto end;
1461 *(tmp->area + tmp->data) = '\0';
1462 chunk_appendf(out, "%s\n", tmp->area);
1463
William Lallemand5685ccf2020-09-16 16:12:25 +02001464 chunk_appendf(out, "Chain Issuer: ");
1465 if ((name = X509_get_issuer_name(ca)) == NULL)
William Lallemandda8584c2020-05-14 10:14:37 +02001466 goto end;
1467 if ((ssl_sock_get_dn_oneline(name, tmp)) == -1)
1468 goto end;
1469 *(tmp->area + tmp->data) = '\0';
1470 chunk_appendf(out, "%s\n", tmp->area);
William Lallemandda8584c2020-05-14 10:14:37 +02001471 }
1472
1473end:
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001474 if (bio)
1475 BIO_free(bio);
1476 free_trash_chunk(tmp);
1477
1478 return 0;
1479}
1480
Remi Tricot-Le Breton3faf0cb2021-06-10 18:10:32 +02001481#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 +02001482/*
1483 * Build the OCSP tree entry's key for a given ckch_store.
1484 * Returns a negative value in case of error.
1485 */
1486static int ckch_store_build_certid(struct ckch_store *ckch_store, unsigned char certid[128], unsigned int *key_length)
1487{
1488 OCSP_RESPONSE *resp;
1489 OCSP_BASICRESP *bs = NULL;
1490 OCSP_SINGLERESP *sr;
1491 OCSP_CERTID *id;
1492 unsigned char *p = NULL;
1493
1494 if (!key_length)
1495 return -1;
1496
1497 *key_length = 0;
1498
1499 if (!ckch_store->ckch->ocsp_response)
1500 return 0;
1501
1502 p = (unsigned char *) ckch_store->ckch->ocsp_response->area;
1503
1504 resp = d2i_OCSP_RESPONSE(NULL, (const unsigned char **)&p,
1505 ckch_store->ckch->ocsp_response->data);
1506 if (!resp) {
1507 goto end;
1508 }
1509
1510 bs = OCSP_response_get1_basic(resp);
1511 if (!bs) {
1512 goto end;
1513 }
1514
1515 sr = OCSP_resp_get0(bs, 0);
1516 if (!sr) {
1517 goto end;
1518 }
1519
1520 id = (OCSP_CERTID*)OCSP_SINGLERESP_get0_id(sr);
1521
1522 p = certid;
1523 *key_length = i2d_OCSP_CERTID(id, &p);
1524
1525end:
1526 return *key_length > 0;
1527}
1528#endif
1529
1530/*
1531 * Dump the OCSP certificate key (if it exists) of certificate <ckch> into
1532 * buffer <out>.
1533 * Returns 0 in case of success.
1534 */
1535static int ckch_store_show_ocsp_certid(struct ckch_store *ckch_store, struct buffer *out)
1536{
Remi Tricot-Le Breton3faf0cb2021-06-10 18:10:32 +02001537#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 +02001538 unsigned char key[OCSP_MAX_CERTID_ASN1_LENGTH] = {};
1539 unsigned int key_length = 0;
1540 int i;
1541
1542 if (ckch_store_build_certid(ckch_store, (unsigned char*)key, &key_length) >= 0) {
1543 /* Dump the CERTID info */
1544 chunk_appendf(out, "OCSP Response Key: ");
1545 for (i = 0; i < key_length; ++i) {
1546 chunk_appendf(out, "%02x", key[i]);
1547 }
1548 chunk_appendf(out, "\n");
1549 }
1550#endif
1551
1552 return 0;
1553}
1554
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001555
1556/* IO handler of the details "show ssl cert <filename>" */
1557static int cli_io_handler_show_cert_detail(struct appctx *appctx)
1558{
1559 struct stream_interface *si = appctx->owner;
1560 struct ckch_store *ckchs = appctx->ctx.cli.p0;
1561 struct buffer *out = alloc_trash_chunk();
1562 int retval = 0;
1563
1564 if (!out)
1565 goto end_no_putchk;
1566
1567 chunk_appendf(out, "Filename: ");
1568 if (ckchs == ckchs_transaction.new_ckchs)
1569 chunk_appendf(out, "*");
1570 chunk_appendf(out, "%s\n", ckchs->path);
1571
1572 chunk_appendf(out, "Status: ");
1573 if (ckchs->ckch->cert == NULL)
1574 chunk_appendf(out, "Empty\n");
1575 else if (LIST_ISEMPTY(&ckchs->ckch_inst))
1576 chunk_appendf(out, "Unused\n");
1577 else
1578 chunk_appendf(out, "Used\n");
1579
1580 retval = show_cert_detail(ckchs->ckch->cert, ckchs->ckch->chain, out);
1581 if (retval < 0)
1582 goto end_no_putchk;
1583 else if (retval)
1584 goto end;
1585
Remi Tricot-Le Bretonda968f62021-06-10 13:51:14 +02001586 ckch_store_show_ocsp_certid(ckchs, out);
1587
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001588end:
William Lallemandda8584c2020-05-14 10:14:37 +02001589 if (ci_putchk(si_ic(si), out) == -1) {
1590 si_rx_room_blk(si);
1591 goto yield;
1592 }
1593
1594end_no_putchk:
William Lallemandda8584c2020-05-14 10:14:37 +02001595 free_trash_chunk(out);
1596 return 1;
1597yield:
William Lallemandda8584c2020-05-14 10:14:37 +02001598 free_trash_chunk(out);
1599 return 0; /* should come back */
1600}
1601
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001602
1603/* IO handler of the details "show ssl cert <filename.ocsp>" */
1604static int cli_io_handler_show_cert_ocsp_detail(struct appctx *appctx)
1605{
Remi Tricot-Le Breton3faf0cb2021-06-10 18:10:32 +02001606#if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) && !defined OPENSSL_IS_BORINGSSL)
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001607 struct stream_interface *si = appctx->owner;
1608 struct ckch_store *ckchs = appctx->ctx.cli.p0;
1609 struct buffer *out = alloc_trash_chunk();
1610 int from_transaction = appctx->ctx.cli.i0;
1611
1612 if (!out)
1613 goto end_no_putchk;
1614
1615 /* If we try to display an ongoing transaction's OCSP response, we
1616 * need to dump the ckch's ocsp_response buffer directly.
1617 * Otherwise, we must rebuild the certificate's certid in order to
1618 * look for the current OCSP response in the tree. */
1619 if (from_transaction && ckchs->ckch->ocsp_response) {
1620 ssl_ocsp_response_print(ckchs->ckch->ocsp_response, out);
1621 }
1622 else {
1623 unsigned char key[OCSP_MAX_CERTID_ASN1_LENGTH] = {};
1624 unsigned int key_length = 0;
1625
1626 if (ckch_store_build_certid(ckchs, (unsigned char*)key, &key_length) < 0)
1627 goto end_no_putchk;
1628
1629 ssl_get_ocspresponse_detail(key, out);
1630 }
1631
1632 if (ci_putchk(si_ic(si), out) == -1) {
1633 si_rx_room_blk(si);
1634 goto yield;
1635 }
1636
1637end_no_putchk:
1638 free_trash_chunk(out);
1639 return 1;
1640yield:
1641 free_trash_chunk(out);
1642 return 0; /* should come back */
1643#else
1644 return cli_err(appctx, "HAProxy was compiled against a version of OpenSSL that doesn't support OCSP stapling.\n");
1645#endif
1646}
1647
William Lallemandda8584c2020-05-14 10:14:37 +02001648/* parsing function for 'show ssl cert [certfile]' */
1649static int cli_parse_show_cert(char **args, char *payload, struct appctx *appctx, void *private)
1650{
1651 struct ckch_store *ckchs;
1652
1653 if (!cli_has_level(appctx, ACCESS_LVL_OPER))
1654 return cli_err(appctx, "Can't allocate memory!\n");
1655
1656 /* The operations on the CKCH architecture are locked so we can
1657 * manipulate ckch_store and ckch_inst */
1658 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
1659 return cli_err(appctx, "Can't show!\nOperations on certificates are currently locked!\n");
1660
1661 /* check if there is a certificate to lookup */
1662 if (*args[3]) {
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001663 int show_ocsp_detail = 0;
1664 int from_transaction = 0;
1665 char *end;
1666
1667 /* We manage the special case "certname.ocsp" through which we
1668 * can show the details of an OCSP response. */
1669 end = strrchr(args[3], '.');
1670 if (end && strcmp(end+1, "ocsp") == 0) {
1671 *end = '\0';
1672 show_ocsp_detail = 1;
1673 }
1674
William Lallemandda8584c2020-05-14 10:14:37 +02001675 if (*args[3] == '*') {
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001676 from_transaction = 1;
William Lallemandda8584c2020-05-14 10:14:37 +02001677 if (!ckchs_transaction.new_ckchs)
1678 goto error;
1679
1680 ckchs = ckchs_transaction.new_ckchs;
1681
Tim Duesterhuse5ff1412021-01-02 22:31:53 +01001682 if (strcmp(args[3] + 1, ckchs->path) != 0)
William Lallemandda8584c2020-05-14 10:14:37 +02001683 goto error;
1684
1685 } else {
1686 if ((ckchs = ckchs_lookup(args[3])) == NULL)
1687 goto error;
1688
1689 }
1690
William Lallemandda8584c2020-05-14 10:14:37 +02001691 appctx->ctx.cli.p0 = ckchs;
1692 /* use the IO handler that shows details */
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001693 if (show_ocsp_detail) {
1694 appctx->ctx.cli.i0 = from_transaction;
1695 appctx->io_handler = cli_io_handler_show_cert_ocsp_detail;
1696 }
1697 else
1698 appctx->io_handler = cli_io_handler_show_cert_detail;
William Lallemandda8584c2020-05-14 10:14:37 +02001699 }
1700
1701 return 0;
1702
1703error:
1704 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1705 return cli_err(appctx, "Can't display the certificate: Not found or the certificate is a bundle!\n");
1706}
1707
1708/* release function of the `set ssl cert' command, free things and unlock the spinlock */
1709static void cli_release_commit_cert(struct appctx *appctx)
1710{
1711 struct ckch_store *new_ckchs;
1712
1713 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1714
1715 if (appctx->st2 != SETCERT_ST_FIN) {
1716 /* free every new sni_ctx and the new store, which are not in the trees so no spinlock there */
1717 new_ckchs = appctx->ctx.ssl.new_ckchs;
1718
1719 /* if the allocation failed, we need to free everything from the temporary list */
1720 ckch_store_free(new_ckchs);
1721 }
1722}
1723
Remi Tricot-Le Bretonbfadc022021-02-24 12:20:48 +01001724
1725/*
1726 * Rebuild a new instance 'new_inst' based on an old instance 'ckchi' and a
1727 * specific ckch_store.
1728 * Returns 0 in case of success, 1 otherwise.
1729 */
1730static int ckch_inst_rebuild(struct ckch_store *ckch_store, struct ckch_inst *ckchi,
1731 struct ckch_inst **new_inst, char **err)
1732{
1733 int retval = 0;
1734 int errcode = 0;
1735 struct sni_ctx *sc0, *sc0s;
1736 char **sni_filter = NULL;
1737 int fcount = 0;
1738
1739 if (ckchi->crtlist_entry) {
1740 sni_filter = ckchi->crtlist_entry->filters;
1741 fcount = ckchi->crtlist_entry->fcount;
1742 }
1743
1744 if (ckchi->is_server_instance)
1745 errcode |= ckch_inst_new_load_srv_store(ckch_store->path, ckch_store, new_inst, err);
1746 else
1747 errcode |= ckch_inst_new_load_store(ckch_store->path, ckch_store, ckchi->bind_conf, ckchi->ssl_conf, sni_filter, fcount, new_inst, err);
1748
1749 if (errcode & ERR_CODE)
1750 return 1;
1751
1752 /* if the previous ckchi was used as the default */
1753 if (ckchi->is_default)
1754 (*new_inst)->is_default = 1;
1755
1756 (*new_inst)->is_server_instance = ckchi->is_server_instance;
1757 (*new_inst)->server = ckchi->server;
1758 /* Create a new SSL_CTX and link it to the new instance. */
1759 if ((*new_inst)->is_server_instance) {
1760 retval = ssl_sock_prep_srv_ctx_and_inst(ckchi->server, (*new_inst)->ctx, (*new_inst));
1761 if (retval)
1762 return 1;
1763 }
1764
1765 /* create the link to the crtlist_entry */
1766 (*new_inst)->crtlist_entry = ckchi->crtlist_entry;
1767
1768 /* we need to initialize the SSL_CTX generated */
1769 /* this iterate on the newly generated SNIs in the new instance to prepare their SSL_CTX */
1770 list_for_each_entry_safe(sc0, sc0s, &(*new_inst)->sni_ctx, by_ckch_inst) {
1771 if (!sc0->order) { /* we initialized only the first SSL_CTX because it's the same in the other sni_ctx's */
1772 errcode |= ssl_sock_prep_ctx_and_inst(ckchi->bind_conf, ckchi->ssl_conf, sc0->ctx, *new_inst, err);
1773 if (errcode & ERR_CODE)
1774 return 1;
1775 }
1776 }
1777
1778 return 0;
1779}
1780
1781/*
1782 * Load all the new SNIs of a newly built ckch instance in the trees, or replace
1783 * a server's main ckch instance.
1784 */
1785static void __ssl_sock_load_new_ckch_instance(struct ckch_inst *ckchi)
1786{
1787 /* The bind_conf will be null on server ckch_instances. */
1788 if (ckchi->is_server_instance) {
1789 int i;
1790 /* a lock is needed here since we have to free the SSL cache */
1791 HA_RWLOCK_WRLOCK(SSL_SERVER_LOCK, &ckchi->server->ssl_ctx.lock);
1792 /* free the server current SSL_CTX */
1793 SSL_CTX_free(ckchi->server->ssl_ctx.ctx);
1794 /* Actual ssl context update */
1795 SSL_CTX_up_ref(ckchi->ctx);
1796 ckchi->server->ssl_ctx.ctx = ckchi->ctx;
1797 ckchi->server->ssl_ctx.inst = ckchi;
1798
1799 /* flush the session cache of the server */
1800 for (i = 0; i < global.nbthread; i++) {
1801 ha_free(&ckchi->server->ssl_ctx.reused_sess[i].ptr);
1802 }
1803 HA_RWLOCK_WRUNLOCK(SSL_SERVER_LOCK, &ckchi->server->ssl_ctx.lock);
1804
1805 } else {
1806 HA_RWLOCK_WRLOCK(SNI_LOCK, &ckchi->bind_conf->sni_lock);
1807 ssl_sock_load_cert_sni(ckchi, ckchi->bind_conf);
1808 HA_RWLOCK_WRUNLOCK(SNI_LOCK, &ckchi->bind_conf->sni_lock);
1809 }
1810}
1811
1812/*
1813 * Delete a ckch instance that was replaced after a CLI command.
1814 */
1815static void __ckch_inst_free_locked(struct ckch_inst *ckchi)
1816{
1817 if (ckchi->is_server_instance) {
1818 /* no lock for servers */
1819 ckch_inst_free(ckchi);
1820 } else {
1821 struct bind_conf __maybe_unused *bind_conf = ckchi->bind_conf;
1822
1823 HA_RWLOCK_WRLOCK(SNI_LOCK, &bind_conf->sni_lock);
1824 ckch_inst_free(ckchi);
1825 HA_RWLOCK_WRUNLOCK(SNI_LOCK, &bind_conf->sni_lock);
1826 }
1827}
1828
1829
William Lallemandda8584c2020-05-14 10:14:37 +02001830/*
1831 * This function tries to create the new ckch_inst and their SNIs
1832 */
1833static int cli_io_handler_commit_cert(struct appctx *appctx)
1834{
1835 struct stream_interface *si = appctx->owner;
1836 int y = 0;
1837 char *err = NULL;
William Lallemandda8584c2020-05-14 10:14:37 +02001838 struct ckch_store *old_ckchs, *new_ckchs = NULL;
1839 struct ckch_inst *ckchi, *ckchis;
1840 struct buffer *trash = alloc_trash_chunk();
William Lallemandda8584c2020-05-14 10:14:37 +02001841 struct crtlist_entry *entry;
1842
1843 if (trash == NULL)
1844 goto error;
1845
1846 if (unlikely(si_ic(si)->flags & (CF_WRITE_ERROR|CF_SHUTW)))
1847 goto error;
1848
1849 while (1) {
1850 switch (appctx->st2) {
1851 case SETCERT_ST_INIT:
1852 /* This state just print the update message */
1853 chunk_printf(trash, "Committing %s", ckchs_transaction.path);
1854 if (ci_putchk(si_ic(si), trash) == -1) {
1855 si_rx_room_blk(si);
1856 goto yield;
1857 }
1858 appctx->st2 = SETCERT_ST_GEN;
1859 /* fallthrough */
1860 case SETCERT_ST_GEN:
1861 /*
1862 * This state generates the ckch instances with their
1863 * sni_ctxs and SSL_CTX.
1864 *
1865 * Since the SSL_CTX generation can be CPU consumer, we
1866 * yield every 10 instances.
1867 */
1868
1869 old_ckchs = appctx->ctx.ssl.old_ckchs;
1870 new_ckchs = appctx->ctx.ssl.new_ckchs;
1871
1872 if (!new_ckchs)
1873 continue;
1874
1875 /* get the next ckchi to regenerate */
1876 ckchi = appctx->ctx.ssl.next_ckchi;
1877 /* we didn't start yet, set it to the first elem */
1878 if (ckchi == NULL)
1879 ckchi = LIST_ELEM(old_ckchs->ckch_inst.n, typeof(ckchi), by_ckchs);
1880
1881 /* walk through the old ckch_inst and creates new ckch_inst using the updated ckchs */
1882 list_for_each_entry_from(ckchi, &old_ckchs->ckch_inst, by_ckchs) {
1883 struct ckch_inst *new_inst;
William Lallemandda8584c2020-05-14 10:14:37 +02001884
1885 /* it takes a lot of CPU to creates SSL_CTXs, so we yield every 10 CKCH instances */
1886 if (y >= 10) {
1887 /* save the next ckchi to compute */
1888 appctx->ctx.ssl.next_ckchi = ckchi;
1889 goto yield;
1890 }
1891
Remi Tricot-Le Bretonbfadc022021-02-24 12:20:48 +01001892 if (ckch_inst_rebuild(new_ckchs, ckchi, &new_inst, &err))
William Lallemandda8584c2020-05-14 10:14:37 +02001893 goto error;
1894
William Lallemandda8584c2020-05-14 10:14:37 +02001895 /* display one dot per new instance */
1896 chunk_appendf(trash, ".");
1897 /* link the new ckch_inst to the duplicate */
Willy Tarreau2b718102021-04-21 07:32:39 +02001898 LIST_APPEND(&new_ckchs->ckch_inst, &new_inst->by_ckchs);
William Lallemandda8584c2020-05-14 10:14:37 +02001899 y++;
1900 }
1901 appctx->st2 = SETCERT_ST_INSERT;
1902 /* fallthrough */
1903 case SETCERT_ST_INSERT:
1904 /* The generation is finished, we can insert everything */
1905
1906 old_ckchs = appctx->ctx.ssl.old_ckchs;
1907 new_ckchs = appctx->ctx.ssl.new_ckchs;
1908
1909 if (!new_ckchs)
1910 continue;
1911
1912 /* get the list of crtlist_entry in the old store, and update the pointers to the store */
1913 LIST_SPLICE(&new_ckchs->crtlist_entry, &old_ckchs->crtlist_entry);
1914 list_for_each_entry(entry, &new_ckchs->crtlist_entry, by_ckch_store) {
1915 ebpt_delete(&entry->node);
1916 /* change the ptr and reinsert the node */
1917 entry->node.key = new_ckchs;
1918 ebpt_insert(&entry->crtlist->entries, &entry->node);
1919 }
1920
William Lallemanda55685b2020-12-15 14:57:46 +01001921 /* insert the new ckch_insts in the crtlist_entry */
1922 list_for_each_entry(ckchi, &new_ckchs->ckch_inst, by_ckchs) {
1923 if (ckchi->crtlist_entry)
Willy Tarreau2b718102021-04-21 07:32:39 +02001924 LIST_INSERT(&ckchi->crtlist_entry->ckch_inst, &ckchi->by_crtlist_entry);
William Lallemanda55685b2020-12-15 14:57:46 +01001925 }
1926
William Lallemandda8584c2020-05-14 10:14:37 +02001927 /* First, we insert every new SNIs in the trees, also replace the default_ctx */
1928 list_for_each_entry_safe(ckchi, ckchis, &new_ckchs->ckch_inst, by_ckchs) {
Remi Tricot-Le Bretonbfadc022021-02-24 12:20:48 +01001929 __ssl_sock_load_new_ckch_instance(ckchi);
William Lallemandda8584c2020-05-14 10:14:37 +02001930 }
1931
1932 /* delete the old sni_ctx, the old ckch_insts and the ckch_store */
1933 list_for_each_entry_safe(ckchi, ckchis, &old_ckchs->ckch_inst, by_ckchs) {
Remi Tricot-Le Bretonbfadc022021-02-24 12:20:48 +01001934 __ckch_inst_free_locked(ckchi);
William Lallemandda8584c2020-05-14 10:14:37 +02001935 }
1936
1937 /* Replace the old ckchs by the new one */
1938 ckch_store_free(old_ckchs);
1939 ebst_insert(&ckchs_tree, &new_ckchs->node);
1940 appctx->st2 = SETCERT_ST_FIN;
1941 /* fallthrough */
1942 case SETCERT_ST_FIN:
1943 /* we achieved the transaction, we can set everything to NULL */
Willy Tarreau61cfdf42021-02-20 10:46:51 +01001944 ha_free(&ckchs_transaction.path);
William Lallemandda8584c2020-05-14 10:14:37 +02001945 ckchs_transaction.new_ckchs = NULL;
1946 ckchs_transaction.old_ckchs = NULL;
1947 goto end;
1948 }
1949 }
1950end:
1951
1952 chunk_appendf(trash, "\n");
William Lallemandda8584c2020-05-14 10:14:37 +02001953 chunk_appendf(trash, "Success!\n");
1954 if (ci_putchk(si_ic(si), trash) == -1)
1955 si_rx_room_blk(si);
1956 free_trash_chunk(trash);
1957 /* success: call the release function and don't come back */
1958 return 1;
1959yield:
1960 /* store the state */
1961 if (ci_putchk(si_ic(si), trash) == -1)
1962 si_rx_room_blk(si);
1963 free_trash_chunk(trash);
1964 si_rx_endp_more(si); /* let's come back later */
1965 return 0; /* should come back */
1966
1967error:
1968 /* spin unlock and free are done in the release function */
1969 if (trash) {
1970 chunk_appendf(trash, "\n%sFailed!\n", err);
1971 if (ci_putchk(si_ic(si), trash) == -1)
1972 si_rx_room_blk(si);
1973 free_trash_chunk(trash);
1974 }
1975 /* error: call the release function and don't come back */
1976 return 1;
1977}
1978
1979/*
1980 * Parsing function of 'commit ssl cert'
1981 */
1982static int cli_parse_commit_cert(char **args, char *payload, struct appctx *appctx, void *private)
1983{
1984 char *err = NULL;
1985
1986 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
1987 return 1;
1988
1989 if (!*args[3])
1990 return cli_err(appctx, "'commit ssl cert expects a filename\n");
1991
1992 /* The operations on the CKCH architecture are locked so we can
1993 * manipulate ckch_store and ckch_inst */
1994 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
1995 return cli_err(appctx, "Can't commit the certificate!\nOperations on certificates are currently locked!\n");
1996
1997 if (!ckchs_transaction.path) {
1998 memprintf(&err, "No ongoing transaction! !\n");
1999 goto error;
2000 }
2001
2002 if (strcmp(ckchs_transaction.path, args[3]) != 0) {
2003 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", ckchs_transaction.path, args[3]);
2004 goto error;
2005 }
2006
William Lallemand5685ccf2020-09-16 16:12:25 +02002007 /* if a certificate is here, a private key must be here too */
2008 if (ckchs_transaction.new_ckchs->ckch->cert && !ckchs_transaction.new_ckchs->ckch->key) {
2009 memprintf(&err, "The transaction must contain at least a certificate and a private key!\n");
2010 goto error;
2011 }
William Lallemanda9419522020-06-24 16:26:41 +02002012
William Lallemand5685ccf2020-09-16 16:12:25 +02002013 if (!X509_check_private_key(ckchs_transaction.new_ckchs->ckch->cert, ckchs_transaction.new_ckchs->ckch->key)) {
2014 memprintf(&err, "inconsistencies between private key and certificate loaded '%s'.\n", ckchs_transaction.path);
2015 goto error;
William Lallemandda8584c2020-05-14 10:14:37 +02002016 }
2017
2018 /* init the appctx structure */
2019 appctx->st2 = SETCERT_ST_INIT;
2020 appctx->ctx.ssl.next_ckchi = NULL;
2021 appctx->ctx.ssl.new_ckchs = ckchs_transaction.new_ckchs;
2022 appctx->ctx.ssl.old_ckchs = ckchs_transaction.old_ckchs;
2023
2024 /* we don't unlock there, it will be unlock after the IO handler, in the release handler */
2025 return 0;
2026
2027error:
2028
2029 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2030 err = memprintf(&err, "%sCan't commit %s!\n", err ? err : "", args[3]);
2031
2032 return cli_dynerr(appctx, err);
2033}
2034
2035
2036
2037
2038/*
2039 * Parsing function of `set ssl cert`, it updates or creates a temporary ckch.
2040 */
2041static int cli_parse_set_cert(char **args, char *payload, struct appctx *appctx, void *private)
2042{
2043 struct ckch_store *new_ckchs = NULL;
2044 struct ckch_store *old_ckchs = NULL;
2045 char *err = NULL;
2046 int i;
William Lallemandda8584c2020-05-14 10:14:37 +02002047 int errcode = 0;
2048 char *end;
2049 int type = CERT_TYPE_PEM;
2050 struct cert_key_and_chain *ckch;
2051 struct buffer *buf;
2052
2053 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2054 return 1;
2055
William Lallemandda8584c2020-05-14 10:14:37 +02002056 if (!*args[3] || !payload)
2057 return cli_err(appctx, "'set ssl cert expects a filename and a certificate as a payload\n");
2058
2059 /* The operations on the CKCH architecture are locked so we can
2060 * manipulate ckch_store and ckch_inst */
2061 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2062 return cli_err(appctx, "Can't update the certificate!\nOperations on certificates are currently locked!\n");
2063
William Lallemand5ba80d62021-05-04 16:17:27 +02002064 if ((buf = alloc_trash_chunk()) == NULL) {
2065 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2066 errcode |= ERR_ALERT | ERR_FATAL;
2067 goto end;
2068 }
William Lallemande5ff4ad2020-06-08 09:40:37 +02002069
William Lallemandda8584c2020-05-14 10:14:37 +02002070 if (!chunk_strcpy(buf, args[3])) {
2071 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2072 errcode |= ERR_ALERT | ERR_FATAL;
2073 goto end;
2074 }
2075
2076 /* check which type of file we want to update */
2077 for (i = 0; cert_exts[i].type < CERT_TYPE_MAX; i++) {
2078 end = strrchr(buf->area, '.');
Tim Duesterhuse5ff1412021-01-02 22:31:53 +01002079 if (end && *cert_exts[i].ext && (strcmp(end + 1, cert_exts[i].ext) == 0)) {
William Lallemandda8584c2020-05-14 10:14:37 +02002080 *end = '\0';
William Lallemand089c1382020-10-23 17:35:12 +02002081 buf->data = strlen(buf->area);
William Lallemandda8584c2020-05-14 10:14:37 +02002082 type = cert_exts[i].type;
2083 break;
2084 }
2085 }
2086
2087 appctx->ctx.ssl.old_ckchs = NULL;
2088 appctx->ctx.ssl.new_ckchs = NULL;
2089
2090 /* if there is an ongoing transaction */
2091 if (ckchs_transaction.path) {
William Lallemandda8584c2020-05-14 10:14:37 +02002092 /* if there is an ongoing transaction, check if this is the same file */
2093 if (strcmp(ckchs_transaction.path, buf->area) != 0) {
William Lallemand089c1382020-10-23 17:35:12 +02002094 /* we didn't find the transaction, must try more cases below */
2095
2096 /* if the del-ext option is activated we should try to take a look at a ".crt" too. */
2097 if (type != CERT_TYPE_PEM && global_ssl.extra_files_noext) {
2098 if (!chunk_strcat(buf, ".crt")) {
2099 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2100 errcode |= ERR_ALERT | ERR_FATAL;
2101 goto end;
2102 }
2103
2104 if (strcmp(ckchs_transaction.path, buf->area) != 0) {
2105 /* remove .crt of the error message */
2106 *(b_orig(buf) + b_data(buf) + strlen(".crt")) = '\0';
2107 b_sub(buf, strlen(".crt"));
2108
2109 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", ckchs_transaction.path, buf->area);
2110 errcode |= ERR_ALERT | ERR_FATAL;
2111 goto end;
2112 }
2113 }
William Lallemandda8584c2020-05-14 10:14:37 +02002114 }
2115
2116 appctx->ctx.ssl.old_ckchs = ckchs_transaction.new_ckchs;
2117
2118 } else {
William Lallemandda8584c2020-05-14 10:14:37 +02002119
William Lallemand95fefa12020-09-09 12:01:33 +02002120 /* lookup for the certificate in the tree */
2121 appctx->ctx.ssl.old_ckchs = ckchs_lookup(buf->area);
William Lallemand089c1382020-10-23 17:35:12 +02002122
2123 if (!appctx->ctx.ssl.old_ckchs) {
2124 /* if the del-ext option is activated we should try to take a look at a ".crt" too. */
2125 if (type != CERT_TYPE_PEM && global_ssl.extra_files_noext) {
2126 if (!chunk_strcat(buf, ".crt")) {
2127 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2128 errcode |= ERR_ALERT | ERR_FATAL;
2129 goto end;
2130 }
2131 appctx->ctx.ssl.old_ckchs = ckchs_lookup(buf->area);
2132 }
2133 }
William Lallemandda8584c2020-05-14 10:14:37 +02002134 }
2135
2136 if (!appctx->ctx.ssl.old_ckchs) {
2137 memprintf(&err, "%sCan't replace a certificate which is not referenced by the configuration!\n",
2138 err ? err : "");
2139 errcode |= ERR_ALERT | ERR_FATAL;
2140 goto end;
2141 }
2142
2143 if (!appctx->ctx.ssl.path) {
2144 /* this is a new transaction, set the path of the transaction */
2145 appctx->ctx.ssl.path = strdup(appctx->ctx.ssl.old_ckchs->path);
2146 if (!appctx->ctx.ssl.path) {
2147 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2148 errcode |= ERR_ALERT | ERR_FATAL;
2149 goto end;
2150 }
2151 }
2152
2153 old_ckchs = appctx->ctx.ssl.old_ckchs;
2154
2155 /* duplicate the ckch store */
2156 new_ckchs = ckchs_dup(old_ckchs);
2157 if (!new_ckchs) {
2158 memprintf(&err, "%sCannot allocate memory!\n",
2159 err ? err : "");
2160 errcode |= ERR_ALERT | ERR_FATAL;
2161 goto end;
2162 }
2163
William Lallemand95fefa12020-09-09 12:01:33 +02002164 ckch = new_ckchs->ckch;
William Lallemandda8584c2020-05-14 10:14:37 +02002165
2166 /* appply the change on the duplicate */
2167 if (cert_exts[type].load(buf->area, payload, ckch, &err) != 0) {
2168 memprintf(&err, "%sCan't load the payload\n", err ? err : "");
2169 errcode |= ERR_ALERT | ERR_FATAL;
2170 goto end;
2171 }
2172
2173 appctx->ctx.ssl.new_ckchs = new_ckchs;
2174
2175 /* we succeed, we can save the ckchs in the transaction */
2176
2177 /* if there wasn't a transaction, update the old ckchs */
2178 if (!ckchs_transaction.old_ckchs) {
2179 ckchs_transaction.old_ckchs = appctx->ctx.ssl.old_ckchs;
2180 ckchs_transaction.path = appctx->ctx.ssl.path;
2181 err = memprintf(&err, "Transaction created for certificate %s!\n", ckchs_transaction.path);
2182 } else {
2183 err = memprintf(&err, "Transaction updated for certificate %s!\n", ckchs_transaction.path);
2184
2185 }
2186
2187 /* free the previous ckchs if there was a transaction */
2188 ckch_store_free(ckchs_transaction.new_ckchs);
2189
2190 ckchs_transaction.new_ckchs = appctx->ctx.ssl.new_ckchs;
2191
2192
2193 /* creates the SNI ctxs later in the IO handler */
2194
2195end:
2196 free_trash_chunk(buf);
2197
2198 if (errcode & ERR_CODE) {
2199
2200 ckch_store_free(appctx->ctx.ssl.new_ckchs);
2201 appctx->ctx.ssl.new_ckchs = NULL;
2202
2203 appctx->ctx.ssl.old_ckchs = NULL;
2204
Willy Tarreau61cfdf42021-02-20 10:46:51 +01002205 ha_free(&appctx->ctx.ssl.path);
William Lallemandda8584c2020-05-14 10:14:37 +02002206
2207 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2208 return cli_dynerr(appctx, memprintf(&err, "%sCan't update %s!\n", err ? err : "", args[3]));
2209 } else {
2210
2211 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2212 return cli_dynmsg(appctx, LOG_NOTICE, err);
2213 }
2214 /* TODO: handle the ERR_WARN which are not handled because of the io_handler */
2215}
2216
2217/* parsing function of 'abort ssl cert' */
2218static int cli_parse_abort_cert(char **args, char *payload, struct appctx *appctx, void *private)
2219{
2220 char *err = NULL;
2221
2222 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2223 return 1;
2224
2225 if (!*args[3])
2226 return cli_err(appctx, "'abort ssl cert' expects a filename\n");
2227
2228 /* The operations on the CKCH architecture are locked so we can
2229 * manipulate ckch_store and ckch_inst */
2230 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2231 return cli_err(appctx, "Can't abort!\nOperations on certificates are currently locked!\n");
2232
2233 if (!ckchs_transaction.path) {
2234 memprintf(&err, "No ongoing transaction!\n");
2235 goto error;
2236 }
2237
2238 if (strcmp(ckchs_transaction.path, args[3]) != 0) {
2239 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to abort a transaction for '%s'\n", ckchs_transaction.path, args[3]);
2240 goto error;
2241 }
2242
2243 /* Only free the ckchs there, because the SNI and instances were not generated yet */
2244 ckch_store_free(ckchs_transaction.new_ckchs);
2245 ckchs_transaction.new_ckchs = NULL;
William Lallemandda8584c2020-05-14 10:14:37 +02002246 ckchs_transaction.old_ckchs = NULL;
Willy Tarreau61cfdf42021-02-20 10:46:51 +01002247 ha_free(&ckchs_transaction.path);
William Lallemandda8584c2020-05-14 10:14:37 +02002248
2249 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2250
2251 err = memprintf(&err, "Transaction aborted for certificate '%s'!\n", args[3]);
2252 return cli_dynmsg(appctx, LOG_NOTICE, err);
2253
2254error:
2255 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2256
2257 return cli_dynerr(appctx, err);
2258}
2259
2260/* parsing function of 'new ssl cert' */
2261static int cli_parse_new_cert(char **args, char *payload, struct appctx *appctx, void *private)
2262{
2263 struct ckch_store *store;
2264 char *err = NULL;
2265 char *path;
2266
2267 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2268 return 1;
2269
2270 if (!*args[3])
2271 return cli_err(appctx, "'new ssl cert' expects a filename\n");
2272
2273 path = args[3];
2274
2275 /* The operations on the CKCH architecture are locked so we can
2276 * manipulate ckch_store and ckch_inst */
2277 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2278 return cli_err(appctx, "Can't create a certificate!\nOperations on certificates are currently locked!\n");
2279
2280 store = ckchs_lookup(path);
2281 if (store != NULL) {
2282 memprintf(&err, "Certificate '%s' already exists!\n", path);
2283 store = NULL; /* we don't want to free it */
2284 goto error;
2285 }
2286 /* we won't support multi-certificate bundle here */
William Lallemandbd8e6ed2020-09-16 16:08:08 +02002287 store = ckch_store_new(path);
William Lallemandda8584c2020-05-14 10:14:37 +02002288 if (!store) {
2289 memprintf(&err, "unable to allocate memory.\n");
2290 goto error;
2291 }
2292
2293 /* insert into the ckchs tree */
2294 ebst_insert(&ckchs_tree, &store->node);
2295 memprintf(&err, "New empty certificate store '%s'!\n", args[3]);
2296
2297 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2298 return cli_dynmsg(appctx, LOG_NOTICE, err);
2299error:
2300 free(store);
2301 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2302 return cli_dynerr(appctx, err);
2303}
2304
2305/* parsing function of 'del ssl cert' */
2306static int cli_parse_del_cert(char **args, char *payload, struct appctx *appctx, void *private)
2307{
2308 struct ckch_store *store;
2309 char *err = NULL;
2310 char *filename;
2311
2312 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2313 return 1;
2314
2315 if (!*args[3])
2316 return cli_err(appctx, "'del ssl cert' expects a certificate name\n");
2317
2318 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2319 return cli_err(appctx, "Can't delete the certificate!\nOperations on certificates are currently locked!\n");
2320
2321 filename = args[3];
2322
2323 store = ckchs_lookup(filename);
2324 if (store == NULL) {
2325 memprintf(&err, "certificate '%s' doesn't exist!\n", filename);
2326 goto error;
2327 }
2328 if (!LIST_ISEMPTY(&store->ckch_inst)) {
2329 memprintf(&err, "certificate '%s' in use, can't be deleted!\n", filename);
2330 goto error;
2331 }
2332
2333 ebmb_delete(&store->node);
2334 ckch_store_free(store);
2335
2336 memprintf(&err, "Certificate '%s' deleted!\n", filename);
2337
2338 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2339 return cli_dynmsg(appctx, LOG_NOTICE, err);
2340
2341error:
2342 memprintf(&err, "Can't remove the certificate: %s\n", err ? err : "");
2343 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2344 return cli_dynerr(appctx, err);
2345}
2346
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002347
Remi Tricot-Le Breton9f40fe02021-03-16 16:21:27 +01002348
2349/* parsing function of 'new ssl ca-file' */
2350static int cli_parse_new_cafile(char **args, char *payload, struct appctx *appctx, void *private)
2351{
2352 struct cafile_entry *cafile_entry;
2353 char *err = NULL;
2354 char *path;
2355
2356 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2357 return 1;
2358
2359 if (!*args[3])
2360 return cli_err(appctx, "'new ssl ca-file' expects a filename\n");
2361
2362 path = args[3];
2363
2364 /* The operations on the CKCH architecture are locked so we can
2365 * manipulate ckch_store and ckch_inst */
2366 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2367 return cli_err(appctx, "Can't create a CA file!\nOperations on certificates are currently locked!\n");
2368
2369 cafile_entry = ssl_store_get_cafile_entry(path, 0);
2370 if (cafile_entry) {
2371 memprintf(&err, "CA file '%s' already exists!\n", path);
2372 goto error;
2373 }
2374
2375 cafile_entry = ssl_store_create_cafile_entry(path, NULL, CAFILE_CERT);
2376 if (!cafile_entry) {
2377 memprintf(&err, "%sCannot allocate memory!\n",
2378 err ? err : "");
2379 goto error;
2380 }
2381
2382 /* Add the newly created cafile_entry to the tree so that
2383 * any new ckch instance created from now can use it. */
2384 if (ssl_store_add_uncommitted_cafile_entry(cafile_entry))
2385 goto error;
2386
2387 memprintf(&err, "New CA file created '%s'!\n", path);
2388
2389 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2390 return cli_dynmsg(appctx, LOG_NOTICE, err);
2391error:
2392 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2393 return cli_dynerr(appctx, err);
2394}
2395
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002396/*
2397 * Parsing function of `set ssl ca-file`
2398 */
2399static int cli_parse_set_cafile(char **args, char *payload, struct appctx *appctx, void *private)
2400{
2401 char *err = NULL;
2402 int errcode = 0;
2403 struct buffer *buf;
2404
2405 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2406 return 1;
2407
2408 if (!*args[3] || !payload)
2409 return cli_err(appctx, "'set ssl ca-file expects a filename and CAs as a payload\n");
2410
2411 /* The operations on the CKCH architecture are locked so we can
2412 * manipulate ckch_store and ckch_inst */
2413 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2414 return cli_err(appctx, "Can't update the CA file!\nOperations on certificates are currently locked!\n");
2415
2416 if ((buf = alloc_trash_chunk()) == NULL) {
2417 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2418 errcode |= ERR_ALERT | ERR_FATAL;
2419 goto end;
2420 }
2421
2422 if (!chunk_strcpy(buf, args[3])) {
2423 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2424 errcode |= ERR_ALERT | ERR_FATAL;
2425 goto end;
2426 }
2427
2428 appctx->ctx.ssl.old_cafile_entry = NULL;
2429 appctx->ctx.ssl.new_cafile_entry = NULL;
2430
2431 /* if there is an ongoing transaction */
2432 if (cafile_transaction.path) {
2433 /* if there is an ongoing transaction, check if this is the same file */
2434 if (strcmp(cafile_transaction.path, buf->area) != 0) {
2435 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", cafile_transaction.path, buf->area);
2436 errcode |= ERR_ALERT | ERR_FATAL;
2437 goto end;
2438 }
2439 appctx->ctx.ssl.old_cafile_entry = cafile_transaction.old_cafile_entry;
2440 }
2441 else {
2442 /* lookup for the certificate in the tree */
2443 appctx->ctx.ssl.old_cafile_entry = ssl_store_get_cafile_entry(buf->area, 0);
2444 }
2445
2446 if (!appctx->ctx.ssl.old_cafile_entry) {
2447 memprintf(&err, "%sCan't replace a CA file which is not referenced by the configuration!\n",
2448 err ? err : "");
2449 errcode |= ERR_ALERT | ERR_FATAL;
2450 goto end;
2451 }
2452
2453 if (!appctx->ctx.ssl.path) {
2454 /* this is a new transaction, set the path of the transaction */
2455 appctx->ctx.ssl.path = strdup(appctx->ctx.ssl.old_cafile_entry->path);
2456 if (!appctx->ctx.ssl.path) {
2457 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2458 errcode |= ERR_ALERT | ERR_FATAL;
2459 goto end;
2460 }
2461 }
2462
2463 if (appctx->ctx.ssl.new_cafile_entry)
2464 ssl_store_delete_cafile_entry(appctx->ctx.ssl.new_cafile_entry);
2465
2466 /* Create a new cafile_entry without adding it to the cafile tree. */
Remi Tricot-Le Breton0bb48242021-04-16 17:59:23 +02002467 appctx->ctx.ssl.new_cafile_entry = ssl_store_create_cafile_entry(appctx->ctx.ssl.path, NULL, CAFILE_CERT);
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002468 if (!appctx->ctx.ssl.new_cafile_entry) {
2469 memprintf(&err, "%sCannot allocate memory!\n",
2470 err ? err : "");
2471 errcode |= ERR_ALERT | ERR_FATAL;
2472 goto end;
2473 }
2474
2475 /* Fill the new entry with the new CAs. */
2476 if (ssl_store_load_ca_from_buf(appctx->ctx.ssl.new_cafile_entry, payload)) {
2477 memprintf(&err, "%sInvalid payload\n", err ? err : "");
2478 errcode |= ERR_ALERT | ERR_FATAL;
2479 goto end;
2480 }
2481
2482 /* we succeed, we can save the ca in the transaction */
2483
2484 /* if there wasn't a transaction, update the old CA */
2485 if (!cafile_transaction.old_cafile_entry) {
2486 cafile_transaction.old_cafile_entry = appctx->ctx.ssl.old_cafile_entry;
2487 cafile_transaction.path = appctx->ctx.ssl.path;
2488 err = memprintf(&err, "transaction created for CA %s!\n", cafile_transaction.path);
2489 } else {
2490 err = memprintf(&err, "transaction updated for CA %s!\n", cafile_transaction.path);
2491 }
2492
2493 /* free the previous CA if there was a transaction */
2494 ssl_store_delete_cafile_entry(cafile_transaction.new_cafile_entry);
2495
2496 cafile_transaction.new_cafile_entry = appctx->ctx.ssl.new_cafile_entry;
2497
2498 /* creates the SNI ctxs later in the IO handler */
2499
2500end:
2501 free_trash_chunk(buf);
2502
2503 if (errcode & ERR_CODE) {
2504 ssl_store_delete_cafile_entry(appctx->ctx.ssl.new_cafile_entry);
2505 appctx->ctx.ssl.new_cafile_entry = NULL;
2506 appctx->ctx.ssl.old_cafile_entry = NULL;
2507
2508 free(appctx->ctx.ssl.path);
2509 appctx->ctx.ssl.path = NULL;
2510
2511 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2512 return cli_dynerr(appctx, memprintf(&err, "%sCan't update %s!\n", err ? err : "", args[3]));
2513 } else {
2514
2515 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2516 return cli_dynmsg(appctx, LOG_NOTICE, err);
2517 }
2518}
2519
2520
2521/*
2522 * Parsing function of 'commit ssl ca-file'
2523 */
2524static int cli_parse_commit_cafile(char **args, char *payload, struct appctx *appctx, void *private)
2525{
2526 char *err = NULL;
2527
2528 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2529 return 1;
2530
2531 if (!*args[3])
2532 return cli_err(appctx, "'commit ssl ca-file expects a filename\n");
2533
2534 /* The operations on the CKCH architecture are locked so we can
2535 * manipulate ckch_store and ckch_inst */
2536 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2537 return cli_err(appctx, "Can't commit the CA file!\nOperations on certificates are currently locked!\n");
2538
2539 if (!cafile_transaction.path) {
2540 memprintf(&err, "No ongoing transaction! !\n");
2541 goto error;
2542 }
2543
2544 if (strcmp(cafile_transaction.path, args[3]) != 0) {
2545 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", cafile_transaction.path, args[3]);
2546 goto error;
2547 }
2548 /* init the appctx structure */
2549 appctx->st2 = SETCERT_ST_INIT;
2550 appctx->ctx.ssl.next_ckchi_link = NULL;
2551 appctx->ctx.ssl.old_cafile_entry = cafile_transaction.old_cafile_entry;
2552 appctx->ctx.ssl.new_cafile_entry = cafile_transaction.new_cafile_entry;
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002553 appctx->ctx.ssl.cafile_type = CAFILE_CERT;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002554
2555 return 0;
2556
2557error:
2558
2559 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2560 err = memprintf(&err, "%sCan't commit %s!\n", err ? err : "", args[3]);
2561
2562 return cli_dynerr(appctx, err);
2563}
2564
2565enum {
2566 CREATE_NEW_INST_OK = 0,
2567 CREATE_NEW_INST_YIELD = -1,
2568 CREATE_NEW_INST_ERR = -2
2569};
2570
2571static inline int __create_new_instance(struct appctx *appctx, struct ckch_inst *ckchi, int *count,
Remi Tricot-Le Bretond75b99e2021-05-17 11:45:55 +02002572 struct buffer *trash, char **err)
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002573{
2574 struct ckch_inst *new_inst;
2575
2576 /* it takes a lot of CPU to creates SSL_CTXs, so we yield every 10 CKCH instances */
2577 if (*count >= 10) {
2578 /* save the next ckchi to compute */
2579 appctx->ctx.ssl.next_ckchi = ckchi;
2580 return CREATE_NEW_INST_YIELD;
2581 }
2582
2583 /* Rebuild a new ckch instance that uses the same ckch_store
2584 * than a reference ckchi instance but will use a new CA file. */
Remi Tricot-Le Bretond75b99e2021-05-17 11:45:55 +02002585 if (ckch_inst_rebuild(ckchi->ckch_store, ckchi, &new_inst, err))
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002586 return CREATE_NEW_INST_ERR;
2587
2588 /* display one dot per new instance */
2589 chunk_appendf(trash, ".");
2590 ++(*count);
2591
2592 return CREATE_NEW_INST_OK;
2593}
2594
2595/*
2596 * This function tries to create new ckch instances and their SNIs using a newly
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002597 * set certificate authority (CA file) or a newly set Certificate Revocation
2598 * List (CRL), depending on the command being called.
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002599 */
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002600static int cli_io_handler_commit_cafile_crlfile(struct appctx *appctx)
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002601{
2602 struct stream_interface *si = appctx->owner;
2603 int y = 0;
2604 char *err = NULL;
Remi Tricot-Le Bretona6b27842021-05-18 10:06:00 +02002605 struct cafile_entry *old_cafile_entry = NULL, *new_cafile_entry = NULL;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002606 struct ckch_inst_link *ckchi_link;
2607 struct buffer *trash = alloc_trash_chunk();
2608
2609 if (trash == NULL)
2610 goto error;
2611
2612 if (unlikely(si_ic(si)->flags & (CF_WRITE_ERROR|CF_SHUTW)))
2613 goto error;
2614
2615 while (1) {
2616 switch (appctx->st2) {
2617 case SETCERT_ST_INIT:
2618 /* This state just print the update message */
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002619 switch (appctx->ctx.ssl.cafile_type) {
2620 case CAFILE_CERT:
2621 chunk_printf(trash, "Committing %s", cafile_transaction.path);
2622 break;
2623 case CAFILE_CRL:
2624 chunk_printf(trash, "Committing %s", crlfile_transaction.path);
2625 break;
2626 default:
2627 goto error;
2628 }
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002629 if (ci_putchk(si_ic(si), trash) == -1) {
2630 si_rx_room_blk(si);
2631 goto yield;
2632 }
2633 appctx->st2 = SETCERT_ST_GEN;
2634 /* fallthrough */
2635 case SETCERT_ST_GEN:
2636 /*
2637 * This state generates the ckch instances with their
2638 * sni_ctxs and SSL_CTX.
2639 *
2640 * Since the SSL_CTX generation can be CPU consumer, we
2641 * yield every 10 instances.
2642 */
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002643 switch (appctx->ctx.ssl.cafile_type) {
2644 case CAFILE_CERT:
2645 old_cafile_entry = appctx->ctx.ssl.old_cafile_entry;
2646 new_cafile_entry = appctx->ctx.ssl.new_cafile_entry;
2647 break;
2648 case CAFILE_CRL:
2649 old_cafile_entry = appctx->ctx.ssl.old_crlfile_entry;
2650 new_cafile_entry = appctx->ctx.ssl.new_crlfile_entry;
2651 break;
2652 }
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002653 if (!new_cafile_entry)
2654 continue;
2655
2656 /* get the next ckchi to regenerate */
2657 ckchi_link = appctx->ctx.ssl.next_ckchi_link;
2658 /* we didn't start yet, set it to the first elem */
2659 if (ckchi_link == NULL) {
2660 ckchi_link = LIST_ELEM(old_cafile_entry->ckch_inst_link.n, typeof(ckchi_link), list);
2661 /* Add the newly created cafile_entry to the tree so that
2662 * any new ckch instance created from now can use it. */
2663 if (ssl_store_add_uncommitted_cafile_entry(new_cafile_entry))
2664 goto error;
2665 }
2666
2667 list_for_each_entry_from(ckchi_link, &old_cafile_entry->ckch_inst_link, list) {
Remi Tricot-Le Bretond75b99e2021-05-17 11:45:55 +02002668 switch (__create_new_instance(appctx, ckchi_link->ckch_inst, &y, trash, &err)) {
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002669 case CREATE_NEW_INST_YIELD:
2670 appctx->ctx.ssl.next_ckchi_link = ckchi_link;
2671 goto yield;
2672 case CREATE_NEW_INST_ERR:
2673 goto error;
2674 default: break;
2675 }
2676 }
2677
2678 appctx->st2 = SETCERT_ST_INSERT;
2679 /* fallthrough */
2680 case SETCERT_ST_INSERT:
2681 /* The generation is finished, we can insert everything */
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002682 switch (appctx->ctx.ssl.cafile_type) {
2683 case CAFILE_CERT:
2684 old_cafile_entry = appctx->ctx.ssl.old_cafile_entry;
2685 new_cafile_entry = appctx->ctx.ssl.new_cafile_entry;
2686 break;
2687 case CAFILE_CRL:
2688 old_cafile_entry = appctx->ctx.ssl.old_crlfile_entry;
2689 new_cafile_entry = appctx->ctx.ssl.new_crlfile_entry;
2690 break;
2691 }
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002692 if (!new_cafile_entry)
2693 continue;
2694
2695 /* insert the new ckch_insts in the crtlist_entry */
2696 list_for_each_entry(ckchi_link, &new_cafile_entry->ckch_inst_link, list) {
2697 if (ckchi_link->ckch_inst->crtlist_entry)
2698 LIST_INSERT(&ckchi_link->ckch_inst->crtlist_entry->ckch_inst,
2699 &ckchi_link->ckch_inst->by_crtlist_entry);
2700 }
2701
2702 /* First, we insert every new SNIs in the trees, also replace the default_ctx */
2703 list_for_each_entry(ckchi_link, &new_cafile_entry->ckch_inst_link, list) {
2704 __ssl_sock_load_new_ckch_instance(ckchi_link->ckch_inst);
2705 }
2706
2707 /* delete the old sni_ctx, the old ckch_insts and the ckch_store */
2708 list_for_each_entry(ckchi_link, &old_cafile_entry->ckch_inst_link, list) {
2709 __ckch_inst_free_locked(ckchi_link->ckch_inst);
2710 }
2711
2712
2713 /* Remove the old cafile entry from the tree */
2714 ebmb_delete(&old_cafile_entry->node);
2715 ssl_store_delete_cafile_entry(old_cafile_entry);
2716
2717 appctx->st2 = SETCERT_ST_FIN;
2718 /* fallthrough */
2719 case SETCERT_ST_FIN:
2720 /* we achieved the transaction, we can set everything to NULL */
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002721 switch (appctx->ctx.ssl.cafile_type) {
2722 case CAFILE_CERT:
2723 ha_free(&cafile_transaction.path);
2724 cafile_transaction.old_cafile_entry = NULL;
2725 cafile_transaction.new_cafile_entry = NULL;
2726 break;
2727 case CAFILE_CRL:
2728 ha_free(&crlfile_transaction.path);
2729 crlfile_transaction.old_crlfile_entry = NULL;
2730 crlfile_transaction.new_crlfile_entry = NULL;
2731 break;
2732 }
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002733 goto end;
2734 }
2735 }
2736end:
2737
2738 chunk_appendf(trash, "\n");
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002739 chunk_appendf(trash, "Success!\n");
2740 if (ci_putchk(si_ic(si), trash) == -1)
2741 si_rx_room_blk(si);
2742 free_trash_chunk(trash);
2743 /* success: call the release function and don't come back */
2744 return 1;
2745yield:
2746 /* store the state */
2747 if (ci_putchk(si_ic(si), trash) == -1)
2748 si_rx_room_blk(si);
2749 free_trash_chunk(trash);
2750 si_rx_endp_more(si); /* let's come back later */
2751 return 0; /* should come back */
2752
2753error:
2754 /* spin unlock and free are done in the release function */
2755 if (trash) {
2756 chunk_appendf(trash, "\n%sFailed!\n", err);
2757 if (ci_putchk(si_ic(si), trash) == -1)
2758 si_rx_room_blk(si);
2759 free_trash_chunk(trash);
2760 }
2761 /* error: call the release function and don't come back */
2762 return 1;
2763}
2764
Remi Tricot-Le Bretond5fd09d2021-03-11 10:22:52 +01002765
2766/* parsing function of 'abort ssl ca-file' */
2767static int cli_parse_abort_cafile(char **args, char *payload, struct appctx *appctx, void *private)
2768{
2769 char *err = NULL;
2770
2771 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2772 return 1;
2773
2774 if (!*args[3])
2775 return cli_err(appctx, "'abort ssl ca-file' expects a filename\n");
2776
2777 /* The operations on the CKCH architecture are locked so we can
2778 * manipulate ckch_store and ckch_inst */
2779 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2780 return cli_err(appctx, "Can't abort!\nOperations on certificates are currently locked!\n");
2781
2782 if (!cafile_transaction.path) {
2783 memprintf(&err, "No ongoing transaction!\n");
2784 goto error;
2785 }
2786
2787 if (strcmp(cafile_transaction.path, args[3]) != 0) {
2788 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to abort a transaction for '%s'\n", cafile_transaction.path, args[3]);
2789 goto error;
2790 }
2791
2792 /* Only free the uncommitted cafile_entry here, because the SNI and instances were not generated yet */
2793 ssl_store_delete_cafile_entry(cafile_transaction.new_cafile_entry);
2794 cafile_transaction.new_cafile_entry = NULL;
2795 cafile_transaction.old_cafile_entry = NULL;
2796 ha_free(&cafile_transaction.path);
2797
2798 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2799
2800 err = memprintf(&err, "Transaction aborted for certificate '%s'!\n", args[3]);
2801 return cli_dynmsg(appctx, LOG_NOTICE, err);
2802
2803error:
2804 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2805
2806 return cli_dynerr(appctx, err);
2807}
2808
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002809/* release function of the `commit ssl ca-file' command, free things and unlock the spinlock */
2810static void cli_release_commit_cafile(struct appctx *appctx)
2811{
2812 if (appctx->st2 != SETCERT_ST_FIN) {
2813 struct cafile_entry *new_cafile_entry = appctx->ctx.ssl.new_cafile_entry;
2814
2815 /* Remove the uncommitted cafile_entry from the tree. */
2816 ebmb_delete(&new_cafile_entry->node);
2817 ssl_store_delete_cafile_entry(new_cafile_entry);
2818 }
2819 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2820}
2821
2822
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01002823/* IO handler of details "show ssl ca-file <filename[:index]>" */
2824static int cli_io_handler_show_cafile_detail(struct appctx *appctx)
2825{
2826 struct stream_interface *si = appctx->owner;
2827 struct cafile_entry *cafile_entry = appctx->ctx.cli.p0;
2828 struct buffer *out = alloc_trash_chunk();
2829 int i;
2830 X509 *cert;
2831 STACK_OF(X509_OBJECT) *objs;
2832 int retval = 0;
2833 long ca_index = (long)appctx->ctx.cli.p1;
2834
2835 if (!out)
2836 goto end_no_putchk;
2837
2838 chunk_appendf(out, "Filename: ");
2839 if (cafile_entry == cafile_transaction.new_cafile_entry)
2840 chunk_appendf(out, "*");
2841 chunk_appendf(out, "%s\n", cafile_entry->path);
2842
2843 chunk_appendf(out, "Status: ");
2844 if (!cafile_entry->ca_store)
2845 chunk_appendf(out, "Empty\n");
2846 else if (LIST_ISEMPTY(&cafile_entry->ckch_inst_link))
2847 chunk_appendf(out, "Unused\n");
2848 else
2849 chunk_appendf(out, "Used\n");
2850
2851 if (!cafile_entry->ca_store)
2852 goto end;
2853
2854 objs = X509_STORE_get0_objects(cafile_entry->ca_store);
2855 for (i = 0; i < sk_X509_OBJECT_num(objs); i++) {
2856 cert = X509_OBJECT_get0_X509(sk_X509_OBJECT_value(objs, i));
2857 if (!cert)
2858 continue;
2859
2860 /* Certificate indexes start at 1 on the CLI output. */
2861 if (ca_index && ca_index-1 != i)
2862 continue;
2863
2864 chunk_appendf(out, "\nCertificate #%d:\n", i+1);
2865 retval = show_cert_detail(cert, NULL, out);
2866 if (retval < 0)
2867 goto end_no_putchk;
2868 else if (retval || ca_index)
2869 goto end;
2870 }
2871
2872end:
2873 if (ci_putchk(si_ic(si), out) == -1) {
2874 si_rx_room_blk(si);
2875 goto yield;
2876 }
2877
2878end_no_putchk:
2879 free_trash_chunk(out);
2880 return 1;
2881yield:
2882 free_trash_chunk(out);
2883 return 0; /* should come back */
2884}
2885
2886
2887/* parsing function for 'show ssl ca-file [cafile[:index]]' */
2888static int cli_parse_show_cafile(char **args, char *payload, struct appctx *appctx, void *private)
2889{
2890 struct cafile_entry *cafile_entry;
2891 long ca_index = 0;
2892 char *colons;
2893 char *err = NULL;
2894
2895 if (!cli_has_level(appctx, ACCESS_LVL_OPER))
2896 return cli_err(appctx, "Can't allocate memory!\n");
2897
2898 /* The operations on the CKCH architecture are locked so we can
2899 * manipulate ckch_store and ckch_inst */
2900 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2901 return cli_err(appctx, "Can't show!\nOperations on certificates are currently locked!\n");
2902
2903 /* check if there is a certificate to lookup */
2904 if (*args[3]) {
2905
2906 /* Look for an optional CA index after the CA file name */
2907 colons = strchr(args[3], ':');
2908 if (colons) {
2909 char *endptr;
2910
2911 ca_index = strtol(colons + 1, &endptr, 10);
2912 /* Indexes start at 1 */
2913 if (colons + 1 == endptr || *endptr != '\0' || ca_index <= 0) {
2914 memprintf(&err, "wrong CA index after colons in '%s'!", args[3]);
2915 goto error;
2916 }
2917 *colons = '\0';
2918 }
2919
2920 if (*args[3] == '*') {
2921 if (!cafile_transaction.new_cafile_entry)
2922 goto error;
2923
2924 cafile_entry = cafile_transaction.new_cafile_entry;
2925
2926 if (strcmp(args[3] + 1, cafile_entry->path) != 0)
2927 goto error;
2928
2929 } else {
2930 /* Get the "original" cafile_entry and not the
2931 * uncommitted one if it exists. */
2932 if ((cafile_entry = ssl_store_get_cafile_entry(args[3], 1)) == NULL || cafile_entry->type != CAFILE_CERT)
2933 goto error;
2934 }
2935
2936 appctx->ctx.cli.p0 = cafile_entry;
2937 appctx->ctx.cli.p1 = (void*)ca_index;
2938 /* use the IO handler that shows details */
2939 appctx->io_handler = cli_io_handler_show_cafile_detail;
2940 }
2941
2942 return 0;
2943
2944error:
2945 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2946 if (err)
2947 return cli_dynerr(appctx, err);
2948 return cli_err(appctx, "Can't display the CA file : Not found!\n");
2949}
2950
2951
2952/* release function of the 'show ssl ca-file' command */
2953static void cli_release_show_cafile(struct appctx *appctx)
2954{
2955 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2956}
2957
2958
2959/* This function returns the number of certificates in a cafile_entry. */
2960static int get_certificate_count(struct cafile_entry *cafile_entry)
2961{
2962 int cert_count = 0;
2963 STACK_OF(X509_OBJECT) *objs;
2964
2965 if (cafile_entry && cafile_entry->ca_store) {
2966 objs = X509_STORE_get0_objects(cafile_entry->ca_store);
2967 if (objs)
2968 cert_count = sk_X509_OBJECT_num(objs);
2969 }
2970 return cert_count;
2971}
2972
2973/* IO handler of "show ssl ca-file". The command taking a specific CA file name
2974 * is managed in cli_io_handler_show_cafile_detail. */
2975static int cli_io_handler_show_cafile(struct appctx *appctx)
2976{
2977 struct buffer *trash = alloc_trash_chunk();
2978 struct ebmb_node *node;
2979 struct stream_interface *si = appctx->owner;
2980 struct cafile_entry *cafile_entry;
2981
2982 if (trash == NULL)
2983 return 1;
2984
2985 if (!appctx->ctx.ssl.old_cafile_entry) {
2986 if (cafile_transaction.old_cafile_entry) {
2987 chunk_appendf(trash, "# transaction\n");
2988 chunk_appendf(trash, "*%s", cafile_transaction.old_cafile_entry->path);
2989
2990 chunk_appendf(trash, " - %d certificate(s)\n", get_certificate_count(cafile_transaction.new_cafile_entry));
2991 }
2992 }
2993
2994 /* First time in this io_handler. */
2995 if (!appctx->ctx.cli.p0) {
2996 chunk_appendf(trash, "# filename\n");
2997 node = ebmb_first(&cafile_tree);
2998 } else {
2999 /* We yielded during a previous call. */
3000 node = &((struct cafile_entry*)appctx->ctx.cli.p0)->node;
3001 }
3002
3003 while (node) {
3004 cafile_entry = ebmb_entry(node, struct cafile_entry, node);
3005 if (cafile_entry->type == CAFILE_CERT) {
3006 chunk_appendf(trash, "%s", cafile_entry->path);
3007
3008 chunk_appendf(trash, " - %d certificate(s)\n", get_certificate_count(cafile_entry));
3009 }
3010
3011 node = ebmb_next(node);
3012 if (ci_putchk(si_ic(si), trash) == -1) {
3013 si_rx_room_blk(si);
3014 goto yield;
3015 }
3016 }
3017
3018 appctx->ctx.cli.p0 = NULL;
3019 free_trash_chunk(trash);
3020 return 1;
3021yield:
3022
3023 free_trash_chunk(trash);
3024 appctx->ctx.cli.p0 = cafile_entry;
3025 return 0; /* should come back */
3026}
3027
Remi Tricot-Le Bretonc3a84772021-03-25 18:13:57 +01003028/* parsing function of 'del ssl ca-file' */
3029static int cli_parse_del_cafile(char **args, char *payload, struct appctx *appctx, void *private)
3030{
3031 struct cafile_entry *cafile_entry;
3032 char *err = NULL;
3033 char *filename;
3034
3035 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
3036 return 1;
3037
3038 if (!*args[3])
3039 return cli_err(appctx, "'del ssl ca-file' expects a CA file name\n");
3040
3041 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3042 return cli_err(appctx, "Can't delete the CA file!\nOperations on certificates are currently locked!\n");
3043
3044 filename = args[3];
3045
3046 cafile_entry = ssl_store_get_cafile_entry(filename, 0);
3047 if (!cafile_entry) {
3048 memprintf(&err, "CA file '%s' doesn't exist!\n", filename);
3049 goto error;
3050 }
3051
3052 if (!LIST_ISEMPTY(&cafile_entry->ckch_inst_link)) {
3053 memprintf(&err, "CA file '%s' in use, can't be deleted!\n", filename);
3054 goto error;
3055 }
3056
3057 /* Remove the cafile_entry from the tree */
3058 ebmb_delete(&cafile_entry->node);
3059 ssl_store_delete_cafile_entry(cafile_entry);
3060
3061 memprintf(&err, "CA file '%s' deleted!\n", filename);
3062
3063 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3064 return cli_dynmsg(appctx, LOG_NOTICE, err);
3065
3066error:
3067 memprintf(&err, "Can't remove the CA file: %s\n", err ? err : "");
3068 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3069 return cli_dynerr(appctx, err);
3070}
3071
Remi Tricot-Le Breton720e3b92021-04-26 11:00:42 +02003072/* parsing function of 'new ssl crl-file' */
3073static int cli_parse_new_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
3074{
3075 struct cafile_entry *cafile_entry;
3076 char *err = NULL;
3077 char *path;
3078
3079 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
3080 return 1;
3081
3082 if (!*args[3])
3083 return cli_err(appctx, "'new ssl crl-file' expects a filename\n");
3084
3085 path = args[3];
3086
3087 /* The operations on the CKCH architecture are locked so we can
3088 * manipulate ckch_store and ckch_inst */
3089 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3090 return cli_err(appctx, "Can't create a CA file!\nOperations on certificates are currently locked!\n");
3091
3092 cafile_entry = ssl_store_get_cafile_entry(path, 0);
3093 if (cafile_entry) {
3094 memprintf(&err, "CRL file '%s' already exists!\n", path);
3095 goto error;
3096 }
3097
3098 cafile_entry = ssl_store_create_cafile_entry(path, NULL, CAFILE_CRL);
3099 if (!cafile_entry) {
3100 memprintf(&err, "%sCannot allocate memory!\n", err ? err : "");
3101 goto error;
3102 }
3103
3104 /* Add the newly created cafile_entry to the tree so that
3105 * any new ckch instance created from now can use it. */
3106 if (ssl_store_add_uncommitted_cafile_entry(cafile_entry))
3107 goto error;
3108
3109 memprintf(&err, "New CRL file created '%s'!\n", path);
3110
3111 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3112 return cli_dynmsg(appctx, LOG_NOTICE, err);
3113error:
3114 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3115 return cli_dynerr(appctx, err);
3116}
3117
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003118/* Parsing function of `set ssl crl-file` */
3119static int cli_parse_set_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
3120{
3121 char *err = NULL;
3122 int errcode = 0;
3123 struct buffer *buf;
3124
3125 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
3126 return 1;
3127
3128 if (!*args[3] || !payload)
3129 return cli_err(appctx, "'set ssl crl-file expects a filename and CAs as a payload\n");
3130
3131 /* The operations on the CKCH architecture are locked so we can
3132 * manipulate ckch_store and ckch_inst */
3133 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3134 return cli_err(appctx, "Can't update the CRL file!\nOperations on certificates are currently locked!\n");
3135
3136 if ((buf = alloc_trash_chunk()) == NULL) {
3137 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
3138 errcode |= ERR_ALERT | ERR_FATAL;
3139 goto end;
3140 }
3141
3142 if (!chunk_strcpy(buf, args[3])) {
3143 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
3144 errcode |= ERR_ALERT | ERR_FATAL;
3145 goto end;
3146 }
3147
3148 appctx->ctx.ssl.old_crlfile_entry = NULL;
3149 appctx->ctx.ssl.new_crlfile_entry = NULL;
3150
3151 /* if there is an ongoing transaction */
3152 if (crlfile_transaction.path) {
3153 /* if there is an ongoing transaction, check if this is the same file */
3154 if (strcmp(crlfile_transaction.path, buf->area) != 0) {
3155 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", crlfile_transaction.path, buf->area);
3156 errcode |= ERR_ALERT | ERR_FATAL;
3157 goto end;
3158 }
3159 appctx->ctx.ssl.old_crlfile_entry = crlfile_transaction.old_crlfile_entry;
3160 }
3161 else {
3162 /* lookup for the certificate in the tree */
3163 appctx->ctx.ssl.old_crlfile_entry = ssl_store_get_cafile_entry(buf->area, 0);
3164 }
3165
3166 if (!appctx->ctx.ssl.old_crlfile_entry) {
3167 memprintf(&err, "%sCan't replace a CRL file which is not referenced by the configuration!\n",
3168 err ? err : "");
3169 errcode |= ERR_ALERT | ERR_FATAL;
3170 goto end;
3171 }
3172
3173 if (!appctx->ctx.ssl.path) {
3174 /* this is a new transaction, set the path of the transaction */
3175 appctx->ctx.ssl.path = strdup(appctx->ctx.ssl.old_crlfile_entry->path);
3176 if (!appctx->ctx.ssl.path) {
3177 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
3178 errcode |= ERR_ALERT | ERR_FATAL;
3179 goto end;
3180 }
3181 }
3182
3183 if (appctx->ctx.ssl.new_crlfile_entry)
3184 ssl_store_delete_cafile_entry(appctx->ctx.ssl.new_crlfile_entry);
3185
3186 /* Create a new cafile_entry without adding it to the cafile tree. */
3187 appctx->ctx.ssl.new_crlfile_entry = ssl_store_create_cafile_entry(appctx->ctx.ssl.path, NULL, CAFILE_CRL);
3188 if (!appctx->ctx.ssl.new_crlfile_entry) {
3189 memprintf(&err, "%sCannot allocate memory!\n", err ? err : "");
3190 errcode |= ERR_ALERT | ERR_FATAL;
3191 goto end;
3192 }
3193
3194 /* Fill the new entry with the new CRL. */
3195 if (ssl_store_load_ca_from_buf(appctx->ctx.ssl.new_crlfile_entry, payload)) {
3196 memprintf(&err, "%sInvalid payload\n", err ? err : "");
3197 errcode |= ERR_ALERT | ERR_FATAL;
3198 goto end;
3199 }
3200
3201 /* we succeed, we can save the crl in the transaction */
3202
3203 /* if there wasn't a transaction, update the old CA */
3204 if (!crlfile_transaction.old_crlfile_entry) {
3205 crlfile_transaction.old_crlfile_entry = appctx->ctx.ssl.old_crlfile_entry;
3206 crlfile_transaction.path = appctx->ctx.ssl.path;
3207 err = memprintf(&err, "transaction created for CA %s!\n", crlfile_transaction.path);
3208 } else {
3209 err = memprintf(&err, "transaction updated for CA %s!\n", crlfile_transaction.path);
3210 }
3211
3212 /* free the previous CRL file if there was a transaction */
3213 ssl_store_delete_cafile_entry(crlfile_transaction.new_crlfile_entry);
3214
3215 crlfile_transaction.new_crlfile_entry = appctx->ctx.ssl.new_crlfile_entry;
3216
3217 /* creates the SNI ctxs later in the IO handler */
3218
3219end:
3220 free_trash_chunk(buf);
3221
3222 if (errcode & ERR_CODE) {
3223 ssl_store_delete_cafile_entry(appctx->ctx.ssl.new_crlfile_entry);
3224 appctx->ctx.ssl.new_crlfile_entry = NULL;
3225 appctx->ctx.ssl.old_crlfile_entry = NULL;
3226
3227 free(appctx->ctx.ssl.path);
3228 appctx->ctx.ssl.path = NULL;
3229
3230 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3231 return cli_dynerr(appctx, memprintf(&err, "%sCan't update %s!\n", err ? err : "", args[3]));
3232 } else {
3233
3234 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3235 return cli_dynmsg(appctx, LOG_NOTICE, err);
3236 }
3237}
3238
3239/* Parsing function of 'commit ssl crl-file' */
3240static int cli_parse_commit_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
3241{
3242 char *err = NULL;
3243
3244 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
3245 return 1;
3246
3247 if (!*args[3])
3248 return cli_err(appctx, "'commit ssl ca-file expects a filename\n");
3249
3250 /* The operations on the CKCH architecture are locked so we can
3251 * manipulate ckch_store and ckch_inst */
3252 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3253 return cli_err(appctx, "Can't commit the CRL file!\nOperations on certificates are currently locked!\n");
3254
3255 if (!crlfile_transaction.path) {
3256 memprintf(&err, "No ongoing transaction! !\n");
3257 goto error;
3258 }
3259
3260 if (strcmp(crlfile_transaction.path, args[3]) != 0) {
3261 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", crlfile_transaction.path, args[3]);
3262 goto error;
3263 }
3264 /* init the appctx structure */
3265 appctx->st2 = SETCERT_ST_INIT;
3266 appctx->ctx.ssl.next_ckchi = NULL;
3267 appctx->ctx.ssl.old_crlfile_entry = crlfile_transaction.old_crlfile_entry;
3268 appctx->ctx.ssl.new_crlfile_entry = crlfile_transaction.new_crlfile_entry;
3269 appctx->ctx.ssl.cafile_type = CAFILE_CRL;
3270
3271 return 0;
3272
3273error:
3274
3275 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3276 err = memprintf(&err, "%sCan't commit %s!\n", err ? err : "", args[3]);
3277
3278 return cli_dynerr(appctx, err);
3279}
3280
3281
3282/* release function of the `commit ssl crl-file' command, free things and unlock the spinlock */
3283static void cli_release_commit_crlfile(struct appctx *appctx)
3284{
3285 if (appctx->st2 != SETCERT_ST_FIN) {
3286 struct cafile_entry *new_crlfile_entry = appctx->ctx.ssl.new_crlfile_entry;
3287
3288 /* Remove the uncommitted cafile_entry from the tree. */
3289 ebmb_delete(&new_crlfile_entry->node);
3290 ssl_store_delete_cafile_entry(new_crlfile_entry);
3291 }
3292 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3293}
3294
Remi Tricot-Le Breton720e3b92021-04-26 11:00:42 +02003295/* parsing function of 'del ssl crl-file' */
3296static int cli_parse_del_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
3297{
3298 struct cafile_entry *cafile_entry;
3299 char *err = NULL;
3300 char *filename;
3301
3302 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
3303 return 1;
3304
3305 if (!*args[3])
3306 return cli_err(appctx, "'del ssl crl-file' expects a CRL file name\n");
3307
3308 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3309 return cli_err(appctx, "Can't delete the CRL file!\nOperations on certificates are currently locked!\n");
3310
3311 filename = args[3];
3312
3313 cafile_entry = ssl_store_get_cafile_entry(filename, 0);
3314 if (!cafile_entry) {
3315 memprintf(&err, "CRL file '%s' doesn't exist!\n", filename);
3316 goto error;
3317 }
3318 if (cafile_entry->type != CAFILE_CRL) {
3319 memprintf(&err, "'del ssl crl-file' does not work on CA files!\n");
3320 goto error;
3321 }
3322
3323 if (!LIST_ISEMPTY(&cafile_entry->ckch_inst_link)) {
3324 memprintf(&err, "CRL file '%s' in use, can't be deleted!\n", filename);
3325 goto error;
3326 }
3327
3328 /* Remove the cafile_entry from the tree */
3329 ebmb_delete(&cafile_entry->node);
3330 ssl_store_delete_cafile_entry(cafile_entry);
3331
3332 memprintf(&err, "CRL file '%s' deleted!\n", filename);
3333
3334 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3335 return cli_dynmsg(appctx, LOG_NOTICE, err);
3336
3337error:
3338 memprintf(&err, "Can't remove the CRL file: %s\n", err ? err : "");
3339 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3340 return cli_dynerr(appctx, err);
3341}
3342
Remi Tricot-Le Bretoneef8e7b2021-04-20 17:42:02 +02003343/* parsing function of 'abort ssl crl-file' */
3344static int cli_parse_abort_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
3345{
3346 char *err = NULL;
3347
3348 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
3349 return 1;
3350
3351 if (!*args[3])
3352 return cli_err(appctx, "'abort ssl crl-file' expects a filename\n");
3353
3354 /* The operations on the CKCH architecture are locked so we can
3355 * manipulate ckch_store and ckch_inst */
3356 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3357 return cli_err(appctx, "Can't abort!\nOperations on certificates are currently locked!\n");
3358
3359 if (!crlfile_transaction.path) {
3360 memprintf(&err, "No ongoing transaction!\n");
3361 goto error;
3362 }
3363
3364 if (strcmp(crlfile_transaction.path, args[3]) != 0) {
3365 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to abort a transaction for '%s'\n", crlfile_transaction.path, args[3]);
3366 goto error;
3367 }
3368
3369 /* Only free the uncommitted cafile_entry here, because the SNI and instances were not generated yet */
3370 ssl_store_delete_cafile_entry(crlfile_transaction.new_crlfile_entry);
3371 crlfile_transaction.new_crlfile_entry = NULL;
3372 crlfile_transaction.old_crlfile_entry = NULL;
3373 ha_free(&crlfile_transaction.path);
3374
3375 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3376
3377 err = memprintf(&err, "Transaction aborted for certificate '%s'!\n", args[3]);
3378 return cli_dynmsg(appctx, LOG_NOTICE, err);
3379
3380error:
3381 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3382
3383 return cli_dynerr(appctx, err);
3384}
3385
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003386
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003387/*
3388 * Display a Certificate Resignation List's information.
3389 * The information displayed is inspired by the output of 'openssl crl -in
3390 * crl.pem -text'.
3391 * Returns 0 in case of success.
3392 */
3393static int show_crl_detail(X509_CRL *crl, struct buffer *out)
3394{
3395 BIO *bio = NULL;
3396 struct buffer *tmp = alloc_trash_chunk();
3397 long version;
3398 X509_NAME *issuer;
3399 int write = -1;
3400 STACK_OF(X509_REVOKED) *rev = NULL;
3401 X509_REVOKED *rev_entry = NULL;
3402 int i;
3403
3404 if (!tmp)
3405 return -1;
3406
3407 if ((bio = BIO_new(BIO_s_mem())) == NULL)
3408 goto end;
3409
3410 /* Version (as displayed by 'openssl crl') */
3411 version = X509_CRL_get_version(crl);
3412 chunk_appendf(out, "Version %ld\n", version + 1);
3413
3414 /* Signature Algorithm */
3415 chunk_appendf(out, "Signature Algorithm: %s\n", OBJ_nid2ln(X509_CRL_get_signature_nid(crl)));
3416
3417 /* Issuer */
3418 chunk_appendf(out, "Issuer: ");
3419 if ((issuer = X509_CRL_get_issuer(crl)) == NULL)
3420 goto end;
3421 if ((ssl_sock_get_dn_oneline(issuer, tmp)) == -1)
3422 goto end;
3423 *(tmp->area + tmp->data) = '\0';
3424 chunk_appendf(out, "%s\n", tmp->area);
3425
3426 /* Last Update */
3427 chunk_appendf(out, "Last Update: ");
3428 chunk_reset(tmp);
Remi Tricot-Le Bretond75b99e2021-05-17 11:45:55 +02003429 if (BIO_reset(bio) == -1)
3430 goto end;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003431 if (ASN1_TIME_print(bio, X509_CRL_get0_lastUpdate(crl)) == 0)
3432 goto end;
3433 write = BIO_read(bio, tmp->area, tmp->size-1);
3434 tmp->area[write] = '\0';
3435 chunk_appendf(out, "%s\n", tmp->area);
3436
3437
3438 /* Next Update */
3439 chunk_appendf(out, "Next Update: ");
3440 chunk_reset(tmp);
Remi Tricot-Le Bretond75b99e2021-05-17 11:45:55 +02003441 if (BIO_reset(bio) == -1)
3442 goto end;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003443 if (ASN1_TIME_print(bio, X509_CRL_get0_nextUpdate(crl)) == 0)
3444 goto end;
3445 write = BIO_read(bio, tmp->area, tmp->size-1);
3446 tmp->area[write] = '\0';
3447 chunk_appendf(out, "%s\n", tmp->area);
3448
3449
3450 /* Revoked Certificates */
3451 rev = X509_CRL_get_REVOKED(crl);
3452 if (sk_X509_REVOKED_num(rev) > 0)
3453 chunk_appendf(out, "Revoked Certificates:\n");
3454 else
3455 chunk_appendf(out, "No Revoked Certificates.\n");
3456
3457 for (i = 0; i < sk_X509_REVOKED_num(rev); i++) {
3458 rev_entry = sk_X509_REVOKED_value(rev, i);
3459
3460 /* Serial Number and Revocation Date */
Remi Tricot-Le Bretond75b99e2021-05-17 11:45:55 +02003461 if (BIO_reset(bio) == -1)
3462 goto end;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003463 BIO_printf(bio , " Serial Number: ");
Remi Tricot-Le Breton18c7d832021-05-17 18:38:34 +02003464 i2a_ASN1_INTEGER(bio, (ASN1_INTEGER*)X509_REVOKED_get0_serialNumber(rev_entry));
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003465 BIO_printf(bio, "\n Revocation Date: ");
Remi Tricot-Le Bretona6b27842021-05-18 10:06:00 +02003466 if (ASN1_TIME_print(bio, X509_REVOKED_get0_revocationDate(rev_entry)) == 0)
3467 goto end;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003468 BIO_printf(bio, "\n");
3469
3470 write = BIO_read(bio, tmp->area, tmp->size-1);
3471 tmp->area[write] = '\0';
3472 chunk_appendf(out, "%s", tmp->area);
3473 }
3474
3475end:
3476 free_trash_chunk(tmp);
3477 if (bio)
3478 BIO_free(bio);
3479
3480 return 0;
3481}
3482
3483/* IO handler of details "show ssl crl-file <filename[:index]>" */
3484static int cli_io_handler_show_crlfile_detail(struct appctx *appctx)
3485{
3486 struct stream_interface *si = appctx->owner;
3487 struct cafile_entry *cafile_entry = appctx->ctx.cli.p0;
3488 struct buffer *out = alloc_trash_chunk();
3489 int i;
3490 X509_CRL *crl;
3491 STACK_OF(X509_OBJECT) *objs;
3492 int retval = 0;
3493 long index = (long)appctx->ctx.cli.p1;
3494
3495 if (!out)
3496 goto end_no_putchk;
3497
3498 chunk_appendf(out, "Filename: ");
3499 if (cafile_entry == crlfile_transaction.new_crlfile_entry)
3500 chunk_appendf(out, "*");
3501 chunk_appendf(out, "%s\n", cafile_entry->path);
3502
3503 chunk_appendf(out, "Status: ");
3504 if (!cafile_entry->ca_store)
3505 chunk_appendf(out, "Empty\n");
3506 else if (LIST_ISEMPTY(&cafile_entry->ckch_inst_link))
3507 chunk_appendf(out, "Unused\n");
3508 else
3509 chunk_appendf(out, "Used\n");
3510
3511 if (!cafile_entry->ca_store)
3512 goto end;
3513
3514 objs = X509_STORE_get0_objects(cafile_entry->ca_store);
3515 for (i = 0; i < sk_X509_OBJECT_num(objs); i++) {
3516 crl = X509_OBJECT_get0_X509_CRL(sk_X509_OBJECT_value(objs, i));
3517 if (!crl)
3518 continue;
3519
3520 /* CRL indexes start at 1 on the CLI output. */
3521 if (index && index-1 != i)
3522 continue;
3523
3524 chunk_appendf(out, "\nCertificate Revocation List #%d:\n", i+1);
3525 retval = show_crl_detail(crl, out);
3526 if (retval < 0)
3527 goto end_no_putchk;
3528 else if (retval || index)
3529 goto end;
3530 }
3531
3532end:
3533 if (ci_putchk(si_ic(si), out) == -1) {
3534 si_rx_room_blk(si);
3535 goto yield;
3536 }
3537
3538end_no_putchk:
3539 free_trash_chunk(out);
3540 return 1;
3541yield:
3542 free_trash_chunk(out);
3543 return 0; /* should come back */
3544}
3545
3546/* parsing function for 'show ssl crl-file [crlfile[:index]]' */
3547static int cli_parse_show_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
3548{
3549 struct cafile_entry *cafile_entry;
3550 long index = 0;
3551 char *colons;
3552 char *err = NULL;
3553
3554 if (!cli_has_level(appctx, ACCESS_LVL_OPER))
3555 return cli_err(appctx, "Can't allocate memory!\n");
3556
3557 /* The operations on the CKCH architecture are locked so we can
3558 * manipulate ckch_store and ckch_inst */
3559 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3560 return cli_err(appctx, "Can't show!\nOperations on certificates are currently locked!\n");
3561
3562 /* check if there is a certificate to lookup */
3563 if (*args[3]) {
3564
3565 /* Look for an optional index after the CRL file name */
3566 colons = strchr(args[3], ':');
3567 if (colons) {
3568 char *endptr;
3569
3570 index = strtol(colons + 1, &endptr, 10);
3571 /* Indexes start at 1 */
3572 if (colons + 1 == endptr || *endptr != '\0' || index <= 0) {
3573 memprintf(&err, "wrong CRL index after colons in '%s'!", args[3]);
3574 goto error;
3575 }
3576 *colons = '\0';
3577 }
3578
3579 if (*args[3] == '*') {
3580 if (!crlfile_transaction.new_crlfile_entry)
3581 goto error;
3582
3583 cafile_entry = crlfile_transaction.new_crlfile_entry;
3584
3585 if (strcmp(args[3] + 1, cafile_entry->path) != 0)
3586 goto error;
3587
3588 } else {
3589 /* Get the "original" cafile_entry and not the
3590 * uncommitted one if it exists. */
3591 if ((cafile_entry = ssl_store_get_cafile_entry(args[3], 1)) == NULL || cafile_entry->type != CAFILE_CRL)
3592 goto error;
3593 }
3594
3595 appctx->ctx.cli.p0 = cafile_entry;
3596 appctx->ctx.cli.p1 = (void*)index;
3597 /* use the IO handler that shows details */
3598 appctx->io_handler = cli_io_handler_show_crlfile_detail;
3599 }
3600
3601 return 0;
3602
3603error:
3604 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3605 if (err)
3606 return cli_dynerr(appctx, err);
3607 return cli_err(appctx, "Can't display the CA file : Not found!\n");
3608}
3609
3610/* IO handler of "show ssl crl-file". The command taking a specific CRL file name
3611 * is managed in cli_io_handler_show_crlfile_detail. */
3612static int cli_io_handler_show_crlfile(struct appctx *appctx)
3613{
3614 struct buffer *trash = alloc_trash_chunk();
3615 struct ebmb_node *node;
3616 struct stream_interface *si = appctx->owner;
3617 struct cafile_entry *cafile_entry;
3618
3619 if (trash == NULL)
3620 return 1;
3621
3622 if (!appctx->ctx.ssl.old_crlfile_entry) {
3623 if (crlfile_transaction.old_crlfile_entry) {
3624 chunk_appendf(trash, "# transaction\n");
3625 chunk_appendf(trash, "*%s\n", crlfile_transaction.old_crlfile_entry->path);
3626 }
3627 }
3628
3629 /* First time in this io_handler. */
3630 if (!appctx->ctx.cli.p0) {
3631 chunk_appendf(trash, "# filename\n");
3632 node = ebmb_first(&cafile_tree);
3633 } else {
3634 /* We yielded during a previous call. */
3635 node = &((struct cafile_entry*)appctx->ctx.cli.p0)->node;
3636 }
3637
3638 while (node) {
3639 cafile_entry = ebmb_entry(node, struct cafile_entry, node);
3640 if (cafile_entry->type == CAFILE_CRL) {
3641 chunk_appendf(trash, "%s\n", cafile_entry->path);
3642 }
3643
3644 node = ebmb_next(node);
3645 if (ci_putchk(si_ic(si), trash) == -1) {
3646 si_rx_room_blk(si);
3647 goto yield;
3648 }
3649 }
3650
3651 appctx->ctx.cli.p0 = NULL;
3652 free_trash_chunk(trash);
3653 return 1;
3654yield:
3655
3656 free_trash_chunk(trash);
3657 appctx->ctx.cli.p0 = cafile_entry;
3658 return 0; /* should come back */
3659}
3660
3661
3662/* release function of the 'show ssl crl-file' command */
3663static void cli_release_show_crlfile(struct appctx *appctx)
3664{
3665 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3666}
3667
3668
William Lallemandee8530c2020-06-23 18:19:42 +02003669void ckch_deinit()
3670{
3671 struct eb_node *node, *next;
3672 struct ckch_store *store;
3673
3674 node = eb_first(&ckchs_tree);
3675 while (node) {
3676 next = eb_next(node);
3677 store = ebmb_entry(node, struct ckch_store, node);
3678 ckch_store_free(store);
3679 node = next;
3680 }
3681}
William Lallemandda8584c2020-05-14 10:14:37 +02003682
3683/* register cli keywords */
3684static struct cli_kw_list cli_kws = {{ },{
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01003685 { { "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 },
3686 { { "set", "ssl", "cert", NULL }, "set ssl cert <certfile> <payload> : replace a certificate file", cli_parse_set_cert, NULL, NULL },
3687 { { "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 },
3688 { { "abort", "ssl", "cert", NULL }, "abort ssl cert <certfile> : abort a transaction for a certificate file", cli_parse_abort_cert, NULL, NULL },
3689 { { "del", "ssl", "cert", NULL }, "del ssl cert <certfile> : delete an unused certificate file", cli_parse_del_cert, NULL, NULL },
3690 { { "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 },
3691
Amaury Denoyelleb11ad9e2021-05-21 11:01:10 +02003692 { { "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 +01003693 { { "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 +02003694 { { "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 +01003695 { { "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 +01003696 { { "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 +01003697 { { "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 +02003698
Remi Tricot-Le Breton720e3b92021-04-26 11:00:42 +02003699 { { "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 +02003700 { { "set", "ssl", "crl-file", NULL }, "set ssl crl-file <crlfile> <payload> : replace a CRL file", cli_parse_set_crlfile, NULL, NULL },
3701 { { "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 +02003702 { { "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 +02003703 { { "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 +02003704 { { "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 +02003705 { { NULL }, NULL, NULL, NULL }
3706}};
3707
3708INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws);
3709