blob: 7cd47627ca38b7ecdda11666d25d31891140356b [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 Tarreauf1d32c42020-06-04 21:07:02 +020023#include <haproxy/channel.h>
Willy Tarreau83487a82020-06-04 20:19:54 +020024#include <haproxy/cli.h>
Willy Tarreau8d366972020-05-27 16:10:29 +020025#include <haproxy/errors.h>
Willy Tarreau47d7f902020-06-04 14:25:47 +020026#include <haproxy/ssl_ckch.h>
Willy Tarreau52d88722020-06-04 14:29:23 +020027#include <haproxy/ssl_crtlist.h>
Willy Tarreau209108d2020-06-04 20:30:20 +020028#include <haproxy/ssl_sock.h>
Willy Tarreau5e539c92020-06-04 20:45:39 +020029#include <haproxy/stream_interface.h>
Willy Tarreau48fbcae2020-06-03 18:09:46 +020030#include <haproxy/tools.h>
William Lallemand6e9556b2020-05-12 17:52:44 +020031
William Lallemand6e9556b2020-05-12 17:52:44 +020032
William Lallemand6e9556b2020-05-12 17:52:44 +020033/* release ssl bind conf */
34void ssl_sock_free_ssl_conf(struct ssl_bind_conf *conf)
35{
36 if (conf) {
37#if defined(OPENSSL_NPN_NEGOTIATED) && !defined(OPENSSL_NO_NEXTPROTONEG)
Willy Tarreau61cfdf42021-02-20 10:46:51 +010038 ha_free(&conf->npn_str);
William Lallemand6e9556b2020-05-12 17:52:44 +020039#endif
40#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
Willy Tarreau61cfdf42021-02-20 10:46:51 +010041 ha_free(&conf->alpn_str);
William Lallemand6e9556b2020-05-12 17:52:44 +020042#endif
Willy Tarreau61cfdf42021-02-20 10:46:51 +010043 ha_free(&conf->ca_file);
44 ha_free(&conf->ca_verify_file);
45 ha_free(&conf->crl_file);
46 ha_free(&conf->ciphers);
Ilya Shipitsinf34ed0b2020-11-21 14:37:34 +050047#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES
Willy Tarreau61cfdf42021-02-20 10:46:51 +010048 ha_free(&conf->ciphersuites);
William Lallemand6e9556b2020-05-12 17:52:44 +020049#endif
Willy Tarreau61cfdf42021-02-20 10:46:51 +010050 ha_free(&conf->curves);
51 ha_free(&conf->ecdhe);
William Lallemand6e9556b2020-05-12 17:52:44 +020052 }
53}
54
William Lallemand82f2d2f2020-09-10 19:06:43 +020055/*
56 * Allocate and copy a ssl_bind_conf structure
57 */
58struct ssl_bind_conf *crtlist_dup_ssl_conf(struct ssl_bind_conf *src)
59{
60 struct ssl_bind_conf *dst;
61
62 if (!src)
63 return NULL;
64
65 dst = calloc(1, sizeof(*dst));
66 if (!dst)
67 return NULL;
68
69#if defined(OPENSSL_NPN_NEGOTIATED) && !defined(OPENSSL_NO_NEXTPROTONEG)
70 if (src->npn_str) {
71 dst->npn_str = strdup(src->npn_str);
72 if (!dst->npn_str)
73 goto error;
74 }
75#endif
76#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
77 if (src->alpn_str) {
78 dst->alpn_str = strdup(src->alpn_str);
79 if (!dst->alpn_str)
80 goto error;
81 }
82#endif
83 if (src->ca_file) {
84 dst->ca_file = strdup(src->ca_file);
85 if (!dst->ca_file)
86 goto error;
87 }
88 if (src->ca_verify_file) {
89 dst->ca_verify_file = strdup(src->ca_verify_file);
90 if (!dst->ca_verify_file)
91 goto error;
92 }
93 if (src->crl_file) {
94 dst->crl_file = strdup(src->crl_file);
95 if (!dst->crl_file)
96 goto error;
97 }
98 if (src->ciphers) {
99 dst->ciphers = strdup(src->ciphers);
100 if (!dst->ciphers)
101 goto error;
102 }
Ilya Shipitsinf34ed0b2020-11-21 14:37:34 +0500103#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES
William Lallemand82f2d2f2020-09-10 19:06:43 +0200104 if (src->ciphersuites) {
105 dst->ciphersuites = strdup(src->ciphersuites);
106 if (!dst->ciphersuites)
107 goto error;
108 }
109#endif
110 if (src->curves) {
111 dst->curves = strdup(src->curves);
112 if (!dst->curves)
113 goto error;
114 }
115 if (src->ecdhe) {
116 dst->ecdhe = strdup(src->ecdhe);
117 if (!dst->ecdhe)
118 goto error;
119 }
Remi Tricot-Le Breton393376a2023-03-14 17:22:24 +0100120
121 dst->ssl_methods_cfg.flags = src->ssl_methods_cfg.flags;
122 dst->ssl_methods_cfg.min = src->ssl_methods_cfg.min;
123 dst->ssl_methods_cfg.max = src->ssl_methods_cfg.max;
124
125 dst->ssl_methods.flags = src->ssl_methods.flags;
126 dst->ssl_methods.min = src->ssl_methods.min;
127 dst->ssl_methods.max = src->ssl_methods.max;
128
William Lallemand82f2d2f2020-09-10 19:06:43 +0200129 return dst;
130
131error:
132 ssl_sock_free_ssl_conf(dst);
133 free(dst);
134
135 return NULL;
136}
William Lallemand6e9556b2020-05-12 17:52:44 +0200137
138/* free sni filters */
139void crtlist_free_filters(char **args)
140{
141 int i;
142
143 if (!args)
144 return;
145
146 for (i = 0; args[i]; i++)
147 free(args[i]);
148
149 free(args);
150}
151
152/* Alloc and duplicate a char ** array */
153char **crtlist_dup_filters(char **args, int fcount)
154{
155 char **dst;
156 int i;
157
158 if (fcount == 0)
159 return NULL;
160
161 dst = calloc(fcount + 1, sizeof(*dst));
162 if (!dst)
163 return NULL;
164
165 for (i = 0; i < fcount; i++) {
166 dst[i] = strdup(args[i]);
167 if (!dst[i])
168 goto error;
169 }
170 return dst;
171
172error:
173 crtlist_free_filters(dst);
174 return NULL;
175}
176
177/*
178 * Detach and free a crtlist_entry.
179 * Free the filters, the ssl_conf and call ckch_inst_free() for each ckch_inst
180 */
181void crtlist_entry_free(struct crtlist_entry *entry)
182{
183 struct ckch_inst *inst, *inst_s;
184
185 if (entry == NULL)
186 return;
187
188 ebpt_delete(&entry->node);
Willy Tarreau2b718102021-04-21 07:32:39 +0200189 LIST_DELETE(&entry->by_crtlist);
190 LIST_DELETE(&entry->by_ckch_store);
William Lallemand6e9556b2020-05-12 17:52:44 +0200191 crtlist_free_filters(entry->filters);
192 ssl_sock_free_ssl_conf(entry->ssl_conf);
193 free(entry->ssl_conf);
194 list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_crtlist_entry) {
195 ckch_inst_free(inst);
196 }
197 free(entry);
198}
William Lallemand5622c452020-09-10 19:08:49 +0200199/*
200 * Duplicate a crt_list entry and its content (ssl_conf, filters/fcount)
201 * Return a pointer to the new entry
202 */
203struct crtlist_entry *crtlist_entry_dup(struct crtlist_entry *src)
204{
205 struct crtlist_entry *entry;
206
207 if (src == NULL)
208 return NULL;
209
210 entry = crtlist_entry_new();
211 if (entry == NULL)
212 return NULL;
213
214 if (src->filters) {
215 entry->filters = crtlist_dup_filters(src->filters, src->fcount);
216 if (!entry->filters)
217 goto error;
218 }
219 entry->fcount = src->fcount;
220 if (src->ssl_conf) {
221 entry->ssl_conf = crtlist_dup_ssl_conf(src->ssl_conf);
222 if (!entry->ssl_conf)
223 goto error;
224 }
225 entry->crtlist = src->crtlist;
226
227 return entry;
228
229error:
230
231 crtlist_free_filters(entry->filters);
232 ssl_sock_free_ssl_conf(entry->ssl_conf);
233 free(entry->ssl_conf);
234 free(entry);
235
236 return NULL;
237}
William Lallemand6e9556b2020-05-12 17:52:44 +0200238
239/*
240 * Allocate and initialize a crtlist_entry
241 */
242struct crtlist_entry *crtlist_entry_new()
243{
244 struct crtlist_entry *entry;
245
246 entry = calloc(1, sizeof(*entry));
247 if (entry == NULL)
248 return NULL;
249
250 LIST_INIT(&entry->ckch_inst);
251
Willy Tarreau2b718102021-04-21 07:32:39 +0200252 /* initialize the nodes so we can LIST_DELETE in any cases */
William Lallemand6e9556b2020-05-12 17:52:44 +0200253 LIST_INIT(&entry->by_crtlist);
254 LIST_INIT(&entry->by_ckch_store);
255
256 return entry;
257}
258
259/* Free a crtlist, from the crt_entry to the content of the ssl_conf */
260void crtlist_free(struct crtlist *crtlist)
261{
262 struct crtlist_entry *entry, *s_entry;
William Lallemand6a3168a2020-06-23 11:43:35 +0200263 struct bind_conf_list *bind_conf_node;
William Lallemand6e9556b2020-05-12 17:52:44 +0200264
265 if (crtlist == NULL)
266 return;
267
William Lallemand6a3168a2020-06-23 11:43:35 +0200268 bind_conf_node = crtlist->bind_conf;
269 while (bind_conf_node) {
270 struct bind_conf_list *next = bind_conf_node->next;
271 free(bind_conf_node);
272 bind_conf_node = next;
273 }
274
William Lallemand6e9556b2020-05-12 17:52:44 +0200275 list_for_each_entry_safe(entry, s_entry, &crtlist->ord_entries, by_crtlist) {
276 crtlist_entry_free(entry);
277 }
278 ebmb_delete(&crtlist->node);
279 free(crtlist);
280}
281
282/* Alloc and initialize a struct crtlist
283 * <filename> is the key of the ebmb_node
284 * <unique> initialize the list of entries to be unique (1) or not (0)
285 */
286struct crtlist *crtlist_new(const char *filename, int unique)
287{
288 struct crtlist *newlist;
289
290 newlist = calloc(1, sizeof(*newlist) + strlen(filename) + 1);
291 if (newlist == NULL)
292 return NULL;
293
294 memcpy(newlist->node.key, filename, strlen(filename) + 1);
295 if (unique)
296 newlist->entries = EB_ROOT_UNIQUE;
297 else
298 newlist->entries = EB_ROOT;
299
300 LIST_INIT(&newlist->ord_entries);
301
302 return newlist;
303}
304
305/*
306 * Read a single crt-list line. /!\ alter the <line> string.
307 * Fill <crt_path> and <crtlist_entry>
308 * <crtlist_entry> must be alloc and free by the caller
309 * <crtlist_entry->ssl_conf> is alloc by the function
310 * <crtlist_entry->filters> is alloc by the function
311 * <crt_path> is a ptr in <line>
312 * Return an error code
313 */
Remi Tricot-Le Bretonfb00f312021-03-23 16:41:53 +0100314int 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 +0200315{
316 int cfgerr = 0;
317 int arg, newarg, cur_arg, i, ssl_b = 0, ssl_e = 0;
318 char *end;
319 char *args[MAX_CRT_ARGS + 1];
320 struct ssl_bind_conf *ssl_conf = NULL;
321
322 if (!line || !crt_path || !entry)
323 return ERR_ALERT | ERR_FATAL;
324
325 end = line + strlen(line);
326 if (end-line >= CRT_LINESIZE-1 && *(end-1) != '\n') {
327 /* Check if we reached the limit and the last char is not \n.
328 * Watch out for the last line without the terminating '\n'!
329 */
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200330 memprintf(err, "parsing [%s:%d]: line too long, limit is %d characters",
331 file, linenum, CRT_LINESIZE-1);
William Lallemand6e9556b2020-05-12 17:52:44 +0200332 cfgerr |= ERR_ALERT | ERR_FATAL;
333 goto error;
334 }
335 arg = 0;
336 newarg = 1;
337 while (*line) {
338 if (isspace((unsigned char)*line)) {
339 newarg = 1;
340 *line = 0;
341 } else if (*line == '[') {
342 if (ssl_b) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200343 memprintf(err, "parsing [%s:%d]: too many '['", file, linenum);
William Lallemand6e9556b2020-05-12 17:52:44 +0200344 cfgerr |= ERR_ALERT | ERR_FATAL;
345 goto error;
346 }
347 if (!arg) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200348 memprintf(err, "parsing [%s:%d]: file must start with a cert", file, linenum);
William Lallemand6e9556b2020-05-12 17:52:44 +0200349 cfgerr |= ERR_ALERT | ERR_FATAL;
350 goto error;
351 }
352 ssl_b = arg;
353 newarg = 1;
354 *line = 0;
355 } else if (*line == ']') {
356 if (ssl_e) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200357 memprintf(err, "parsing [%s:%d]: too many ']'", file, linenum);
William Lallemand6e9556b2020-05-12 17:52:44 +0200358 cfgerr |= ERR_ALERT | ERR_FATAL;
359 goto error;
360 }
361 if (!ssl_b) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200362 memprintf(err, "parsing [%s:%d]: missing '['", file, linenum);
William Lallemand6e9556b2020-05-12 17:52:44 +0200363 cfgerr |= ERR_ALERT | ERR_FATAL;
364 goto error;
365 }
366 ssl_e = arg;
367 newarg = 1;
368 *line = 0;
369 } else if (newarg) {
370 if (arg == MAX_CRT_ARGS) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200371 memprintf(err, "parsing [%s:%d]: too many args ", file, linenum);
William Lallemand6e9556b2020-05-12 17:52:44 +0200372 cfgerr |= ERR_ALERT | ERR_FATAL;
373 goto error;
374 }
375 newarg = 0;
376 args[arg++] = line;
377 }
378 line++;
379 }
380 args[arg++] = line;
381
382 /* empty line */
383 if (!*args[0]) {
384 cfgerr |= ERR_NONE;
385 goto error;
386 }
387
388 *crt_path = args[0];
389
390 if (ssl_b) {
William Lallemand32072012023-02-07 17:06:35 +0100391 if (ssl_b > 1) {
392 memprintf(err, "parsing [%s:%d]: malformated line, filters can't be between filename and options!", file, linenum);
393 cfgerr |= ERR_WARN;
394 }
395
William Lallemand6e9556b2020-05-12 17:52:44 +0200396 ssl_conf = calloc(1, sizeof *ssl_conf);
397 if (!ssl_conf) {
398 memprintf(err, "not enough memory!");
399 cfgerr |= ERR_ALERT | ERR_FATAL;
400 goto error;
401 }
402 }
Remi Tricot-Le Bretonfb00f312021-03-23 16:41:53 +0100403
William Lallemand6e9556b2020-05-12 17:52:44 +0200404 cur_arg = ssl_b ? ssl_b : 1;
405 while (cur_arg < ssl_e) {
406 newarg = 0;
407 for (i = 0; ssl_bind_kws[i].kw != NULL; i++) {
408 if (strcmp(ssl_bind_kws[i].kw, args[cur_arg]) == 0) {
409 newarg = 1;
Remi Tricot-Le Bretonfb00f312021-03-23 16:41:53 +0100410 cfgerr |= ssl_bind_kws[i].parse(args, cur_arg, NULL, ssl_conf, from_cli, err);
William Lallemand6e9556b2020-05-12 17:52:44 +0200411 if (cur_arg + 1 + ssl_bind_kws[i].skip > ssl_e) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200412 memprintf(err, "parsing [%s:%d]: ssl args out of '[]' for %s",
413 file, linenum, args[cur_arg]);
William Lallemand6e9556b2020-05-12 17:52:44 +0200414 cfgerr |= ERR_ALERT | ERR_FATAL;
415 goto error;
416 }
417 cur_arg += 1 + ssl_bind_kws[i].skip;
418 break;
419 }
420 }
421 if (!cfgerr && !newarg) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200422 memprintf(err, "parsing [%s:%d]: unknown ssl keyword %s",
423 file, linenum, args[cur_arg]);
William Lallemand6e9556b2020-05-12 17:52:44 +0200424 cfgerr |= ERR_ALERT | ERR_FATAL;
425 goto error;
426 }
427 }
428 entry->linenum = linenum;
429 entry->ssl_conf = ssl_conf;
430 entry->filters = crtlist_dup_filters(&args[cur_arg], arg - cur_arg - 1);
431 entry->fcount = arg - cur_arg - 1;
432
433 return cfgerr;
434
435error:
436 crtlist_free_filters(entry->filters);
437 entry->filters = NULL;
438 ssl_sock_free_ssl_conf(entry->ssl_conf);
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100439 ha_free(&entry->ssl_conf);
William Lallemand6e9556b2020-05-12 17:52:44 +0200440 return cfgerr;
441}
442
443
444
445/* This function parse a crt-list file and store it in a struct crtlist, each line is a crtlist_entry structure
446 * Fill the <crtlist> argument with a pointer to a new crtlist struct
447 *
448 * This function tries to open and store certificate files.
449 */
450int crtlist_parse_file(char *file, struct bind_conf *bind_conf, struct proxy *curproxy, struct crtlist **crtlist, char **err)
451{
452 struct crtlist *newlist;
453 struct crtlist_entry *entry = NULL;
454 char thisline[CRT_LINESIZE];
William Lallemand6e9556b2020-05-12 17:52:44 +0200455 FILE *f;
456 struct stat buf;
457 int linenum = 0;
458 int cfgerr = 0;
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200459 int missing_lf = -1;
William Lallemand6e9556b2020-05-12 17:52:44 +0200460
461 if ((f = fopen(file, "r")) == NULL) {
462 memprintf(err, "cannot open file '%s' : %s", file, strerror(errno));
463 return ERR_ALERT | ERR_FATAL;
464 }
465
466 newlist = crtlist_new(file, 0);
467 if (newlist == NULL) {
468 memprintf(err, "Not enough memory!");
469 cfgerr |= ERR_ALERT | ERR_FATAL;
470 goto error;
471 }
472
473 while (fgets(thisline, sizeof(thisline), f) != NULL) {
474 char *end;
475 char *line = thisline;
476 char *crt_path;
William Lallemand86c2dd62020-11-20 14:23:38 +0100477 char path[MAXPATHLEN+1];
William Lallemand6e9556b2020-05-12 17:52:44 +0200478 struct ckch_store *ckchs;
William Lallemand77e1c6f2020-11-20 18:26:09 +0100479 int found = 0;
William Lallemand6e9556b2020-05-12 17:52:44 +0200480
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200481 if (missing_lf != -1) {
482 memprintf(err, "parsing [%s:%d]: Stray NUL character at position %d.\n",
483 file, linenum, (missing_lf + 1));
484 cfgerr |= ERR_ALERT | ERR_FATAL;
485 missing_lf = -1;
486 break;
487 }
488
William Lallemand6e9556b2020-05-12 17:52:44 +0200489 linenum++;
490 end = line + strlen(line);
491 if (end-line == sizeof(thisline)-1 && *(end-1) != '\n') {
492 /* Check if we reached the limit and the last char is not \n.
493 * Watch out for the last line without the terminating '\n'!
494 */
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200495 memprintf(err, "parsing [%s:%d]: line too long, limit is %d characters",
496 file, linenum, (int)sizeof(thisline)-1);
William Lallemand6e9556b2020-05-12 17:52:44 +0200497 cfgerr |= ERR_ALERT | ERR_FATAL;
498 break;
499 }
500
501 if (*line == '#' || *line == '\n' || *line == '\r')
502 continue;
503
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200504 if (end > line && *(end-1) == '\n') {
505 /* kill trailing LF */
506 *(end - 1) = 0;
507 }
508 else {
509 /* mark this line as truncated */
510 missing_lf = end - line;
511 }
512
William Lallemand6e9556b2020-05-12 17:52:44 +0200513 entry = crtlist_entry_new();
514 if (entry == NULL) {
515 memprintf(err, "Not enough memory!");
516 cfgerr |= ERR_ALERT | ERR_FATAL;
517 goto error;
518 }
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200519
Remi Tricot-Le Bretonfb00f312021-03-23 16:41:53 +0100520 cfgerr |= crtlist_parse_line(thisline, &crt_path, entry, file, linenum, 0, err);
William Lallemand20b0fed2020-09-28 15:45:16 +0200521 if (cfgerr & ERR_CODE)
William Lallemand6e9556b2020-05-12 17:52:44 +0200522 goto error;
523
524 /* empty line */
525 if (!crt_path || !*crt_path) {
526 crtlist_entry_free(entry);
527 entry = NULL;
528 continue;
529 }
530
531 if (*crt_path != '/' && global_ssl.crt_base) {
Willy Tarreau326a8662022-05-09 10:31:28 +0200532 if ((strlen(global_ssl.crt_base) + 1 + strlen(crt_path)) > sizeof(path) ||
Willy Tarreau6c3cc562022-05-09 21:14:04 +0200533 snprintf(path, sizeof(path), "%s/%s", global_ssl.crt_base, crt_path) > sizeof(path)) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200534 memprintf(err, "parsing [%s:%d]: '%s' : path too long",
535 file, linenum, crt_path);
William Lallemand6e9556b2020-05-12 17:52:44 +0200536 cfgerr |= ERR_ALERT | ERR_FATAL;
537 goto error;
538 }
William Lallemand6e9556b2020-05-12 17:52:44 +0200539 crt_path = path;
540 }
541
542 /* Look for a ckch_store or create one */
543 ckchs = ckchs_lookup(crt_path);
544 if (ckchs == NULL) {
William Lallemand47da8212020-09-10 19:13:27 +0200545 if (stat(crt_path, &buf) == 0) {
William Lallemand77e1c6f2020-11-20 18:26:09 +0100546 found++;
William Lallemand47da8212020-09-10 19:13:27 +0200547
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200548 ckchs = ckchs_load_cert_file(crt_path, err);
William Lallemand47da8212020-09-10 19:13:27 +0200549 if (ckchs == NULL) {
550 cfgerr |= ERR_ALERT | ERR_FATAL;
551 goto error;
552 }
553
554 entry->node.key = ckchs;
555 entry->crtlist = newlist;
556 ebpt_insert(&newlist->entries, &entry->node);
Willy Tarreau2b718102021-04-21 07:32:39 +0200557 LIST_APPEND(&newlist->ord_entries, &entry->by_crtlist);
558 LIST_APPEND(&ckchs->crtlist_entry, &entry->by_ckch_store);
William Lallemand6e9556b2020-05-12 17:52:44 +0200559
William Lallemand73404572020-11-20 18:23:40 +0100560 } else if (global_ssl.extra_files & SSL_GF_BUNDLE) {
William Lallemand47da8212020-09-10 19:13:27 +0200561 /* If we didn't find the file, this could be a
William Lallemand51f784b2020-10-02 18:08:18 +0200562 bundle, since 2.3 we don't support multiple
563 certificate in the same OpenSSL store, so we
564 emulate it by loading each file separately. To
565 do so we need to duplicate the entry in the
566 crt-list because it becomes independent */
William Lallemand47da8212020-09-10 19:13:27 +0200567 char fp[MAXPATHLEN+1] = {0};
568 int n = 0;
569 struct crtlist_entry *entry_dup = entry; /* use the previous created entry */
570 for (n = 0; n < SSL_SOCK_NUM_KEYTYPES; n++) {
571 struct stat buf;
572 int ret;
573
William Lallemand86c2dd62020-11-20 14:23:38 +0100574 ret = snprintf(fp, sizeof(fp), "%s.%s", crt_path, SSL_SOCK_KEYTYPE_NAMES[n]);
William Lallemand47da8212020-09-10 19:13:27 +0200575 if (ret > sizeof(fp))
576 continue;
577
578 ckchs = ckchs_lookup(fp);
William Lallemand77e1c6f2020-11-20 18:26:09 +0100579 if (!ckchs) {
580 if (stat(fp, &buf) == 0) {
581 ckchs = ckchs_load_cert_file(fp, err);
582 if (!ckchs) {
William Lallemand47da8212020-09-10 19:13:27 +0200583 cfgerr |= ERR_ALERT | ERR_FATAL;
584 goto error;
585 }
William Lallemand77e1c6f2020-11-20 18:26:09 +0100586 } else {
587 continue; /* didn't find this extension, skip */
588 }
589 }
590 found++;
591 linenum++; /* we duplicate the line for this entry in the bundle */
592 if (!entry_dup) { /* if the entry was used, duplicate one */
593 linenum++;
594 entry_dup = crtlist_entry_dup(entry);
595 if (!entry_dup) {
596 cfgerr |= ERR_ALERT | ERR_FATAL;
597 goto error;
William Lallemand47da8212020-09-10 19:13:27 +0200598 }
William Lallemand77e1c6f2020-11-20 18:26:09 +0100599 entry_dup->linenum = linenum;
600 }
William Lallemand47da8212020-09-10 19:13:27 +0200601
William Lallemand77e1c6f2020-11-20 18:26:09 +0100602 entry_dup->node.key = ckchs;
603 entry_dup->crtlist = newlist;
604 ebpt_insert(&newlist->entries, &entry_dup->node);
Willy Tarreau2b718102021-04-21 07:32:39 +0200605 LIST_APPEND(&newlist->ord_entries, &entry_dup->by_crtlist);
606 LIST_APPEND(&ckchs->crtlist_entry, &entry_dup->by_ckch_store);
William Lallemand47da8212020-09-10 19:13:27 +0200607
William Lallemand77e1c6f2020-11-20 18:26:09 +0100608 entry_dup = NULL; /* the entry was used, we need a new one next round */
William Lallemand47da8212020-09-10 19:13:27 +0200609 }
William Lallemandb7fdfdf2020-12-04 15:45:02 +0100610#if HA_OPENSSL_VERSION_NUMBER < 0x10101000L
611 if (found) {
612 memprintf(err, "%sCan't load '%s'. Loading a multi certificates bundle requires OpenSSL >= 1.1.1\n",
613 err && *err ? *err : "", crt_path);
614 cfgerr |= ERR_ALERT | ERR_FATAL;
615 }
616#endif
William Lallemand47da8212020-09-10 19:13:27 +0200617 }
William Lallemand77e1c6f2020-11-20 18:26:09 +0100618 if (!found) {
619 memprintf(err, "%sunable to stat SSL certificate from file '%s' : %s.\n",
620 err && *err ? *err : "", crt_path, strerror(errno));
621 cfgerr |= ERR_ALERT | ERR_FATAL;
622 }
623
William Lallemand50c03aa2020-11-06 16:24:07 +0100624 } else {
625 entry->node.key = ckchs;
626 entry->crtlist = newlist;
627 ebpt_insert(&newlist->entries, &entry->node);
Willy Tarreau2b718102021-04-21 07:32:39 +0200628 LIST_APPEND(&newlist->ord_entries, &entry->by_crtlist);
629 LIST_APPEND(&ckchs->crtlist_entry, &entry->by_ckch_store);
William Lallemand77e1c6f2020-11-20 18:26:09 +0100630 found++;
William Lallemand47da8212020-09-10 19:13:27 +0200631 }
William Lallemand6e9556b2020-05-12 17:52:44 +0200632 entry = NULL;
633 }
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200634
635 if (missing_lf != -1) {
636 memprintf(err, "parsing [%s:%d]: Missing LF on last line, file might have been truncated at position %d.\n",
637 file, linenum, (missing_lf + 1));
638 cfgerr |= ERR_ALERT | ERR_FATAL;
639 }
640
William Lallemand6e9556b2020-05-12 17:52:44 +0200641 if (cfgerr & ERR_CODE)
642 goto error;
643
644 newlist->linecount = linenum;
645
646 fclose(f);
647 *crtlist = newlist;
648
649 return cfgerr;
650error:
651 crtlist_entry_free(entry);
652
653 fclose(f);
654 crtlist_free(newlist);
655 return cfgerr;
656}
657
658/* This function reads a directory and stores it in a struct crtlist, each file is a crtlist_entry structure
659 * Fill the <crtlist> argument with a pointer to a new crtlist struct
660 *
661 * This function tries to open and store certificate files.
662 */
663int crtlist_load_cert_dir(char *path, struct bind_conf *bind_conf, struct crtlist **crtlist, char **err)
664{
665 struct crtlist *dir;
666 struct dirent **de_list;
667 int i, n;
668 struct stat buf;
669 char *end;
670 char fp[MAXPATHLEN+1];
671 int cfgerr = 0;
672 struct ckch_store *ckchs;
William Lallemand6e9556b2020-05-12 17:52:44 +0200673
674 dir = crtlist_new(path, 1);
675 if (dir == NULL) {
676 memprintf(err, "not enough memory");
677 return ERR_ALERT | ERR_FATAL;
678 }
679
680 n = scandir(path, &de_list, 0, alphasort);
681 if (n < 0) {
682 memprintf(err, "%sunable to scan directory '%s' : %s.\n",
683 err && *err ? *err : "", path, strerror(errno));
684 cfgerr |= ERR_ALERT | ERR_FATAL;
685 }
686 else {
687 for (i = 0; i < n; i++) {
688 struct crtlist_entry *entry;
689 struct dirent *de = de_list[i];
690
691 end = strrchr(de->d_name, '.');
Tim Duesterhuse5ff1412021-01-02 22:31:53 +0100692 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 +0200693 goto ignore_entry;
694
695 snprintf(fp, sizeof(fp), "%s/%s", path, de->d_name);
696 if (stat(fp, &buf) != 0) {
697 memprintf(err, "%sunable to stat SSL certificate from file '%s' : %s.\n",
698 err && *err ? *err : "", fp, strerror(errno));
699 cfgerr |= ERR_ALERT | ERR_FATAL;
700 goto ignore_entry;
701 }
702 if (!S_ISREG(buf.st_mode))
703 goto ignore_entry;
704
705 entry = crtlist_entry_new();
706 if (entry == NULL) {
707 memprintf(err, "not enough memory '%s'", fp);
708 cfgerr |= ERR_ALERT | ERR_FATAL;
709 goto ignore_entry;
710 }
711
William Lallemand6e9556b2020-05-12 17:52:44 +0200712 ckchs = ckchs_lookup(fp);
713 if (ckchs == NULL)
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200714 ckchs = ckchs_load_cert_file(fp, err);
William Lallemand6e9556b2020-05-12 17:52:44 +0200715 if (ckchs == NULL) {
716 free(de);
717 free(entry);
718 cfgerr |= ERR_ALERT | ERR_FATAL;
719 goto end;
720 }
721 entry->node.key = ckchs;
722 entry->crtlist = dir;
Willy Tarreau2b718102021-04-21 07:32:39 +0200723 LIST_APPEND(&ckchs->crtlist_entry, &entry->by_ckch_store);
724 LIST_APPEND(&dir->ord_entries, &entry->by_crtlist);
William Lallemand6e9556b2020-05-12 17:52:44 +0200725 ebpt_insert(&dir->entries, &entry->node);
726
727ignore_entry:
728 free(de);
729 }
730end:
731 free(de_list);
732 }
733
734 if (cfgerr & ERR_CODE) {
735 /* free the dir and entries on error */
736 crtlist_free(dir);
737 } else {
738 *crtlist = dir;
739 }
740 return cfgerr;
741
742}
743
William Lallemandc756bbd2020-05-13 17:23:59 +0200744/*
745 * Take an ssl_bind_conf structure and append the configuration line used to
746 * create it in the buffer
747 */
748static void dump_crtlist_sslconf(struct buffer *buf, const struct ssl_bind_conf *conf)
749{
750 int space = 0;
751
752 if (conf == NULL)
753 return;
754
755 chunk_appendf(buf, " [");
756#ifdef OPENSSL_NPN_NEGOTIATED
757 if (conf->npn_str) {
758 int len = conf->npn_len;
759 char *ptr = conf->npn_str;
760 int comma = 0;
761
762 if (space) chunk_appendf(buf, " ");
763 chunk_appendf(buf, "npn ");
764 while (len) {
765 unsigned short size;
766
767 size = *ptr;
768 ptr++;
769 if (comma)
770 chunk_memcat(buf, ",", 1);
771 chunk_memcat(buf, ptr, size);
772 ptr += size;
773 len -= size + 1;
774 comma = 1;
775 }
776 chunk_memcat(buf, "", 1); /* finish with a \0 */
777 space++;
778 }
779#endif
780#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
781 if (conf->alpn_str) {
782 int len = conf->alpn_len;
783 char *ptr = conf->alpn_str;
784 int comma = 0;
785
786 if (space) chunk_appendf(buf, " ");
787 chunk_appendf(buf, "alpn ");
788 while (len) {
789 unsigned short size;
790
791 size = *ptr;
792 ptr++;
793 if (comma)
794 chunk_memcat(buf, ",", 1);
795 chunk_memcat(buf, ptr, size);
796 ptr += size;
797 len -= size + 1;
798 comma = 1;
799 }
800 chunk_memcat(buf, "", 1); /* finish with a \0 */
801 space++;
802 }
803#endif
804 /* verify */
805 {
806 if (conf->verify == SSL_SOCK_VERIFY_NONE) {
807 if (space) chunk_appendf(buf, " ");
808 chunk_appendf(buf, "verify none");
809 space++;
810 } else if (conf->verify == SSL_SOCK_VERIFY_OPTIONAL) {
811 if (space) chunk_appendf(buf, " ");
812 chunk_appendf(buf, "verify optional");
813 space++;
814 } else if (conf->verify == SSL_SOCK_VERIFY_REQUIRED) {
815 if (space) chunk_appendf(buf, " ");
816 chunk_appendf(buf, "verify required");
817 space++;
818 }
819 }
820
821 if (conf->no_ca_names) {
822 if (space) chunk_appendf(buf, " ");
823 chunk_appendf(buf, "no-ca-names");
824 space++;
825 }
826
827 if (conf->early_data) {
828 if (space) chunk_appendf(buf, " ");
829 chunk_appendf(buf, "allow-0rtt");
830 space++;
831 }
832 if (conf->ca_file) {
833 if (space) chunk_appendf(buf, " ");
834 chunk_appendf(buf, "ca-file %s", conf->ca_file);
835 space++;
836 }
837 if (conf->crl_file) {
838 if (space) chunk_appendf(buf, " ");
839 chunk_appendf(buf, "crl-file %s", conf->crl_file);
840 space++;
841 }
842 if (conf->ciphers) {
843 if (space) chunk_appendf(buf, " ");
844 chunk_appendf(buf, "ciphers %s", conf->ciphers);
845 space++;
846 }
Ilya Shipitsinf34ed0b2020-11-21 14:37:34 +0500847#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES
William Lallemandc756bbd2020-05-13 17:23:59 +0200848 if (conf->ciphersuites) {
849 if (space) chunk_appendf(buf, " ");
850 chunk_appendf(buf, "ciphersuites %s", conf->ciphersuites);
851 space++;
852 }
853#endif
854 if (conf->curves) {
855 if (space) chunk_appendf(buf, " ");
856 chunk_appendf(buf, "curves %s", conf->curves);
857 space++;
858 }
859 if (conf->ecdhe) {
860 if (space) chunk_appendf(buf, " ");
861 chunk_appendf(buf, "ecdhe %s", conf->ecdhe);
862 space++;
863 }
864
865 /* the crt-lists only support ssl-min-ver and ssl-max-ver */
William Lallemand8177ad92020-05-20 16:49:02 +0200866 if (conf->ssl_methods_cfg.min) {
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-min-ver %s", methodVersions[conf->ssl_methods_cfg.min].name);
William Lallemandc756bbd2020-05-13 17:23:59 +0200869 space++;
870 }
871
William Lallemand8177ad92020-05-20 16:49:02 +0200872 if (conf->ssl_methods_cfg.max) {
William Lallemandc756bbd2020-05-13 17:23:59 +0200873 if (space) chunk_appendf(buf, " ");
William Lallemand8177ad92020-05-20 16:49:02 +0200874 chunk_appendf(buf, "ssl-max-ver %s", methodVersions[conf->ssl_methods_cfg.max].name);
William Lallemandc756bbd2020-05-13 17:23:59 +0200875 space++;
876 }
877
878 chunk_appendf(buf, "]");
879
880 return;
881}
882
883/* dump a list of filters */
884static void dump_crtlist_filters(struct buffer *buf, struct crtlist_entry *entry)
885{
886 int i;
887
888 if (!entry->fcount)
889 return;
890
891 for (i = 0; i < entry->fcount; i++) {
892 chunk_appendf(buf, " %s", entry->filters[i]);
893 }
894 return;
895}
896
897/************************** CLI functions ****************************/
898
899
900/* CLI IO handler for '(show|dump) ssl crt-list' */
901static int cli_io_handler_dump_crtlist(struct appctx *appctx)
902{
903 struct buffer *trash = alloc_trash_chunk();
904 struct stream_interface *si = appctx->owner;
905 struct ebmb_node *lnode;
906
907 if (trash == NULL)
908 return 1;
909
910 /* dump the list of crt-lists */
911 lnode = appctx->ctx.cli.p1;
912 if (lnode == NULL)
913 lnode = ebmb_first(&crtlists_tree);
914 while (lnode) {
915 chunk_appendf(trash, "%s\n", lnode->key);
916 if (ci_putchk(si_ic(si), trash) == -1) {
917 si_rx_room_blk(si);
918 goto yield;
919 }
920 lnode = ebmb_next(lnode);
921 }
922 free_trash_chunk(trash);
923 return 1;
924yield:
925 appctx->ctx.cli.p1 = lnode;
926 free_trash_chunk(trash);
927 return 0;
928}
929
930/* CLI IO handler for '(show|dump) ssl crt-list <filename>' */
931static int cli_io_handler_dump_crtlist_entries(struct appctx *appctx)
932{
933 struct buffer *trash = alloc_trash_chunk();
934 struct crtlist *crtlist;
935 struct stream_interface *si = appctx->owner;
936 struct crtlist_entry *entry;
937
938 if (trash == NULL)
939 return 1;
940
941 crtlist = ebmb_entry(appctx->ctx.cli.p0, struct crtlist, node);
942
943 entry = appctx->ctx.cli.p1;
944 if (entry == NULL) {
945 entry = LIST_ELEM((crtlist->ord_entries).n, typeof(entry), by_crtlist);
946 chunk_appendf(trash, "# %s\n", crtlist->node.key);
947 if (ci_putchk(si_ic(si), trash) == -1) {
948 si_rx_room_blk(si);
949 goto yield;
950 }
951 }
952
953 list_for_each_entry_from(entry, &crtlist->ord_entries, by_crtlist) {
954 struct ckch_store *store;
955 const char *filename;
956
957 store = entry->node.key;
958 filename = store->path;
959 chunk_appendf(trash, "%s", filename);
960 if (appctx->ctx.cli.i0 == 's') /* show */
961 chunk_appendf(trash, ":%d", entry->linenum);
962 dump_crtlist_sslconf(trash, entry->ssl_conf);
963 dump_crtlist_filters(trash, entry);
964 chunk_appendf(trash, "\n");
965
966 if (ci_putchk(si_ic(si), trash) == -1) {
967 si_rx_room_blk(si);
968 goto yield;
969 }
970 }
971 free_trash_chunk(trash);
972 return 1;
973yield:
974 appctx->ctx.cli.p1 = entry;
975 free_trash_chunk(trash);
976 return 0;
977}
978
979/* CLI argument parser for '(show|dump) ssl crt-list' */
980static int cli_parse_dump_crtlist(char **args, char *payload, struct appctx *appctx, void *private)
981{
982 struct ebmb_node *lnode;
983 char *filename = NULL;
984 int mode;
William Lallemand99cc2182020-06-25 15:19:51 +0200985 char *end;
William Lallemandc756bbd2020-05-13 17:23:59 +0200986
987 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
988 return 1;
989
990 appctx->ctx.cli.p0 = NULL;
991 appctx->ctx.cli.p1 = NULL;
992
Tim Duesterhuse5ff1412021-01-02 22:31:53 +0100993 if (*args[3] && strcmp(args[3], "-n") == 0) {
William Lallemandc756bbd2020-05-13 17:23:59 +0200994 mode = 's';
995 filename = args[4];
996 } else {
997 mode = 'd';
998 filename = args[3];
999 }
1000
1001 if (mode == 's' && !*args[4])
1002 return cli_err(appctx, "'show ssl crt-list -n' expects a filename or a directory\n");
1003
1004 if (filename && *filename) {
William Lallemand99cc2182020-06-25 15:19:51 +02001005
1006
1007 /* strip trailing slashes, including first one */
1008 for (end = filename + strlen(filename) - 1; end >= filename && *end == '/'; end--)
1009 *end = 0;
1010
William Lallemandc756bbd2020-05-13 17:23:59 +02001011 lnode = ebst_lookup(&crtlists_tree, filename);
1012 if (lnode == NULL)
1013 return cli_err(appctx, "didn't find the specified filename\n");
1014
1015 appctx->ctx.cli.p0 = lnode;
1016 appctx->io_handler = cli_io_handler_dump_crtlist_entries;
1017 }
1018 appctx->ctx.cli.i0 = mode;
1019
1020 return 0;
1021}
1022
1023/* release function of the "add ssl crt-list' command, free things and unlock
1024 the spinlock */
1025static void cli_release_add_crtlist(struct appctx *appctx)
1026{
1027 struct crtlist_entry *entry = appctx->ctx.cli.p1;
1028
Christopher Faulet59b4ef52022-06-01 16:31:09 +02001029 if (entry) {
William Lallemandc756bbd2020-05-13 17:23:59 +02001030 struct ckch_inst *inst, *inst_s;
Christopher Faulet59b4ef52022-06-01 16:31:09 +02001031
William Lallemandc756bbd2020-05-13 17:23:59 +02001032 /* upon error free the ckch_inst and everything inside */
1033 ebpt_delete(&entry->node);
Willy Tarreau2b718102021-04-21 07:32:39 +02001034 LIST_DELETE(&entry->by_crtlist);
1035 LIST_DELETE(&entry->by_ckch_store);
William Lallemandc756bbd2020-05-13 17:23:59 +02001036
1037 list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_ckchs) {
1038 ckch_inst_free(inst);
1039 }
1040 crtlist_free_filters(entry->filters);
1041 ssl_sock_free_ssl_conf(entry->ssl_conf);
1042 free(entry->ssl_conf);
1043 free(entry);
1044 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001045 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
Christopher Faulet59b4ef52022-06-01 16:31:09 +02001046 ha_free(&appctx->ctx.cli.err);
William Lallemandc756bbd2020-05-13 17:23:59 +02001047}
1048
1049
1050/* IO Handler for the "add ssl crt-list" command It adds a new entry in the
1051 * crt-list and generates the ckch_insts for each bind_conf that uses this crt-list
1052 *
1053 * The logic is the same as the "commit ssl cert" command but without the
1054 * freeing of the old structures, because there are none.
1055 */
1056static int cli_io_handler_add_crtlist(struct appctx *appctx)
1057{
1058 struct bind_conf_list *bind_conf_node;
1059 struct stream_interface *si = appctx->owner;
1060 struct crtlist *crtlist = appctx->ctx.cli.p0;
1061 struct crtlist_entry *entry = appctx->ctx.cli.p1;
1062 struct ckch_store *store = entry->node.key;
William Lallemandc756bbd2020-05-13 17:23:59 +02001063 struct ckch_inst *new_inst;
William Lallemandc756bbd2020-05-13 17:23:59 +02001064 int i = 0;
1065 int errcode = 0;
1066
William Lallemandc756bbd2020-05-13 17:23:59 +02001067 /* for each bind_conf which use the crt-list, a new ckch_inst must be
1068 * created.
1069 */
1070 if (unlikely(si_ic(si)->flags & (CF_WRITE_ERROR|CF_SHUTW)))
Christopher Faulet59b4ef52022-06-01 16:31:09 +02001071 goto end;
William Lallemandc756bbd2020-05-13 17:23:59 +02001072
1073 while (1) {
1074 switch (appctx->st2) {
1075 case SETCERT_ST_INIT:
1076 /* This state just print the update message */
Christopher Faulet59b4ef52022-06-01 16:31:09 +02001077 chunk_printf(&trash, "Inserting certificate '%s' in crt-list '%s'", store->path, crtlist->node.key);
1078 if (ci_putchk(si_ic(si), &trash) == -1)
William Lallemandc756bbd2020-05-13 17:23:59 +02001079 goto yield;
William Lallemandc756bbd2020-05-13 17:23:59 +02001080 appctx->st2 = SETCERT_ST_GEN;
1081 /* fallthrough */
1082 case SETCERT_ST_GEN:
1083 bind_conf_node = appctx->ctx.cli.p2; /* get the previous ptr from the yield */
1084 if (bind_conf_node == NULL)
1085 bind_conf_node = crtlist->bind_conf;
1086 for (; bind_conf_node; bind_conf_node = bind_conf_node->next) {
1087 struct bind_conf *bind_conf = bind_conf_node->bind_conf;
1088 struct sni_ctx *sni;
1089
Christopher Faulet59b4ef52022-06-01 16:31:09 +02001090 appctx->ctx.cli.p2 = bind_conf_node;
1091
William Lallemandc756bbd2020-05-13 17:23:59 +02001092 /* yield every 10 generations */
1093 if (i > 10) {
Christopher Faulet59b4ef52022-06-01 16:31:09 +02001094 si_rx_endp_more(si); /* let's come back later */
William Lallemandc756bbd2020-05-13 17:23:59 +02001095 goto yield;
1096 }
1097
Christopher Faulet59b4ef52022-06-01 16:31:09 +02001098 /* display one dot for each new instance */
1099 if (ci_putstr(si_ic(si), ".") == -1)
1100 goto yield;
1101
William Lallemandc756bbd2020-05-13 17:23:59 +02001102 /* we don't support multi-cert bundles, only simple ones */
Christopher Faulet59b4ef52022-06-01 16:31:09 +02001103 appctx->ctx.cli.err = NULL;
1104 errcode |= ckch_inst_new_load_store(store->path, store, bind_conf, entry->ssl_conf, entry->filters, entry->fcount, &new_inst, &appctx->ctx.cli.err);
1105 if (errcode & ERR_CODE) {
1106 appctx->st2 = SETCERT_ST_ERROR;
William Lallemandc756bbd2020-05-13 17:23:59 +02001107 goto error;
Christopher Faulet59b4ef52022-06-01 16:31:09 +02001108 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001109
1110 /* we need to initialize the SSL_CTX generated */
1111 /* this iterate on the newly generated SNIs in the new instance to prepare their SSL_CTX */
1112 list_for_each_entry(sni, &new_inst->sni_ctx, by_ckch_inst) {
1113 if (!sni->order) { /* we initialized only the first SSL_CTX because it's the same in the other sni_ctx's */
Christopher Faulet59b4ef52022-06-01 16:31:09 +02001114 appctx->ctx.cli.err = NULL;
1115 errcode |= ssl_sock_prepare_ctx(bind_conf, new_inst->ssl_conf, sni->ctx, &appctx->ctx.cli.err);
1116 if (errcode & ERR_CODE) {
1117 appctx->st2 = SETCERT_ST_ERROR;
William Lallemandc756bbd2020-05-13 17:23:59 +02001118 goto error;
Christopher Faulet59b4ef52022-06-01 16:31:09 +02001119 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001120 }
1121 }
Christopher Faulet59b4ef52022-06-01 16:31:09 +02001122
William Lallemandc756bbd2020-05-13 17:23:59 +02001123 i++;
Willy Tarreau2b718102021-04-21 07:32:39 +02001124 LIST_APPEND(&store->ckch_inst, &new_inst->by_ckchs);
1125 LIST_APPEND(&entry->ckch_inst, &new_inst->by_crtlist_entry);
William Lallemand9ab8f8d2020-06-24 01:00:52 +02001126 new_inst->crtlist_entry = entry;
William Lallemandc756bbd2020-05-13 17:23:59 +02001127 }
1128 appctx->st2 = SETCERT_ST_INSERT;
1129 /* fallthrough */
1130 case SETCERT_ST_INSERT:
William Lallemand31d463d2022-06-20 16:51:53 +02001131 /* the insertion is called for every instance of the store, not
1132 * only the one we generated.
1133 * But the ssl_sock_load_cert_sni() skip the sni already
1134 * inserted. Not every instance has a bind_conf, it could be
1135 * the store of a server so we should be careful */
1136
William Lallemandc756bbd2020-05-13 17:23:59 +02001137 list_for_each_entry(new_inst, &store->ckch_inst, by_ckchs) {
William Lallemand31d463d2022-06-20 16:51:53 +02001138 if (!new_inst->bind_conf) /* this is a server instance */
1139 continue;
William Lallemandc756bbd2020-05-13 17:23:59 +02001140 HA_RWLOCK_WRLOCK(SNI_LOCK, &new_inst->bind_conf->sni_lock);
1141 ssl_sock_load_cert_sni(new_inst, new_inst->bind_conf);
1142 HA_RWLOCK_WRUNLOCK(SNI_LOCK, &new_inst->bind_conf->sni_lock);
1143 }
1144 entry->linenum = ++crtlist->linecount;
Christopher Faulet59b4ef52022-06-01 16:31:09 +02001145 appctx->ctx.cli.p1 = NULL;
1146 appctx->st2 = SETCERT_ST_SUCCESS;
1147 /* fallthrough */
1148 case SETCERT_ST_SUCCESS:
1149 chunk_reset(&trash);
1150 chunk_appendf(&trash, "\n");
1151 if (appctx->ctx.cli.err)
1152 chunk_appendf(&trash, "%s", appctx->ctx.cli.err);
1153 chunk_appendf(&trash, "Success!\n");
1154 if (ci_putchk(si_ic(si), &trash) == -1)
1155 goto yield;
William Lallemandc756bbd2020-05-13 17:23:59 +02001156 appctx->st2 = SETCERT_ST_FIN;
1157 goto end;
Christopher Faulet59b4ef52022-06-01 16:31:09 +02001158
1159 case SETCERT_ST_ERROR:
1160 error:
1161 chunk_printf(&trash, "\n%sFailed!\n", appctx->ctx.cli.err);
1162 if (ci_putchk(si_ic(si), &trash) == -1)
1163 goto yield;
1164 goto end;
1165
1166 default:
1167 goto end;
William Lallemandc756bbd2020-05-13 17:23:59 +02001168 }
1169 }
1170
1171end:
William Lallemandc756bbd2020-05-13 17:23:59 +02001172 /* success: call the release function and don't come back */
1173 return 1;
1174yield:
Christopher Faulet59b4ef52022-06-01 16:31:09 +02001175 si_rx_room_blk(si);
William Lallemandc756bbd2020-05-13 17:23:59 +02001176 return 0; /* should come back */
William Lallemandc756bbd2020-05-13 17:23:59 +02001177}
1178
1179
1180/*
1181 * Parse a "add ssl crt-list <crt-list> <certfile>" line.
1182 * Filters and option must be passed through payload:
1183 */
1184static int cli_parse_add_crtlist(char **args, char *payload, struct appctx *appctx, void *private)
1185{
1186 int cfgerr = 0;
1187 struct ckch_store *store;
1188 char *err = NULL;
1189 char path[MAXPATHLEN+1];
1190 char *crtlist_path;
1191 char *cert_path = NULL;
1192 struct ebmb_node *eb;
1193 struct ebpt_node *inserted;
1194 struct crtlist *crtlist;
1195 struct crtlist_entry *entry = NULL;
William Lallemand99cc2182020-06-25 15:19:51 +02001196 char *end;
William Lallemandc756bbd2020-05-13 17:23:59 +02001197
1198 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
1199 return 1;
1200
1201 if (!*args[3] || (!payload && !*args[4]))
1202 return cli_err(appctx, "'add ssl crtlist' expects a filename and a certificate name\n");
1203
1204 crtlist_path = args[3];
1205
William Lallemand99cc2182020-06-25 15:19:51 +02001206 /* strip trailing slashes, including first one */
1207 for (end = crtlist_path + strlen(crtlist_path) - 1; end >= crtlist_path && *end == '/'; end--)
1208 *end = 0;
1209
William Lallemandc756bbd2020-05-13 17:23:59 +02001210 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
1211 return cli_err(appctx, "Operations on certificates are currently locked!\n");
1212
1213 eb = ebst_lookup(&crtlists_tree, crtlist_path);
1214 if (!eb) {
1215 memprintf(&err, "crt-list '%s' does not exist!", crtlist_path);
1216 goto error;
1217 }
1218 crtlist = ebmb_entry(eb, struct crtlist, node);
1219
1220 entry = crtlist_entry_new();
1221 if (entry == NULL) {
1222 memprintf(&err, "Not enough memory!");
1223 goto error;
1224 }
1225
1226 if (payload) {
1227 char *lf;
1228
1229 lf = strrchr(payload, '\n');
1230 if (lf) {
1231 memprintf(&err, "only one line of payload is supported!");
1232 goto error;
1233 }
1234 /* cert_path is filled here */
Remi Tricot-Le Bretonfb00f312021-03-23 16:41:53 +01001235 cfgerr |= crtlist_parse_line(payload, &cert_path, entry, "CLI", 1, 1, &err);
William Lallemandc756bbd2020-05-13 17:23:59 +02001236 if (cfgerr & ERR_CODE)
1237 goto error;
1238 } else {
1239 cert_path = args[4];
1240 }
1241
1242 if (!cert_path) {
1243 memprintf(&err, "'add ssl crtlist' should contain the certificate name in the payload");
1244 cfgerr |= ERR_ALERT | ERR_FATAL;
1245 goto error;
1246 }
1247
1248 if (eb_gettag(crtlist->entries.b[EB_RGHT])) {
1249 char *slash;
1250
1251 slash = strrchr(cert_path, '/');
1252 if (!slash) {
1253 memprintf(&err, "'%s' is a directory, certificate path '%s' must contain the directory path", (char *)crtlist->node.key, cert_path);
1254 goto error;
1255 }
1256 /* temporary replace / by 0 to do an strcmp */
1257 *slash = '\0';
1258 if (strcmp(cert_path, (char*)crtlist->node.key) != 0) {
1259 *slash = '/';
1260 memprintf(&err, "'%s' is a directory, certificate path '%s' must contain the directory path", (char *)crtlist->node.key, cert_path);
1261 goto error;
1262 }
1263 *slash = '/';
1264 }
1265
1266 if (*cert_path != '/' && global_ssl.crt_base) {
Willy Tarreau326a8662022-05-09 10:31:28 +02001267 if ((strlen(global_ssl.crt_base) + 1 + strlen(cert_path)) > sizeof(path) ||
1268 snprintf(path, sizeof(path), "%s/%s", global_ssl.crt_base, cert_path) > sizeof(path)) {
William Lallemandc756bbd2020-05-13 17:23:59 +02001269 memprintf(&err, "'%s' : path too long", cert_path);
1270 cfgerr |= ERR_ALERT | ERR_FATAL;
1271 goto error;
1272 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001273 cert_path = path;
1274 }
1275
1276 store = ckchs_lookup(cert_path);
1277 if (store == NULL) {
1278 memprintf(&err, "certificate '%s' does not exist!", cert_path);
1279 goto error;
1280 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001281 if (store->ckch == NULL || store->ckch->cert == NULL) {
1282 memprintf(&err, "certificate '%s' is empty!", cert_path);
1283 goto error;
1284 }
1285
1286 /* check if it's possible to insert this new crtlist_entry */
1287 entry->node.key = store;
1288 inserted = ebpt_insert(&crtlist->entries, &entry->node);
1289 if (inserted != &entry->node) {
1290 memprintf(&err, "file already exists in this directory!");
1291 goto error;
1292 }
1293
1294 /* this is supposed to be a directory (EB_ROOT_UNIQUE), so no ssl_conf are allowed */
1295 if ((entry->ssl_conf || entry->filters) && eb_gettag(crtlist->entries.b[EB_RGHT])) {
1296 memprintf(&err, "this is a directory, SSL configuration and filters are not allowed");
1297 goto error;
1298 }
1299
Willy Tarreau2b718102021-04-21 07:32:39 +02001300 LIST_APPEND(&crtlist->ord_entries, &entry->by_crtlist);
William Lallemandc756bbd2020-05-13 17:23:59 +02001301 entry->crtlist = crtlist;
Willy Tarreau2b718102021-04-21 07:32:39 +02001302 LIST_APPEND(&store->crtlist_entry, &entry->by_ckch_store);
William Lallemandc756bbd2020-05-13 17:23:59 +02001303
1304 appctx->st2 = SETCERT_ST_INIT;
1305 appctx->ctx.cli.p0 = crtlist;
1306 appctx->ctx.cli.p1 = entry;
1307
1308 /* unlock is done in the release handler */
1309 return 0;
1310
1311error:
1312 crtlist_entry_free(entry);
1313 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1314 err = memprintf(&err, "Can't edit the crt-list: %s\n", err ? err : "");
1315 return cli_dynerr(appctx, err);
1316}
1317
1318/* Parse a "del ssl crt-list <crt-list> <certfile>" line. */
1319static int cli_parse_del_crtlist(char **args, char *payload, struct appctx *appctx, void *private)
1320{
1321 struct ckch_store *store;
1322 char *err = NULL;
1323 char *crtlist_path, *cert_path;
1324 struct ebmb_node *ebmb;
1325 struct ebpt_node *ebpt;
1326 struct crtlist *crtlist;
1327 struct crtlist_entry *entry = NULL;
1328 struct ckch_inst *inst, *inst_s;
1329 int linenum = 0;
1330 char *colons;
William Lallemand99cc2182020-06-25 15:19:51 +02001331 char *end;
Remi Tricot-Le Bretonbc2c3862021-03-26 10:47:50 +01001332 int error_message_dumped = 0;
William Lallemandc756bbd2020-05-13 17:23:59 +02001333
1334 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
1335 return 1;
1336
1337 if (!*args[3] || !*args[4])
1338 return cli_err(appctx, "'del ssl crtlist' expects a filename and a certificate name\n");
1339
1340 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
1341 return cli_err(appctx, "Can't delete!\nOperations on certificates are currently locked!\n");
1342
1343 crtlist_path = args[3];
1344 cert_path = args[4];
1345
1346 colons = strchr(cert_path, ':');
1347 if (colons) {
1348 char *endptr;
1349
1350 linenum = strtol(colons + 1, &endptr, 10);
1351 if (colons + 1 == endptr || *endptr != '\0') {
1352 memprintf(&err, "wrong line number after colons in '%s'!", cert_path);
1353 goto error;
1354 }
1355 *colons = '\0';
1356 }
William Lallemand99cc2182020-06-25 15:19:51 +02001357
1358 /* strip trailing slashes, including first one */
1359 for (end = crtlist_path + strlen(crtlist_path) - 1; end >= crtlist_path && *end == '/'; end--)
1360 *end = 0;
1361
William Lallemandc756bbd2020-05-13 17:23:59 +02001362 /* look for crtlist */
1363 ebmb = ebst_lookup(&crtlists_tree, crtlist_path);
1364 if (!ebmb) {
1365 memprintf(&err, "crt-list '%s' does not exist!", crtlist_path);
1366 goto error;
1367 }
1368 crtlist = ebmb_entry(ebmb, struct crtlist, node);
1369
1370 /* look for store */
1371 store = ckchs_lookup(cert_path);
1372 if (store == NULL) {
1373 memprintf(&err, "certificate '%s' does not exist!", cert_path);
1374 goto error;
1375 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001376 if (store->ckch == NULL || store->ckch->cert == NULL) {
1377 memprintf(&err, "certificate '%s' is empty!", cert_path);
1378 goto error;
1379 }
1380
1381 ebpt = ebpt_lookup(&crtlist->entries, store);
1382 if (!ebpt) {
1383 memprintf(&err, "certificate '%s' can't be found in crt-list '%s'!", cert_path, crtlist_path);
1384 goto error;
1385 }
1386
1387 /* list the line number of entries for errors in err, and select the right ebpt */
1388 for (; ebpt; ebpt = ebpt_next_dup(ebpt)) {
1389 struct crtlist_entry *tmp;
1390
1391 tmp = ebpt_entry(ebpt, struct crtlist_entry, node);
1392 memprintf(&err, "%s%s%d", err ? err : "", err ? ", " : "", tmp->linenum);
1393
1394 /* select the entry we wanted */
1395 if (linenum == 0 || tmp->linenum == linenum) {
1396 if (!entry)
1397 entry = tmp;
1398 }
1399 }
1400
1401 /* we didn't found the specified entry */
1402 if (!entry) {
1403 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);
1404 goto error;
1405 }
1406
1407 /* we didn't specified a line number but there were several entries */
1408 if (linenum == 0 && ebpt_next_dup(&entry->node)) {
1409 memprintf(&err, "found the certificate '%s' in several entries, please specify a line number preceded by colons (%s)!", cert_path, err ? err : NULL);
1410 goto error;
1411 }
1412
Remi Tricot-Le Bretonbc2c3862021-03-26 10:47:50 +01001413 /* Iterate over all the instances in order to see if any of them is a
1414 * default instance. If this is the case, the entry won't be suppressed. */
1415 list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_crtlist_entry) {
1416 if (inst->is_default && !inst->bind_conf->strict_sni) {
1417 if (!error_message_dumped) {
1418 memprintf(&err, "certificate '%s' cannot be deleted, it is used as default certificate by the following frontends:\n", cert_path);
1419 error_message_dumped = 1;
1420 }
1421 memprintf(&err, "%s\t- %s:%d\n", err, inst->bind_conf->file, inst->bind_conf->line);
1422 }
1423 }
1424 if (error_message_dumped)
1425 goto error;
1426
William Lallemandc756bbd2020-05-13 17:23:59 +02001427 /* upon error free the ckch_inst and everything inside */
1428
1429 ebpt_delete(&entry->node);
Willy Tarreau2b718102021-04-21 07:32:39 +02001430 LIST_DELETE(&entry->by_crtlist);
1431 LIST_DELETE(&entry->by_ckch_store);
William Lallemandc756bbd2020-05-13 17:23:59 +02001432
1433 list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_crtlist_entry) {
1434 struct sni_ctx *sni, *sni_s;
1435
1436 HA_RWLOCK_WRLOCK(SNI_LOCK, &inst->bind_conf->sni_lock);
1437 list_for_each_entry_safe(sni, sni_s, &inst->sni_ctx, by_ckch_inst) {
1438 ebmb_delete(&sni->name);
Willy Tarreau2b718102021-04-21 07:32:39 +02001439 LIST_DELETE(&sni->by_ckch_inst);
William Lallemandc756bbd2020-05-13 17:23:59 +02001440 SSL_CTX_free(sni->ctx);
1441 free(sni);
1442 }
1443 HA_RWLOCK_WRUNLOCK(SNI_LOCK, &inst->bind_conf->sni_lock);
Willy Tarreau2b718102021-04-21 07:32:39 +02001444 LIST_DELETE(&inst->by_ckchs);
Remi Tricot-Le Breton1ae47ec2024-02-07 16:38:42 +01001445 ckch_inst_free(inst);
William Lallemandc756bbd2020-05-13 17:23:59 +02001446 }
1447
1448 crtlist_free_filters(entry->filters);
1449 ssl_sock_free_ssl_conf(entry->ssl_conf);
1450 free(entry->ssl_conf);
1451 free(entry);
1452
1453 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1454 err = memprintf(&err, "Entry '%s' deleted in crtlist '%s'!\n", cert_path, crtlist_path);
1455 return cli_dynmsg(appctx, LOG_NOTICE, err);
1456
1457error:
1458 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1459 err = memprintf(&err, "Can't delete the entry: %s\n", err ? err : "");
1460 return cli_dynerr(appctx, err);
1461}
1462
1463
William Lallemandee8530c2020-06-23 18:19:42 +02001464/* unlink and free all crt-list and crt-list entries */
1465void crtlist_deinit()
1466{
1467 struct eb_node *node, *next;
1468 struct crtlist *crtlist;
1469
1470 node = eb_first(&crtlists_tree);
1471 while (node) {
1472 next = eb_next(node);
1473 crtlist = ebmb_entry(node, struct crtlist, node);
1474 crtlist_free(crtlist);
1475 node = next;
1476 }
1477}
1478
William Lallemandc756bbd2020-05-13 17:23:59 +02001479
1480/* register cli keywords */
1481static struct cli_kw_list cli_kws = {{ },{
Willy Tarreaub205bfd2021-05-07 11:38:37 +02001482 { { "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 },
1483 { { "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 },
1484 { { "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 +02001485 { { NULL }, NULL, NULL, NULL } }
1486};
1487
1488INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws);
1489