blob: 2bd70a7d4e3fed371c8e633bbe783ad167b2cabc [file] [log] [blame]
William Lallemand03c331c2020-05-13 10:10:01 +02001/*
2 *
3 * Copyright (C) 2020 HAProxy Technologies, William Lallemand <wlallemand@haproxy.com>
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version
8 * 2 of the License, or (at your option) any later version.
9 *
10 */
11
12#define _GNU_SOURCE
13#include <ctype.h>
William Lallemand87fd9942022-04-01 20:12:03 +020014#include <dirent.h>
William Lallemand03c331c2020-05-13 10:10:01 +020015#include <errno.h>
16#include <fcntl.h>
17#include <stdio.h>
18#include <stdlib.h>
19#include <string.h>
Willy Tarreauaeed4a82020-06-04 22:01:04 +020020#include <syslog.h>
William Lallemand03c331c2020-05-13 10:10:01 +020021#include <unistd.h>
22
23#include <sys/stat.h>
24#include <sys/types.h>
25
Willy Tarreau74f24562021-10-06 17:54:12 +020026#include <import/ebpttree.h>
Willy Tarreaub2551052020-06-09 09:07:15 +020027#include <import/ebsttree.h>
28
Willy Tarreau50c2f1e2022-05-04 19:26:59 +020029#include <haproxy/applet.h>
Willy Tarreau8d366972020-05-27 16:10:29 +020030#include <haproxy/base64.h>
Willy Tarreauf1d32c42020-06-04 21:07:02 +020031#include <haproxy/channel.h>
Willy Tarreau83487a82020-06-04 20:19:54 +020032#include <haproxy/cli.h>
Willy Tarreau8d366972020-05-27 16:10:29 +020033#include <haproxy/errors.h>
Willy Tarreau5edca2f2022-05-27 09:25:10 +020034#include <haproxy/sc_strm.h>
Willy Tarreau47d7f902020-06-04 14:25:47 +020035#include <haproxy/ssl_ckch.h>
Willy Tarreau209108d2020-06-04 20:30:20 +020036#include <haproxy/ssl_sock.h>
Remi Tricot-Le Bretona1b3d8e2024-02-07 16:38:41 +010037#include <haproxy/ssl_ocsp.h>
Willy Tarreaub2bd8652020-06-04 14:21:22 +020038#include <haproxy/ssl_utils.h>
Willy Tarreaucb086c62022-05-27 09:47:12 +020039#include <haproxy/stconn.h>
Willy Tarreau48fbcae2020-06-03 18:09:46 +020040#include <haproxy/tools.h>
William Lallemand03c331c2020-05-13 10:10:01 +020041
William Lallemandda8584c2020-05-14 10:14:37 +020042/* Uncommitted CKCH transaction */
43
44static struct {
45 struct ckch_store *new_ckchs;
46 struct ckch_store *old_ckchs;
47 char *path;
48} ckchs_transaction;
49
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +010050/* Uncommitted CA file transaction */
51
52static struct {
53 struct cafile_entry *old_cafile_entry;
54 struct cafile_entry *new_cafile_entry;
55 char *path;
56} cafile_transaction;
William Lallemandda8584c2020-05-14 10:14:37 +020057
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +020058/* Uncommitted CRL file transaction */
59
60static struct {
61 struct cafile_entry *old_crlfile_entry;
62 struct cafile_entry *new_crlfile_entry;
63 char *path;
64} crlfile_transaction;
65
Willy Tarreau50c2f1e2022-05-04 19:26:59 +020066/* CLI context used by "show cafile" */
67struct show_cafile_ctx {
68 struct cafile_entry *cur_cafile_entry;
69 struct cafile_entry *old_cafile_entry;
70 int ca_index;
71 int show_all;
72};
William Lallemand03c331c2020-05-13 10:10:01 +020073
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +020074/* CLI context used by "show crlfile" */
75struct show_crlfile_ctx {
76 struct cafile_entry *cafile_entry;
Christopher Faulet51095ee2022-06-03 10:21:27 +020077 struct cafile_entry *old_crlfile_entry;
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +020078 int index;
79};
80
Willy Tarreau96c9a6c2022-05-04 19:51:37 +020081/* CLI context used by "show cert" */
82struct show_cert_ctx {
83 struct ckch_store *old_ckchs;
84 struct ckch_store *cur_ckchs;
85 int transaction;
86};
87
Willy Tarreaua645b6a2022-05-04 19:58:00 +020088/* CLI context used by "commit cert" */
89struct commit_cert_ctx {
90 struct ckch_store *old_ckchs;
91 struct ckch_store *new_ckchs;
92 struct ckch_inst *next_ckchi;
Christopher Faulet9d56e242022-05-31 16:37:01 +020093 char *err;
Willy Tarreaucb1b4ed2022-05-05 08:15:27 +020094 enum {
95 CERT_ST_INIT = 0,
96 CERT_ST_GEN,
97 CERT_ST_INSERT,
Christopher Faulet9d56e242022-05-31 16:37:01 +020098 CERT_ST_SUCCESS,
Willy Tarreaucb1b4ed2022-05-05 08:15:27 +020099 CERT_ST_FIN,
Christopher Faulet9d56e242022-05-31 16:37:01 +0200100 CERT_ST_ERROR,
Willy Tarreaucb1b4ed2022-05-05 08:15:27 +0200101 } state;
Willy Tarreaua645b6a2022-05-04 19:58:00 +0200102};
103
Willy Tarreaudec23dc2022-05-04 20:25:05 +0200104/* CLI context used by "commit cafile" and "commit crlfile" */
105struct commit_cacrlfile_ctx {
Christopher Faulet6af2fc62022-06-03 11:42:38 +0200106 struct cafile_entry *old_entry;
107 struct cafile_entry *new_entry;
Willy Tarreaudec23dc2022-05-04 20:25:05 +0200108 struct ckch_inst_link *next_ckchi_link;
Christopher Faulet14df9132022-06-03 09:17:09 +0200109 enum cafile_type cafile_type; /* either CA or CRL, depending on the current command */
Christopher Faulete9c3bd12022-05-31 17:51:06 +0200110 char *err;
Willy Tarreau1d6dd802022-05-05 08:17:29 +0200111 enum {
112 CACRL_ST_INIT = 0,
113 CACRL_ST_GEN,
114 CACRL_ST_INSERT,
Christopher Faulete9c3bd12022-05-31 17:51:06 +0200115 CACRL_ST_SUCCESS,
Willy Tarreau1d6dd802022-05-05 08:17:29 +0200116 CACRL_ST_FIN,
Christopher Faulete9c3bd12022-05-31 17:51:06 +0200117 CACRL_ST_ERROR,
Willy Tarreau1d6dd802022-05-05 08:17:29 +0200118 } state;
Willy Tarreaudec23dc2022-05-04 20:25:05 +0200119};
120
Willy Tarreaua37693f2022-05-04 20:12:55 +0200121
William Lallemand03c331c2020-05-13 10:10:01 +0200122/******************** cert_key_and_chain functions *************************
123 * These are the functions that fills a cert_key_and_chain structure. For the
124 * functions filling a SSL_CTX from a cert_key_and_chain, see ssl_sock.c
125 */
126
127/*
128 * Try to parse Signed Certificate Timestamp List structure. This function
129 * makes only basic test if the data seems like SCTL. No signature validation
130 * is performed.
131 */
132static int ssl_sock_parse_sctl(struct buffer *sctl)
133{
134 int ret = 1;
135 int len, pos, sct_len;
136 unsigned char *data;
137
138 if (sctl->data < 2)
139 goto out;
140
141 data = (unsigned char *) sctl->area;
142 len = (data[0] << 8) | data[1];
143
144 if (len + 2 != sctl->data)
145 goto out;
146
147 data = data + 2;
148 pos = 0;
149 while (pos < len) {
150 if (len - pos < 2)
151 goto out;
152
153 sct_len = (data[pos] << 8) | data[pos + 1];
154 if (pos + sct_len + 2 > len)
155 goto out;
156
157 pos += sct_len + 2;
158 }
159
160 ret = 0;
161
162out:
163 return ret;
164}
165
166/* Try to load a sctl from a buffer <buf> if not NULL, or read the file <sctl_path>
167 * It fills the ckch->sctl buffer
168 * return 0 on success or != 0 on failure */
William Lallemand52ddd992022-11-22 11:51:53 +0100169int ssl_sock_load_sctl_from_file(const char *sctl_path, char *buf, struct ckch_data *data, char **err)
William Lallemand03c331c2020-05-13 10:10:01 +0200170{
171 int fd = -1;
172 int r = 0;
173 int ret = 1;
174 struct buffer tmp;
175 struct buffer *src;
176 struct buffer *sctl;
177
178 if (buf) {
William Lallemand8d673942021-01-27 14:58:51 +0100179 chunk_initstr(&tmp, buf);
William Lallemand03c331c2020-05-13 10:10:01 +0200180 src = &tmp;
181 } else {
182 fd = open(sctl_path, O_RDONLY);
183 if (fd == -1)
184 goto end;
185
186 trash.data = 0;
187 while (trash.data < trash.size) {
188 r = read(fd, trash.area + trash.data, trash.size - trash.data);
189 if (r < 0) {
190 if (errno == EINTR)
191 continue;
192 goto end;
193 }
194 else if (r == 0) {
195 break;
196 }
197 trash.data += r;
198 }
199 src = &trash;
200 }
201
202 ret = ssl_sock_parse_sctl(src);
203 if (ret)
204 goto end;
205
206 sctl = calloc(1, sizeof(*sctl));
207 if (!chunk_dup(sctl, src)) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100208 ha_free(&sctl);
William Lallemand03c331c2020-05-13 10:10:01 +0200209 goto end;
210 }
211 /* no error, fill ckch with new context, old context must be free */
William Lallemand52ddd992022-11-22 11:51:53 +0100212 if (data->sctl) {
213 ha_free(&data->sctl->area);
214 free(data->sctl);
William Lallemand03c331c2020-05-13 10:10:01 +0200215 }
William Lallemand52ddd992022-11-22 11:51:53 +0100216 data->sctl = sctl;
William Lallemand03c331c2020-05-13 10:10:01 +0200217 ret = 0;
218end:
219 if (fd != -1)
220 close(fd);
221
222 return ret;
223}
224
225#if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) || defined OPENSSL_IS_BORINGSSL)
226/*
Ilya Shipitsin46a030c2020-07-05 16:36:08 +0500227 * This function load the OCSP Response in DER format contained in file at
William Lallemand03c331c2020-05-13 10:10:01 +0200228 * path 'ocsp_path' or base64 in a buffer <buf>
229 *
230 * Returns 0 on success, 1 in error case.
231 */
William Lallemand52ddd992022-11-22 11:51:53 +0100232int ssl_sock_load_ocsp_response_from_file(const char *ocsp_path, char *buf, struct ckch_data *data, char **err)
William Lallemand03c331c2020-05-13 10:10:01 +0200233{
234 int fd = -1;
235 int r = 0;
236 int ret = 1;
237 struct buffer *ocsp_response;
238 struct buffer *src = NULL;
239
240 if (buf) {
241 int i, j;
242 /* if it's from a buffer it will be base64 */
243
244 /* remove \r and \n from the payload */
245 for (i = 0, j = 0; buf[i]; i++) {
246 if (buf[i] == '\r' || buf[i] == '\n')
247 continue;
248 buf[j++] = buf[i];
249 }
250 buf[j] = 0;
251
252 ret = base64dec(buf, j, trash.area, trash.size);
253 if (ret < 0) {
254 memprintf(err, "Error reading OCSP response in base64 format");
255 goto end;
256 }
257 trash.data = ret;
258 src = &trash;
259 } else {
260 fd = open(ocsp_path, O_RDONLY);
261 if (fd == -1) {
262 memprintf(err, "Error opening OCSP response file");
263 goto end;
264 }
265
266 trash.data = 0;
267 while (trash.data < trash.size) {
268 r = read(fd, trash.area + trash.data, trash.size - trash.data);
269 if (r < 0) {
270 if (errno == EINTR)
271 continue;
272
273 memprintf(err, "Error reading OCSP response from file");
274 goto end;
275 }
276 else if (r == 0) {
277 break;
278 }
279 trash.data += r;
280 }
281 close(fd);
282 fd = -1;
283 src = &trash;
284 }
285
286 ocsp_response = calloc(1, sizeof(*ocsp_response));
287 if (!chunk_dup(ocsp_response, src)) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100288 ha_free(&ocsp_response);
William Lallemand03c331c2020-05-13 10:10:01 +0200289 goto end;
290 }
William Lallemand52ddd992022-11-22 11:51:53 +0100291 /* no error, fill data with new context, old context must be free */
292 if (data->ocsp_response) {
293 ha_free(&data->ocsp_response->area);
294 free(data->ocsp_response);
William Lallemand03c331c2020-05-13 10:10:01 +0200295 }
William Lallemand52ddd992022-11-22 11:51:53 +0100296 data->ocsp_response = ocsp_response;
William Lallemand03c331c2020-05-13 10:10:01 +0200297 ret = 0;
298end:
299 if (fd != -1)
300 close(fd);
301
302 return ret;
303}
304#endif
305
306/*
307 * Try to load in a ckch every files related to a ckch.
308 * (PEM, sctl, ocsp, issuer etc.)
309 *
310 * This function is only used to load files during the configuration parsing,
311 * it is not used with the CLI.
312 *
313 * This allows us to carry the contents of the file without having to read the
314 * file multiple times. The caller must call
315 * ssl_sock_free_cert_key_and_chain_contents.
316 *
317 * returns:
318 * 0 on Success
319 * 1 on SSL Failure
320 */
William Lallemand52ddd992022-11-22 11:51:53 +0100321int ssl_sock_load_files_into_ckch(const char *path, struct ckch_data *data, char **err)
William Lallemand03c331c2020-05-13 10:10:01 +0200322{
William Lallemand8e8581e2020-10-20 17:36:46 +0200323 struct buffer *fp = NULL;
William Lallemand03c331c2020-05-13 10:10:01 +0200324 int ret = 1;
Remi Tricot-Le Breton9bf3a1f2022-05-09 11:07:13 +0200325 struct stat st;
William Lallemand03c331c2020-05-13 10:10:01 +0200326
327 /* try to load the PEM */
William Lallemand52ddd992022-11-22 11:51:53 +0100328 if (ssl_sock_load_pem_into_ckch(path, NULL, data , err) != 0) {
William Lallemand03c331c2020-05-13 10:10:01 +0200329 goto end;
330 }
331
William Lallemand8e8581e2020-10-20 17:36:46 +0200332 fp = alloc_trash_chunk();
333 if (!fp) {
334 memprintf(err, "%sCan't allocate memory\n", err && *err ? *err : "");
335 goto end;
336 }
337
338 if (!chunk_strcpy(fp, path) || (b_data(fp) > MAXPATHLEN)) {
339 memprintf(err, "%s '%s' filename too long'.\n",
340 err && *err ? *err : "", fp->area);
341 ret = 1;
342 goto end;
343 }
344
William Lallemand089c1382020-10-23 17:35:12 +0200345 /* remove the ".crt" extension */
William Lallemand8e8581e2020-10-20 17:36:46 +0200346 if (global_ssl.extra_files_noext) {
347 char *ext;
348
349 /* look for the extension */
350 if ((ext = strrchr(fp->area, '.'))) {
William Lallemand8e8581e2020-10-20 17:36:46 +0200351
Tim Duesterhuse5ff1412021-01-02 22:31:53 +0100352 if (strcmp(ext, ".crt") == 0) {
William Lallemand8e8581e2020-10-20 17:36:46 +0200353 *ext = '\0';
William Lallemand089c1382020-10-23 17:35:12 +0200354 fp->data = strlen(fp->area);
355 }
William Lallemand8e8581e2020-10-20 17:36:46 +0200356 }
357
358 }
359
William Lallemand52ddd992022-11-22 11:51:53 +0100360 if (data->key == NULL) {
Remi Tricot-Le Breton1bad7db2022-06-07 16:29:44 +0200361 /* If no private key was found yet and we cannot look for it in extra
362 * files, raise an error.
363 */
364 if (!(global_ssl.extra_files & SSL_GF_KEY)) {
365 memprintf(err, "%sNo Private Key found in '%s'.\n", err && *err ? *err : "", fp->area);
366 goto end;
367 }
William Lallemand8e8581e2020-10-20 17:36:46 +0200368
Remi Tricot-Le Breton1bad7db2022-06-07 16:29:44 +0200369 /* try to load an external private key if it wasn't in the PEM */
370 if (!chunk_strcat(fp, ".key") || (b_data(fp) > MAXPATHLEN)) {
371 memprintf(err, "%s '%s' filename too long'.\n",
Remi Tricot-Le Breton9bf3a1f2022-05-09 11:07:13 +0200372 err && *err ? *err : "", fp->area);
Remi Tricot-Le Breton1bad7db2022-06-07 16:29:44 +0200373 ret = 1;
William Lallemand8e8581e2020-10-20 17:36:46 +0200374 goto end;
375 }
William Lallemand03c331c2020-05-13 10:10:01 +0200376
Remi Tricot-Le Breton1bad7db2022-06-07 16:29:44 +0200377 if (stat(fp->area, &st) == 0) {
William Lallemand52ddd992022-11-22 11:51:53 +0100378 if (ssl_sock_load_key_into_ckch(fp->area, NULL, data, err)) {
Remi Tricot-Le Breton1bad7db2022-06-07 16:29:44 +0200379 memprintf(err, "%s '%s' is present but cannot be read or parsed'.\n",
380 err && *err ? *err : "", fp->area);
381 goto end;
382 }
383 }
384
William Lallemand52ddd992022-11-22 11:51:53 +0100385 if (data->key == NULL) {
Remi Tricot-Le Breton1bad7db2022-06-07 16:29:44 +0200386 memprintf(err, "%sNo Private Key found in '%s'.\n", err && *err ? *err : "", fp->area);
387 goto end;
388 }
389 /* remove the added extension */
390 *(fp->area + fp->data - strlen(".key")) = '\0';
391 b_sub(fp, strlen(".key"));
William Lallemand03c331c2020-05-13 10:10:01 +0200392 }
Remi Tricot-Le Breton9bf3a1f2022-05-09 11:07:13 +0200393
William Lallemand03c331c2020-05-13 10:10:01 +0200394
William Lallemand52ddd992022-11-22 11:51:53 +0100395 if (!X509_check_private_key(data->cert, data->key)) {
William Lallemand03c331c2020-05-13 10:10:01 +0200396 memprintf(err, "%sinconsistencies between private key and certificate loaded '%s'.\n",
397 err && *err ? *err : "", path);
398 goto end;
399 }
400
Ilya Shipitsinc47d6762021-02-13 11:45:33 +0500401#ifdef HAVE_SSL_SCTL
William Lallemand03c331c2020-05-13 10:10:01 +0200402 /* try to load the sctl file */
403 if (global_ssl.extra_files & SSL_GF_SCTL) {
William Lallemand03c331c2020-05-13 10:10:01 +0200404 struct stat st;
405
William Lallemand8e8581e2020-10-20 17:36:46 +0200406 if (!chunk_strcat(fp, ".sctl") || b_data(fp) > MAXPATHLEN) {
407 memprintf(err, "%s '%s' filename too long'.\n",
408 err && *err ? *err : "", fp->area);
409 ret = 1;
410 goto end;
411 }
412
413 if (stat(fp->area, &st) == 0) {
William Lallemand52ddd992022-11-22 11:51:53 +0100414 if (ssl_sock_load_sctl_from_file(fp->area, NULL, data, err)) {
William Lallemand03c331c2020-05-13 10:10:01 +0200415 memprintf(err, "%s '%s.sctl' is present but cannot be read or parsed'.\n",
William Lallemand8e8581e2020-10-20 17:36:46 +0200416 err && *err ? *err : "", fp->area);
William Lallemand03c331c2020-05-13 10:10:01 +0200417 ret = 1;
418 goto end;
419 }
420 }
William Lallemand8e8581e2020-10-20 17:36:46 +0200421 /* remove the added extension */
422 *(fp->area + fp->data - strlen(".sctl")) = '\0';
423 b_sub(fp, strlen(".sctl"));
William Lallemand03c331c2020-05-13 10:10:01 +0200424 }
425#endif
426
427 /* try to load an ocsp response file */
428 if (global_ssl.extra_files & SSL_GF_OCSP) {
William Lallemand03c331c2020-05-13 10:10:01 +0200429 struct stat st;
430
William Lallemand8e8581e2020-10-20 17:36:46 +0200431 if (!chunk_strcat(fp, ".ocsp") || b_data(fp) > MAXPATHLEN) {
432 memprintf(err, "%s '%s' filename too long'.\n",
433 err && *err ? *err : "", fp->area);
434 ret = 1;
435 goto end;
436 }
437
438 if (stat(fp->area, &st) == 0) {
William Lallemand52ddd992022-11-22 11:51:53 +0100439 if (ssl_sock_load_ocsp_response_from_file(fp->area, NULL, data, err)) {
William Lallemand03c331c2020-05-13 10:10:01 +0200440 ret = 1;
441 goto end;
442 }
443 }
William Lallemand8e8581e2020-10-20 17:36:46 +0200444 /* remove the added extension */
445 *(fp->area + fp->data - strlen(".ocsp")) = '\0';
446 b_sub(fp, strlen(".ocsp"));
William Lallemand03c331c2020-05-13 10:10:01 +0200447 }
448
449#ifndef OPENSSL_IS_BORINGSSL /* Useless for BoringSSL */
William Lallemand52ddd992022-11-22 11:51:53 +0100450 if (data->ocsp_response && (global_ssl.extra_files & SSL_GF_OCSP_ISSUER)) {
William Lallemand03c331c2020-05-13 10:10:01 +0200451 /* if no issuer was found, try to load an issuer from the .issuer */
William Lallemand52ddd992022-11-22 11:51:53 +0100452 if (!data->ocsp_issuer) {
William Lallemand03c331c2020-05-13 10:10:01 +0200453 struct stat st;
William Lallemand8e8581e2020-10-20 17:36:46 +0200454
455 if (!chunk_strcat(fp, ".issuer") || b_data(fp) > MAXPATHLEN) {
456 memprintf(err, "%s '%s' filename too long'.\n",
457 err && *err ? *err : "", fp->area);
458 ret = 1;
459 goto end;
460 }
William Lallemand03c331c2020-05-13 10:10:01 +0200461
William Lallemand8e8581e2020-10-20 17:36:46 +0200462 if (stat(fp->area, &st) == 0) {
William Lallemand52ddd992022-11-22 11:51:53 +0100463 if (ssl_sock_load_issuer_file_into_ckch(fp->area, NULL, data, err)) {
William Lallemand03c331c2020-05-13 10:10:01 +0200464 ret = 1;
465 goto end;
466 }
467
William Lallemand52ddd992022-11-22 11:51:53 +0100468 if (X509_check_issued(data->ocsp_issuer, data->cert) != X509_V_OK) {
William Lallemand03c331c2020-05-13 10:10:01 +0200469 memprintf(err, "%s '%s' is not an issuer'.\n",
William Lallemand8e8581e2020-10-20 17:36:46 +0200470 err && *err ? *err : "", fp->area);
William Lallemand03c331c2020-05-13 10:10:01 +0200471 ret = 1;
472 goto end;
473 }
474 }
William Lallemand8e8581e2020-10-20 17:36:46 +0200475 /* remove the added extension */
476 *(fp->area + fp->data - strlen(".issuer")) = '\0';
477 b_sub(fp, strlen(".issuer"));
William Lallemand03c331c2020-05-13 10:10:01 +0200478 }
479 }
480#endif
481
482 ret = 0;
483
484end:
485
486 ERR_clear_error();
487
488 /* Something went wrong in one of the reads */
489 if (ret != 0)
William Lallemand52ddd992022-11-22 11:51:53 +0100490 ssl_sock_free_cert_key_and_chain_contents(data);
William Lallemand03c331c2020-05-13 10:10:01 +0200491
William Lallemand8e8581e2020-10-20 17:36:46 +0200492 free_trash_chunk(fp);
493
William Lallemand03c331c2020-05-13 10:10:01 +0200494 return ret;
495}
496
497/*
498 * Try to load a private key file from a <path> or a buffer <buf>
499 *
500 * If it failed you should not attempt to use the ckch but free it.
501 *
502 * Return 0 on success or != 0 on failure
503 */
William Lallemand52ddd992022-11-22 11:51:53 +0100504int ssl_sock_load_key_into_ckch(const char *path, char *buf, struct ckch_data *data , char **err)
William Lallemand03c331c2020-05-13 10:10:01 +0200505{
506 BIO *in = NULL;
507 int ret = 1;
508 EVP_PKEY *key = NULL;
509
510 if (buf) {
511 /* reading from a buffer */
512 in = BIO_new_mem_buf(buf, -1);
513 if (in == NULL) {
514 memprintf(err, "%sCan't allocate memory\n", err && *err ? *err : "");
515 goto end;
516 }
517
518 } else {
519 /* reading from a file */
520 in = BIO_new(BIO_s_file());
521 if (in == NULL)
522 goto end;
523
524 if (BIO_read_filename(in, path) <= 0)
525 goto end;
526 }
527
528 /* Read Private Key */
529 key = PEM_read_bio_PrivateKey(in, NULL, NULL, NULL);
530 if (key == NULL) {
531 memprintf(err, "%sunable to load private key from file '%s'.\n",
532 err && *err ? *err : "", path);
533 goto end;
534 }
535
536 ret = 0;
537
William Lallemand52ddd992022-11-22 11:51:53 +0100538 SWAP(data->key, key);
William Lallemand03c331c2020-05-13 10:10:01 +0200539
540end:
541
542 ERR_clear_error();
543 if (in)
544 BIO_free(in);
545 if (key)
546 EVP_PKEY_free(key);
547
548 return ret;
549}
550
551/*
552 * Try to load a PEM file from a <path> or a buffer <buf>
553 * The PEM must contain at least a Certificate,
554 * It could contain a DH, a certificate chain and a PrivateKey.
555 *
556 * If it failed you should not attempt to use the ckch but free it.
557 *
558 * Return 0 on success or != 0 on failure
559 */
William Lallemand52ddd992022-11-22 11:51:53 +0100560int ssl_sock_load_pem_into_ckch(const char *path, char *buf, struct ckch_data *data , char **err)
William Lallemand03c331c2020-05-13 10:10:01 +0200561{
562 BIO *in = NULL;
563 int ret = 1;
564 X509 *ca;
565 X509 *cert = NULL;
566 EVP_PKEY *key = NULL;
Remi Tricot-Le Bretonc76c3c42022-02-11 12:04:55 +0100567 HASSL_DH *dh = NULL;
William Lallemand03c331c2020-05-13 10:10:01 +0200568 STACK_OF(X509) *chain = NULL;
569
570 if (buf) {
571 /* reading from a buffer */
572 in = BIO_new_mem_buf(buf, -1);
573 if (in == NULL) {
574 memprintf(err, "%sCan't allocate memory\n", err && *err ? *err : "");
575 goto end;
576 }
577
578 } else {
579 /* reading from a file */
580 in = BIO_new(BIO_s_file());
581 if (in == NULL) {
582 memprintf(err, "%sCan't allocate memory\n", err && *err ? *err : "");
583 goto end;
584 }
585
586 if (BIO_read_filename(in, path) <= 0) {
587 memprintf(err, "%scannot open the file '%s'.\n",
588 err && *err ? *err : "", path);
589 goto end;
590 }
591 }
592
593 /* Read Private Key */
594 key = PEM_read_bio_PrivateKey(in, NULL, NULL, NULL);
595 /* no need to check for errors here, because the private key could be loaded later */
596
597#ifndef OPENSSL_NO_DH
598 /* Seek back to beginning of file */
599 if (BIO_reset(in) == -1) {
600 memprintf(err, "%san error occurred while reading the file '%s'.\n",
601 err && *err ? *err : "", path);
602 goto end;
603 }
604
Remi Tricot-Le Bretonc76c3c42022-02-11 12:04:55 +0100605 dh = ssl_sock_get_dh_from_bio(in);
606 ERR_clear_error();
William Lallemand03c331c2020-05-13 10:10:01 +0200607 /* no need to return an error there, dh is not mandatory */
608#endif
609
610 /* Seek back to beginning of file */
611 if (BIO_reset(in) == -1) {
612 memprintf(err, "%san error occurred while reading the file '%s'.\n",
613 err && *err ? *err : "", path);
614 goto end;
615 }
616
617 /* Read Certificate */
618 cert = PEM_read_bio_X509_AUX(in, NULL, NULL, NULL);
619 if (cert == NULL) {
William Lallemanda5384522022-10-25 15:53:01 +0200620 ret = ERR_get_error();
621 memprintf(err, "%sunable to load certificate from file '%s': %s.\n",
622 err && *err ? *err : "", path, ERR_reason_error_string(ret));
William Lallemand03c331c2020-05-13 10:10:01 +0200623 goto end;
624 }
625
626 /* Look for a Certificate Chain */
627 while ((ca = PEM_read_bio_X509(in, NULL, NULL, NULL))) {
628 if (chain == NULL)
629 chain = sk_X509_new_null();
630 if (!sk_X509_push(chain, ca)) {
631 X509_free(ca);
William Lallemand432cd1a2022-10-25 15:55:13 +0200632 break;
William Lallemand03c331c2020-05-13 10:10:01 +0200633 }
634 }
635
636 ret = ERR_get_error();
William Lallemand78c7a062022-11-15 17:12:03 +0100637 if (ret && !(ERR_GET_LIB(ret) == ERR_LIB_PEM && ERR_GET_REASON(ret) == PEM_R_NO_START_LINE)) {
William Lallemandf784b902022-10-25 12:31:39 +0200638 memprintf(err, "%sunable to load certificate chain from file '%s': %s\n",
639 err && *err ? *err : "", path, ERR_reason_error_string(ret));
William Lallemand03c331c2020-05-13 10:10:01 +0200640 goto end;
641 }
642
William Lallemand52ddd992022-11-22 11:51:53 +0100643 /* once it loaded the PEM, it should remove everything else in the data */
644 if (data->ocsp_response) {
645 ha_free(&data->ocsp_response->area);
646 ha_free(&data->ocsp_response);
William Lallemand03c331c2020-05-13 10:10:01 +0200647 }
648
William Lallemand52ddd992022-11-22 11:51:53 +0100649 if (data->sctl) {
650 ha_free(&data->sctl->area);
651 ha_free(&data->sctl);
William Lallemand03c331c2020-05-13 10:10:01 +0200652 }
653
William Lallemand52ddd992022-11-22 11:51:53 +0100654 if (data->ocsp_issuer) {
655 X509_free(data->ocsp_issuer);
656 data->ocsp_issuer = NULL;
William Lallemand03c331c2020-05-13 10:10:01 +0200657 }
658
William Lallemand52ddd992022-11-22 11:51:53 +0100659 /* no error, fill data with new context, old context will be free at end: */
660 SWAP(data->key, key);
661 SWAP(data->dh, dh);
662 SWAP(data->cert, cert);
663 SWAP(data->chain, chain);
William Lallemand03c331c2020-05-13 10:10:01 +0200664
665 ret = 0;
666
667end:
668
669 ERR_clear_error();
670 if (in)
671 BIO_free(in);
672 if (key)
673 EVP_PKEY_free(key);
674 if (dh)
Remi Tricot-Le Bretonc76c3c42022-02-11 12:04:55 +0100675 HASSL_DH_free(dh);
William Lallemand03c331c2020-05-13 10:10:01 +0200676 if (cert)
677 X509_free(cert);
678 if (chain)
679 sk_X509_pop_free(chain, X509_free);
680
681 return ret;
682}
683
684/* Frees the contents of a cert_key_and_chain
685 */
William Lallemand52ddd992022-11-22 11:51:53 +0100686void ssl_sock_free_cert_key_and_chain_contents(struct ckch_data *data)
William Lallemand03c331c2020-05-13 10:10:01 +0200687{
William Lallemand52ddd992022-11-22 11:51:53 +0100688 if (!data)
William Lallemand03c331c2020-05-13 10:10:01 +0200689 return;
690
691 /* Free the certificate and set pointer to NULL */
William Lallemand52ddd992022-11-22 11:51:53 +0100692 if (data->cert)
693 X509_free(data->cert);
694 data->cert = NULL;
William Lallemand03c331c2020-05-13 10:10:01 +0200695
696 /* Free the key and set pointer to NULL */
William Lallemand52ddd992022-11-22 11:51:53 +0100697 if (data->key)
698 EVP_PKEY_free(data->key);
699 data->key = NULL;
William Lallemand03c331c2020-05-13 10:10:01 +0200700
701 /* Free each certificate in the chain */
William Lallemand52ddd992022-11-22 11:51:53 +0100702 if (data->chain)
703 sk_X509_pop_free(data->chain, X509_free);
704 data->chain = NULL;
William Lallemand03c331c2020-05-13 10:10:01 +0200705
William Lallemand52ddd992022-11-22 11:51:53 +0100706 if (data->dh)
707 HASSL_DH_free(data->dh);
708 data->dh = NULL;
William Lallemand03c331c2020-05-13 10:10:01 +0200709
William Lallemand52ddd992022-11-22 11:51:53 +0100710 if (data->sctl) {
711 ha_free(&data->sctl->area);
712 ha_free(&data->sctl);
William Lallemand03c331c2020-05-13 10:10:01 +0200713 }
714
William Lallemand52ddd992022-11-22 11:51:53 +0100715 if (data->ocsp_response) {
716 ha_free(&data->ocsp_response->area);
717 ha_free(&data->ocsp_response);
William Lallemand03c331c2020-05-13 10:10:01 +0200718 }
719
William Lallemand52ddd992022-11-22 11:51:53 +0100720 if (data->ocsp_issuer)
721 X509_free(data->ocsp_issuer);
722 data->ocsp_issuer = NULL;
Remi Tricot-Le Bretoncc346672022-12-20 11:11:08 +0100723
William Lallemand60289bf2024-02-26 17:53:02 +0100724 OCSP_CERTID_free(data->ocsp_cid);
725 data->ocsp_cid = NULL;
William Lallemand03c331c2020-05-13 10:10:01 +0200726}
727
728/*
729 *
730 * This function copy a cert_key_and_chain in memory
731 *
732 * It's used to try to apply changes on a ckch before committing them, because
733 * most of the time it's not possible to revert those changes
734 *
735 * Return a the dst or NULL
736 */
William Lallemand52ddd992022-11-22 11:51:53 +0100737struct ckch_data *ssl_sock_copy_cert_key_and_chain(struct ckch_data *src,
738 struct ckch_data *dst)
William Lallemand03c331c2020-05-13 10:10:01 +0200739{
William Lallemand6c096142021-02-23 14:45:45 +0100740 if (!src || !dst)
741 return NULL;
742
William Lallemand03c331c2020-05-13 10:10:01 +0200743 if (src->cert) {
744 dst->cert = src->cert;
745 X509_up_ref(src->cert);
746 }
747
748 if (src->key) {
749 dst->key = src->key;
750 EVP_PKEY_up_ref(src->key);
751 }
752
753 if (src->chain) {
754 dst->chain = X509_chain_up_ref(src->chain);
755 }
756
757 if (src->dh) {
Uriah Pollock3cbf09e2022-11-23 16:41:25 +0100758#ifndef USE_OPENSSL_WOLFSSL
Remi Tricot-Le Bretonc76c3c42022-02-11 12:04:55 +0100759 HASSL_DH_up_ref(src->dh);
William Lallemand03c331c2020-05-13 10:10:01 +0200760 dst->dh = src->dh;
Uriah Pollock3cbf09e2022-11-23 16:41:25 +0100761#else
762 dst->dh = wolfSSL_DH_dup(src->dh);
763 if (!dst->dh)
764 goto error;
765#endif
William Lallemand03c331c2020-05-13 10:10:01 +0200766 }
767
768 if (src->sctl) {
769 struct buffer *sctl;
770
771 sctl = calloc(1, sizeof(*sctl));
772 if (!chunk_dup(sctl, src->sctl)) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100773 ha_free(&sctl);
William Lallemand03c331c2020-05-13 10:10:01 +0200774 goto error;
775 }
776 dst->sctl = sctl;
777 }
778
779 if (src->ocsp_response) {
780 struct buffer *ocsp_response;
781
782 ocsp_response = calloc(1, sizeof(*ocsp_response));
783 if (!chunk_dup(ocsp_response, src->ocsp_response)) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100784 ha_free(&ocsp_response);
William Lallemand03c331c2020-05-13 10:10:01 +0200785 goto error;
786 }
787 dst->ocsp_response = ocsp_response;
788 }
789
790 if (src->ocsp_issuer) {
791 X509_up_ref(src->ocsp_issuer);
792 dst->ocsp_issuer = src->ocsp_issuer;
793 }
794
Remi Tricot-Le Bretoncc346672022-12-20 11:11:08 +0100795 dst->ocsp_cid = OCSP_CERTID_dup(src->ocsp_cid);
796
Remi Tricot-Le Breton2760f4a2024-02-07 16:38:40 +0100797 dst->ocsp_update_mode = src->ocsp_update_mode;
798
William Lallemand03c331c2020-05-13 10:10:01 +0200799 return dst;
800
801error:
802
803 /* free everything */
804 ssl_sock_free_cert_key_and_chain_contents(dst);
805
806 return NULL;
807}
808
809/*
810 * return 0 on success or != 0 on failure
811 */
William Lallemand52ddd992022-11-22 11:51:53 +0100812int ssl_sock_load_issuer_file_into_ckch(const char *path, char *buf, struct ckch_data *data, char **err)
William Lallemand03c331c2020-05-13 10:10:01 +0200813{
814 int ret = 1;
815 BIO *in = NULL;
816 X509 *issuer;
817
818 if (buf) {
819 /* reading from a buffer */
820 in = BIO_new_mem_buf(buf, -1);
821 if (in == NULL) {
822 memprintf(err, "%sCan't allocate memory\n", err && *err ? *err : "");
823 goto end;
824 }
825
826 } else {
827 /* reading from a file */
828 in = BIO_new(BIO_s_file());
829 if (in == NULL)
830 goto end;
831
832 if (BIO_read_filename(in, path) <= 0)
833 goto end;
834 }
835
836 issuer = PEM_read_bio_X509_AUX(in, NULL, NULL, NULL);
837 if (!issuer) {
838 memprintf(err, "%s'%s' cannot be read or parsed'.\n",
839 err && *err ? *err : "", path);
840 goto end;
841 }
William Lallemand52ddd992022-11-22 11:51:53 +0100842 /* no error, fill data with new context, old context must be free */
843 if (data->ocsp_issuer)
844 X509_free(data->ocsp_issuer);
845 data->ocsp_issuer = issuer;
William Lallemand03c331c2020-05-13 10:10:01 +0200846 ret = 0;
847
848end:
849
850 ERR_clear_error();
851 if (in)
852 BIO_free(in);
853
854 return ret;
855}
856
857/******************** ckch_store functions ***********************************
858 * The ckch_store is a structure used to cache and index the SSL files used in
859 * configuration
860 */
861
862/*
863 * Free a ckch_store, its ckch, its instances and remove it from the ebtree
864 */
865void ckch_store_free(struct ckch_store *store)
866{
867 struct ckch_inst *inst, *inst_s;
868
869 if (!store)
870 return;
871
William Lallemand03c331c2020-05-13 10:10:01 +0200872 list_for_each_entry_safe(inst, inst_s, &store->ckch_inst, by_ckchs) {
873 ckch_inst_free(inst);
874 }
875 ebmb_delete(&store->node);
Remi Tricot-Le Bretonfcf47472024-02-07 16:38:44 +0100876
877 ssl_sock_free_cert_key_and_chain_contents(store->data);
878 ha_free(&store->data);
879
William Lallemand03c331c2020-05-13 10:10:01 +0200880 free(store);
881}
882
883/*
884 * create and initialize a ckch_store
885 * <path> is the key name
886 * <nmemb> is the number of store->ckch objects to allocate
887 *
888 * Return a ckch_store or NULL upon failure.
889 */
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200890struct ckch_store *ckch_store_new(const char *filename)
William Lallemand03c331c2020-05-13 10:10:01 +0200891{
892 struct ckch_store *store;
893 int pathlen;
894
895 pathlen = strlen(filename);
896 store = calloc(1, sizeof(*store) + pathlen + 1);
897 if (!store)
898 return NULL;
899
William Lallemand03c331c2020-05-13 10:10:01 +0200900 memcpy(store->path, filename, pathlen + 1);
901
902 LIST_INIT(&store->ckch_inst);
903 LIST_INIT(&store->crtlist_entry);
904
William Lallemand52ddd992022-11-22 11:51:53 +0100905 store->data = calloc(1, sizeof(*store->data));
906 if (!store->data)
William Lallemand03c331c2020-05-13 10:10:01 +0200907 goto error;
908
909 return store;
910error:
911 ckch_store_free(store);
912 return NULL;
913}
914
915/* allocate and duplicate a ckch_store
916 * Return a new ckch_store or NULL */
917struct ckch_store *ckchs_dup(const struct ckch_store *src)
918{
919 struct ckch_store *dst;
920
William Lallemand6c096142021-02-23 14:45:45 +0100921 if (!src)
922 return NULL;
923
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200924 dst = ckch_store_new(src->path);
Eric Salama6ac61e32021-02-23 16:50:57 +0100925 if (!dst)
926 return NULL;
William Lallemand03c331c2020-05-13 10:10:01 +0200927
William Lallemand52ddd992022-11-22 11:51:53 +0100928 if (!ssl_sock_copy_cert_key_and_chain(src->data, dst->data))
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200929 goto error;
William Lallemand03c331c2020-05-13 10:10:01 +0200930
931 return dst;
932
933error:
934 ckch_store_free(dst);
935
936 return NULL;
937}
938
939/*
940 * lookup a path into the ckchs tree.
941 */
942struct ckch_store *ckchs_lookup(char *path)
943{
944 struct ebmb_node *eb;
945
946 eb = ebst_lookup(&ckchs_tree, path);
947 if (!eb)
948 return NULL;
949
950 return ebmb_entry(eb, struct ckch_store, node);
951}
952
953/*
954 * This function allocate a ckch_store and populate it with certificates from files.
955 */
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200956struct ckch_store *ckchs_load_cert_file(char *path, char **err)
William Lallemand03c331c2020-05-13 10:10:01 +0200957{
958 struct ckch_store *ckchs;
959
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200960 ckchs = ckch_store_new(path);
William Lallemand03c331c2020-05-13 10:10:01 +0200961 if (!ckchs) {
962 memprintf(err, "%sunable to allocate memory.\n", err && *err ? *err : "");
963 goto end;
964 }
William Lallemand03c331c2020-05-13 10:10:01 +0200965
William Lallemand52ddd992022-11-22 11:51:53 +0100966 if (ssl_sock_load_files_into_ckch(path, ckchs->data, err) == 1)
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200967 goto end;
William Lallemand03c331c2020-05-13 10:10:01 +0200968
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200969 /* insert into the ckchs tree */
970 memcpy(ckchs->path, path, strlen(path) + 1);
971 ebst_insert(&ckchs_tree, &ckchs->node);
William Lallemand03c331c2020-05-13 10:10:01 +0200972 return ckchs;
973
974end:
975 ckch_store_free(ckchs);
976
977 return NULL;
978}
979
William Lallemandfa1d8b42020-05-13 15:46:10 +0200980
981/******************** ckch_inst functions ******************************/
982
983/* unlink a ckch_inst, free all SNIs, free the ckch_inst */
984/* The caller must use the lock of the bind_conf if used with inserted SNIs */
985void ckch_inst_free(struct ckch_inst *inst)
986{
987 struct sni_ctx *sni, *sni_s;
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +0100988 struct ckch_inst_link_ref *link_ref, *link_ref_s;
William Lallemandfa1d8b42020-05-13 15:46:10 +0200989
990 if (inst == NULL)
991 return;
992
993 list_for_each_entry_safe(sni, sni_s, &inst->sni_ctx, by_ckch_inst) {
994 SSL_CTX_free(sni->ctx);
Willy Tarreau2b718102021-04-21 07:32:39 +0200995 LIST_DELETE(&sni->by_ckch_inst);
William Lallemandfa1d8b42020-05-13 15:46:10 +0200996 ebmb_delete(&sni->name);
997 free(sni);
998 }
Remi Tricot-Le Bretonf3eedfe2021-01-25 17:19:44 +0100999 SSL_CTX_free(inst->ctx);
1000 inst->ctx = NULL;
Willy Tarreau2b718102021-04-21 07:32:39 +02001001 LIST_DELETE(&inst->by_ckchs);
1002 LIST_DELETE(&inst->by_crtlist_entry);
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001003
William Lallemande0fa91f2022-08-31 14:26:49 +02001004 /* Free the cafile_link_refs list */
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001005 list_for_each_entry_safe(link_ref, link_ref_s, &inst->cafile_link_refs, list) {
William Lallemande0fa91f2022-08-31 14:26:49 +02001006 if (link_ref->link && LIST_INLIST(&link_ref->link->list)) {
1007 /* Try to detach and free the ckch_inst_link only if it
1008 * was attached, this way it can be used to loop from
1009 * the caller */
1010 LIST_DEL_INIT(&link_ref->link->list);
1011 ha_free(&link_ref->link);
1012 }
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001013 LIST_DELETE(&link_ref->list);
1014 free(link_ref);
1015 }
1016
William Lallemandfa1d8b42020-05-13 15:46:10 +02001017 free(inst);
1018}
1019
1020/* Alloc and init a ckch_inst */
1021struct ckch_inst *ckch_inst_new()
1022{
1023 struct ckch_inst *ckch_inst;
1024
1025 ckch_inst = calloc(1, sizeof *ckch_inst);
1026 if (!ckch_inst)
1027 return NULL;
1028
1029 LIST_INIT(&ckch_inst->sni_ctx);
1030 LIST_INIT(&ckch_inst->by_ckchs);
1031 LIST_INIT(&ckch_inst->by_crtlist_entry);
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001032 LIST_INIT(&ckch_inst->cafile_link_refs);
William Lallemandfa1d8b42020-05-13 15:46:10 +02001033
1034 return ckch_inst;
1035}
1036
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001037
1038/******************** ssl_store functions ******************************/
Remi Tricot-Le Breton9f0c9362021-02-19 15:06:28 +01001039struct eb_root cafile_tree = EB_ROOT;
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001040
Remi Tricot-Le Breton9f0c9362021-02-19 15:06:28 +01001041/*
1042 * Returns the cafile_entry found in the cafile_tree indexed by the path 'path'.
1043 * If 'oldest_entry' is 1, returns the "original" cafile_entry (since
1044 * during a set cafile/commit cafile cycle there might be two entries for any
1045 * given path, the original one and the new one set via the CLI but not
1046 * committed yet).
1047 */
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001048struct cafile_entry *ssl_store_get_cafile_entry(char *path, int oldest_entry)
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001049{
Remi Tricot-Le Breton9f0c9362021-02-19 15:06:28 +01001050 struct cafile_entry *ca_e = NULL;
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001051 struct ebmb_node *eb;
1052
1053 eb = ebst_lookup(&cafile_tree, path);
Remi Tricot-Le Breton9f0c9362021-02-19 15:06:28 +01001054 while (eb) {
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001055 ca_e = ebmb_entry(eb, struct cafile_entry, node);
Remi Tricot-Le Breton9f0c9362021-02-19 15:06:28 +01001056 /* The ebst_lookup in a tree that has duplicates returns the
1057 * oldest entry first. If we want the latest entry, we need to
1058 * iterate over all the duplicates until we find the last one
1059 * (in our case there should never be more than two entries for
1060 * any given path). */
1061 if (oldest_entry)
1062 return ca_e;
1063 eb = ebmb_next_dup(eb);
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001064 }
Remi Tricot-Le Breton9f0c9362021-02-19 15:06:28 +01001065 return ca_e;
1066}
1067
Remi Tricot-Le Breton38c999b2021-02-23 16:28:43 +01001068int ssl_store_add_uncommitted_cafile_entry(struct cafile_entry *entry)
1069{
1070 return (ebst_insert(&cafile_tree, &entry->node) != &entry->node);
1071}
1072
Remi Tricot-Le Breton9f0c9362021-02-19 15:06:28 +01001073X509_STORE* ssl_store_get0_locations_file(char *path)
1074{
1075 struct cafile_entry *ca_e = ssl_store_get_cafile_entry(path, 0);
1076
1077 if (ca_e)
1078 return ca_e->ca_store;
1079
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001080 return NULL;
1081}
1082
Remi Tricot-Le Breton5daff3c2021-02-22 15:54:55 +01001083/* Create a cafile_entry object, without adding it to the cafile_tree. */
Remi Tricot-Le Breton0bb48242021-04-16 17:59:23 +02001084struct 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 +01001085{
1086 struct cafile_entry *ca_e;
1087 int pathlen;
1088
1089 pathlen = strlen(path);
1090
1091 ca_e = calloc(1, sizeof(*ca_e) + pathlen + 1);
1092 if (ca_e) {
1093 memcpy(ca_e->path, path, pathlen + 1);
1094 ca_e->ca_store = store;
Remi Tricot-Le Breton0bb48242021-04-16 17:59:23 +02001095 ca_e->type = type;
Remi Tricot-Le Breton5daff3c2021-02-22 15:54:55 +01001096 LIST_INIT(&ca_e->ckch_inst_link);
1097 }
1098 return ca_e;
1099}
1100
William Lallemand62c0b992022-07-29 17:50:58 +02001101
1102/* Duplicate a cafile_entry
1103 * Allocate the X509_STORE and copy the X509 and CRL inside.
1104 *
1105 * Return the newly allocated cafile_entry or NULL.
1106 *
1107 */
1108struct cafile_entry *ssl_store_dup_cafile_entry(struct cafile_entry *src)
1109{
1110 struct cafile_entry *dst = NULL;
1111 X509_STORE *store = NULL;
1112 STACK_OF(X509_OBJECT) *objs;
1113 int i;
1114
1115 if (!src)
1116 return NULL;
1117
1118 if (src->ca_store) {
1119 /* if there was a store in the src, copy it */
1120 store = X509_STORE_new();
1121 if (!store)
1122 goto err;
1123
1124 objs = X509_STORE_get0_objects(src->ca_store);
1125 for (i = 0; i < sk_X509_OBJECT_num(objs); i++) {
1126 X509 *cert;
1127 X509_CRL *crl;
1128
1129 cert = X509_OBJECT_get0_X509(sk_X509_OBJECT_value(objs, i));
1130 if (cert) {
1131 if (X509_STORE_add_cert(store, cert) == 0) {
1132 /* only exits on error if the error is not about duplicate certificates */
1133 if (!(ERR_GET_REASON(ERR_get_error()) == X509_R_CERT_ALREADY_IN_HASH_TABLE)) {
1134 goto err;
1135 }
1136 }
1137
1138 }
1139 crl = X509_OBJECT_get0_X509_CRL(sk_X509_OBJECT_value(objs, i));
1140 if (crl) {
1141 if (X509_STORE_add_crl(store, crl) == 0) {
1142 /* only exits on error if the error is not about duplicate certificates */
1143 if (!(ERR_GET_REASON(ERR_get_error()) == X509_R_CERT_ALREADY_IN_HASH_TABLE)) {
1144 goto err;
1145 }
1146 }
1147
1148 }
1149 }
1150 }
1151 dst = ssl_store_create_cafile_entry(src->path, store, src->type);
1152
1153 return dst;
1154
1155err:
1156 X509_STORE_free(store);
1157 ha_free(&dst);
1158
1159 return NULL;
1160}
1161
Remi Tricot-Le Breton5daff3c2021-02-22 15:54:55 +01001162/* Delete a cafile_entry. The caller is responsible from removing this entry
1163 * from the cafile_tree first if is was previously added into it. */
1164void ssl_store_delete_cafile_entry(struct cafile_entry *ca_e)
1165{
1166 struct ckch_inst_link *link, *link_s;
1167 if (!ca_e)
1168 return;
1169
1170 X509_STORE_free(ca_e->ca_store);
1171
1172 list_for_each_entry_safe(link, link_s, &ca_e->ckch_inst_link, list) {
1173 struct ckch_inst *inst = link->ckch_inst;
1174 struct ckch_inst_link_ref *link_ref, *link_ref_s;
1175 list_for_each_entry_safe(link_ref, link_ref_s, &inst->cafile_link_refs, list) {
1176 if (link_ref->link == link) {
1177 LIST_DELETE(&link_ref->list);
1178 free(link_ref);
1179 break;
1180 }
1181 }
1182 LIST_DELETE(&link->list);
1183 free(link);
1184 }
1185
1186 free(ca_e);
1187}
1188
Remi Tricot-Le Breton383fb142021-02-22 18:26:14 +01001189/*
William Lallemandd4774d32022-07-29 17:08:02 +02001190 * Fill a cafile_entry <ca_e> X509_STORE ca_e->store out of a buffer <cert_buf>
1191 * instead of out of a file. The <append> field should be set to 1 if you want
1192 * to keep the existing X509_STORE and append data to it.
1193 *
1194 * This function is used when the "set ssl ca-file" cli command is used.
Remi Tricot-Le Breton383fb142021-02-22 18:26:14 +01001195 * It can parse CERTIFICATE sections as well as CRL ones.
1196 * Returns 0 in case of success, 1 otherwise.
William Lallemandd4774d32022-07-29 17:08:02 +02001197 *
1198 * /!\ Warning: If there was an error the X509_STORE could have been modified so it's
1199 * better to not use it after a return 1.
Remi Tricot-Le Breton383fb142021-02-22 18:26:14 +01001200 */
William Lallemandd4774d32022-07-29 17:08:02 +02001201int ssl_store_load_ca_from_buf(struct cafile_entry *ca_e, char *cert_buf, int append)
Remi Tricot-Le Breton383fb142021-02-22 18:26:14 +01001202{
William Lallemandd4774d32022-07-29 17:08:02 +02001203 BIO *bio = NULL;
1204 STACK_OF(X509_INFO) *infos;
1205 X509_INFO *info;
1206 int i;
1207 int retval = 1;
1208 int retcert = 0;
Remi Tricot-Le Breton383fb142021-02-22 18:26:14 +01001209
1210 if (!ca_e)
1211 return 1;
1212
William Lallemandd4774d32022-07-29 17:08:02 +02001213 if (!append) {
1214 X509_STORE_free(ca_e->ca_store);
1215 ca_e->ca_store = NULL;
1216 }
1217
1218 if (!ca_e->ca_store)
Remi Tricot-Le Breton383fb142021-02-22 18:26:14 +01001219 ca_e->ca_store = X509_STORE_new();
Remi Tricot-Le Breton383fb142021-02-22 18:26:14 +01001220
William Lallemandd4774d32022-07-29 17:08:02 +02001221 if (!ca_e->ca_store)
1222 goto end;
Remi Tricot-Le Breton383fb142021-02-22 18:26:14 +01001223
William Lallemandd4774d32022-07-29 17:08:02 +02001224 bio = BIO_new_mem_buf(cert_buf, strlen(cert_buf));
1225 if (!bio)
1226 goto end;
1227
1228 infos = PEM_X509_INFO_read_bio(bio, NULL, NULL, NULL);
1229 if (!infos)
1230 goto end;
1231
1232 for (i = 0; i < sk_X509_INFO_num(infos) && !retcert; i++) {
1233 info = sk_X509_INFO_value(infos, i);
1234
1235 /* X509_STORE_add_cert and X509_STORE_add_crl return 1 on success */
1236 if (info->x509)
1237 retcert = !X509_STORE_add_cert(ca_e->ca_store, info->x509);
1238 if (!retcert && info->crl)
1239 retcert = !X509_STORE_add_crl(ca_e->ca_store, info->crl);
Remi Tricot-Le Breton383fb142021-02-22 18:26:14 +01001240 }
1241
William Lallemandd4774d32022-07-29 17:08:02 +02001242 /* return an error if we didn't compute all the X509_INFO or if there was none
1243 * set to 0 if everything was right */
1244 if (!(retcert || (i != sk_X509_INFO_num(infos)) || (sk_X509_INFO_num(infos) == 0)))
1245 retval = 0;
1246
1247 /* Cleanup */
1248 sk_X509_INFO_pop_free(infos, X509_INFO_free);
1249
1250end:
1251 BIO_free(bio);
1252
Remi Tricot-Le Breton383fb142021-02-22 18:26:14 +01001253 return retval;
1254}
1255
William Lallemand0f17ab22022-07-19 18:03:16 +02001256/*
1257 * Try to load a ca-file from disk into the ca-file cache.
William Lallemand0a2d6322022-11-24 19:14:19 +01001258 * <shuterror> allows you to to stop emitting the errors.
William Lallemand0f17ab22022-07-19 18:03:16 +02001259 * Return 0 upon error
1260 */
William Lallemand0a2d6322022-11-24 19:14:19 +01001261int __ssl_store_load_locations_file(char *path, int create_if_none, enum cafile_type type, int shuterror)
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001262{
1263 X509_STORE *store = ssl_store_get0_locations_file(path);
1264
1265 /* If this function is called by the CLI, we should not call the
1266 * X509_STORE_load_locations function because it performs forbidden disk
1267 * accesses. */
1268 if (!store && create_if_none) {
William Lallemand87fd9942022-04-01 20:12:03 +02001269 STACK_OF(X509_OBJECT) *objs;
1270 int cert_count = 0;
1271 struct stat buf;
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001272 struct cafile_entry *ca_e;
William Lallemandc6b17632022-04-01 23:39:37 +02001273 const char *file = NULL;
1274 const char *dir = NULL;
William Lallemand0f17ab22022-07-19 18:03:16 +02001275 unsigned long e;
William Lallemand87fd9942022-04-01 20:12:03 +02001276
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001277 store = X509_STORE_new();
William Lallemand0f17ab22022-07-19 18:03:16 +02001278 if (!store) {
William Lallemand0a2d6322022-11-24 19:14:19 +01001279 if (!shuterror)
1280 ha_alert("Cannot allocate memory!\n");
William Lallemand0f17ab22022-07-19 18:03:16 +02001281 goto err;
1282 }
William Lallemand87fd9942022-04-01 20:12:03 +02001283
William Lallemandc6b17632022-04-01 23:39:37 +02001284 if (strcmp(path, "@system-ca") == 0) {
1285 dir = X509_get_default_cert_dir();
William Lallemand0f17ab22022-07-19 18:03:16 +02001286 if (!dir) {
William Lallemand0a2d6322022-11-24 19:14:19 +01001287 if (!shuterror)
1288 ha_alert("Couldn't get the system CA directory from X509_get_default_cert_dir().\n");
William Lallemand0f17ab22022-07-19 18:03:16 +02001289 goto err;
1290 }
William Lallemand87fd9942022-04-01 20:12:03 +02001291
William Lallemandc6b17632022-04-01 23:39:37 +02001292 } else {
1293
William Lallemand0f17ab22022-07-19 18:03:16 +02001294 if (stat(path, &buf) == -1) {
William Lallemand0a2d6322022-11-24 19:14:19 +01001295 if (!shuterror)
1296 ha_alert("Couldn't open the ca-file '%s' (%s).\n", path, strerror(errno));
William Lallemandc6b17632022-04-01 23:39:37 +02001297 goto err;
William Lallemand0f17ab22022-07-19 18:03:16 +02001298 }
William Lallemandc6b17632022-04-01 23:39:37 +02001299
1300 if (S_ISDIR(buf.st_mode))
1301 dir = path;
1302 else
1303 file = path;
1304 }
William Lallemand87fd9942022-04-01 20:12:03 +02001305
1306 if (file) {
1307 if (!X509_STORE_load_locations(store, file, NULL)) {
William Lallemand0f17ab22022-07-19 18:03:16 +02001308 e = ERR_get_error();
William Lallemand0a2d6322022-11-24 19:14:19 +01001309 if (!shuterror)
1310 ha_alert("Couldn't open the ca-file '%s' (%s).\n", path, ERR_reason_error_string(e));
William Lallemand87fd9942022-04-01 20:12:03 +02001311 goto err;
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001312 }
William Lallemand80296b42022-04-05 10:19:30 +02001313 } else if (dir) {
William Lallemand87fd9942022-04-01 20:12:03 +02001314 int n, i;
1315 struct dirent **de_list;
1316
1317 n = scandir(dir, &de_list, 0, alphasort);
1318 if (n < 0)
1319 goto err;
1320
1321 for (i= 0; i < n; i++) {
1322 char *end;
1323 struct dirent *de = de_list[i];
1324 BIO *in = NULL;
1325 X509 *ca = NULL;;
1326
William Lallemand43482322022-07-18 18:42:52 +02001327 ERR_clear_error();
1328
William Lallemand87fd9942022-04-01 20:12:03 +02001329 /* we try to load the files that would have
1330 * been loaded in an hashed directory loaded by
1331 * X509_LOOKUP_hash_dir, so according to "man 1
1332 * c_rehash", we should load ".pem", ".crt",
William Lallemande4b93eb2022-05-09 09:29:00 +02001333 * ".cer", or ".crl". Files starting with a dot
1334 * are ignored.
William Lallemand87fd9942022-04-01 20:12:03 +02001335 */
1336 end = strrchr(de->d_name, '.');
William Lallemande4b93eb2022-05-09 09:29:00 +02001337 if (!end || de->d_name[0] == '.' ||
1338 (strcmp(end, ".pem") != 0 &&
1339 strcmp(end, ".crt") != 0 &&
1340 strcmp(end, ".cer") != 0 &&
1341 strcmp(end, ".crl") != 0)) {
William Lallemand87fd9942022-04-01 20:12:03 +02001342 free(de);
1343 continue;
1344 }
1345 in = BIO_new(BIO_s_file());
1346 if (in == NULL)
1347 goto scandir_err;
1348
William Lallemandc6b17632022-04-01 23:39:37 +02001349 chunk_printf(&trash, "%s/%s", dir, de->d_name);
William Lallemand87fd9942022-04-01 20:12:03 +02001350
1351 if (BIO_read_filename(in, trash.area) == 0)
1352 goto scandir_err;
1353
1354 if (PEM_read_bio_X509_AUX(in, &ca, NULL, NULL) == NULL)
1355 goto scandir_err;
1356
William Lallemand43482322022-07-18 18:42:52 +02001357 if (X509_STORE_add_cert(store, ca) == 0) {
1358 /* only exits on error if the error is not about duplicate certificates */
1359 if (!(ERR_GET_REASON(ERR_get_error()) == X509_R_CERT_ALREADY_IN_HASH_TABLE)) {
1360 goto scandir_err;
1361 }
1362 }
William Lallemand87fd9942022-04-01 20:12:03 +02001363
William Lallemand4cfbf3c2022-04-26 15:57:33 +02001364 X509_free(ca);
William Lallemand87fd9942022-04-01 20:12:03 +02001365 BIO_free(in);
1366 free(de);
1367 continue;
1368
1369scandir_err:
William Lallemand0f17ab22022-07-19 18:03:16 +02001370 e = ERR_get_error();
William Lallemand4cfbf3c2022-04-26 15:57:33 +02001371 X509_free(ca);
William Lallemand87fd9942022-04-01 20:12:03 +02001372 BIO_free(in);
1373 free(de);
William Lallemand0f17ab22022-07-19 18:03:16 +02001374 /* warn if it can load one of the files, but don't abort */
William Lallemand0a2d6322022-11-24 19:14:19 +01001375 if (!shuterror)
1376 ha_warning("ca-file: '%s' couldn't load '%s' (%s)\n", path, trash.area, ERR_reason_error_string(e));
William Lallemand87fd9942022-04-01 20:12:03 +02001377
1378 }
1379 free(de_list);
William Lallemand80296b42022-04-05 10:19:30 +02001380 } else {
William Lallemand0a2d6322022-11-24 19:14:19 +01001381 if (!shuterror)
1382 ha_alert("ca-file: couldn't load '%s'\n", path);
William Lallemand80296b42022-04-05 10:19:30 +02001383 goto err;
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001384 }
William Lallemand87fd9942022-04-01 20:12:03 +02001385
1386 objs = X509_STORE_get0_objects(store);
1387 cert_count = sk_X509_OBJECT_num(objs);
William Lallemand0f17ab22022-07-19 18:03:16 +02001388 if (cert_count == 0) {
William Lallemand0a2d6322022-11-24 19:14:19 +01001389 if (!shuterror)
1390 ha_warning("ca-file: 0 CA were loaded from '%s'\n", path);
William Lallemand0f17ab22022-07-19 18:03:16 +02001391 }
William Lallemand87fd9942022-04-01 20:12:03 +02001392 ca_e = ssl_store_create_cafile_entry(path, store, type);
William Lallemand0f17ab22022-07-19 18:03:16 +02001393 if (!ca_e) {
William Lallemand0a2d6322022-11-24 19:14:19 +01001394 if (!shuterror)
1395 ha_alert("Cannot allocate memory!\n");
William Lallemand87fd9942022-04-01 20:12:03 +02001396 goto err;
William Lallemand0f17ab22022-07-19 18:03:16 +02001397 }
William Lallemand87fd9942022-04-01 20:12:03 +02001398 ebst_insert(&cafile_tree, &ca_e->node);
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001399 }
1400 return (store != NULL);
William Lallemand87fd9942022-04-01 20:12:03 +02001401
1402err:
1403 X509_STORE_free(store);
1404 store = NULL;
1405 return 0;
1406
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001407}
1408
William Lallemand0a2d6322022-11-24 19:14:19 +01001409int ssl_store_load_locations_file(char *path, int create_if_none, enum cafile_type type)
1410{
1411 return __ssl_store_load_locations_file(path, create_if_none, type, 0);
1412}
Remi Tricot-Le Bretonaf8820a2021-04-13 10:10:37 +02001413
William Lallemandda8584c2020-05-14 10:14:37 +02001414/*************************** CLI commands ***********************/
1415
1416/* Type of SSL payloads that can be updated over the CLI */
1417
William Lallemandff8bf982022-03-29 10:44:23 +02001418struct cert_exts cert_exts[] = {
1419 { "", CERT_TYPE_PEM, &ssl_sock_load_pem_into_ckch }, /* default mode, no extensions */
1420 { "key", CERT_TYPE_KEY, &ssl_sock_load_key_into_ckch },
William Lallemandda8584c2020-05-14 10:14:37 +02001421#if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) || defined OPENSSL_IS_BORINGSSL)
William Lallemandff8bf982022-03-29 10:44:23 +02001422 { "ocsp", CERT_TYPE_OCSP, &ssl_sock_load_ocsp_response_from_file },
William Lallemandda8584c2020-05-14 10:14:37 +02001423#endif
Ilya Shipitsinc47d6762021-02-13 11:45:33 +05001424#ifdef HAVE_SSL_SCTL
William Lallemandff8bf982022-03-29 10:44:23 +02001425 { "sctl", CERT_TYPE_SCTL, &ssl_sock_load_sctl_from_file },
William Lallemandda8584c2020-05-14 10:14:37 +02001426#endif
William Lallemandff8bf982022-03-29 10:44:23 +02001427 { "issuer", CERT_TYPE_ISSUER, &ssl_sock_load_issuer_file_into_ckch },
1428 { NULL, CERT_TYPE_MAX, NULL },
William Lallemandda8584c2020-05-14 10:14:37 +02001429};
1430
1431
1432/* release function of the `show ssl cert' command */
1433static void cli_release_show_cert(struct appctx *appctx)
1434{
1435 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1436}
1437
Willy Tarreau4fd9b4d2022-05-04 16:11:50 +02001438/* IO handler of "show ssl cert <filename>".
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001439 * It makes use of a show_cert_ctx context, and ckchs_transaction in read-only.
Willy Tarreau4fd9b4d2022-05-04 16:11:50 +02001440 */
William Lallemandda8584c2020-05-14 10:14:37 +02001441static int cli_io_handler_show_cert(struct appctx *appctx)
1442{
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001443 struct show_cert_ctx *ctx = appctx->svcctx;
William Lallemandda8584c2020-05-14 10:14:37 +02001444 struct buffer *trash = alloc_trash_chunk();
1445 struct ebmb_node *node;
Christopher Fauletd1d2e4d2022-06-03 16:24:02 +02001446 struct ckch_store *ckchs = NULL;
William Lallemandda8584c2020-05-14 10:14:37 +02001447
1448 if (trash == NULL)
1449 return 1;
1450
Christopher Faulet3e94f5d2022-06-03 10:46:40 +02001451 if (!ctx->old_ckchs && ckchs_transaction.old_ckchs) {
1452 ckchs = ckchs_transaction.old_ckchs;
1453 chunk_appendf(trash, "# transaction\n");
1454 chunk_appendf(trash, "*%s\n", ckchs->path);
1455 if (applet_putchk(appctx, trash) == -1)
1456 goto yield;
1457 ctx->old_ckchs = ckchs_transaction.old_ckchs;
William Lallemandda8584c2020-05-14 10:14:37 +02001458 }
1459
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001460 if (!ctx->cur_ckchs) {
William Lallemandda8584c2020-05-14 10:14:37 +02001461 chunk_appendf(trash, "# filename\n");
1462 node = ebmb_first(&ckchs_tree);
1463 } else {
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001464 node = &ctx->cur_ckchs->node;
William Lallemandda8584c2020-05-14 10:14:37 +02001465 }
1466 while (node) {
1467 ckchs = ebmb_entry(node, struct ckch_store, node);
William Lallemand5685ccf2020-09-16 16:12:25 +02001468 chunk_appendf(trash, "%s\n", ckchs->path);
William Lallemandda8584c2020-05-14 10:14:37 +02001469
1470 node = ebmb_next(node);
Willy Tarreaud0a06d52022-05-18 15:07:19 +02001471 if (applet_putchk(appctx, trash) == -1)
William Lallemandda8584c2020-05-14 10:14:37 +02001472 goto yield;
William Lallemandda8584c2020-05-14 10:14:37 +02001473 }
1474
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001475 ctx->cur_ckchs = NULL;
William Lallemandda8584c2020-05-14 10:14:37 +02001476 free_trash_chunk(trash);
1477 return 1;
1478yield:
1479
1480 free_trash_chunk(trash);
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001481 ctx->cur_ckchs = ckchs;
William Lallemandda8584c2020-05-14 10:14:37 +02001482 return 0; /* should come back */
1483}
1484
1485/*
1486 * Extract and format the DNS SAN extensions and copy result into a chuink
1487 * Return 0;
1488 */
1489#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
1490static int ssl_sock_get_san_oneline(X509 *cert, struct buffer *out)
1491{
1492 int i;
1493 char *str;
1494 STACK_OF(GENERAL_NAME) *names = NULL;
1495
1496 names = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
1497 if (names) {
1498 for (i = 0; i < sk_GENERAL_NAME_num(names); i++) {
1499 GENERAL_NAME *name = sk_GENERAL_NAME_value(names, i);
1500 if (i > 0)
1501 chunk_appendf(out, ", ");
1502 if (name->type == GEN_DNS) {
1503 if (ASN1_STRING_to_UTF8((unsigned char **)&str, name->d.dNSName) >= 0) {
1504 chunk_appendf(out, "DNS:%s", str);
1505 OPENSSL_free(str);
1506 }
1507 }
1508 }
1509 sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free);
1510 }
1511 return 0;
1512}
1513#endif
1514
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001515/*
1516 * Build the ckch_inst_link that will be chained in the CA file entry and the
1517 * corresponding ckch_inst_link_ref that will be chained in the ckch instance.
1518 * Return 0 in case of success.
1519 */
1520static int do_chain_inst_and_cafile(struct cafile_entry *cafile_entry, struct ckch_inst *ckch_inst)
1521{
1522 struct ckch_inst_link *new_link;
1523 if (!LIST_ISEMPTY(&cafile_entry->ckch_inst_link)) {
1524 struct ckch_inst_link *link = LIST_ELEM(cafile_entry->ckch_inst_link.n,
1525 typeof(link), list);
1526 /* Do not add multiple references to the same
1527 * instance in a cafile_entry */
1528 if (link->ckch_inst == ckch_inst) {
1529 return 1;
1530 }
1531 }
1532
1533 new_link = calloc(1, sizeof(*new_link));
1534 if (new_link) {
1535 struct ckch_inst_link_ref *new_link_ref = calloc(1, sizeof(*new_link_ref));
1536 if (!new_link_ref) {
1537 free(new_link);
1538 return 1;
1539 }
1540
1541 new_link->ckch_inst = ckch_inst;
1542 new_link_ref->link = new_link;
1543 LIST_INIT(&new_link->list);
1544 LIST_INIT(&new_link_ref->list);
1545
1546 LIST_APPEND(&cafile_entry->ckch_inst_link, &new_link->list);
1547 LIST_APPEND(&ckch_inst->cafile_link_refs, &new_link_ref->list);
1548 }
1549
1550 return 0;
1551}
1552
1553
1554/*
1555 * Link a CA file tree entry to the ckch instance that uses it.
1556 * To determine if and which CA file tree entries need to be linked to the
1557 * instance, we follow the same logic performed in ssl_sock_prepare_ctx when
1558 * processing the verify option.
1559 * This function works for a frontend as well as for a backend, depending on the
1560 * configuration parameters given (bind_conf or server).
1561 */
1562void ckch_inst_add_cafile_link(struct ckch_inst *ckch_inst, struct bind_conf *bind_conf,
1563 struct ssl_bind_conf *ssl_conf, const struct server *srv)
1564{
1565 int verify = SSL_VERIFY_NONE;
1566
1567 if (srv) {
1568
1569 if (global.ssl_server_verify == SSL_SERVER_VERIFY_REQUIRED)
1570 verify = SSL_VERIFY_PEER;
1571 switch (srv->ssl_ctx.verify) {
1572 case SSL_SOCK_VERIFY_NONE:
1573 verify = SSL_VERIFY_NONE;
1574 break;
1575 case SSL_SOCK_VERIFY_REQUIRED:
1576 verify = SSL_VERIFY_PEER;
1577 break;
1578 }
1579 }
1580 else {
1581 switch ((ssl_conf && ssl_conf->verify) ? ssl_conf->verify : bind_conf->ssl_conf.verify) {
1582 case SSL_SOCK_VERIFY_NONE:
1583 verify = SSL_VERIFY_NONE;
1584 break;
1585 case SSL_SOCK_VERIFY_OPTIONAL:
1586 verify = SSL_VERIFY_PEER;
1587 break;
1588 case SSL_SOCK_VERIFY_REQUIRED:
1589 verify = SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
1590 break;
1591 }
1592 }
1593
1594 if (verify & SSL_VERIFY_PEER) {
1595 struct cafile_entry *ca_file_entry = NULL;
1596 struct cafile_entry *ca_verify_file_entry = NULL;
Remi Tricot-Le Bretonf81c70c2021-04-20 16:54:21 +02001597 struct cafile_entry *crl_file_entry = NULL;
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001598 if (srv) {
1599 if (srv->ssl_ctx.ca_file) {
1600 ca_file_entry = ssl_store_get_cafile_entry(srv->ssl_ctx.ca_file, 0);
1601
1602 }
Remi Tricot-Le Bretonf81c70c2021-04-20 16:54:21 +02001603 if (srv->ssl_ctx.crl_file) {
1604 crl_file_entry = ssl_store_get_cafile_entry(srv->ssl_ctx.crl_file, 0);
1605 }
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001606 }
1607 else {
1608 char *ca_file = (ssl_conf && ssl_conf->ca_file) ? ssl_conf->ca_file : bind_conf->ssl_conf.ca_file;
1609 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 +02001610 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 +01001611
1612 if (ca_file)
1613 ca_file_entry = ssl_store_get_cafile_entry(ca_file, 0);
1614 if (ca_verify_file)
1615 ca_verify_file_entry = ssl_store_get_cafile_entry(ca_verify_file, 0);
Remi Tricot-Le Bretonf81c70c2021-04-20 16:54:21 +02001616 if (crl_file)
1617 crl_file_entry = ssl_store_get_cafile_entry(crl_file, 0);
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001618 }
1619
1620 if (ca_file_entry) {
1621 /* If we have a ckch instance that is not already in the
1622 * cafile_entry's list, add it to it. */
1623 if (do_chain_inst_and_cafile(ca_file_entry, ckch_inst))
1624 return;
1625
1626 }
1627 if (ca_verify_file_entry && (ca_file_entry != ca_verify_file_entry)) {
1628 /* If we have a ckch instance that is not already in the
1629 * cafile_entry's list, add it to it. */
1630 if (do_chain_inst_and_cafile(ca_verify_file_entry, ckch_inst))
1631 return;
1632 }
Remi Tricot-Le Bretonf81c70c2021-04-20 16:54:21 +02001633 if (crl_file_entry) {
1634 /* If we have a ckch instance that is not already in the
1635 * cafile_entry's list, add it to it. */
1636 if (do_chain_inst_and_cafile(crl_file_entry, ckch_inst))
1637 return;
1638 }
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001639 }
1640}
1641
William Lallemandda8584c2020-05-14 10:14:37 +02001642
1643
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001644static int show_cert_detail(X509 *cert, STACK_OF(X509) *chain, struct buffer *out)
William Lallemandda8584c2020-05-14 10:14:37 +02001645{
William Lallemandda8584c2020-05-14 10:14:37 +02001646 BIO *bio = NULL;
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001647 struct buffer *tmp = alloc_trash_chunk();
William Lallemandda8584c2020-05-14 10:14:37 +02001648 int i;
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001649 int write = -1;
1650 unsigned int len = 0;
1651 X509_NAME *name = NULL;
William Lallemandda8584c2020-05-14 10:14:37 +02001652
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001653 if (!tmp)
1654 return -1;
William Lallemandda8584c2020-05-14 10:14:37 +02001655
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001656 if (!cert)
William Lallemand5685ccf2020-09-16 16:12:25 +02001657 goto end;
William Lallemandda8584c2020-05-14 10:14:37 +02001658
William Lallemand5685ccf2020-09-16 16:12:25 +02001659 if (chain == NULL) {
1660 struct issuer_chain *issuer;
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001661 issuer = ssl_get0_issuer_chain(cert);
William Lallemand5685ccf2020-09-16 16:12:25 +02001662 if (issuer) {
1663 chain = issuer->chain;
1664 chunk_appendf(out, "Chain Filename: ");
1665 chunk_appendf(out, "%s\n", issuer->path);
William Lallemandda8584c2020-05-14 10:14:37 +02001666 }
William Lallemand5685ccf2020-09-16 16:12:25 +02001667 }
1668 chunk_appendf(out, "Serial: ");
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001669 if (ssl_sock_get_serial(cert, tmp) == -1)
William Lallemand5685ccf2020-09-16 16:12:25 +02001670 goto end;
1671 dump_binary(out, tmp->area, tmp->data);
1672 chunk_appendf(out, "\n");
William Lallemandda8584c2020-05-14 10:14:37 +02001673
William Lallemand5685ccf2020-09-16 16:12:25 +02001674 chunk_appendf(out, "notBefore: ");
1675 chunk_reset(tmp);
1676 if ((bio = BIO_new(BIO_s_mem())) == NULL)
1677 goto end;
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001678 if (ASN1_TIME_print(bio, X509_getm_notBefore(cert)) == 0)
William Lallemand5685ccf2020-09-16 16:12:25 +02001679 goto end;
1680 write = BIO_read(bio, tmp->area, tmp->size-1);
1681 tmp->area[write] = '\0';
1682 BIO_free(bio);
1683 bio = NULL;
1684 chunk_appendf(out, "%s\n", tmp->area);
William Lallemandda8584c2020-05-14 10:14:37 +02001685
William Lallemand5685ccf2020-09-16 16:12:25 +02001686 chunk_appendf(out, "notAfter: ");
1687 chunk_reset(tmp);
1688 if ((bio = BIO_new(BIO_s_mem())) == NULL)
1689 goto end;
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001690 if (ASN1_TIME_print(bio, X509_getm_notAfter(cert)) == 0)
William Lallemand5685ccf2020-09-16 16:12:25 +02001691 goto end;
1692 if ((write = BIO_read(bio, tmp->area, tmp->size-1)) <= 0)
1693 goto end;
1694 tmp->area[write] = '\0';
1695 BIO_free(bio);
1696 bio = NULL;
1697 chunk_appendf(out, "%s\n", tmp->area);
William Lallemandda8584c2020-05-14 10:14:37 +02001698
1699#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
William Lallemand5685ccf2020-09-16 16:12:25 +02001700 chunk_appendf(out, "Subject Alternative Name: ");
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001701 if (ssl_sock_get_san_oneline(cert, out) == -1)
William Lallemand5685ccf2020-09-16 16:12:25 +02001702 goto end;
1703 *(out->area + out->data) = '\0';
1704 chunk_appendf(out, "\n");
William Lallemandda8584c2020-05-14 10:14:37 +02001705#endif
William Lallemand5685ccf2020-09-16 16:12:25 +02001706 chunk_reset(tmp);
1707 chunk_appendf(out, "Algorithm: ");
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001708 if (cert_get_pkey_algo(cert, tmp) == 0)
William Lallemand5685ccf2020-09-16 16:12:25 +02001709 goto end;
1710 chunk_appendf(out, "%s\n", tmp->area);
William Lallemandda8584c2020-05-14 10:14:37 +02001711
William Lallemand5685ccf2020-09-16 16:12:25 +02001712 chunk_reset(tmp);
1713 chunk_appendf(out, "SHA1 FingerPrint: ");
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001714 if (X509_digest(cert, EVP_sha1(), (unsigned char *) tmp->area, &len) == 0)
William Lallemand5685ccf2020-09-16 16:12:25 +02001715 goto end;
1716 tmp->data = len;
1717 dump_binary(out, tmp->area, tmp->data);
1718 chunk_appendf(out, "\n");
William Lallemandda8584c2020-05-14 10:14:37 +02001719
William Lallemand5685ccf2020-09-16 16:12:25 +02001720 chunk_appendf(out, "Subject: ");
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001721 if ((name = X509_get_subject_name(cert)) == NULL)
William Lallemand5685ccf2020-09-16 16:12:25 +02001722 goto end;
1723 if ((ssl_sock_get_dn_oneline(name, tmp)) == -1)
1724 goto end;
1725 *(tmp->area + tmp->data) = '\0';
1726 chunk_appendf(out, "%s\n", tmp->area);
1727
1728 chunk_appendf(out, "Issuer: ");
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001729 if ((name = X509_get_issuer_name(cert)) == NULL)
William Lallemand5685ccf2020-09-16 16:12:25 +02001730 goto end;
1731 if ((ssl_sock_get_dn_oneline(name, tmp)) == -1)
1732 goto end;
1733 *(tmp->area + tmp->data) = '\0';
1734 chunk_appendf(out, "%s\n", tmp->area);
1735
1736 /* Displays subject of each certificate in the chain */
1737 for (i = 0; i < sk_X509_num(chain); i++) {
1738 X509 *ca = sk_X509_value(chain, i);
1739
1740 chunk_appendf(out, "Chain Subject: ");
1741 if ((name = X509_get_subject_name(ca)) == NULL)
William Lallemandda8584c2020-05-14 10:14:37 +02001742 goto end;
1743 if ((ssl_sock_get_dn_oneline(name, tmp)) == -1)
1744 goto end;
1745 *(tmp->area + tmp->data) = '\0';
1746 chunk_appendf(out, "%s\n", tmp->area);
1747
William Lallemand5685ccf2020-09-16 16:12:25 +02001748 chunk_appendf(out, "Chain Issuer: ");
1749 if ((name = X509_get_issuer_name(ca)) == NULL)
William Lallemandda8584c2020-05-14 10:14:37 +02001750 goto end;
1751 if ((ssl_sock_get_dn_oneline(name, tmp)) == -1)
1752 goto end;
1753 *(tmp->area + tmp->data) = '\0';
1754 chunk_appendf(out, "%s\n", tmp->area);
William Lallemandda8584c2020-05-14 10:14:37 +02001755 }
1756
1757end:
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001758 if (bio)
1759 BIO_free(bio);
1760 free_trash_chunk(tmp);
1761
1762 return 0;
1763}
1764
Remi Tricot-Le Bretonda968f62021-06-10 13:51:14 +02001765/*
1766 * Dump the OCSP certificate key (if it exists) of certificate <ckch> into
1767 * buffer <out>.
1768 * Returns 0 in case of success.
1769 */
1770static int ckch_store_show_ocsp_certid(struct ckch_store *ckch_store, struct buffer *out)
1771{
Remi Tricot-Le Breton3faf0cb2021-06-10 18:10:32 +02001772#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 +02001773 unsigned char key[OCSP_MAX_CERTID_ASN1_LENGTH] = {};
1774 unsigned int key_length = 0;
1775 int i;
1776
Remi Tricot-Le Bretona1b3d8e2024-02-07 16:38:41 +01001777 if (ssl_ocsp_build_response_key(ckch_store->data->ocsp_cid, (unsigned char*)key, &key_length) >= 0) {
Remi Tricot-Le Bretonda968f62021-06-10 13:51:14 +02001778 /* Dump the CERTID info */
1779 chunk_appendf(out, "OCSP Response Key: ");
1780 for (i = 0; i < key_length; ++i) {
1781 chunk_appendf(out, "%02x", key[i]);
1782 }
1783 chunk_appendf(out, "\n");
1784 }
1785#endif
1786
1787 return 0;
1788}
1789
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001790
Willy Tarreau4fd9b4d2022-05-04 16:11:50 +02001791/* IO handler of the details "show ssl cert <filename>".
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001792 * It uses a struct show_cert_ctx and ckchs_transaction in read-only.
Willy Tarreau4fd9b4d2022-05-04 16:11:50 +02001793 */
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001794static int cli_io_handler_show_cert_detail(struct appctx *appctx)
1795{
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001796 struct show_cert_ctx *ctx = appctx->svcctx;
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001797 struct ckch_store *ckchs = ctx->cur_ckchs;
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001798 struct buffer *out = alloc_trash_chunk();
1799 int retval = 0;
1800
1801 if (!out)
1802 goto end_no_putchk;
1803
1804 chunk_appendf(out, "Filename: ");
1805 if (ckchs == ckchs_transaction.new_ckchs)
1806 chunk_appendf(out, "*");
1807 chunk_appendf(out, "%s\n", ckchs->path);
1808
1809 chunk_appendf(out, "Status: ");
William Lallemand52ddd992022-11-22 11:51:53 +01001810 if (ckchs->data->cert == NULL)
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001811 chunk_appendf(out, "Empty\n");
1812 else if (LIST_ISEMPTY(&ckchs->ckch_inst))
1813 chunk_appendf(out, "Unused\n");
1814 else
1815 chunk_appendf(out, "Used\n");
1816
William Lallemand52ddd992022-11-22 11:51:53 +01001817 retval = show_cert_detail(ckchs->data->cert, ckchs->data->chain, out);
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001818 if (retval < 0)
1819 goto end_no_putchk;
1820 else if (retval)
1821 goto end;
1822
Remi Tricot-Le Bretonda968f62021-06-10 13:51:14 +02001823 ckch_store_show_ocsp_certid(ckchs, out);
1824
Remi Tricot-Le Breton523f0e42021-03-16 10:11:44 +01001825end:
Willy Tarreaud0a06d52022-05-18 15:07:19 +02001826 if (applet_putchk(appctx, out) == -1)
William Lallemandda8584c2020-05-14 10:14:37 +02001827 goto yield;
William Lallemandda8584c2020-05-14 10:14:37 +02001828
1829end_no_putchk:
William Lallemandda8584c2020-05-14 10:14:37 +02001830 free_trash_chunk(out);
1831 return 1;
1832yield:
William Lallemandda8584c2020-05-14 10:14:37 +02001833 free_trash_chunk(out);
1834 return 0; /* should come back */
1835}
1836
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001837
Willy Tarreau4fd9b4d2022-05-04 16:11:50 +02001838/* IO handler of the details "show ssl cert <filename.ocsp>".
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001839 * It uses a show_cert_ctx.
Willy Tarreau4fd9b4d2022-05-04 16:11:50 +02001840 */
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001841static int cli_io_handler_show_cert_ocsp_detail(struct appctx *appctx)
1842{
Remi Tricot-Le Breton3faf0cb2021-06-10 18:10:32 +02001843#if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) && !defined OPENSSL_IS_BORINGSSL)
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001844 struct show_cert_ctx *ctx = appctx->svcctx;
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001845 struct ckch_store *ckchs = ctx->cur_ckchs;
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001846 struct buffer *out = alloc_trash_chunk();
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001847 int from_transaction = ctx->transaction;
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001848
1849 if (!out)
1850 goto end_no_putchk;
1851
1852 /* If we try to display an ongoing transaction's OCSP response, we
1853 * need to dump the ckch's ocsp_response buffer directly.
1854 * Otherwise, we must rebuild the certificate's certid in order to
1855 * look for the current OCSP response in the tree. */
William Lallemand52ddd992022-11-22 11:51:53 +01001856 if (from_transaction && ckchs->data->ocsp_response) {
1857 if (ssl_ocsp_response_print(ckchs->data->ocsp_response, out))
Remi Tricot-Le Bretona9a591a2022-02-16 14:42:22 +01001858 goto end_no_putchk;
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001859 }
1860 else {
1861 unsigned char key[OCSP_MAX_CERTID_ASN1_LENGTH] = {};
1862 unsigned int key_length = 0;
1863
Remi Tricot-Le Bretona1b3d8e2024-02-07 16:38:41 +01001864 if (ssl_ocsp_build_response_key(ckchs->data->ocsp_cid, (unsigned char*)key, &key_length) < 0)
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001865 goto end_no_putchk;
1866
Remi Tricot-Le Bretona9a591a2022-02-16 14:42:22 +01001867 if (ssl_get_ocspresponse_detail(key, out))
1868 goto end_no_putchk;
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001869 }
1870
Willy Tarreaud0a06d52022-05-18 15:07:19 +02001871 if (applet_putchk(appctx, out) == -1)
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001872 goto yield;
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001873
1874end_no_putchk:
1875 free_trash_chunk(out);
1876 return 1;
1877yield:
1878 free_trash_chunk(out);
1879 return 0; /* should come back */
1880#else
1881 return cli_err(appctx, "HAProxy was compiled against a version of OpenSSL that doesn't support OCSP stapling.\n");
1882#endif
1883}
1884
William Lallemandda8584c2020-05-14 10:14:37 +02001885/* parsing function for 'show ssl cert [certfile]' */
1886static int cli_parse_show_cert(char **args, char *payload, struct appctx *appctx, void *private)
1887{
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001888 struct show_cert_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
William Lallemandda8584c2020-05-14 10:14:37 +02001889 struct ckch_store *ckchs;
1890
1891 if (!cli_has_level(appctx, ACCESS_LVL_OPER))
1892 return cli_err(appctx, "Can't allocate memory!\n");
1893
1894 /* The operations on the CKCH architecture are locked so we can
1895 * manipulate ckch_store and ckch_inst */
1896 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
1897 return cli_err(appctx, "Can't show!\nOperations on certificates are currently locked!\n");
1898
1899 /* check if there is a certificate to lookup */
1900 if (*args[3]) {
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001901 int show_ocsp_detail = 0;
1902 int from_transaction = 0;
1903 char *end;
1904
1905 /* We manage the special case "certname.ocsp" through which we
1906 * can show the details of an OCSP response. */
1907 end = strrchr(args[3], '.');
1908 if (end && strcmp(end+1, "ocsp") == 0) {
1909 *end = '\0';
1910 show_ocsp_detail = 1;
1911 }
1912
William Lallemandda8584c2020-05-14 10:14:37 +02001913 if (*args[3] == '*') {
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001914 from_transaction = 1;
William Lallemandda8584c2020-05-14 10:14:37 +02001915 if (!ckchs_transaction.new_ckchs)
1916 goto error;
1917
1918 ckchs = ckchs_transaction.new_ckchs;
1919
Tim Duesterhuse5ff1412021-01-02 22:31:53 +01001920 if (strcmp(args[3] + 1, ckchs->path) != 0)
William Lallemandda8584c2020-05-14 10:14:37 +02001921 goto error;
1922
1923 } else {
1924 if ((ckchs = ckchs_lookup(args[3])) == NULL)
1925 goto error;
1926
1927 }
1928
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001929 ctx->cur_ckchs = ckchs;
William Lallemandda8584c2020-05-14 10:14:37 +02001930 /* use the IO handler that shows details */
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001931 if (show_ocsp_detail) {
Willy Tarreau96c9a6c2022-05-04 19:51:37 +02001932 ctx->transaction = from_transaction;
Remi Tricot-Le Breton6056e612021-06-10 13:51:15 +02001933 appctx->io_handler = cli_io_handler_show_cert_ocsp_detail;
1934 }
1935 else
1936 appctx->io_handler = cli_io_handler_show_cert_detail;
William Lallemandda8584c2020-05-14 10:14:37 +02001937 }
1938
1939 return 0;
1940
1941error:
1942 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1943 return cli_err(appctx, "Can't display the certificate: Not found or the certificate is a bundle!\n");
1944}
1945
1946/* release function of the `set ssl cert' command, free things and unlock the spinlock */
1947static void cli_release_commit_cert(struct appctx *appctx)
1948{
Willy Tarreaua645b6a2022-05-04 19:58:00 +02001949 struct commit_cert_ctx *ctx = appctx->svcctx;
William Lallemandda8584c2020-05-14 10:14:37 +02001950
1951 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
Christopher Faulet9d56e242022-05-31 16:37:01 +02001952 /* free every new sni_ctx and the new store, which are not in the trees so no spinlock there */
1953 if (ctx->new_ckchs)
1954 ckch_store_free(ctx->new_ckchs);
1955 ha_free(&ctx->err);
William Lallemandda8584c2020-05-14 10:14:37 +02001956}
1957
Remi Tricot-Le Bretonbfadc022021-02-24 12:20:48 +01001958
1959/*
1960 * Rebuild a new instance 'new_inst' based on an old instance 'ckchi' and a
1961 * specific ckch_store.
1962 * Returns 0 in case of success, 1 otherwise.
1963 */
William Lallemande60c7d62022-03-30 11:26:15 +02001964int ckch_inst_rebuild(struct ckch_store *ckch_store, struct ckch_inst *ckchi,
1965 struct ckch_inst **new_inst, char **err)
Remi Tricot-Le Bretonbfadc022021-02-24 12:20:48 +01001966{
1967 int retval = 0;
1968 int errcode = 0;
1969 struct sni_ctx *sc0, *sc0s;
1970 char **sni_filter = NULL;
1971 int fcount = 0;
1972
1973 if (ckchi->crtlist_entry) {
1974 sni_filter = ckchi->crtlist_entry->filters;
1975 fcount = ckchi->crtlist_entry->fcount;
1976 }
1977
1978 if (ckchi->is_server_instance)
1979 errcode |= ckch_inst_new_load_srv_store(ckch_store->path, ckch_store, new_inst, err);
1980 else
1981 errcode |= ckch_inst_new_load_store(ckch_store->path, ckch_store, ckchi->bind_conf, ckchi->ssl_conf, sni_filter, fcount, new_inst, err);
1982
1983 if (errcode & ERR_CODE)
1984 return 1;
1985
1986 /* if the previous ckchi was used as the default */
1987 if (ckchi->is_default)
1988 (*new_inst)->is_default = 1;
1989
1990 (*new_inst)->is_server_instance = ckchi->is_server_instance;
1991 (*new_inst)->server = ckchi->server;
1992 /* Create a new SSL_CTX and link it to the new instance. */
1993 if ((*new_inst)->is_server_instance) {
1994 retval = ssl_sock_prep_srv_ctx_and_inst(ckchi->server, (*new_inst)->ctx, (*new_inst));
1995 if (retval)
1996 return 1;
1997 }
1998
1999 /* create the link to the crtlist_entry */
2000 (*new_inst)->crtlist_entry = ckchi->crtlist_entry;
2001
2002 /* we need to initialize the SSL_CTX generated */
2003 /* this iterate on the newly generated SNIs in the new instance to prepare their SSL_CTX */
2004 list_for_each_entry_safe(sc0, sc0s, &(*new_inst)->sni_ctx, by_ckch_inst) {
2005 if (!sc0->order) { /* we initialized only the first SSL_CTX because it's the same in the other sni_ctx's */
2006 errcode |= ssl_sock_prep_ctx_and_inst(ckchi->bind_conf, ckchi->ssl_conf, sc0->ctx, *new_inst, err);
2007 if (errcode & ERR_CODE)
2008 return 1;
2009 }
2010 }
2011
2012 return 0;
2013}
2014
2015/*
2016 * Load all the new SNIs of a newly built ckch instance in the trees, or replace
2017 * a server's main ckch instance.
2018 */
2019static void __ssl_sock_load_new_ckch_instance(struct ckch_inst *ckchi)
2020{
2021 /* The bind_conf will be null on server ckch_instances. */
2022 if (ckchi->is_server_instance) {
2023 int i;
2024 /* a lock is needed here since we have to free the SSL cache */
2025 HA_RWLOCK_WRLOCK(SSL_SERVER_LOCK, &ckchi->server->ssl_ctx.lock);
2026 /* free the server current SSL_CTX */
2027 SSL_CTX_free(ckchi->server->ssl_ctx.ctx);
2028 /* Actual ssl context update */
2029 SSL_CTX_up_ref(ckchi->ctx);
2030 ckchi->server->ssl_ctx.ctx = ckchi->ctx;
2031 ckchi->server->ssl_ctx.inst = ckchi;
2032
2033 /* flush the session cache of the server */
2034 for (i = 0; i < global.nbthread; i++) {
William Lallemandce990332021-11-23 15:15:09 +01002035 ha_free(&ckchi->server->ssl_ctx.reused_sess[i].sni);
Remi Tricot-Le Bretonbfadc022021-02-24 12:20:48 +01002036 ha_free(&ckchi->server->ssl_ctx.reused_sess[i].ptr);
2037 }
2038 HA_RWLOCK_WRUNLOCK(SSL_SERVER_LOCK, &ckchi->server->ssl_ctx.lock);
2039
2040 } else {
2041 HA_RWLOCK_WRLOCK(SNI_LOCK, &ckchi->bind_conf->sni_lock);
2042 ssl_sock_load_cert_sni(ckchi, ckchi->bind_conf);
2043 HA_RWLOCK_WRUNLOCK(SNI_LOCK, &ckchi->bind_conf->sni_lock);
2044 }
2045}
2046
2047/*
2048 * Delete a ckch instance that was replaced after a CLI command.
2049 */
2050static void __ckch_inst_free_locked(struct ckch_inst *ckchi)
2051{
2052 if (ckchi->is_server_instance) {
2053 /* no lock for servers */
2054 ckch_inst_free(ckchi);
2055 } else {
2056 struct bind_conf __maybe_unused *bind_conf = ckchi->bind_conf;
2057
2058 HA_RWLOCK_WRLOCK(SNI_LOCK, &bind_conf->sni_lock);
2059 ckch_inst_free(ckchi);
2060 HA_RWLOCK_WRUNLOCK(SNI_LOCK, &bind_conf->sni_lock);
2061 }
2062}
2063
William Lallemand3b5a3a62022-03-29 14:29:31 +02002064/* Replace a ckch_store in the ckch tree and insert the whole dependencies,
2065* then free the previous dependencies and store.
2066* Used in the case of a certificate update.
2067*
2068* Every dependencies must allocated before using this function.
2069*
2070* This function can't fail as it only update pointers, and does not alloc anything.
2071*
2072* /!\ This function must be used under the ckch lock. /!\
2073*
2074* - Insert every dependencies (SNI, crtlist_entry, ckch_inst, etc)
2075* - Delete the old ckch_store from the tree
2076* - Insert the new ckch_store
2077* - Free the old dependencies and the old ckch_store
2078*/
2079void ckch_store_replace(struct ckch_store *old_ckchs, struct ckch_store *new_ckchs)
2080{
2081 struct crtlist_entry *entry;
2082 struct ckch_inst *ckchi, *ckchis;
2083
2084 LIST_SPLICE(&new_ckchs->crtlist_entry, &old_ckchs->crtlist_entry);
2085 list_for_each_entry(entry, &new_ckchs->crtlist_entry, by_ckch_store) {
2086 ebpt_delete(&entry->node);
2087 /* change the ptr and reinsert the node */
2088 entry->node.key = new_ckchs;
2089 ebpt_insert(&entry->crtlist->entries, &entry->node);
2090 }
2091 /* insert the new ckch_insts in the crtlist_entry */
2092 list_for_each_entry(ckchi, &new_ckchs->ckch_inst, by_ckchs) {
2093 if (ckchi->crtlist_entry)
2094 LIST_INSERT(&ckchi->crtlist_entry->ckch_inst, &ckchi->by_crtlist_entry);
2095 }
2096 /* First, we insert every new SNIs in the trees, also replace the default_ctx */
2097 list_for_each_entry_safe(ckchi, ckchis, &new_ckchs->ckch_inst, by_ckchs) {
2098 __ssl_sock_load_new_ckch_instance(ckchi);
2099 }
2100 /* delete the old sni_ctx, the old ckch_insts and the ckch_store */
2101 list_for_each_entry_safe(ckchi, ckchis, &old_ckchs->ckch_inst, by_ckchs) {
2102 __ckch_inst_free_locked(ckchi);
2103 }
2104
2105 ckch_store_free(old_ckchs);
2106 ebst_insert(&ckchs_tree, &new_ckchs->node);
2107}
2108
Remi Tricot-Le Bretonbfadc022021-02-24 12:20:48 +01002109
William Lallemandda8584c2020-05-14 10:14:37 +02002110/*
2111 * This function tries to create the new ckch_inst and their SNIs
William Lallemand30fcca12022-03-30 12:03:12 +02002112 *
2113 * /!\ don't forget to update __hlua_ckch_commit() if you changes things there. /!\
William Lallemandda8584c2020-05-14 10:14:37 +02002114 */
2115static int cli_io_handler_commit_cert(struct appctx *appctx)
2116{
Willy Tarreaua645b6a2022-05-04 19:58:00 +02002117 struct commit_cert_ctx *ctx = appctx->svcctx;
Willy Tarreauc12b3212022-05-27 11:08:15 +02002118 struct stconn *sc = appctx_sc(appctx);
William Lallemandda8584c2020-05-14 10:14:37 +02002119 int y = 0;
William Lallemandda8584c2020-05-14 10:14:37 +02002120 struct ckch_store *old_ckchs, *new_ckchs = NULL;
William Lallemand3b5a3a62022-03-29 14:29:31 +02002121 struct ckch_inst *ckchi;
William Lallemandda8584c2020-05-14 10:14:37 +02002122
Frédéric Lécaillec33e4df2023-12-05 15:11:33 +01002123 usermsgs_clr("CLI");
Christopher Faulet87633c32023-04-03 18:32:50 +02002124 /* FIXME: Don't watch the other side !*/
Christopher Faulet208c7122023-04-13 16:16:15 +02002125 if (unlikely(sc_opposite(sc)->flags & SC_FL_SHUT_DONE))
Christopher Faulet9d56e242022-05-31 16:37:01 +02002126 goto end;
William Lallemandda8584c2020-05-14 10:14:37 +02002127
2128 while (1) {
Willy Tarreaucb1b4ed2022-05-05 08:15:27 +02002129 switch (ctx->state) {
2130 case CERT_ST_INIT:
William Lallemandda8584c2020-05-14 10:14:37 +02002131 /* This state just print the update message */
Christopher Faulet9d56e242022-05-31 16:37:01 +02002132 chunk_printf(&trash, "Committing %s", ckchs_transaction.path);
2133 if (applet_putchk(appctx, &trash) == -1)
William Lallemandda8584c2020-05-14 10:14:37 +02002134 goto yield;
Willy Tarreaud0a06d52022-05-18 15:07:19 +02002135
Willy Tarreaucb1b4ed2022-05-05 08:15:27 +02002136 ctx->state = CERT_ST_GEN;
Willy Tarreau6fcc86b2022-11-14 07:05:31 +01002137 __fallthrough;
Willy Tarreaucb1b4ed2022-05-05 08:15:27 +02002138 case CERT_ST_GEN:
William Lallemandda8584c2020-05-14 10:14:37 +02002139 /*
2140 * This state generates the ckch instances with their
2141 * sni_ctxs and SSL_CTX.
2142 *
2143 * Since the SSL_CTX generation can be CPU consumer, we
2144 * yield every 10 instances.
2145 */
2146
Willy Tarreaua645b6a2022-05-04 19:58:00 +02002147 old_ckchs = ctx->old_ckchs;
2148 new_ckchs = ctx->new_ckchs;
William Lallemandda8584c2020-05-14 10:14:37 +02002149
William Lallemandda8584c2020-05-14 10:14:37 +02002150 /* get the next ckchi to regenerate */
Willy Tarreaua645b6a2022-05-04 19:58:00 +02002151 ckchi = ctx->next_ckchi;
William Lallemandda8584c2020-05-14 10:14:37 +02002152 /* we didn't start yet, set it to the first elem */
2153 if (ckchi == NULL)
2154 ckchi = LIST_ELEM(old_ckchs->ckch_inst.n, typeof(ckchi), by_ckchs);
2155
2156 /* walk through the old ckch_inst and creates new ckch_inst using the updated ckchs */
2157 list_for_each_entry_from(ckchi, &old_ckchs->ckch_inst, by_ckchs) {
2158 struct ckch_inst *new_inst;
William Lallemandda8584c2020-05-14 10:14:37 +02002159
Christopher Faulet9d56e242022-05-31 16:37:01 +02002160 /* save the next ckchi to compute in case of yield */
2161 ctx->next_ckchi = ckchi;
2162
William Lallemandda8584c2020-05-14 10:14:37 +02002163 /* it takes a lot of CPU to creates SSL_CTXs, so we yield every 10 CKCH instances */
2164 if (y >= 10) {
Christopher Faulet9d56e242022-05-31 16:37:01 +02002165 applet_have_more_data(appctx); /* let's come back later */
William Lallemandda8584c2020-05-14 10:14:37 +02002166 goto yield;
2167 }
2168
Christopher Faulet9d56e242022-05-31 16:37:01 +02002169 /* display one dot per new instance */
2170 if (applet_putstr(appctx, ".") == -1)
2171 goto yield;
2172
2173 ctx->err = NULL;
2174 if (ckch_inst_rebuild(new_ckchs, ckchi, &new_inst, &ctx->err)) {
2175 ctx->state = CERT_ST_ERROR;
William Lallemandda8584c2020-05-14 10:14:37 +02002176 goto error;
Christopher Faulet9d56e242022-05-31 16:37:01 +02002177 }
William Lallemandda8584c2020-05-14 10:14:37 +02002178
William Lallemandda8584c2020-05-14 10:14:37 +02002179 /* link the new ckch_inst to the duplicate */
Willy Tarreau2b718102021-04-21 07:32:39 +02002180 LIST_APPEND(&new_ckchs->ckch_inst, &new_inst->by_ckchs);
William Lallemandda8584c2020-05-14 10:14:37 +02002181 y++;
2182 }
Willy Tarreaucb1b4ed2022-05-05 08:15:27 +02002183 ctx->state = CERT_ST_INSERT;
Willy Tarreau6fcc86b2022-11-14 07:05:31 +01002184 __fallthrough;
Willy Tarreaucb1b4ed2022-05-05 08:15:27 +02002185 case CERT_ST_INSERT:
William Lallemandda8584c2020-05-14 10:14:37 +02002186 /* The generation is finished, we can insert everything */
2187
Willy Tarreaua645b6a2022-05-04 19:58:00 +02002188 old_ckchs = ctx->old_ckchs;
2189 new_ckchs = ctx->new_ckchs;
William Lallemandda8584c2020-05-14 10:14:37 +02002190
William Lallemand3b5a3a62022-03-29 14:29:31 +02002191 /* insert everything and remove the previous objects */
2192 ckch_store_replace(old_ckchs, new_ckchs);
Christopher Faulet9d56e242022-05-31 16:37:01 +02002193 ctx->new_ckchs = ctx->old_ckchs = NULL;
2194 ctx->state = CERT_ST_SUCCESS;
Willy Tarreau6fcc86b2022-11-14 07:05:31 +01002195 __fallthrough;
Christopher Faulet9d56e242022-05-31 16:37:01 +02002196 case CERT_ST_SUCCESS:
Frédéric Lécaillec33e4df2023-12-05 15:11:33 +01002197 chunk_printf(&trash, "\n%sSuccess!\n", usermsgs_str());
2198 if (applet_putchk(appctx, &trash) == -1)
Christopher Faulet9d56e242022-05-31 16:37:01 +02002199 goto yield;
Willy Tarreaucb1b4ed2022-05-05 08:15:27 +02002200 ctx->state = CERT_ST_FIN;
Willy Tarreau6fcc86b2022-11-14 07:05:31 +01002201 __fallthrough;
Willy Tarreaucb1b4ed2022-05-05 08:15:27 +02002202 case CERT_ST_FIN:
William Lallemandda8584c2020-05-14 10:14:37 +02002203 /* we achieved the transaction, we can set everything to NULL */
William Lallemandda8584c2020-05-14 10:14:37 +02002204 ckchs_transaction.new_ckchs = NULL;
2205 ckchs_transaction.old_ckchs = NULL;
Christopher Faulete2ef4dd2022-05-31 18:07:59 +02002206 ckchs_transaction.path = NULL;
William Lallemandda8584c2020-05-14 10:14:37 +02002207 goto end;
Christopher Faulet9d56e242022-05-31 16:37:01 +02002208
2209 case CERT_ST_ERROR:
2210 error:
Frédéric Lécaillec33e4df2023-12-05 15:11:33 +01002211 chunk_printf(&trash, "\n%s%sFailed!\n", usermsgs_str(), ctx->err);
Christopher Faulet9d56e242022-05-31 16:37:01 +02002212 if (applet_putchk(appctx, &trash) == -1)
2213 goto yield;
2214 ctx->state = CERT_ST_FIN;
2215 break;
William Lallemandda8584c2020-05-14 10:14:37 +02002216 }
2217 }
2218end:
Frédéric Lécaillec33e4df2023-12-05 15:11:33 +01002219 usermsgs_clr(NULL);
William Lallemandda8584c2020-05-14 10:14:37 +02002220 /* success: call the release function and don't come back */
2221 return 1;
Christopher Faulet9d56e242022-05-31 16:37:01 +02002222
William Lallemandda8584c2020-05-14 10:14:37 +02002223yield:
Frédéric Lécaillec33e4df2023-12-05 15:11:33 +01002224 usermsgs_clr(NULL);
William Lallemandda8584c2020-05-14 10:14:37 +02002225 return 0; /* should come back */
William Lallemandda8584c2020-05-14 10:14:37 +02002226}
2227
2228/*
2229 * Parsing function of 'commit ssl cert'
2230 */
2231static int cli_parse_commit_cert(char **args, char *payload, struct appctx *appctx, void *private)
2232{
Willy Tarreaua645b6a2022-05-04 19:58:00 +02002233 struct commit_cert_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
William Lallemandda8584c2020-05-14 10:14:37 +02002234 char *err = NULL;
2235
2236 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2237 return 1;
2238
2239 if (!*args[3])
2240 return cli_err(appctx, "'commit ssl cert expects a filename\n");
2241
2242 /* The operations on the CKCH architecture are locked so we can
2243 * manipulate ckch_store and ckch_inst */
2244 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2245 return cli_err(appctx, "Can't commit the certificate!\nOperations on certificates are currently locked!\n");
2246
2247 if (!ckchs_transaction.path) {
2248 memprintf(&err, "No ongoing transaction! !\n");
2249 goto error;
2250 }
2251
2252 if (strcmp(ckchs_transaction.path, args[3]) != 0) {
2253 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", ckchs_transaction.path, args[3]);
2254 goto error;
2255 }
2256
William Lallemand5685ccf2020-09-16 16:12:25 +02002257 /* if a certificate is here, a private key must be here too */
William Lallemand52ddd992022-11-22 11:51:53 +01002258 if (ckchs_transaction.new_ckchs->data->cert && !ckchs_transaction.new_ckchs->data->key) {
William Lallemand5685ccf2020-09-16 16:12:25 +02002259 memprintf(&err, "The transaction must contain at least a certificate and a private key!\n");
2260 goto error;
2261 }
William Lallemanda9419522020-06-24 16:26:41 +02002262
William Lallemand52ddd992022-11-22 11:51:53 +01002263 if (!X509_check_private_key(ckchs_transaction.new_ckchs->data->cert, ckchs_transaction.new_ckchs->data->key)) {
William Lallemand5685ccf2020-09-16 16:12:25 +02002264 memprintf(&err, "inconsistencies between private key and certificate loaded '%s'.\n", ckchs_transaction.path);
2265 goto error;
William Lallemandda8584c2020-05-14 10:14:37 +02002266 }
2267
2268 /* init the appctx structure */
Willy Tarreaucb1b4ed2022-05-05 08:15:27 +02002269 ctx->state = CERT_ST_INIT;
Willy Tarreaua645b6a2022-05-04 19:58:00 +02002270 ctx->next_ckchi = NULL;
2271 ctx->new_ckchs = ckchs_transaction.new_ckchs;
2272 ctx->old_ckchs = ckchs_transaction.old_ckchs;
William Lallemandda8584c2020-05-14 10:14:37 +02002273
2274 /* we don't unlock there, it will be unlock after the IO handler, in the release handler */
2275 return 0;
2276
2277error:
2278
2279 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2280 err = memprintf(&err, "%sCan't commit %s!\n", err ? err : "", args[3]);
2281
2282 return cli_dynerr(appctx, err);
2283}
2284
2285
2286
2287
2288/*
2289 * Parsing function of `set ssl cert`, it updates or creates a temporary ckch.
Willy Tarreau329f4b42022-05-04 20:05:55 +02002290 * It uses a set_cert_ctx context, and ckchs_transaction under a lock.
William Lallemandda8584c2020-05-14 10:14:37 +02002291 */
2292static int cli_parse_set_cert(char **args, char *payload, struct appctx *appctx, void *private)
2293{
2294 struct ckch_store *new_ckchs = NULL;
2295 struct ckch_store *old_ckchs = NULL;
2296 char *err = NULL;
2297 int i;
William Lallemandda8584c2020-05-14 10:14:37 +02002298 int errcode = 0;
2299 char *end;
William Lallemandff8bf982022-03-29 10:44:23 +02002300 struct cert_exts *cert_ext = &cert_exts[0]; /* default one, PEM */
William Lallemand52ddd992022-11-22 11:51:53 +01002301 struct ckch_data *data;
William Lallemandda8584c2020-05-14 10:14:37 +02002302 struct buffer *buf;
2303
2304 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2305 return 1;
2306
William Lallemandda8584c2020-05-14 10:14:37 +02002307 if (!*args[3] || !payload)
2308 return cli_err(appctx, "'set ssl cert expects a filename and a certificate as a payload\n");
2309
2310 /* The operations on the CKCH architecture are locked so we can
2311 * manipulate ckch_store and ckch_inst */
2312 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2313 return cli_err(appctx, "Can't update the certificate!\nOperations on certificates are currently locked!\n");
2314
William Lallemand5ba80d62021-05-04 16:17:27 +02002315 if ((buf = alloc_trash_chunk()) == NULL) {
2316 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2317 errcode |= ERR_ALERT | ERR_FATAL;
2318 goto end;
2319 }
William Lallemande5ff4ad2020-06-08 09:40:37 +02002320
William Lallemandda8584c2020-05-14 10:14:37 +02002321 if (!chunk_strcpy(buf, args[3])) {
2322 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2323 errcode |= ERR_ALERT | ERR_FATAL;
2324 goto end;
2325 }
2326
2327 /* check which type of file we want to update */
William Lallemandff8bf982022-03-29 10:44:23 +02002328 for (i = 0; cert_exts[i].ext != NULL; i++) {
William Lallemandda8584c2020-05-14 10:14:37 +02002329 end = strrchr(buf->area, '.');
Tim Duesterhuse5ff1412021-01-02 22:31:53 +01002330 if (end && *cert_exts[i].ext && (strcmp(end + 1, cert_exts[i].ext) == 0)) {
William Lallemandda8584c2020-05-14 10:14:37 +02002331 *end = '\0';
William Lallemand089c1382020-10-23 17:35:12 +02002332 buf->data = strlen(buf->area);
William Lallemandff8bf982022-03-29 10:44:23 +02002333 cert_ext = &cert_exts[i];
William Lallemandda8584c2020-05-14 10:14:37 +02002334 break;
2335 }
2336 }
2337
William Lallemandda8584c2020-05-14 10:14:37 +02002338 /* if there is an ongoing transaction */
2339 if (ckchs_transaction.path) {
William Lallemandda8584c2020-05-14 10:14:37 +02002340 /* if there is an ongoing transaction, check if this is the same file */
2341 if (strcmp(ckchs_transaction.path, buf->area) != 0) {
William Lallemand089c1382020-10-23 17:35:12 +02002342 /* we didn't find the transaction, must try more cases below */
2343
2344 /* if the del-ext option is activated we should try to take a look at a ".crt" too. */
William Lallemandff8bf982022-03-29 10:44:23 +02002345 if (cert_ext->type != CERT_TYPE_PEM && global_ssl.extra_files_noext) {
William Lallemand089c1382020-10-23 17:35:12 +02002346 if (!chunk_strcat(buf, ".crt")) {
2347 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2348 errcode |= ERR_ALERT | ERR_FATAL;
2349 goto end;
2350 }
2351
2352 if (strcmp(ckchs_transaction.path, buf->area) != 0) {
2353 /* remove .crt of the error message */
2354 *(b_orig(buf) + b_data(buf) + strlen(".crt")) = '\0';
2355 b_sub(buf, strlen(".crt"));
2356
2357 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", ckchs_transaction.path, buf->area);
2358 errcode |= ERR_ALERT | ERR_FATAL;
2359 goto end;
2360 }
2361 }
William Lallemandda8584c2020-05-14 10:14:37 +02002362 }
2363
Christopher Faulet24a20b92022-06-03 11:50:40 +02002364 old_ckchs = ckchs_transaction.new_ckchs;
William Lallemandda8584c2020-05-14 10:14:37 +02002365
2366 } else {
William Lallemandda8584c2020-05-14 10:14:37 +02002367
William Lallemand95fefa12020-09-09 12:01:33 +02002368 /* lookup for the certificate in the tree */
Christopher Faulet24a20b92022-06-03 11:50:40 +02002369 old_ckchs = ckchs_lookup(buf->area);
William Lallemand089c1382020-10-23 17:35:12 +02002370
Christopher Faulet24a20b92022-06-03 11:50:40 +02002371 if (!old_ckchs) {
William Lallemand089c1382020-10-23 17:35:12 +02002372 /* if the del-ext option is activated we should try to take a look at a ".crt" too. */
William Lallemandff8bf982022-03-29 10:44:23 +02002373 if (cert_ext->type != CERT_TYPE_PEM && global_ssl.extra_files_noext) {
William Lallemand089c1382020-10-23 17:35:12 +02002374 if (!chunk_strcat(buf, ".crt")) {
2375 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2376 errcode |= ERR_ALERT | ERR_FATAL;
2377 goto end;
2378 }
Christopher Faulet24a20b92022-06-03 11:50:40 +02002379 old_ckchs = ckchs_lookup(buf->area);
William Lallemand089c1382020-10-23 17:35:12 +02002380 }
2381 }
William Lallemandda8584c2020-05-14 10:14:37 +02002382 }
2383
Christopher Faulet24a20b92022-06-03 11:50:40 +02002384 if (!old_ckchs) {
William Lallemandda8584c2020-05-14 10:14:37 +02002385 memprintf(&err, "%sCan't replace a certificate which is not referenced by the configuration!\n",
2386 err ? err : "");
2387 errcode |= ERR_ALERT | ERR_FATAL;
2388 goto end;
2389 }
2390
William Lallemandda8584c2020-05-14 10:14:37 +02002391 /* duplicate the ckch store */
2392 new_ckchs = ckchs_dup(old_ckchs);
2393 if (!new_ckchs) {
2394 memprintf(&err, "%sCannot allocate memory!\n",
2395 err ? err : "");
2396 errcode |= ERR_ALERT | ERR_FATAL;
2397 goto end;
2398 }
2399
Frédéric Lécaillec5572712023-12-05 15:38:29 +01002400 /* Reset the OCSP CID */
2401 if (cert_ext->type == CERT_TYPE_PEM || cert_ext->type == CERT_TYPE_KEY ||
2402 cert_ext->type == CERT_TYPE_ISSUER) {
2403 OCSP_CERTID_free(new_ckchs->data->ocsp_cid);
2404 new_ckchs->data->ocsp_cid = NULL;
2405 }
2406
William Lallemand52ddd992022-11-22 11:51:53 +01002407 data = new_ckchs->data;
William Lallemandda8584c2020-05-14 10:14:37 +02002408
2409 /* appply the change on the duplicate */
William Lallemand52ddd992022-11-22 11:51:53 +01002410 if (cert_ext->load(buf->area, payload, data, &err) != 0) {
William Lallemandda8584c2020-05-14 10:14:37 +02002411 memprintf(&err, "%sCan't load the payload\n", err ? err : "");
2412 errcode |= ERR_ALERT | ERR_FATAL;
2413 goto end;
2414 }
2415
William Lallemandda8584c2020-05-14 10:14:37 +02002416 /* we succeed, we can save the ckchs in the transaction */
2417
2418 /* if there wasn't a transaction, update the old ckchs */
2419 if (!ckchs_transaction.old_ckchs) {
Christopher Faulet24a20b92022-06-03 11:50:40 +02002420 ckchs_transaction.old_ckchs = old_ckchs;
2421 ckchs_transaction.path = old_ckchs->path;
William Lallemandda8584c2020-05-14 10:14:37 +02002422 err = memprintf(&err, "Transaction created for certificate %s!\n", ckchs_transaction.path);
2423 } else {
2424 err = memprintf(&err, "Transaction updated for certificate %s!\n", ckchs_transaction.path);
2425
2426 }
2427
2428 /* free the previous ckchs if there was a transaction */
2429 ckch_store_free(ckchs_transaction.new_ckchs);
2430
Christopher Faulet24a20b92022-06-03 11:50:40 +02002431 ckchs_transaction.new_ckchs = new_ckchs;
William Lallemandda8584c2020-05-14 10:14:37 +02002432
2433
2434 /* creates the SNI ctxs later in the IO handler */
2435
2436end:
2437 free_trash_chunk(buf);
2438
2439 if (errcode & ERR_CODE) {
Christopher Faulet24a20b92022-06-03 11:50:40 +02002440 ckch_store_free(new_ckchs);
William Lallemandda8584c2020-05-14 10:14:37 +02002441 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2442 return cli_dynerr(appctx, memprintf(&err, "%sCan't update %s!\n", err ? err : "", args[3]));
2443 } else {
William Lallemandda8584c2020-05-14 10:14:37 +02002444 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2445 return cli_dynmsg(appctx, LOG_NOTICE, err);
2446 }
2447 /* TODO: handle the ERR_WARN which are not handled because of the io_handler */
2448}
2449
2450/* parsing function of 'abort ssl cert' */
2451static int cli_parse_abort_cert(char **args, char *payload, struct appctx *appctx, void *private)
2452{
2453 char *err = NULL;
2454
2455 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2456 return 1;
2457
2458 if (!*args[3])
2459 return cli_err(appctx, "'abort ssl cert' expects a filename\n");
2460
2461 /* The operations on the CKCH architecture are locked so we can
2462 * manipulate ckch_store and ckch_inst */
2463 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2464 return cli_err(appctx, "Can't abort!\nOperations on certificates are currently locked!\n");
2465
2466 if (!ckchs_transaction.path) {
2467 memprintf(&err, "No ongoing transaction!\n");
2468 goto error;
2469 }
2470
2471 if (strcmp(ckchs_transaction.path, args[3]) != 0) {
2472 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to abort a transaction for '%s'\n", ckchs_transaction.path, args[3]);
2473 goto error;
2474 }
2475
2476 /* Only free the ckchs there, because the SNI and instances were not generated yet */
2477 ckch_store_free(ckchs_transaction.new_ckchs);
2478 ckchs_transaction.new_ckchs = NULL;
William Lallemandda8584c2020-05-14 10:14:37 +02002479 ckchs_transaction.old_ckchs = NULL;
Christopher Faulete2ef4dd2022-05-31 18:07:59 +02002480 ckchs_transaction.path = NULL;
William Lallemandda8584c2020-05-14 10:14:37 +02002481
2482 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2483
2484 err = memprintf(&err, "Transaction aborted for certificate '%s'!\n", args[3]);
2485 return cli_dynmsg(appctx, LOG_NOTICE, err);
2486
2487error:
2488 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2489
2490 return cli_dynerr(appctx, err);
2491}
2492
2493/* parsing function of 'new ssl cert' */
2494static int cli_parse_new_cert(char **args, char *payload, struct appctx *appctx, void *private)
2495{
2496 struct ckch_store *store;
2497 char *err = NULL;
2498 char *path;
2499
2500 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2501 return 1;
2502
2503 if (!*args[3])
2504 return cli_err(appctx, "'new ssl cert' expects a filename\n");
2505
2506 path = args[3];
2507
2508 /* The operations on the CKCH architecture are locked so we can
2509 * manipulate ckch_store and ckch_inst */
2510 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2511 return cli_err(appctx, "Can't create a certificate!\nOperations on certificates are currently locked!\n");
2512
2513 store = ckchs_lookup(path);
2514 if (store != NULL) {
2515 memprintf(&err, "Certificate '%s' already exists!\n", path);
2516 store = NULL; /* we don't want to free it */
2517 goto error;
2518 }
2519 /* we won't support multi-certificate bundle here */
William Lallemandbd8e6ed2020-09-16 16:08:08 +02002520 store = ckch_store_new(path);
William Lallemandda8584c2020-05-14 10:14:37 +02002521 if (!store) {
2522 memprintf(&err, "unable to allocate memory.\n");
2523 goto error;
2524 }
2525
2526 /* insert into the ckchs tree */
2527 ebst_insert(&ckchs_tree, &store->node);
2528 memprintf(&err, "New empty certificate store '%s'!\n", args[3]);
2529
2530 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2531 return cli_dynmsg(appctx, LOG_NOTICE, err);
2532error:
2533 free(store);
2534 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2535 return cli_dynerr(appctx, err);
2536}
2537
2538/* parsing function of 'del ssl cert' */
2539static int cli_parse_del_cert(char **args, char *payload, struct appctx *appctx, void *private)
2540{
2541 struct ckch_store *store;
2542 char *err = NULL;
2543 char *filename;
2544
2545 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2546 return 1;
2547
2548 if (!*args[3])
2549 return cli_err(appctx, "'del ssl cert' expects a certificate name\n");
2550
2551 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2552 return cli_err(appctx, "Can't delete the certificate!\nOperations on certificates are currently locked!\n");
2553
2554 filename = args[3];
2555
Christopher Faulet926fefc2022-05-31 18:04:25 +02002556 if (ckchs_transaction.path && strcmp(ckchs_transaction.path, filename) == 0) {
2557 memprintf(&err, "ongoing transaction for the certificate '%s'", filename);
2558 goto error;
2559 }
2560
William Lallemandda8584c2020-05-14 10:14:37 +02002561 store = ckchs_lookup(filename);
2562 if (store == NULL) {
2563 memprintf(&err, "certificate '%s' doesn't exist!\n", filename);
2564 goto error;
2565 }
2566 if (!LIST_ISEMPTY(&store->ckch_inst)) {
2567 memprintf(&err, "certificate '%s' in use, can't be deleted!\n", filename);
2568 goto error;
2569 }
2570
2571 ebmb_delete(&store->node);
2572 ckch_store_free(store);
2573
2574 memprintf(&err, "Certificate '%s' deleted!\n", filename);
2575
2576 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2577 return cli_dynmsg(appctx, LOG_NOTICE, err);
2578
2579error:
2580 memprintf(&err, "Can't remove the certificate: %s\n", err ? err : "");
2581 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2582 return cli_dynerr(appctx, err);
2583}
2584
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002585
Remi Tricot-Le Breton9f40fe02021-03-16 16:21:27 +01002586
2587/* parsing function of 'new ssl ca-file' */
2588static int cli_parse_new_cafile(char **args, char *payload, struct appctx *appctx, void *private)
2589{
2590 struct cafile_entry *cafile_entry;
2591 char *err = NULL;
2592 char *path;
2593
2594 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2595 return 1;
2596
2597 if (!*args[3])
2598 return cli_err(appctx, "'new ssl ca-file' expects a filename\n");
2599
2600 path = args[3];
2601
2602 /* The operations on the CKCH architecture are locked so we can
2603 * manipulate ckch_store and ckch_inst */
2604 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2605 return cli_err(appctx, "Can't create a CA file!\nOperations on certificates are currently locked!\n");
2606
2607 cafile_entry = ssl_store_get_cafile_entry(path, 0);
2608 if (cafile_entry) {
2609 memprintf(&err, "CA file '%s' already exists!\n", path);
2610 goto error;
2611 }
2612
2613 cafile_entry = ssl_store_create_cafile_entry(path, NULL, CAFILE_CERT);
2614 if (!cafile_entry) {
2615 memprintf(&err, "%sCannot allocate memory!\n",
2616 err ? err : "");
2617 goto error;
2618 }
2619
2620 /* Add the newly created cafile_entry to the tree so that
2621 * any new ckch instance created from now can use it. */
2622 if (ssl_store_add_uncommitted_cafile_entry(cafile_entry))
2623 goto error;
2624
2625 memprintf(&err, "New CA file created '%s'!\n", path);
2626
2627 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2628 return cli_dynmsg(appctx, LOG_NOTICE, err);
2629error:
2630 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2631 return cli_dynerr(appctx, err);
2632}
2633
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002634/*
2635 * Parsing function of `set ssl ca-file`
2636 */
2637static int cli_parse_set_cafile(char **args, char *payload, struct appctx *appctx, void *private)
2638{
Christopher Faulet132c5952022-06-03 11:56:26 +02002639 struct cafile_entry *old_cafile_entry = NULL;
2640 struct cafile_entry *new_cafile_entry = NULL;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002641 char *err = NULL;
2642 int errcode = 0;
2643 struct buffer *buf;
William Lallemand62c0b992022-07-29 17:50:58 +02002644 int add_cmd = 0;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002645
2646 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2647 return 1;
2648
William Lallemand62c0b992022-07-29 17:50:58 +02002649 /* this is "add ssl ca-file" */
2650 if (*args[0] == 'a')
2651 add_cmd = 1;
2652
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002653 if (!*args[3] || !payload)
2654 return cli_err(appctx, "'set ssl ca-file expects a filename and CAs as a payload\n");
2655
2656 /* The operations on the CKCH architecture are locked so we can
2657 * manipulate ckch_store and ckch_inst */
2658 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2659 return cli_err(appctx, "Can't update the CA file!\nOperations on certificates are currently locked!\n");
2660
2661 if ((buf = alloc_trash_chunk()) == NULL) {
2662 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2663 errcode |= ERR_ALERT | ERR_FATAL;
2664 goto end;
2665 }
2666
2667 if (!chunk_strcpy(buf, args[3])) {
2668 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
2669 errcode |= ERR_ALERT | ERR_FATAL;
2670 goto end;
2671 }
2672
Christopher Faulet132c5952022-06-03 11:56:26 +02002673 old_cafile_entry = NULL;
2674 new_cafile_entry = NULL;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002675
2676 /* if there is an ongoing transaction */
2677 if (cafile_transaction.path) {
2678 /* if there is an ongoing transaction, check if this is the same file */
2679 if (strcmp(cafile_transaction.path, buf->area) != 0) {
2680 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", cafile_transaction.path, buf->area);
2681 errcode |= ERR_ALERT | ERR_FATAL;
2682 goto end;
2683 }
Christopher Faulet132c5952022-06-03 11:56:26 +02002684 old_cafile_entry = cafile_transaction.old_cafile_entry;
William Lallemand62c0b992022-07-29 17:50:58 +02002685 } else {
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002686 /* lookup for the certificate in the tree */
Christopher Faulet132c5952022-06-03 11:56:26 +02002687 old_cafile_entry = ssl_store_get_cafile_entry(buf->area, 0);
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002688 }
2689
Christopher Faulet132c5952022-06-03 11:56:26 +02002690 if (!old_cafile_entry) {
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002691 memprintf(&err, "%sCan't replace a CA file which is not referenced by the configuration!\n",
2692 err ? err : "");
2693 errcode |= ERR_ALERT | ERR_FATAL;
2694 goto end;
2695 }
2696
William Lallemand62c0b992022-07-29 17:50:58 +02002697 /* if the transaction is new, duplicate the old_ca_file_entry, otherwise duplicate the cafile in the current transaction */
2698 if (cafile_transaction.new_cafile_entry)
2699 new_cafile_entry = ssl_store_dup_cafile_entry(cafile_transaction.new_cafile_entry);
2700 else
2701 new_cafile_entry = ssl_store_dup_cafile_entry(old_cafile_entry);
2702
Christopher Faulet132c5952022-06-03 11:56:26 +02002703 if (!new_cafile_entry) {
William Lallemand62c0b992022-07-29 17:50:58 +02002704 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002705 errcode |= ERR_ALERT | ERR_FATAL;
2706 goto end;
2707 }
2708
William Lallemand62c0b992022-07-29 17:50:58 +02002709 /* Fill the new entry with the new CAs. The add_cmd variable determine
2710 if we flush the X509_STORE or not */
2711 if (ssl_store_load_ca_from_buf(new_cafile_entry, payload, add_cmd)) {
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002712 memprintf(&err, "%sInvalid payload\n", err ? err : "");
2713 errcode |= ERR_ALERT | ERR_FATAL;
2714 goto end;
2715 }
2716
2717 /* we succeed, we can save the ca in the transaction */
2718
2719 /* if there wasn't a transaction, update the old CA */
2720 if (!cafile_transaction.old_cafile_entry) {
Christopher Faulet132c5952022-06-03 11:56:26 +02002721 cafile_transaction.old_cafile_entry = old_cafile_entry;
2722 cafile_transaction.path = old_cafile_entry->path;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002723 err = memprintf(&err, "transaction created for CA %s!\n", cafile_transaction.path);
2724 } else {
2725 err = memprintf(&err, "transaction updated for CA %s!\n", cafile_transaction.path);
2726 }
2727
2728 /* free the previous CA if there was a transaction */
2729 ssl_store_delete_cafile_entry(cafile_transaction.new_cafile_entry);
2730
Christopher Faulet132c5952022-06-03 11:56:26 +02002731 cafile_transaction.new_cafile_entry = new_cafile_entry;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002732
2733 /* creates the SNI ctxs later in the IO handler */
2734
2735end:
2736 free_trash_chunk(buf);
2737
2738 if (errcode & ERR_CODE) {
Christopher Faulet132c5952022-06-03 11:56:26 +02002739 ssl_store_delete_cafile_entry(new_cafile_entry);
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002740 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2741 return cli_dynerr(appctx, memprintf(&err, "%sCan't update %s!\n", err ? err : "", args[3]));
2742 } else {
2743
2744 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2745 return cli_dynmsg(appctx, LOG_NOTICE, err);
2746 }
2747}
2748
2749
2750/*
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002751 * Parsing function of 'commit ssl ca-file'.
2752 * It uses a commit_cacrlfile_ctx that's also shared with "commit ssl crl-file".
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002753 */
2754static int cli_parse_commit_cafile(char **args, char *payload, struct appctx *appctx, void *private)
2755{
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002756 struct commit_cacrlfile_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002757 char *err = NULL;
2758
2759 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2760 return 1;
2761
2762 if (!*args[3])
2763 return cli_err(appctx, "'commit ssl ca-file expects a filename\n");
2764
2765 /* The operations on the CKCH architecture are locked so we can
2766 * manipulate ckch_store and ckch_inst */
2767 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2768 return cli_err(appctx, "Can't commit the CA file!\nOperations on certificates are currently locked!\n");
2769
2770 if (!cafile_transaction.path) {
2771 memprintf(&err, "No ongoing transaction! !\n");
2772 goto error;
2773 }
2774
2775 if (strcmp(cafile_transaction.path, args[3]) != 0) {
2776 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", cafile_transaction.path, args[3]);
2777 goto error;
2778 }
2779 /* init the appctx structure */
Willy Tarreau1d6dd802022-05-05 08:17:29 +02002780 ctx->state = CACRL_ST_INIT;
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002781 ctx->next_ckchi_link = NULL;
Christopher Faulet6af2fc62022-06-03 11:42:38 +02002782 ctx->old_entry = cafile_transaction.old_cafile_entry;
2783 ctx->new_entry = cafile_transaction.new_cafile_entry;
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002784 ctx->cafile_type = CAFILE_CERT;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002785
2786 return 0;
2787
2788error:
2789
2790 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2791 err = memprintf(&err, "%sCan't commit %s!\n", err ? err : "", args[3]);
2792
2793 return cli_dynerr(appctx, err);
2794}
2795
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002796/*
2797 * This function tries to create new ckch instances and their SNIs using a newly
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002798 * set certificate authority (CA file) or a newly set Certificate Revocation
2799 * List (CRL), depending on the command being called.
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002800 */
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002801static int cli_io_handler_commit_cafile_crlfile(struct appctx *appctx)
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002802{
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002803 struct commit_cacrlfile_ctx *ctx = appctx->svcctx;
Willy Tarreauc12b3212022-05-27 11:08:15 +02002804 struct stconn *sc = appctx_sc(appctx);
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002805 int y = 0;
Christopher Faulet6af2fc62022-06-03 11:42:38 +02002806 struct cafile_entry *old_cafile_entry = ctx->old_entry;
2807 struct cafile_entry *new_cafile_entry = ctx->new_entry;
William Lallemand0bfa3e72022-08-30 17:32:38 +02002808 struct ckch_inst_link *ckchi_link;
Christopher Fauletddc8e1c2022-06-03 09:00:09 +02002809 char *path;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002810
Christopher Faulet87633c32023-04-03 18:32:50 +02002811 /* FIXME: Don't watch the other side !*/
Christopher Faulet208c7122023-04-13 16:16:15 +02002812 if (unlikely(sc_opposite(sc)->flags & SC_FL_SHUT_DONE))
Christopher Faulete9c3bd12022-05-31 17:51:06 +02002813 goto end;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002814
Christopher Fauletddc8e1c2022-06-03 09:00:09 +02002815 /* The ctx was already validated by the ca-file/crl-file parsing
2816 * function. Entries can only be NULL in CACRL_ST_SUCCESS or
2817 * CACRL_ST_FIN states
2818 */
2819 switch (ctx->cafile_type) {
2820 case CAFILE_CERT:
Christopher Fauletddc8e1c2022-06-03 09:00:09 +02002821 path = cafile_transaction.path;
2822 break;
2823 case CAFILE_CRL:
Christopher Fauletddc8e1c2022-06-03 09:00:09 +02002824 path = crlfile_transaction.path;
2825 break;
Christopher Fauletea2c8c62022-06-03 16:37:31 +02002826 default:
Willy Tarreaud543ae02022-06-22 05:40:25 +02002827 path = NULL;
Christopher Fauletea2c8c62022-06-03 16:37:31 +02002828 goto error;
Christopher Fauletddc8e1c2022-06-03 09:00:09 +02002829 }
2830
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002831 while (1) {
Willy Tarreau1d6dd802022-05-05 08:17:29 +02002832 switch (ctx->state) {
2833 case CACRL_ST_INIT:
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002834 /* This state just print the update message */
Christopher Fauletddc8e1c2022-06-03 09:00:09 +02002835 chunk_printf(&trash, "Committing %s", path);
Christopher Faulete9c3bd12022-05-31 17:51:06 +02002836 if (applet_putchk(appctx, &trash) == -1)
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002837 goto yield;
Willy Tarreaud0a06d52022-05-18 15:07:19 +02002838
Willy Tarreau1d6dd802022-05-05 08:17:29 +02002839 ctx->state = CACRL_ST_GEN;
Willy Tarreau6fcc86b2022-11-14 07:05:31 +01002840 __fallthrough;
Willy Tarreau1d6dd802022-05-05 08:17:29 +02002841 case CACRL_ST_GEN:
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002842 /*
2843 * This state generates the ckch instances with their
2844 * sni_ctxs and SSL_CTX.
2845 *
2846 * Since the SSL_CTX generation can be CPU consumer, we
2847 * yield every 10 instances.
2848 */
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002849
2850 /* get the next ckchi to regenerate */
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002851 ckchi_link = ctx->next_ckchi_link;
Christopher Faulete9c3bd12022-05-31 17:51:06 +02002852
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002853 /* we didn't start yet, set it to the first elem */
2854 if (ckchi_link == NULL) {
2855 ckchi_link = LIST_ELEM(old_cafile_entry->ckch_inst_link.n, typeof(ckchi_link), list);
2856 /* Add the newly created cafile_entry to the tree so that
2857 * any new ckch instance created from now can use it. */
Christopher Faulete9c3bd12022-05-31 17:51:06 +02002858 if (ssl_store_add_uncommitted_cafile_entry(new_cafile_entry)) {
2859 ctx->state = CACRL_ST_ERROR;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002860 goto error;
Christopher Faulete9c3bd12022-05-31 17:51:06 +02002861 }
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002862 }
2863
2864 list_for_each_entry_from(ckchi_link, &old_cafile_entry->ckch_inst_link, list) {
Christopher Faulete9c3bd12022-05-31 17:51:06 +02002865 struct ckch_inst *new_inst;
2866
2867 /* save the next ckchi to compute */
2868 ctx->next_ckchi_link = ckchi_link;
2869
2870 /* it takes a lot of CPU to creates SSL_CTXs, so we yield every 10 CKCH instances */
2871 if (y >= 10) {
2872 applet_have_more_data(appctx); /* let's come back later */
2873 goto yield;
2874 }
2875
2876 /* display one dot per new instance */
2877 if (applet_putstr(appctx, ".") == -1)
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002878 goto yield;
Christopher Faulete9c3bd12022-05-31 17:51:06 +02002879
2880 /* Rebuild a new ckch instance that uses the same ckch_store
2881 * than a reference ckchi instance but will use a new CA file. */
2882 ctx->err = NULL;
2883 if (ckch_inst_rebuild(ckchi_link->ckch_inst->ckch_store, ckchi_link->ckch_inst, &new_inst, &ctx->err)) {
2884 ctx->state = CACRL_ST_ERROR;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002885 goto error;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002886 }
Christopher Faulete9c3bd12022-05-31 17:51:06 +02002887
2888 y++;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002889 }
2890
Willy Tarreau1d6dd802022-05-05 08:17:29 +02002891 ctx->state = CACRL_ST_INSERT;
Willy Tarreau6fcc86b2022-11-14 07:05:31 +01002892 __fallthrough;
Willy Tarreau1d6dd802022-05-05 08:17:29 +02002893 case CACRL_ST_INSERT:
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002894 /* The generation is finished, we can insert everything */
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002895
2896 /* insert the new ckch_insts in the crtlist_entry */
2897 list_for_each_entry(ckchi_link, &new_cafile_entry->ckch_inst_link, list) {
2898 if (ckchi_link->ckch_inst->crtlist_entry)
2899 LIST_INSERT(&ckchi_link->ckch_inst->crtlist_entry->ckch_inst,
2900 &ckchi_link->ckch_inst->by_crtlist_entry);
2901 }
2902
2903 /* First, we insert every new SNIs in the trees, also replace the default_ctx */
2904 list_for_each_entry(ckchi_link, &new_cafile_entry->ckch_inst_link, list) {
2905 __ssl_sock_load_new_ckch_instance(ckchi_link->ckch_inst);
2906 }
2907
William Lallemande0fa91f2022-08-31 14:26:49 +02002908 /* delete the old sni_ctx, the old ckch_insts
2909 * and the ckch_store. ckch_inst_free() also
2910 * manipulates the list so it's cleaner to loop
2911 * until it's empty */
2912 while (!LIST_ISEMPTY(&old_cafile_entry->ckch_inst_link)) {
2913 ckchi_link = LIST_ELEM(old_cafile_entry->ckch_inst_link.n, typeof(ckchi_link), list);
2914
2915 LIST_DEL_INIT(&ckchi_link->list); /* must reinit because ckch_inst checks the list */
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002916 __ckch_inst_free_locked(ckchi_link->ckch_inst);
William Lallemande0fa91f2022-08-31 14:26:49 +02002917 free(ckchi_link);
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002918 }
2919
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002920 /* Remove the old cafile entry from the tree */
2921 ebmb_delete(&old_cafile_entry->node);
2922 ssl_store_delete_cafile_entry(old_cafile_entry);
2923
Christopher Faulet6af2fc62022-06-03 11:42:38 +02002924 ctx->old_entry = ctx->new_entry = NULL;
Christopher Faulete9c3bd12022-05-31 17:51:06 +02002925 ctx->state = CACRL_ST_SUCCESS;
Willy Tarreau6fcc86b2022-11-14 07:05:31 +01002926 __fallthrough;
Christopher Faulete9c3bd12022-05-31 17:51:06 +02002927 case CACRL_ST_SUCCESS:
2928 if (applet_putstr(appctx, "\nSuccess!\n") == -1)
2929 goto yield;
Willy Tarreau1d6dd802022-05-05 08:17:29 +02002930 ctx->state = CACRL_ST_FIN;
Willy Tarreau6fcc86b2022-11-14 07:05:31 +01002931 __fallthrough;
Willy Tarreau1d6dd802022-05-05 08:17:29 +02002932 case CACRL_ST_FIN:
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002933 /* we achieved the transaction, we can set everything to NULL */
Willy Tarreaudec23dc2022-05-04 20:25:05 +02002934 switch (ctx->cafile_type) {
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002935 case CAFILE_CERT:
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002936 cafile_transaction.old_cafile_entry = NULL;
2937 cafile_transaction.new_cafile_entry = NULL;
Christopher Faulet1e00c7e2022-05-31 18:10:19 +02002938 cafile_transaction.path = NULL;
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002939 break;
2940 case CAFILE_CRL:
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002941 crlfile_transaction.old_crlfile_entry = NULL;
2942 crlfile_transaction.new_crlfile_entry = NULL;
Christopher Faulet1e00c7e2022-05-31 18:10:19 +02002943 crlfile_transaction.path = NULL;
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02002944 break;
2945 }
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002946 goto end;
Christopher Faulete9c3bd12022-05-31 17:51:06 +02002947
2948 case CACRL_ST_ERROR:
2949 error:
2950 chunk_printf(&trash, "\n%sFailed!\n", ctx->err);
2951 if (applet_putchk(appctx, &trash) == -1)
2952 goto yield;
2953 ctx->state = CACRL_ST_FIN;
2954 break;
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002955 }
2956 }
2957end:
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002958 /* success: call the release function and don't come back */
2959 return 1;
2960yield:
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002961 return 0; /* should come back */
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01002962}
2963
Remi Tricot-Le Bretond5fd09d2021-03-11 10:22:52 +01002964
2965/* parsing function of 'abort ssl ca-file' */
2966static int cli_parse_abort_cafile(char **args, char *payload, struct appctx *appctx, void *private)
2967{
2968 char *err = NULL;
2969
2970 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
2971 return 1;
2972
2973 if (!*args[3])
2974 return cli_err(appctx, "'abort ssl ca-file' expects a filename\n");
2975
2976 /* The operations on the CKCH architecture are locked so we can
2977 * manipulate ckch_store and ckch_inst */
2978 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
2979 return cli_err(appctx, "Can't abort!\nOperations on certificates are currently locked!\n");
2980
2981 if (!cafile_transaction.path) {
2982 memprintf(&err, "No ongoing transaction!\n");
2983 goto error;
2984 }
2985
2986 if (strcmp(cafile_transaction.path, args[3]) != 0) {
2987 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to abort a transaction for '%s'\n", cafile_transaction.path, args[3]);
2988 goto error;
2989 }
2990
2991 /* Only free the uncommitted cafile_entry here, because the SNI and instances were not generated yet */
2992 ssl_store_delete_cafile_entry(cafile_transaction.new_cafile_entry);
2993 cafile_transaction.new_cafile_entry = NULL;
2994 cafile_transaction.old_cafile_entry = NULL;
Christopher Faulet1e00c7e2022-05-31 18:10:19 +02002995 cafile_transaction.path = NULL;
Remi Tricot-Le Bretond5fd09d2021-03-11 10:22:52 +01002996
2997 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
2998
2999 err = memprintf(&err, "Transaction aborted for certificate '%s'!\n", args[3]);
3000 return cli_dynmsg(appctx, LOG_NOTICE, err);
3001
3002error:
3003 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3004
3005 return cli_dynerr(appctx, err);
3006}
3007
Willy Tarreau821c3b02022-05-04 15:47:39 +02003008/* release function of the `commit ssl ca-file' command, free things and unlock the spinlock.
Willy Tarreaudec23dc2022-05-04 20:25:05 +02003009 * It uses a commit_cacrlfile_ctx context.
Willy Tarreau821c3b02022-05-04 15:47:39 +02003010 */
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01003011static void cli_release_commit_cafile(struct appctx *appctx)
3012{
Willy Tarreaudec23dc2022-05-04 20:25:05 +02003013 struct commit_cacrlfile_ctx *ctx = appctx->svcctx;
Christopher Faulet6af2fc62022-06-03 11:42:38 +02003014 struct cafile_entry *new_cafile_entry = ctx->new_entry;
Willy Tarreaudec23dc2022-05-04 20:25:05 +02003015
Christopher Faulete9c3bd12022-05-31 17:51:06 +02003016 /* Remove the uncommitted cafile_entry from the tree. */
3017 if (new_cafile_entry) {
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01003018 ebmb_delete(&new_cafile_entry->node);
3019 ssl_store_delete_cafile_entry(new_cafile_entry);
3020 }
3021 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
Christopher Faulete9c3bd12022-05-31 17:51:06 +02003022 ha_free(&ctx->err);
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01003023}
3024
3025
Willy Tarreau821c3b02022-05-04 15:47:39 +02003026/* IO handler of details "show ssl ca-file <filename[:index]>".
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003027 * It uses a show_cafile_ctx context, and the global
3028 * cafile_transaction.new_cafile_entry in read-only.
Willy Tarreau821c3b02022-05-04 15:47:39 +02003029 */
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003030static int cli_io_handler_show_cafile_detail(struct appctx *appctx)
3031{
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003032 struct show_cafile_ctx *ctx = appctx->svcctx;
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003033 struct cafile_entry *cafile_entry = ctx->cur_cafile_entry;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003034 struct buffer *out = alloc_trash_chunk();
William Lallemand03a32e52022-04-26 18:17:15 +02003035 int i = 0;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003036 X509 *cert;
3037 STACK_OF(X509_OBJECT) *objs;
3038 int retval = 0;
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003039 int ca_index = ctx->ca_index;
3040 int show_all = ctx->show_all;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003041
3042 if (!out)
3043 goto end_no_putchk;
3044
3045 chunk_appendf(out, "Filename: ");
3046 if (cafile_entry == cafile_transaction.new_cafile_entry)
3047 chunk_appendf(out, "*");
3048 chunk_appendf(out, "%s\n", cafile_entry->path);
3049
3050 chunk_appendf(out, "Status: ");
3051 if (!cafile_entry->ca_store)
3052 chunk_appendf(out, "Empty\n");
3053 else if (LIST_ISEMPTY(&cafile_entry->ckch_inst_link))
3054 chunk_appendf(out, "Unused\n");
3055 else
3056 chunk_appendf(out, "Used\n");
3057
3058 if (!cafile_entry->ca_store)
3059 goto end;
3060
3061 objs = X509_STORE_get0_objects(cafile_entry->ca_store);
William Lallemand03a32e52022-04-26 18:17:15 +02003062 for (i = ca_index; i < sk_X509_OBJECT_num(objs); i++) {
3063
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003064 cert = X509_OBJECT_get0_X509(sk_X509_OBJECT_value(objs, i));
3065 if (!cert)
3066 continue;
3067
William Lallemand03a32e52022-04-26 18:17:15 +02003068 /* file starts at line 1 */
Remi Tricot-Le Bretone8041fe2022-04-05 16:44:21 +02003069 chunk_appendf(out, " \nCertificate #%d:\n", i+1);
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003070 retval = show_cert_detail(cert, NULL, out);
3071 if (retval < 0)
3072 goto end_no_putchk;
William Lallemand03a32e52022-04-26 18:17:15 +02003073 else if (retval)
3074 goto yield;
3075
Willy Tarreaud0a06d52022-05-18 15:07:19 +02003076 if (applet_putchk(appctx, out) == -1)
William Lallemand03a32e52022-04-26 18:17:15 +02003077 goto yield;
William Lallemand03a32e52022-04-26 18:17:15 +02003078
3079 if (!show_all) /* only need to dump one certificate */
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003080 goto end;
3081 }
3082
3083end:
William Lallemand03a32e52022-04-26 18:17:15 +02003084 free_trash_chunk(out);
3085 return 1; /* end, don't come back */
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003086
3087end_no_putchk:
3088 free_trash_chunk(out);
3089 return 1;
3090yield:
William Lallemand03a32e52022-04-26 18:17:15 +02003091 /* save the current state */
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003092 ctx->ca_index = i;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003093 free_trash_chunk(out);
3094 return 0; /* should come back */
3095}
3096
3097
Willy Tarreau06305792022-05-04 15:57:30 +02003098/* parsing function for 'show ssl ca-file [cafile[:index]]'.
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003099 * It prepares a show_cafile_ctx context, and checks the global
3100 * cafile_transaction under the ckch_lock (read only).
Willy Tarreau06305792022-05-04 15:57:30 +02003101 */
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003102static int cli_parse_show_cafile(char **args, char *payload, struct appctx *appctx, void *private)
3103{
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003104 struct show_cafile_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003105 struct cafile_entry *cafile_entry;
William Lallemand03a32e52022-04-26 18:17:15 +02003106 int ca_index = 0;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003107 char *colons;
3108 char *err = NULL;
3109
3110 if (!cli_has_level(appctx, ACCESS_LVL_OPER))
3111 return cli_err(appctx, "Can't allocate memory!\n");
3112
3113 /* The operations on the CKCH architecture are locked so we can
3114 * manipulate ckch_store and ckch_inst */
3115 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3116 return cli_err(appctx, "Can't show!\nOperations on certificates are currently locked!\n");
3117
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003118 ctx->show_all = 1; /* show all certificates */
3119 ctx->ca_index = 0;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003120 /* check if there is a certificate to lookup */
3121 if (*args[3]) {
3122
3123 /* Look for an optional CA index after the CA file name */
3124 colons = strchr(args[3], ':');
3125 if (colons) {
3126 char *endptr;
3127
3128 ca_index = strtol(colons + 1, &endptr, 10);
3129 /* Indexes start at 1 */
3130 if (colons + 1 == endptr || *endptr != '\0' || ca_index <= 0) {
3131 memprintf(&err, "wrong CA index after colons in '%s'!", args[3]);
3132 goto error;
3133 }
3134 *colons = '\0';
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003135 ctx->ca_index = ca_index - 1; /* we start counting at 0 in the ca_store, but at 1 on the CLI */
3136 ctx->show_all = 0; /* show only one certificate */
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003137 }
3138
3139 if (*args[3] == '*') {
3140 if (!cafile_transaction.new_cafile_entry)
3141 goto error;
3142
3143 cafile_entry = cafile_transaction.new_cafile_entry;
3144
3145 if (strcmp(args[3] + 1, cafile_entry->path) != 0)
3146 goto error;
3147
3148 } else {
3149 /* Get the "original" cafile_entry and not the
3150 * uncommitted one if it exists. */
3151 if ((cafile_entry = ssl_store_get_cafile_entry(args[3], 1)) == NULL || cafile_entry->type != CAFILE_CERT)
3152 goto error;
3153 }
3154
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003155 ctx->cur_cafile_entry = cafile_entry;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003156 /* use the IO handler that shows details */
3157 appctx->io_handler = cli_io_handler_show_cafile_detail;
3158 }
3159
3160 return 0;
3161
3162error:
3163 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3164 if (err)
3165 return cli_dynerr(appctx, err);
3166 return cli_err(appctx, "Can't display the CA file : Not found!\n");
3167}
3168
3169
3170/* release function of the 'show ssl ca-file' command */
3171static void cli_release_show_cafile(struct appctx *appctx)
3172{
3173 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3174}
3175
3176
3177/* This function returns the number of certificates in a cafile_entry. */
3178static int get_certificate_count(struct cafile_entry *cafile_entry)
3179{
3180 int cert_count = 0;
3181 STACK_OF(X509_OBJECT) *objs;
3182
3183 if (cafile_entry && cafile_entry->ca_store) {
3184 objs = X509_STORE_get0_objects(cafile_entry->ca_store);
3185 if (objs)
3186 cert_count = sk_X509_OBJECT_num(objs);
3187 }
3188 return cert_count;
3189}
3190
3191/* IO handler of "show ssl ca-file". The command taking a specific CA file name
Willy Tarreau821c3b02022-05-04 15:47:39 +02003192 * is managed in cli_io_handler_show_cafile_detail.
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003193 * It uses a show_cafile_ctx and the global cafile_transaction.new_cafile_entry
3194 * in read-only.
Willy Tarreau821c3b02022-05-04 15:47:39 +02003195 */
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003196static int cli_io_handler_show_cafile(struct appctx *appctx)
3197{
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003198 struct show_cafile_ctx *ctx = appctx->svcctx;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003199 struct buffer *trash = alloc_trash_chunk();
3200 struct ebmb_node *node;
Christopher Faulet677cb4f2022-06-03 16:25:35 +02003201 struct cafile_entry *cafile_entry = NULL;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003202
3203 if (trash == NULL)
3204 return 1;
3205
Christopher Faulet5a2154b2022-06-03 10:42:48 +02003206 if (!ctx->old_cafile_entry && cafile_transaction.old_cafile_entry) {
3207 chunk_appendf(trash, "# transaction\n");
3208 chunk_appendf(trash, "*%s", cafile_transaction.old_cafile_entry->path);
3209 chunk_appendf(trash, " - %d certificate(s)\n", get_certificate_count(cafile_transaction.new_cafile_entry));
3210 if (applet_putchk(appctx, trash) == -1)
3211 goto yield;
3212 ctx->old_cafile_entry = cafile_transaction.new_cafile_entry;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003213 }
3214
3215 /* First time in this io_handler. */
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003216 if (!ctx->cur_cafile_entry) {
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003217 chunk_appendf(trash, "# filename\n");
3218 node = ebmb_first(&cafile_tree);
3219 } else {
3220 /* We yielded during a previous call. */
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003221 node = &ctx->cur_cafile_entry->node;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003222 }
3223
3224 while (node) {
3225 cafile_entry = ebmb_entry(node, struct cafile_entry, node);
3226 if (cafile_entry->type == CAFILE_CERT) {
3227 chunk_appendf(trash, "%s", cafile_entry->path);
3228
3229 chunk_appendf(trash, " - %d certificate(s)\n", get_certificate_count(cafile_entry));
3230 }
3231
3232 node = ebmb_next(node);
Willy Tarreaud0a06d52022-05-18 15:07:19 +02003233 if (applet_putchk(appctx, trash) == -1)
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003234 goto yield;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003235 }
3236
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003237 ctx->cur_cafile_entry = NULL;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003238 free_trash_chunk(trash);
3239 return 1;
3240yield:
3241
3242 free_trash_chunk(trash);
Willy Tarreau50c2f1e2022-05-04 19:26:59 +02003243 ctx->cur_cafile_entry = cafile_entry;
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003244 return 0; /* should come back */
3245}
3246
Remi Tricot-Le Bretonc3a84772021-03-25 18:13:57 +01003247/* parsing function of 'del ssl ca-file' */
3248static int cli_parse_del_cafile(char **args, char *payload, struct appctx *appctx, void *private)
3249{
3250 struct cafile_entry *cafile_entry;
3251 char *err = NULL;
3252 char *filename;
3253
3254 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
3255 return 1;
3256
3257 if (!*args[3])
3258 return cli_err(appctx, "'del ssl ca-file' expects a CA file name\n");
3259
3260 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3261 return cli_err(appctx, "Can't delete the CA file!\nOperations on certificates are currently locked!\n");
3262
3263 filename = args[3];
3264
Christopher Faulet1f08fa42022-05-31 18:06:30 +02003265 if (cafile_transaction.path && strcmp(cafile_transaction.path, filename) == 0) {
3266 memprintf(&err, "ongoing transaction for the CA file '%s'", filename);
3267 goto error;
3268 }
3269
Remi Tricot-Le Bretonc3a84772021-03-25 18:13:57 +01003270 cafile_entry = ssl_store_get_cafile_entry(filename, 0);
3271 if (!cafile_entry) {
3272 memprintf(&err, "CA file '%s' doesn't exist!\n", filename);
3273 goto error;
3274 }
3275
3276 if (!LIST_ISEMPTY(&cafile_entry->ckch_inst_link)) {
3277 memprintf(&err, "CA file '%s' in use, can't be deleted!\n", filename);
3278 goto error;
3279 }
3280
3281 /* Remove the cafile_entry from the tree */
3282 ebmb_delete(&cafile_entry->node);
3283 ssl_store_delete_cafile_entry(cafile_entry);
3284
3285 memprintf(&err, "CA file '%s' deleted!\n", filename);
3286
3287 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3288 return cli_dynmsg(appctx, LOG_NOTICE, err);
3289
3290error:
3291 memprintf(&err, "Can't remove the CA file: %s\n", err ? err : "");
3292 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3293 return cli_dynerr(appctx, err);
3294}
3295
Remi Tricot-Le Breton720e3b92021-04-26 11:00:42 +02003296/* parsing function of 'new ssl crl-file' */
3297static int cli_parse_new_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
3298{
3299 struct cafile_entry *cafile_entry;
3300 char *err = NULL;
3301 char *path;
3302
3303 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
3304 return 1;
3305
3306 if (!*args[3])
3307 return cli_err(appctx, "'new ssl crl-file' expects a filename\n");
3308
3309 path = args[3];
3310
3311 /* The operations on the CKCH architecture are locked so we can
3312 * manipulate ckch_store and ckch_inst */
3313 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
Remi Tricot-Le Breton444d7022022-05-05 17:18:40 +02003314 return cli_err(appctx, "Can't create a CRL file!\nOperations on certificates are currently locked!\n");
Remi Tricot-Le Breton720e3b92021-04-26 11:00:42 +02003315
3316 cafile_entry = ssl_store_get_cafile_entry(path, 0);
3317 if (cafile_entry) {
3318 memprintf(&err, "CRL file '%s' already exists!\n", path);
3319 goto error;
3320 }
3321
3322 cafile_entry = ssl_store_create_cafile_entry(path, NULL, CAFILE_CRL);
3323 if (!cafile_entry) {
3324 memprintf(&err, "%sCannot allocate memory!\n", err ? err : "");
3325 goto error;
3326 }
3327
3328 /* Add the newly created cafile_entry to the tree so that
3329 * any new ckch instance created from now can use it. */
3330 if (ssl_store_add_uncommitted_cafile_entry(cafile_entry))
3331 goto error;
3332
3333 memprintf(&err, "New CRL file created '%s'!\n", path);
3334
3335 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3336 return cli_dynmsg(appctx, LOG_NOTICE, err);
3337error:
3338 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3339 return cli_dynerr(appctx, err);
3340}
3341
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003342/* Parsing function of `set ssl crl-file` */
3343static int cli_parse_set_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
3344{
Christopher Faulet1f90f332022-06-03 16:34:30 +02003345 struct cafile_entry *old_crlfile_entry = NULL;
3346 struct cafile_entry *new_crlfile_entry = NULL;
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003347 char *err = NULL;
3348 int errcode = 0;
3349 struct buffer *buf;
3350
3351 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
3352 return 1;
3353
3354 if (!*args[3] || !payload)
Remi Tricot-Le Breton444d7022022-05-05 17:18:40 +02003355 return cli_err(appctx, "'set ssl crl-file expects a filename and CRLs as a payload\n");
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003356
3357 /* The operations on the CKCH architecture are locked so we can
3358 * manipulate ckch_store and ckch_inst */
3359 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3360 return cli_err(appctx, "Can't update the CRL file!\nOperations on certificates are currently locked!\n");
3361
3362 if ((buf = alloc_trash_chunk()) == NULL) {
3363 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
3364 errcode |= ERR_ALERT | ERR_FATAL;
3365 goto end;
3366 }
3367
3368 if (!chunk_strcpy(buf, args[3])) {
3369 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
3370 errcode |= ERR_ALERT | ERR_FATAL;
3371 goto end;
3372 }
3373
Christopher Fauletd6c66f02022-06-03 11:59:10 +02003374 old_crlfile_entry = NULL;
3375 new_crlfile_entry = NULL;
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003376
3377 /* if there is an ongoing transaction */
3378 if (crlfile_transaction.path) {
3379 /* if there is an ongoing transaction, check if this is the same file */
3380 if (strcmp(crlfile_transaction.path, buf->area) != 0) {
3381 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", crlfile_transaction.path, buf->area);
3382 errcode |= ERR_ALERT | ERR_FATAL;
3383 goto end;
3384 }
Christopher Fauletd6c66f02022-06-03 11:59:10 +02003385 old_crlfile_entry = crlfile_transaction.old_crlfile_entry;
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003386 }
3387 else {
3388 /* lookup for the certificate in the tree */
Christopher Fauletd6c66f02022-06-03 11:59:10 +02003389 old_crlfile_entry = ssl_store_get_cafile_entry(buf->area, 0);
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003390 }
3391
Christopher Fauletd6c66f02022-06-03 11:59:10 +02003392 if (!old_crlfile_entry) {
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003393 memprintf(&err, "%sCan't replace a CRL file which is not referenced by the configuration!\n",
3394 err ? err : "");
3395 errcode |= ERR_ALERT | ERR_FATAL;
3396 goto end;
3397 }
3398
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003399 /* Create a new cafile_entry without adding it to the cafile tree. */
Christopher Fauletd6c66f02022-06-03 11:59:10 +02003400 new_crlfile_entry = ssl_store_create_cafile_entry(old_crlfile_entry->path, NULL, CAFILE_CRL);
3401 if (!new_crlfile_entry) {
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003402 memprintf(&err, "%sCannot allocate memory!\n", err ? err : "");
3403 errcode |= ERR_ALERT | ERR_FATAL;
3404 goto end;
3405 }
3406
3407 /* Fill the new entry with the new CRL. */
William Lallemandd4774d32022-07-29 17:08:02 +02003408 if (ssl_store_load_ca_from_buf(new_crlfile_entry, payload, 0)) {
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003409 memprintf(&err, "%sInvalid payload\n", err ? err : "");
3410 errcode |= ERR_ALERT | ERR_FATAL;
3411 goto end;
3412 }
3413
3414 /* we succeed, we can save the crl in the transaction */
3415
Remi Tricot-Le Breton444d7022022-05-05 17:18:40 +02003416 /* if there wasn't a transaction, update the old CRL */
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003417 if (!crlfile_transaction.old_crlfile_entry) {
Christopher Fauletd6c66f02022-06-03 11:59:10 +02003418 crlfile_transaction.old_crlfile_entry = old_crlfile_entry;
3419 crlfile_transaction.path = old_crlfile_entry->path;
Remi Tricot-Le Breton444d7022022-05-05 17:18:40 +02003420 err = memprintf(&err, "transaction created for CRL %s!\n", crlfile_transaction.path);
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003421 } else {
Remi Tricot-Le Breton444d7022022-05-05 17:18:40 +02003422 err = memprintf(&err, "transaction updated for CRL %s!\n", crlfile_transaction.path);
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003423 }
3424
3425 /* free the previous CRL file if there was a transaction */
3426 ssl_store_delete_cafile_entry(crlfile_transaction.new_crlfile_entry);
3427
Christopher Fauletd6c66f02022-06-03 11:59:10 +02003428 crlfile_transaction.new_crlfile_entry = new_crlfile_entry;
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003429
3430 /* creates the SNI ctxs later in the IO handler */
3431
3432end:
3433 free_trash_chunk(buf);
3434
3435 if (errcode & ERR_CODE) {
Christopher Fauletd6c66f02022-06-03 11:59:10 +02003436 ssl_store_delete_cafile_entry(new_crlfile_entry);
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003437 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3438 return cli_dynerr(appctx, memprintf(&err, "%sCan't update %s!\n", err ? err : "", args[3]));
3439 } else {
3440
3441 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3442 return cli_dynmsg(appctx, LOG_NOTICE, err);
3443 }
3444}
3445
Willy Tarreaudec23dc2022-05-04 20:25:05 +02003446/* Parsing function of 'commit ssl crl-file'.
3447 * It uses a commit_cacrlfile_ctx that's also shared with "commit ssl ca-file".
3448 */
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003449static int cli_parse_commit_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
3450{
Willy Tarreaudec23dc2022-05-04 20:25:05 +02003451 struct commit_cacrlfile_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003452 char *err = NULL;
3453
3454 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
3455 return 1;
3456
3457 if (!*args[3])
3458 return cli_err(appctx, "'commit ssl ca-file expects a filename\n");
3459
3460 /* The operations on the CKCH architecture are locked so we can
3461 * manipulate ckch_store and ckch_inst */
3462 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3463 return cli_err(appctx, "Can't commit the CRL file!\nOperations on certificates are currently locked!\n");
3464
3465 if (!crlfile_transaction.path) {
3466 memprintf(&err, "No ongoing transaction! !\n");
3467 goto error;
3468 }
3469
3470 if (strcmp(crlfile_transaction.path, args[3]) != 0) {
3471 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to set '%s'\n", crlfile_transaction.path, args[3]);
3472 goto error;
3473 }
3474 /* init the appctx structure */
Willy Tarreau1d6dd802022-05-05 08:17:29 +02003475 ctx->state = CACRL_ST_INIT;
Christopher Fauletf814c4a2022-06-03 11:32:05 +02003476 ctx->next_ckchi_link = NULL;
Christopher Faulet6af2fc62022-06-03 11:42:38 +02003477 ctx->old_entry = crlfile_transaction.old_crlfile_entry;
3478 ctx->new_entry = crlfile_transaction.new_crlfile_entry;
Willy Tarreaudec23dc2022-05-04 20:25:05 +02003479 ctx->cafile_type = CAFILE_CRL;
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003480
3481 return 0;
3482
3483error:
3484
3485 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3486 err = memprintf(&err, "%sCan't commit %s!\n", err ? err : "", args[3]);
3487
3488 return cli_dynerr(appctx, err);
3489}
3490
3491
Willy Tarreaudec23dc2022-05-04 20:25:05 +02003492/* release function of the `commit ssl crl-file' command, free things and unlock the spinlock.
3493 * it uses a commit_cacrlfile_ctx that's the same as for "commit ssl ca-file".
3494 */
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003495static void cli_release_commit_crlfile(struct appctx *appctx)
3496{
Willy Tarreaudec23dc2022-05-04 20:25:05 +02003497 struct commit_cacrlfile_ctx *ctx = appctx->svcctx;
Christopher Faulet6af2fc62022-06-03 11:42:38 +02003498 struct cafile_entry *new_crlfile_entry = ctx->new_entry;
Willy Tarreaudec23dc2022-05-04 20:25:05 +02003499
Christopher Faulete9c3bd12022-05-31 17:51:06 +02003500 /* Remove the uncommitted cafile_entry from the tree. */
3501 if (new_crlfile_entry) {
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003502 ebmb_delete(&new_crlfile_entry->node);
3503 ssl_store_delete_cafile_entry(new_crlfile_entry);
3504 }
3505 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
Christopher Faulete9c3bd12022-05-31 17:51:06 +02003506 ha_free(&ctx->err);
Remi Tricot-Le Bretona51b3392021-04-20 17:38:14 +02003507}
3508
Remi Tricot-Le Breton720e3b92021-04-26 11:00:42 +02003509/* parsing function of 'del ssl crl-file' */
3510static int cli_parse_del_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
3511{
3512 struct cafile_entry *cafile_entry;
3513 char *err = NULL;
3514 char *filename;
3515
3516 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
3517 return 1;
3518
3519 if (!*args[3])
3520 return cli_err(appctx, "'del ssl crl-file' expects a CRL file name\n");
3521
3522 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3523 return cli_err(appctx, "Can't delete the CRL file!\nOperations on certificates are currently locked!\n");
3524
3525 filename = args[3];
3526
Christopher Faulet1f08fa42022-05-31 18:06:30 +02003527 if (crlfile_transaction.path && strcmp(crlfile_transaction.path, filename) == 0) {
3528 memprintf(&err, "ongoing transaction for the CRL file '%s'", filename);
3529 goto error;
3530 }
3531
Remi Tricot-Le Breton720e3b92021-04-26 11:00:42 +02003532 cafile_entry = ssl_store_get_cafile_entry(filename, 0);
3533 if (!cafile_entry) {
3534 memprintf(&err, "CRL file '%s' doesn't exist!\n", filename);
3535 goto error;
3536 }
3537 if (cafile_entry->type != CAFILE_CRL) {
3538 memprintf(&err, "'del ssl crl-file' does not work on CA files!\n");
3539 goto error;
3540 }
3541
3542 if (!LIST_ISEMPTY(&cafile_entry->ckch_inst_link)) {
3543 memprintf(&err, "CRL file '%s' in use, can't be deleted!\n", filename);
3544 goto error;
3545 }
3546
3547 /* Remove the cafile_entry from the tree */
3548 ebmb_delete(&cafile_entry->node);
3549 ssl_store_delete_cafile_entry(cafile_entry);
3550
3551 memprintf(&err, "CRL file '%s' deleted!\n", filename);
3552
3553 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3554 return cli_dynmsg(appctx, LOG_NOTICE, err);
3555
3556error:
3557 memprintf(&err, "Can't remove the CRL file: %s\n", err ? err : "");
3558 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3559 return cli_dynerr(appctx, err);
3560}
3561
Remi Tricot-Le Bretoneef8e7b2021-04-20 17:42:02 +02003562/* parsing function of 'abort ssl crl-file' */
3563static int cli_parse_abort_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
3564{
3565 char *err = NULL;
3566
3567 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
3568 return 1;
3569
3570 if (!*args[3])
3571 return cli_err(appctx, "'abort ssl crl-file' expects a filename\n");
3572
3573 /* The operations on the CKCH architecture are locked so we can
3574 * manipulate ckch_store and ckch_inst */
3575 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3576 return cli_err(appctx, "Can't abort!\nOperations on certificates are currently locked!\n");
3577
3578 if (!crlfile_transaction.path) {
3579 memprintf(&err, "No ongoing transaction!\n");
3580 goto error;
3581 }
3582
3583 if (strcmp(crlfile_transaction.path, args[3]) != 0) {
3584 memprintf(&err, "The ongoing transaction is about '%s' but you are trying to abort a transaction for '%s'\n", crlfile_transaction.path, args[3]);
3585 goto error;
3586 }
3587
3588 /* Only free the uncommitted cafile_entry here, because the SNI and instances were not generated yet */
3589 ssl_store_delete_cafile_entry(crlfile_transaction.new_crlfile_entry);
3590 crlfile_transaction.new_crlfile_entry = NULL;
3591 crlfile_transaction.old_crlfile_entry = NULL;
Christopher Faulet1e00c7e2022-05-31 18:10:19 +02003592 crlfile_transaction.path = NULL;
Remi Tricot-Le Bretoneef8e7b2021-04-20 17:42:02 +02003593
3594 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3595
3596 err = memprintf(&err, "Transaction aborted for certificate '%s'!\n", args[3]);
3597 return cli_dynmsg(appctx, LOG_NOTICE, err);
3598
3599error:
3600 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3601
3602 return cli_dynerr(appctx, err);
3603}
3604
Remi Tricot-Le Breton2a22e162021-03-16 11:19:33 +01003605
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003606/*
3607 * Display a Certificate Resignation List's information.
3608 * The information displayed is inspired by the output of 'openssl crl -in
3609 * crl.pem -text'.
3610 * Returns 0 in case of success.
3611 */
3612static int show_crl_detail(X509_CRL *crl, struct buffer *out)
3613{
3614 BIO *bio = NULL;
3615 struct buffer *tmp = alloc_trash_chunk();
3616 long version;
3617 X509_NAME *issuer;
3618 int write = -1;
Uriah Pollock3cbf09e2022-11-23 16:41:25 +01003619#ifndef USE_OPENSSL_WOLFSSL
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003620 STACK_OF(X509_REVOKED) *rev = NULL;
3621 X509_REVOKED *rev_entry = NULL;
3622 int i;
Uriah Pollock3cbf09e2022-11-23 16:41:25 +01003623#endif
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003624
3625 if (!tmp)
3626 return -1;
3627
3628 if ((bio = BIO_new(BIO_s_mem())) == NULL)
3629 goto end;
3630
3631 /* Version (as displayed by 'openssl crl') */
3632 version = X509_CRL_get_version(crl);
3633 chunk_appendf(out, "Version %ld\n", version + 1);
3634
3635 /* Signature Algorithm */
3636 chunk_appendf(out, "Signature Algorithm: %s\n", OBJ_nid2ln(X509_CRL_get_signature_nid(crl)));
3637
3638 /* Issuer */
3639 chunk_appendf(out, "Issuer: ");
3640 if ((issuer = X509_CRL_get_issuer(crl)) == NULL)
3641 goto end;
3642 if ((ssl_sock_get_dn_oneline(issuer, tmp)) == -1)
3643 goto end;
3644 *(tmp->area + tmp->data) = '\0';
3645 chunk_appendf(out, "%s\n", tmp->area);
3646
3647 /* Last Update */
3648 chunk_appendf(out, "Last Update: ");
3649 chunk_reset(tmp);
Remi Tricot-Le Bretond75b99e2021-05-17 11:45:55 +02003650 if (BIO_reset(bio) == -1)
3651 goto end;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003652 if (ASN1_TIME_print(bio, X509_CRL_get0_lastUpdate(crl)) == 0)
3653 goto end;
3654 write = BIO_read(bio, tmp->area, tmp->size-1);
3655 tmp->area[write] = '\0';
3656 chunk_appendf(out, "%s\n", tmp->area);
3657
3658
3659 /* Next Update */
3660 chunk_appendf(out, "Next Update: ");
3661 chunk_reset(tmp);
Remi Tricot-Le Bretond75b99e2021-05-17 11:45:55 +02003662 if (BIO_reset(bio) == -1)
3663 goto end;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003664 if (ASN1_TIME_print(bio, X509_CRL_get0_nextUpdate(crl)) == 0)
3665 goto end;
3666 write = BIO_read(bio, tmp->area, tmp->size-1);
3667 tmp->area[write] = '\0';
3668 chunk_appendf(out, "%s\n", tmp->area);
3669
Uriah Pollock3cbf09e2022-11-23 16:41:25 +01003670#ifndef USE_OPENSSL_WOLFSSL
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003671 /* Revoked Certificates */
3672 rev = X509_CRL_get_REVOKED(crl);
3673 if (sk_X509_REVOKED_num(rev) > 0)
3674 chunk_appendf(out, "Revoked Certificates:\n");
3675 else
3676 chunk_appendf(out, "No Revoked Certificates.\n");
3677
3678 for (i = 0; i < sk_X509_REVOKED_num(rev); i++) {
3679 rev_entry = sk_X509_REVOKED_value(rev, i);
3680
3681 /* Serial Number and Revocation Date */
Remi Tricot-Le Bretond75b99e2021-05-17 11:45:55 +02003682 if (BIO_reset(bio) == -1)
3683 goto end;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003684 BIO_printf(bio , " Serial Number: ");
Remi Tricot-Le Breton18c7d832021-05-17 18:38:34 +02003685 i2a_ASN1_INTEGER(bio, (ASN1_INTEGER*)X509_REVOKED_get0_serialNumber(rev_entry));
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003686 BIO_printf(bio, "\n Revocation Date: ");
Remi Tricot-Le Bretona6b27842021-05-18 10:06:00 +02003687 if (ASN1_TIME_print(bio, X509_REVOKED_get0_revocationDate(rev_entry)) == 0)
3688 goto end;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003689 BIO_printf(bio, "\n");
3690
3691 write = BIO_read(bio, tmp->area, tmp->size-1);
3692 tmp->area[write] = '\0';
3693 chunk_appendf(out, "%s", tmp->area);
3694 }
Uriah Pollock3cbf09e2022-11-23 16:41:25 +01003695#endif /* not USE_OPENSSL_WOLFSSL */
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003696
3697end:
3698 free_trash_chunk(tmp);
3699 if (bio)
3700 BIO_free(bio);
3701
3702 return 0;
3703}
3704
Willy Tarreau821c3b02022-05-04 15:47:39 +02003705/* IO handler of details "show ssl crl-file <filename[:index]>".
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003706 * It uses show_crlfile_ctx and the global
3707 * crlfile_transaction.new_cafile_entry in read-only.
Willy Tarreau821c3b02022-05-04 15:47:39 +02003708 */
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003709static int cli_io_handler_show_crlfile_detail(struct appctx *appctx)
3710{
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003711 struct show_crlfile_ctx *ctx = appctx->svcctx;
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003712 struct cafile_entry *cafile_entry = ctx->cafile_entry;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003713 struct buffer *out = alloc_trash_chunk();
3714 int i;
3715 X509_CRL *crl;
3716 STACK_OF(X509_OBJECT) *objs;
3717 int retval = 0;
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003718 int index = ctx->index;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003719
3720 if (!out)
3721 goto end_no_putchk;
3722
3723 chunk_appendf(out, "Filename: ");
3724 if (cafile_entry == crlfile_transaction.new_crlfile_entry)
3725 chunk_appendf(out, "*");
3726 chunk_appendf(out, "%s\n", cafile_entry->path);
3727
3728 chunk_appendf(out, "Status: ");
3729 if (!cafile_entry->ca_store)
3730 chunk_appendf(out, "Empty\n");
3731 else if (LIST_ISEMPTY(&cafile_entry->ckch_inst_link))
3732 chunk_appendf(out, "Unused\n");
3733 else
3734 chunk_appendf(out, "Used\n");
3735
3736 if (!cafile_entry->ca_store)
3737 goto end;
3738
3739 objs = X509_STORE_get0_objects(cafile_entry->ca_store);
3740 for (i = 0; i < sk_X509_OBJECT_num(objs); i++) {
3741 crl = X509_OBJECT_get0_X509_CRL(sk_X509_OBJECT_value(objs, i));
3742 if (!crl)
3743 continue;
3744
3745 /* CRL indexes start at 1 on the CLI output. */
3746 if (index && index-1 != i)
3747 continue;
3748
Remi Tricot-Le Bretone8041fe2022-04-05 16:44:21 +02003749 chunk_appendf(out, " \nCertificate Revocation List #%d:\n", i+1);
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003750 retval = show_crl_detail(crl, out);
3751 if (retval < 0)
3752 goto end_no_putchk;
3753 else if (retval || index)
3754 goto end;
3755 }
3756
3757end:
Willy Tarreaud0a06d52022-05-18 15:07:19 +02003758 if (applet_putchk(appctx, out) == -1)
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003759 goto yield;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003760
3761end_no_putchk:
3762 free_trash_chunk(out);
3763 return 1;
3764yield:
3765 free_trash_chunk(out);
3766 return 0; /* should come back */
3767}
3768
Willy Tarreau821c3b02022-05-04 15:47:39 +02003769/* parsing function for 'show ssl crl-file [crlfile[:index]]'.
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003770 * It sets the context to a show_crlfile_ctx, and the global
Willy Tarreau821c3b02022-05-04 15:47:39 +02003771 * cafile_transaction.new_crlfile_entry under the ckch_lock.
3772 */
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003773static int cli_parse_show_crlfile(char **args, char *payload, struct appctx *appctx, void *private)
3774{
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003775 struct show_crlfile_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003776 struct cafile_entry *cafile_entry;
3777 long index = 0;
3778 char *colons;
3779 char *err = NULL;
3780
3781 if (!cli_has_level(appctx, ACCESS_LVL_OPER))
3782 return cli_err(appctx, "Can't allocate memory!\n");
3783
3784 /* The operations on the CKCH architecture are locked so we can
3785 * manipulate ckch_store and ckch_inst */
3786 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
3787 return cli_err(appctx, "Can't show!\nOperations on certificates are currently locked!\n");
3788
3789 /* check if there is a certificate to lookup */
3790 if (*args[3]) {
3791
3792 /* Look for an optional index after the CRL file name */
3793 colons = strchr(args[3], ':');
3794 if (colons) {
3795 char *endptr;
3796
3797 index = strtol(colons + 1, &endptr, 10);
3798 /* Indexes start at 1 */
3799 if (colons + 1 == endptr || *endptr != '\0' || index <= 0) {
3800 memprintf(&err, "wrong CRL index after colons in '%s'!", args[3]);
3801 goto error;
3802 }
3803 *colons = '\0';
3804 }
3805
3806 if (*args[3] == '*') {
3807 if (!crlfile_transaction.new_crlfile_entry)
3808 goto error;
3809
3810 cafile_entry = crlfile_transaction.new_crlfile_entry;
3811
3812 if (strcmp(args[3] + 1, cafile_entry->path) != 0)
3813 goto error;
3814
3815 } else {
3816 /* Get the "original" cafile_entry and not the
3817 * uncommitted one if it exists. */
3818 if ((cafile_entry = ssl_store_get_cafile_entry(args[3], 1)) == NULL || cafile_entry->type != CAFILE_CRL)
3819 goto error;
3820 }
3821
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003822 ctx->cafile_entry = cafile_entry;
3823 ctx->index = index;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003824 /* use the IO handler that shows details */
3825 appctx->io_handler = cli_io_handler_show_crlfile_detail;
3826 }
3827
3828 return 0;
3829
3830error:
3831 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3832 if (err)
3833 return cli_dynerr(appctx, err);
Remi Tricot-Le Breton444d7022022-05-05 17:18:40 +02003834 return cli_err(appctx, "Can't display the CRL file : Not found!\n");
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003835}
3836
3837/* IO handler of "show ssl crl-file". The command taking a specific CRL file name
3838 * is managed in cli_io_handler_show_crlfile_detail. */
3839static int cli_io_handler_show_crlfile(struct appctx *appctx)
3840{
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003841 struct show_crlfile_ctx *ctx = appctx->svcctx;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003842 struct buffer *trash = alloc_trash_chunk();
3843 struct ebmb_node *node;
Christopher Faulet88041b32022-06-03 16:26:56 +02003844 struct cafile_entry *cafile_entry = NULL;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003845
3846 if (trash == NULL)
3847 return 1;
3848
Christopher Faulet9a99e542022-06-03 10:32:18 +02003849 if (!ctx->old_crlfile_entry && crlfile_transaction.old_crlfile_entry) {
3850 chunk_appendf(trash, "# transaction\n");
3851 chunk_appendf(trash, "*%s\n", crlfile_transaction.old_crlfile_entry->path);
3852 if (applet_putchk(appctx, trash) == -1)
3853 goto yield;
3854 ctx->old_crlfile_entry = crlfile_transaction.old_crlfile_entry;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003855 }
3856
3857 /* First time in this io_handler. */
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003858 if (!ctx->cafile_entry) {
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003859 chunk_appendf(trash, "# filename\n");
3860 node = ebmb_first(&cafile_tree);
3861 } else {
3862 /* We yielded during a previous call. */
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003863 node = &ctx->cafile_entry->node;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003864 }
3865
3866 while (node) {
3867 cafile_entry = ebmb_entry(node, struct cafile_entry, node);
3868 if (cafile_entry->type == CAFILE_CRL) {
3869 chunk_appendf(trash, "%s\n", cafile_entry->path);
3870 }
3871
3872 node = ebmb_next(node);
Willy Tarreaud0a06d52022-05-18 15:07:19 +02003873 if (applet_putchk(appctx, trash) == -1)
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003874 goto yield;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003875 }
3876
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003877 ctx->cafile_entry = NULL;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003878 free_trash_chunk(trash);
3879 return 1;
3880yield:
3881
3882 free_trash_chunk(trash);
Willy Tarreauf3e8b3e2022-05-04 19:38:57 +02003883 ctx->cafile_entry = cafile_entry;
Remi Tricot-Le Breton51e28b62021-04-20 17:58:01 +02003884 return 0; /* should come back */
3885}
3886
3887
3888/* release function of the 'show ssl crl-file' command */
3889static void cli_release_show_crlfile(struct appctx *appctx)
3890{
3891 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
3892}
3893
3894
William Lallemandee8530c2020-06-23 18:19:42 +02003895void ckch_deinit()
3896{
3897 struct eb_node *node, *next;
3898 struct ckch_store *store;
William Lallemandb0c48272022-04-26 15:44:53 +02003899 struct ebmb_node *canode;
William Lallemandee8530c2020-06-23 18:19:42 +02003900
William Lallemandb0c48272022-04-26 15:44:53 +02003901 /* deinit the ckch stores */
William Lallemandee8530c2020-06-23 18:19:42 +02003902 node = eb_first(&ckchs_tree);
3903 while (node) {
3904 next = eb_next(node);
3905 store = ebmb_entry(node, struct ckch_store, node);
3906 ckch_store_free(store);
3907 node = next;
3908 }
William Lallemandb0c48272022-04-26 15:44:53 +02003909
3910 /* deinit the ca-file store */
3911 canode = ebmb_first(&cafile_tree);
3912 while (canode) {
3913 struct cafile_entry *entry = NULL;
3914
3915 entry = ebmb_entry(canode, struct cafile_entry, node);
3916 canode = ebmb_next(canode);
William Lallemand946580e2022-08-29 18:36:18 +02003917 ebmb_delete(&entry->node);
William Lallemandb0c48272022-04-26 15:44:53 +02003918 ssl_store_delete_cafile_entry(entry);
3919 }
William Lallemandee8530c2020-06-23 18:19:42 +02003920}
William Lallemandda8584c2020-05-14 10:14:37 +02003921
3922/* register cli keywords */
3923static struct cli_kw_list cli_kws = {{ },{
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01003924 { { "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 },
3925 { { "set", "ssl", "cert", NULL }, "set ssl cert <certfile> <payload> : replace a certificate file", cli_parse_set_cert, NULL, NULL },
3926 { { "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 },
3927 { { "abort", "ssl", "cert", NULL }, "abort ssl cert <certfile> : abort a transaction for a certificate file", cli_parse_abort_cert, NULL, NULL },
3928 { { "del", "ssl", "cert", NULL }, "del ssl cert <certfile> : delete an unused certificate file", cli_parse_del_cert, NULL, NULL },
3929 { { "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 },
3930
Amaury Denoyelleb11ad9e2021-05-21 11:01:10 +02003931 { { "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 },
William Lallemand62c0b992022-07-29 17:50:58 +02003932 { { "add", "ssl", "ca-file", NULL }, "add ssl ca-file <cafile> <payload> : add a certificate into the CA file", cli_parse_set_cafile, NULL, NULL },
Remi Tricot-Le Bretona32a68b2021-02-24 17:35:43 +01003933 { { "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 +02003934 { { "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 +01003935 { { "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 +01003936 { { "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 +01003937 { { "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 +02003938
William Lallemand3ba9c302024-03-05 14:49:17 +01003939 { { "new", "ssl", "crl-file", NULL }, "new ssl crl-file <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 +02003940 { { "set", "ssl", "crl-file", NULL }, "set ssl crl-file <crlfile> <payload> : replace a CRL file", cli_parse_set_crlfile, NULL, NULL },
3941 { { "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 +02003942 { { "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 +02003943 { { "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 +02003944 { { "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 +02003945 { { NULL }, NULL, NULL, NULL }
3946}};
3947
3948INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws);
3949