blob: 4d17b01d870b9e51c9ca5458b429f9f467404cf4 [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
Willy Tarreau6b6c3632022-05-05 13:43:49 +020041/* CLI context for "add ssl crt-list" */
42struct add_crtlist_ctx {
43 struct crtlist *crtlist;
44 struct crtlist_entry *entry;
45 struct bind_conf_list *bind_conf_node;
46};
47
William Lallemand6e9556b2020-05-12 17:52:44 +020048/* release ssl bind conf */
49void ssl_sock_free_ssl_conf(struct ssl_bind_conf *conf)
50{
51 if (conf) {
52#if defined(OPENSSL_NPN_NEGOTIATED) && !defined(OPENSSL_NO_NEXTPROTONEG)
Willy Tarreau61cfdf42021-02-20 10:46:51 +010053 ha_free(&conf->npn_str);
William Lallemand6e9556b2020-05-12 17:52:44 +020054#endif
55#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
Willy Tarreau61cfdf42021-02-20 10:46:51 +010056 ha_free(&conf->alpn_str);
William Lallemand6e9556b2020-05-12 17:52:44 +020057#endif
Willy Tarreau61cfdf42021-02-20 10:46:51 +010058 ha_free(&conf->ca_file);
59 ha_free(&conf->ca_verify_file);
60 ha_free(&conf->crl_file);
61 ha_free(&conf->ciphers);
Ilya Shipitsinf34ed0b2020-11-21 14:37:34 +050062#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES
Willy Tarreau61cfdf42021-02-20 10:46:51 +010063 ha_free(&conf->ciphersuites);
William Lallemand6e9556b2020-05-12 17:52:44 +020064#endif
Willy Tarreau61cfdf42021-02-20 10:46:51 +010065 ha_free(&conf->curves);
66 ha_free(&conf->ecdhe);
William Lallemand6e9556b2020-05-12 17:52:44 +020067 }
68}
69
William Lallemand82f2d2f2020-09-10 19:06:43 +020070/*
71 * Allocate and copy a ssl_bind_conf structure
72 */
73struct ssl_bind_conf *crtlist_dup_ssl_conf(struct ssl_bind_conf *src)
74{
75 struct ssl_bind_conf *dst;
76
77 if (!src)
78 return NULL;
79
80 dst = calloc(1, sizeof(*dst));
81 if (!dst)
82 return NULL;
83
84#if defined(OPENSSL_NPN_NEGOTIATED) && !defined(OPENSSL_NO_NEXTPROTONEG)
85 if (src->npn_str) {
86 dst->npn_str = strdup(src->npn_str);
87 if (!dst->npn_str)
88 goto error;
89 }
90#endif
91#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
92 if (src->alpn_str) {
93 dst->alpn_str = strdup(src->alpn_str);
94 if (!dst->alpn_str)
95 goto error;
96 }
97#endif
98 if (src->ca_file) {
99 dst->ca_file = strdup(src->ca_file);
100 if (!dst->ca_file)
101 goto error;
102 }
103 if (src->ca_verify_file) {
104 dst->ca_verify_file = strdup(src->ca_verify_file);
105 if (!dst->ca_verify_file)
106 goto error;
107 }
108 if (src->crl_file) {
109 dst->crl_file = strdup(src->crl_file);
110 if (!dst->crl_file)
111 goto error;
112 }
113 if (src->ciphers) {
114 dst->ciphers = strdup(src->ciphers);
115 if (!dst->ciphers)
116 goto error;
117 }
Ilya Shipitsinf34ed0b2020-11-21 14:37:34 +0500118#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES
William Lallemand82f2d2f2020-09-10 19:06:43 +0200119 if (src->ciphersuites) {
120 dst->ciphersuites = strdup(src->ciphersuites);
121 if (!dst->ciphersuites)
122 goto error;
123 }
124#endif
125 if (src->curves) {
126 dst->curves = strdup(src->curves);
127 if (!dst->curves)
128 goto error;
129 }
130 if (src->ecdhe) {
131 dst->ecdhe = strdup(src->ecdhe);
132 if (!dst->ecdhe)
133 goto error;
134 }
135 return dst;
136
137error:
138 ssl_sock_free_ssl_conf(dst);
139 free(dst);
140
141 return NULL;
142}
William Lallemand6e9556b2020-05-12 17:52:44 +0200143
144/* free sni filters */
145void crtlist_free_filters(char **args)
146{
147 int i;
148
149 if (!args)
150 return;
151
152 for (i = 0; args[i]; i++)
153 free(args[i]);
154
155 free(args);
156}
157
158/* Alloc and duplicate a char ** array */
159char **crtlist_dup_filters(char **args, int fcount)
160{
161 char **dst;
162 int i;
163
164 if (fcount == 0)
165 return NULL;
166
167 dst = calloc(fcount + 1, sizeof(*dst));
168 if (!dst)
169 return NULL;
170
171 for (i = 0; i < fcount; i++) {
172 dst[i] = strdup(args[i]);
173 if (!dst[i])
174 goto error;
175 }
176 return dst;
177
178error:
179 crtlist_free_filters(dst);
180 return NULL;
181}
182
183/*
184 * Detach and free a crtlist_entry.
185 * Free the filters, the ssl_conf and call ckch_inst_free() for each ckch_inst
186 */
187void crtlist_entry_free(struct crtlist_entry *entry)
188{
189 struct ckch_inst *inst, *inst_s;
190
191 if (entry == NULL)
192 return;
193
194 ebpt_delete(&entry->node);
Willy Tarreau2b718102021-04-21 07:32:39 +0200195 LIST_DELETE(&entry->by_crtlist);
196 LIST_DELETE(&entry->by_ckch_store);
William Lallemand6e9556b2020-05-12 17:52:44 +0200197 crtlist_free_filters(entry->filters);
198 ssl_sock_free_ssl_conf(entry->ssl_conf);
199 free(entry->ssl_conf);
200 list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_crtlist_entry) {
201 ckch_inst_free(inst);
202 }
203 free(entry);
204}
William Lallemand5622c452020-09-10 19:08:49 +0200205/*
206 * Duplicate a crt_list entry and its content (ssl_conf, filters/fcount)
207 * Return a pointer to the new entry
208 */
209struct crtlist_entry *crtlist_entry_dup(struct crtlist_entry *src)
210{
211 struct crtlist_entry *entry;
212
213 if (src == NULL)
214 return NULL;
215
216 entry = crtlist_entry_new();
217 if (entry == NULL)
218 return NULL;
219
220 if (src->filters) {
221 entry->filters = crtlist_dup_filters(src->filters, src->fcount);
222 if (!entry->filters)
223 goto error;
224 }
225 entry->fcount = src->fcount;
226 if (src->ssl_conf) {
227 entry->ssl_conf = crtlist_dup_ssl_conf(src->ssl_conf);
228 if (!entry->ssl_conf)
229 goto error;
230 }
231 entry->crtlist = src->crtlist;
232
233 return entry;
234
235error:
236
237 crtlist_free_filters(entry->filters);
238 ssl_sock_free_ssl_conf(entry->ssl_conf);
239 free(entry->ssl_conf);
240 free(entry);
241
242 return NULL;
243}
William Lallemand6e9556b2020-05-12 17:52:44 +0200244
245/*
246 * Allocate and initialize a crtlist_entry
247 */
248struct crtlist_entry *crtlist_entry_new()
249{
250 struct crtlist_entry *entry;
251
252 entry = calloc(1, sizeof(*entry));
253 if (entry == NULL)
254 return NULL;
255
256 LIST_INIT(&entry->ckch_inst);
257
Willy Tarreau2b718102021-04-21 07:32:39 +0200258 /* initialize the nodes so we can LIST_DELETE in any cases */
William Lallemand6e9556b2020-05-12 17:52:44 +0200259 LIST_INIT(&entry->by_crtlist);
260 LIST_INIT(&entry->by_ckch_store);
261
262 return entry;
263}
264
265/* Free a crtlist, from the crt_entry to the content of the ssl_conf */
266void crtlist_free(struct crtlist *crtlist)
267{
268 struct crtlist_entry *entry, *s_entry;
William Lallemand6a3168a2020-06-23 11:43:35 +0200269 struct bind_conf_list *bind_conf_node;
William Lallemand6e9556b2020-05-12 17:52:44 +0200270
271 if (crtlist == NULL)
272 return;
273
William Lallemand6a3168a2020-06-23 11:43:35 +0200274 bind_conf_node = crtlist->bind_conf;
275 while (bind_conf_node) {
276 struct bind_conf_list *next = bind_conf_node->next;
277 free(bind_conf_node);
278 bind_conf_node = next;
279 }
280
William Lallemand6e9556b2020-05-12 17:52:44 +0200281 list_for_each_entry_safe(entry, s_entry, &crtlist->ord_entries, by_crtlist) {
282 crtlist_entry_free(entry);
283 }
284 ebmb_delete(&crtlist->node);
285 free(crtlist);
286}
287
288/* Alloc and initialize a struct crtlist
289 * <filename> is the key of the ebmb_node
290 * <unique> initialize the list of entries to be unique (1) or not (0)
291 */
292struct crtlist *crtlist_new(const char *filename, int unique)
293{
294 struct crtlist *newlist;
295
296 newlist = calloc(1, sizeof(*newlist) + strlen(filename) + 1);
297 if (newlist == NULL)
298 return NULL;
299
300 memcpy(newlist->node.key, filename, strlen(filename) + 1);
301 if (unique)
302 newlist->entries = EB_ROOT_UNIQUE;
303 else
304 newlist->entries = EB_ROOT;
305
306 LIST_INIT(&newlist->ord_entries);
307
308 return newlist;
309}
310
311/*
312 * Read a single crt-list line. /!\ alter the <line> string.
313 * Fill <crt_path> and <crtlist_entry>
314 * <crtlist_entry> must be alloc and free by the caller
315 * <crtlist_entry->ssl_conf> is alloc by the function
316 * <crtlist_entry->filters> is alloc by the function
317 * <crt_path> is a ptr in <line>
318 * Return an error code
319 */
Remi Tricot-Le Bretonfb00f312021-03-23 16:41:53 +0100320int 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 +0200321{
322 int cfgerr = 0;
323 int arg, newarg, cur_arg, i, ssl_b = 0, ssl_e = 0;
324 char *end;
325 char *args[MAX_CRT_ARGS + 1];
326 struct ssl_bind_conf *ssl_conf = NULL;
327
328 if (!line || !crt_path || !entry)
329 return ERR_ALERT | ERR_FATAL;
330
331 end = line + strlen(line);
332 if (end-line >= CRT_LINESIZE-1 && *(end-1) != '\n') {
333 /* Check if we reached the limit and the last char is not \n.
334 * Watch out for the last line without the terminating '\n'!
335 */
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200336 memprintf(err, "parsing [%s:%d]: line too long, limit is %d characters",
337 file, linenum, CRT_LINESIZE-1);
William Lallemand6e9556b2020-05-12 17:52:44 +0200338 cfgerr |= ERR_ALERT | ERR_FATAL;
339 goto error;
340 }
341 arg = 0;
342 newarg = 1;
343 while (*line) {
344 if (isspace((unsigned char)*line)) {
345 newarg = 1;
346 *line = 0;
347 } else if (*line == '[') {
348 if (ssl_b) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200349 memprintf(err, "parsing [%s:%d]: too many '['", file, linenum);
William Lallemand6e9556b2020-05-12 17:52:44 +0200350 cfgerr |= ERR_ALERT | ERR_FATAL;
351 goto error;
352 }
353 if (!arg) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200354 memprintf(err, "parsing [%s:%d]: file must start with a cert", file, linenum);
William Lallemand6e9556b2020-05-12 17:52:44 +0200355 cfgerr |= ERR_ALERT | ERR_FATAL;
356 goto error;
357 }
358 ssl_b = arg;
359 newarg = 1;
360 *line = 0;
361 } else if (*line == ']') {
362 if (ssl_e) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200363 memprintf(err, "parsing [%s:%d]: too many ']'", file, linenum);
William Lallemand6e9556b2020-05-12 17:52:44 +0200364 cfgerr |= ERR_ALERT | ERR_FATAL;
365 goto error;
366 }
367 if (!ssl_b) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200368 memprintf(err, "parsing [%s:%d]: missing '['", file, linenum);
William Lallemand6e9556b2020-05-12 17:52:44 +0200369 cfgerr |= ERR_ALERT | ERR_FATAL;
370 goto error;
371 }
372 ssl_e = arg;
373 newarg = 1;
374 *line = 0;
375 } else if (newarg) {
376 if (arg == MAX_CRT_ARGS) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200377 memprintf(err, "parsing [%s:%d]: too many args ", file, linenum);
William Lallemand6e9556b2020-05-12 17:52:44 +0200378 cfgerr |= ERR_ALERT | ERR_FATAL;
379 goto error;
380 }
381 newarg = 0;
382 args[arg++] = line;
383 }
384 line++;
385 }
386 args[arg++] = line;
387
388 /* empty line */
389 if (!*args[0]) {
390 cfgerr |= ERR_NONE;
391 goto error;
392 }
393
394 *crt_path = args[0];
395
396 if (ssl_b) {
397 ssl_conf = calloc(1, sizeof *ssl_conf);
398 if (!ssl_conf) {
399 memprintf(err, "not enough memory!");
400 cfgerr |= ERR_ALERT | ERR_FATAL;
401 goto error;
402 }
403 }
Remi Tricot-Le Bretonfb00f312021-03-23 16:41:53 +0100404
William Lallemand6e9556b2020-05-12 17:52:44 +0200405 cur_arg = ssl_b ? ssl_b : 1;
406 while (cur_arg < ssl_e) {
407 newarg = 0;
408 for (i = 0; ssl_bind_kws[i].kw != NULL; i++) {
409 if (strcmp(ssl_bind_kws[i].kw, args[cur_arg]) == 0) {
410 newarg = 1;
Remi Tricot-Le Bretonfb00f312021-03-23 16:41:53 +0100411 cfgerr |= ssl_bind_kws[i].parse(args, cur_arg, NULL, ssl_conf, from_cli, err);
William Lallemand6e9556b2020-05-12 17:52:44 +0200412 if (cur_arg + 1 + ssl_bind_kws[i].skip > ssl_e) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200413 memprintf(err, "parsing [%s:%d]: ssl args out of '[]' for %s",
414 file, linenum, args[cur_arg]);
William Lallemand6e9556b2020-05-12 17:52:44 +0200415 cfgerr |= ERR_ALERT | ERR_FATAL;
416 goto error;
417 }
418 cur_arg += 1 + ssl_bind_kws[i].skip;
419 break;
420 }
421 }
422 if (!cfgerr && !newarg) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200423 memprintf(err, "parsing [%s:%d]: unknown ssl keyword %s",
424 file, linenum, args[cur_arg]);
William Lallemand6e9556b2020-05-12 17:52:44 +0200425 cfgerr |= ERR_ALERT | ERR_FATAL;
426 goto error;
427 }
428 }
429 entry->linenum = linenum;
430 entry->ssl_conf = ssl_conf;
431 entry->filters = crtlist_dup_filters(&args[cur_arg], arg - cur_arg - 1);
432 entry->fcount = arg - cur_arg - 1;
433
434 return cfgerr;
435
436error:
437 crtlist_free_filters(entry->filters);
438 entry->filters = NULL;
439 ssl_sock_free_ssl_conf(entry->ssl_conf);
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100440 ha_free(&entry->ssl_conf);
William Lallemand6e9556b2020-05-12 17:52:44 +0200441 return cfgerr;
442}
443
444
445
446/* This function parse a crt-list file and store it in a struct crtlist, each line is a crtlist_entry structure
447 * Fill the <crtlist> argument with a pointer to a new crtlist struct
448 *
449 * This function tries to open and store certificate files.
450 */
451int crtlist_parse_file(char *file, struct bind_conf *bind_conf, struct proxy *curproxy, struct crtlist **crtlist, char **err)
452{
453 struct crtlist *newlist;
454 struct crtlist_entry *entry = NULL;
455 char thisline[CRT_LINESIZE];
William Lallemand6e9556b2020-05-12 17:52:44 +0200456 FILE *f;
457 struct stat buf;
458 int linenum = 0;
459 int cfgerr = 0;
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200460 int missing_lf = -1;
William Lallemand6e9556b2020-05-12 17:52:44 +0200461
462 if ((f = fopen(file, "r")) == NULL) {
463 memprintf(err, "cannot open file '%s' : %s", file, strerror(errno));
464 return ERR_ALERT | ERR_FATAL;
465 }
466
467 newlist = crtlist_new(file, 0);
468 if (newlist == NULL) {
469 memprintf(err, "Not enough memory!");
470 cfgerr |= ERR_ALERT | ERR_FATAL;
471 goto error;
472 }
473
474 while (fgets(thisline, sizeof(thisline), f) != NULL) {
475 char *end;
476 char *line = thisline;
477 char *crt_path;
William Lallemand86c2dd62020-11-20 14:23:38 +0100478 char path[MAXPATHLEN+1];
William Lallemand6e9556b2020-05-12 17:52:44 +0200479 struct ckch_store *ckchs;
William Lallemand77e1c6f2020-11-20 18:26:09 +0100480 int found = 0;
William Lallemand6e9556b2020-05-12 17:52:44 +0200481
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200482 if (missing_lf != -1) {
483 memprintf(err, "parsing [%s:%d]: Stray NUL character at position %d.\n",
484 file, linenum, (missing_lf + 1));
485 cfgerr |= ERR_ALERT | ERR_FATAL;
486 missing_lf = -1;
487 break;
488 }
489
William Lallemand6e9556b2020-05-12 17:52:44 +0200490 linenum++;
491 end = line + strlen(line);
492 if (end-line == sizeof(thisline)-1 && *(end-1) != '\n') {
493 /* Check if we reached the limit and the last char is not \n.
494 * Watch out for the last line without the terminating '\n'!
495 */
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200496 memprintf(err, "parsing [%s:%d]: line too long, limit is %d characters",
497 file, linenum, (int)sizeof(thisline)-1);
William Lallemand6e9556b2020-05-12 17:52:44 +0200498 cfgerr |= ERR_ALERT | ERR_FATAL;
499 break;
500 }
501
502 if (*line == '#' || *line == '\n' || *line == '\r')
503 continue;
504
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200505 if (end > line && *(end-1) == '\n') {
506 /* kill trailing LF */
507 *(end - 1) = 0;
508 }
509 else {
510 /* mark this line as truncated */
511 missing_lf = end - line;
512 }
513
William Lallemand6e9556b2020-05-12 17:52:44 +0200514 entry = crtlist_entry_new();
515 if (entry == NULL) {
516 memprintf(err, "Not enough memory!");
517 cfgerr |= ERR_ALERT | ERR_FATAL;
518 goto error;
519 }
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200520
Remi Tricot-Le Bretonfb00f312021-03-23 16:41:53 +0100521 cfgerr |= crtlist_parse_line(thisline, &crt_path, entry, file, linenum, 0, err);
William Lallemand20b0fed2020-09-28 15:45:16 +0200522 if (cfgerr & ERR_CODE)
William Lallemand6e9556b2020-05-12 17:52:44 +0200523 goto error;
524
525 /* empty line */
526 if (!crt_path || !*crt_path) {
527 crtlist_entry_free(entry);
528 entry = NULL;
529 continue;
530 }
531
532 if (*crt_path != '/' && global_ssl.crt_base) {
533 if ((strlen(global_ssl.crt_base) + 1 + strlen(crt_path)) > MAXPATHLEN) {
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 }
539 snprintf(path, sizeof(path), "%s/%s", global_ssl.crt_base, crt_path);
540 crt_path = path;
541 }
542
543 /* Look for a ckch_store or create one */
544 ckchs = ckchs_lookup(crt_path);
545 if (ckchs == NULL) {
William Lallemand47da8212020-09-10 19:13:27 +0200546 if (stat(crt_path, &buf) == 0) {
William Lallemand77e1c6f2020-11-20 18:26:09 +0100547 found++;
William Lallemand47da8212020-09-10 19:13:27 +0200548
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200549 ckchs = ckchs_load_cert_file(crt_path, err);
William Lallemand47da8212020-09-10 19:13:27 +0200550 if (ckchs == NULL) {
551 cfgerr |= ERR_ALERT | ERR_FATAL;
552 goto error;
553 }
554
555 entry->node.key = ckchs;
556 entry->crtlist = newlist;
557 ebpt_insert(&newlist->entries, &entry->node);
Willy Tarreau2b718102021-04-21 07:32:39 +0200558 LIST_APPEND(&newlist->ord_entries, &entry->by_crtlist);
559 LIST_APPEND(&ckchs->crtlist_entry, &entry->by_ckch_store);
William Lallemand6e9556b2020-05-12 17:52:44 +0200560
William Lallemand73404572020-11-20 18:23:40 +0100561 } else if (global_ssl.extra_files & SSL_GF_BUNDLE) {
William Lallemand47da8212020-09-10 19:13:27 +0200562 /* If we didn't find the file, this could be a
William Lallemand51f784b2020-10-02 18:08:18 +0200563 bundle, since 2.3 we don't support multiple
564 certificate in the same OpenSSL store, so we
565 emulate it by loading each file separately. To
566 do so we need to duplicate the entry in the
567 crt-list because it becomes independent */
William Lallemand47da8212020-09-10 19:13:27 +0200568 char fp[MAXPATHLEN+1] = {0};
569 int n = 0;
570 struct crtlist_entry *entry_dup = entry; /* use the previous created entry */
571 for (n = 0; n < SSL_SOCK_NUM_KEYTYPES; n++) {
572 struct stat buf;
573 int ret;
574
William Lallemand86c2dd62020-11-20 14:23:38 +0100575 ret = snprintf(fp, sizeof(fp), "%s.%s", crt_path, SSL_SOCK_KEYTYPE_NAMES[n]);
William Lallemand47da8212020-09-10 19:13:27 +0200576 if (ret > sizeof(fp))
577 continue;
578
579 ckchs = ckchs_lookup(fp);
William Lallemand77e1c6f2020-11-20 18:26:09 +0100580 if (!ckchs) {
581 if (stat(fp, &buf) == 0) {
582 ckchs = ckchs_load_cert_file(fp, err);
583 if (!ckchs) {
William Lallemand47da8212020-09-10 19:13:27 +0200584 cfgerr |= ERR_ALERT | ERR_FATAL;
585 goto error;
586 }
William Lallemand77e1c6f2020-11-20 18:26:09 +0100587 } else {
588 continue; /* didn't find this extension, skip */
589 }
590 }
591 found++;
592 linenum++; /* we duplicate the line for this entry in the bundle */
593 if (!entry_dup) { /* if the entry was used, duplicate one */
594 linenum++;
595 entry_dup = crtlist_entry_dup(entry);
596 if (!entry_dup) {
597 cfgerr |= ERR_ALERT | ERR_FATAL;
598 goto error;
William Lallemand47da8212020-09-10 19:13:27 +0200599 }
William Lallemand77e1c6f2020-11-20 18:26:09 +0100600 entry_dup->linenum = linenum;
601 }
William Lallemand47da8212020-09-10 19:13:27 +0200602
William Lallemand77e1c6f2020-11-20 18:26:09 +0100603 entry_dup->node.key = ckchs;
604 entry_dup->crtlist = newlist;
605 ebpt_insert(&newlist->entries, &entry_dup->node);
Willy Tarreau2b718102021-04-21 07:32:39 +0200606 LIST_APPEND(&newlist->ord_entries, &entry_dup->by_crtlist);
607 LIST_APPEND(&ckchs->crtlist_entry, &entry_dup->by_ckch_store);
William Lallemand47da8212020-09-10 19:13:27 +0200608
William Lallemand77e1c6f2020-11-20 18:26:09 +0100609 entry_dup = NULL; /* the entry was used, we need a new one next round */
William Lallemand47da8212020-09-10 19:13:27 +0200610 }
William Lallemandb7fdfdf2020-12-04 15:45:02 +0100611#if HA_OPENSSL_VERSION_NUMBER < 0x10101000L
612 if (found) {
613 memprintf(err, "%sCan't load '%s'. Loading a multi certificates bundle requires OpenSSL >= 1.1.1\n",
614 err && *err ? *err : "", crt_path);
615 cfgerr |= ERR_ALERT | ERR_FATAL;
616 }
617#endif
William Lallemand47da8212020-09-10 19:13:27 +0200618 }
William Lallemand77e1c6f2020-11-20 18:26:09 +0100619 if (!found) {
620 memprintf(err, "%sunable to stat SSL certificate from file '%s' : %s.\n",
621 err && *err ? *err : "", crt_path, strerror(errno));
622 cfgerr |= ERR_ALERT | ERR_FATAL;
623 }
624
William Lallemand50c03aa2020-11-06 16:24:07 +0100625 } else {
626 entry->node.key = ckchs;
627 entry->crtlist = newlist;
628 ebpt_insert(&newlist->entries, &entry->node);
Willy Tarreau2b718102021-04-21 07:32:39 +0200629 LIST_APPEND(&newlist->ord_entries, &entry->by_crtlist);
630 LIST_APPEND(&ckchs->crtlist_entry, &entry->by_ckch_store);
William Lallemand77e1c6f2020-11-20 18:26:09 +0100631 found++;
William Lallemand47da8212020-09-10 19:13:27 +0200632 }
William Lallemand6e9556b2020-05-12 17:52:44 +0200633 entry = NULL;
634 }
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200635
636 if (missing_lf != -1) {
637 memprintf(err, "parsing [%s:%d]: Missing LF on last line, file might have been truncated at position %d.\n",
638 file, linenum, (missing_lf + 1));
639 cfgerr |= ERR_ALERT | ERR_FATAL;
640 }
641
William Lallemand6e9556b2020-05-12 17:52:44 +0200642 if (cfgerr & ERR_CODE)
643 goto error;
644
645 newlist->linecount = linenum;
646
647 fclose(f);
648 *crtlist = newlist;
649
650 return cfgerr;
651error:
652 crtlist_entry_free(entry);
653
654 fclose(f);
655 crtlist_free(newlist);
656 return cfgerr;
657}
658
659/* This function reads a directory and stores it in a struct crtlist, each file is a crtlist_entry structure
660 * Fill the <crtlist> argument with a pointer to a new crtlist struct
661 *
662 * This function tries to open and store certificate files.
663 */
664int crtlist_load_cert_dir(char *path, struct bind_conf *bind_conf, struct crtlist **crtlist, char **err)
665{
666 struct crtlist *dir;
667 struct dirent **de_list;
668 int i, n;
669 struct stat buf;
670 char *end;
671 char fp[MAXPATHLEN+1];
672 int cfgerr = 0;
673 struct ckch_store *ckchs;
William Lallemand6e9556b2020-05-12 17:52:44 +0200674
675 dir = crtlist_new(path, 1);
676 if (dir == NULL) {
677 memprintf(err, "not enough memory");
678 return ERR_ALERT | ERR_FATAL;
679 }
680
681 n = scandir(path, &de_list, 0, alphasort);
682 if (n < 0) {
683 memprintf(err, "%sunable to scan directory '%s' : %s.\n",
684 err && *err ? *err : "", path, strerror(errno));
685 cfgerr |= ERR_ALERT | ERR_FATAL;
686 }
687 else {
688 for (i = 0; i < n; i++) {
689 struct crtlist_entry *entry;
690 struct dirent *de = de_list[i];
691
692 end = strrchr(de->d_name, '.');
Tim Duesterhuse5ff1412021-01-02 22:31:53 +0100693 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 +0200694 goto ignore_entry;
695
696 snprintf(fp, sizeof(fp), "%s/%s", path, de->d_name);
697 if (stat(fp, &buf) != 0) {
698 memprintf(err, "%sunable to stat SSL certificate from file '%s' : %s.\n",
699 err && *err ? *err : "", fp, strerror(errno));
700 cfgerr |= ERR_ALERT | ERR_FATAL;
701 goto ignore_entry;
702 }
703 if (!S_ISREG(buf.st_mode))
704 goto ignore_entry;
705
706 entry = crtlist_entry_new();
707 if (entry == NULL) {
708 memprintf(err, "not enough memory '%s'", fp);
709 cfgerr |= ERR_ALERT | ERR_FATAL;
710 goto ignore_entry;
711 }
712
William Lallemand6e9556b2020-05-12 17:52:44 +0200713 ckchs = ckchs_lookup(fp);
714 if (ckchs == NULL)
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200715 ckchs = ckchs_load_cert_file(fp, err);
William Lallemand6e9556b2020-05-12 17:52:44 +0200716 if (ckchs == NULL) {
717 free(de);
718 free(entry);
719 cfgerr |= ERR_ALERT | ERR_FATAL;
720 goto end;
721 }
722 entry->node.key = ckchs;
723 entry->crtlist = dir;
Willy Tarreau2b718102021-04-21 07:32:39 +0200724 LIST_APPEND(&ckchs->crtlist_entry, &entry->by_ckch_store);
725 LIST_APPEND(&dir->ord_entries, &entry->by_crtlist);
William Lallemand6e9556b2020-05-12 17:52:44 +0200726 ebpt_insert(&dir->entries, &entry->node);
727
728ignore_entry:
729 free(de);
730 }
731end:
732 free(de_list);
733 }
734
735 if (cfgerr & ERR_CODE) {
736 /* free the dir and entries on error */
737 crtlist_free(dir);
738 } else {
739 *crtlist = dir;
740 }
741 return cfgerr;
742
743}
744
William Lallemandc756bbd2020-05-13 17:23:59 +0200745/*
746 * Take an ssl_bind_conf structure and append the configuration line used to
747 * create it in the buffer
748 */
749static void dump_crtlist_sslconf(struct buffer *buf, const struct ssl_bind_conf *conf)
750{
751 int space = 0;
752
753 if (conf == NULL)
754 return;
755
756 chunk_appendf(buf, " [");
757#ifdef OPENSSL_NPN_NEGOTIATED
758 if (conf->npn_str) {
759 int len = conf->npn_len;
760 char *ptr = conf->npn_str;
761 int comma = 0;
762
763 if (space) chunk_appendf(buf, " ");
764 chunk_appendf(buf, "npn ");
765 while (len) {
766 unsigned short size;
767
768 size = *ptr;
769 ptr++;
770 if (comma)
771 chunk_memcat(buf, ",", 1);
772 chunk_memcat(buf, ptr, size);
773 ptr += size;
774 len -= size + 1;
775 comma = 1;
776 }
777 chunk_memcat(buf, "", 1); /* finish with a \0 */
778 space++;
779 }
780#endif
781#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
782 if (conf->alpn_str) {
783 int len = conf->alpn_len;
784 char *ptr = conf->alpn_str;
785 int comma = 0;
786
787 if (space) chunk_appendf(buf, " ");
788 chunk_appendf(buf, "alpn ");
789 while (len) {
790 unsigned short size;
791
792 size = *ptr;
793 ptr++;
794 if (comma)
795 chunk_memcat(buf, ",", 1);
796 chunk_memcat(buf, ptr, size);
797 ptr += size;
798 len -= size + 1;
799 comma = 1;
800 }
801 chunk_memcat(buf, "", 1); /* finish with a \0 */
802 space++;
803 }
804#endif
805 /* verify */
806 {
807 if (conf->verify == SSL_SOCK_VERIFY_NONE) {
808 if (space) chunk_appendf(buf, " ");
809 chunk_appendf(buf, "verify none");
810 space++;
811 } else if (conf->verify == SSL_SOCK_VERIFY_OPTIONAL) {
812 if (space) chunk_appendf(buf, " ");
813 chunk_appendf(buf, "verify optional");
814 space++;
815 } else if (conf->verify == SSL_SOCK_VERIFY_REQUIRED) {
816 if (space) chunk_appendf(buf, " ");
817 chunk_appendf(buf, "verify required");
818 space++;
819 }
820 }
821
822 if (conf->no_ca_names) {
823 if (space) chunk_appendf(buf, " ");
824 chunk_appendf(buf, "no-ca-names");
825 space++;
826 }
827
828 if (conf->early_data) {
829 if (space) chunk_appendf(buf, " ");
830 chunk_appendf(buf, "allow-0rtt");
831 space++;
832 }
833 if (conf->ca_file) {
834 if (space) chunk_appendf(buf, " ");
835 chunk_appendf(buf, "ca-file %s", conf->ca_file);
836 space++;
837 }
838 if (conf->crl_file) {
839 if (space) chunk_appendf(buf, " ");
840 chunk_appendf(buf, "crl-file %s", conf->crl_file);
841 space++;
842 }
843 if (conf->ciphers) {
844 if (space) chunk_appendf(buf, " ");
845 chunk_appendf(buf, "ciphers %s", conf->ciphers);
846 space++;
847 }
Ilya Shipitsinf34ed0b2020-11-21 14:37:34 +0500848#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES
William Lallemandc756bbd2020-05-13 17:23:59 +0200849 if (conf->ciphersuites) {
850 if (space) chunk_appendf(buf, " ");
851 chunk_appendf(buf, "ciphersuites %s", conf->ciphersuites);
852 space++;
853 }
854#endif
855 if (conf->curves) {
856 if (space) chunk_appendf(buf, " ");
857 chunk_appendf(buf, "curves %s", conf->curves);
858 space++;
859 }
860 if (conf->ecdhe) {
861 if (space) chunk_appendf(buf, " ");
862 chunk_appendf(buf, "ecdhe %s", conf->ecdhe);
863 space++;
864 }
865
866 /* the crt-lists only support ssl-min-ver and ssl-max-ver */
William Lallemand8177ad92020-05-20 16:49:02 +0200867 if (conf->ssl_methods_cfg.min) {
William Lallemandc756bbd2020-05-13 17:23:59 +0200868 if (space) chunk_appendf(buf, " ");
William Lallemand8177ad92020-05-20 16:49:02 +0200869 chunk_appendf(buf, "ssl-min-ver %s", methodVersions[conf->ssl_methods_cfg.min].name);
William Lallemandc756bbd2020-05-13 17:23:59 +0200870 space++;
871 }
872
William Lallemand8177ad92020-05-20 16:49:02 +0200873 if (conf->ssl_methods_cfg.max) {
William Lallemandc756bbd2020-05-13 17:23:59 +0200874 if (space) chunk_appendf(buf, " ");
William Lallemand8177ad92020-05-20 16:49:02 +0200875 chunk_appendf(buf, "ssl-max-ver %s", methodVersions[conf->ssl_methods_cfg.max].name);
William Lallemandc756bbd2020-05-13 17:23:59 +0200876 space++;
877 }
878
879 chunk_appendf(buf, "]");
880
881 return;
882}
883
884/* dump a list of filters */
885static void dump_crtlist_filters(struct buffer *buf, struct crtlist_entry *entry)
886{
887 int i;
888
889 if (!entry->fcount)
890 return;
891
892 for (i = 0; i < entry->fcount; i++) {
893 chunk_appendf(buf, " %s", entry->filters[i]);
894 }
895 return;
896}
897
898/************************** CLI functions ****************************/
899
900
Willy Tarreaua2fcca02022-05-05 11:53:23 +0200901/* CLI IO handler for '(show|dump) ssl crt-list'.
902 * It uses show_crtlist_ctx for the context.
903 */
William Lallemandc756bbd2020-05-13 17:23:59 +0200904static int cli_io_handler_dump_crtlist(struct appctx *appctx)
905{
Willy Tarreaua2fcca02022-05-05 11:53:23 +0200906 struct show_crtlist_ctx *ctx = appctx->svcctx;
William Lallemandc756bbd2020-05-13 17:23:59 +0200907 struct buffer *trash = alloc_trash_chunk();
Christopher Faulet908628c2022-03-25 16:43:49 +0100908 struct conn_stream *cs = appctx->owner;
William Lallemandc756bbd2020-05-13 17:23:59 +0200909 struct ebmb_node *lnode;
910
911 if (trash == NULL)
912 return 1;
913
914 /* dump the list of crt-lists */
Willy Tarreaua2fcca02022-05-05 11:53:23 +0200915 lnode = ctx->crtlist_node;
William Lallemandc756bbd2020-05-13 17:23:59 +0200916 if (lnode == NULL)
917 lnode = ebmb_first(&crtlists_tree);
918 while (lnode) {
919 chunk_appendf(trash, "%s\n", lnode->key);
Christopher Faulet908628c2022-03-25 16:43:49 +0100920 if (ci_putchk(cs_ic(cs), trash) == -1) {
Christopher Fauleta0bdec32022-04-04 07:51:21 +0200921 cs_rx_room_blk(cs);
William Lallemandc756bbd2020-05-13 17:23:59 +0200922 goto yield;
923 }
924 lnode = ebmb_next(lnode);
925 }
926 free_trash_chunk(trash);
927 return 1;
928yield:
Willy Tarreaua2fcca02022-05-05 11:53:23 +0200929 ctx->crtlist_node = lnode;
William Lallemandc756bbd2020-05-13 17:23:59 +0200930 free_trash_chunk(trash);
931 return 0;
932}
933
934/* CLI IO handler for '(show|dump) ssl crt-list <filename>' */
935static int cli_io_handler_dump_crtlist_entries(struct appctx *appctx)
936{
Willy Tarreaua2fcca02022-05-05 11:53:23 +0200937 struct show_crtlist_ctx *ctx = appctx->svcctx;
William Lallemandc756bbd2020-05-13 17:23:59 +0200938 struct buffer *trash = alloc_trash_chunk();
939 struct crtlist *crtlist;
Christopher Faulet908628c2022-03-25 16:43:49 +0100940 struct conn_stream *cs = appctx->owner;
William Lallemandc756bbd2020-05-13 17:23:59 +0200941 struct crtlist_entry *entry;
942
943 if (trash == NULL)
944 return 1;
945
Willy Tarreaua2fcca02022-05-05 11:53:23 +0200946 crtlist = ebmb_entry(ctx->crtlist_node, struct crtlist, node);
William Lallemandc756bbd2020-05-13 17:23:59 +0200947
Willy Tarreaua2fcca02022-05-05 11:53:23 +0200948 entry = ctx->entry;
William Lallemandc756bbd2020-05-13 17:23:59 +0200949 if (entry == NULL) {
950 entry = LIST_ELEM((crtlist->ord_entries).n, typeof(entry), by_crtlist);
951 chunk_appendf(trash, "# %s\n", crtlist->node.key);
Christopher Faulet908628c2022-03-25 16:43:49 +0100952 if (ci_putchk(cs_ic(cs), trash) == -1) {
Christopher Fauleta0bdec32022-04-04 07:51:21 +0200953 cs_rx_room_blk(cs);
William Lallemandc756bbd2020-05-13 17:23:59 +0200954 goto yield;
955 }
956 }
957
958 list_for_each_entry_from(entry, &crtlist->ord_entries, by_crtlist) {
959 struct ckch_store *store;
960 const char *filename;
961
962 store = entry->node.key;
963 filename = store->path;
964 chunk_appendf(trash, "%s", filename);
Willy Tarreaua2fcca02022-05-05 11:53:23 +0200965 if (ctx->mode == 's') /* show */
William Lallemandc756bbd2020-05-13 17:23:59 +0200966 chunk_appendf(trash, ":%d", entry->linenum);
967 dump_crtlist_sslconf(trash, entry->ssl_conf);
968 dump_crtlist_filters(trash, entry);
969 chunk_appendf(trash, "\n");
970
Christopher Faulet908628c2022-03-25 16:43:49 +0100971 if (ci_putchk(cs_ic(cs), trash) == -1) {
Christopher Fauleta0bdec32022-04-04 07:51:21 +0200972 cs_rx_room_blk(cs);
William Lallemandc756bbd2020-05-13 17:23:59 +0200973 goto yield;
974 }
975 }
976 free_trash_chunk(trash);
977 return 1;
978yield:
Willy Tarreaua2fcca02022-05-05 11:53:23 +0200979 ctx->entry = entry;
William Lallemandc756bbd2020-05-13 17:23:59 +0200980 free_trash_chunk(trash);
981 return 0;
982}
983
984/* CLI argument parser for '(show|dump) ssl crt-list' */
985static int cli_parse_dump_crtlist(char **args, char *payload, struct appctx *appctx, void *private)
986{
Willy Tarreaua2fcca02022-05-05 11:53:23 +0200987 struct show_crtlist_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
William Lallemandc756bbd2020-05-13 17:23:59 +0200988 struct ebmb_node *lnode;
989 char *filename = NULL;
990 int mode;
William Lallemand99cc2182020-06-25 15:19:51 +0200991 char *end;
William Lallemandc756bbd2020-05-13 17:23:59 +0200992
993 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
994 return 1;
995
Tim Duesterhuse5ff1412021-01-02 22:31:53 +0100996 if (*args[3] && strcmp(args[3], "-n") == 0) {
William Lallemandc756bbd2020-05-13 17:23:59 +0200997 mode = 's';
998 filename = args[4];
999 } else {
1000 mode = 'd';
1001 filename = args[3];
1002 }
1003
1004 if (mode == 's' && !*args[4])
1005 return cli_err(appctx, "'show ssl crt-list -n' expects a filename or a directory\n");
1006
1007 if (filename && *filename) {
William Lallemand99cc2182020-06-25 15:19:51 +02001008
1009
1010 /* strip trailing slashes, including first one */
1011 for (end = filename + strlen(filename) - 1; end >= filename && *end == '/'; end--)
1012 *end = 0;
1013
William Lallemandc756bbd2020-05-13 17:23:59 +02001014 lnode = ebst_lookup(&crtlists_tree, filename);
1015 if (lnode == NULL)
1016 return cli_err(appctx, "didn't find the specified filename\n");
1017
Willy Tarreaua2fcca02022-05-05 11:53:23 +02001018 ctx->crtlist_node = lnode;
William Lallemandc756bbd2020-05-13 17:23:59 +02001019 appctx->io_handler = cli_io_handler_dump_crtlist_entries;
1020 }
Willy Tarreaua2fcca02022-05-05 11:53:23 +02001021 ctx->mode = mode;
William Lallemandc756bbd2020-05-13 17:23:59 +02001022
1023 return 0;
1024}
1025
1026/* release function of the "add ssl crt-list' command, free things and unlock
Willy Tarreau6b6c3632022-05-05 13:43:49 +02001027 * the spinlock. It uses the add_crtlist_ctx.
1028 */
William Lallemandc756bbd2020-05-13 17:23:59 +02001029static void cli_release_add_crtlist(struct appctx *appctx)
1030{
Willy Tarreau6b6c3632022-05-05 13:43:49 +02001031 struct add_crtlist_ctx *ctx = appctx->svcctx;
1032 struct crtlist_entry *entry = ctx->entry;
William Lallemandc756bbd2020-05-13 17:23:59 +02001033
1034 if (appctx->st2 != SETCERT_ST_FIN) {
1035 struct ckch_inst *inst, *inst_s;
1036 /* upon error free the ckch_inst and everything inside */
1037 ebpt_delete(&entry->node);
Willy Tarreau2b718102021-04-21 07:32:39 +02001038 LIST_DELETE(&entry->by_crtlist);
1039 LIST_DELETE(&entry->by_ckch_store);
William Lallemandc756bbd2020-05-13 17:23:59 +02001040
1041 list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_ckchs) {
1042 ckch_inst_free(inst);
1043 }
1044 crtlist_free_filters(entry->filters);
1045 ssl_sock_free_ssl_conf(entry->ssl_conf);
1046 free(entry->ssl_conf);
1047 free(entry);
1048 }
1049
1050 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1051}
1052
1053
1054/* IO Handler for the "add ssl crt-list" command It adds a new entry in the
1055 * crt-list and generates the ckch_insts for each bind_conf that uses this crt-list
1056 *
1057 * The logic is the same as the "commit ssl cert" command but without the
1058 * freeing of the old structures, because there are none.
Willy Tarreau6b6c3632022-05-05 13:43:49 +02001059 *
1060 * It uses the add_crtlist_ctx for the context.
William Lallemandc756bbd2020-05-13 17:23:59 +02001061 */
1062static int cli_io_handler_add_crtlist(struct appctx *appctx)
1063{
Willy Tarreau6b6c3632022-05-05 13:43:49 +02001064 struct add_crtlist_ctx *ctx = appctx->svcctx;
William Lallemandc756bbd2020-05-13 17:23:59 +02001065 struct bind_conf_list *bind_conf_node;
Christopher Faulet908628c2022-03-25 16:43:49 +01001066 struct conn_stream *cs = appctx->owner;
Willy Tarreau6b6c3632022-05-05 13:43:49 +02001067 struct crtlist *crtlist = ctx->crtlist;
1068 struct crtlist_entry *entry = ctx->entry;
William Lallemandc756bbd2020-05-13 17:23:59 +02001069 struct ckch_store *store = entry->node.key;
1070 struct buffer *trash = alloc_trash_chunk();
1071 struct ckch_inst *new_inst;
1072 char *err = NULL;
1073 int i = 0;
1074 int errcode = 0;
1075
1076 if (trash == NULL)
1077 goto error;
1078
1079 /* for each bind_conf which use the crt-list, a new ckch_inst must be
1080 * created.
1081 */
Christopher Faulet908628c2022-03-25 16:43:49 +01001082 if (unlikely(cs_ic(cs)->flags & (CF_WRITE_ERROR|CF_SHUTW)))
William Lallemandc756bbd2020-05-13 17:23:59 +02001083 goto error;
1084
Willy Tarreau1b948ef2022-05-05 13:50:46 +02001085 switch (appctx->st2) {
1086 case SETCERT_ST_INIT:
1087 /* This state just print the update message */
1088 chunk_printf(trash, "Inserting certificate '%s' in crt-list '%s'", store->path, crtlist->node.key);
1089 if (ci_putchk(cs_ic(cs), trash) == -1) {
1090 cs_rx_room_blk(cs);
1091 goto yield;
1092 }
1093 appctx->st2 = SETCERT_ST_GEN;
1094 /* fallthrough */
1095 case SETCERT_ST_GEN:
Willy Tarreau6b6c3632022-05-05 13:43:49 +02001096 bind_conf_node = ctx->bind_conf_node; /* get the previous ptr from the yield */
Willy Tarreau1b948ef2022-05-05 13:50:46 +02001097 if (bind_conf_node == NULL)
1098 bind_conf_node = crtlist->bind_conf;
1099 for (; bind_conf_node; bind_conf_node = bind_conf_node->next) {
1100 struct bind_conf *bind_conf = bind_conf_node->bind_conf;
1101 struct sni_ctx *sni;
1102
1103 /* yield every 10 generations */
1104 if (i > 10) {
Willy Tarreau6b6c3632022-05-05 13:43:49 +02001105 ctx->bind_conf_node = bind_conf_node;
Willy Tarreau1b948ef2022-05-05 13:50:46 +02001106 goto yield;
1107 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001108
Willy Tarreau1b948ef2022-05-05 13:50:46 +02001109 /* we don't support multi-cert bundles, only simple ones */
1110 errcode |= ckch_inst_new_load_store(store->path, store, bind_conf, entry->ssl_conf, entry->filters, entry->fcount, &new_inst, &err);
1111 if (errcode & ERR_CODE)
1112 goto error;
William Lallemandc756bbd2020-05-13 17:23:59 +02001113
Willy Tarreau1b948ef2022-05-05 13:50:46 +02001114 /* we need to initialize the SSL_CTX generated */
1115 /* this iterate on the newly generated SNIs in the new instance to prepare their SSL_CTX */
1116 list_for_each_entry(sni, &new_inst->sni_ctx, by_ckch_inst) {
1117 if (!sni->order) { /* we initialized only the first SSL_CTX because it's the same in the other sni_ctx's */
1118 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 +02001119 if (errcode & ERR_CODE)
1120 goto error;
William Lallemandc756bbd2020-05-13 17:23:59 +02001121 }
Willy Tarreau1b948ef2022-05-05 13:50:46 +02001122 }
1123 /* display one dot for each new instance */
1124 chunk_appendf(trash, ".");
1125 i++;
1126 LIST_APPEND(&store->ckch_inst, &new_inst->by_ckchs);
1127 LIST_APPEND(&entry->ckch_inst, &new_inst->by_crtlist_entry);
1128 new_inst->crtlist_entry = entry;
William Lallemandc756bbd2020-05-13 17:23:59 +02001129 }
Willy Tarreau1b948ef2022-05-05 13:50:46 +02001130 appctx->st2 = SETCERT_ST_INSERT;
1131 /* fallthrough */
1132 case SETCERT_ST_INSERT:
1133 /* insert SNIs in bind_conf */
1134 list_for_each_entry(new_inst, &store->ckch_inst, by_ckchs) {
1135 HA_RWLOCK_WRLOCK(SNI_LOCK, &new_inst->bind_conf->sni_lock);
1136 ssl_sock_load_cert_sni(new_inst, new_inst->bind_conf);
1137 HA_RWLOCK_WRUNLOCK(SNI_LOCK, &new_inst->bind_conf->sni_lock);
1138 }
1139 entry->linenum = ++crtlist->linecount;
1140 appctx->st2 = SETCERT_ST_FIN;
William Lallemandc756bbd2020-05-13 17:23:59 +02001141 }
1142
William Lallemandc756bbd2020-05-13 17:23:59 +02001143 chunk_appendf(trash, "\n");
1144 if (errcode & ERR_WARN)
1145 chunk_appendf(trash, "%s", err);
1146 chunk_appendf(trash, "Success!\n");
Christopher Faulet908628c2022-03-25 16:43:49 +01001147 if (ci_putchk(cs_ic(cs), trash) == -1)
Christopher Fauleta0bdec32022-04-04 07:51:21 +02001148 cs_rx_room_blk(cs);
William Lallemandc756bbd2020-05-13 17:23:59 +02001149 free_trash_chunk(trash);
1150 /* success: call the release function and don't come back */
1151 return 1;
1152yield:
1153 /* store the state */
Christopher Faulet908628c2022-03-25 16:43:49 +01001154 if (ci_putchk(cs_ic(cs), trash) == -1)
Christopher Fauleta0bdec32022-04-04 07:51:21 +02001155 cs_rx_room_blk(cs);
William Lallemandc756bbd2020-05-13 17:23:59 +02001156 free_trash_chunk(trash);
Christopher Fauleta0bdec32022-04-04 07:51:21 +02001157 cs_rx_endp_more(cs); /* let's come back later */
William Lallemandc756bbd2020-05-13 17:23:59 +02001158 return 0; /* should come back */
1159
1160error:
1161 /* spin unlock and free are done in the release function */
1162 if (trash) {
1163 chunk_appendf(trash, "\n%sFailed!\n", err);
Christopher Faulet908628c2022-03-25 16:43:49 +01001164 if (ci_putchk(cs_ic(cs), trash) == -1)
Christopher Fauleta0bdec32022-04-04 07:51:21 +02001165 cs_rx_room_blk(cs);
William Lallemandc756bbd2020-05-13 17:23:59 +02001166 free_trash_chunk(trash);
1167 }
1168 /* error: call the release function and don't come back */
1169 return 1;
1170}
1171
1172
1173/*
1174 * Parse a "add ssl crt-list <crt-list> <certfile>" line.
Willy Tarreau6b6c3632022-05-05 13:43:49 +02001175 * Filters and option must be passed through payload.
1176 * It sets a struct add_crtlist_ctx.
William Lallemandc756bbd2020-05-13 17:23:59 +02001177 */
1178static int cli_parse_add_crtlist(char **args, char *payload, struct appctx *appctx, void *private)
1179{
Willy Tarreau6b6c3632022-05-05 13:43:49 +02001180 struct add_crtlist_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
William Lallemandc756bbd2020-05-13 17:23:59 +02001181 int cfgerr = 0;
1182 struct ckch_store *store;
1183 char *err = NULL;
1184 char path[MAXPATHLEN+1];
1185 char *crtlist_path;
1186 char *cert_path = NULL;
1187 struct ebmb_node *eb;
1188 struct ebpt_node *inserted;
1189 struct crtlist *crtlist;
1190 struct crtlist_entry *entry = NULL;
William Lallemand99cc2182020-06-25 15:19:51 +02001191 char *end;
William Lallemandc756bbd2020-05-13 17:23:59 +02001192
1193 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
1194 return 1;
1195
1196 if (!*args[3] || (!payload && !*args[4]))
1197 return cli_err(appctx, "'add ssl crtlist' expects a filename and a certificate name\n");
1198
1199 crtlist_path = args[3];
1200
William Lallemand99cc2182020-06-25 15:19:51 +02001201 /* strip trailing slashes, including first one */
1202 for (end = crtlist_path + strlen(crtlist_path) - 1; end >= crtlist_path && *end == '/'; end--)
1203 *end = 0;
1204
William Lallemandc756bbd2020-05-13 17:23:59 +02001205 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
1206 return cli_err(appctx, "Operations on certificates are currently locked!\n");
1207
1208 eb = ebst_lookup(&crtlists_tree, crtlist_path);
1209 if (!eb) {
1210 memprintf(&err, "crt-list '%s' does not exist!", crtlist_path);
1211 goto error;
1212 }
1213 crtlist = ebmb_entry(eb, struct crtlist, node);
1214
1215 entry = crtlist_entry_new();
1216 if (entry == NULL) {
1217 memprintf(&err, "Not enough memory!");
1218 goto error;
1219 }
1220
1221 if (payload) {
1222 char *lf;
1223
1224 lf = strrchr(payload, '\n');
1225 if (lf) {
1226 memprintf(&err, "only one line of payload is supported!");
1227 goto error;
1228 }
1229 /* cert_path is filled here */
Remi Tricot-Le Bretonfb00f312021-03-23 16:41:53 +01001230 cfgerr |= crtlist_parse_line(payload, &cert_path, entry, "CLI", 1, 1, &err);
William Lallemandc756bbd2020-05-13 17:23:59 +02001231 if (cfgerr & ERR_CODE)
1232 goto error;
1233 } else {
1234 cert_path = args[4];
1235 }
1236
1237 if (!cert_path) {
1238 memprintf(&err, "'add ssl crtlist' should contain the certificate name in the payload");
1239 cfgerr |= ERR_ALERT | ERR_FATAL;
1240 goto error;
1241 }
1242
1243 if (eb_gettag(crtlist->entries.b[EB_RGHT])) {
1244 char *slash;
1245
1246 slash = strrchr(cert_path, '/');
1247 if (!slash) {
1248 memprintf(&err, "'%s' is a directory, certificate path '%s' must contain the directory path", (char *)crtlist->node.key, cert_path);
1249 goto error;
1250 }
1251 /* temporary replace / by 0 to do an strcmp */
1252 *slash = '\0';
1253 if (strcmp(cert_path, (char*)crtlist->node.key) != 0) {
1254 *slash = '/';
1255 memprintf(&err, "'%s' is a directory, certificate path '%s' must contain the directory path", (char *)crtlist->node.key, cert_path);
1256 goto error;
1257 }
1258 *slash = '/';
1259 }
1260
1261 if (*cert_path != '/' && global_ssl.crt_base) {
1262 if ((strlen(global_ssl.crt_base) + 1 + strlen(cert_path)) > MAXPATHLEN) {
1263 memprintf(&err, "'%s' : path too long", cert_path);
1264 cfgerr |= ERR_ALERT | ERR_FATAL;
1265 goto error;
1266 }
1267 snprintf(path, sizeof(path), "%s/%s", global_ssl.crt_base, cert_path);
1268 cert_path = path;
1269 }
1270
1271 store = ckchs_lookup(cert_path);
1272 if (store == NULL) {
1273 memprintf(&err, "certificate '%s' does not exist!", cert_path);
1274 goto error;
1275 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001276 if (store->ckch == NULL || store->ckch->cert == NULL) {
1277 memprintf(&err, "certificate '%s' is empty!", cert_path);
1278 goto error;
1279 }
1280
1281 /* check if it's possible to insert this new crtlist_entry */
1282 entry->node.key = store;
1283 inserted = ebpt_insert(&crtlist->entries, &entry->node);
1284 if (inserted != &entry->node) {
1285 memprintf(&err, "file already exists in this directory!");
1286 goto error;
1287 }
1288
1289 /* this is supposed to be a directory (EB_ROOT_UNIQUE), so no ssl_conf are allowed */
1290 if ((entry->ssl_conf || entry->filters) && eb_gettag(crtlist->entries.b[EB_RGHT])) {
1291 memprintf(&err, "this is a directory, SSL configuration and filters are not allowed");
1292 goto error;
1293 }
1294
Willy Tarreau2b718102021-04-21 07:32:39 +02001295 LIST_APPEND(&crtlist->ord_entries, &entry->by_crtlist);
William Lallemandc756bbd2020-05-13 17:23:59 +02001296 entry->crtlist = crtlist;
Willy Tarreau2b718102021-04-21 07:32:39 +02001297 LIST_APPEND(&store->crtlist_entry, &entry->by_ckch_store);
William Lallemandc756bbd2020-05-13 17:23:59 +02001298
1299 appctx->st2 = SETCERT_ST_INIT;
Willy Tarreau6b6c3632022-05-05 13:43:49 +02001300 ctx->crtlist = crtlist;
1301 ctx->entry = entry;
William Lallemandc756bbd2020-05-13 17:23:59 +02001302
1303 /* unlock is done in the release handler */
1304 return 0;
1305
1306error:
1307 crtlist_entry_free(entry);
1308 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1309 err = memprintf(&err, "Can't edit the crt-list: %s\n", err ? err : "");
1310 return cli_dynerr(appctx, err);
1311}
1312
1313/* Parse a "del ssl crt-list <crt-list> <certfile>" line. */
1314static int cli_parse_del_crtlist(char **args, char *payload, struct appctx *appctx, void *private)
1315{
1316 struct ckch_store *store;
1317 char *err = NULL;
1318 char *crtlist_path, *cert_path;
1319 struct ebmb_node *ebmb;
1320 struct ebpt_node *ebpt;
1321 struct crtlist *crtlist;
1322 struct crtlist_entry *entry = NULL;
1323 struct ckch_inst *inst, *inst_s;
1324 int linenum = 0;
1325 char *colons;
William Lallemand99cc2182020-06-25 15:19:51 +02001326 char *end;
Remi Tricot-Le Bretonbc2c3862021-03-26 10:47:50 +01001327 int error_message_dumped = 0;
William Lallemandc756bbd2020-05-13 17:23:59 +02001328
1329 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
1330 return 1;
1331
1332 if (!*args[3] || !*args[4])
1333 return cli_err(appctx, "'del ssl crtlist' expects a filename and a certificate name\n");
1334
1335 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
1336 return cli_err(appctx, "Can't delete!\nOperations on certificates are currently locked!\n");
1337
1338 crtlist_path = args[3];
1339 cert_path = args[4];
1340
1341 colons = strchr(cert_path, ':');
1342 if (colons) {
1343 char *endptr;
1344
1345 linenum = strtol(colons + 1, &endptr, 10);
1346 if (colons + 1 == endptr || *endptr != '\0') {
1347 memprintf(&err, "wrong line number after colons in '%s'!", cert_path);
1348 goto error;
1349 }
1350 *colons = '\0';
1351 }
William Lallemand99cc2182020-06-25 15:19:51 +02001352
1353 /* strip trailing slashes, including first one */
1354 for (end = crtlist_path + strlen(crtlist_path) - 1; end >= crtlist_path && *end == '/'; end--)
1355 *end = 0;
1356
William Lallemandc756bbd2020-05-13 17:23:59 +02001357 /* look for crtlist */
1358 ebmb = ebst_lookup(&crtlists_tree, crtlist_path);
1359 if (!ebmb) {
1360 memprintf(&err, "crt-list '%s' does not exist!", crtlist_path);
1361 goto error;
1362 }
1363 crtlist = ebmb_entry(ebmb, struct crtlist, node);
1364
1365 /* look for store */
1366 store = ckchs_lookup(cert_path);
1367 if (store == NULL) {
1368 memprintf(&err, "certificate '%s' does not exist!", cert_path);
1369 goto error;
1370 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001371 if (store->ckch == NULL || store->ckch->cert == NULL) {
1372 memprintf(&err, "certificate '%s' is empty!", cert_path);
1373 goto error;
1374 }
1375
1376 ebpt = ebpt_lookup(&crtlist->entries, store);
1377 if (!ebpt) {
1378 memprintf(&err, "certificate '%s' can't be found in crt-list '%s'!", cert_path, crtlist_path);
1379 goto error;
1380 }
1381
1382 /* list the line number of entries for errors in err, and select the right ebpt */
1383 for (; ebpt; ebpt = ebpt_next_dup(ebpt)) {
1384 struct crtlist_entry *tmp;
1385
1386 tmp = ebpt_entry(ebpt, struct crtlist_entry, node);
1387 memprintf(&err, "%s%s%d", err ? err : "", err ? ", " : "", tmp->linenum);
1388
1389 /* select the entry we wanted */
1390 if (linenum == 0 || tmp->linenum == linenum) {
1391 if (!entry)
1392 entry = tmp;
1393 }
1394 }
1395
1396 /* we didn't found the specified entry */
1397 if (!entry) {
1398 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);
1399 goto error;
1400 }
1401
1402 /* we didn't specified a line number but there were several entries */
1403 if (linenum == 0 && ebpt_next_dup(&entry->node)) {
1404 memprintf(&err, "found the certificate '%s' in several entries, please specify a line number preceded by colons (%s)!", cert_path, err ? err : NULL);
1405 goto error;
1406 }
1407
Remi Tricot-Le Bretonbc2c3862021-03-26 10:47:50 +01001408 /* Iterate over all the instances in order to see if any of them is a
1409 * default instance. If this is the case, the entry won't be suppressed. */
1410 list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_crtlist_entry) {
1411 if (inst->is_default && !inst->bind_conf->strict_sni) {
1412 if (!error_message_dumped) {
1413 memprintf(&err, "certificate '%s' cannot be deleted, it is used as default certificate by the following frontends:\n", cert_path);
1414 error_message_dumped = 1;
1415 }
1416 memprintf(&err, "%s\t- %s:%d\n", err, inst->bind_conf->file, inst->bind_conf->line);
1417 }
1418 }
1419 if (error_message_dumped)
1420 goto error;
1421
William Lallemandc756bbd2020-05-13 17:23:59 +02001422 /* upon error free the ckch_inst and everything inside */
1423
1424 ebpt_delete(&entry->node);
Willy Tarreau2b718102021-04-21 07:32:39 +02001425 LIST_DELETE(&entry->by_crtlist);
1426 LIST_DELETE(&entry->by_ckch_store);
William Lallemandc756bbd2020-05-13 17:23:59 +02001427
1428 list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_crtlist_entry) {
1429 struct sni_ctx *sni, *sni_s;
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001430 struct ckch_inst_link_ref *link_ref, *link_ref_s;
William Lallemandc756bbd2020-05-13 17:23:59 +02001431
1432 HA_RWLOCK_WRLOCK(SNI_LOCK, &inst->bind_conf->sni_lock);
1433 list_for_each_entry_safe(sni, sni_s, &inst->sni_ctx, by_ckch_inst) {
1434 ebmb_delete(&sni->name);
Willy Tarreau2b718102021-04-21 07:32:39 +02001435 LIST_DELETE(&sni->by_ckch_inst);
William Lallemandc756bbd2020-05-13 17:23:59 +02001436 SSL_CTX_free(sni->ctx);
1437 free(sni);
1438 }
1439 HA_RWLOCK_WRUNLOCK(SNI_LOCK, &inst->bind_conf->sni_lock);
Willy Tarreau2b718102021-04-21 07:32:39 +02001440 LIST_DELETE(&inst->by_ckchs);
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001441 list_for_each_entry_safe(link_ref, link_ref_s, &inst->cafile_link_refs, list) {
1442 LIST_DELETE(&link_ref->link->list);
1443 LIST_DELETE(&link_ref->list);
1444 free(link_ref);
1445 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001446 free(inst);
1447 }
1448
1449 crtlist_free_filters(entry->filters);
1450 ssl_sock_free_ssl_conf(entry->ssl_conf);
1451 free(entry->ssl_conf);
1452 free(entry);
1453
1454 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1455 err = memprintf(&err, "Entry '%s' deleted in crtlist '%s'!\n", cert_path, crtlist_path);
1456 return cli_dynmsg(appctx, LOG_NOTICE, err);
1457
1458error:
1459 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1460 err = memprintf(&err, "Can't delete the entry: %s\n", err ? err : "");
1461 return cli_dynerr(appctx, err);
1462}
1463
1464
William Lallemandee8530c2020-06-23 18:19:42 +02001465/* unlink and free all crt-list and crt-list entries */
1466void crtlist_deinit()
1467{
1468 struct eb_node *node, *next;
1469 struct crtlist *crtlist;
1470
1471 node = eb_first(&crtlists_tree);
1472 while (node) {
1473 next = eb_next(node);
1474 crtlist = ebmb_entry(node, struct crtlist, node);
1475 crtlist_free(crtlist);
1476 node = next;
1477 }
1478}
1479
William Lallemandc756bbd2020-05-13 17:23:59 +02001480
1481/* register cli keywords */
1482static struct cli_kw_list cli_kws = {{ },{
Willy Tarreaub205bfd2021-05-07 11:38:37 +02001483 { { "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 },
1484 { { "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 },
1485 { { "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 +02001486 { { NULL }, NULL, NULL, NULL } }
1487};
1488
1489INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws);
1490