blob: eeb031b27e96ca53f0ad351b4561b80a8ae8e81d [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 Tarreau74f24562021-10-06 17:54:12 +020025#include <import/ebpttree.h>
Willy Tarreaub2551052020-06-09 09:07:15 +020026#include <import/ebsttree.h>
27
Willy Tarreau8d366972020-05-27 16:10:29 +020028#include <haproxy/base64.h>
Willy Tarreauf1d32c42020-06-04 21:07:02 +020029#include <haproxy/channel.h>
Willy Tarreau83487a82020-06-04 20:19:54 +020030#include <haproxy/cli.h>
Willy Tarreau8d366972020-05-27 16:10:29 +020031#include <haproxy/errors.h>
Willy Tarreau47d7f902020-06-04 14:25:47 +020032#include <haproxy/ssl_ckch.h>
Willy Tarreau209108d2020-06-04 20:30:20 +020033#include <haproxy/ssl_sock.h>
Willy Tarreaub2bd8652020-06-04 14:21:22 +020034#include <haproxy/ssl_utils.h>
Willy Tarreau5e539c92020-06-04 20:45:39 +020035#include <haproxy/stream_interface.h>
Willy Tarreau48fbcae2020-06-03 18:09:46 +020036#include <haproxy/tools.h>
William Lallemand03c331c2020-05-13 10:10:01 +020037
William Lallemandda8584c2020-05-14 10:14:37 +020038/* Uncommitted CKCH transaction */
39
40static struct {
41 struct ckch_store *new_ckchs;
42 struct ckch_store *old_ckchs;
43 char *path;
44} ckchs_transaction;
45
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +010046/* Uncommitted CA file transaction */
47
48static struct {
49 struct cafile_entry *old_cafile_entry;
50 struct cafile_entry *new_cafile_entry;
51 char *path;
52} cafile_transaction;
William Lallemandda8584c2020-05-14 10:14:37 +020053
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +020054/* Uncommitted CRL file transaction */
55
56static struct {
57 struct cafile_entry *old_crlfile_entry;
58 struct cafile_entry *new_crlfile_entry;
59 char *path;
60} crlfile_transaction;
61
William Lallemand03c331c2020-05-13 10:10:01 +020062
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +010063
William Lallemand03c331c2020-05-13 10:10:01 +020064/******************** cert_key_and_chain functions *************************
65 * These are the functions that fills a cert_key_and_chain structure. For the
66 * functions filling a SSL_CTX from a cert_key_and_chain, see ssl_sock.c
67 */
68
69/*
70 * Try to parse Signed Certificate Timestamp List structure. This function
71 * makes only basic test if the data seems like SCTL. No signature validation
72 * is performed.
73 */
74static int ssl_sock_parse_sctl(struct buffer *sctl)
75{
76 int ret = 1;
77 int len, pos, sct_len;
78 unsigned char *data;
79
80 if (sctl->data < 2)
81 goto out;
82
83 data = (unsigned char *) sctl->area;
84 len = (data[0] << 8) | data[1];
85
86 if (len + 2 != sctl->data)
87 goto out;
88
89 data = data + 2;
90 pos = 0;
91 while (pos < len) {
92 if (len - pos < 2)
93 goto out;
94
95 sct_len = (data[pos] << 8) | data[pos + 1];
96 if (pos + sct_len + 2 > len)
97 goto out;
98
99 pos += sct_len + 2;
100 }
101
102 ret = 0;
103
104out:
105 return ret;
106}
107
108/* Try to load a sctl from a buffer <buf> if not NULL, or read the file <sctl_path>
109 * It fills the ckch->sctl buffer
110 * return 0 on success or != 0 on failure */
111int ssl_sock_load_sctl_from_file(const char *sctl_path, char *buf, struct cert_key_and_chain *ckch, char **err)
112{
113 int fd = -1;
114 int r = 0;
115 int ret = 1;
116 struct buffer tmp;
117 struct buffer *src;
118 struct buffer *sctl;
119
120 if (buf) {
William Lallemand8d673942021-01-27 14:58:51 +0100121 chunk_initstr(&tmp, buf);
William Lallemand03c331c2020-05-13 10:10:01 +0200122 src = &tmp;
123 } else {
124 fd = open(sctl_path, O_RDONLY);
125 if (fd == -1)
126 goto end;
127
128 trash.data = 0;
129 while (trash.data < trash.size) {
130 r = read(fd, trash.area + trash.data, trash.size - trash.data);
131 if (r < 0) {
132 if (errno == EINTR)
133 continue;
134 goto end;
135 }
136 else if (r == 0) {
137 break;
138 }
139 trash.data += r;
140 }
141 src = &trash;
142 }
143
144 ret = ssl_sock_parse_sctl(src);
145 if (ret)
146 goto end;
147
148 sctl = calloc(1, sizeof(*sctl));
149 if (!chunk_dup(sctl, src)) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100150 ha_free(&sctl);
William Lallemand03c331c2020-05-13 10:10:01 +0200151 goto end;
152 }
153 /* no error, fill ckch with new context, old context must be free */
154 if (ckch->sctl) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100155 ha_free(&ckch->sctl->area);
William Lallemand03c331c2020-05-13 10:10:01 +0200156 free(ckch->sctl);
157 }
158 ckch->sctl = sctl;
159 ret = 0;
160end:
161 if (fd != -1)
162 close(fd);
163
164 return ret;
165}
166
167#if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) || defined OPENSSL_IS_BORINGSSL)
168/*
Ilya Shipitsin46a030c2020-07-05 16:36:08 +0500169 * This function load the OCSP Response in DER format contained in file at
William Lallemand03c331c2020-05-13 10:10:01 +0200170 * path 'ocsp_path' or base64 in a buffer <buf>
171 *
172 * Returns 0 on success, 1 in error case.
173 */
174int ssl_sock_load_ocsp_response_from_file(const char *ocsp_path, char *buf, struct cert_key_and_chain *ckch, char **err)
175{
176 int fd = -1;
177 int r = 0;
178 int ret = 1;
179 struct buffer *ocsp_response;
180 struct buffer *src = NULL;
181
182 if (buf) {
183 int i, j;
184 /* if it's from a buffer it will be base64 */
185
186 /* remove \r and \n from the payload */
187 for (i = 0, j = 0; buf[i]; i++) {
188 if (buf[i] == '\r' || buf[i] == '\n')
189 continue;
190 buf[j++] = buf[i];
191 }
192 buf[j] = 0;
193
194 ret = base64dec(buf, j, trash.area, trash.size);
195 if (ret < 0) {
196 memprintf(err, "Error reading OCSP response in base64 format");
197 goto end;
198 }
199 trash.data = ret;
200 src = &trash;
201 } else {
202 fd = open(ocsp_path, O_RDONLY);
203 if (fd == -1) {
204 memprintf(err, "Error opening OCSP response file");
205 goto end;
206 }
207
208 trash.data = 0;
209 while (trash.data < trash.size) {
210 r = read(fd, trash.area + trash.data, trash.size - trash.data);
211 if (r < 0) {
212 if (errno == EINTR)
213 continue;
214
215 memprintf(err, "Error reading OCSP response from file");
216 goto end;
217 }
218 else if (r == 0) {
219 break;
220 }
221 trash.data += r;
222 }
223 close(fd);
224 fd = -1;
225 src = &trash;
226 }
227
228 ocsp_response = calloc(1, sizeof(*ocsp_response));
229 if (!chunk_dup(ocsp_response, src)) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100230 ha_free(&ocsp_response);
William Lallemand03c331c2020-05-13 10:10:01 +0200231 goto end;
232 }
233 /* no error, fill ckch with new context, old context must be free */
234 if (ckch->ocsp_response) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100235 ha_free(&ckch->ocsp_response->area);
William Lallemand03c331c2020-05-13 10:10:01 +0200236 free(ckch->ocsp_response);
237 }
238 ckch->ocsp_response = ocsp_response;
239 ret = 0;
240end:
241 if (fd != -1)
242 close(fd);
243
244 return ret;
245}
246#endif
247
248/*
249 * Try to load in a ckch every files related to a ckch.
250 * (PEM, sctl, ocsp, issuer etc.)
251 *
252 * This function is only used to load files during the configuration parsing,
253 * it is not used with the CLI.
254 *
255 * This allows us to carry the contents of the file without having to read the
256 * file multiple times. The caller must call
257 * ssl_sock_free_cert_key_and_chain_contents.
258 *
259 * returns:
260 * 0 on Success
261 * 1 on SSL Failure
262 */
263int ssl_sock_load_files_into_ckch(const char *path, struct cert_key_and_chain *ckch, char **err)
264{
William Lallemand8e8581e2020-10-20 17:36:46 +0200265 struct buffer *fp = NULL;
William Lallemand03c331c2020-05-13 10:10:01 +0200266 int ret = 1;
267
268 /* try to load the PEM */
269 if (ssl_sock_load_pem_into_ckch(path, NULL, ckch , err) != 0) {
270 goto end;
271 }
272
William Lallemand8e8581e2020-10-20 17:36:46 +0200273 fp = alloc_trash_chunk();
274 if (!fp) {
275 memprintf(err, "%sCan't allocate memory\n", err && *err ? *err : "");
276 goto end;
277 }
278
279 if (!chunk_strcpy(fp, path) || (b_data(fp) > MAXPATHLEN)) {
280 memprintf(err, "%s '%s' filename too long'.\n",
281 err && *err ? *err : "", fp->area);
282 ret = 1;
283 goto end;
284 }
285
William Lallemand089c1382020-10-23 17:35:12 +0200286 /* remove the ".crt" extension */
William Lallemand8e8581e2020-10-20 17:36:46 +0200287 if (global_ssl.extra_files_noext) {
288 char *ext;
289
290 /* look for the extension */
291 if ((ext = strrchr(fp->area, '.'))) {
William Lallemand8e8581e2020-10-20 17:36:46 +0200292
Tim Duesterhuse5ff1412021-01-02 22:31:53 +0100293 if (strcmp(ext, ".crt") == 0) {
William Lallemand8e8581e2020-10-20 17:36:46 +0200294 *ext = '\0';
William Lallemand089c1382020-10-23 17:35:12 +0200295 fp->data = strlen(fp->area);
296 }
William Lallemand8e8581e2020-10-20 17:36:46 +0200297 }
298
299 }
300
William Lallemand03c331c2020-05-13 10:10:01 +0200301 /* try to load an external private key if it wasn't in the PEM */
302 if ((ckch->key == NULL) && (global_ssl.extra_files & SSL_GF_KEY)) {
William Lallemand03c331c2020-05-13 10:10:01 +0200303 struct stat st;
304
William Lallemand8e8581e2020-10-20 17:36:46 +0200305
306 if (!chunk_strcat(fp, ".key") || (b_data(fp) > MAXPATHLEN)) {
307 memprintf(err, "%s '%s' filename too long'.\n",
308 err && *err ? *err : "", fp->area);
309 ret = 1;
310 goto end;
311 }
312
313 if (stat(fp->area, &st) == 0) {
314 if (ssl_sock_load_key_into_ckch(fp->area, NULL, ckch, err)) {
William Lallemand03c331c2020-05-13 10:10:01 +0200315 memprintf(err, "%s '%s' is present but cannot be read or parsed'.\n",
William Lallemand8e8581e2020-10-20 17:36:46 +0200316 err && *err ? *err : "", fp->area);
William Lallemand03c331c2020-05-13 10:10:01 +0200317 goto end;
318 }
319 }
William Lallemand03c331c2020-05-13 10:10:01 +0200320
William Lallemand8e8581e2020-10-20 17:36:46 +0200321 if (ckch->key == NULL) {
322 memprintf(err, "%sNo Private Key found in '%s'.\n", err && *err ? *err : "", fp->area);
323 goto end;
324 }
325 /* remove the added extension */
326 *(fp->area + fp->data - strlen(".key")) = '\0';
327 b_sub(fp, strlen(".key"));
William Lallemand03c331c2020-05-13 10:10:01 +0200328 }
329
330 if (!X509_check_private_key(ckch->cert, ckch->key)) {
331 memprintf(err, "%sinconsistencies between private key and certificate loaded '%s'.\n",
332 err && *err ? *err : "", path);
333 goto end;
334 }
335
Ilya Shipitsinc47d6762021-02-13 11:45:33 +0500336#ifdef HAVE_SSL_SCTL
William Lallemand03c331c2020-05-13 10:10:01 +0200337 /* try to load the sctl file */
338 if (global_ssl.extra_files & SSL_GF_SCTL) {
William Lallemand03c331c2020-05-13 10:10:01 +0200339 struct stat st;
340
William Lallemand8e8581e2020-10-20 17:36:46 +0200341 if (!chunk_strcat(fp, ".sctl") || b_data(fp) > MAXPATHLEN) {
342 memprintf(err, "%s '%s' filename too long'.\n",
343 err && *err ? *err : "", fp->area);
344 ret = 1;
345 goto end;
346 }
347
348 if (stat(fp->area, &st) == 0) {
349 if (ssl_sock_load_sctl_from_file(fp->area, NULL, ckch, err)) {
William Lallemand03c331c2020-05-13 10:10:01 +0200350 memprintf(err, "%s '%s.sctl' is present but cannot be read or parsed'.\n",
William Lallemand8e8581e2020-10-20 17:36:46 +0200351 err && *err ? *err : "", fp->area);
William Lallemand03c331c2020-05-13 10:10:01 +0200352 ret = 1;
353 goto end;
354 }
355 }
William Lallemand8e8581e2020-10-20 17:36:46 +0200356 /* remove the added extension */
357 *(fp->area + fp->data - strlen(".sctl")) = '\0';
358 b_sub(fp, strlen(".sctl"));
William Lallemand03c331c2020-05-13 10:10:01 +0200359 }
360#endif
361
362 /* try to load an ocsp response file */
363 if (global_ssl.extra_files & SSL_GF_OCSP) {
William Lallemand03c331c2020-05-13 10:10:01 +0200364 struct stat st;
365
William Lallemand8e8581e2020-10-20 17:36:46 +0200366 if (!chunk_strcat(fp, ".ocsp") || b_data(fp) > MAXPATHLEN) {
367 memprintf(err, "%s '%s' filename too long'.\n",
368 err && *err ? *err : "", fp->area);
369 ret = 1;
370 goto end;
371 }
372
373 if (stat(fp->area, &st) == 0) {
374 if (ssl_sock_load_ocsp_response_from_file(fp->area, NULL, ckch, err)) {
William Lallemand03c331c2020-05-13 10:10:01 +0200375 ret = 1;
376 goto end;
377 }
378 }
William Lallemand8e8581e2020-10-20 17:36:46 +0200379 /* remove the added extension */
380 *(fp->area + fp->data - strlen(".ocsp")) = '\0';
381 b_sub(fp, strlen(".ocsp"));
William Lallemand03c331c2020-05-13 10:10:01 +0200382 }
383
384#ifndef OPENSSL_IS_BORINGSSL /* Useless for BoringSSL */
385 if (ckch->ocsp_response && (global_ssl.extra_files & SSL_GF_OCSP_ISSUER)) {
386 /* if no issuer was found, try to load an issuer from the .issuer */
387 if (!ckch->ocsp_issuer) {
388 struct stat st;
William Lallemand8e8581e2020-10-20 17:36:46 +0200389
390 if (!chunk_strcat(fp, ".issuer") || b_data(fp) > MAXPATHLEN) {
391 memprintf(err, "%s '%s' filename too long'.\n",
392 err && *err ? *err : "", fp->area);
393 ret = 1;
394 goto end;
395 }
William Lallemand03c331c2020-05-13 10:10:01 +0200396
William Lallemand8e8581e2020-10-20 17:36:46 +0200397 if (stat(fp->area, &st) == 0) {
398 if (ssl_sock_load_issuer_file_into_ckch(fp->area, NULL, ckch, err)) {
William Lallemand03c331c2020-05-13 10:10:01 +0200399 ret = 1;
400 goto end;
401 }
402
403 if (X509_check_issued(ckch->ocsp_issuer, ckch->cert) != X509_V_OK) {
404 memprintf(err, "%s '%s' is not an issuer'.\n",
William Lallemand8e8581e2020-10-20 17:36:46 +0200405 err && *err ? *err : "", fp->area);
William Lallemand03c331c2020-05-13 10:10:01 +0200406 ret = 1;
407 goto end;
408 }
409 }
William Lallemand8e8581e2020-10-20 17:36:46 +0200410 /* remove the added extension */
411 *(fp->area + fp->data - strlen(".issuer")) = '\0';
412 b_sub(fp, strlen(".issuer"));
William Lallemand03c331c2020-05-13 10:10:01 +0200413 }
414 }
415#endif
416
417 ret = 0;
418
419end:
420
421 ERR_clear_error();
422
423 /* Something went wrong in one of the reads */
424 if (ret != 0)
425 ssl_sock_free_cert_key_and_chain_contents(ckch);
426
William Lallemand8e8581e2020-10-20 17:36:46 +0200427 free_trash_chunk(fp);
428
William Lallemand03c331c2020-05-13 10:10:01 +0200429 return ret;
430}
431
432/*
433 * Try to load a private key file from a <path> or a buffer <buf>
434 *
435 * If it failed you should not attempt to use the ckch but free it.
436 *
437 * Return 0 on success or != 0 on failure
438 */
439int ssl_sock_load_key_into_ckch(const char *path, char *buf, struct cert_key_and_chain *ckch , char **err)
440{
441 BIO *in = NULL;
442 int ret = 1;
443 EVP_PKEY *key = NULL;
444
445 if (buf) {
446 /* reading from a buffer */
447 in = BIO_new_mem_buf(buf, -1);
448 if (in == NULL) {
449 memprintf(err, "%sCan't allocate memory\n", err && *err ? *err : "");
450 goto end;
451 }
452
453 } else {
454 /* reading from a file */
455 in = BIO_new(BIO_s_file());
456 if (in == NULL)
457 goto end;
458
459 if (BIO_read_filename(in, path) <= 0)
460 goto end;
461 }
462
463 /* Read Private Key */
464 key = PEM_read_bio_PrivateKey(in, NULL, NULL, NULL);
465 if (key == NULL) {
466 memprintf(err, "%sunable to load private key from file '%s'.\n",
467 err && *err ? *err : "", path);
468 goto end;
469 }
470
471 ret = 0;
472
473 SWAP(ckch->key, key);
474
475end:
476
477 ERR_clear_error();
478 if (in)
479 BIO_free(in);
480 if (key)
481 EVP_PKEY_free(key);
482
483 return ret;
484}
485
486/*
487 * Try to load a PEM file from a <path> or a buffer <buf>
488 * The PEM must contain at least a Certificate,
489 * It could contain a DH, a certificate chain and a PrivateKey.
490 *
491 * If it failed you should not attempt to use the ckch but free it.
492 *
493 * Return 0 on success or != 0 on failure
494 */
495int ssl_sock_load_pem_into_ckch(const char *path, char *buf, struct cert_key_and_chain *ckch , char **err)
496{
497 BIO *in = NULL;
498 int ret = 1;
499 X509 *ca;
500 X509 *cert = NULL;
501 EVP_PKEY *key = NULL;
502 DH *dh = NULL;
503 STACK_OF(X509) *chain = NULL;
504
505 if (buf) {
506 /* reading from a buffer */
507 in = BIO_new_mem_buf(buf, -1);
508 if (in == NULL) {
509 memprintf(err, "%sCan't allocate memory\n", err && *err ? *err : "");
510 goto end;
511 }
512
513 } else {
514 /* reading from a file */
515 in = BIO_new(BIO_s_file());
516 if (in == NULL) {
517 memprintf(err, "%sCan't allocate memory\n", err && *err ? *err : "");
518 goto end;
519 }
520
521 if (BIO_read_filename(in, path) <= 0) {
522 memprintf(err, "%scannot open the file '%s'.\n",
523 err && *err ? *err : "", path);
524 goto end;
525 }
526 }
527
528 /* Read Private Key */
529 key = PEM_read_bio_PrivateKey(in, NULL, NULL, NULL);
530 /* no need to check for errors here, because the private key could be loaded later */
531
532#ifndef OPENSSL_NO_DH
533 /* Seek back to beginning of file */
534 if (BIO_reset(in) == -1) {
535 memprintf(err, "%san error occurred while reading the file '%s'.\n",
536 err && *err ? *err : "", path);
537 goto end;
538 }
539
540 dh = PEM_read_bio_DHparams(in, NULL, NULL, NULL);
541 /* no need to return an error there, dh is not mandatory */
542#endif
543
544 /* Seek back to beginning of file */
545 if (BIO_reset(in) == -1) {
546 memprintf(err, "%san error occurred while reading the file '%s'.\n",
547 err && *err ? *err : "", path);
548 goto end;
549 }
550
551 /* Read Certificate */
552 cert = PEM_read_bio_X509_AUX(in, NULL, NULL, NULL);
553 if (cert == NULL) {
554 memprintf(err, "%sunable to load certificate from file '%s'.\n",
555 err && *err ? *err : "", path);
556 goto end;
557 }
558
559 /* Look for a Certificate Chain */
560 while ((ca = PEM_read_bio_X509(in, NULL, NULL, NULL))) {
561 if (chain == NULL)
562 chain = sk_X509_new_null();
563 if (!sk_X509_push(chain, ca)) {
564 X509_free(ca);
565 goto end;
566 }
567 }
568
569 ret = ERR_get_error();
570 if (ret && (ERR_GET_LIB(ret) != ERR_LIB_PEM && ERR_GET_REASON(ret) != PEM_R_NO_START_LINE)) {
571 memprintf(err, "%sunable to load certificate chain from file '%s'.\n",
572 err && *err ? *err : "", path);
573 goto end;
574 }
575
576 /* once it loaded the PEM, it should remove everything else in the ckch */
577 if (ckch->ocsp_response) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100578 ha_free(&ckch->ocsp_response->area);
579 ha_free(&ckch->ocsp_response);
William Lallemand03c331c2020-05-13 10:10:01 +0200580 }
581
582 if (ckch->sctl) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100583 ha_free(&ckch->sctl->area);
584 ha_free(&ckch->sctl);
William Lallemand03c331c2020-05-13 10:10:01 +0200585 }
586
587 if (ckch->ocsp_issuer) {
588 X509_free(ckch->ocsp_issuer);
589 ckch->ocsp_issuer = NULL;
590 }
591
592 /* no error, fill ckch with new context, old context will be free at end: */
593 SWAP(ckch->key, key);
594 SWAP(ckch->dh, dh);
595 SWAP(ckch->cert, cert);
596 SWAP(ckch->chain, chain);
597
598 ret = 0;
599
600end:
601
602 ERR_clear_error();
603 if (in)
604 BIO_free(in);
605 if (key)
606 EVP_PKEY_free(key);
607 if (dh)
608 DH_free(dh);
609 if (cert)
610 X509_free(cert);
611 if (chain)
612 sk_X509_pop_free(chain, X509_free);
613
614 return ret;
615}
616
617/* Frees the contents of a cert_key_and_chain
618 */
619void ssl_sock_free_cert_key_and_chain_contents(struct cert_key_and_chain *ckch)
620{
621 if (!ckch)
622 return;
623
624 /* Free the certificate and set pointer to NULL */
625 if (ckch->cert)
626 X509_free(ckch->cert);
627 ckch->cert = NULL;
628
629 /* Free the key and set pointer to NULL */
630 if (ckch->key)
631 EVP_PKEY_free(ckch->key);
632 ckch->key = NULL;
633
634 /* Free each certificate in the chain */
635 if (ckch->chain)
636 sk_X509_pop_free(ckch->chain, X509_free);
637 ckch->chain = NULL;
638
639 if (ckch->dh)
640 DH_free(ckch->dh);
641 ckch->dh = NULL;
642
643 if (ckch->sctl) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100644 ha_free(&ckch->sctl->area);
645 ha_free(&ckch->sctl);
William Lallemand03c331c2020-05-13 10:10:01 +0200646 }
647
648 if (ckch->ocsp_response) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100649 ha_free(&ckch->ocsp_response->area);
650 ha_free(&ckch->ocsp_response);
William Lallemand03c331c2020-05-13 10:10:01 +0200651 }
652
653 if (ckch->ocsp_issuer)
654 X509_free(ckch->ocsp_issuer);
655 ckch->ocsp_issuer = NULL;
656}
657
658/*
659 *
660 * This function copy a cert_key_and_chain in memory
661 *
662 * It's used to try to apply changes on a ckch before committing them, because
663 * most of the time it's not possible to revert those changes
664 *
665 * Return a the dst or NULL
666 */
667struct cert_key_and_chain *ssl_sock_copy_cert_key_and_chain(struct cert_key_and_chain *src,
668 struct cert_key_and_chain *dst)
669{
William Lallemand6c096142021-02-23 14:45:45 +0100670 if (!src || !dst)
671 return NULL;
672
William Lallemand03c331c2020-05-13 10:10:01 +0200673 if (src->cert) {
674 dst->cert = src->cert;
675 X509_up_ref(src->cert);
676 }
677
678 if (src->key) {
679 dst->key = src->key;
680 EVP_PKEY_up_ref(src->key);
681 }
682
683 if (src->chain) {
684 dst->chain = X509_chain_up_ref(src->chain);
685 }
686
687 if (src->dh) {
688 DH_up_ref(src->dh);
689 dst->dh = src->dh;
690 }
691
692 if (src->sctl) {
693 struct buffer *sctl;
694
695 sctl = calloc(1, sizeof(*sctl));
696 if (!chunk_dup(sctl, src->sctl)) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100697 ha_free(&sctl);
William Lallemand03c331c2020-05-13 10:10:01 +0200698 goto error;
699 }
700 dst->sctl = sctl;
701 }
702
703 if (src->ocsp_response) {
704 struct buffer *ocsp_response;
705
706 ocsp_response = calloc(1, sizeof(*ocsp_response));
707 if (!chunk_dup(ocsp_response, src->ocsp_response)) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100708 ha_free(&ocsp_response);
William Lallemand03c331c2020-05-13 10:10:01 +0200709 goto error;
710 }
711 dst->ocsp_response = ocsp_response;
712 }
713
714 if (src->ocsp_issuer) {
715 X509_up_ref(src->ocsp_issuer);
716 dst->ocsp_issuer = src->ocsp_issuer;
717 }
718
719 return dst;
720
721error:
722
723 /* free everything */
724 ssl_sock_free_cert_key_and_chain_contents(dst);
725
726 return NULL;
727}
728
729/*
730 * return 0 on success or != 0 on failure
731 */
732int ssl_sock_load_issuer_file_into_ckch(const char *path, char *buf, struct cert_key_and_chain *ckch, char **err)
733{
734 int ret = 1;
735 BIO *in = NULL;
736 X509 *issuer;
737
738 if (buf) {
739 /* reading from a buffer */
740 in = BIO_new_mem_buf(buf, -1);
741 if (in == NULL) {
742 memprintf(err, "%sCan't allocate memory\n", err && *err ? *err : "");
743 goto end;
744 }
745
746 } else {
747 /* reading from a file */
748 in = BIO_new(BIO_s_file());
749 if (in == NULL)
750 goto end;
751
752 if (BIO_read_filename(in, path) <= 0)
753 goto end;
754 }
755
756 issuer = PEM_read_bio_X509_AUX(in, NULL, NULL, NULL);
757 if (!issuer) {
758 memprintf(err, "%s'%s' cannot be read or parsed'.\n",
759 err && *err ? *err : "", path);
760 goto end;
761 }
762 /* no error, fill ckch with new context, old context must be free */
763 if (ckch->ocsp_issuer)
764 X509_free(ckch->ocsp_issuer);
765 ckch->ocsp_issuer = issuer;
766 ret = 0;
767
768end:
769
770 ERR_clear_error();
771 if (in)
772 BIO_free(in);
773
774 return ret;
775}
776
777/******************** ckch_store functions ***********************************
778 * The ckch_store is a structure used to cache and index the SSL files used in
779 * configuration
780 */
781
782/*
783 * Free a ckch_store, its ckch, its instances and remove it from the ebtree
784 */
785void ckch_store_free(struct ckch_store *store)
786{
787 struct ckch_inst *inst, *inst_s;
788
789 if (!store)
790 return;
791
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200792 ssl_sock_free_cert_key_and_chain_contents(store->ckch);
William Lallemand03c331c2020-05-13 10:10:01 +0200793
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100794 ha_free(&store->ckch);
William Lallemand03c331c2020-05-13 10:10:01 +0200795
796 list_for_each_entry_safe(inst, inst_s, &store->ckch_inst, by_ckchs) {
797 ckch_inst_free(inst);
798 }
799 ebmb_delete(&store->node);
800 free(store);
801}
802
803/*
804 * create and initialize a ckch_store
805 * <path> is the key name
806 * <nmemb> is the number of store->ckch objects to allocate
807 *
808 * Return a ckch_store or NULL upon failure.
809 */
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200810struct ckch_store *ckch_store_new(const char *filename)
William Lallemand03c331c2020-05-13 10:10:01 +0200811{
812 struct ckch_store *store;
813 int pathlen;
814
815 pathlen = strlen(filename);
816 store = calloc(1, sizeof(*store) + pathlen + 1);
817 if (!store)
818 return NULL;
819
William Lallemand03c331c2020-05-13 10:10:01 +0200820 memcpy(store->path, filename, pathlen + 1);
821
822 LIST_INIT(&store->ckch_inst);
823 LIST_INIT(&store->crtlist_entry);
824
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200825 store->ckch = calloc(1, sizeof(*store->ckch));
William Lallemand03c331c2020-05-13 10:10:01 +0200826 if (!store->ckch)
827 goto error;
828
829 return store;
830error:
831 ckch_store_free(store);
832 return NULL;
833}
834
835/* allocate and duplicate a ckch_store
836 * Return a new ckch_store or NULL */
837struct ckch_store *ckchs_dup(const struct ckch_store *src)
838{
839 struct ckch_store *dst;
840
William Lallemand6c096142021-02-23 14:45:45 +0100841 if (!src)
842 return NULL;
843
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200844 dst = ckch_store_new(src->path);
Eric Salama6ac61e32021-02-23 16:50:57 +0100845 if (!dst)
846 return NULL;
William Lallemand03c331c2020-05-13 10:10:01 +0200847
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200848 if (!ssl_sock_copy_cert_key_and_chain(src->ckch, dst->ckch))
849 goto error;
William Lallemand03c331c2020-05-13 10:10:01 +0200850
851 return dst;
852
853error:
854 ckch_store_free(dst);
855
856 return NULL;
857}
858
859/*
860 * lookup a path into the ckchs tree.
861 */
862struct ckch_store *ckchs_lookup(char *path)
863{
864 struct ebmb_node *eb;
865
866 eb = ebst_lookup(&ckchs_tree, path);
867 if (!eb)
868 return NULL;
869
870 return ebmb_entry(eb, struct ckch_store, node);
871}
872
873/*
874 * This function allocate a ckch_store and populate it with certificates from files.
875 */
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200876struct ckch_store *ckchs_load_cert_file(char *path, char **err)
William Lallemand03c331c2020-05-13 10:10:01 +0200877{
878 struct ckch_store *ckchs;
879
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200880 ckchs = ckch_store_new(path);
William Lallemand03c331c2020-05-13 10:10:01 +0200881 if (!ckchs) {
882 memprintf(err, "%sunable to allocate memory.\n", err && *err ? *err : "");
883 goto end;
884 }
William Lallemand03c331c2020-05-13 10:10:01 +0200885
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200886 if (ssl_sock_load_files_into_ckch(path, ckchs->ckch, err) == 1)
887 goto end;
William Lallemand03c331c2020-05-13 10:10:01 +0200888
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200889 /* insert into the ckchs tree */
890 memcpy(ckchs->path, path, strlen(path) + 1);
891 ebst_insert(&ckchs_tree, &ckchs->node);
William Lallemand03c331c2020-05-13 10:10:01 +0200892 return ckchs;
893
894end:
895 ckch_store_free(ckchs);
896
897 return NULL;
898}
899
William Lallemandfa1d8b42020-05-13 15:46:10 +0200900
901/******************** ckch_inst functions ******************************/
902
903/* unlink a ckch_inst, free all SNIs, free the ckch_inst */
904/* The caller must use the lock of the bind_conf if used with inserted SNIs */
905void ckch_inst_free(struct ckch_inst *inst)
906{
907 struct sni_ctx *sni, *sni_s;
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +0100908 struct ckch_inst_link_ref *link_ref, *link_ref_s;
William Lallemandfa1d8b42020-05-13 15:46:10 +0200909
910 if (inst == NULL)
911 return;
912
913 list_for_each_entry_safe(sni, sni_s, &inst->sni_ctx, by_ckch_inst) {
914 SSL_CTX_free(sni->ctx);
Willy Tarreau2b718102021-04-21 07:32:39 +0200915 LIST_DELETE(&sni->by_ckch_inst);
William Lallemandfa1d8b42020-05-13 15:46:10 +0200916 ebmb_delete(&sni->name);
917 free(sni);
918 }
Remi Tricot-Le Bretonf3eedfe2021-01-25 17:19:44 +0100919 SSL_CTX_free(inst->ctx);
920 inst->ctx = NULL;
Willy Tarreau2b718102021-04-21 07:32:39 +0200921 LIST_DELETE(&inst->by_ckchs);
922 LIST_DELETE(&inst->by_crtlist_entry);
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +0100923
924 list_for_each_entry_safe(link_ref, link_ref_s, &inst->cafile_link_refs, list) {
925 LIST_DELETE(&link_ref->link->list);
926 LIST_DELETE(&link_ref->list);
927 free(link_ref);
928 }
929
William Lallemandfa1d8b42020-05-13 15:46:10 +0200930 free(inst);
931}
932
933/* Alloc and init a ckch_inst */
934struct ckch_inst *ckch_inst_new()
935{
936 struct ckch_inst *ckch_inst;
937
938 ckch_inst = calloc(1, sizeof *ckch_inst);
939 if (!ckch_inst)
940 return NULL;
941
942 LIST_INIT(&ckch_inst->sni_ctx);
943 LIST_INIT(&ckch_inst->by_ckchs);
944 LIST_INIT(&ckch_inst->by_crtlist_entry);
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +0100945 LIST_INIT(&ckch_inst->cafile_link_refs);
William Lallemandfa1d8b42020-05-13 15:46:10 +0200946
947 return ckch_inst;
948}
949
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +0200950
951/******************** ssl_store functions ******************************/
Remi Tricot-Le Breton9f0c9362021-02-19 15:06:28 +0100952struct eb_root cafile_tree = EB_ROOT;
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +0200953
Remi Tricot-Le Breton9f0c9362021-02-19 15:06:28 +0100954/*
955 * Returns the cafile_entry found in the cafile_tree indexed by the path 'path'.
956 * If 'oldest_entry' is 1, returns the "original" cafile_entry (since
957 * during a set cafile/commit cafile cycle there might be two entries for any
958 * given path, the original one and the new one set via the CLI but not
959 * committed yet).
960 */
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +0100961struct cafile_entry *ssl_store_get_cafile_entry(char *path, int oldest_entry)
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +0200962{
Remi Tricot-Le Breton9f0c9362021-02-19 15:06:28 +0100963 struct cafile_entry *ca_e = NULL;
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +0200964 struct ebmb_node *eb;
965
966 eb = ebst_lookup(&cafile_tree, path);
Remi Tricot-Le Breton9f0c9362021-02-19 15:06:28 +0100967 while (eb) {
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +0200968 ca_e = ebmb_entry(eb, struct cafile_entry, node);
Remi Tricot-Le Breton9f0c9362021-02-19 15:06:28 +0100969 /* The ebst_lookup in a tree that has duplicates returns the
970 * oldest entry first. If we want the latest entry, we need to
971 * iterate over all the duplicates until we find the last one
972 * (in our case there should never be more than two entries for
973 * any given path). */
974 if (oldest_entry)
975 return ca_e;
976 eb = ebmb_next_dup(eb);
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +0200977 }
Remi Tricot-Le Breton9f0c9362021-02-19 15:06:28 +0100978 return ca_e;
979}
980
Remi Tricot-Le Breton38c999b2021-02-23 16:28:43 +0100981int ssl_store_add_uncommitted_cafile_entry(struct cafile_entry *entry)
982{
983 return (ebst_insert(&cafile_tree, &entry->node) != &entry->node);
984}
985
Remi Tricot-Le Breton9f0c9362021-02-19 15:06:28 +0100986X509_STORE* ssl_store_get0_locations_file(char *path)
987{
988 struct cafile_entry *ca_e = ssl_store_get_cafile_entry(path, 0);
989
990 if (ca_e)
991 return ca_e->ca_store;
992
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +0200993 return NULL;
994}
995
Remi Tricot-Le Breton5daff3c2021-02-22 15:54:55 +0100996/* Create a cafile_entry object, without adding it to the cafile_tree. */
Remi Tricot-Le Breton0bb48242021-04-16 17:59:23 +0200997struct 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 +0100998{
999 struct cafile_entry *ca_e;
1000 int pathlen;
1001
1002 pathlen = strlen(path);
1003
1004 ca_e = calloc(1, sizeof(*ca_e) + pathlen + 1);
1005 if (ca_e) {
1006 memcpy(ca_e->path, path, pathlen + 1);
1007 ca_e->ca_store = store;
Remi Tricot-Le Breton0bb48242021-04-16 17:59:23 +02001008 ca_e->type = type;
Remi Tricot-Le Breton5daff3c2021-02-22 15:54:55 +01001009 LIST_INIT(&ca_e->ckch_inst_link);
1010 }
1011 return ca_e;
1012}
1013
1014/* Delete a cafile_entry. The caller is responsible from removing this entry
1015 * from the cafile_tree first if is was previously added into it. */
1016void ssl_store_delete_cafile_entry(struct cafile_entry *ca_e)
1017{
1018 struct ckch_inst_link *link, *link_s;
1019 if (!ca_e)
1020 return;
1021
1022 X509_STORE_free(ca_e->ca_store);
1023
1024 list_for_each_entry_safe(link, link_s, &ca_e->ckch_inst_link, list) {
1025 struct ckch_inst *inst = link->ckch_inst;
1026 struct ckch_inst_link_ref *link_ref, *link_ref_s;
1027 list_for_each_entry_safe(link_ref, link_ref_s, &inst->cafile_link_refs, list) {
1028 if (link_ref->link == link) {
1029 LIST_DELETE(&link_ref->list);
1030 free(link_ref);
1031 break;
1032 }
1033 }
1034 LIST_DELETE(&link->list);
1035 free(link);
1036 }
1037
1038 free(ca_e);
1039}
1040
Remi Tricot-Le Breton383fb142021-02-22 18:26:14 +01001041/*
1042 * Build a cafile_entry out of a buffer instead of out of a file.
1043 * This function is used when the "commit ssl ca-file" cli command is used.
1044 * It can parse CERTIFICATE sections as well as CRL ones.
1045 * Returns 0 in case of success, 1 otherwise.
1046 */
1047int ssl_store_load_ca_from_buf(struct cafile_entry *ca_e, char *cert_buf)
1048{
1049 int retval = 0;
1050
1051 if (!ca_e)
1052 return 1;
1053
1054 if (!ca_e->ca_store) {
1055 ca_e->ca_store = X509_STORE_new();
1056 if (ca_e->ca_store) {
1057 BIO *bio = BIO_new_mem_buf(cert_buf, strlen(cert_buf));
1058 if (bio) {
1059 X509_INFO *info;
1060 int i;
1061 STACK_OF(X509_INFO) *infos = PEM_X509_INFO_read_bio(bio, NULL, NULL, NULL);
1062 if (!infos)
1063 {
1064 BIO_free(bio);
1065 return 1;
1066 }
1067
1068 for (i = 0; i < sk_X509_INFO_num(infos) && !retval; i++) {
1069 info = sk_X509_INFO_value(infos, i);
1070 /* X509_STORE_add_cert and X509_STORE_add_crl return 1 on success */
1071 if (info->x509) {
1072 retval = !X509_STORE_add_cert(ca_e->ca_store, info->x509);
1073 }
1074 if (!retval && info->crl) {
1075 retval = !X509_STORE_add_crl(ca_e->ca_store, info->crl);
1076 }
1077 }
1078 retval = retval || (i != sk_X509_INFO_num(infos));
1079
1080 /* Cleanup */
1081 sk_X509_INFO_pop_free(infos, X509_INFO_free);
1082 BIO_free(bio);
1083 }
1084 }
1085 }
1086
1087 return retval;
1088}
1089
Remi Tricot-Le Breton0bb48242021-04-16 17:59:23 +02001090int ssl_store_load_locations_file(char *path, int create_if_none, enum cafile_type type)
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001091{
1092 X509_STORE *store = ssl_store_get0_locations_file(path);
1093
1094 /* If this function is called by the CLI, we should not call the
1095 * X509_STORE_load_locations function because it performs forbidden disk
1096 * accesses. */
1097 if (!store && create_if_none) {
1098 struct cafile_entry *ca_e;
1099 store = X509_STORE_new();
1100 if (X509_STORE_load_locations(store, path, NULL)) {
Remi Tricot-Le Breton0bb48242021-04-16 17:59:23 +02001101 ca_e = ssl_store_create_cafile_entry(path, store, type);
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001102 if (ca_e) {
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001103 ebst_insert(&cafile_tree, &ca_e->node);
1104 }
1105 } else {
1106 X509_STORE_free(store);
1107 store = NULL;
1108 }
1109 }
1110 return (store != NULL);
1111}
1112
1113
William Lallemandda8584c2020-05-14 10:14:37 +02001114/*************************** CLI commands ***********************/
1115
1116/* Type of SSL payloads that can be updated over the CLI */
1117
1118enum {
1119 CERT_TYPE_PEM = 0,
1120 CERT_TYPE_KEY,
1121#if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) || defined OPENSSL_IS_BORINGSSL)
1122 CERT_TYPE_OCSP,
1123#endif
1124 CERT_TYPE_ISSUER,
Ilya Shipitsinc47d6762021-02-13 11:45:33 +05001125#ifdef HAVE_SSL_SCTL
William Lallemandda8584c2020-05-14 10:14:37 +02001126 CERT_TYPE_SCTL,
1127#endif
1128 CERT_TYPE_MAX,
1129};
1130
1131struct {
1132 const char *ext;
1133 int type;
1134 int (*load)(const char *path, char *payload, struct cert_key_and_chain *ckch, char **err);
1135 /* add a parsing callback */
1136} cert_exts[CERT_TYPE_MAX+1] = {
1137 [CERT_TYPE_PEM] = { "", CERT_TYPE_PEM, &ssl_sock_load_pem_into_ckch }, /* default mode, no extensions */
1138 [CERT_TYPE_KEY] = { "key", CERT_TYPE_KEY, &ssl_sock_load_key_into_ckch },
1139#if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) || defined OPENSSL_IS_BORINGSSL)
1140 [CERT_TYPE_OCSP] = { "ocsp", CERT_TYPE_OCSP, &ssl_sock_load_ocsp_response_from_file },
1141#endif
Ilya Shipitsinc47d6762021-02-13 11:45:33 +05001142#ifdef HAVE_SSL_SCTL
William Lallemandda8584c2020-05-14 10:14:37 +02001143 [CERT_TYPE_SCTL] = { "sctl", CERT_TYPE_SCTL, &ssl_sock_load_sctl_from_file },
1144#endif
1145 [CERT_TYPE_ISSUER] = { "issuer", CERT_TYPE_ISSUER, &ssl_sock_load_issuer_file_into_ckch },
1146 [CERT_TYPE_MAX] = { NULL, CERT_TYPE_MAX, NULL },
1147};
1148
1149
1150/* release function of the `show ssl cert' command */
1151static void cli_release_show_cert(struct appctx *appctx)
1152{
1153 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1154}
1155
1156/* IO handler of "show ssl cert <filename>" */
1157static int cli_io_handler_show_cert(struct appctx *appctx)
1158{
1159 struct buffer *trash = alloc_trash_chunk();
1160 struct ebmb_node *node;
1161 struct stream_interface *si = appctx->owner;
1162 struct ckch_store *ckchs;
1163
1164 if (trash == NULL)
1165 return 1;
1166
1167 if (!appctx->ctx.ssl.old_ckchs) {
1168 if (ckchs_transaction.old_ckchs) {
1169 ckchs = ckchs_transaction.old_ckchs;
1170 chunk_appendf(trash, "# transaction\n");
William Lallemand5685ccf2020-09-16 16:12:25 +02001171 chunk_appendf(trash, "*%s\n", ckchs->path);
William Lallemandda8584c2020-05-14 10:14:37 +02001172 }
1173 }
1174
1175 if (!appctx->ctx.cli.p0) {
1176 chunk_appendf(trash, "# filename\n");
1177 node = ebmb_first(&ckchs_tree);
1178 } else {
1179 node = &((struct ckch_store *)appctx->ctx.cli.p0)->node;
1180 }
1181 while (node) {
1182 ckchs = ebmb_entry(node, struct ckch_store, node);
William Lallemand5685ccf2020-09-16 16:12:25 +02001183 chunk_appendf(trash, "%s\n", ckchs->path);
William Lallemandda8584c2020-05-14 10:14:37 +02001184
1185 node = ebmb_next(node);
1186 if (ci_putchk(si_ic(si), trash) == -1) {
1187 si_rx_room_blk(si);
1188 goto yield;
1189 }
1190 }
1191
1192 appctx->ctx.cli.p0 = NULL;
1193 free_trash_chunk(trash);
1194 return 1;
1195yield:
1196
1197 free_trash_chunk(trash);
1198 appctx->ctx.cli.p0 = ckchs;
1199 return 0; /* should come back */
1200}
1201
1202/*
1203 * Extract and format the DNS SAN extensions and copy result into a chuink
1204 * Return 0;
1205 */
1206#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
1207static int ssl_sock_get_san_oneline(X509 *cert, struct buffer *out)
1208{
1209 int i;
1210 char *str;
1211 STACK_OF(GENERAL_NAME) *names = NULL;
1212
1213 names = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
1214 if (names) {
1215 for (i = 0; i < sk_GENERAL_NAME_num(names); i++) {
1216 GENERAL_NAME *name = sk_GENERAL_NAME_value(names, i);
1217 if (i > 0)
1218 chunk_appendf(out, ", ");
1219 if (name->type == GEN_DNS) {
1220 if (ASN1_STRING_to_UTF8((unsigned char **)&str, name->d.dNSName) >= 0) {
1221 chunk_appendf(out, "DNS:%s", str);
1222 OPENSSL_free(str);
1223 }
1224 }
1225 }
1226 sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free);
1227 }
1228 return 0;
1229}
1230#endif
1231
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001232/*
1233 * Build the ckch_inst_link that will be chained in the CA file entry and the
1234 * corresponding ckch_inst_link_ref that will be chained in the ckch instance.
1235 * Return 0 in case of success.
1236 */
1237static int do_chain_inst_and_cafile(struct cafile_entry *cafile_entry, struct ckch_inst *ckch_inst)
1238{
1239 struct ckch_inst_link *new_link;
1240 if (!LIST_ISEMPTY(&cafile_entry->ckch_inst_link)) {
1241 struct ckch_inst_link *link = LIST_ELEM(cafile_entry->ckch_inst_link.n,
1242 typeof(link), list);
1243 /* Do not add multiple references to the same
1244 * instance in a cafile_entry */
1245 if (link->ckch_inst == ckch_inst) {
1246 return 1;
1247 }
1248 }
1249
1250 new_link = calloc(1, sizeof(*new_link));
1251 if (new_link) {
1252 struct ckch_inst_link_ref *new_link_ref = calloc(1, sizeof(*new_link_ref));
1253 if (!new_link_ref) {
1254 free(new_link);
1255 return 1;
1256 }
1257
1258 new_link->ckch_inst = ckch_inst;
1259 new_link_ref->link = new_link;
1260 LIST_INIT(&new_link->list);
1261 LIST_INIT(&new_link_ref->list);
1262
1263 LIST_APPEND(&cafile_entry->ckch_inst_link, &new_link->list);
1264 LIST_APPEND(&ckch_inst->cafile_link_refs, &new_link_ref->list);
1265 }
1266
1267 return 0;
1268}
1269
1270
1271/*
1272 * Link a CA file tree entry to the ckch instance that uses it.
1273 * To determine if and which CA file tree entries need to be linked to the
1274 * instance, we follow the same logic performed in ssl_sock_prepare_ctx when
1275 * processing the verify option.
1276 * This function works for a frontend as well as for a backend, depending on the
1277 * configuration parameters given (bind_conf or server).
1278 */
1279void ckch_inst_add_cafile_link(struct ckch_inst *ckch_inst, struct bind_conf *bind_conf,
1280 struct ssl_bind_conf *ssl_conf, const struct server *srv)
1281{
1282 int verify = SSL_VERIFY_NONE;
1283
1284 if (srv) {
1285
1286 if (global.ssl_server_verify == SSL_SERVER_VERIFY_REQUIRED)
1287 verify = SSL_VERIFY_PEER;
1288 switch (srv->ssl_ctx.verify) {
1289 case SSL_SOCK_VERIFY_NONE:
1290 verify = SSL_VERIFY_NONE;
1291 break;
1292 case SSL_SOCK_VERIFY_REQUIRED:
1293 verify = SSL_VERIFY_PEER;
1294 break;
1295 }
1296 }
1297 else {
1298 switch ((ssl_conf && ssl_conf->verify) ? ssl_conf->verify : bind_conf->ssl_conf.verify) {
1299 case SSL_SOCK_VERIFY_NONE:
1300 verify = SSL_VERIFY_NONE;
1301 break;
1302 case SSL_SOCK_VERIFY_OPTIONAL:
1303 verify = SSL_VERIFY_PEER;
1304 break;
1305 case SSL_SOCK_VERIFY_REQUIRED:
1306 verify = SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
1307 break;
1308 }
1309 }
1310
1311 if (verify & SSL_VERIFY_PEER) {
1312 struct cafile_entry *ca_file_entry = NULL;
1313 struct cafile_entry *ca_verify_file_entry = NULL;
Remi Tricot-Le Bretonf81c70c2021-04-20 16:54:21 +02001314 struct cafile_entry *crl_file_entry = NULL;
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001315 if (srv) {
1316 if (srv->ssl_ctx.ca_file) {
1317 ca_file_entry = ssl_store_get_cafile_entry(srv->ssl_ctx.ca_file, 0);
1318
1319 }
Remi Tricot-Le Bretonf81c70c2021-04-20 16:54:21 +02001320 if (srv->ssl_ctx.crl_file) {
1321 crl_file_entry = ssl_store_get_cafile_entry(srv->ssl_ctx.crl_file, 0);
1322 }
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001323 }
1324 else {
1325 char *ca_file = (ssl_conf && ssl_conf->ca_file) ? ssl_conf->ca_file : bind_conf->ssl_conf.ca_file;
1326 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 +02001327 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 +01001328
1329 if (ca_file)
1330 ca_file_entry = ssl_store_get_cafile_entry(ca_file, 0);
1331 if (ca_verify_file)
1332 ca_verify_file_entry = ssl_store_get_cafile_entry(ca_verify_file, 0);
Remi Tricot-Le Bretonf81c70c2021-04-20 16:54:21 +02001333 if (crl_file)
1334 crl_file_entry = ssl_store_get_cafile_entry(crl_file, 0);
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001335 }
1336
1337 if (ca_file_entry) {
1338 /* If we have a ckch instance that is not already in the
1339 * cafile_entry's list, add it to it. */
1340 if (do_chain_inst_and_cafile(ca_file_entry, ckch_inst))
1341 return;
1342
1343 }
1344 if (ca_verify_file_entry && (ca_file_entry != ca_verify_file_entry)) {
1345 /* If we have a ckch instance that is not already in the
1346 * cafile_entry's list, add it to it. */
1347 if (do_chain_inst_and_cafile(ca_verify_file_entry, ckch_inst))
1348 return;
1349 }
Remi Tricot-Le Bretonf81c70c2021-04-20 16:54:21 +02001350 if (crl_file_entry) {
1351 /* If we have a ckch instance that is not already in the
1352 * cafile_entry's list, add it to it. */
1353 if (do_chain_inst_and_cafile(crl_file_entry, ckch_inst))
1354 return;
1355 }
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001356 }
1357}
1358
William Lallemandda8584c2020-05-14 10:14:37 +02001359
1360
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001361static int show_cert_detail(X509 *cert, STACK_OF(X509) *chain, struct buffer *out)
William Lallemandda8584c2020-05-14 10:14:37 +02001362{
William Lallemandda8584c2020-05-14 10:14:37 +02001363 BIO *bio = NULL;
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001364 struct buffer *tmp = alloc_trash_chunk();
William Lallemandda8584c2020-05-14 10:14:37 +02001365 int i;
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001366 int write = -1;
1367 unsigned int len = 0;
1368 X509_NAME *name = NULL;
William Lallemandda8584c2020-05-14 10:14:37 +02001369
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001370 if (!tmp)
1371 return -1;
William Lallemandda8584c2020-05-14 10:14:37 +02001372
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001373 if (!cert)
William Lallemand5685ccf2020-09-16 16:12:25 +02001374 goto end;
William Lallemandda8584c2020-05-14 10:14:37 +02001375
William Lallemand5685ccf2020-09-16 16:12:25 +02001376 if (chain == NULL) {
1377 struct issuer_chain *issuer;
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001378 issuer = ssl_get0_issuer_chain(cert);
William Lallemand5685ccf2020-09-16 16:12:25 +02001379 if (issuer) {
1380 chain = issuer->chain;
1381 chunk_appendf(out, "Chain Filename: ");
1382 chunk_appendf(out, "%s\n", issuer->path);
William Lallemandda8584c2020-05-14 10:14:37 +02001383 }
William Lallemand5685ccf2020-09-16 16:12:25 +02001384 }
1385 chunk_appendf(out, "Serial: ");
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001386 if (ssl_sock_get_serial(cert, tmp) == -1)
William Lallemand5685ccf2020-09-16 16:12:25 +02001387 goto end;
1388 dump_binary(out, tmp->area, tmp->data);
1389 chunk_appendf(out, "\n");
William Lallemandda8584c2020-05-14 10:14:37 +02001390
William Lallemand5685ccf2020-09-16 16:12:25 +02001391 chunk_appendf(out, "notBefore: ");
1392 chunk_reset(tmp);
1393 if ((bio = BIO_new(BIO_s_mem())) == NULL)
1394 goto end;
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001395 if (ASN1_TIME_print(bio, X509_getm_notBefore(cert)) == 0)
William Lallemand5685ccf2020-09-16 16:12:25 +02001396 goto end;
1397 write = BIO_read(bio, tmp->area, tmp->size-1);
1398 tmp->area[write] = '\0';
1399 BIO_free(bio);
1400 bio = NULL;
1401 chunk_appendf(out, "%s\n", tmp->area);
William Lallemandda8584c2020-05-14 10:14:37 +02001402
William Lallemand5685ccf2020-09-16 16:12:25 +02001403 chunk_appendf(out, "notAfter: ");
1404 chunk_reset(tmp);
1405 if ((bio = BIO_new(BIO_s_mem())) == NULL)
1406 goto end;
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001407 if (ASN1_TIME_print(bio, X509_getm_notAfter(cert)) == 0)
William Lallemand5685ccf2020-09-16 16:12:25 +02001408 goto end;
1409 if ((write = BIO_read(bio, tmp->area, tmp->size-1)) <= 0)
1410 goto end;
1411 tmp->area[write] = '\0';
1412 BIO_free(bio);
1413 bio = NULL;
1414 chunk_appendf(out, "%s\n", tmp->area);
William Lallemandda8584c2020-05-14 10:14:37 +02001415
1416#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
William Lallemand5685ccf2020-09-16 16:12:25 +02001417 chunk_appendf(out, "Subject Alternative Name: ");
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001418 if (ssl_sock_get_san_oneline(cert, out) == -1)
William Lallemand5685ccf2020-09-16 16:12:25 +02001419 goto end;
1420 *(out->area + out->data) = '\0';
1421 chunk_appendf(out, "\n");
William Lallemandda8584c2020-05-14 10:14:37 +02001422#endif
William Lallemand5685ccf2020-09-16 16:12:25 +02001423 chunk_reset(tmp);
1424 chunk_appendf(out, "Algorithm: ");
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001425 if (cert_get_pkey_algo(cert, tmp) == 0)
William Lallemand5685ccf2020-09-16 16:12:25 +02001426 goto end;
1427 chunk_appendf(out, "%s\n", tmp->area);
William Lallemandda8584c2020-05-14 10:14:37 +02001428
William Lallemand5685ccf2020-09-16 16:12:25 +02001429 chunk_reset(tmp);
1430 chunk_appendf(out, "SHA1 FingerPrint: ");
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001431 if (X509_digest(cert, EVP_sha1(), (unsigned char *) tmp->area, &len) == 0)
William Lallemand5685ccf2020-09-16 16:12:25 +02001432 goto end;
1433 tmp->data = len;
1434 dump_binary(out, tmp->area, tmp->data);
1435 chunk_appendf(out, "\n");
William Lallemandda8584c2020-05-14 10:14:37 +02001436
William Lallemand5685ccf2020-09-16 16:12:25 +02001437 chunk_appendf(out, "Subject: ");
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001438 if ((name = X509_get_subject_name(cert)) == NULL)
William Lallemand5685ccf2020-09-16 16:12:25 +02001439 goto end;
1440 if ((ssl_sock_get_dn_oneline(name, tmp)) == -1)
1441 goto end;
1442 *(tmp->area + tmp->data) = '\0';
1443 chunk_appendf(out, "%s\n", tmp->area);
1444
1445 chunk_appendf(out, "Issuer: ");
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001446 if ((name = X509_get_issuer_name(cert)) == NULL)
William Lallemand5685ccf2020-09-16 16:12:25 +02001447 goto end;
1448 if ((ssl_sock_get_dn_oneline(name, tmp)) == -1)
1449 goto end;
1450 *(tmp->area + tmp->data) = '\0';
1451 chunk_appendf(out, "%s\n", tmp->area);
1452
1453 /* Displays subject of each certificate in the chain */
1454 for (i = 0; i < sk_X509_num(chain); i++) {
1455 X509 *ca = sk_X509_value(chain, i);
1456
1457 chunk_appendf(out, "Chain Subject: ");
1458 if ((name = X509_get_subject_name(ca)) == NULL)
William Lallemandda8584c2020-05-14 10:14:37 +02001459 goto end;
1460 if ((ssl_sock_get_dn_oneline(name, tmp)) == -1)
1461 goto end;
1462 *(tmp->area + tmp->data) = '\0';
1463 chunk_appendf(out, "%s\n", tmp->area);
1464
William Lallemand5685ccf2020-09-16 16:12:25 +02001465 chunk_appendf(out, "Chain Issuer: ");
1466 if ((name = X509_get_issuer_name(ca)) == NULL)
William Lallemandda8584c2020-05-14 10:14:37 +02001467 goto end;
1468 if ((ssl_sock_get_dn_oneline(name, tmp)) == -1)
1469 goto end;
1470 *(tmp->area + tmp->data) = '\0';
1471 chunk_appendf(out, "%s\n", tmp->area);
William Lallemandda8584c2020-05-14 10:14:37 +02001472 }
1473
1474end:
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001475 if (bio)
1476 BIO_free(bio);
1477 free_trash_chunk(tmp);
1478
1479 return 0;
1480}
1481
Remi Tricot-Le Breton3faf0cb2021-06-10 18:10:32 +02001482#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 +02001483/*
1484 * Build the OCSP tree entry's key for a given ckch_store.
1485 * Returns a negative value in case of error.
1486 */
1487static int ckch_store_build_certid(struct ckch_store *ckch_store, unsigned char certid[128], unsigned int *key_length)
1488{
1489 OCSP_RESPONSE *resp;
1490 OCSP_BASICRESP *bs = NULL;
1491 OCSP_SINGLERESP *sr;
1492 OCSP_CERTID *id;
1493 unsigned char *p = NULL;
1494
1495 if (!key_length)
1496 return -1;
1497
1498 *key_length = 0;
1499
1500 if (!ckch_store->ckch->ocsp_response)
1501 return 0;
1502
1503 p = (unsigned char *) ckch_store->ckch->ocsp_response->area;
1504
1505 resp = d2i_OCSP_RESPONSE(NULL, (const unsigned char **)&p,
1506 ckch_store->ckch->ocsp_response->data);
1507 if (!resp) {
1508 goto end;
1509 }
1510
1511 bs = OCSP_response_get1_basic(resp);
1512 if (!bs) {
1513 goto end;
1514 }
1515
1516 sr = OCSP_resp_get0(bs, 0);
1517 if (!sr) {
1518 goto end;
1519 }
1520
1521 id = (OCSP_CERTID*)OCSP_SINGLERESP_get0_id(sr);
1522
1523 p = certid;
1524 *key_length = i2d_OCSP_CERTID(id, &p);
1525
1526end:
1527 return *key_length > 0;
1528}
1529#endif
1530
1531/*
1532 * Dump the OCSP certificate key (if it exists) of certificate <ckch> into
1533 * buffer <out>.
1534 * Returns 0 in case of success.
1535 */
1536static int ckch_store_show_ocsp_certid(struct ckch_store *ckch_store, struct buffer *out)
1537{
Remi Tricot-Le Breton3faf0cb2021-06-10 18:10:32 +02001538#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 +02001539 unsigned char key[OCSP_MAX_CERTID_ASN1_LENGTH] = {};
1540 unsigned int key_length = 0;
1541 int i;
1542
1543 if (ckch_store_build_certid(ckch_store, (unsigned char*)key, &key_length) >= 0) {
1544 /* Dump the CERTID info */
1545 chunk_appendf(out, "OCSP Response Key: ");
1546 for (i = 0; i < key_length; ++i) {
1547 chunk_appendf(out, "%02x", key[i]);
1548 }
1549 chunk_appendf(out, "\n");
1550 }
1551#endif
1552
1553 return 0;
1554}
1555
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001556
1557/* IO handler of the details "show ssl cert <filename>" */
1558static int cli_io_handler_show_cert_detail(struct appctx *appctx)
1559{
1560 struct stream_interface *si = appctx->owner;
1561 struct ckch_store *ckchs = appctx->ctx.cli.p0;
1562 struct buffer *out = alloc_trash_chunk();
1563 int retval = 0;
1564
1565 if (!out)
1566 goto end_no_putchk;
1567
1568 chunk_appendf(out, "Filename: ");
1569 if (ckchs == ckchs_transaction.new_ckchs)
1570 chunk_appendf(out, "*");
1571 chunk_appendf(out, "%s\n", ckchs->path);
1572
1573 chunk_appendf(out, "Status: ");
1574 if (ckchs->ckch->cert == NULL)
1575 chunk_appendf(out, "Empty\n");
1576 else if (LIST_ISEMPTY(&ckchs->ckch_inst))
1577 chunk_appendf(out, "Unused\n");
1578 else
1579 chunk_appendf(out, "Used\n");
1580
1581 retval = show_cert_detail(ckchs->ckch->cert, ckchs->ckch->chain, out);
1582 if (retval < 0)
1583 goto end_no_putchk;
1584 else if (retval)
1585 goto end;
1586
Remi Tricot-Le Bretonda968f62021-06-10 13:51:14 +02001587 ckch_store_show_ocsp_certid(ckchs, out);
1588
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001589end:
William Lallemandda8584c2020-05-14 10:14:37 +02001590 if (ci_putchk(si_ic(si), out) == -1) {
1591 si_rx_room_blk(si);
1592 goto yield;
1593 }
1594
1595end_no_putchk:
William Lallemandda8584c2020-05-14 10:14:37 +02001596 free_trash_chunk(out);
1597 return 1;
1598yield:
William Lallemandda8584c2020-05-14 10:14:37 +02001599 free_trash_chunk(out);
1600 return 0; /* should come back */
1601}
1602
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001603
1604/* IO handler of the details "show ssl cert <filename.ocsp>" */
1605static int cli_io_handler_show_cert_ocsp_detail(struct appctx *appctx)
1606{
Remi Tricot-Le Breton3faf0cb2021-06-10 18:10:32 +02001607#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 +02001608 struct stream_interface *si = appctx->owner;
1609 struct ckch_store *ckchs = appctx->ctx.cli.p0;
1610 struct buffer *out = alloc_trash_chunk();
1611 int from_transaction = appctx->ctx.cli.i0;
1612
1613 if (!out)
1614 goto end_no_putchk;
1615
1616 /* If we try to display an ongoing transaction's OCSP response, we
1617 * need to dump the ckch's ocsp_response buffer directly.
1618 * Otherwise, we must rebuild the certificate's certid in order to
1619 * look for the current OCSP response in the tree. */
1620 if (from_transaction && ckchs->ckch->ocsp_response) {
1621 ssl_ocsp_response_print(ckchs->ckch->ocsp_response, out);
1622 }
1623 else {
1624 unsigned char key[OCSP_MAX_CERTID_ASN1_LENGTH] = {};
1625 unsigned int key_length = 0;
1626
1627 if (ckch_store_build_certid(ckchs, (unsigned char*)key, &key_length) < 0)
1628 goto end_no_putchk;
1629
1630 ssl_get_ocspresponse_detail(key, out);
1631 }
1632
1633 if (ci_putchk(si_ic(si), out) == -1) {
1634 si_rx_room_blk(si);
1635 goto yield;
1636 }
1637
1638end_no_putchk:
1639 free_trash_chunk(out);
1640 return 1;
1641yield:
1642 free_trash_chunk(out);
1643 return 0; /* should come back */
1644#else
1645 return cli_err(appctx, "HAProxy was compiled against a version of OpenSSL that doesn't support OCSP stapling.\n");
1646#endif
1647}
1648
William Lallemandda8584c2020-05-14 10:14:37 +02001649/* parsing function for 'show ssl cert [certfile]' */
1650static int cli_parse_show_cert(char **args, char *payload, struct appctx *appctx, void *private)
1651{
1652 struct ckch_store *ckchs;
1653
1654 if (!cli_has_level(appctx, ACCESS_LVL_OPER))
1655 return cli_err(appctx, "Can't allocate memory!\n");
1656
1657 /* The operations on the CKCH architecture are locked so we can
1658 * manipulate ckch_store and ckch_inst */
1659 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
1660 return cli_err(appctx, "Can't show!\nOperations on certificates are currently locked!\n");
1661
1662 /* check if there is a certificate to lookup */
1663 if (*args[3]) {
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001664 int show_ocsp_detail = 0;
1665 int from_transaction = 0;
1666 char *end;
1667
1668 /* We manage the special case "certname.ocsp" through which we
1669 * can show the details of an OCSP response. */
1670 end = strrchr(args[3], '.');
1671 if (end && strcmp(end+1, "ocsp") == 0) {
1672 *end = '\0';
1673 show_ocsp_detail = 1;
1674 }
1675
William Lallemandda8584c2020-05-14 10:14:37 +02001676 if (*args[3] == '*') {
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001677 from_transaction = 1;
William Lallemandda8584c2020-05-14 10:14:37 +02001678 if (!ckchs_transaction.new_ckchs)
1679 goto error;
1680
1681 ckchs = ckchs_transaction.new_ckchs;
1682
Tim Duesterhuse5ff1412021-01-02 22:31:53 +01001683 if (strcmp(args[3] + 1, ckchs->path) != 0)
William Lallemandda8584c2020-05-14 10:14:37 +02001684 goto error;
1685
1686 } else {
1687 if ((ckchs = ckchs_lookup(args[3])) == NULL)
1688 goto error;
1689
1690 }
1691
William Lallemandda8584c2020-05-14 10:14:37 +02001692 appctx->ctx.cli.p0 = ckchs;
1693 /* use the IO handler that shows details */
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001694 if (show_ocsp_detail) {
1695 appctx->ctx.cli.i0 = from_transaction;
1696 appctx->io_handler = cli_io_handler_show_cert_ocsp_detail;
1697 }
1698 else
1699 appctx->io_handler = cli_io_handler_show_cert_detail;
William Lallemandda8584c2020-05-14 10:14:37 +02001700 }
1701
1702 return 0;
1703
1704error:
1705 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1706 return cli_err(appctx, "Can't display the certificate: Not found or the certificate is a bundle!\n");
1707}
1708
1709/* release function of the `set ssl cert' command, free things and unlock the spinlock */
1710static void cli_release_commit_cert(struct appctx *appctx)
1711{
1712 struct ckch_store *new_ckchs;
1713
1714 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1715
1716 if (appctx->st2 != SETCERT_ST_FIN) {
1717 /* free every new sni_ctx and the new store, which are not in the trees so no spinlock there */
1718 new_ckchs = appctx->ctx.ssl.new_ckchs;
1719
1720 /* if the allocation failed, we need to free everything from the temporary list */
1721 ckch_store_free(new_ckchs);
1722 }
1723}
1724
Remi Tricot-Le Bretonbfadc022021-02-24 12:20:48 +01001725
1726/*
1727 * Rebuild a new instance 'new_inst' based on an old instance 'ckchi' and a
1728 * specific ckch_store.
1729 * Returns 0 in case of success, 1 otherwise.
1730 */
1731static int ckch_inst_rebuild(struct ckch_store *ckch_store, struct ckch_inst *ckchi,
1732 struct ckch_inst **new_inst, char **err)
1733{
1734 int retval = 0;
1735 int errcode = 0;
1736 struct sni_ctx *sc0, *sc0s;
1737 char **sni_filter = NULL;
1738 int fcount = 0;
1739
1740 if (ckchi->crtlist_entry) {
1741 sni_filter = ckchi->crtlist_entry->filters;
1742 fcount = ckchi->crtlist_entry->fcount;
1743 }
1744
1745 if (ckchi->is_server_instance)
1746 errcode |= ckch_inst_new_load_srv_store(ckch_store->path, ckch_store, new_inst, err);
1747 else
1748 errcode |= ckch_inst_new_load_store(ckch_store->path, ckch_store, ckchi->bind_conf, ckchi->ssl_conf, sni_filter, fcount, new_inst, err);
1749
1750 if (errcode & ERR_CODE)
1751 return 1;
1752
1753 /* if the previous ckchi was used as the default */
1754 if (ckchi->is_default)
1755 (*new_inst)->is_default = 1;
1756
1757 (*new_inst)->is_server_instance = ckchi->is_server_instance;
1758 (*new_inst)->server = ckchi->server;
1759 /* Create a new SSL_CTX and link it to the new instance. */
1760 if ((*new_inst)->is_server_instance) {
1761 retval = ssl_sock_prep_srv_ctx_and_inst(ckchi->server, (*new_inst)->ctx, (*new_inst));
1762 if (retval)
1763 return 1;
1764 }
1765
1766 /* create the link to the crtlist_entry */
1767 (*new_inst)->crtlist_entry = ckchi->crtlist_entry;
1768
1769 /* we need to initialize the SSL_CTX generated */
1770 /* this iterate on the newly generated SNIs in the new instance to prepare their SSL_CTX */
1771 list_for_each_entry_safe(sc0, sc0s, &(*new_inst)->sni_ctx, by_ckch_inst) {
1772 if (!sc0->order) { /* we initialized only the first SSL_CTX because it's the same in the other sni_ctx's */
1773 errcode |= ssl_sock_prep_ctx_and_inst(ckchi->bind_conf, ckchi->ssl_conf, sc0->ctx, *new_inst, err);
1774 if (errcode & ERR_CODE)
1775 return 1;
1776 }
1777 }
1778
1779 return 0;
1780}
1781
1782/*
1783 * Load all the new SNIs of a newly built ckch instance in the trees, or replace
1784 * a server's main ckch instance.
1785 */
1786static void __ssl_sock_load_new_ckch_instance(struct ckch_inst *ckchi)
1787{
1788 /* The bind_conf will be null on server ckch_instances. */
1789 if (ckchi->is_server_instance) {
1790 int i;
1791 /* a lock is needed here since we have to free the SSL cache */
1792 HA_RWLOCK_WRLOCK(SSL_SERVER_LOCK, &ckchi->server->ssl_ctx.lock);
1793 /* free the server current SSL_CTX */
1794 SSL_CTX_free(ckchi->server->ssl_ctx.ctx);
1795 /* Actual ssl context update */
1796 SSL_CTX_up_ref(ckchi->ctx);
1797 ckchi->server->ssl_ctx.ctx = ckchi->ctx;
1798 ckchi->server->ssl_ctx.inst = ckchi;
1799
1800 /* flush the session cache of the server */
1801 for (i = 0; i < global.nbthread; i++) {
1802 ha_free(&ckchi->server->ssl_ctx.reused_sess[i].ptr);
1803 }
1804 HA_RWLOCK_WRUNLOCK(SSL_SERVER_LOCK, &ckchi->server->ssl_ctx.lock);
1805
1806 } else {
1807 HA_RWLOCK_WRLOCK(SNI_LOCK, &ckchi->bind_conf->sni_lock);
1808 ssl_sock_load_cert_sni(ckchi, ckchi->bind_conf);
1809 HA_RWLOCK_WRUNLOCK(SNI_LOCK, &ckchi->bind_conf->sni_lock);
1810 }
1811}
1812
1813/*
1814 * Delete a ckch instance that was replaced after a CLI command.
1815 */
1816static void __ckch_inst_free_locked(struct ckch_inst *ckchi)
1817{
1818 if (ckchi->is_server_instance) {
1819 /* no lock for servers */
1820 ckch_inst_free(ckchi);
1821 } else {
1822 struct bind_conf __maybe_unused *bind_conf = ckchi->bind_conf;
1823
1824 HA_RWLOCK_WRLOCK(SNI_LOCK, &bind_conf->sni_lock);
1825 ckch_inst_free(ckchi);
1826 HA_RWLOCK_WRUNLOCK(SNI_LOCK, &bind_conf->sni_lock);
1827 }
1828}
1829
1830
William Lallemandda8584c2020-05-14 10:14:37 +02001831/*
1832 * This function tries to create the new ckch_inst and their SNIs
1833 */
1834static int cli_io_handler_commit_cert(struct appctx *appctx)
1835{
1836 struct stream_interface *si = appctx->owner;
1837 int y = 0;
1838 char *err = NULL;
William Lallemandda8584c2020-05-14 10:14:37 +02001839 struct ckch_store *old_ckchs, *new_ckchs = NULL;
1840 struct ckch_inst *ckchi, *ckchis;
1841 struct buffer *trash = alloc_trash_chunk();
William Lallemandda8584c2020-05-14 10:14:37 +02001842 struct crtlist_entry *entry;
1843
1844 if (trash == NULL)
1845 goto error;
1846
1847 if (unlikely(si_ic(si)->flags & (CF_WRITE_ERROR|CF_SHUTW)))
1848 goto error;
1849
1850 while (1) {
1851 switch (appctx->st2) {
1852 case SETCERT_ST_INIT:
1853 /* This state just print the update message */
1854 chunk_printf(trash, "Committing %s", ckchs_transaction.path);
1855 if (ci_putchk(si_ic(si), trash) == -1) {
1856 si_rx_room_blk(si);
1857 goto yield;
1858 }
1859 appctx->st2 = SETCERT_ST_GEN;
1860 /* fallthrough */
1861 case SETCERT_ST_GEN:
1862 /*
1863 * This state generates the ckch instances with their
1864 * sni_ctxs and SSL_CTX.
1865 *
1866 * Since the SSL_CTX generation can be CPU consumer, we
1867 * yield every 10 instances.
1868 */
1869
1870 old_ckchs = appctx->ctx.ssl.old_ckchs;
1871 new_ckchs = appctx->ctx.ssl.new_ckchs;
1872
1873 if (!new_ckchs)
1874 continue;
1875
1876 /* get the next ckchi to regenerate */
1877 ckchi = appctx->ctx.ssl.next_ckchi;
1878 /* we didn't start yet, set it to the first elem */
1879 if (ckchi == NULL)
1880 ckchi = LIST_ELEM(old_ckchs->ckch_inst.n, typeof(ckchi), by_ckchs);
1881
1882 /* walk through the old ckch_inst and creates new ckch_inst using the updated ckchs */
1883 list_for_each_entry_from(ckchi, &old_ckchs->ckch_inst, by_ckchs) {
1884 struct ckch_inst *new_inst;
William Lallemandda8584c2020-05-14 10:14:37 +02001885
1886 /* it takes a lot of CPU to creates SSL_CTXs, so we yield every 10 CKCH instances */
1887 if (y >= 10) {
1888 /* save the next ckchi to compute */
1889 appctx->ctx.ssl.next_ckchi = ckchi;
1890 goto yield;
1891 }
1892
Remi Tricot-Le Bretonbfadc022021-02-24 12:20:48 +01001893 if (ckch_inst_rebuild(new_ckchs, ckchi, &new_inst, &err))
William Lallemandda8584c2020-05-14 10:14:37 +02001894 goto error;
1895
William Lallemandda8584c2020-05-14 10:14:37 +02001896 /* display one dot per new instance */
1897 chunk_appendf(trash, ".");
1898 /* link the new ckch_inst to the duplicate */
Willy Tarreau2b718102021-04-21 07:32:39 +02001899 LIST_APPEND(&new_ckchs->ckch_inst, &new_inst->by_ckchs);
William Lallemandda8584c2020-05-14 10:14:37 +02001900 y++;
1901 }
1902 appctx->st2 = SETCERT_ST_INSERT;
1903 /* fallthrough */
1904 case SETCERT_ST_INSERT:
1905 /* The generation is finished, we can insert everything */
1906
1907 old_ckchs = appctx->ctx.ssl.old_ckchs;
1908 new_ckchs = appctx->ctx.ssl.new_ckchs;
1909
1910 if (!new_ckchs)
1911 continue;
1912
1913 /* get the list of crtlist_entry in the old store, and update the pointers to the store */
1914 LIST_SPLICE(&new_ckchs->crtlist_entry, &old_ckchs->crtlist_entry);
1915 list_for_each_entry(entry, &new_ckchs->crtlist_entry, by_ckch_store) {
1916 ebpt_delete(&entry->node);
1917 /* change the ptr and reinsert the node */
1918 entry->node.key = new_ckchs;
1919 ebpt_insert(&entry->crtlist->entries, &entry->node);
1920 }
1921
William Lallemanda55685b2020-12-15 14:57:46 +01001922 /* insert the new ckch_insts in the crtlist_entry */
1923 list_for_each_entry(ckchi, &new_ckchs->ckch_inst, by_ckchs) {
1924 if (ckchi->crtlist_entry)
Willy Tarreau2b718102021-04-21 07:32:39 +02001925 LIST_INSERT(&ckchi->crtlist_entry->ckch_inst, &ckchi->by_crtlist_entry);
William Lallemanda55685b2020-12-15 14:57:46 +01001926 }
1927
William Lallemandda8584c2020-05-14 10:14:37 +02001928 /* First, we insert every new SNIs in the trees, also replace the default_ctx */
1929 list_for_each_entry_safe(ckchi, ckchis, &new_ckchs->ckch_inst, by_ckchs) {
Remi Tricot-Le Bretonbfadc022021-02-24 12:20:48 +01001930 __ssl_sock_load_new_ckch_instance(ckchi);
William Lallemandda8584c2020-05-14 10:14:37 +02001931 }
1932
1933 /* delete the old sni_ctx, the old ckch_insts and the ckch_store */
1934 list_for_each_entry_safe(ckchi, ckchis, &old_ckchs->ckch_inst, by_ckchs) {
Remi Tricot-Le Bretonbfadc022021-02-24 12:20:48 +01001935 __ckch_inst_free_locked(ckchi);
William Lallemandda8584c2020-05-14 10:14:37 +02001936 }
1937
1938 /* Replace the old ckchs by the new one */
1939 ckch_store_free(old_ckchs);
1940 ebst_insert(&ckchs_tree, &new_ckchs->node);
1941 appctx->st2 = SETCERT_ST_FIN;
1942 /* fallthrough */
1943 case SETCERT_ST_FIN:
1944 /* we achieved the transaction, we can set everything to NULL */
Willy Tarreau61cfdf42021-02-20 10:46:51 +01001945 ha_free(&ckchs_transaction.path);
William Lallemandda8584c2020-05-14 10:14:37 +02001946 ckchs_transaction.new_ckchs = NULL;
1947 ckchs_transaction.old_ckchs = NULL;
1948 goto end;
1949 }
1950 }
1951end:
1952
1953 chunk_appendf(trash, "\n");
William Lallemandda8584c2020-05-14 10:14:37 +02001954 chunk_appendf(trash, "Success!\n");
1955 if (ci_putchk(si_ic(si), trash) == -1)
1956 si_rx_room_blk(si);
1957 free_trash_chunk(trash);
1958 /* success: call the release function and don't come back */
1959 return 1;
1960yield:
1961 /* store the state */
1962 if (ci_putchk(si_ic(si), trash) == -1)
1963 si_rx_room_blk(si);
1964 free_trash_chunk(trash);
1965 si_rx_endp_more(si); /* let's come back later */
1966 return 0; /* should come back */
1967
1968error:
1969 /* spin unlock and free are done in the release function */
1970 if (trash) {
1971 chunk_appendf(trash, "\n%sFailed!\n", err);
1972 if (ci_putchk(si_ic(si), trash) == -1)
1973 si_rx_room_blk(si);
1974 free_trash_chunk(trash);
1975 }
1976 /* error: call the release function and don't come back */
1977 return 1;
1978}
1979
1980/*
1981 * Parsing function of 'commit ssl cert'
1982 */
1983static int cli_parse_commit_cert(char **args, char *payload, struct appctx *appctx, void *private)
1984{
1985 char *err = NULL;
1986
1987 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
1988 return 1;
1989
1990 if (!*args[3])
1991 return cli_err(appctx, "'commit ssl cert expects a filename\n");
1992
1993 /* The operations on the CKCH architecture are locked so we can
1994 * manipulate ckch_store and ckch_inst */
1995 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
1996 return cli_err(appctx, "Can't commit the certificate!\nOperations on certificates are currently locked!\n");
1997
1998 if (!ckchs_transaction.path) {
1999 memprintf(&err, "No ongoing transaction! !\n");
2000 goto error;
2001 }
2002
2003 if (strcmp(ckchs_transaction.path, args[3]) != 0) {
2004 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", ckchs_transaction.path, args[3]);
2005 goto error;
2006 }
2007
William Lallemand5685ccf2020-09-16 16:12:25 +02002008 /* if a certificate is here, a private key must be here too */
2009 if (ckchs_transaction.new_ckchs->ckch->cert && !ckchs_transaction.new_ckchs->ckch->key) {
2010 memprintf(&err, "The transaction must contain at least a certificate and a private key!\n");
2011 goto error;
2012 }
William Lallemanda9419522020-06-24 16:26:41 +02002013
William Lallemand5685ccf2020-09-16 16:12:25 +02002014 if (!X509_check_private_key(ckchs_transaction.new_ckchs->ckch->cert, ckchs_transaction.new_ckchs->ckch->key)) {
2015 memprintf(&err, "inconsistencies between private key and certificate loaded '%s'.\n", ckchs_transaction.path);
2016 goto error;
William Lallemandda8584c2020-05-14 10:14:37 +02002017 }
2018
2019 /* init the appctx structure */
2020 appctx->st2 = SETCERT_ST_INIT;
2021 appctx->ctx.ssl.next_ckchi = NULL;
2022 appctx->ctx.ssl.new_ckchs = ckchs_transaction.new_ckchs;
2023 appctx->ctx.ssl.old_ckchs = ckchs_transaction.old_ckchs;
2024
2025 /* we don't unlock there, it will be unlock after the IO handler, in the release handler */
2026 return 0;
2027
2028error:
2029
2030 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2031 err = memprintf(&err, "%sCan't commit %s!\n", err ? err : "", args[3]);
2032
2033 return cli_dynerr(appctx, err);
2034}
2035
2036
2037
2038
2039/*
2040 * Parsing function of `set ssl cert`, it updates or creates a temporary ckch.
2041 */
2042static int cli_parse_set_cert(char **args, char *payload, struct appctx *appctx, void *private)
2043{
2044 struct ckch_store *new_ckchs = NULL;
2045 struct ckch_store *old_ckchs = NULL;
2046 char *err = NULL;
2047 int i;
William Lallemandda8584c2020-05-14 10:14:37 +02002048 int errcode = 0;
2049 char *end;
2050 int type = CERT_TYPE_PEM;
2051 struct cert_key_and_chain *ckch;
2052 struct buffer *buf;
2053
2054 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2055 return 1;
2056
William Lallemandda8584c2020-05-14 10:14:37 +02002057 if (!*args[3] || !payload)
2058 return cli_err(appctx, "'set ssl cert expects a filename and a certificate as a payload\n");
2059
2060 /* The operations on the CKCH architecture are locked so we can
2061 * manipulate ckch_store and ckch_inst */
2062 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2063 return cli_err(appctx, "Can't update the certificate!\nOperations on certificates are currently locked!\n");
2064
William Lallemand5ba80d62021-05-04 16:17:27 +02002065 if ((buf = alloc_trash_chunk()) == NULL) {
2066 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2067 errcode |= ERR_ALERT | ERR_FATAL;
2068 goto end;
2069 }
William Lallemande5ff4ad2020-06-08 09:40:37 +02002070
William Lallemandda8584c2020-05-14 10:14:37 +02002071 if (!chunk_strcpy(buf, args[3])) {
2072 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2073 errcode |= ERR_ALERT | ERR_FATAL;
2074 goto end;
2075 }
2076
2077 /* check which type of file we want to update */
2078 for (i = 0; cert_exts[i].type < CERT_TYPE_MAX; i++) {
2079 end = strrchr(buf->area, '.');
Tim Duesterhuse5ff1412021-01-02 22:31:53 +01002080 if (end && *cert_exts[i].ext && (strcmp(end + 1, cert_exts[i].ext) == 0)) {
William Lallemandda8584c2020-05-14 10:14:37 +02002081 *end = '\0';
William Lallemand089c1382020-10-23 17:35:12 +02002082 buf->data = strlen(buf->area);
William Lallemandda8584c2020-05-14 10:14:37 +02002083 type = cert_exts[i].type;
2084 break;
2085 }
2086 }
2087
2088 appctx->ctx.ssl.old_ckchs = NULL;
2089 appctx->ctx.ssl.new_ckchs = NULL;
2090
2091 /* if there is an ongoing transaction */
2092 if (ckchs_transaction.path) {
William Lallemandda8584c2020-05-14 10:14:37 +02002093 /* if there is an ongoing transaction, check if this is the same file */
2094 if (strcmp(ckchs_transaction.path, buf->area) != 0) {
William Lallemand089c1382020-10-23 17:35:12 +02002095 /* we didn't find the transaction, must try more cases below */
2096
2097 /* if the del-ext option is activated we should try to take a look at a ".crt" too. */
2098 if (type != CERT_TYPE_PEM && global_ssl.extra_files_noext) {
2099 if (!chunk_strcat(buf, ".crt")) {
2100 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2101 errcode |= ERR_ALERT | ERR_FATAL;
2102 goto end;
2103 }
2104
2105 if (strcmp(ckchs_transaction.path, buf->area) != 0) {
2106 /* remove .crt of the error message */
2107 *(b_orig(buf) + b_data(buf) + strlen(".crt")) = '\0';
2108 b_sub(buf, strlen(".crt"));
2109
2110 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", ckchs_transaction.path, buf->area);
2111 errcode |= ERR_ALERT | ERR_FATAL;
2112 goto end;
2113 }
2114 }
William Lallemandda8584c2020-05-14 10:14:37 +02002115 }
2116
2117 appctx->ctx.ssl.old_ckchs = ckchs_transaction.new_ckchs;
2118
2119 } else {
William Lallemandda8584c2020-05-14 10:14:37 +02002120
William Lallemand95fefa12020-09-09 12:01:33 +02002121 /* lookup for the certificate in the tree */
2122 appctx->ctx.ssl.old_ckchs = ckchs_lookup(buf->area);
William Lallemand089c1382020-10-23 17:35:12 +02002123
2124 if (!appctx->ctx.ssl.old_ckchs) {
2125 /* if the del-ext option is activated we should try to take a look at a ".crt" too. */
2126 if (type != CERT_TYPE_PEM && global_ssl.extra_files_noext) {
2127 if (!chunk_strcat(buf, ".crt")) {
2128 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2129 errcode |= ERR_ALERT | ERR_FATAL;
2130 goto end;
2131 }
2132 appctx->ctx.ssl.old_ckchs = ckchs_lookup(buf->area);
2133 }
2134 }
William Lallemandda8584c2020-05-14 10:14:37 +02002135 }
2136
2137 if (!appctx->ctx.ssl.old_ckchs) {
2138 memprintf(&err, "%sCan't replace a certificate which is not referenced by the configuration!\n",
2139 err ? err : "");
2140 errcode |= ERR_ALERT | ERR_FATAL;
2141 goto end;
2142 }
2143
2144 if (!appctx->ctx.ssl.path) {
2145 /* this is a new transaction, set the path of the transaction */
2146 appctx->ctx.ssl.path = strdup(appctx->ctx.ssl.old_ckchs->path);
2147 if (!appctx->ctx.ssl.path) {
2148 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2149 errcode |= ERR_ALERT | ERR_FATAL;
2150 goto end;
2151 }
2152 }
2153
2154 old_ckchs = appctx->ctx.ssl.old_ckchs;
2155
2156 /* duplicate the ckch store */
2157 new_ckchs = ckchs_dup(old_ckchs);
2158 if (!new_ckchs) {
2159 memprintf(&err, "%sCannot allocate memory!\n",
2160 err ? err : "");
2161 errcode |= ERR_ALERT | ERR_FATAL;
2162 goto end;
2163 }
2164
William Lallemand95fefa12020-09-09 12:01:33 +02002165 ckch = new_ckchs->ckch;
William Lallemandda8584c2020-05-14 10:14:37 +02002166
2167 /* appply the change on the duplicate */
2168 if (cert_exts[type].load(buf->area, payload, ckch, &err) != 0) {
2169 memprintf(&err, "%sCan't load the payload\n", err ? err : "");
2170 errcode |= ERR_ALERT | ERR_FATAL;
2171 goto end;
2172 }
2173
2174 appctx->ctx.ssl.new_ckchs = new_ckchs;
2175
2176 /* we succeed, we can save the ckchs in the transaction */
2177
2178 /* if there wasn't a transaction, update the old ckchs */
2179 if (!ckchs_transaction.old_ckchs) {
2180 ckchs_transaction.old_ckchs = appctx->ctx.ssl.old_ckchs;
2181 ckchs_transaction.path = appctx->ctx.ssl.path;
2182 err = memprintf(&err, "Transaction created for certificate %s!\n", ckchs_transaction.path);
2183 } else {
2184 err = memprintf(&err, "Transaction updated for certificate %s!\n", ckchs_transaction.path);
2185
2186 }
2187
2188 /* free the previous ckchs if there was a transaction */
2189 ckch_store_free(ckchs_transaction.new_ckchs);
2190
2191 ckchs_transaction.new_ckchs = appctx->ctx.ssl.new_ckchs;
2192
2193
2194 /* creates the SNI ctxs later in the IO handler */
2195
2196end:
2197 free_trash_chunk(buf);
2198
2199 if (errcode & ERR_CODE) {
2200
2201 ckch_store_free(appctx->ctx.ssl.new_ckchs);
2202 appctx->ctx.ssl.new_ckchs = NULL;
2203
2204 appctx->ctx.ssl.old_ckchs = NULL;
2205
Willy Tarreau61cfdf42021-02-20 10:46:51 +01002206 ha_free(&appctx->ctx.ssl.path);
William Lallemandda8584c2020-05-14 10:14:37 +02002207
2208 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2209 return cli_dynerr(appctx, memprintf(&err, "%sCan't update %s!\n", err ? err : "", args[3]));
2210 } else {
2211
2212 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2213 return cli_dynmsg(appctx, LOG_NOTICE, err);
2214 }
2215 /* TODO: handle the ERR_WARN which are not handled because of the io_handler */
2216}
2217
2218/* parsing function of 'abort ssl cert' */
2219static int cli_parse_abort_cert(char **args, char *payload, struct appctx *appctx, void *private)
2220{
2221 char *err = NULL;
2222
2223 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2224 return 1;
2225
2226 if (!*args[3])
2227 return cli_err(appctx, "'abort ssl cert' expects a filename\n");
2228
2229 /* The operations on the CKCH architecture are locked so we can
2230 * manipulate ckch_store and ckch_inst */
2231 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2232 return cli_err(appctx, "Can't abort!\nOperations on certificates are currently locked!\n");
2233
2234 if (!ckchs_transaction.path) {
2235 memprintf(&err, "No ongoing transaction!\n");
2236 goto error;
2237 }
2238
2239 if (strcmp(ckchs_transaction.path, args[3]) != 0) {
2240 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to abort a transaction for '%s'\n", ckchs_transaction.path, args[3]);
2241 goto error;
2242 }
2243
2244 /* Only free the ckchs there, because the SNI and instances were not generated yet */
2245 ckch_store_free(ckchs_transaction.new_ckchs);
2246 ckchs_transaction.new_ckchs = NULL;
William Lallemandda8584c2020-05-14 10:14:37 +02002247 ckchs_transaction.old_ckchs = NULL;
Willy Tarreau61cfdf42021-02-20 10:46:51 +01002248 ha_free(&ckchs_transaction.path);
William Lallemandda8584c2020-05-14 10:14:37 +02002249
2250 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2251
2252 err = memprintf(&err, "Transaction aborted for certificate '%s'!\n", args[3]);
2253 return cli_dynmsg(appctx, LOG_NOTICE, err);
2254
2255error:
2256 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2257
2258 return cli_dynerr(appctx, err);
2259}
2260
2261/* parsing function of 'new ssl cert' */
2262static int cli_parse_new_cert(char **args, char *payload, struct appctx *appctx, void *private)
2263{
2264 struct ckch_store *store;
2265 char *err = NULL;
2266 char *path;
2267
2268 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2269 return 1;
2270
2271 if (!*args[3])
2272 return cli_err(appctx, "'new ssl cert' expects a filename\n");
2273
2274 path = args[3];
2275
2276 /* The operations on the CKCH architecture are locked so we can
2277 * manipulate ckch_store and ckch_inst */
2278 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2279 return cli_err(appctx, "Can't create a certificate!\nOperations on certificates are currently locked!\n");
2280
2281 store = ckchs_lookup(path);
2282 if (store != NULL) {
2283 memprintf(&err, "Certificate '%s' already exists!\n", path);
2284 store = NULL; /* we don't want to free it */
2285 goto error;
2286 }
2287 /* we won't support multi-certificate bundle here */
William Lallemandbd8e6ed2020-09-16 16:08:08 +02002288 store = ckch_store_new(path);
William Lallemandda8584c2020-05-14 10:14:37 +02002289 if (!store) {
2290 memprintf(&err, "unable to allocate memory.\n");
2291 goto error;
2292 }
2293
2294 /* insert into the ckchs tree */
2295 ebst_insert(&ckchs_tree, &store->node);
2296 memprintf(&err, "New empty certificate store '%s'!\n", args[3]);
2297
2298 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2299 return cli_dynmsg(appctx, LOG_NOTICE, err);
2300error:
2301 free(store);
2302 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2303 return cli_dynerr(appctx, err);
2304}
2305
2306/* parsing function of 'del ssl cert' */
2307static int cli_parse_del_cert(char **args, char *payload, struct appctx *appctx, void *private)
2308{
2309 struct ckch_store *store;
2310 char *err = NULL;
2311 char *filename;
2312
2313 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2314 return 1;
2315
2316 if (!*args[3])
2317 return cli_err(appctx, "'del ssl cert' expects a certificate name\n");
2318
2319 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2320 return cli_err(appctx, "Can't delete the certificate!\nOperations on certificates are currently locked!\n");
2321
2322 filename = args[3];
2323
2324 store = ckchs_lookup(filename);
2325 if (store == NULL) {
2326 memprintf(&err, "certificate '%s' doesn't exist!\n", filename);
2327 goto error;
2328 }
2329 if (!LIST_ISEMPTY(&store->ckch_inst)) {
2330 memprintf(&err, "certificate '%s' in use, can't be deleted!\n", filename);
2331 goto error;
2332 }
2333
2334 ebmb_delete(&store->node);
2335 ckch_store_free(store);
2336
2337 memprintf(&err, "Certificate '%s' deleted!\n", filename);
2338
2339 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2340 return cli_dynmsg(appctx, LOG_NOTICE, err);
2341
2342error:
2343 memprintf(&err, "Can't remove the certificate: %s\n", err ? err : "");
2344 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2345 return cli_dynerr(appctx, err);
2346}
2347
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002348
Remi Tricot-Le Breton9f40fe02021-03-16 16:21:27 +01002349
2350/* parsing function of 'new ssl ca-file' */
2351static int cli_parse_new_cafile(char **args, char *payload, struct appctx *appctx, void *private)
2352{
2353 struct cafile_entry *cafile_entry;
2354 char *err = NULL;
2355 char *path;
2356
2357 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2358 return 1;
2359
2360 if (!*args[3])
2361 return cli_err(appctx, "'new ssl ca-file' expects a filename\n");
2362
2363 path = args[3];
2364
2365 /* The operations on the CKCH architecture are locked so we can
2366 * manipulate ckch_store and ckch_inst */
2367 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2368 return cli_err(appctx, "Can't create a CA file!\nOperations on certificates are currently locked!\n");
2369
2370 cafile_entry = ssl_store_get_cafile_entry(path, 0);
2371 if (cafile_entry) {
2372 memprintf(&err, "CA file '%s' already exists!\n", path);
2373 goto error;
2374 }
2375
2376 cafile_entry = ssl_store_create_cafile_entry(path, NULL, CAFILE_CERT);
2377 if (!cafile_entry) {
2378 memprintf(&err, "%sCannot allocate memory!\n",
2379 err ? err : "");
2380 goto error;
2381 }
2382
2383 /* Add the newly created cafile_entry to the tree so that
2384 * any new ckch instance created from now can use it. */
2385 if (ssl_store_add_uncommitted_cafile_entry(cafile_entry))
2386 goto error;
2387
2388 memprintf(&err, "New CA file created '%s'!\n", path);
2389
2390 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2391 return cli_dynmsg(appctx, LOG_NOTICE, err);
2392error:
2393 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2394 return cli_dynerr(appctx, err);
2395}
2396
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002397/*
2398 * Parsing function of `set ssl ca-file`
2399 */
2400static int cli_parse_set_cafile(char **args, char *payload, struct appctx *appctx, void *private)
2401{
2402 char *err = NULL;
2403 int errcode = 0;
2404 struct buffer *buf;
2405
2406 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2407 return 1;
2408
2409 if (!*args[3] || !payload)
2410 return cli_err(appctx, "'set ssl ca-file expects a filename and CAs as a payload\n");
2411
2412 /* The operations on the CKCH architecture are locked so we can
2413 * manipulate ckch_store and ckch_inst */
2414 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2415 return cli_err(appctx, "Can't update the CA file!\nOperations on certificates are currently locked!\n");
2416
2417 if ((buf = alloc_trash_chunk()) == NULL) {
2418 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2419 errcode |= ERR_ALERT | ERR_FATAL;
2420 goto end;
2421 }
2422
2423 if (!chunk_strcpy(buf, args[3])) {
2424 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2425 errcode |= ERR_ALERT | ERR_FATAL;
2426 goto end;
2427 }
2428
2429 appctx->ctx.ssl.old_cafile_entry = NULL;
2430 appctx->ctx.ssl.new_cafile_entry = NULL;
2431
2432 /* if there is an ongoing transaction */
2433 if (cafile_transaction.path) {
2434 /* if there is an ongoing transaction, check if this is the same file */
2435 if (strcmp(cafile_transaction.path, buf->area) != 0) {
2436 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", cafile_transaction.path, buf->area);
2437 errcode |= ERR_ALERT | ERR_FATAL;
2438 goto end;
2439 }
2440 appctx->ctx.ssl.old_cafile_entry = cafile_transaction.old_cafile_entry;
2441 }
2442 else {
2443 /* lookup for the certificate in the tree */
2444 appctx->ctx.ssl.old_cafile_entry = ssl_store_get_cafile_entry(buf->area, 0);
2445 }
2446
2447 if (!appctx->ctx.ssl.old_cafile_entry) {
2448 memprintf(&err, "%sCan't replace a CA file which is not referenced by the configuration!\n",
2449 err ? err : "");
2450 errcode |= ERR_ALERT | ERR_FATAL;
2451 goto end;
2452 }
2453
2454 if (!appctx->ctx.ssl.path) {
2455 /* this is a new transaction, set the path of the transaction */
2456 appctx->ctx.ssl.path = strdup(appctx->ctx.ssl.old_cafile_entry->path);
2457 if (!appctx->ctx.ssl.path) {
2458 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2459 errcode |= ERR_ALERT | ERR_FATAL;
2460 goto end;
2461 }
2462 }
2463
2464 if (appctx->ctx.ssl.new_cafile_entry)
2465 ssl_store_delete_cafile_entry(appctx->ctx.ssl.new_cafile_entry);
2466
2467 /* Create a new cafile_entry without adding it to the cafile tree. */
Remi Tricot-Le Breton0bb48242021-04-16 17:59:23 +02002468 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 +01002469 if (!appctx->ctx.ssl.new_cafile_entry) {
2470 memprintf(&err, "%sCannot allocate memory!\n",
2471 err ? err : "");
2472 errcode |= ERR_ALERT | ERR_FATAL;
2473 goto end;
2474 }
2475
2476 /* Fill the new entry with the new CAs. */
2477 if (ssl_store_load_ca_from_buf(appctx->ctx.ssl.new_cafile_entry, payload)) {
2478 memprintf(&err, "%sInvalid payload\n", err ? err : "");
2479 errcode |= ERR_ALERT | ERR_FATAL;
2480 goto end;
2481 }
2482
2483 /* we succeed, we can save the ca in the transaction */
2484
2485 /* if there wasn't a transaction, update the old CA */
2486 if (!cafile_transaction.old_cafile_entry) {
2487 cafile_transaction.old_cafile_entry = appctx->ctx.ssl.old_cafile_entry;
2488 cafile_transaction.path = appctx->ctx.ssl.path;
2489 err = memprintf(&err, "transaction created for CA %s!\n", cafile_transaction.path);
2490 } else {
2491 err = memprintf(&err, "transaction updated for CA %s!\n", cafile_transaction.path);
2492 }
2493
2494 /* free the previous CA if there was a transaction */
2495 ssl_store_delete_cafile_entry(cafile_transaction.new_cafile_entry);
2496
2497 cafile_transaction.new_cafile_entry = appctx->ctx.ssl.new_cafile_entry;
2498
2499 /* creates the SNI ctxs later in the IO handler */
2500
2501end:
2502 free_trash_chunk(buf);
2503
2504 if (errcode & ERR_CODE) {
2505 ssl_store_delete_cafile_entry(appctx->ctx.ssl.new_cafile_entry);
2506 appctx->ctx.ssl.new_cafile_entry = NULL;
2507 appctx->ctx.ssl.old_cafile_entry = NULL;
2508
Tim Duesterhus025b93e2021-11-04 21:03:52 +01002509 ha_free(&appctx->ctx.ssl.path);
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002510
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
Tim Duesterhus025b93e2021-11-04 21:03:52 +01003227 ha_free(&appctx->ctx.ssl.path);
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003228
3229 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3230 return cli_dynerr(appctx, memprintf(&err, "%sCan't update %s!\n", err ? err : "", args[3]));
3231 } else {
3232
3233 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3234 return cli_dynmsg(appctx, LOG_NOTICE, err);
3235 }
3236}
3237
3238/* Parsing function of 'commit ssl crl-file' */
3239static int cli_parse_commit_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
3240{
3241 char *err = NULL;
3242
3243 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
3244 return 1;
3245
3246 if (!*args[3])
3247 return cli_err(appctx, "'commit ssl ca-file expects a filename\n");
3248
3249 /* The operations on the CKCH architecture are locked so we can
3250 * manipulate ckch_store and ckch_inst */
3251 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3252 return cli_err(appctx, "Can't commit the CRL file!\nOperations on certificates are currently locked!\n");
3253
3254 if (!crlfile_transaction.path) {
3255 memprintf(&err, "No ongoing transaction! !\n");
3256 goto error;
3257 }
3258
3259 if (strcmp(crlfile_transaction.path, args[3]) != 0) {
3260 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", crlfile_transaction.path, args[3]);
3261 goto error;
3262 }
3263 /* init the appctx structure */
3264 appctx->st2 = SETCERT_ST_INIT;
3265 appctx->ctx.ssl.next_ckchi = NULL;
3266 appctx->ctx.ssl.old_crlfile_entry = crlfile_transaction.old_crlfile_entry;
3267 appctx->ctx.ssl.new_crlfile_entry = crlfile_transaction.new_crlfile_entry;
3268 appctx->ctx.ssl.cafile_type = CAFILE_CRL;
3269
3270 return 0;
3271
3272error:
3273
3274 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3275 err = memprintf(&err, "%sCan't commit %s!\n", err ? err : "", args[3]);
3276
3277 return cli_dynerr(appctx, err);
3278}
3279
3280
3281/* release function of the `commit ssl crl-file' command, free things and unlock the spinlock */
3282static void cli_release_commit_crlfile(struct appctx *appctx)
3283{
3284 if (appctx->st2 != SETCERT_ST_FIN) {
3285 struct cafile_entry *new_crlfile_entry = appctx->ctx.ssl.new_crlfile_entry;
3286
3287 /* Remove the uncommitted cafile_entry from the tree. */
3288 ebmb_delete(&new_crlfile_entry->node);
3289 ssl_store_delete_cafile_entry(new_crlfile_entry);
3290 }
3291 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3292}
3293
Remi Tricot-Le Breton720e3b92021-04-26 11:00:42 +02003294/* parsing function of 'del ssl crl-file' */
3295static int cli_parse_del_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
3296{
3297 struct cafile_entry *cafile_entry;
3298 char *err = NULL;
3299 char *filename;
3300
3301 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
3302 return 1;
3303
3304 if (!*args[3])
3305 return cli_err(appctx, "'del ssl crl-file' expects a CRL file name\n");
3306
3307 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3308 return cli_err(appctx, "Can't delete the CRL file!\nOperations on certificates are currently locked!\n");
3309
3310 filename = args[3];
3311
3312 cafile_entry = ssl_store_get_cafile_entry(filename, 0);
3313 if (!cafile_entry) {
3314 memprintf(&err, "CRL file '%s' doesn't exist!\n", filename);
3315 goto error;
3316 }
3317 if (cafile_entry->type != CAFILE_CRL) {
3318 memprintf(&err, "'del ssl crl-file' does not work on CA files!\n");
3319 goto error;
3320 }
3321
3322 if (!LIST_ISEMPTY(&cafile_entry->ckch_inst_link)) {
3323 memprintf(&err, "CRL file '%s' in use, can't be deleted!\n", filename);
3324 goto error;
3325 }
3326
3327 /* Remove the cafile_entry from the tree */
3328 ebmb_delete(&cafile_entry->node);
3329 ssl_store_delete_cafile_entry(cafile_entry);
3330
3331 memprintf(&err, "CRL file '%s' deleted!\n", filename);
3332
3333 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3334 return cli_dynmsg(appctx, LOG_NOTICE, err);
3335
3336error:
3337 memprintf(&err, "Can't remove the CRL file: %s\n", err ? err : "");
3338 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3339 return cli_dynerr(appctx, err);
3340}
3341
Remi Tricot-Le Bretoneef8e7b2021-04-20 17:42:02 +02003342/* parsing function of 'abort ssl crl-file' */
3343static int cli_parse_abort_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
3344{
3345 char *err = NULL;
3346
3347 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
3348 return 1;
3349
3350 if (!*args[3])
3351 return cli_err(appctx, "'abort ssl crl-file' expects a filename\n");
3352
3353 /* The operations on the CKCH architecture are locked so we can
3354 * manipulate ckch_store and ckch_inst */
3355 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3356 return cli_err(appctx, "Can't abort!\nOperations on certificates are currently locked!\n");
3357
3358 if (!crlfile_transaction.path) {
3359 memprintf(&err, "No ongoing transaction!\n");
3360 goto error;
3361 }
3362
3363 if (strcmp(crlfile_transaction.path, args[3]) != 0) {
3364 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to abort a transaction for '%s'\n", crlfile_transaction.path, args[3]);
3365 goto error;
3366 }
3367
3368 /* Only free the uncommitted cafile_entry here, because the SNI and instances were not generated yet */
3369 ssl_store_delete_cafile_entry(crlfile_transaction.new_crlfile_entry);
3370 crlfile_transaction.new_crlfile_entry = NULL;
3371 crlfile_transaction.old_crlfile_entry = NULL;
3372 ha_free(&crlfile_transaction.path);
3373
3374 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3375
3376 err = memprintf(&err, "Transaction aborted for certificate '%s'!\n", args[3]);
3377 return cli_dynmsg(appctx, LOG_NOTICE, err);
3378
3379error:
3380 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3381
3382 return cli_dynerr(appctx, err);
3383}
3384
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003385
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003386/*
3387 * Display a Certificate Resignation List's information.
3388 * The information displayed is inspired by the output of 'openssl crl -in
3389 * crl.pem -text'.
3390 * Returns 0 in case of success.
3391 */
3392static int show_crl_detail(X509_CRL *crl, struct buffer *out)
3393{
3394 BIO *bio = NULL;
3395 struct buffer *tmp = alloc_trash_chunk();
3396 long version;
3397 X509_NAME *issuer;
3398 int write = -1;
3399 STACK_OF(X509_REVOKED) *rev = NULL;
3400 X509_REVOKED *rev_entry = NULL;
3401 int i;
3402
3403 if (!tmp)
3404 return -1;
3405
3406 if ((bio = BIO_new(BIO_s_mem())) == NULL)
3407 goto end;
3408
3409 /* Version (as displayed by 'openssl crl') */
3410 version = X509_CRL_get_version(crl);
3411 chunk_appendf(out, "Version %ld\n", version + 1);
3412
3413 /* Signature Algorithm */
3414 chunk_appendf(out, "Signature Algorithm: %s\n", OBJ_nid2ln(X509_CRL_get_signature_nid(crl)));
3415
3416 /* Issuer */
3417 chunk_appendf(out, "Issuer: ");
3418 if ((issuer = X509_CRL_get_issuer(crl)) == NULL)
3419 goto end;
3420 if ((ssl_sock_get_dn_oneline(issuer, tmp)) == -1)
3421 goto end;
3422 *(tmp->area + tmp->data) = '\0';
3423 chunk_appendf(out, "%s\n", tmp->area);
3424
3425 /* Last Update */
3426 chunk_appendf(out, "Last Update: ");
3427 chunk_reset(tmp);
Remi Tricot-Le Bretond75b99e2021-05-17 11:45:55 +02003428 if (BIO_reset(bio) == -1)
3429 goto end;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003430 if (ASN1_TIME_print(bio, X509_CRL_get0_lastUpdate(crl)) == 0)
3431 goto end;
3432 write = BIO_read(bio, tmp->area, tmp->size-1);
3433 tmp->area[write] = '\0';
3434 chunk_appendf(out, "%s\n", tmp->area);
3435
3436
3437 /* Next Update */
3438 chunk_appendf(out, "Next Update: ");
3439 chunk_reset(tmp);
Remi Tricot-Le Bretond75b99e2021-05-17 11:45:55 +02003440 if (BIO_reset(bio) == -1)
3441 goto end;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003442 if (ASN1_TIME_print(bio, X509_CRL_get0_nextUpdate(crl)) == 0)
3443 goto end;
3444 write = BIO_read(bio, tmp->area, tmp->size-1);
3445 tmp->area[write] = '\0';
3446 chunk_appendf(out, "%s\n", tmp->area);
3447
3448
3449 /* Revoked Certificates */
3450 rev = X509_CRL_get_REVOKED(crl);
3451 if (sk_X509_REVOKED_num(rev) > 0)
3452 chunk_appendf(out, "Revoked Certificates:\n");
3453 else
3454 chunk_appendf(out, "No Revoked Certificates.\n");
3455
3456 for (i = 0; i < sk_X509_REVOKED_num(rev); i++) {
3457 rev_entry = sk_X509_REVOKED_value(rev, i);
3458
3459 /* Serial Number and Revocation Date */
Remi Tricot-Le Bretond75b99e2021-05-17 11:45:55 +02003460 if (BIO_reset(bio) == -1)
3461 goto end;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003462 BIO_printf(bio , " Serial Number: ");
Remi Tricot-Le Breton18c7d832021-05-17 18:38:34 +02003463 i2a_ASN1_INTEGER(bio, (ASN1_INTEGER*)X509_REVOKED_get0_serialNumber(rev_entry));
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003464 BIO_printf(bio, "\n Revocation Date: ");
Remi Tricot-Le Bretona6b27842021-05-18 10:06:00 +02003465 if (ASN1_TIME_print(bio, X509_REVOKED_get0_revocationDate(rev_entry)) == 0)
3466 goto end;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003467 BIO_printf(bio, "\n");
3468
3469 write = BIO_read(bio, tmp->area, tmp->size-1);
3470 tmp->area[write] = '\0';
3471 chunk_appendf(out, "%s", tmp->area);
3472 }
3473
3474end:
3475 free_trash_chunk(tmp);
3476 if (bio)
3477 BIO_free(bio);
3478
3479 return 0;
3480}
3481
3482/* IO handler of details "show ssl crl-file <filename[:index]>" */
3483static int cli_io_handler_show_crlfile_detail(struct appctx *appctx)
3484{
3485 struct stream_interface *si = appctx->owner;
3486 struct cafile_entry *cafile_entry = appctx->ctx.cli.p0;
3487 struct buffer *out = alloc_trash_chunk();
3488 int i;
3489 X509_CRL *crl;
3490 STACK_OF(X509_OBJECT) *objs;
3491 int retval = 0;
3492 long index = (long)appctx->ctx.cli.p1;
3493
3494 if (!out)
3495 goto end_no_putchk;
3496
3497 chunk_appendf(out, "Filename: ");
3498 if (cafile_entry == crlfile_transaction.new_crlfile_entry)
3499 chunk_appendf(out, "*");
3500 chunk_appendf(out, "%s\n", cafile_entry->path);
3501
3502 chunk_appendf(out, "Status: ");
3503 if (!cafile_entry->ca_store)
3504 chunk_appendf(out, "Empty\n");
3505 else if (LIST_ISEMPTY(&cafile_entry->ckch_inst_link))
3506 chunk_appendf(out, "Unused\n");
3507 else
3508 chunk_appendf(out, "Used\n");
3509
3510 if (!cafile_entry->ca_store)
3511 goto end;
3512
3513 objs = X509_STORE_get0_objects(cafile_entry->ca_store);
3514 for (i = 0; i < sk_X509_OBJECT_num(objs); i++) {
3515 crl = X509_OBJECT_get0_X509_CRL(sk_X509_OBJECT_value(objs, i));
3516 if (!crl)
3517 continue;
3518
3519 /* CRL indexes start at 1 on the CLI output. */
3520 if (index && index-1 != i)
3521 continue;
3522
3523 chunk_appendf(out, "\nCertificate Revocation List #%d:\n", i+1);
3524 retval = show_crl_detail(crl, out);
3525 if (retval < 0)
3526 goto end_no_putchk;
3527 else if (retval || index)
3528 goto end;
3529 }
3530
3531end:
3532 if (ci_putchk(si_ic(si), out) == -1) {
3533 si_rx_room_blk(si);
3534 goto yield;
3535 }
3536
3537end_no_putchk:
3538 free_trash_chunk(out);
3539 return 1;
3540yield:
3541 free_trash_chunk(out);
3542 return 0; /* should come back */
3543}
3544
3545/* parsing function for 'show ssl crl-file [crlfile[:index]]' */
3546static int cli_parse_show_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
3547{
3548 struct cafile_entry *cafile_entry;
3549 long index = 0;
3550 char *colons;
3551 char *err = NULL;
3552
3553 if (!cli_has_level(appctx, ACCESS_LVL_OPER))
3554 return cli_err(appctx, "Can't allocate memory!\n");
3555
3556 /* The operations on the CKCH architecture are locked so we can
3557 * manipulate ckch_store and ckch_inst */
3558 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3559 return cli_err(appctx, "Can't show!\nOperations on certificates are currently locked!\n");
3560
3561 /* check if there is a certificate to lookup */
3562 if (*args[3]) {
3563
3564 /* Look for an optional index after the CRL file name */
3565 colons = strchr(args[3], ':');
3566 if (colons) {
3567 char *endptr;
3568
3569 index = strtol(colons + 1, &endptr, 10);
3570 /* Indexes start at 1 */
3571 if (colons + 1 == endptr || *endptr != '\0' || index <= 0) {
3572 memprintf(&err, "wrong CRL index after colons in '%s'!", args[3]);
3573 goto error;
3574 }
3575 *colons = '\0';
3576 }
3577
3578 if (*args[3] == '*') {
3579 if (!crlfile_transaction.new_crlfile_entry)
3580 goto error;
3581
3582 cafile_entry = crlfile_transaction.new_crlfile_entry;
3583
3584 if (strcmp(args[3] + 1, cafile_entry->path) != 0)
3585 goto error;
3586
3587 } else {
3588 /* Get the "original" cafile_entry and not the
3589 * uncommitted one if it exists. */
3590 if ((cafile_entry = ssl_store_get_cafile_entry(args[3], 1)) == NULL || cafile_entry->type != CAFILE_CRL)
3591 goto error;
3592 }
3593
3594 appctx->ctx.cli.p0 = cafile_entry;
3595 appctx->ctx.cli.p1 = (void*)index;
3596 /* use the IO handler that shows details */
3597 appctx->io_handler = cli_io_handler_show_crlfile_detail;
3598 }
3599
3600 return 0;
3601
3602error:
3603 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3604 if (err)
3605 return cli_dynerr(appctx, err);
3606 return cli_err(appctx, "Can't display the CA file : Not found!\n");
3607}
3608
3609/* IO handler of "show ssl crl-file". The command taking a specific CRL file name
3610 * is managed in cli_io_handler_show_crlfile_detail. */
3611static int cli_io_handler_show_crlfile(struct appctx *appctx)
3612{
3613 struct buffer *trash = alloc_trash_chunk();
3614 struct ebmb_node *node;
3615 struct stream_interface *si = appctx->owner;
3616 struct cafile_entry *cafile_entry;
3617
3618 if (trash == NULL)
3619 return 1;
3620
3621 if (!appctx->ctx.ssl.old_crlfile_entry) {
3622 if (crlfile_transaction.old_crlfile_entry) {
3623 chunk_appendf(trash, "# transaction\n");
3624 chunk_appendf(trash, "*%s\n", crlfile_transaction.old_crlfile_entry->path);
3625 }
3626 }
3627
3628 /* First time in this io_handler. */
3629 if (!appctx->ctx.cli.p0) {
3630 chunk_appendf(trash, "# filename\n");
3631 node = ebmb_first(&cafile_tree);
3632 } else {
3633 /* We yielded during a previous call. */
3634 node = &((struct cafile_entry*)appctx->ctx.cli.p0)->node;
3635 }
3636
3637 while (node) {
3638 cafile_entry = ebmb_entry(node, struct cafile_entry, node);
3639 if (cafile_entry->type == CAFILE_CRL) {
3640 chunk_appendf(trash, "%s\n", cafile_entry->path);
3641 }
3642
3643 node = ebmb_next(node);
3644 if (ci_putchk(si_ic(si), trash) == -1) {
3645 si_rx_room_blk(si);
3646 goto yield;
3647 }
3648 }
3649
3650 appctx->ctx.cli.p0 = NULL;
3651 free_trash_chunk(trash);
3652 return 1;
3653yield:
3654
3655 free_trash_chunk(trash);
3656 appctx->ctx.cli.p0 = cafile_entry;
3657 return 0; /* should come back */
3658}
3659
3660
3661/* release function of the 'show ssl crl-file' command */
3662static void cli_release_show_crlfile(struct appctx *appctx)
3663{
3664 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3665}
3666
3667
William Lallemandee8530c2020-06-23 18:19:42 +02003668void ckch_deinit()
3669{
3670 struct eb_node *node, *next;
3671 struct ckch_store *store;
3672
3673 node = eb_first(&ckchs_tree);
3674 while (node) {
3675 next = eb_next(node);
3676 store = ebmb_entry(node, struct ckch_store, node);
3677 ckch_store_free(store);
3678 node = next;
3679 }
3680}
William Lallemandda8584c2020-05-14 10:14:37 +02003681
3682/* register cli keywords */
3683static struct cli_kw_list cli_kws = {{ },{
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01003684 { { "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 },
3685 { { "set", "ssl", "cert", NULL }, "set ssl cert <certfile> <payload> : replace a certificate file", cli_parse_set_cert, NULL, NULL },
3686 { { "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 },
3687 { { "abort", "ssl", "cert", NULL }, "abort ssl cert <certfile> : abort a transaction for a certificate file", cli_parse_abort_cert, NULL, NULL },
3688 { { "del", "ssl", "cert", NULL }, "del ssl cert <certfile> : delete an unused certificate file", cli_parse_del_cert, NULL, NULL },
3689 { { "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 },
3690
Amaury Denoyelleb11ad9e2021-05-21 11:01:10 +02003691 { { "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 +01003692 { { "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 +02003693 { { "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 +01003694 { { "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 +01003695 { { "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 +01003696 { { "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 +02003697
Remi Tricot-Le Breton720e3b92021-04-26 11:00:42 +02003698 { { "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 +02003699 { { "set", "ssl", "crl-file", NULL }, "set ssl crl-file <crlfile> <payload> : replace a CRL file", cli_parse_set_crlfile, NULL, NULL },
3700 { { "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 +02003701 { { "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 +02003702 { { "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 +02003703 { { "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 +02003704 { { NULL }, NULL, NULL, NULL }
3705}};
3706
3707INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws);
3708