blob: d03c9932b744ab1d196b8477779c243d76fce723 [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 }
120 return dst;
121
122error:
123 ssl_sock_free_ssl_conf(dst);
124 free(dst);
125
126 return NULL;
127}
William Lallemand6e9556b2020-05-12 17:52:44 +0200128
129/* free sni filters */
130void crtlist_free_filters(char **args)
131{
132 int i;
133
134 if (!args)
135 return;
136
137 for (i = 0; args[i]; i++)
138 free(args[i]);
139
140 free(args);
141}
142
143/* Alloc and duplicate a char ** array */
144char **crtlist_dup_filters(char **args, int fcount)
145{
146 char **dst;
147 int i;
148
149 if (fcount == 0)
150 return NULL;
151
152 dst = calloc(fcount + 1, sizeof(*dst));
153 if (!dst)
154 return NULL;
155
156 for (i = 0; i < fcount; i++) {
157 dst[i] = strdup(args[i]);
158 if (!dst[i])
159 goto error;
160 }
161 return dst;
162
163error:
164 crtlist_free_filters(dst);
165 return NULL;
166}
167
168/*
169 * Detach and free a crtlist_entry.
170 * Free the filters, the ssl_conf and call ckch_inst_free() for each ckch_inst
171 */
172void crtlist_entry_free(struct crtlist_entry *entry)
173{
174 struct ckch_inst *inst, *inst_s;
175
176 if (entry == NULL)
177 return;
178
179 ebpt_delete(&entry->node);
Willy Tarreau2b718102021-04-21 07:32:39 +0200180 LIST_DELETE(&entry->by_crtlist);
181 LIST_DELETE(&entry->by_ckch_store);
William Lallemand6e9556b2020-05-12 17:52:44 +0200182 crtlist_free_filters(entry->filters);
183 ssl_sock_free_ssl_conf(entry->ssl_conf);
184 free(entry->ssl_conf);
185 list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_crtlist_entry) {
186 ckch_inst_free(inst);
187 }
188 free(entry);
189}
William Lallemand5622c452020-09-10 19:08:49 +0200190/*
191 * Duplicate a crt_list entry and its content (ssl_conf, filters/fcount)
192 * Return a pointer to the new entry
193 */
194struct crtlist_entry *crtlist_entry_dup(struct crtlist_entry *src)
195{
196 struct crtlist_entry *entry;
197
198 if (src == NULL)
199 return NULL;
200
201 entry = crtlist_entry_new();
202 if (entry == NULL)
203 return NULL;
204
205 if (src->filters) {
206 entry->filters = crtlist_dup_filters(src->filters, src->fcount);
207 if (!entry->filters)
208 goto error;
209 }
210 entry->fcount = src->fcount;
211 if (src->ssl_conf) {
212 entry->ssl_conf = crtlist_dup_ssl_conf(src->ssl_conf);
213 if (!entry->ssl_conf)
214 goto error;
215 }
216 entry->crtlist = src->crtlist;
217
218 return entry;
219
220error:
221
222 crtlist_free_filters(entry->filters);
223 ssl_sock_free_ssl_conf(entry->ssl_conf);
224 free(entry->ssl_conf);
225 free(entry);
226
227 return NULL;
228}
William Lallemand6e9556b2020-05-12 17:52:44 +0200229
230/*
231 * Allocate and initialize a crtlist_entry
232 */
233struct crtlist_entry *crtlist_entry_new()
234{
235 struct crtlist_entry *entry;
236
237 entry = calloc(1, sizeof(*entry));
238 if (entry == NULL)
239 return NULL;
240
241 LIST_INIT(&entry->ckch_inst);
242
Willy Tarreau2b718102021-04-21 07:32:39 +0200243 /* initialize the nodes so we can LIST_DELETE in any cases */
William Lallemand6e9556b2020-05-12 17:52:44 +0200244 LIST_INIT(&entry->by_crtlist);
245 LIST_INIT(&entry->by_ckch_store);
246
247 return entry;
248}
249
250/* Free a crtlist, from the crt_entry to the content of the ssl_conf */
251void crtlist_free(struct crtlist *crtlist)
252{
253 struct crtlist_entry *entry, *s_entry;
William Lallemand6a3168a2020-06-23 11:43:35 +0200254 struct bind_conf_list *bind_conf_node;
William Lallemand6e9556b2020-05-12 17:52:44 +0200255
256 if (crtlist == NULL)
257 return;
258
William Lallemand6a3168a2020-06-23 11:43:35 +0200259 bind_conf_node = crtlist->bind_conf;
260 while (bind_conf_node) {
261 struct bind_conf_list *next = bind_conf_node->next;
262 free(bind_conf_node);
263 bind_conf_node = next;
264 }
265
William Lallemand6e9556b2020-05-12 17:52:44 +0200266 list_for_each_entry_safe(entry, s_entry, &crtlist->ord_entries, by_crtlist) {
267 crtlist_entry_free(entry);
268 }
269 ebmb_delete(&crtlist->node);
270 free(crtlist);
271}
272
273/* Alloc and initialize a struct crtlist
274 * <filename> is the key of the ebmb_node
275 * <unique> initialize the list of entries to be unique (1) or not (0)
276 */
277struct crtlist *crtlist_new(const char *filename, int unique)
278{
279 struct crtlist *newlist;
280
281 newlist = calloc(1, sizeof(*newlist) + strlen(filename) + 1);
282 if (newlist == NULL)
283 return NULL;
284
285 memcpy(newlist->node.key, filename, strlen(filename) + 1);
286 if (unique)
287 newlist->entries = EB_ROOT_UNIQUE;
288 else
289 newlist->entries = EB_ROOT;
290
291 LIST_INIT(&newlist->ord_entries);
292
293 return newlist;
294}
295
296/*
297 * Read a single crt-list line. /!\ alter the <line> string.
298 * Fill <crt_path> and <crtlist_entry>
299 * <crtlist_entry> must be alloc and free by the caller
300 * <crtlist_entry->ssl_conf> is alloc by the function
301 * <crtlist_entry->filters> is alloc by the function
302 * <crt_path> is a ptr in <line>
303 * Return an error code
304 */
Remi Tricot-Le Bretonfb00f312021-03-23 16:41:53 +0100305int 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 +0200306{
307 int cfgerr = 0;
308 int arg, newarg, cur_arg, i, ssl_b = 0, ssl_e = 0;
309 char *end;
310 char *args[MAX_CRT_ARGS + 1];
311 struct ssl_bind_conf *ssl_conf = NULL;
312
313 if (!line || !crt_path || !entry)
314 return ERR_ALERT | ERR_FATAL;
315
316 end = line + strlen(line);
317 if (end-line >= CRT_LINESIZE-1 && *(end-1) != '\n') {
318 /* Check if we reached the limit and the last char is not \n.
319 * Watch out for the last line without the terminating '\n'!
320 */
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200321 memprintf(err, "parsing [%s:%d]: line too long, limit is %d characters",
322 file, linenum, CRT_LINESIZE-1);
William Lallemand6e9556b2020-05-12 17:52:44 +0200323 cfgerr |= ERR_ALERT | ERR_FATAL;
324 goto error;
325 }
326 arg = 0;
327 newarg = 1;
328 while (*line) {
329 if (isspace((unsigned char)*line)) {
330 newarg = 1;
331 *line = 0;
332 } else if (*line == '[') {
333 if (ssl_b) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200334 memprintf(err, "parsing [%s:%d]: too many '['", file, linenum);
William Lallemand6e9556b2020-05-12 17:52:44 +0200335 cfgerr |= ERR_ALERT | ERR_FATAL;
336 goto error;
337 }
338 if (!arg) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200339 memprintf(err, "parsing [%s:%d]: file must start with a cert", file, linenum);
William Lallemand6e9556b2020-05-12 17:52:44 +0200340 cfgerr |= ERR_ALERT | ERR_FATAL;
341 goto error;
342 }
343 ssl_b = arg;
344 newarg = 1;
345 *line = 0;
346 } else if (*line == ']') {
347 if (ssl_e) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200348 memprintf(err, "parsing [%s:%d]: too many ']'", file, linenum);
William Lallemand6e9556b2020-05-12 17:52:44 +0200349 cfgerr |= ERR_ALERT | ERR_FATAL;
350 goto error;
351 }
352 if (!ssl_b) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200353 memprintf(err, "parsing [%s:%d]: missing '['", file, linenum);
William Lallemand6e9556b2020-05-12 17:52:44 +0200354 cfgerr |= ERR_ALERT | ERR_FATAL;
355 goto error;
356 }
357 ssl_e = arg;
358 newarg = 1;
359 *line = 0;
360 } else if (newarg) {
361 if (arg == MAX_CRT_ARGS) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200362 memprintf(err, "parsing [%s:%d]: too many args ", file, linenum);
William Lallemand6e9556b2020-05-12 17:52:44 +0200363 cfgerr |= ERR_ALERT | ERR_FATAL;
364 goto error;
365 }
366 newarg = 0;
367 args[arg++] = line;
368 }
369 line++;
370 }
371 args[arg++] = line;
372
373 /* empty line */
374 if (!*args[0]) {
375 cfgerr |= ERR_NONE;
376 goto error;
377 }
378
379 *crt_path = args[0];
380
381 if (ssl_b) {
382 ssl_conf = calloc(1, sizeof *ssl_conf);
383 if (!ssl_conf) {
384 memprintf(err, "not enough memory!");
385 cfgerr |= ERR_ALERT | ERR_FATAL;
386 goto error;
387 }
388 }
Remi Tricot-Le Bretonfb00f312021-03-23 16:41:53 +0100389
William Lallemand6e9556b2020-05-12 17:52:44 +0200390 cur_arg = ssl_b ? ssl_b : 1;
391 while (cur_arg < ssl_e) {
392 newarg = 0;
393 for (i = 0; ssl_bind_kws[i].kw != NULL; i++) {
394 if (strcmp(ssl_bind_kws[i].kw, args[cur_arg]) == 0) {
395 newarg = 1;
Remi Tricot-Le Bretonfb00f312021-03-23 16:41:53 +0100396 cfgerr |= ssl_bind_kws[i].parse(args, cur_arg, NULL, ssl_conf, from_cli, err);
William Lallemand6e9556b2020-05-12 17:52:44 +0200397 if (cur_arg + 1 + ssl_bind_kws[i].skip > ssl_e) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200398 memprintf(err, "parsing [%s:%d]: ssl args out of '[]' for %s",
399 file, linenum, args[cur_arg]);
William Lallemand6e9556b2020-05-12 17:52:44 +0200400 cfgerr |= ERR_ALERT | ERR_FATAL;
401 goto error;
402 }
403 cur_arg += 1 + ssl_bind_kws[i].skip;
404 break;
405 }
406 }
407 if (!cfgerr && !newarg) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200408 memprintf(err, "parsing [%s:%d]: unknown ssl keyword %s",
409 file, linenum, args[cur_arg]);
William Lallemand6e9556b2020-05-12 17:52:44 +0200410 cfgerr |= ERR_ALERT | ERR_FATAL;
411 goto error;
412 }
413 }
414 entry->linenum = linenum;
415 entry->ssl_conf = ssl_conf;
416 entry->filters = crtlist_dup_filters(&args[cur_arg], arg - cur_arg - 1);
417 entry->fcount = arg - cur_arg - 1;
418
419 return cfgerr;
420
421error:
422 crtlist_free_filters(entry->filters);
423 entry->filters = NULL;
424 ssl_sock_free_ssl_conf(entry->ssl_conf);
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100425 ha_free(&entry->ssl_conf);
William Lallemand6e9556b2020-05-12 17:52:44 +0200426 return cfgerr;
427}
428
429
430
431/* This function parse a crt-list file and store it in a struct crtlist, each line is a crtlist_entry structure
432 * Fill the <crtlist> argument with a pointer to a new crtlist struct
433 *
434 * This function tries to open and store certificate files.
435 */
436int crtlist_parse_file(char *file, struct bind_conf *bind_conf, struct proxy *curproxy, struct crtlist **crtlist, char **err)
437{
438 struct crtlist *newlist;
439 struct crtlist_entry *entry = NULL;
440 char thisline[CRT_LINESIZE];
William Lallemand6e9556b2020-05-12 17:52:44 +0200441 FILE *f;
442 struct stat buf;
443 int linenum = 0;
444 int cfgerr = 0;
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200445 int missing_lf = -1;
William Lallemand6e9556b2020-05-12 17:52:44 +0200446
447 if ((f = fopen(file, "r")) == NULL) {
448 memprintf(err, "cannot open file '%s' : %s", file, strerror(errno));
449 return ERR_ALERT | ERR_FATAL;
450 }
451
452 newlist = crtlist_new(file, 0);
453 if (newlist == NULL) {
454 memprintf(err, "Not enough memory!");
455 cfgerr |= ERR_ALERT | ERR_FATAL;
456 goto error;
457 }
458
459 while (fgets(thisline, sizeof(thisline), f) != NULL) {
460 char *end;
461 char *line = thisline;
462 char *crt_path;
William Lallemand86c2dd62020-11-20 14:23:38 +0100463 char path[MAXPATHLEN+1];
William Lallemand6e9556b2020-05-12 17:52:44 +0200464 struct ckch_store *ckchs;
William Lallemand77e1c6f2020-11-20 18:26:09 +0100465 int found = 0;
William Lallemand6e9556b2020-05-12 17:52:44 +0200466
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200467 if (missing_lf != -1) {
468 memprintf(err, "parsing [%s:%d]: Stray NUL character at position %d.\n",
469 file, linenum, (missing_lf + 1));
470 cfgerr |= ERR_ALERT | ERR_FATAL;
471 missing_lf = -1;
472 break;
473 }
474
William Lallemand6e9556b2020-05-12 17:52:44 +0200475 linenum++;
476 end = line + strlen(line);
477 if (end-line == sizeof(thisline)-1 && *(end-1) != '\n') {
478 /* Check if we reached the limit and the last char is not \n.
479 * Watch out for the last line without the terminating '\n'!
480 */
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200481 memprintf(err, "parsing [%s:%d]: line too long, limit is %d characters",
482 file, linenum, (int)sizeof(thisline)-1);
William Lallemand6e9556b2020-05-12 17:52:44 +0200483 cfgerr |= ERR_ALERT | ERR_FATAL;
484 break;
485 }
486
487 if (*line == '#' || *line == '\n' || *line == '\r')
488 continue;
489
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200490 if (end > line && *(end-1) == '\n') {
491 /* kill trailing LF */
492 *(end - 1) = 0;
493 }
494 else {
495 /* mark this line as truncated */
496 missing_lf = end - line;
497 }
498
William Lallemand6e9556b2020-05-12 17:52:44 +0200499 entry = crtlist_entry_new();
500 if (entry == NULL) {
501 memprintf(err, "Not enough memory!");
502 cfgerr |= ERR_ALERT | ERR_FATAL;
503 goto error;
504 }
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200505
Remi Tricot-Le Bretonfb00f312021-03-23 16:41:53 +0100506 cfgerr |= crtlist_parse_line(thisline, &crt_path, entry, file, linenum, 0, err);
William Lallemand20b0fed2020-09-28 15:45:16 +0200507 if (cfgerr & ERR_CODE)
William Lallemand6e9556b2020-05-12 17:52:44 +0200508 goto error;
509
510 /* empty line */
511 if (!crt_path || !*crt_path) {
512 crtlist_entry_free(entry);
513 entry = NULL;
514 continue;
515 }
516
517 if (*crt_path != '/' && global_ssl.crt_base) {
Willy Tarreau326a8662022-05-09 10:31:28 +0200518 if ((strlen(global_ssl.crt_base) + 1 + strlen(crt_path)) > sizeof(path) ||
Willy Tarreau6c3cc562022-05-09 21:14:04 +0200519 snprintf(path, sizeof(path), "%s/%s", global_ssl.crt_base, crt_path) > sizeof(path)) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200520 memprintf(err, "parsing [%s:%d]: '%s' : path too long",
521 file, linenum, crt_path);
William Lallemand6e9556b2020-05-12 17:52:44 +0200522 cfgerr |= ERR_ALERT | ERR_FATAL;
523 goto error;
524 }
William Lallemand6e9556b2020-05-12 17:52:44 +0200525 crt_path = path;
526 }
527
528 /* Look for a ckch_store or create one */
529 ckchs = ckchs_lookup(crt_path);
530 if (ckchs == NULL) {
William Lallemand47da8212020-09-10 19:13:27 +0200531 if (stat(crt_path, &buf) == 0) {
William Lallemand77e1c6f2020-11-20 18:26:09 +0100532 found++;
William Lallemand47da8212020-09-10 19:13:27 +0200533
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200534 ckchs = ckchs_load_cert_file(crt_path, err);
William Lallemand47da8212020-09-10 19:13:27 +0200535 if (ckchs == NULL) {
536 cfgerr |= ERR_ALERT | ERR_FATAL;
537 goto error;
538 }
539
540 entry->node.key = ckchs;
541 entry->crtlist = newlist;
542 ebpt_insert(&newlist->entries, &entry->node);
Willy Tarreau2b718102021-04-21 07:32:39 +0200543 LIST_APPEND(&newlist->ord_entries, &entry->by_crtlist);
544 LIST_APPEND(&ckchs->crtlist_entry, &entry->by_ckch_store);
William Lallemand6e9556b2020-05-12 17:52:44 +0200545
William Lallemand73404572020-11-20 18:23:40 +0100546 } else if (global_ssl.extra_files & SSL_GF_BUNDLE) {
William Lallemand47da8212020-09-10 19:13:27 +0200547 /* If we didn't find the file, this could be a
William Lallemand51f784b2020-10-02 18:08:18 +0200548 bundle, since 2.3 we don't support multiple
549 certificate in the same OpenSSL store, so we
550 emulate it by loading each file separately. To
551 do so we need to duplicate the entry in the
552 crt-list because it becomes independent */
William Lallemand47da8212020-09-10 19:13:27 +0200553 char fp[MAXPATHLEN+1] = {0};
554 int n = 0;
555 struct crtlist_entry *entry_dup = entry; /* use the previous created entry */
556 for (n = 0; n < SSL_SOCK_NUM_KEYTYPES; n++) {
557 struct stat buf;
558 int ret;
559
William Lallemand86c2dd62020-11-20 14:23:38 +0100560 ret = snprintf(fp, sizeof(fp), "%s.%s", crt_path, SSL_SOCK_KEYTYPE_NAMES[n]);
William Lallemand47da8212020-09-10 19:13:27 +0200561 if (ret > sizeof(fp))
562 continue;
563
564 ckchs = ckchs_lookup(fp);
William Lallemand77e1c6f2020-11-20 18:26:09 +0100565 if (!ckchs) {
566 if (stat(fp, &buf) == 0) {
567 ckchs = ckchs_load_cert_file(fp, err);
568 if (!ckchs) {
William Lallemand47da8212020-09-10 19:13:27 +0200569 cfgerr |= ERR_ALERT | ERR_FATAL;
570 goto error;
571 }
William Lallemand77e1c6f2020-11-20 18:26:09 +0100572 } else {
573 continue; /* didn't find this extension, skip */
574 }
575 }
576 found++;
577 linenum++; /* we duplicate the line for this entry in the bundle */
578 if (!entry_dup) { /* if the entry was used, duplicate one */
579 linenum++;
580 entry_dup = crtlist_entry_dup(entry);
581 if (!entry_dup) {
582 cfgerr |= ERR_ALERT | ERR_FATAL;
583 goto error;
William Lallemand47da8212020-09-10 19:13:27 +0200584 }
William Lallemand77e1c6f2020-11-20 18:26:09 +0100585 entry_dup->linenum = linenum;
586 }
William Lallemand47da8212020-09-10 19:13:27 +0200587
William Lallemand77e1c6f2020-11-20 18:26:09 +0100588 entry_dup->node.key = ckchs;
589 entry_dup->crtlist = newlist;
590 ebpt_insert(&newlist->entries, &entry_dup->node);
Willy Tarreau2b718102021-04-21 07:32:39 +0200591 LIST_APPEND(&newlist->ord_entries, &entry_dup->by_crtlist);
592 LIST_APPEND(&ckchs->crtlist_entry, &entry_dup->by_ckch_store);
William Lallemand47da8212020-09-10 19:13:27 +0200593
William Lallemand77e1c6f2020-11-20 18:26:09 +0100594 entry_dup = NULL; /* the entry was used, we need a new one next round */
William Lallemand47da8212020-09-10 19:13:27 +0200595 }
William Lallemandb7fdfdf2020-12-04 15:45:02 +0100596#if HA_OPENSSL_VERSION_NUMBER < 0x10101000L
597 if (found) {
598 memprintf(err, "%sCan't load '%s'. Loading a multi certificates bundle requires OpenSSL >= 1.1.1\n",
599 err && *err ? *err : "", crt_path);
600 cfgerr |= ERR_ALERT | ERR_FATAL;
601 }
602#endif
William Lallemand47da8212020-09-10 19:13:27 +0200603 }
William Lallemand77e1c6f2020-11-20 18:26:09 +0100604 if (!found) {
605 memprintf(err, "%sunable to stat SSL certificate from file '%s' : %s.\n",
606 err && *err ? *err : "", crt_path, strerror(errno));
607 cfgerr |= ERR_ALERT | ERR_FATAL;
608 }
609
William Lallemand50c03aa2020-11-06 16:24:07 +0100610 } else {
611 entry->node.key = ckchs;
612 entry->crtlist = newlist;
613 ebpt_insert(&newlist->entries, &entry->node);
Willy Tarreau2b718102021-04-21 07:32:39 +0200614 LIST_APPEND(&newlist->ord_entries, &entry->by_crtlist);
615 LIST_APPEND(&ckchs->crtlist_entry, &entry->by_ckch_store);
William Lallemand77e1c6f2020-11-20 18:26:09 +0100616 found++;
William Lallemand47da8212020-09-10 19:13:27 +0200617 }
William Lallemand6e9556b2020-05-12 17:52:44 +0200618 entry = NULL;
619 }
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200620
621 if (missing_lf != -1) {
622 memprintf(err, "parsing [%s:%d]: Missing LF on last line, file might have been truncated at position %d.\n",
623 file, linenum, (missing_lf + 1));
624 cfgerr |= ERR_ALERT | ERR_FATAL;
625 }
626
William Lallemand6e9556b2020-05-12 17:52:44 +0200627 if (cfgerr & ERR_CODE)
628 goto error;
629
630 newlist->linecount = linenum;
631
632 fclose(f);
633 *crtlist = newlist;
634
635 return cfgerr;
636error:
637 crtlist_entry_free(entry);
638
639 fclose(f);
640 crtlist_free(newlist);
641 return cfgerr;
642}
643
644/* This function reads a directory and stores it in a struct crtlist, each file is a crtlist_entry structure
645 * Fill the <crtlist> argument with a pointer to a new crtlist struct
646 *
647 * This function tries to open and store certificate files.
648 */
649int crtlist_load_cert_dir(char *path, struct bind_conf *bind_conf, struct crtlist **crtlist, char **err)
650{
651 struct crtlist *dir;
652 struct dirent **de_list;
653 int i, n;
654 struct stat buf;
655 char *end;
656 char fp[MAXPATHLEN+1];
657 int cfgerr = 0;
658 struct ckch_store *ckchs;
William Lallemand6e9556b2020-05-12 17:52:44 +0200659
660 dir = crtlist_new(path, 1);
661 if (dir == NULL) {
662 memprintf(err, "not enough memory");
663 return ERR_ALERT | ERR_FATAL;
664 }
665
666 n = scandir(path, &de_list, 0, alphasort);
667 if (n < 0) {
668 memprintf(err, "%sunable to scan directory '%s' : %s.\n",
669 err && *err ? *err : "", path, strerror(errno));
670 cfgerr |= ERR_ALERT | ERR_FATAL;
671 }
672 else {
673 for (i = 0; i < n; i++) {
674 struct crtlist_entry *entry;
675 struct dirent *de = de_list[i];
676
677 end = strrchr(de->d_name, '.');
Tim Duesterhuse5ff1412021-01-02 22:31:53 +0100678 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 +0200679 goto ignore_entry;
680
681 snprintf(fp, sizeof(fp), "%s/%s", path, de->d_name);
682 if (stat(fp, &buf) != 0) {
683 memprintf(err, "%sunable to stat SSL certificate from file '%s' : %s.\n",
684 err && *err ? *err : "", fp, strerror(errno));
685 cfgerr |= ERR_ALERT | ERR_FATAL;
686 goto ignore_entry;
687 }
688 if (!S_ISREG(buf.st_mode))
689 goto ignore_entry;
690
691 entry = crtlist_entry_new();
692 if (entry == NULL) {
693 memprintf(err, "not enough memory '%s'", fp);
694 cfgerr |= ERR_ALERT | ERR_FATAL;
695 goto ignore_entry;
696 }
697
William Lallemand6e9556b2020-05-12 17:52:44 +0200698 ckchs = ckchs_lookup(fp);
699 if (ckchs == NULL)
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200700 ckchs = ckchs_load_cert_file(fp, err);
William Lallemand6e9556b2020-05-12 17:52:44 +0200701 if (ckchs == NULL) {
702 free(de);
703 free(entry);
704 cfgerr |= ERR_ALERT | ERR_FATAL;
705 goto end;
706 }
707 entry->node.key = ckchs;
708 entry->crtlist = dir;
Willy Tarreau2b718102021-04-21 07:32:39 +0200709 LIST_APPEND(&ckchs->crtlist_entry, &entry->by_ckch_store);
710 LIST_APPEND(&dir->ord_entries, &entry->by_crtlist);
William Lallemand6e9556b2020-05-12 17:52:44 +0200711 ebpt_insert(&dir->entries, &entry->node);
712
713ignore_entry:
714 free(de);
715 }
716end:
717 free(de_list);
718 }
719
720 if (cfgerr & ERR_CODE) {
721 /* free the dir and entries on error */
722 crtlist_free(dir);
723 } else {
724 *crtlist = dir;
725 }
726 return cfgerr;
727
728}
729
William Lallemandc756bbd2020-05-13 17:23:59 +0200730/*
731 * Take an ssl_bind_conf structure and append the configuration line used to
732 * create it in the buffer
733 */
734static void dump_crtlist_sslconf(struct buffer *buf, const struct ssl_bind_conf *conf)
735{
736 int space = 0;
737
738 if (conf == NULL)
739 return;
740
741 chunk_appendf(buf, " [");
742#ifdef OPENSSL_NPN_NEGOTIATED
743 if (conf->npn_str) {
744 int len = conf->npn_len;
745 char *ptr = conf->npn_str;
746 int comma = 0;
747
748 if (space) chunk_appendf(buf, " ");
749 chunk_appendf(buf, "npn ");
750 while (len) {
751 unsigned short size;
752
753 size = *ptr;
754 ptr++;
755 if (comma)
756 chunk_memcat(buf, ",", 1);
757 chunk_memcat(buf, ptr, size);
758 ptr += size;
759 len -= size + 1;
760 comma = 1;
761 }
762 chunk_memcat(buf, "", 1); /* finish with a \0 */
763 space++;
764 }
765#endif
766#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
767 if (conf->alpn_str) {
768 int len = conf->alpn_len;
769 char *ptr = conf->alpn_str;
770 int comma = 0;
771
772 if (space) chunk_appendf(buf, " ");
773 chunk_appendf(buf, "alpn ");
774 while (len) {
775 unsigned short size;
776
777 size = *ptr;
778 ptr++;
779 if (comma)
780 chunk_memcat(buf, ",", 1);
781 chunk_memcat(buf, ptr, size);
782 ptr += size;
783 len -= size + 1;
784 comma = 1;
785 }
786 chunk_memcat(buf, "", 1); /* finish with a \0 */
787 space++;
788 }
789#endif
790 /* verify */
791 {
792 if (conf->verify == SSL_SOCK_VERIFY_NONE) {
793 if (space) chunk_appendf(buf, " ");
794 chunk_appendf(buf, "verify none");
795 space++;
796 } else if (conf->verify == SSL_SOCK_VERIFY_OPTIONAL) {
797 if (space) chunk_appendf(buf, " ");
798 chunk_appendf(buf, "verify optional");
799 space++;
800 } else if (conf->verify == SSL_SOCK_VERIFY_REQUIRED) {
801 if (space) chunk_appendf(buf, " ");
802 chunk_appendf(buf, "verify required");
803 space++;
804 }
805 }
806
807 if (conf->no_ca_names) {
808 if (space) chunk_appendf(buf, " ");
809 chunk_appendf(buf, "no-ca-names");
810 space++;
811 }
812
813 if (conf->early_data) {
814 if (space) chunk_appendf(buf, " ");
815 chunk_appendf(buf, "allow-0rtt");
816 space++;
817 }
818 if (conf->ca_file) {
819 if (space) chunk_appendf(buf, " ");
820 chunk_appendf(buf, "ca-file %s", conf->ca_file);
821 space++;
822 }
823 if (conf->crl_file) {
824 if (space) chunk_appendf(buf, " ");
825 chunk_appendf(buf, "crl-file %s", conf->crl_file);
826 space++;
827 }
828 if (conf->ciphers) {
829 if (space) chunk_appendf(buf, " ");
830 chunk_appendf(buf, "ciphers %s", conf->ciphers);
831 space++;
832 }
Ilya Shipitsinf34ed0b2020-11-21 14:37:34 +0500833#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES
William Lallemandc756bbd2020-05-13 17:23:59 +0200834 if (conf->ciphersuites) {
835 if (space) chunk_appendf(buf, " ");
836 chunk_appendf(buf, "ciphersuites %s", conf->ciphersuites);
837 space++;
838 }
839#endif
840 if (conf->curves) {
841 if (space) chunk_appendf(buf, " ");
842 chunk_appendf(buf, "curves %s", conf->curves);
843 space++;
844 }
845 if (conf->ecdhe) {
846 if (space) chunk_appendf(buf, " ");
847 chunk_appendf(buf, "ecdhe %s", conf->ecdhe);
848 space++;
849 }
850
851 /* the crt-lists only support ssl-min-ver and ssl-max-ver */
William Lallemand8177ad92020-05-20 16:49:02 +0200852 if (conf->ssl_methods_cfg.min) {
William Lallemandc756bbd2020-05-13 17:23:59 +0200853 if (space) chunk_appendf(buf, " ");
William Lallemand8177ad92020-05-20 16:49:02 +0200854 chunk_appendf(buf, "ssl-min-ver %s", methodVersions[conf->ssl_methods_cfg.min].name);
William Lallemandc756bbd2020-05-13 17:23:59 +0200855 space++;
856 }
857
William Lallemand8177ad92020-05-20 16:49:02 +0200858 if (conf->ssl_methods_cfg.max) {
William Lallemandc756bbd2020-05-13 17:23:59 +0200859 if (space) chunk_appendf(buf, " ");
William Lallemand8177ad92020-05-20 16:49:02 +0200860 chunk_appendf(buf, "ssl-max-ver %s", methodVersions[conf->ssl_methods_cfg.max].name);
William Lallemandc756bbd2020-05-13 17:23:59 +0200861 space++;
862 }
863
864 chunk_appendf(buf, "]");
865
866 return;
867}
868
869/* dump a list of filters */
870static void dump_crtlist_filters(struct buffer *buf, struct crtlist_entry *entry)
871{
872 int i;
873
874 if (!entry->fcount)
875 return;
876
877 for (i = 0; i < entry->fcount; i++) {
878 chunk_appendf(buf, " %s", entry->filters[i]);
879 }
880 return;
881}
882
883/************************** CLI functions ****************************/
884
885
886/* CLI IO handler for '(show|dump) ssl crt-list' */
887static int cli_io_handler_dump_crtlist(struct appctx *appctx)
888{
889 struct buffer *trash = alloc_trash_chunk();
890 struct stream_interface *si = appctx->owner;
891 struct ebmb_node *lnode;
892
893 if (trash == NULL)
894 return 1;
895
896 /* dump the list of crt-lists */
897 lnode = appctx->ctx.cli.p1;
898 if (lnode == NULL)
899 lnode = ebmb_first(&crtlists_tree);
900 while (lnode) {
901 chunk_appendf(trash, "%s\n", lnode->key);
902 if (ci_putchk(si_ic(si), trash) == -1) {
903 si_rx_room_blk(si);
904 goto yield;
905 }
906 lnode = ebmb_next(lnode);
907 }
908 free_trash_chunk(trash);
909 return 1;
910yield:
911 appctx->ctx.cli.p1 = lnode;
912 free_trash_chunk(trash);
913 return 0;
914}
915
916/* CLI IO handler for '(show|dump) ssl crt-list <filename>' */
917static int cli_io_handler_dump_crtlist_entries(struct appctx *appctx)
918{
919 struct buffer *trash = alloc_trash_chunk();
920 struct crtlist *crtlist;
921 struct stream_interface *si = appctx->owner;
922 struct crtlist_entry *entry;
923
924 if (trash == NULL)
925 return 1;
926
927 crtlist = ebmb_entry(appctx->ctx.cli.p0, struct crtlist, node);
928
929 entry = appctx->ctx.cli.p1;
930 if (entry == NULL) {
931 entry = LIST_ELEM((crtlist->ord_entries).n, typeof(entry), by_crtlist);
932 chunk_appendf(trash, "# %s\n", crtlist->node.key);
933 if (ci_putchk(si_ic(si), trash) == -1) {
934 si_rx_room_blk(si);
935 goto yield;
936 }
937 }
938
939 list_for_each_entry_from(entry, &crtlist->ord_entries, by_crtlist) {
940 struct ckch_store *store;
941 const char *filename;
942
943 store = entry->node.key;
944 filename = store->path;
945 chunk_appendf(trash, "%s", filename);
946 if (appctx->ctx.cli.i0 == 's') /* show */
947 chunk_appendf(trash, ":%d", entry->linenum);
948 dump_crtlist_sslconf(trash, entry->ssl_conf);
949 dump_crtlist_filters(trash, entry);
950 chunk_appendf(trash, "\n");
951
952 if (ci_putchk(si_ic(si), trash) == -1) {
953 si_rx_room_blk(si);
954 goto yield;
955 }
956 }
957 free_trash_chunk(trash);
958 return 1;
959yield:
960 appctx->ctx.cli.p1 = entry;
961 free_trash_chunk(trash);
962 return 0;
963}
964
965/* CLI argument parser for '(show|dump) ssl crt-list' */
966static int cli_parse_dump_crtlist(char **args, char *payload, struct appctx *appctx, void *private)
967{
968 struct ebmb_node *lnode;
969 char *filename = NULL;
970 int mode;
William Lallemand99cc2182020-06-25 15:19:51 +0200971 char *end;
William Lallemandc756bbd2020-05-13 17:23:59 +0200972
973 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
974 return 1;
975
976 appctx->ctx.cli.p0 = NULL;
977 appctx->ctx.cli.p1 = NULL;
978
Tim Duesterhuse5ff1412021-01-02 22:31:53 +0100979 if (*args[3] && strcmp(args[3], "-n") == 0) {
William Lallemandc756bbd2020-05-13 17:23:59 +0200980 mode = 's';
981 filename = args[4];
982 } else {
983 mode = 'd';
984 filename = args[3];
985 }
986
987 if (mode == 's' && !*args[4])
988 return cli_err(appctx, "'show ssl crt-list -n' expects a filename or a directory\n");
989
990 if (filename && *filename) {
William Lallemand99cc2182020-06-25 15:19:51 +0200991
992
993 /* strip trailing slashes, including first one */
994 for (end = filename + strlen(filename) - 1; end >= filename && *end == '/'; end--)
995 *end = 0;
996
William Lallemandc756bbd2020-05-13 17:23:59 +0200997 lnode = ebst_lookup(&crtlists_tree, filename);
998 if (lnode == NULL)
999 return cli_err(appctx, "didn't find the specified filename\n");
1000
1001 appctx->ctx.cli.p0 = lnode;
1002 appctx->io_handler = cli_io_handler_dump_crtlist_entries;
1003 }
1004 appctx->ctx.cli.i0 = mode;
1005
1006 return 0;
1007}
1008
1009/* release function of the "add ssl crt-list' command, free things and unlock
1010 the spinlock */
1011static void cli_release_add_crtlist(struct appctx *appctx)
1012{
1013 struct crtlist_entry *entry = appctx->ctx.cli.p1;
1014
Christopher Faulet59b4ef52022-06-01 16:31:09 +02001015 if (entry) {
William Lallemandc756bbd2020-05-13 17:23:59 +02001016 struct ckch_inst *inst, *inst_s;
Christopher Faulet59b4ef52022-06-01 16:31:09 +02001017
William Lallemandc756bbd2020-05-13 17:23:59 +02001018 /* upon error free the ckch_inst and everything inside */
1019 ebpt_delete(&entry->node);
Willy Tarreau2b718102021-04-21 07:32:39 +02001020 LIST_DELETE(&entry->by_crtlist);
1021 LIST_DELETE(&entry->by_ckch_store);
William Lallemandc756bbd2020-05-13 17:23:59 +02001022
1023 list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_ckchs) {
1024 ckch_inst_free(inst);
1025 }
1026 crtlist_free_filters(entry->filters);
1027 ssl_sock_free_ssl_conf(entry->ssl_conf);
1028 free(entry->ssl_conf);
1029 free(entry);
1030 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001031 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
Christopher Faulet59b4ef52022-06-01 16:31:09 +02001032 ha_free(&appctx->ctx.cli.err);
William Lallemandc756bbd2020-05-13 17:23:59 +02001033}
1034
1035
1036/* IO Handler for the "add ssl crt-list" command It adds a new entry in the
1037 * crt-list and generates the ckch_insts for each bind_conf that uses this crt-list
1038 *
1039 * The logic is the same as the "commit ssl cert" command but without the
1040 * freeing of the old structures, because there are none.
1041 */
1042static int cli_io_handler_add_crtlist(struct appctx *appctx)
1043{
1044 struct bind_conf_list *bind_conf_node;
1045 struct stream_interface *si = appctx->owner;
1046 struct crtlist *crtlist = appctx->ctx.cli.p0;
1047 struct crtlist_entry *entry = appctx->ctx.cli.p1;
1048 struct ckch_store *store = entry->node.key;
William Lallemandc756bbd2020-05-13 17:23:59 +02001049 struct ckch_inst *new_inst;
William Lallemandc756bbd2020-05-13 17:23:59 +02001050 int i = 0;
1051 int errcode = 0;
1052
William Lallemandc756bbd2020-05-13 17:23:59 +02001053 /* for each bind_conf which use the crt-list, a new ckch_inst must be
1054 * created.
1055 */
1056 if (unlikely(si_ic(si)->flags & (CF_WRITE_ERROR|CF_SHUTW)))
Christopher Faulet59b4ef52022-06-01 16:31:09 +02001057 goto end;
William Lallemandc756bbd2020-05-13 17:23:59 +02001058
1059 while (1) {
1060 switch (appctx->st2) {
1061 case SETCERT_ST_INIT:
1062 /* This state just print the update message */
Christopher Faulet59b4ef52022-06-01 16:31:09 +02001063 chunk_printf(&trash, "Inserting certificate '%s' in crt-list '%s'", store->path, crtlist->node.key);
1064 if (ci_putchk(si_ic(si), &trash) == -1)
William Lallemandc756bbd2020-05-13 17:23:59 +02001065 goto yield;
William Lallemandc756bbd2020-05-13 17:23:59 +02001066 appctx->st2 = SETCERT_ST_GEN;
1067 /* fallthrough */
1068 case SETCERT_ST_GEN:
1069 bind_conf_node = appctx->ctx.cli.p2; /* get the previous ptr from the yield */
1070 if (bind_conf_node == NULL)
1071 bind_conf_node = crtlist->bind_conf;
1072 for (; bind_conf_node; bind_conf_node = bind_conf_node->next) {
1073 struct bind_conf *bind_conf = bind_conf_node->bind_conf;
1074 struct sni_ctx *sni;
1075
Christopher Faulet59b4ef52022-06-01 16:31:09 +02001076 appctx->ctx.cli.p2 = bind_conf_node;
1077
William Lallemandc756bbd2020-05-13 17:23:59 +02001078 /* yield every 10 generations */
1079 if (i > 10) {
Christopher Faulet59b4ef52022-06-01 16:31:09 +02001080 si_rx_endp_more(si); /* let's come back later */
William Lallemandc756bbd2020-05-13 17:23:59 +02001081 goto yield;
1082 }
1083
Christopher Faulet59b4ef52022-06-01 16:31:09 +02001084 /* display one dot for each new instance */
1085 if (ci_putstr(si_ic(si), ".") == -1)
1086 goto yield;
1087
William Lallemandc756bbd2020-05-13 17:23:59 +02001088 /* we don't support multi-cert bundles, only simple ones */
Christopher Faulet59b4ef52022-06-01 16:31:09 +02001089 appctx->ctx.cli.err = NULL;
1090 errcode |= ckch_inst_new_load_store(store->path, store, bind_conf, entry->ssl_conf, entry->filters, entry->fcount, &new_inst, &appctx->ctx.cli.err);
1091 if (errcode & ERR_CODE) {
1092 appctx->st2 = SETCERT_ST_ERROR;
William Lallemandc756bbd2020-05-13 17:23:59 +02001093 goto error;
Christopher Faulet59b4ef52022-06-01 16:31:09 +02001094 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001095
1096 /* we need to initialize the SSL_CTX generated */
1097 /* this iterate on the newly generated SNIs in the new instance to prepare their SSL_CTX */
1098 list_for_each_entry(sni, &new_inst->sni_ctx, by_ckch_inst) {
1099 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 +02001100 appctx->ctx.cli.err = NULL;
1101 errcode |= ssl_sock_prepare_ctx(bind_conf, new_inst->ssl_conf, sni->ctx, &appctx->ctx.cli.err);
1102 if (errcode & ERR_CODE) {
1103 appctx->st2 = SETCERT_ST_ERROR;
William Lallemandc756bbd2020-05-13 17:23:59 +02001104 goto error;
Christopher Faulet59b4ef52022-06-01 16:31:09 +02001105 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001106 }
1107 }
Christopher Faulet59b4ef52022-06-01 16:31:09 +02001108
William Lallemandc756bbd2020-05-13 17:23:59 +02001109 i++;
Willy Tarreau2b718102021-04-21 07:32:39 +02001110 LIST_APPEND(&store->ckch_inst, &new_inst->by_ckchs);
1111 LIST_APPEND(&entry->ckch_inst, &new_inst->by_crtlist_entry);
William Lallemand9ab8f8d2020-06-24 01:00:52 +02001112 new_inst->crtlist_entry = entry;
William Lallemandc756bbd2020-05-13 17:23:59 +02001113 }
1114 appctx->st2 = SETCERT_ST_INSERT;
1115 /* fallthrough */
1116 case SETCERT_ST_INSERT:
William Lallemand31d463d2022-06-20 16:51:53 +02001117 /* the insertion is called for every instance of the store, not
1118 * only the one we generated.
1119 * But the ssl_sock_load_cert_sni() skip the sni already
1120 * inserted. Not every instance has a bind_conf, it could be
1121 * the store of a server so we should be careful */
1122
William Lallemandc756bbd2020-05-13 17:23:59 +02001123 list_for_each_entry(new_inst, &store->ckch_inst, by_ckchs) {
William Lallemand31d463d2022-06-20 16:51:53 +02001124 if (!new_inst->bind_conf) /* this is a server instance */
1125 continue;
William Lallemandc756bbd2020-05-13 17:23:59 +02001126 HA_RWLOCK_WRLOCK(SNI_LOCK, &new_inst->bind_conf->sni_lock);
1127 ssl_sock_load_cert_sni(new_inst, new_inst->bind_conf);
1128 HA_RWLOCK_WRUNLOCK(SNI_LOCK, &new_inst->bind_conf->sni_lock);
1129 }
1130 entry->linenum = ++crtlist->linecount;
Christopher Faulet59b4ef52022-06-01 16:31:09 +02001131 appctx->ctx.cli.p1 = NULL;
1132 appctx->st2 = SETCERT_ST_SUCCESS;
1133 /* fallthrough */
1134 case SETCERT_ST_SUCCESS:
1135 chunk_reset(&trash);
1136 chunk_appendf(&trash, "\n");
1137 if (appctx->ctx.cli.err)
1138 chunk_appendf(&trash, "%s", appctx->ctx.cli.err);
1139 chunk_appendf(&trash, "Success!\n");
1140 if (ci_putchk(si_ic(si), &trash) == -1)
1141 goto yield;
William Lallemandc756bbd2020-05-13 17:23:59 +02001142 appctx->st2 = SETCERT_ST_FIN;
1143 goto end;
Christopher Faulet59b4ef52022-06-01 16:31:09 +02001144
1145 case SETCERT_ST_ERROR:
1146 error:
1147 chunk_printf(&trash, "\n%sFailed!\n", appctx->ctx.cli.err);
1148 if (ci_putchk(si_ic(si), &trash) == -1)
1149 goto yield;
1150 goto end;
1151
1152 default:
1153 goto end;
William Lallemandc756bbd2020-05-13 17:23:59 +02001154 }
1155 }
1156
1157end:
William Lallemandc756bbd2020-05-13 17:23:59 +02001158 /* success: call the release function and don't come back */
1159 return 1;
1160yield:
Christopher Faulet59b4ef52022-06-01 16:31:09 +02001161 si_rx_room_blk(si);
William Lallemandc756bbd2020-05-13 17:23:59 +02001162 return 0; /* should come back */
William Lallemandc756bbd2020-05-13 17:23:59 +02001163}
1164
1165
1166/*
1167 * Parse a "add ssl crt-list <crt-list> <certfile>" line.
1168 * Filters and option must be passed through payload:
1169 */
1170static int cli_parse_add_crtlist(char **args, char *payload, struct appctx *appctx, void *private)
1171{
1172 int cfgerr = 0;
1173 struct ckch_store *store;
1174 char *err = NULL;
1175 char path[MAXPATHLEN+1];
1176 char *crtlist_path;
1177 char *cert_path = NULL;
1178 struct ebmb_node *eb;
1179 struct ebpt_node *inserted;
1180 struct crtlist *crtlist;
1181 struct crtlist_entry *entry = NULL;
William Lallemand99cc2182020-06-25 15:19:51 +02001182 char *end;
William Lallemandc756bbd2020-05-13 17:23:59 +02001183
1184 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
1185 return 1;
1186
1187 if (!*args[3] || (!payload && !*args[4]))
1188 return cli_err(appctx, "'add ssl crtlist' expects a filename and a certificate name\n");
1189
1190 crtlist_path = args[3];
1191
William Lallemand99cc2182020-06-25 15:19:51 +02001192 /* strip trailing slashes, including first one */
1193 for (end = crtlist_path + strlen(crtlist_path) - 1; end >= crtlist_path && *end == '/'; end--)
1194 *end = 0;
1195
William Lallemandc756bbd2020-05-13 17:23:59 +02001196 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
1197 return cli_err(appctx, "Operations on certificates are currently locked!\n");
1198
1199 eb = ebst_lookup(&crtlists_tree, crtlist_path);
1200 if (!eb) {
1201 memprintf(&err, "crt-list '%s' does not exist!", crtlist_path);
1202 goto error;
1203 }
1204 crtlist = ebmb_entry(eb, struct crtlist, node);
1205
1206 entry = crtlist_entry_new();
1207 if (entry == NULL) {
1208 memprintf(&err, "Not enough memory!");
1209 goto error;
1210 }
1211
1212 if (payload) {
1213 char *lf;
1214
1215 lf = strrchr(payload, '\n');
1216 if (lf) {
1217 memprintf(&err, "only one line of payload is supported!");
1218 goto error;
1219 }
1220 /* cert_path is filled here */
Remi Tricot-Le Bretonfb00f312021-03-23 16:41:53 +01001221 cfgerr |= crtlist_parse_line(payload, &cert_path, entry, "CLI", 1, 1, &err);
William Lallemandc756bbd2020-05-13 17:23:59 +02001222 if (cfgerr & ERR_CODE)
1223 goto error;
1224 } else {
1225 cert_path = args[4];
1226 }
1227
1228 if (!cert_path) {
1229 memprintf(&err, "'add ssl crtlist' should contain the certificate name in the payload");
1230 cfgerr |= ERR_ALERT | ERR_FATAL;
1231 goto error;
1232 }
1233
1234 if (eb_gettag(crtlist->entries.b[EB_RGHT])) {
1235 char *slash;
1236
1237 slash = strrchr(cert_path, '/');
1238 if (!slash) {
1239 memprintf(&err, "'%s' is a directory, certificate path '%s' must contain the directory path", (char *)crtlist->node.key, cert_path);
1240 goto error;
1241 }
1242 /* temporary replace / by 0 to do an strcmp */
1243 *slash = '\0';
1244 if (strcmp(cert_path, (char*)crtlist->node.key) != 0) {
1245 *slash = '/';
1246 memprintf(&err, "'%s' is a directory, certificate path '%s' must contain the directory path", (char *)crtlist->node.key, cert_path);
1247 goto error;
1248 }
1249 *slash = '/';
1250 }
1251
1252 if (*cert_path != '/' && global_ssl.crt_base) {
Willy Tarreau326a8662022-05-09 10:31:28 +02001253 if ((strlen(global_ssl.crt_base) + 1 + strlen(cert_path)) > sizeof(path) ||
1254 snprintf(path, sizeof(path), "%s/%s", global_ssl.crt_base, cert_path) > sizeof(path)) {
William Lallemandc756bbd2020-05-13 17:23:59 +02001255 memprintf(&err, "'%s' : path too long", cert_path);
1256 cfgerr |= ERR_ALERT | ERR_FATAL;
1257 goto error;
1258 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001259 cert_path = path;
1260 }
1261
1262 store = ckchs_lookup(cert_path);
1263 if (store == NULL) {
1264 memprintf(&err, "certificate '%s' does not exist!", cert_path);
1265 goto error;
1266 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001267 if (store->ckch == NULL || store->ckch->cert == NULL) {
1268 memprintf(&err, "certificate '%s' is empty!", cert_path);
1269 goto error;
1270 }
1271
1272 /* check if it's possible to insert this new crtlist_entry */
1273 entry->node.key = store;
1274 inserted = ebpt_insert(&crtlist->entries, &entry->node);
1275 if (inserted != &entry->node) {
1276 memprintf(&err, "file already exists in this directory!");
1277 goto error;
1278 }
1279
1280 /* this is supposed to be a directory (EB_ROOT_UNIQUE), so no ssl_conf are allowed */
1281 if ((entry->ssl_conf || entry->filters) && eb_gettag(crtlist->entries.b[EB_RGHT])) {
1282 memprintf(&err, "this is a directory, SSL configuration and filters are not allowed");
1283 goto error;
1284 }
1285
Willy Tarreau2b718102021-04-21 07:32:39 +02001286 LIST_APPEND(&crtlist->ord_entries, &entry->by_crtlist);
William Lallemandc756bbd2020-05-13 17:23:59 +02001287 entry->crtlist = crtlist;
Willy Tarreau2b718102021-04-21 07:32:39 +02001288 LIST_APPEND(&store->crtlist_entry, &entry->by_ckch_store);
William Lallemandc756bbd2020-05-13 17:23:59 +02001289
1290 appctx->st2 = SETCERT_ST_INIT;
1291 appctx->ctx.cli.p0 = crtlist;
1292 appctx->ctx.cli.p1 = entry;
1293
1294 /* unlock is done in the release handler */
1295 return 0;
1296
1297error:
1298 crtlist_entry_free(entry);
1299 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1300 err = memprintf(&err, "Can't edit the crt-list: %s\n", err ? err : "");
1301 return cli_dynerr(appctx, err);
1302}
1303
1304/* Parse a "del ssl crt-list <crt-list> <certfile>" line. */
1305static int cli_parse_del_crtlist(char **args, char *payload, struct appctx *appctx, void *private)
1306{
1307 struct ckch_store *store;
1308 char *err = NULL;
1309 char *crtlist_path, *cert_path;
1310 struct ebmb_node *ebmb;
1311 struct ebpt_node *ebpt;
1312 struct crtlist *crtlist;
1313 struct crtlist_entry *entry = NULL;
1314 struct ckch_inst *inst, *inst_s;
1315 int linenum = 0;
1316 char *colons;
William Lallemand99cc2182020-06-25 15:19:51 +02001317 char *end;
Remi Tricot-Le Bretonbc2c3862021-03-26 10:47:50 +01001318 int error_message_dumped = 0;
William Lallemandc756bbd2020-05-13 17:23:59 +02001319
1320 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
1321 return 1;
1322
1323 if (!*args[3] || !*args[4])
1324 return cli_err(appctx, "'del ssl crtlist' expects a filename and a certificate name\n");
1325
1326 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
1327 return cli_err(appctx, "Can't delete!\nOperations on certificates are currently locked!\n");
1328
1329 crtlist_path = args[3];
1330 cert_path = args[4];
1331
1332 colons = strchr(cert_path, ':');
1333 if (colons) {
1334 char *endptr;
1335
1336 linenum = strtol(colons + 1, &endptr, 10);
1337 if (colons + 1 == endptr || *endptr != '\0') {
1338 memprintf(&err, "wrong line number after colons in '%s'!", cert_path);
1339 goto error;
1340 }
1341 *colons = '\0';
1342 }
William Lallemand99cc2182020-06-25 15:19:51 +02001343
1344 /* strip trailing slashes, including first one */
1345 for (end = crtlist_path + strlen(crtlist_path) - 1; end >= crtlist_path && *end == '/'; end--)
1346 *end = 0;
1347
William Lallemandc756bbd2020-05-13 17:23:59 +02001348 /* look for crtlist */
1349 ebmb = ebst_lookup(&crtlists_tree, crtlist_path);
1350 if (!ebmb) {
1351 memprintf(&err, "crt-list '%s' does not exist!", crtlist_path);
1352 goto error;
1353 }
1354 crtlist = ebmb_entry(ebmb, struct crtlist, node);
1355
1356 /* look for store */
1357 store = ckchs_lookup(cert_path);
1358 if (store == NULL) {
1359 memprintf(&err, "certificate '%s' does not exist!", cert_path);
1360 goto error;
1361 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001362 if (store->ckch == NULL || store->ckch->cert == NULL) {
1363 memprintf(&err, "certificate '%s' is empty!", cert_path);
1364 goto error;
1365 }
1366
1367 ebpt = ebpt_lookup(&crtlist->entries, store);
1368 if (!ebpt) {
1369 memprintf(&err, "certificate '%s' can't be found in crt-list '%s'!", cert_path, crtlist_path);
1370 goto error;
1371 }
1372
1373 /* list the line number of entries for errors in err, and select the right ebpt */
1374 for (; ebpt; ebpt = ebpt_next_dup(ebpt)) {
1375 struct crtlist_entry *tmp;
1376
1377 tmp = ebpt_entry(ebpt, struct crtlist_entry, node);
1378 memprintf(&err, "%s%s%d", err ? err : "", err ? ", " : "", tmp->linenum);
1379
1380 /* select the entry we wanted */
1381 if (linenum == 0 || tmp->linenum == linenum) {
1382 if (!entry)
1383 entry = tmp;
1384 }
1385 }
1386
1387 /* we didn't found the specified entry */
1388 if (!entry) {
1389 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);
1390 goto error;
1391 }
1392
1393 /* we didn't specified a line number but there were several entries */
1394 if (linenum == 0 && ebpt_next_dup(&entry->node)) {
1395 memprintf(&err, "found the certificate '%s' in several entries, please specify a line number preceded by colons (%s)!", cert_path, err ? err : NULL);
1396 goto error;
1397 }
1398
Remi Tricot-Le Bretonbc2c3862021-03-26 10:47:50 +01001399 /* Iterate over all the instances in order to see if any of them is a
1400 * default instance. If this is the case, the entry won't be suppressed. */
1401 list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_crtlist_entry) {
1402 if (inst->is_default && !inst->bind_conf->strict_sni) {
1403 if (!error_message_dumped) {
1404 memprintf(&err, "certificate '%s' cannot be deleted, it is used as default certificate by the following frontends:\n", cert_path);
1405 error_message_dumped = 1;
1406 }
1407 memprintf(&err, "%s\t- %s:%d\n", err, inst->bind_conf->file, inst->bind_conf->line);
1408 }
1409 }
1410 if (error_message_dumped)
1411 goto error;
1412
William Lallemandc756bbd2020-05-13 17:23:59 +02001413 /* upon error free the ckch_inst and everything inside */
1414
1415 ebpt_delete(&entry->node);
Willy Tarreau2b718102021-04-21 07:32:39 +02001416 LIST_DELETE(&entry->by_crtlist);
1417 LIST_DELETE(&entry->by_ckch_store);
William Lallemandc756bbd2020-05-13 17:23:59 +02001418
1419 list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_crtlist_entry) {
1420 struct sni_ctx *sni, *sni_s;
1421
1422 HA_RWLOCK_WRLOCK(SNI_LOCK, &inst->bind_conf->sni_lock);
1423 list_for_each_entry_safe(sni, sni_s, &inst->sni_ctx, by_ckch_inst) {
1424 ebmb_delete(&sni->name);
Willy Tarreau2b718102021-04-21 07:32:39 +02001425 LIST_DELETE(&sni->by_ckch_inst);
William Lallemandc756bbd2020-05-13 17:23:59 +02001426 SSL_CTX_free(sni->ctx);
1427 free(sni);
1428 }
1429 HA_RWLOCK_WRUNLOCK(SNI_LOCK, &inst->bind_conf->sni_lock);
Willy Tarreau2b718102021-04-21 07:32:39 +02001430 LIST_DELETE(&inst->by_ckchs);
William Lallemandc756bbd2020-05-13 17:23:59 +02001431 free(inst);
1432 }
1433
1434 crtlist_free_filters(entry->filters);
1435 ssl_sock_free_ssl_conf(entry->ssl_conf);
1436 free(entry->ssl_conf);
1437 free(entry);
1438
1439 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1440 err = memprintf(&err, "Entry '%s' deleted in crtlist '%s'!\n", cert_path, crtlist_path);
1441 return cli_dynmsg(appctx, LOG_NOTICE, err);
1442
1443error:
1444 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1445 err = memprintf(&err, "Can't delete the entry: %s\n", err ? err : "");
1446 return cli_dynerr(appctx, err);
1447}
1448
1449
William Lallemandee8530c2020-06-23 18:19:42 +02001450/* unlink and free all crt-list and crt-list entries */
1451void crtlist_deinit()
1452{
1453 struct eb_node *node, *next;
1454 struct crtlist *crtlist;
1455
1456 node = eb_first(&crtlists_tree);
1457 while (node) {
1458 next = eb_next(node);
1459 crtlist = ebmb_entry(node, struct crtlist, node);
1460 crtlist_free(crtlist);
1461 node = next;
1462 }
1463}
1464
William Lallemandc756bbd2020-05-13 17:23:59 +02001465
1466/* register cli keywords */
1467static struct cli_kw_list cli_kws = {{ },{
Willy Tarreaub205bfd2021-05-07 11:38:37 +02001468 { { "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 },
1469 { { "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 },
1470 { { "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 +02001471 { { NULL }, NULL, NULL, NULL } }
1472};
1473
1474INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws);
1475