blob: 95758bb51b8886e101a78317045ff35012fc00e0 [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:
1117 /* insert SNIs in bind_conf */
1118 list_for_each_entry(new_inst, &store->ckch_inst, by_ckchs) {
1119 HA_RWLOCK_WRLOCK(SNI_LOCK, &new_inst->bind_conf->sni_lock);
1120 ssl_sock_load_cert_sni(new_inst, new_inst->bind_conf);
1121 HA_RWLOCK_WRUNLOCK(SNI_LOCK, &new_inst->bind_conf->sni_lock);
1122 }
1123 entry->linenum = ++crtlist->linecount;
Christopher Faulet59b4ef52022-06-01 16:31:09 +02001124 appctx->ctx.cli.p1 = NULL;
1125 appctx->st2 = SETCERT_ST_SUCCESS;
1126 /* fallthrough */
1127 case SETCERT_ST_SUCCESS:
1128 chunk_reset(&trash);
1129 chunk_appendf(&trash, "\n");
1130 if (appctx->ctx.cli.err)
1131 chunk_appendf(&trash, "%s", appctx->ctx.cli.err);
1132 chunk_appendf(&trash, "Success!\n");
1133 if (ci_putchk(si_ic(si), &trash) == -1)
1134 goto yield;
William Lallemandc756bbd2020-05-13 17:23:59 +02001135 appctx->st2 = SETCERT_ST_FIN;
1136 goto end;
Christopher Faulet59b4ef52022-06-01 16:31:09 +02001137
1138 case SETCERT_ST_ERROR:
1139 error:
1140 chunk_printf(&trash, "\n%sFailed!\n", appctx->ctx.cli.err);
1141 if (ci_putchk(si_ic(si), &trash) == -1)
1142 goto yield;
1143 goto end;
1144
1145 default:
1146 goto end;
William Lallemandc756bbd2020-05-13 17:23:59 +02001147 }
1148 }
1149
1150end:
William Lallemandc756bbd2020-05-13 17:23:59 +02001151 /* success: call the release function and don't come back */
1152 return 1;
1153yield:
Christopher Faulet59b4ef52022-06-01 16:31:09 +02001154 si_rx_room_blk(si);
William Lallemandc756bbd2020-05-13 17:23:59 +02001155 return 0; /* should come back */
William Lallemandc756bbd2020-05-13 17:23:59 +02001156}
1157
1158
1159/*
1160 * Parse a "add ssl crt-list <crt-list> <certfile>" line.
1161 * Filters and option must be passed through payload:
1162 */
1163static int cli_parse_add_crtlist(char **args, char *payload, struct appctx *appctx, void *private)
1164{
1165 int cfgerr = 0;
1166 struct ckch_store *store;
1167 char *err = NULL;
1168 char path[MAXPATHLEN+1];
1169 char *crtlist_path;
1170 char *cert_path = NULL;
1171 struct ebmb_node *eb;
1172 struct ebpt_node *inserted;
1173 struct crtlist *crtlist;
1174 struct crtlist_entry *entry = NULL;
William Lallemand99cc2182020-06-25 15:19:51 +02001175 char *end;
William Lallemandc756bbd2020-05-13 17:23:59 +02001176
1177 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
1178 return 1;
1179
1180 if (!*args[3] || (!payload && !*args[4]))
1181 return cli_err(appctx, "'add ssl crtlist' expects a filename and a certificate name\n");
1182
1183 crtlist_path = args[3];
1184
William Lallemand99cc2182020-06-25 15:19:51 +02001185 /* strip trailing slashes, including first one */
1186 for (end = crtlist_path + strlen(crtlist_path) - 1; end >= crtlist_path && *end == '/'; end--)
1187 *end = 0;
1188
William Lallemandc756bbd2020-05-13 17:23:59 +02001189 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
1190 return cli_err(appctx, "Operations on certificates are currently locked!\n");
1191
1192 eb = ebst_lookup(&crtlists_tree, crtlist_path);
1193 if (!eb) {
1194 memprintf(&err, "crt-list '%s' does not exist!", crtlist_path);
1195 goto error;
1196 }
1197 crtlist = ebmb_entry(eb, struct crtlist, node);
1198
1199 entry = crtlist_entry_new();
1200 if (entry == NULL) {
1201 memprintf(&err, "Not enough memory!");
1202 goto error;
1203 }
1204
1205 if (payload) {
1206 char *lf;
1207
1208 lf = strrchr(payload, '\n');
1209 if (lf) {
1210 memprintf(&err, "only one line of payload is supported!");
1211 goto error;
1212 }
1213 /* cert_path is filled here */
Remi Tricot-Le Bretonfb00f312021-03-23 16:41:53 +01001214 cfgerr |= crtlist_parse_line(payload, &cert_path, entry, "CLI", 1, 1, &err);
William Lallemandc756bbd2020-05-13 17:23:59 +02001215 if (cfgerr & ERR_CODE)
1216 goto error;
1217 } else {
1218 cert_path = args[4];
1219 }
1220
1221 if (!cert_path) {
1222 memprintf(&err, "'add ssl crtlist' should contain the certificate name in the payload");
1223 cfgerr |= ERR_ALERT | ERR_FATAL;
1224 goto error;
1225 }
1226
1227 if (eb_gettag(crtlist->entries.b[EB_RGHT])) {
1228 char *slash;
1229
1230 slash = strrchr(cert_path, '/');
1231 if (!slash) {
1232 memprintf(&err, "'%s' is a directory, certificate path '%s' must contain the directory path", (char *)crtlist->node.key, cert_path);
1233 goto error;
1234 }
1235 /* temporary replace / by 0 to do an strcmp */
1236 *slash = '\0';
1237 if (strcmp(cert_path, (char*)crtlist->node.key) != 0) {
1238 *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 *slash = '/';
1243 }
1244
1245 if (*cert_path != '/' && global_ssl.crt_base) {
Willy Tarreau326a8662022-05-09 10:31:28 +02001246 if ((strlen(global_ssl.crt_base) + 1 + strlen(cert_path)) > sizeof(path) ||
1247 snprintf(path, sizeof(path), "%s/%s", global_ssl.crt_base, cert_path) > sizeof(path)) {
William Lallemandc756bbd2020-05-13 17:23:59 +02001248 memprintf(&err, "'%s' : path too long", cert_path);
1249 cfgerr |= ERR_ALERT | ERR_FATAL;
1250 goto error;
1251 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001252 cert_path = path;
1253 }
1254
1255 store = ckchs_lookup(cert_path);
1256 if (store == NULL) {
1257 memprintf(&err, "certificate '%s' does not exist!", cert_path);
1258 goto error;
1259 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001260 if (store->ckch == NULL || store->ckch->cert == NULL) {
1261 memprintf(&err, "certificate '%s' is empty!", cert_path);
1262 goto error;
1263 }
1264
1265 /* check if it's possible to insert this new crtlist_entry */
1266 entry->node.key = store;
1267 inserted = ebpt_insert(&crtlist->entries, &entry->node);
1268 if (inserted != &entry->node) {
1269 memprintf(&err, "file already exists in this directory!");
1270 goto error;
1271 }
1272
1273 /* this is supposed to be a directory (EB_ROOT_UNIQUE), so no ssl_conf are allowed */
1274 if ((entry->ssl_conf || entry->filters) && eb_gettag(crtlist->entries.b[EB_RGHT])) {
1275 memprintf(&err, "this is a directory, SSL configuration and filters are not allowed");
1276 goto error;
1277 }
1278
Willy Tarreau2b718102021-04-21 07:32:39 +02001279 LIST_APPEND(&crtlist->ord_entries, &entry->by_crtlist);
William Lallemandc756bbd2020-05-13 17:23:59 +02001280 entry->crtlist = crtlist;
Willy Tarreau2b718102021-04-21 07:32:39 +02001281 LIST_APPEND(&store->crtlist_entry, &entry->by_ckch_store);
William Lallemandc756bbd2020-05-13 17:23:59 +02001282
1283 appctx->st2 = SETCERT_ST_INIT;
1284 appctx->ctx.cli.p0 = crtlist;
1285 appctx->ctx.cli.p1 = entry;
1286
1287 /* unlock is done in the release handler */
1288 return 0;
1289
1290error:
1291 crtlist_entry_free(entry);
1292 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1293 err = memprintf(&err, "Can't edit the crt-list: %s\n", err ? err : "");
1294 return cli_dynerr(appctx, err);
1295}
1296
1297/* Parse a "del ssl crt-list <crt-list> <certfile>" line. */
1298static int cli_parse_del_crtlist(char **args, char *payload, struct appctx *appctx, void *private)
1299{
1300 struct ckch_store *store;
1301 char *err = NULL;
1302 char *crtlist_path, *cert_path;
1303 struct ebmb_node *ebmb;
1304 struct ebpt_node *ebpt;
1305 struct crtlist *crtlist;
1306 struct crtlist_entry *entry = NULL;
1307 struct ckch_inst *inst, *inst_s;
1308 int linenum = 0;
1309 char *colons;
William Lallemand99cc2182020-06-25 15:19:51 +02001310 char *end;
Remi Tricot-Le Bretonbc2c3862021-03-26 10:47:50 +01001311 int error_message_dumped = 0;
William Lallemandc756bbd2020-05-13 17:23:59 +02001312
1313 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
1314 return 1;
1315
1316 if (!*args[3] || !*args[4])
1317 return cli_err(appctx, "'del ssl crtlist' expects a filename and a certificate name\n");
1318
1319 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
1320 return cli_err(appctx, "Can't delete!\nOperations on certificates are currently locked!\n");
1321
1322 crtlist_path = args[3];
1323 cert_path = args[4];
1324
1325 colons = strchr(cert_path, ':');
1326 if (colons) {
1327 char *endptr;
1328
1329 linenum = strtol(colons + 1, &endptr, 10);
1330 if (colons + 1 == endptr || *endptr != '\0') {
1331 memprintf(&err, "wrong line number after colons in '%s'!", cert_path);
1332 goto error;
1333 }
1334 *colons = '\0';
1335 }
William Lallemand99cc2182020-06-25 15:19:51 +02001336
1337 /* strip trailing slashes, including first one */
1338 for (end = crtlist_path + strlen(crtlist_path) - 1; end >= crtlist_path && *end == '/'; end--)
1339 *end = 0;
1340
William Lallemandc756bbd2020-05-13 17:23:59 +02001341 /* look for crtlist */
1342 ebmb = ebst_lookup(&crtlists_tree, crtlist_path);
1343 if (!ebmb) {
1344 memprintf(&err, "crt-list '%s' does not exist!", crtlist_path);
1345 goto error;
1346 }
1347 crtlist = ebmb_entry(ebmb, struct crtlist, node);
1348
1349 /* look for store */
1350 store = ckchs_lookup(cert_path);
1351 if (store == NULL) {
1352 memprintf(&err, "certificate '%s' does not exist!", cert_path);
1353 goto error;
1354 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001355 if (store->ckch == NULL || store->ckch->cert == NULL) {
1356 memprintf(&err, "certificate '%s' is empty!", cert_path);
1357 goto error;
1358 }
1359
1360 ebpt = ebpt_lookup(&crtlist->entries, store);
1361 if (!ebpt) {
1362 memprintf(&err, "certificate '%s' can't be found in crt-list '%s'!", cert_path, crtlist_path);
1363 goto error;
1364 }
1365
1366 /* list the line number of entries for errors in err, and select the right ebpt */
1367 for (; ebpt; ebpt = ebpt_next_dup(ebpt)) {
1368 struct crtlist_entry *tmp;
1369
1370 tmp = ebpt_entry(ebpt, struct crtlist_entry, node);
1371 memprintf(&err, "%s%s%d", err ? err : "", err ? ", " : "", tmp->linenum);
1372
1373 /* select the entry we wanted */
1374 if (linenum == 0 || tmp->linenum == linenum) {
1375 if (!entry)
1376 entry = tmp;
1377 }
1378 }
1379
1380 /* we didn't found the specified entry */
1381 if (!entry) {
1382 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);
1383 goto error;
1384 }
1385
1386 /* we didn't specified a line number but there were several entries */
1387 if (linenum == 0 && ebpt_next_dup(&entry->node)) {
1388 memprintf(&err, "found the certificate '%s' in several entries, please specify a line number preceded by colons (%s)!", cert_path, err ? err : NULL);
1389 goto error;
1390 }
1391
Remi Tricot-Le Bretonbc2c3862021-03-26 10:47:50 +01001392 /* Iterate over all the instances in order to see if any of them is a
1393 * default instance. If this is the case, the entry won't be suppressed. */
1394 list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_crtlist_entry) {
1395 if (inst->is_default && !inst->bind_conf->strict_sni) {
1396 if (!error_message_dumped) {
1397 memprintf(&err, "certificate '%s' cannot be deleted, it is used as default certificate by the following frontends:\n", cert_path);
1398 error_message_dumped = 1;
1399 }
1400 memprintf(&err, "%s\t- %s:%d\n", err, inst->bind_conf->file, inst->bind_conf->line);
1401 }
1402 }
1403 if (error_message_dumped)
1404 goto error;
1405
William Lallemandc756bbd2020-05-13 17:23:59 +02001406 /* upon error free the ckch_inst and everything inside */
1407
1408 ebpt_delete(&entry->node);
Willy Tarreau2b718102021-04-21 07:32:39 +02001409 LIST_DELETE(&entry->by_crtlist);
1410 LIST_DELETE(&entry->by_ckch_store);
William Lallemandc756bbd2020-05-13 17:23:59 +02001411
1412 list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_crtlist_entry) {
1413 struct sni_ctx *sni, *sni_s;
1414
1415 HA_RWLOCK_WRLOCK(SNI_LOCK, &inst->bind_conf->sni_lock);
1416 list_for_each_entry_safe(sni, sni_s, &inst->sni_ctx, by_ckch_inst) {
1417 ebmb_delete(&sni->name);
Willy Tarreau2b718102021-04-21 07:32:39 +02001418 LIST_DELETE(&sni->by_ckch_inst);
William Lallemandc756bbd2020-05-13 17:23:59 +02001419 SSL_CTX_free(sni->ctx);
1420 free(sni);
1421 }
1422 HA_RWLOCK_WRUNLOCK(SNI_LOCK, &inst->bind_conf->sni_lock);
Willy Tarreau2b718102021-04-21 07:32:39 +02001423 LIST_DELETE(&inst->by_ckchs);
William Lallemandc756bbd2020-05-13 17:23:59 +02001424 free(inst);
1425 }
1426
1427 crtlist_free_filters(entry->filters);
1428 ssl_sock_free_ssl_conf(entry->ssl_conf);
1429 free(entry->ssl_conf);
1430 free(entry);
1431
1432 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1433 err = memprintf(&err, "Entry '%s' deleted in crtlist '%s'!\n", cert_path, crtlist_path);
1434 return cli_dynmsg(appctx, LOG_NOTICE, err);
1435
1436error:
1437 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1438 err = memprintf(&err, "Can't delete the entry: %s\n", err ? err : "");
1439 return cli_dynerr(appctx, err);
1440}
1441
1442
William Lallemandee8530c2020-06-23 18:19:42 +02001443/* unlink and free all crt-list and crt-list entries */
1444void crtlist_deinit()
1445{
1446 struct eb_node *node, *next;
1447 struct crtlist *crtlist;
1448
1449 node = eb_first(&crtlists_tree);
1450 while (node) {
1451 next = eb_next(node);
1452 crtlist = ebmb_entry(node, struct crtlist, node);
1453 crtlist_free(crtlist);
1454 node = next;
1455 }
1456}
1457
William Lallemandc756bbd2020-05-13 17:23:59 +02001458
1459/* register cli keywords */
1460static struct cli_kw_list cli_kws = {{ },{
Willy Tarreaub205bfd2021-05-07 11:38:37 +02001461 { { "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 },
1462 { { "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 },
1463 { { "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 +02001464 { { NULL }, NULL, NULL, NULL } }
1465};
1466
1467INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws);
1468