blob: 14d47ebcf03fced248b9b9132ee71e7eee29e1ce [file] [log] [blame]
William Lallemand6e9556b2020-05-12 17:52:44 +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 */
Willy Tarreaub2551052020-06-09 09:07:15 +020011#include <sys/stat.h>
12#include <sys/types.h>
William Lallemand6e9556b2020-05-12 17:52:44 +020013
Willy Tarreaub2551052020-06-09 09:07:15 +020014#include <dirent.h>
William Lallemand212e9932020-05-18 08:33:09 +020015#include <errno.h>
William Lallemand6e9556b2020-05-12 17:52:44 +020016#include <stdlib.h>
17#include <string.h>
Willy Tarreauaeed4a82020-06-04 22:01:04 +020018#include <syslog.h>
Willy Tarreaub2551052020-06-09 09:07:15 +020019
20#include <import/ebpttree.h>
21#include <import/ebsttree.h>
William Lallemand6e9556b2020-05-12 17:52:44 +020022
Willy Tarreaua2fcca02022-05-05 11:53:23 +020023#include <haproxy/applet.h>
Willy Tarreauf1d32c42020-06-04 21:07:02 +020024#include <haproxy/channel.h>
Willy Tarreau83487a82020-06-04 20:19:54 +020025#include <haproxy/cli.h>
Christopher Faulet908628c2022-03-25 16:43:49 +010026#include <haproxy/conn_stream.h>
27#include <haproxy/cs_utils.h>
Willy Tarreau8d366972020-05-27 16:10:29 +020028#include <haproxy/errors.h>
Willy Tarreau47d7f902020-06-04 14:25:47 +020029#include <haproxy/ssl_ckch.h>
Willy Tarreau52d88722020-06-04 14:29:23 +020030#include <haproxy/ssl_crtlist.h>
Willy Tarreau209108d2020-06-04 20:30:20 +020031#include <haproxy/ssl_sock.h>
Willy Tarreau48fbcae2020-06-03 18:09:46 +020032#include <haproxy/tools.h>
William Lallemand6e9556b2020-05-12 17:52:44 +020033
Willy Tarreaua2fcca02022-05-05 11:53:23 +020034/* CLI context for "show ssl crt-list" or "dump ssl crt-list" */
35struct show_crtlist_ctx {
36 struct ebmb_node *crtlist_node; /* ebmb_node for the current crtlist */
37 struct crtlist_entry *entry; /* current entry */
38 int mode; /* 'd' for dump, 's' for show */
39};
William Lallemand6e9556b2020-05-12 17:52:44 +020040
William Lallemand6e9556b2020-05-12 17:52:44 +020041/* release ssl bind conf */
42void ssl_sock_free_ssl_conf(struct ssl_bind_conf *conf)
43{
44 if (conf) {
45#if defined(OPENSSL_NPN_NEGOTIATED) && !defined(OPENSSL_NO_NEXTPROTONEG)
Willy Tarreau61cfdf42021-02-20 10:46:51 +010046 ha_free(&conf->npn_str);
William Lallemand6e9556b2020-05-12 17:52:44 +020047#endif
48#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
Willy Tarreau61cfdf42021-02-20 10:46:51 +010049 ha_free(&conf->alpn_str);
William Lallemand6e9556b2020-05-12 17:52:44 +020050#endif
Willy Tarreau61cfdf42021-02-20 10:46:51 +010051 ha_free(&conf->ca_file);
52 ha_free(&conf->ca_verify_file);
53 ha_free(&conf->crl_file);
54 ha_free(&conf->ciphers);
Ilya Shipitsinf34ed0b2020-11-21 14:37:34 +050055#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES
Willy Tarreau61cfdf42021-02-20 10:46:51 +010056 ha_free(&conf->ciphersuites);
William Lallemand6e9556b2020-05-12 17:52:44 +020057#endif
Willy Tarreau61cfdf42021-02-20 10:46:51 +010058 ha_free(&conf->curves);
59 ha_free(&conf->ecdhe);
William Lallemand6e9556b2020-05-12 17:52:44 +020060 }
61}
62
William Lallemand82f2d2f2020-09-10 19:06:43 +020063/*
64 * Allocate and copy a ssl_bind_conf structure
65 */
66struct ssl_bind_conf *crtlist_dup_ssl_conf(struct ssl_bind_conf *src)
67{
68 struct ssl_bind_conf *dst;
69
70 if (!src)
71 return NULL;
72
73 dst = calloc(1, sizeof(*dst));
74 if (!dst)
75 return NULL;
76
77#if defined(OPENSSL_NPN_NEGOTIATED) && !defined(OPENSSL_NO_NEXTPROTONEG)
78 if (src->npn_str) {
79 dst->npn_str = strdup(src->npn_str);
80 if (!dst->npn_str)
81 goto error;
82 }
83#endif
84#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
85 if (src->alpn_str) {
86 dst->alpn_str = strdup(src->alpn_str);
87 if (!dst->alpn_str)
88 goto error;
89 }
90#endif
91 if (src->ca_file) {
92 dst->ca_file = strdup(src->ca_file);
93 if (!dst->ca_file)
94 goto error;
95 }
96 if (src->ca_verify_file) {
97 dst->ca_verify_file = strdup(src->ca_verify_file);
98 if (!dst->ca_verify_file)
99 goto error;
100 }
101 if (src->crl_file) {
102 dst->crl_file = strdup(src->crl_file);
103 if (!dst->crl_file)
104 goto error;
105 }
106 if (src->ciphers) {
107 dst->ciphers = strdup(src->ciphers);
108 if (!dst->ciphers)
109 goto error;
110 }
Ilya Shipitsinf34ed0b2020-11-21 14:37:34 +0500111#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES
William Lallemand82f2d2f2020-09-10 19:06:43 +0200112 if (src->ciphersuites) {
113 dst->ciphersuites = strdup(src->ciphersuites);
114 if (!dst->ciphersuites)
115 goto error;
116 }
117#endif
118 if (src->curves) {
119 dst->curves = strdup(src->curves);
120 if (!dst->curves)
121 goto error;
122 }
123 if (src->ecdhe) {
124 dst->ecdhe = strdup(src->ecdhe);
125 if (!dst->ecdhe)
126 goto error;
127 }
128 return dst;
129
130error:
131 ssl_sock_free_ssl_conf(dst);
132 free(dst);
133
134 return NULL;
135}
William Lallemand6e9556b2020-05-12 17:52:44 +0200136
137/* free sni filters */
138void crtlist_free_filters(char **args)
139{
140 int i;
141
142 if (!args)
143 return;
144
145 for (i = 0; args[i]; i++)
146 free(args[i]);
147
148 free(args);
149}
150
151/* Alloc and duplicate a char ** array */
152char **crtlist_dup_filters(char **args, int fcount)
153{
154 char **dst;
155 int i;
156
157 if (fcount == 0)
158 return NULL;
159
160 dst = calloc(fcount + 1, sizeof(*dst));
161 if (!dst)
162 return NULL;
163
164 for (i = 0; i < fcount; i++) {
165 dst[i] = strdup(args[i]);
166 if (!dst[i])
167 goto error;
168 }
169 return dst;
170
171error:
172 crtlist_free_filters(dst);
173 return NULL;
174}
175
176/*
177 * Detach and free a crtlist_entry.
178 * Free the filters, the ssl_conf and call ckch_inst_free() for each ckch_inst
179 */
180void crtlist_entry_free(struct crtlist_entry *entry)
181{
182 struct ckch_inst *inst, *inst_s;
183
184 if (entry == NULL)
185 return;
186
187 ebpt_delete(&entry->node);
Willy Tarreau2b718102021-04-21 07:32:39 +0200188 LIST_DELETE(&entry->by_crtlist);
189 LIST_DELETE(&entry->by_ckch_store);
William Lallemand6e9556b2020-05-12 17:52:44 +0200190 crtlist_free_filters(entry->filters);
191 ssl_sock_free_ssl_conf(entry->ssl_conf);
192 free(entry->ssl_conf);
193 list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_crtlist_entry) {
194 ckch_inst_free(inst);
195 }
196 free(entry);
197}
William Lallemand5622c452020-09-10 19:08:49 +0200198/*
199 * Duplicate a crt_list entry and its content (ssl_conf, filters/fcount)
200 * Return a pointer to the new entry
201 */
202struct crtlist_entry *crtlist_entry_dup(struct crtlist_entry *src)
203{
204 struct crtlist_entry *entry;
205
206 if (src == NULL)
207 return NULL;
208
209 entry = crtlist_entry_new();
210 if (entry == NULL)
211 return NULL;
212
213 if (src->filters) {
214 entry->filters = crtlist_dup_filters(src->filters, src->fcount);
215 if (!entry->filters)
216 goto error;
217 }
218 entry->fcount = src->fcount;
219 if (src->ssl_conf) {
220 entry->ssl_conf = crtlist_dup_ssl_conf(src->ssl_conf);
221 if (!entry->ssl_conf)
222 goto error;
223 }
224 entry->crtlist = src->crtlist;
225
226 return entry;
227
228error:
229
230 crtlist_free_filters(entry->filters);
231 ssl_sock_free_ssl_conf(entry->ssl_conf);
232 free(entry->ssl_conf);
233 free(entry);
234
235 return NULL;
236}
William Lallemand6e9556b2020-05-12 17:52:44 +0200237
238/*
239 * Allocate and initialize a crtlist_entry
240 */
241struct crtlist_entry *crtlist_entry_new()
242{
243 struct crtlist_entry *entry;
244
245 entry = calloc(1, sizeof(*entry));
246 if (entry == NULL)
247 return NULL;
248
249 LIST_INIT(&entry->ckch_inst);
250
Willy Tarreau2b718102021-04-21 07:32:39 +0200251 /* initialize the nodes so we can LIST_DELETE in any cases */
William Lallemand6e9556b2020-05-12 17:52:44 +0200252 LIST_INIT(&entry->by_crtlist);
253 LIST_INIT(&entry->by_ckch_store);
254
255 return entry;
256}
257
258/* Free a crtlist, from the crt_entry to the content of the ssl_conf */
259void crtlist_free(struct crtlist *crtlist)
260{
261 struct crtlist_entry *entry, *s_entry;
William Lallemand6a3168a2020-06-23 11:43:35 +0200262 struct bind_conf_list *bind_conf_node;
William Lallemand6e9556b2020-05-12 17:52:44 +0200263
264 if (crtlist == NULL)
265 return;
266
William Lallemand6a3168a2020-06-23 11:43:35 +0200267 bind_conf_node = crtlist->bind_conf;
268 while (bind_conf_node) {
269 struct bind_conf_list *next = bind_conf_node->next;
270 free(bind_conf_node);
271 bind_conf_node = next;
272 }
273
William Lallemand6e9556b2020-05-12 17:52:44 +0200274 list_for_each_entry_safe(entry, s_entry, &crtlist->ord_entries, by_crtlist) {
275 crtlist_entry_free(entry);
276 }
277 ebmb_delete(&crtlist->node);
278 free(crtlist);
279}
280
281/* Alloc and initialize a struct crtlist
282 * <filename> is the key of the ebmb_node
283 * <unique> initialize the list of entries to be unique (1) or not (0)
284 */
285struct crtlist *crtlist_new(const char *filename, int unique)
286{
287 struct crtlist *newlist;
288
289 newlist = calloc(1, sizeof(*newlist) + strlen(filename) + 1);
290 if (newlist == NULL)
291 return NULL;
292
293 memcpy(newlist->node.key, filename, strlen(filename) + 1);
294 if (unique)
295 newlist->entries = EB_ROOT_UNIQUE;
296 else
297 newlist->entries = EB_ROOT;
298
299 LIST_INIT(&newlist->ord_entries);
300
301 return newlist;
302}
303
304/*
305 * Read a single crt-list line. /!\ alter the <line> string.
306 * Fill <crt_path> and <crtlist_entry>
307 * <crtlist_entry> must be alloc and free by the caller
308 * <crtlist_entry->ssl_conf> is alloc by the function
309 * <crtlist_entry->filters> is alloc by the function
310 * <crt_path> is a ptr in <line>
311 * Return an error code
312 */
Remi Tricot-Le Bretonfb00f312021-03-23 16:41:53 +0100313int crtlist_parse_line(char *line, char **crt_path, struct crtlist_entry *entry, const char *file, int linenum, int from_cli, char **err)
William Lallemand6e9556b2020-05-12 17:52:44 +0200314{
315 int cfgerr = 0;
316 int arg, newarg, cur_arg, i, ssl_b = 0, ssl_e = 0;
317 char *end;
318 char *args[MAX_CRT_ARGS + 1];
319 struct ssl_bind_conf *ssl_conf = NULL;
320
321 if (!line || !crt_path || !entry)
322 return ERR_ALERT | ERR_FATAL;
323
324 end = line + strlen(line);
325 if (end-line >= CRT_LINESIZE-1 && *(end-1) != '\n') {
326 /* Check if we reached the limit and the last char is not \n.
327 * Watch out for the last line without the terminating '\n'!
328 */
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200329 memprintf(err, "parsing [%s:%d]: line too long, limit is %d characters",
330 file, linenum, CRT_LINESIZE-1);
William Lallemand6e9556b2020-05-12 17:52:44 +0200331 cfgerr |= ERR_ALERT | ERR_FATAL;
332 goto error;
333 }
334 arg = 0;
335 newarg = 1;
336 while (*line) {
337 if (isspace((unsigned char)*line)) {
338 newarg = 1;
339 *line = 0;
340 } else if (*line == '[') {
341 if (ssl_b) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200342 memprintf(err, "parsing [%s:%d]: too many '['", file, linenum);
William Lallemand6e9556b2020-05-12 17:52:44 +0200343 cfgerr |= ERR_ALERT | ERR_FATAL;
344 goto error;
345 }
346 if (!arg) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200347 memprintf(err, "parsing [%s:%d]: file must start with a cert", file, linenum);
William Lallemand6e9556b2020-05-12 17:52:44 +0200348 cfgerr |= ERR_ALERT | ERR_FATAL;
349 goto error;
350 }
351 ssl_b = arg;
352 newarg = 1;
353 *line = 0;
354 } else if (*line == ']') {
355 if (ssl_e) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200356 memprintf(err, "parsing [%s:%d]: too many ']'", file, linenum);
William Lallemand6e9556b2020-05-12 17:52:44 +0200357 cfgerr |= ERR_ALERT | ERR_FATAL;
358 goto error;
359 }
360 if (!ssl_b) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200361 memprintf(err, "parsing [%s:%d]: missing '['", file, linenum);
William Lallemand6e9556b2020-05-12 17:52:44 +0200362 cfgerr |= ERR_ALERT | ERR_FATAL;
363 goto error;
364 }
365 ssl_e = arg;
366 newarg = 1;
367 *line = 0;
368 } else if (newarg) {
369 if (arg == MAX_CRT_ARGS) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200370 memprintf(err, "parsing [%s:%d]: too many args ", file, linenum);
William Lallemand6e9556b2020-05-12 17:52:44 +0200371 cfgerr |= ERR_ALERT | ERR_FATAL;
372 goto error;
373 }
374 newarg = 0;
375 args[arg++] = line;
376 }
377 line++;
378 }
379 args[arg++] = line;
380
381 /* empty line */
382 if (!*args[0]) {
383 cfgerr |= ERR_NONE;
384 goto error;
385 }
386
387 *crt_path = args[0];
388
389 if (ssl_b) {
390 ssl_conf = calloc(1, sizeof *ssl_conf);
391 if (!ssl_conf) {
392 memprintf(err, "not enough memory!");
393 cfgerr |= ERR_ALERT | ERR_FATAL;
394 goto error;
395 }
396 }
Remi Tricot-Le Bretonfb00f312021-03-23 16:41:53 +0100397
William Lallemand6e9556b2020-05-12 17:52:44 +0200398 cur_arg = ssl_b ? ssl_b : 1;
399 while (cur_arg < ssl_e) {
400 newarg = 0;
401 for (i = 0; ssl_bind_kws[i].kw != NULL; i++) {
402 if (strcmp(ssl_bind_kws[i].kw, args[cur_arg]) == 0) {
403 newarg = 1;
Remi Tricot-Le Bretonfb00f312021-03-23 16:41:53 +0100404 cfgerr |= ssl_bind_kws[i].parse(args, cur_arg, NULL, ssl_conf, from_cli, err);
William Lallemand6e9556b2020-05-12 17:52:44 +0200405 if (cur_arg + 1 + ssl_bind_kws[i].skip > ssl_e) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200406 memprintf(err, "parsing [%s:%d]: ssl args out of '[]' for %s",
407 file, linenum, args[cur_arg]);
William Lallemand6e9556b2020-05-12 17:52:44 +0200408 cfgerr |= ERR_ALERT | ERR_FATAL;
409 goto error;
410 }
411 cur_arg += 1 + ssl_bind_kws[i].skip;
412 break;
413 }
414 }
415 if (!cfgerr && !newarg) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200416 memprintf(err, "parsing [%s:%d]: unknown ssl keyword %s",
417 file, linenum, args[cur_arg]);
William Lallemand6e9556b2020-05-12 17:52:44 +0200418 cfgerr |= ERR_ALERT | ERR_FATAL;
419 goto error;
420 }
421 }
422 entry->linenum = linenum;
423 entry->ssl_conf = ssl_conf;
424 entry->filters = crtlist_dup_filters(&args[cur_arg], arg - cur_arg - 1);
425 entry->fcount = arg - cur_arg - 1;
426
427 return cfgerr;
428
429error:
430 crtlist_free_filters(entry->filters);
431 entry->filters = NULL;
432 ssl_sock_free_ssl_conf(entry->ssl_conf);
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100433 ha_free(&entry->ssl_conf);
William Lallemand6e9556b2020-05-12 17:52:44 +0200434 return cfgerr;
435}
436
437
438
439/* This function parse a crt-list file and store it in a struct crtlist, each line is a crtlist_entry structure
440 * Fill the <crtlist> argument with a pointer to a new crtlist struct
441 *
442 * This function tries to open and store certificate files.
443 */
444int crtlist_parse_file(char *file, struct bind_conf *bind_conf, struct proxy *curproxy, struct crtlist **crtlist, char **err)
445{
446 struct crtlist *newlist;
447 struct crtlist_entry *entry = NULL;
448 char thisline[CRT_LINESIZE];
William Lallemand6e9556b2020-05-12 17:52:44 +0200449 FILE *f;
450 struct stat buf;
451 int linenum = 0;
452 int cfgerr = 0;
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200453 int missing_lf = -1;
William Lallemand6e9556b2020-05-12 17:52:44 +0200454
455 if ((f = fopen(file, "r")) == NULL) {
456 memprintf(err, "cannot open file '%s' : %s", file, strerror(errno));
457 return ERR_ALERT | ERR_FATAL;
458 }
459
460 newlist = crtlist_new(file, 0);
461 if (newlist == NULL) {
462 memprintf(err, "Not enough memory!");
463 cfgerr |= ERR_ALERT | ERR_FATAL;
464 goto error;
465 }
466
467 while (fgets(thisline, sizeof(thisline), f) != NULL) {
468 char *end;
469 char *line = thisline;
470 char *crt_path;
William Lallemand86c2dd62020-11-20 14:23:38 +0100471 char path[MAXPATHLEN+1];
William Lallemand6e9556b2020-05-12 17:52:44 +0200472 struct ckch_store *ckchs;
William Lallemand77e1c6f2020-11-20 18:26:09 +0100473 int found = 0;
William Lallemand6e9556b2020-05-12 17:52:44 +0200474
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200475 if (missing_lf != -1) {
476 memprintf(err, "parsing [%s:%d]: Stray NUL character at position %d.\n",
477 file, linenum, (missing_lf + 1));
478 cfgerr |= ERR_ALERT | ERR_FATAL;
479 missing_lf = -1;
480 break;
481 }
482
William Lallemand6e9556b2020-05-12 17:52:44 +0200483 linenum++;
484 end = line + strlen(line);
485 if (end-line == sizeof(thisline)-1 && *(end-1) != '\n') {
486 /* Check if we reached the limit and the last char is not \n.
487 * Watch out for the last line without the terminating '\n'!
488 */
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200489 memprintf(err, "parsing [%s:%d]: line too long, limit is %d characters",
490 file, linenum, (int)sizeof(thisline)-1);
William Lallemand6e9556b2020-05-12 17:52:44 +0200491 cfgerr |= ERR_ALERT | ERR_FATAL;
492 break;
493 }
494
495 if (*line == '#' || *line == '\n' || *line == '\r')
496 continue;
497
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200498 if (end > line && *(end-1) == '\n') {
499 /* kill trailing LF */
500 *(end - 1) = 0;
501 }
502 else {
503 /* mark this line as truncated */
504 missing_lf = end - line;
505 }
506
William Lallemand6e9556b2020-05-12 17:52:44 +0200507 entry = crtlist_entry_new();
508 if (entry == NULL) {
509 memprintf(err, "Not enough memory!");
510 cfgerr |= ERR_ALERT | ERR_FATAL;
511 goto error;
512 }
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200513
Remi Tricot-Le Bretonfb00f312021-03-23 16:41:53 +0100514 cfgerr |= crtlist_parse_line(thisline, &crt_path, entry, file, linenum, 0, err);
William Lallemand20b0fed2020-09-28 15:45:16 +0200515 if (cfgerr & ERR_CODE)
William Lallemand6e9556b2020-05-12 17:52:44 +0200516 goto error;
517
518 /* empty line */
519 if (!crt_path || !*crt_path) {
520 crtlist_entry_free(entry);
521 entry = NULL;
522 continue;
523 }
524
525 if (*crt_path != '/' && global_ssl.crt_base) {
526 if ((strlen(global_ssl.crt_base) + 1 + strlen(crt_path)) > MAXPATHLEN) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200527 memprintf(err, "parsing [%s:%d]: '%s' : path too long",
528 file, linenum, crt_path);
William Lallemand6e9556b2020-05-12 17:52:44 +0200529 cfgerr |= ERR_ALERT | ERR_FATAL;
530 goto error;
531 }
532 snprintf(path, sizeof(path), "%s/%s", global_ssl.crt_base, crt_path);
533 crt_path = path;
534 }
535
536 /* Look for a ckch_store or create one */
537 ckchs = ckchs_lookup(crt_path);
538 if (ckchs == NULL) {
William Lallemand47da8212020-09-10 19:13:27 +0200539 if (stat(crt_path, &buf) == 0) {
William Lallemand77e1c6f2020-11-20 18:26:09 +0100540 found++;
William Lallemand47da8212020-09-10 19:13:27 +0200541
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200542 ckchs = ckchs_load_cert_file(crt_path, err);
William Lallemand47da8212020-09-10 19:13:27 +0200543 if (ckchs == NULL) {
544 cfgerr |= ERR_ALERT | ERR_FATAL;
545 goto error;
546 }
547
548 entry->node.key = ckchs;
549 entry->crtlist = newlist;
550 ebpt_insert(&newlist->entries, &entry->node);
Willy Tarreau2b718102021-04-21 07:32:39 +0200551 LIST_APPEND(&newlist->ord_entries, &entry->by_crtlist);
552 LIST_APPEND(&ckchs->crtlist_entry, &entry->by_ckch_store);
William Lallemand6e9556b2020-05-12 17:52:44 +0200553
William Lallemand73404572020-11-20 18:23:40 +0100554 } else if (global_ssl.extra_files & SSL_GF_BUNDLE) {
William Lallemand47da8212020-09-10 19:13:27 +0200555 /* If we didn't find the file, this could be a
William Lallemand51f784b2020-10-02 18:08:18 +0200556 bundle, since 2.3 we don't support multiple
557 certificate in the same OpenSSL store, so we
558 emulate it by loading each file separately. To
559 do so we need to duplicate the entry in the
560 crt-list because it becomes independent */
William Lallemand47da8212020-09-10 19:13:27 +0200561 char fp[MAXPATHLEN+1] = {0};
562 int n = 0;
563 struct crtlist_entry *entry_dup = entry; /* use the previous created entry */
564 for (n = 0; n < SSL_SOCK_NUM_KEYTYPES; n++) {
565 struct stat buf;
566 int ret;
567
William Lallemand86c2dd62020-11-20 14:23:38 +0100568 ret = snprintf(fp, sizeof(fp), "%s.%s", crt_path, SSL_SOCK_KEYTYPE_NAMES[n]);
William Lallemand47da8212020-09-10 19:13:27 +0200569 if (ret > sizeof(fp))
570 continue;
571
572 ckchs = ckchs_lookup(fp);
William Lallemand77e1c6f2020-11-20 18:26:09 +0100573 if (!ckchs) {
574 if (stat(fp, &buf) == 0) {
575 ckchs = ckchs_load_cert_file(fp, err);
576 if (!ckchs) {
William Lallemand47da8212020-09-10 19:13:27 +0200577 cfgerr |= ERR_ALERT | ERR_FATAL;
578 goto error;
579 }
William Lallemand77e1c6f2020-11-20 18:26:09 +0100580 } else {
581 continue; /* didn't find this extension, skip */
582 }
583 }
584 found++;
585 linenum++; /* we duplicate the line for this entry in the bundle */
586 if (!entry_dup) { /* if the entry was used, duplicate one */
587 linenum++;
588 entry_dup = crtlist_entry_dup(entry);
589 if (!entry_dup) {
590 cfgerr |= ERR_ALERT | ERR_FATAL;
591 goto error;
William Lallemand47da8212020-09-10 19:13:27 +0200592 }
William Lallemand77e1c6f2020-11-20 18:26:09 +0100593 entry_dup->linenum = linenum;
594 }
William Lallemand47da8212020-09-10 19:13:27 +0200595
William Lallemand77e1c6f2020-11-20 18:26:09 +0100596 entry_dup->node.key = ckchs;
597 entry_dup->crtlist = newlist;
598 ebpt_insert(&newlist->entries, &entry_dup->node);
Willy Tarreau2b718102021-04-21 07:32:39 +0200599 LIST_APPEND(&newlist->ord_entries, &entry_dup->by_crtlist);
600 LIST_APPEND(&ckchs->crtlist_entry, &entry_dup->by_ckch_store);
William Lallemand47da8212020-09-10 19:13:27 +0200601
William Lallemand77e1c6f2020-11-20 18:26:09 +0100602 entry_dup = NULL; /* the entry was used, we need a new one next round */
William Lallemand47da8212020-09-10 19:13:27 +0200603 }
William Lallemandb7fdfdf2020-12-04 15:45:02 +0100604#if HA_OPENSSL_VERSION_NUMBER < 0x10101000L
605 if (found) {
606 memprintf(err, "%sCan't load '%s'. Loading a multi certificates bundle requires OpenSSL >= 1.1.1\n",
607 err && *err ? *err : "", crt_path);
608 cfgerr |= ERR_ALERT | ERR_FATAL;
609 }
610#endif
William Lallemand47da8212020-09-10 19:13:27 +0200611 }
William Lallemand77e1c6f2020-11-20 18:26:09 +0100612 if (!found) {
613 memprintf(err, "%sunable to stat SSL certificate from file '%s' : %s.\n",
614 err && *err ? *err : "", crt_path, strerror(errno));
615 cfgerr |= ERR_ALERT | ERR_FATAL;
616 }
617
William Lallemand50c03aa2020-11-06 16:24:07 +0100618 } else {
619 entry->node.key = ckchs;
620 entry->crtlist = newlist;
621 ebpt_insert(&newlist->entries, &entry->node);
Willy Tarreau2b718102021-04-21 07:32:39 +0200622 LIST_APPEND(&newlist->ord_entries, &entry->by_crtlist);
623 LIST_APPEND(&ckchs->crtlist_entry, &entry->by_ckch_store);
William Lallemand77e1c6f2020-11-20 18:26:09 +0100624 found++;
William Lallemand47da8212020-09-10 19:13:27 +0200625 }
William Lallemand6e9556b2020-05-12 17:52:44 +0200626 entry = NULL;
627 }
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200628
629 if (missing_lf != -1) {
630 memprintf(err, "parsing [%s:%d]: Missing LF on last line, file might have been truncated at position %d.\n",
631 file, linenum, (missing_lf + 1));
632 cfgerr |= ERR_ALERT | ERR_FATAL;
633 }
634
William Lallemand6e9556b2020-05-12 17:52:44 +0200635 if (cfgerr & ERR_CODE)
636 goto error;
637
638 newlist->linecount = linenum;
639
640 fclose(f);
641 *crtlist = newlist;
642
643 return cfgerr;
644error:
645 crtlist_entry_free(entry);
646
647 fclose(f);
648 crtlist_free(newlist);
649 return cfgerr;
650}
651
652/* This function reads a directory and stores it in a struct crtlist, each file is a crtlist_entry structure
653 * Fill the <crtlist> argument with a pointer to a new crtlist struct
654 *
655 * This function tries to open and store certificate files.
656 */
657int crtlist_load_cert_dir(char *path, struct bind_conf *bind_conf, struct crtlist **crtlist, char **err)
658{
659 struct crtlist *dir;
660 struct dirent **de_list;
661 int i, n;
662 struct stat buf;
663 char *end;
664 char fp[MAXPATHLEN+1];
665 int cfgerr = 0;
666 struct ckch_store *ckchs;
William Lallemand6e9556b2020-05-12 17:52:44 +0200667
668 dir = crtlist_new(path, 1);
669 if (dir == NULL) {
670 memprintf(err, "not enough memory");
671 return ERR_ALERT | ERR_FATAL;
672 }
673
674 n = scandir(path, &de_list, 0, alphasort);
675 if (n < 0) {
676 memprintf(err, "%sunable to scan directory '%s' : %s.\n",
677 err && *err ? *err : "", path, strerror(errno));
678 cfgerr |= ERR_ALERT | ERR_FATAL;
679 }
680 else {
681 for (i = 0; i < n; i++) {
682 struct crtlist_entry *entry;
683 struct dirent *de = de_list[i];
684
685 end = strrchr(de->d_name, '.');
Tim Duesterhuse5ff1412021-01-02 22:31:53 +0100686 if (end && (strcmp(end, ".issuer") == 0 || strcmp(end, ".ocsp") == 0 || strcmp(end, ".sctl") == 0 || strcmp(end, ".key") == 0))
William Lallemand6e9556b2020-05-12 17:52:44 +0200687 goto ignore_entry;
688
689 snprintf(fp, sizeof(fp), "%s/%s", path, de->d_name);
690 if (stat(fp, &buf) != 0) {
691 memprintf(err, "%sunable to stat SSL certificate from file '%s' : %s.\n",
692 err && *err ? *err : "", fp, strerror(errno));
693 cfgerr |= ERR_ALERT | ERR_FATAL;
694 goto ignore_entry;
695 }
696 if (!S_ISREG(buf.st_mode))
697 goto ignore_entry;
698
699 entry = crtlist_entry_new();
700 if (entry == NULL) {
701 memprintf(err, "not enough memory '%s'", fp);
702 cfgerr |= ERR_ALERT | ERR_FATAL;
703 goto ignore_entry;
704 }
705
William Lallemand6e9556b2020-05-12 17:52:44 +0200706 ckchs = ckchs_lookup(fp);
707 if (ckchs == NULL)
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200708 ckchs = ckchs_load_cert_file(fp, err);
William Lallemand6e9556b2020-05-12 17:52:44 +0200709 if (ckchs == NULL) {
710 free(de);
711 free(entry);
712 cfgerr |= ERR_ALERT | ERR_FATAL;
713 goto end;
714 }
715 entry->node.key = ckchs;
716 entry->crtlist = dir;
Willy Tarreau2b718102021-04-21 07:32:39 +0200717 LIST_APPEND(&ckchs->crtlist_entry, &entry->by_ckch_store);
718 LIST_APPEND(&dir->ord_entries, &entry->by_crtlist);
William Lallemand6e9556b2020-05-12 17:52:44 +0200719 ebpt_insert(&dir->entries, &entry->node);
720
721ignore_entry:
722 free(de);
723 }
724end:
725 free(de_list);
726 }
727
728 if (cfgerr & ERR_CODE) {
729 /* free the dir and entries on error */
730 crtlist_free(dir);
731 } else {
732 *crtlist = dir;
733 }
734 return cfgerr;
735
736}
737
William Lallemandc756bbd2020-05-13 17:23:59 +0200738/*
739 * Take an ssl_bind_conf structure and append the configuration line used to
740 * create it in the buffer
741 */
742static void dump_crtlist_sslconf(struct buffer *buf, const struct ssl_bind_conf *conf)
743{
744 int space = 0;
745
746 if (conf == NULL)
747 return;
748
749 chunk_appendf(buf, " [");
750#ifdef OPENSSL_NPN_NEGOTIATED
751 if (conf->npn_str) {
752 int len = conf->npn_len;
753 char *ptr = conf->npn_str;
754 int comma = 0;
755
756 if (space) chunk_appendf(buf, " ");
757 chunk_appendf(buf, "npn ");
758 while (len) {
759 unsigned short size;
760
761 size = *ptr;
762 ptr++;
763 if (comma)
764 chunk_memcat(buf, ",", 1);
765 chunk_memcat(buf, ptr, size);
766 ptr += size;
767 len -= size + 1;
768 comma = 1;
769 }
770 chunk_memcat(buf, "", 1); /* finish with a \0 */
771 space++;
772 }
773#endif
774#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
775 if (conf->alpn_str) {
776 int len = conf->alpn_len;
777 char *ptr = conf->alpn_str;
778 int comma = 0;
779
780 if (space) chunk_appendf(buf, " ");
781 chunk_appendf(buf, "alpn ");
782 while (len) {
783 unsigned short size;
784
785 size = *ptr;
786 ptr++;
787 if (comma)
788 chunk_memcat(buf, ",", 1);
789 chunk_memcat(buf, ptr, size);
790 ptr += size;
791 len -= size + 1;
792 comma = 1;
793 }
794 chunk_memcat(buf, "", 1); /* finish with a \0 */
795 space++;
796 }
797#endif
798 /* verify */
799 {
800 if (conf->verify == SSL_SOCK_VERIFY_NONE) {
801 if (space) chunk_appendf(buf, " ");
802 chunk_appendf(buf, "verify none");
803 space++;
804 } else if (conf->verify == SSL_SOCK_VERIFY_OPTIONAL) {
805 if (space) chunk_appendf(buf, " ");
806 chunk_appendf(buf, "verify optional");
807 space++;
808 } else if (conf->verify == SSL_SOCK_VERIFY_REQUIRED) {
809 if (space) chunk_appendf(buf, " ");
810 chunk_appendf(buf, "verify required");
811 space++;
812 }
813 }
814
815 if (conf->no_ca_names) {
816 if (space) chunk_appendf(buf, " ");
817 chunk_appendf(buf, "no-ca-names");
818 space++;
819 }
820
821 if (conf->early_data) {
822 if (space) chunk_appendf(buf, " ");
823 chunk_appendf(buf, "allow-0rtt");
824 space++;
825 }
826 if (conf->ca_file) {
827 if (space) chunk_appendf(buf, " ");
828 chunk_appendf(buf, "ca-file %s", conf->ca_file);
829 space++;
830 }
831 if (conf->crl_file) {
832 if (space) chunk_appendf(buf, " ");
833 chunk_appendf(buf, "crl-file %s", conf->crl_file);
834 space++;
835 }
836 if (conf->ciphers) {
837 if (space) chunk_appendf(buf, " ");
838 chunk_appendf(buf, "ciphers %s", conf->ciphers);
839 space++;
840 }
Ilya Shipitsinf34ed0b2020-11-21 14:37:34 +0500841#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES
William Lallemandc756bbd2020-05-13 17:23:59 +0200842 if (conf->ciphersuites) {
843 if (space) chunk_appendf(buf, " ");
844 chunk_appendf(buf, "ciphersuites %s", conf->ciphersuites);
845 space++;
846 }
847#endif
848 if (conf->curves) {
849 if (space) chunk_appendf(buf, " ");
850 chunk_appendf(buf, "curves %s", conf->curves);
851 space++;
852 }
853 if (conf->ecdhe) {
854 if (space) chunk_appendf(buf, " ");
855 chunk_appendf(buf, "ecdhe %s", conf->ecdhe);
856 space++;
857 }
858
859 /* the crt-lists only support ssl-min-ver and ssl-max-ver */
William Lallemand8177ad92020-05-20 16:49:02 +0200860 if (conf->ssl_methods_cfg.min) {
William Lallemandc756bbd2020-05-13 17:23:59 +0200861 if (space) chunk_appendf(buf, " ");
William Lallemand8177ad92020-05-20 16:49:02 +0200862 chunk_appendf(buf, "ssl-min-ver %s", methodVersions[conf->ssl_methods_cfg.min].name);
William Lallemandc756bbd2020-05-13 17:23:59 +0200863 space++;
864 }
865
William Lallemand8177ad92020-05-20 16:49:02 +0200866 if (conf->ssl_methods_cfg.max) {
William Lallemandc756bbd2020-05-13 17:23:59 +0200867 if (space) chunk_appendf(buf, " ");
William Lallemand8177ad92020-05-20 16:49:02 +0200868 chunk_appendf(buf, "ssl-max-ver %s", methodVersions[conf->ssl_methods_cfg.max].name);
William Lallemandc756bbd2020-05-13 17:23:59 +0200869 space++;
870 }
871
872 chunk_appendf(buf, "]");
873
874 return;
875}
876
877/* dump a list of filters */
878static void dump_crtlist_filters(struct buffer *buf, struct crtlist_entry *entry)
879{
880 int i;
881
882 if (!entry->fcount)
883 return;
884
885 for (i = 0; i < entry->fcount; i++) {
886 chunk_appendf(buf, " %s", entry->filters[i]);
887 }
888 return;
889}
890
891/************************** CLI functions ****************************/
892
893
Willy Tarreaua2fcca02022-05-05 11:53:23 +0200894/* CLI IO handler for '(show|dump) ssl crt-list'.
895 * It uses show_crtlist_ctx for the context.
896 */
William Lallemandc756bbd2020-05-13 17:23:59 +0200897static int cli_io_handler_dump_crtlist(struct appctx *appctx)
898{
Willy Tarreaua2fcca02022-05-05 11:53:23 +0200899 struct show_crtlist_ctx *ctx = appctx->svcctx;
William Lallemandc756bbd2020-05-13 17:23:59 +0200900 struct buffer *trash = alloc_trash_chunk();
Christopher Faulet908628c2022-03-25 16:43:49 +0100901 struct conn_stream *cs = appctx->owner;
William Lallemandc756bbd2020-05-13 17:23:59 +0200902 struct ebmb_node *lnode;
903
904 if (trash == NULL)
905 return 1;
906
907 /* dump the list of crt-lists */
Willy Tarreaua2fcca02022-05-05 11:53:23 +0200908 lnode = ctx->crtlist_node;
William Lallemandc756bbd2020-05-13 17:23:59 +0200909 if (lnode == NULL)
910 lnode = ebmb_first(&crtlists_tree);
911 while (lnode) {
912 chunk_appendf(trash, "%s\n", lnode->key);
Christopher Faulet908628c2022-03-25 16:43:49 +0100913 if (ci_putchk(cs_ic(cs), trash) == -1) {
Christopher Fauleta0bdec32022-04-04 07:51:21 +0200914 cs_rx_room_blk(cs);
William Lallemandc756bbd2020-05-13 17:23:59 +0200915 goto yield;
916 }
917 lnode = ebmb_next(lnode);
918 }
919 free_trash_chunk(trash);
920 return 1;
921yield:
Willy Tarreaua2fcca02022-05-05 11:53:23 +0200922 ctx->crtlist_node = lnode;
William Lallemandc756bbd2020-05-13 17:23:59 +0200923 free_trash_chunk(trash);
924 return 0;
925}
926
927/* CLI IO handler for '(show|dump) ssl crt-list <filename>' */
928static int cli_io_handler_dump_crtlist_entries(struct appctx *appctx)
929{
Willy Tarreaua2fcca02022-05-05 11:53:23 +0200930 struct show_crtlist_ctx *ctx = appctx->svcctx;
William Lallemandc756bbd2020-05-13 17:23:59 +0200931 struct buffer *trash = alloc_trash_chunk();
932 struct crtlist *crtlist;
Christopher Faulet908628c2022-03-25 16:43:49 +0100933 struct conn_stream *cs = appctx->owner;
William Lallemandc756bbd2020-05-13 17:23:59 +0200934 struct crtlist_entry *entry;
935
936 if (trash == NULL)
937 return 1;
938
Willy Tarreaua2fcca02022-05-05 11:53:23 +0200939 crtlist = ebmb_entry(ctx->crtlist_node, struct crtlist, node);
William Lallemandc756bbd2020-05-13 17:23:59 +0200940
Willy Tarreaua2fcca02022-05-05 11:53:23 +0200941 entry = ctx->entry;
William Lallemandc756bbd2020-05-13 17:23:59 +0200942 if (entry == NULL) {
943 entry = LIST_ELEM((crtlist->ord_entries).n, typeof(entry), by_crtlist);
944 chunk_appendf(trash, "# %s\n", crtlist->node.key);
Christopher Faulet908628c2022-03-25 16:43:49 +0100945 if (ci_putchk(cs_ic(cs), trash) == -1) {
Christopher Fauleta0bdec32022-04-04 07:51:21 +0200946 cs_rx_room_blk(cs);
William Lallemandc756bbd2020-05-13 17:23:59 +0200947 goto yield;
948 }
949 }
950
951 list_for_each_entry_from(entry, &crtlist->ord_entries, by_crtlist) {
952 struct ckch_store *store;
953 const char *filename;
954
955 store = entry->node.key;
956 filename = store->path;
957 chunk_appendf(trash, "%s", filename);
Willy Tarreaua2fcca02022-05-05 11:53:23 +0200958 if (ctx->mode == 's') /* show */
William Lallemandc756bbd2020-05-13 17:23:59 +0200959 chunk_appendf(trash, ":%d", entry->linenum);
960 dump_crtlist_sslconf(trash, entry->ssl_conf);
961 dump_crtlist_filters(trash, entry);
962 chunk_appendf(trash, "\n");
963
Christopher Faulet908628c2022-03-25 16:43:49 +0100964 if (ci_putchk(cs_ic(cs), trash) == -1) {
Christopher Fauleta0bdec32022-04-04 07:51:21 +0200965 cs_rx_room_blk(cs);
William Lallemandc756bbd2020-05-13 17:23:59 +0200966 goto yield;
967 }
968 }
969 free_trash_chunk(trash);
970 return 1;
971yield:
Willy Tarreaua2fcca02022-05-05 11:53:23 +0200972 ctx->entry = entry;
William Lallemandc756bbd2020-05-13 17:23:59 +0200973 free_trash_chunk(trash);
974 return 0;
975}
976
977/* CLI argument parser for '(show|dump) ssl crt-list' */
978static int cli_parse_dump_crtlist(char **args, char *payload, struct appctx *appctx, void *private)
979{
Willy Tarreaua2fcca02022-05-05 11:53:23 +0200980 struct show_crtlist_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
William Lallemandc756bbd2020-05-13 17:23:59 +0200981 struct ebmb_node *lnode;
982 char *filename = NULL;
983 int mode;
William Lallemand99cc2182020-06-25 15:19:51 +0200984 char *end;
William Lallemandc756bbd2020-05-13 17:23:59 +0200985
986 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
987 return 1;
988
Tim Duesterhuse5ff1412021-01-02 22:31:53 +0100989 if (*args[3] && strcmp(args[3], "-n") == 0) {
William Lallemandc756bbd2020-05-13 17:23:59 +0200990 mode = 's';
991 filename = args[4];
992 } else {
993 mode = 'd';
994 filename = args[3];
995 }
996
997 if (mode == 's' && !*args[4])
998 return cli_err(appctx, "'show ssl crt-list -n' expects a filename or a directory\n");
999
1000 if (filename && *filename) {
William Lallemand99cc2182020-06-25 15:19:51 +02001001
1002
1003 /* strip trailing slashes, including first one */
1004 for (end = filename + strlen(filename) - 1; end >= filename && *end == '/'; end--)
1005 *end = 0;
1006
William Lallemandc756bbd2020-05-13 17:23:59 +02001007 lnode = ebst_lookup(&crtlists_tree, filename);
1008 if (lnode == NULL)
1009 return cli_err(appctx, "didn't find the specified filename\n");
1010
Willy Tarreaua2fcca02022-05-05 11:53:23 +02001011 ctx->crtlist_node = lnode;
William Lallemandc756bbd2020-05-13 17:23:59 +02001012 appctx->io_handler = cli_io_handler_dump_crtlist_entries;
1013 }
Willy Tarreaua2fcca02022-05-05 11:53:23 +02001014 ctx->mode = mode;
William Lallemandc756bbd2020-05-13 17:23:59 +02001015
1016 return 0;
1017}
1018
1019/* release function of the "add ssl crt-list' command, free things and unlock
1020 the spinlock */
1021static void cli_release_add_crtlist(struct appctx *appctx)
1022{
1023 struct crtlist_entry *entry = appctx->ctx.cli.p1;
1024
1025 if (appctx->st2 != SETCERT_ST_FIN) {
1026 struct ckch_inst *inst, *inst_s;
1027 /* upon error free the ckch_inst and everything inside */
1028 ebpt_delete(&entry->node);
Willy Tarreau2b718102021-04-21 07:32:39 +02001029 LIST_DELETE(&entry->by_crtlist);
1030 LIST_DELETE(&entry->by_ckch_store);
William Lallemandc756bbd2020-05-13 17:23:59 +02001031
1032 list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_ckchs) {
1033 ckch_inst_free(inst);
1034 }
1035 crtlist_free_filters(entry->filters);
1036 ssl_sock_free_ssl_conf(entry->ssl_conf);
1037 free(entry->ssl_conf);
1038 free(entry);
1039 }
1040
1041 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1042}
1043
1044
1045/* IO Handler for the "add ssl crt-list" command It adds a new entry in the
1046 * crt-list and generates the ckch_insts for each bind_conf that uses this crt-list
1047 *
1048 * The logic is the same as the "commit ssl cert" command but without the
1049 * freeing of the old structures, because there are none.
1050 */
1051static int cli_io_handler_add_crtlist(struct appctx *appctx)
1052{
1053 struct bind_conf_list *bind_conf_node;
Christopher Faulet908628c2022-03-25 16:43:49 +01001054 struct conn_stream *cs = appctx->owner;
William Lallemandc756bbd2020-05-13 17:23:59 +02001055 struct crtlist *crtlist = appctx->ctx.cli.p0;
1056 struct crtlist_entry *entry = appctx->ctx.cli.p1;
1057 struct ckch_store *store = entry->node.key;
1058 struct buffer *trash = alloc_trash_chunk();
1059 struct ckch_inst *new_inst;
1060 char *err = NULL;
1061 int i = 0;
1062 int errcode = 0;
1063
1064 if (trash == NULL)
1065 goto error;
1066
1067 /* for each bind_conf which use the crt-list, a new ckch_inst must be
1068 * created.
1069 */
Christopher Faulet908628c2022-03-25 16:43:49 +01001070 if (unlikely(cs_ic(cs)->flags & (CF_WRITE_ERROR|CF_SHUTW)))
William Lallemandc756bbd2020-05-13 17:23:59 +02001071 goto error;
1072
Willy Tarreau1b948ef2022-05-05 13:50:46 +02001073 switch (appctx->st2) {
1074 case SETCERT_ST_INIT:
1075 /* This state just print the update message */
1076 chunk_printf(trash, "Inserting certificate '%s' in crt-list '%s'", store->path, crtlist->node.key);
1077 if (ci_putchk(cs_ic(cs), trash) == -1) {
1078 cs_rx_room_blk(cs);
1079 goto yield;
1080 }
1081 appctx->st2 = SETCERT_ST_GEN;
1082 /* fallthrough */
1083 case SETCERT_ST_GEN:
1084 bind_conf_node = appctx->ctx.cli.p2; /* get the previous ptr from the yield */
1085 if (bind_conf_node == NULL)
1086 bind_conf_node = crtlist->bind_conf;
1087 for (; bind_conf_node; bind_conf_node = bind_conf_node->next) {
1088 struct bind_conf *bind_conf = bind_conf_node->bind_conf;
1089 struct sni_ctx *sni;
1090
1091 /* yield every 10 generations */
1092 if (i > 10) {
1093 appctx->ctx.cli.p2 = bind_conf_node;
1094 goto yield;
1095 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001096
Willy Tarreau1b948ef2022-05-05 13:50:46 +02001097 /* we don't support multi-cert bundles, only simple ones */
1098 errcode |= ckch_inst_new_load_store(store->path, store, bind_conf, entry->ssl_conf, entry->filters, entry->fcount, &new_inst, &err);
1099 if (errcode & ERR_CODE)
1100 goto error;
William Lallemandc756bbd2020-05-13 17:23:59 +02001101
Willy Tarreau1b948ef2022-05-05 13:50:46 +02001102 /* we need to initialize the SSL_CTX generated */
1103 /* this iterate on the newly generated SNIs in the new instance to prepare their SSL_CTX */
1104 list_for_each_entry(sni, &new_inst->sni_ctx, by_ckch_inst) {
1105 if (!sni->order) { /* we initialized only the first SSL_CTX because it's the same in the other sni_ctx's */
1106 errcode |= ssl_sock_prep_ctx_and_inst(bind_conf, new_inst->ssl_conf, sni->ctx, sni->ckch_inst, &err);
William Lallemandc756bbd2020-05-13 17:23:59 +02001107 if (errcode & ERR_CODE)
1108 goto error;
William Lallemandc756bbd2020-05-13 17:23:59 +02001109 }
Willy Tarreau1b948ef2022-05-05 13:50:46 +02001110 }
1111 /* display one dot for each new instance */
1112 chunk_appendf(trash, ".");
1113 i++;
1114 LIST_APPEND(&store->ckch_inst, &new_inst->by_ckchs);
1115 LIST_APPEND(&entry->ckch_inst, &new_inst->by_crtlist_entry);
1116 new_inst->crtlist_entry = entry;
William Lallemandc756bbd2020-05-13 17:23:59 +02001117 }
Willy Tarreau1b948ef2022-05-05 13:50:46 +02001118 appctx->st2 = SETCERT_ST_INSERT;
1119 /* fallthrough */
1120 case SETCERT_ST_INSERT:
1121 /* insert SNIs in bind_conf */
1122 list_for_each_entry(new_inst, &store->ckch_inst, by_ckchs) {
1123 HA_RWLOCK_WRLOCK(SNI_LOCK, &new_inst->bind_conf->sni_lock);
1124 ssl_sock_load_cert_sni(new_inst, new_inst->bind_conf);
1125 HA_RWLOCK_WRUNLOCK(SNI_LOCK, &new_inst->bind_conf->sni_lock);
1126 }
1127 entry->linenum = ++crtlist->linecount;
1128 appctx->st2 = SETCERT_ST_FIN;
William Lallemandc756bbd2020-05-13 17:23:59 +02001129 }
1130
William Lallemandc756bbd2020-05-13 17:23:59 +02001131 chunk_appendf(trash, "\n");
1132 if (errcode & ERR_WARN)
1133 chunk_appendf(trash, "%s", err);
1134 chunk_appendf(trash, "Success!\n");
Christopher Faulet908628c2022-03-25 16:43:49 +01001135 if (ci_putchk(cs_ic(cs), trash) == -1)
Christopher Fauleta0bdec32022-04-04 07:51:21 +02001136 cs_rx_room_blk(cs);
William Lallemandc756bbd2020-05-13 17:23:59 +02001137 free_trash_chunk(trash);
1138 /* success: call the release function and don't come back */
1139 return 1;
1140yield:
1141 /* store the state */
Christopher Faulet908628c2022-03-25 16:43:49 +01001142 if (ci_putchk(cs_ic(cs), trash) == -1)
Christopher Fauleta0bdec32022-04-04 07:51:21 +02001143 cs_rx_room_blk(cs);
William Lallemandc756bbd2020-05-13 17:23:59 +02001144 free_trash_chunk(trash);
Christopher Fauleta0bdec32022-04-04 07:51:21 +02001145 cs_rx_endp_more(cs); /* let's come back later */
William Lallemandc756bbd2020-05-13 17:23:59 +02001146 return 0; /* should come back */
1147
1148error:
1149 /* spin unlock and free are done in the release function */
1150 if (trash) {
1151 chunk_appendf(trash, "\n%sFailed!\n", err);
Christopher Faulet908628c2022-03-25 16:43:49 +01001152 if (ci_putchk(cs_ic(cs), trash) == -1)
Christopher Fauleta0bdec32022-04-04 07:51:21 +02001153 cs_rx_room_blk(cs);
William Lallemandc756bbd2020-05-13 17:23:59 +02001154 free_trash_chunk(trash);
1155 }
1156 /* error: call the release function and don't come back */
1157 return 1;
1158}
1159
1160
1161/*
1162 * Parse a "add ssl crt-list <crt-list> <certfile>" line.
1163 * Filters and option must be passed through payload:
1164 */
1165static int cli_parse_add_crtlist(char **args, char *payload, struct appctx *appctx, void *private)
1166{
1167 int cfgerr = 0;
1168 struct ckch_store *store;
1169 char *err = NULL;
1170 char path[MAXPATHLEN+1];
1171 char *crtlist_path;
1172 char *cert_path = NULL;
1173 struct ebmb_node *eb;
1174 struct ebpt_node *inserted;
1175 struct crtlist *crtlist;
1176 struct crtlist_entry *entry = NULL;
William Lallemand99cc2182020-06-25 15:19:51 +02001177 char *end;
William Lallemandc756bbd2020-05-13 17:23:59 +02001178
1179 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
1180 return 1;
1181
1182 if (!*args[3] || (!payload && !*args[4]))
1183 return cli_err(appctx, "'add ssl crtlist' expects a filename and a certificate name\n");
1184
1185 crtlist_path = args[3];
1186
William Lallemand99cc2182020-06-25 15:19:51 +02001187 /* strip trailing slashes, including first one */
1188 for (end = crtlist_path + strlen(crtlist_path) - 1; end >= crtlist_path && *end == '/'; end--)
1189 *end = 0;
1190
William Lallemandc756bbd2020-05-13 17:23:59 +02001191 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
1192 return cli_err(appctx, "Operations on certificates are currently locked!\n");
1193
1194 eb = ebst_lookup(&crtlists_tree, crtlist_path);
1195 if (!eb) {
1196 memprintf(&err, "crt-list '%s' does not exist!", crtlist_path);
1197 goto error;
1198 }
1199 crtlist = ebmb_entry(eb, struct crtlist, node);
1200
1201 entry = crtlist_entry_new();
1202 if (entry == NULL) {
1203 memprintf(&err, "Not enough memory!");
1204 goto error;
1205 }
1206
1207 if (payload) {
1208 char *lf;
1209
1210 lf = strrchr(payload, '\n');
1211 if (lf) {
1212 memprintf(&err, "only one line of payload is supported!");
1213 goto error;
1214 }
1215 /* cert_path is filled here */
Remi Tricot-Le Bretonfb00f312021-03-23 16:41:53 +01001216 cfgerr |= crtlist_parse_line(payload, &cert_path, entry, "CLI", 1, 1, &err);
William Lallemandc756bbd2020-05-13 17:23:59 +02001217 if (cfgerr & ERR_CODE)
1218 goto error;
1219 } else {
1220 cert_path = args[4];
1221 }
1222
1223 if (!cert_path) {
1224 memprintf(&err, "'add ssl crtlist' should contain the certificate name in the payload");
1225 cfgerr |= ERR_ALERT | ERR_FATAL;
1226 goto error;
1227 }
1228
1229 if (eb_gettag(crtlist->entries.b[EB_RGHT])) {
1230 char *slash;
1231
1232 slash = strrchr(cert_path, '/');
1233 if (!slash) {
1234 memprintf(&err, "'%s' is a directory, certificate path '%s' must contain the directory path", (char *)crtlist->node.key, cert_path);
1235 goto error;
1236 }
1237 /* temporary replace / by 0 to do an strcmp */
1238 *slash = '\0';
1239 if (strcmp(cert_path, (char*)crtlist->node.key) != 0) {
1240 *slash = '/';
1241 memprintf(&err, "'%s' is a directory, certificate path '%s' must contain the directory path", (char *)crtlist->node.key, cert_path);
1242 goto error;
1243 }
1244 *slash = '/';
1245 }
1246
1247 if (*cert_path != '/' && global_ssl.crt_base) {
1248 if ((strlen(global_ssl.crt_base) + 1 + strlen(cert_path)) > MAXPATHLEN) {
1249 memprintf(&err, "'%s' : path too long", cert_path);
1250 cfgerr |= ERR_ALERT | ERR_FATAL;
1251 goto error;
1252 }
1253 snprintf(path, sizeof(path), "%s/%s", global_ssl.crt_base, cert_path);
1254 cert_path = path;
1255 }
1256
1257 store = ckchs_lookup(cert_path);
1258 if (store == NULL) {
1259 memprintf(&err, "certificate '%s' does not exist!", cert_path);
1260 goto error;
1261 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001262 if (store->ckch == NULL || store->ckch->cert == NULL) {
1263 memprintf(&err, "certificate '%s' is empty!", cert_path);
1264 goto error;
1265 }
1266
1267 /* check if it's possible to insert this new crtlist_entry */
1268 entry->node.key = store;
1269 inserted = ebpt_insert(&crtlist->entries, &entry->node);
1270 if (inserted != &entry->node) {
1271 memprintf(&err, "file already exists in this directory!");
1272 goto error;
1273 }
1274
1275 /* this is supposed to be a directory (EB_ROOT_UNIQUE), so no ssl_conf are allowed */
1276 if ((entry->ssl_conf || entry->filters) && eb_gettag(crtlist->entries.b[EB_RGHT])) {
1277 memprintf(&err, "this is a directory, SSL configuration and filters are not allowed");
1278 goto error;
1279 }
1280
Willy Tarreau2b718102021-04-21 07:32:39 +02001281 LIST_APPEND(&crtlist->ord_entries, &entry->by_crtlist);
William Lallemandc756bbd2020-05-13 17:23:59 +02001282 entry->crtlist = crtlist;
Willy Tarreau2b718102021-04-21 07:32:39 +02001283 LIST_APPEND(&store->crtlist_entry, &entry->by_ckch_store);
William Lallemandc756bbd2020-05-13 17:23:59 +02001284
1285 appctx->st2 = SETCERT_ST_INIT;
1286 appctx->ctx.cli.p0 = crtlist;
1287 appctx->ctx.cli.p1 = entry;
1288
1289 /* unlock is done in the release handler */
1290 return 0;
1291
1292error:
1293 crtlist_entry_free(entry);
1294 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1295 err = memprintf(&err, "Can't edit the crt-list: %s\n", err ? err : "");
1296 return cli_dynerr(appctx, err);
1297}
1298
1299/* Parse a "del ssl crt-list <crt-list> <certfile>" line. */
1300static int cli_parse_del_crtlist(char **args, char *payload, struct appctx *appctx, void *private)
1301{
1302 struct ckch_store *store;
1303 char *err = NULL;
1304 char *crtlist_path, *cert_path;
1305 struct ebmb_node *ebmb;
1306 struct ebpt_node *ebpt;
1307 struct crtlist *crtlist;
1308 struct crtlist_entry *entry = NULL;
1309 struct ckch_inst *inst, *inst_s;
1310 int linenum = 0;
1311 char *colons;
William Lallemand99cc2182020-06-25 15:19:51 +02001312 char *end;
Remi Tricot-Le Bretonbc2c3862021-03-26 10:47:50 +01001313 int error_message_dumped = 0;
William Lallemandc756bbd2020-05-13 17:23:59 +02001314
1315 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
1316 return 1;
1317
1318 if (!*args[3] || !*args[4])
1319 return cli_err(appctx, "'del ssl crtlist' expects a filename and a certificate name\n");
1320
1321 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
1322 return cli_err(appctx, "Can't delete!\nOperations on certificates are currently locked!\n");
1323
1324 crtlist_path = args[3];
1325 cert_path = args[4];
1326
1327 colons = strchr(cert_path, ':');
1328 if (colons) {
1329 char *endptr;
1330
1331 linenum = strtol(colons + 1, &endptr, 10);
1332 if (colons + 1 == endptr || *endptr != '\0') {
1333 memprintf(&err, "wrong line number after colons in '%s'!", cert_path);
1334 goto error;
1335 }
1336 *colons = '\0';
1337 }
William Lallemand99cc2182020-06-25 15:19:51 +02001338
1339 /* strip trailing slashes, including first one */
1340 for (end = crtlist_path + strlen(crtlist_path) - 1; end >= crtlist_path && *end == '/'; end--)
1341 *end = 0;
1342
William Lallemandc756bbd2020-05-13 17:23:59 +02001343 /* look for crtlist */
1344 ebmb = ebst_lookup(&crtlists_tree, crtlist_path);
1345 if (!ebmb) {
1346 memprintf(&err, "crt-list '%s' does not exist!", crtlist_path);
1347 goto error;
1348 }
1349 crtlist = ebmb_entry(ebmb, struct crtlist, node);
1350
1351 /* look for store */
1352 store = ckchs_lookup(cert_path);
1353 if (store == NULL) {
1354 memprintf(&err, "certificate '%s' does not exist!", cert_path);
1355 goto error;
1356 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001357 if (store->ckch == NULL || store->ckch->cert == NULL) {
1358 memprintf(&err, "certificate '%s' is empty!", cert_path);
1359 goto error;
1360 }
1361
1362 ebpt = ebpt_lookup(&crtlist->entries, store);
1363 if (!ebpt) {
1364 memprintf(&err, "certificate '%s' can't be found in crt-list '%s'!", cert_path, crtlist_path);
1365 goto error;
1366 }
1367
1368 /* list the line number of entries for errors in err, and select the right ebpt */
1369 for (; ebpt; ebpt = ebpt_next_dup(ebpt)) {
1370 struct crtlist_entry *tmp;
1371
1372 tmp = ebpt_entry(ebpt, struct crtlist_entry, node);
1373 memprintf(&err, "%s%s%d", err ? err : "", err ? ", " : "", tmp->linenum);
1374
1375 /* select the entry we wanted */
1376 if (linenum == 0 || tmp->linenum == linenum) {
1377 if (!entry)
1378 entry = tmp;
1379 }
1380 }
1381
1382 /* we didn't found the specified entry */
1383 if (!entry) {
1384 memprintf(&err, "found a certificate '%s' but the line number is incorrect, please specify a correct line number preceded by colons (%s)!", cert_path, err ? err : NULL);
1385 goto error;
1386 }
1387
1388 /* we didn't specified a line number but there were several entries */
1389 if (linenum == 0 && ebpt_next_dup(&entry->node)) {
1390 memprintf(&err, "found the certificate '%s' in several entries, please specify a line number preceded by colons (%s)!", cert_path, err ? err : NULL);
1391 goto error;
1392 }
1393
Remi Tricot-Le Bretonbc2c3862021-03-26 10:47:50 +01001394 /* Iterate over all the instances in order to see if any of them is a
1395 * default instance. If this is the case, the entry won't be suppressed. */
1396 list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_crtlist_entry) {
1397 if (inst->is_default && !inst->bind_conf->strict_sni) {
1398 if (!error_message_dumped) {
1399 memprintf(&err, "certificate '%s' cannot be deleted, it is used as default certificate by the following frontends:\n", cert_path);
1400 error_message_dumped = 1;
1401 }
1402 memprintf(&err, "%s\t- %s:%d\n", err, inst->bind_conf->file, inst->bind_conf->line);
1403 }
1404 }
1405 if (error_message_dumped)
1406 goto error;
1407
William Lallemandc756bbd2020-05-13 17:23:59 +02001408 /* upon error free the ckch_inst and everything inside */
1409
1410 ebpt_delete(&entry->node);
Willy Tarreau2b718102021-04-21 07:32:39 +02001411 LIST_DELETE(&entry->by_crtlist);
1412 LIST_DELETE(&entry->by_ckch_store);
William Lallemandc756bbd2020-05-13 17:23:59 +02001413
1414 list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_crtlist_entry) {
1415 struct sni_ctx *sni, *sni_s;
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001416 struct ckch_inst_link_ref *link_ref, *link_ref_s;
William Lallemandc756bbd2020-05-13 17:23:59 +02001417
1418 HA_RWLOCK_WRLOCK(SNI_LOCK, &inst->bind_conf->sni_lock);
1419 list_for_each_entry_safe(sni, sni_s, &inst->sni_ctx, by_ckch_inst) {
1420 ebmb_delete(&sni->name);
Willy Tarreau2b718102021-04-21 07:32:39 +02001421 LIST_DELETE(&sni->by_ckch_inst);
William Lallemandc756bbd2020-05-13 17:23:59 +02001422 SSL_CTX_free(sni->ctx);
1423 free(sni);
1424 }
1425 HA_RWLOCK_WRUNLOCK(SNI_LOCK, &inst->bind_conf->sni_lock);
Willy Tarreau2b718102021-04-21 07:32:39 +02001426 LIST_DELETE(&inst->by_ckchs);
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001427 list_for_each_entry_safe(link_ref, link_ref_s, &inst->cafile_link_refs, list) {
1428 LIST_DELETE(&link_ref->link->list);
1429 LIST_DELETE(&link_ref->list);
1430 free(link_ref);
1431 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001432 free(inst);
1433 }
1434
1435 crtlist_free_filters(entry->filters);
1436 ssl_sock_free_ssl_conf(entry->ssl_conf);
1437 free(entry->ssl_conf);
1438 free(entry);
1439
1440 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1441 err = memprintf(&err, "Entry '%s' deleted in crtlist '%s'!\n", cert_path, crtlist_path);
1442 return cli_dynmsg(appctx, LOG_NOTICE, err);
1443
1444error:
1445 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1446 err = memprintf(&err, "Can't delete the entry: %s\n", err ? err : "");
1447 return cli_dynerr(appctx, err);
1448}
1449
1450
William Lallemandee8530c2020-06-23 18:19:42 +02001451/* unlink and free all crt-list and crt-list entries */
1452void crtlist_deinit()
1453{
1454 struct eb_node *node, *next;
1455 struct crtlist *crtlist;
1456
1457 node = eb_first(&crtlists_tree);
1458 while (node) {
1459 next = eb_next(node);
1460 crtlist = ebmb_entry(node, struct crtlist, node);
1461 crtlist_free(crtlist);
1462 node = next;
1463 }
1464}
1465
William Lallemandc756bbd2020-05-13 17:23:59 +02001466
1467/* register cli keywords */
1468static struct cli_kw_list cli_kws = {{ },{
Willy Tarreaub205bfd2021-05-07 11:38:37 +02001469 { { "add", "ssl", "crt-list", NULL }, "add ssl crt-list <list> <cert> [opts]* : add to crt-list file <list> a line <cert> or a payload", cli_parse_add_crtlist, cli_io_handler_add_crtlist, cli_release_add_crtlist },
1470 { { "del", "ssl", "crt-list", NULL }, "del ssl crt-list <list> <cert[:line]> : delete a line <cert> from crt-list file <list>", cli_parse_del_crtlist, NULL, NULL },
1471 { { "show", "ssl", "crt-list", NULL }, "show ssl crt-list [-n] [<list>] : show the list of crt-lists or the content of a crt-list file <list>", cli_parse_dump_crtlist, cli_io_handler_dump_crtlist, NULL },
William Lallemandc756bbd2020-05-13 17:23:59 +02001472 { { NULL }, NULL, NULL, NULL } }
1473};
1474
1475INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws);
1476