blob: 94923734f1dcd2211dc0d4f04552438f5cc51d04 [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>
Christopher Faulet908628c2022-03-25 16:43:49 +010025#include <haproxy/conn_stream.h>
26#include <haproxy/cs_utils.h>
Willy Tarreau8d366972020-05-27 16:10:29 +020027#include <haproxy/errors.h>
Willy Tarreau47d7f902020-06-04 14:25:47 +020028#include <haproxy/ssl_ckch.h>
Willy Tarreau52d88722020-06-04 14:29:23 +020029#include <haproxy/ssl_crtlist.h>
Willy Tarreau209108d2020-06-04 20:30:20 +020030#include <haproxy/ssl_sock.h>
Willy Tarreau48fbcae2020-06-03 18:09:46 +020031#include <haproxy/tools.h>
William Lallemand6e9556b2020-05-12 17:52:44 +020032
William Lallemand6e9556b2020-05-12 17:52:44 +020033
William Lallemand6e9556b2020-05-12 17:52:44 +020034/* release ssl bind conf */
35void ssl_sock_free_ssl_conf(struct ssl_bind_conf *conf)
36{
37 if (conf) {
38#if defined(OPENSSL_NPN_NEGOTIATED) && !defined(OPENSSL_NO_NEXTPROTONEG)
Willy Tarreau61cfdf42021-02-20 10:46:51 +010039 ha_free(&conf->npn_str);
William Lallemand6e9556b2020-05-12 17:52:44 +020040#endif
41#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
Willy Tarreau61cfdf42021-02-20 10:46:51 +010042 ha_free(&conf->alpn_str);
William Lallemand6e9556b2020-05-12 17:52:44 +020043#endif
Willy Tarreau61cfdf42021-02-20 10:46:51 +010044 ha_free(&conf->ca_file);
45 ha_free(&conf->ca_verify_file);
46 ha_free(&conf->crl_file);
47 ha_free(&conf->ciphers);
Ilya Shipitsinf34ed0b2020-11-21 14:37:34 +050048#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES
Willy Tarreau61cfdf42021-02-20 10:46:51 +010049 ha_free(&conf->ciphersuites);
William Lallemand6e9556b2020-05-12 17:52:44 +020050#endif
Willy Tarreau61cfdf42021-02-20 10:46:51 +010051 ha_free(&conf->curves);
52 ha_free(&conf->ecdhe);
William Lallemand6e9556b2020-05-12 17:52:44 +020053 }
54}
55
William Lallemand82f2d2f2020-09-10 19:06:43 +020056/*
57 * Allocate and copy a ssl_bind_conf structure
58 */
59struct ssl_bind_conf *crtlist_dup_ssl_conf(struct ssl_bind_conf *src)
60{
61 struct ssl_bind_conf *dst;
62
63 if (!src)
64 return NULL;
65
66 dst = calloc(1, sizeof(*dst));
67 if (!dst)
68 return NULL;
69
70#if defined(OPENSSL_NPN_NEGOTIATED) && !defined(OPENSSL_NO_NEXTPROTONEG)
71 if (src->npn_str) {
72 dst->npn_str = strdup(src->npn_str);
73 if (!dst->npn_str)
74 goto error;
75 }
76#endif
77#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
78 if (src->alpn_str) {
79 dst->alpn_str = strdup(src->alpn_str);
80 if (!dst->alpn_str)
81 goto error;
82 }
83#endif
84 if (src->ca_file) {
85 dst->ca_file = strdup(src->ca_file);
86 if (!dst->ca_file)
87 goto error;
88 }
89 if (src->ca_verify_file) {
90 dst->ca_verify_file = strdup(src->ca_verify_file);
91 if (!dst->ca_verify_file)
92 goto error;
93 }
94 if (src->crl_file) {
95 dst->crl_file = strdup(src->crl_file);
96 if (!dst->crl_file)
97 goto error;
98 }
99 if (src->ciphers) {
100 dst->ciphers = strdup(src->ciphers);
101 if (!dst->ciphers)
102 goto error;
103 }
Ilya Shipitsinf34ed0b2020-11-21 14:37:34 +0500104#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES
William Lallemand82f2d2f2020-09-10 19:06:43 +0200105 if (src->ciphersuites) {
106 dst->ciphersuites = strdup(src->ciphersuites);
107 if (!dst->ciphersuites)
108 goto error;
109 }
110#endif
111 if (src->curves) {
112 dst->curves = strdup(src->curves);
113 if (!dst->curves)
114 goto error;
115 }
116 if (src->ecdhe) {
117 dst->ecdhe = strdup(src->ecdhe);
118 if (!dst->ecdhe)
119 goto error;
120 }
121 return dst;
122
123error:
124 ssl_sock_free_ssl_conf(dst);
125 free(dst);
126
127 return NULL;
128}
William Lallemand6e9556b2020-05-12 17:52:44 +0200129
130/* free sni filters */
131void crtlist_free_filters(char **args)
132{
133 int i;
134
135 if (!args)
136 return;
137
138 for (i = 0; args[i]; i++)
139 free(args[i]);
140
141 free(args);
142}
143
144/* Alloc and duplicate a char ** array */
145char **crtlist_dup_filters(char **args, int fcount)
146{
147 char **dst;
148 int i;
149
150 if (fcount == 0)
151 return NULL;
152
153 dst = calloc(fcount + 1, sizeof(*dst));
154 if (!dst)
155 return NULL;
156
157 for (i = 0; i < fcount; i++) {
158 dst[i] = strdup(args[i]);
159 if (!dst[i])
160 goto error;
161 }
162 return dst;
163
164error:
165 crtlist_free_filters(dst);
166 return NULL;
167}
168
169/*
170 * Detach and free a crtlist_entry.
171 * Free the filters, the ssl_conf and call ckch_inst_free() for each ckch_inst
172 */
173void crtlist_entry_free(struct crtlist_entry *entry)
174{
175 struct ckch_inst *inst, *inst_s;
176
177 if (entry == NULL)
178 return;
179
180 ebpt_delete(&entry->node);
Willy Tarreau2b718102021-04-21 07:32:39 +0200181 LIST_DELETE(&entry->by_crtlist);
182 LIST_DELETE(&entry->by_ckch_store);
William Lallemand6e9556b2020-05-12 17:52:44 +0200183 crtlist_free_filters(entry->filters);
184 ssl_sock_free_ssl_conf(entry->ssl_conf);
185 free(entry->ssl_conf);
186 list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_crtlist_entry) {
187 ckch_inst_free(inst);
188 }
189 free(entry);
190}
William Lallemand5622c452020-09-10 19:08:49 +0200191/*
192 * Duplicate a crt_list entry and its content (ssl_conf, filters/fcount)
193 * Return a pointer to the new entry
194 */
195struct crtlist_entry *crtlist_entry_dup(struct crtlist_entry *src)
196{
197 struct crtlist_entry *entry;
198
199 if (src == NULL)
200 return NULL;
201
202 entry = crtlist_entry_new();
203 if (entry == NULL)
204 return NULL;
205
206 if (src->filters) {
207 entry->filters = crtlist_dup_filters(src->filters, src->fcount);
208 if (!entry->filters)
209 goto error;
210 }
211 entry->fcount = src->fcount;
212 if (src->ssl_conf) {
213 entry->ssl_conf = crtlist_dup_ssl_conf(src->ssl_conf);
214 if (!entry->ssl_conf)
215 goto error;
216 }
217 entry->crtlist = src->crtlist;
218
219 return entry;
220
221error:
222
223 crtlist_free_filters(entry->filters);
224 ssl_sock_free_ssl_conf(entry->ssl_conf);
225 free(entry->ssl_conf);
226 free(entry);
227
228 return NULL;
229}
William Lallemand6e9556b2020-05-12 17:52:44 +0200230
231/*
232 * Allocate and initialize a crtlist_entry
233 */
234struct crtlist_entry *crtlist_entry_new()
235{
236 struct crtlist_entry *entry;
237
238 entry = calloc(1, sizeof(*entry));
239 if (entry == NULL)
240 return NULL;
241
242 LIST_INIT(&entry->ckch_inst);
243
Willy Tarreau2b718102021-04-21 07:32:39 +0200244 /* initialize the nodes so we can LIST_DELETE in any cases */
William Lallemand6e9556b2020-05-12 17:52:44 +0200245 LIST_INIT(&entry->by_crtlist);
246 LIST_INIT(&entry->by_ckch_store);
247
248 return entry;
249}
250
251/* Free a crtlist, from the crt_entry to the content of the ssl_conf */
252void crtlist_free(struct crtlist *crtlist)
253{
254 struct crtlist_entry *entry, *s_entry;
William Lallemand6a3168a2020-06-23 11:43:35 +0200255 struct bind_conf_list *bind_conf_node;
William Lallemand6e9556b2020-05-12 17:52:44 +0200256
257 if (crtlist == NULL)
258 return;
259
William Lallemand6a3168a2020-06-23 11:43:35 +0200260 bind_conf_node = crtlist->bind_conf;
261 while (bind_conf_node) {
262 struct bind_conf_list *next = bind_conf_node->next;
263 free(bind_conf_node);
264 bind_conf_node = next;
265 }
266
William Lallemand6e9556b2020-05-12 17:52:44 +0200267 list_for_each_entry_safe(entry, s_entry, &crtlist->ord_entries, by_crtlist) {
268 crtlist_entry_free(entry);
269 }
270 ebmb_delete(&crtlist->node);
271 free(crtlist);
272}
273
274/* Alloc and initialize a struct crtlist
275 * <filename> is the key of the ebmb_node
276 * <unique> initialize the list of entries to be unique (1) or not (0)
277 */
278struct crtlist *crtlist_new(const char *filename, int unique)
279{
280 struct crtlist *newlist;
281
282 newlist = calloc(1, sizeof(*newlist) + strlen(filename) + 1);
283 if (newlist == NULL)
284 return NULL;
285
286 memcpy(newlist->node.key, filename, strlen(filename) + 1);
287 if (unique)
288 newlist->entries = EB_ROOT_UNIQUE;
289 else
290 newlist->entries = EB_ROOT;
291
292 LIST_INIT(&newlist->ord_entries);
293
294 return newlist;
295}
296
297/*
298 * Read a single crt-list line. /!\ alter the <line> string.
299 * Fill <crt_path> and <crtlist_entry>
300 * <crtlist_entry> must be alloc and free by the caller
301 * <crtlist_entry->ssl_conf> is alloc by the function
302 * <crtlist_entry->filters> is alloc by the function
303 * <crt_path> is a ptr in <line>
304 * Return an error code
305 */
Remi Tricot-Le Bretonfb00f312021-03-23 16:41:53 +0100306int 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 +0200307{
308 int cfgerr = 0;
309 int arg, newarg, cur_arg, i, ssl_b = 0, ssl_e = 0;
310 char *end;
311 char *args[MAX_CRT_ARGS + 1];
312 struct ssl_bind_conf *ssl_conf = NULL;
313
314 if (!line || !crt_path || !entry)
315 return ERR_ALERT | ERR_FATAL;
316
317 end = line + strlen(line);
318 if (end-line >= CRT_LINESIZE-1 && *(end-1) != '\n') {
319 /* Check if we reached the limit and the last char is not \n.
320 * Watch out for the last line without the terminating '\n'!
321 */
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200322 memprintf(err, "parsing [%s:%d]: line too long, limit is %d characters",
323 file, linenum, CRT_LINESIZE-1);
William Lallemand6e9556b2020-05-12 17:52:44 +0200324 cfgerr |= ERR_ALERT | ERR_FATAL;
325 goto error;
326 }
327 arg = 0;
328 newarg = 1;
329 while (*line) {
330 if (isspace((unsigned char)*line)) {
331 newarg = 1;
332 *line = 0;
333 } else if (*line == '[') {
334 if (ssl_b) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200335 memprintf(err, "parsing [%s:%d]: too many '['", file, linenum);
William Lallemand6e9556b2020-05-12 17:52:44 +0200336 cfgerr |= ERR_ALERT | ERR_FATAL;
337 goto error;
338 }
339 if (!arg) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200340 memprintf(err, "parsing [%s:%d]: file must start with a cert", file, linenum);
William Lallemand6e9556b2020-05-12 17:52:44 +0200341 cfgerr |= ERR_ALERT | ERR_FATAL;
342 goto error;
343 }
344 ssl_b = arg;
345 newarg = 1;
346 *line = 0;
347 } else if (*line == ']') {
348 if (ssl_e) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200349 memprintf(err, "parsing [%s:%d]: too many ']'", file, linenum);
William Lallemand6e9556b2020-05-12 17:52:44 +0200350 cfgerr |= ERR_ALERT | ERR_FATAL;
351 goto error;
352 }
353 if (!ssl_b) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200354 memprintf(err, "parsing [%s:%d]: missing '['", file, linenum);
William Lallemand6e9556b2020-05-12 17:52:44 +0200355 cfgerr |= ERR_ALERT | ERR_FATAL;
356 goto error;
357 }
358 ssl_e = arg;
359 newarg = 1;
360 *line = 0;
361 } else if (newarg) {
362 if (arg == MAX_CRT_ARGS) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200363 memprintf(err, "parsing [%s:%d]: too many args ", file, linenum);
William Lallemand6e9556b2020-05-12 17:52:44 +0200364 cfgerr |= ERR_ALERT | ERR_FATAL;
365 goto error;
366 }
367 newarg = 0;
368 args[arg++] = line;
369 }
370 line++;
371 }
372 args[arg++] = line;
373
374 /* empty line */
375 if (!*args[0]) {
376 cfgerr |= ERR_NONE;
377 goto error;
378 }
379
380 *crt_path = args[0];
381
382 if (ssl_b) {
383 ssl_conf = calloc(1, sizeof *ssl_conf);
384 if (!ssl_conf) {
385 memprintf(err, "not enough memory!");
386 cfgerr |= ERR_ALERT | ERR_FATAL;
387 goto error;
388 }
389 }
Remi Tricot-Le Bretonfb00f312021-03-23 16:41:53 +0100390
William Lallemand6e9556b2020-05-12 17:52:44 +0200391 cur_arg = ssl_b ? ssl_b : 1;
392 while (cur_arg < ssl_e) {
393 newarg = 0;
394 for (i = 0; ssl_bind_kws[i].kw != NULL; i++) {
395 if (strcmp(ssl_bind_kws[i].kw, args[cur_arg]) == 0) {
396 newarg = 1;
Remi Tricot-Le Bretonfb00f312021-03-23 16:41:53 +0100397 cfgerr |= ssl_bind_kws[i].parse(args, cur_arg, NULL, ssl_conf, from_cli, err);
William Lallemand6e9556b2020-05-12 17:52:44 +0200398 if (cur_arg + 1 + ssl_bind_kws[i].skip > ssl_e) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200399 memprintf(err, "parsing [%s:%d]: ssl args out of '[]' for %s",
400 file, linenum, args[cur_arg]);
William Lallemand6e9556b2020-05-12 17:52:44 +0200401 cfgerr |= ERR_ALERT | ERR_FATAL;
402 goto error;
403 }
404 cur_arg += 1 + ssl_bind_kws[i].skip;
405 break;
406 }
407 }
408 if (!cfgerr && !newarg) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200409 memprintf(err, "parsing [%s:%d]: unknown ssl keyword %s",
410 file, linenum, args[cur_arg]);
William Lallemand6e9556b2020-05-12 17:52:44 +0200411 cfgerr |= ERR_ALERT | ERR_FATAL;
412 goto error;
413 }
414 }
415 entry->linenum = linenum;
416 entry->ssl_conf = ssl_conf;
417 entry->filters = crtlist_dup_filters(&args[cur_arg], arg - cur_arg - 1);
418 entry->fcount = arg - cur_arg - 1;
419
420 return cfgerr;
421
422error:
423 crtlist_free_filters(entry->filters);
424 entry->filters = NULL;
425 ssl_sock_free_ssl_conf(entry->ssl_conf);
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100426 ha_free(&entry->ssl_conf);
William Lallemand6e9556b2020-05-12 17:52:44 +0200427 return cfgerr;
428}
429
430
431
432/* This function parse a crt-list file and store it in a struct crtlist, each line is a crtlist_entry structure
433 * Fill the <crtlist> argument with a pointer to a new crtlist struct
434 *
435 * This function tries to open and store certificate files.
436 */
437int crtlist_parse_file(char *file, struct bind_conf *bind_conf, struct proxy *curproxy, struct crtlist **crtlist, char **err)
438{
439 struct crtlist *newlist;
440 struct crtlist_entry *entry = NULL;
441 char thisline[CRT_LINESIZE];
William Lallemand6e9556b2020-05-12 17:52:44 +0200442 FILE *f;
443 struct stat buf;
444 int linenum = 0;
445 int cfgerr = 0;
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200446 int missing_lf = -1;
William Lallemand6e9556b2020-05-12 17:52:44 +0200447
448 if ((f = fopen(file, "r")) == NULL) {
449 memprintf(err, "cannot open file '%s' : %s", file, strerror(errno));
450 return ERR_ALERT | ERR_FATAL;
451 }
452
453 newlist = crtlist_new(file, 0);
454 if (newlist == NULL) {
455 memprintf(err, "Not enough memory!");
456 cfgerr |= ERR_ALERT | ERR_FATAL;
457 goto error;
458 }
459
460 while (fgets(thisline, sizeof(thisline), f) != NULL) {
461 char *end;
462 char *line = thisline;
463 char *crt_path;
William Lallemand86c2dd62020-11-20 14:23:38 +0100464 char path[MAXPATHLEN+1];
William Lallemand6e9556b2020-05-12 17:52:44 +0200465 struct ckch_store *ckchs;
William Lallemand77e1c6f2020-11-20 18:26:09 +0100466 int found = 0;
William Lallemand6e9556b2020-05-12 17:52:44 +0200467
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200468 if (missing_lf != -1) {
469 memprintf(err, "parsing [%s:%d]: Stray NUL character at position %d.\n",
470 file, linenum, (missing_lf + 1));
471 cfgerr |= ERR_ALERT | ERR_FATAL;
472 missing_lf = -1;
473 break;
474 }
475
William Lallemand6e9556b2020-05-12 17:52:44 +0200476 linenum++;
477 end = line + strlen(line);
478 if (end-line == sizeof(thisline)-1 && *(end-1) != '\n') {
479 /* Check if we reached the limit and the last char is not \n.
480 * Watch out for the last line without the terminating '\n'!
481 */
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200482 memprintf(err, "parsing [%s:%d]: line too long, limit is %d characters",
483 file, linenum, (int)sizeof(thisline)-1);
William Lallemand6e9556b2020-05-12 17:52:44 +0200484 cfgerr |= ERR_ALERT | ERR_FATAL;
485 break;
486 }
487
488 if (*line == '#' || *line == '\n' || *line == '\r')
489 continue;
490
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200491 if (end > line && *(end-1) == '\n') {
492 /* kill trailing LF */
493 *(end - 1) = 0;
494 }
495 else {
496 /* mark this line as truncated */
497 missing_lf = end - line;
498 }
499
William Lallemand6e9556b2020-05-12 17:52:44 +0200500 entry = crtlist_entry_new();
501 if (entry == NULL) {
502 memprintf(err, "Not enough memory!");
503 cfgerr |= ERR_ALERT | ERR_FATAL;
504 goto error;
505 }
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200506
Remi Tricot-Le Bretonfb00f312021-03-23 16:41:53 +0100507 cfgerr |= crtlist_parse_line(thisline, &crt_path, entry, file, linenum, 0, err);
William Lallemand20b0fed2020-09-28 15:45:16 +0200508 if (cfgerr & ERR_CODE)
William Lallemand6e9556b2020-05-12 17:52:44 +0200509 goto error;
510
511 /* empty line */
512 if (!crt_path || !*crt_path) {
513 crtlist_entry_free(entry);
514 entry = NULL;
515 continue;
516 }
517
518 if (*crt_path != '/' && global_ssl.crt_base) {
519 if ((strlen(global_ssl.crt_base) + 1 + strlen(crt_path)) > MAXPATHLEN) {
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 }
525 snprintf(path, sizeof(path), "%s/%s", global_ssl.crt_base, crt_path);
526 crt_path = path;
527 }
528
529 /* Look for a ckch_store or create one */
530 ckchs = ckchs_lookup(crt_path);
531 if (ckchs == NULL) {
William Lallemand47da8212020-09-10 19:13:27 +0200532 if (stat(crt_path, &buf) == 0) {
William Lallemand77e1c6f2020-11-20 18:26:09 +0100533 found++;
William Lallemand47da8212020-09-10 19:13:27 +0200534
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200535 ckchs = ckchs_load_cert_file(crt_path, err);
William Lallemand47da8212020-09-10 19:13:27 +0200536 if (ckchs == NULL) {
537 cfgerr |= ERR_ALERT | ERR_FATAL;
538 goto error;
539 }
540
541 entry->node.key = ckchs;
542 entry->crtlist = newlist;
543 ebpt_insert(&newlist->entries, &entry->node);
Willy Tarreau2b718102021-04-21 07:32:39 +0200544 LIST_APPEND(&newlist->ord_entries, &entry->by_crtlist);
545 LIST_APPEND(&ckchs->crtlist_entry, &entry->by_ckch_store);
William Lallemand6e9556b2020-05-12 17:52:44 +0200546
William Lallemand73404572020-11-20 18:23:40 +0100547 } else if (global_ssl.extra_files & SSL_GF_BUNDLE) {
William Lallemand47da8212020-09-10 19:13:27 +0200548 /* If we didn't find the file, this could be a
William Lallemand51f784b2020-10-02 18:08:18 +0200549 bundle, since 2.3 we don't support multiple
550 certificate in the same OpenSSL store, so we
551 emulate it by loading each file separately. To
552 do so we need to duplicate the entry in the
553 crt-list because it becomes independent */
William Lallemand47da8212020-09-10 19:13:27 +0200554 char fp[MAXPATHLEN+1] = {0};
555 int n = 0;
556 struct crtlist_entry *entry_dup = entry; /* use the previous created entry */
557 for (n = 0; n < SSL_SOCK_NUM_KEYTYPES; n++) {
558 struct stat buf;
559 int ret;
560
William Lallemand86c2dd62020-11-20 14:23:38 +0100561 ret = snprintf(fp, sizeof(fp), "%s.%s", crt_path, SSL_SOCK_KEYTYPE_NAMES[n]);
William Lallemand47da8212020-09-10 19:13:27 +0200562 if (ret > sizeof(fp))
563 continue;
564
565 ckchs = ckchs_lookup(fp);
William Lallemand77e1c6f2020-11-20 18:26:09 +0100566 if (!ckchs) {
567 if (stat(fp, &buf) == 0) {
568 ckchs = ckchs_load_cert_file(fp, err);
569 if (!ckchs) {
William Lallemand47da8212020-09-10 19:13:27 +0200570 cfgerr |= ERR_ALERT | ERR_FATAL;
571 goto error;
572 }
William Lallemand77e1c6f2020-11-20 18:26:09 +0100573 } else {
574 continue; /* didn't find this extension, skip */
575 }
576 }
577 found++;
578 linenum++; /* we duplicate the line for this entry in the bundle */
579 if (!entry_dup) { /* if the entry was used, duplicate one */
580 linenum++;
581 entry_dup = crtlist_entry_dup(entry);
582 if (!entry_dup) {
583 cfgerr |= ERR_ALERT | ERR_FATAL;
584 goto error;
William Lallemand47da8212020-09-10 19:13:27 +0200585 }
William Lallemand77e1c6f2020-11-20 18:26:09 +0100586 entry_dup->linenum = linenum;
587 }
William Lallemand47da8212020-09-10 19:13:27 +0200588
William Lallemand77e1c6f2020-11-20 18:26:09 +0100589 entry_dup->node.key = ckchs;
590 entry_dup->crtlist = newlist;
591 ebpt_insert(&newlist->entries, &entry_dup->node);
Willy Tarreau2b718102021-04-21 07:32:39 +0200592 LIST_APPEND(&newlist->ord_entries, &entry_dup->by_crtlist);
593 LIST_APPEND(&ckchs->crtlist_entry, &entry_dup->by_ckch_store);
William Lallemand47da8212020-09-10 19:13:27 +0200594
William Lallemand77e1c6f2020-11-20 18:26:09 +0100595 entry_dup = NULL; /* the entry was used, we need a new one next round */
William Lallemand47da8212020-09-10 19:13:27 +0200596 }
William Lallemandb7fdfdf2020-12-04 15:45:02 +0100597#if HA_OPENSSL_VERSION_NUMBER < 0x10101000L
598 if (found) {
599 memprintf(err, "%sCan't load '%s'. Loading a multi certificates bundle requires OpenSSL >= 1.1.1\n",
600 err && *err ? *err : "", crt_path);
601 cfgerr |= ERR_ALERT | ERR_FATAL;
602 }
603#endif
William Lallemand47da8212020-09-10 19:13:27 +0200604 }
William Lallemand77e1c6f2020-11-20 18:26:09 +0100605 if (!found) {
606 memprintf(err, "%sunable to stat SSL certificate from file '%s' : %s.\n",
607 err && *err ? *err : "", crt_path, strerror(errno));
608 cfgerr |= ERR_ALERT | ERR_FATAL;
609 }
610
William Lallemand50c03aa2020-11-06 16:24:07 +0100611 } else {
612 entry->node.key = ckchs;
613 entry->crtlist = newlist;
614 ebpt_insert(&newlist->entries, &entry->node);
Willy Tarreau2b718102021-04-21 07:32:39 +0200615 LIST_APPEND(&newlist->ord_entries, &entry->by_crtlist);
616 LIST_APPEND(&ckchs->crtlist_entry, &entry->by_ckch_store);
William Lallemand77e1c6f2020-11-20 18:26:09 +0100617 found++;
William Lallemand47da8212020-09-10 19:13:27 +0200618 }
William Lallemand6e9556b2020-05-12 17:52:44 +0200619 entry = NULL;
620 }
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200621
622 if (missing_lf != -1) {
623 memprintf(err, "parsing [%s:%d]: Missing LF on last line, file might have been truncated at position %d.\n",
624 file, linenum, (missing_lf + 1));
625 cfgerr |= ERR_ALERT | ERR_FATAL;
626 }
627
William Lallemand6e9556b2020-05-12 17:52:44 +0200628 if (cfgerr & ERR_CODE)
629 goto error;
630
631 newlist->linecount = linenum;
632
633 fclose(f);
634 *crtlist = newlist;
635
636 return cfgerr;
637error:
638 crtlist_entry_free(entry);
639
640 fclose(f);
641 crtlist_free(newlist);
642 return cfgerr;
643}
644
645/* This function reads a directory and stores it in a struct crtlist, each file is a crtlist_entry structure
646 * Fill the <crtlist> argument with a pointer to a new crtlist struct
647 *
648 * This function tries to open and store certificate files.
649 */
650int crtlist_load_cert_dir(char *path, struct bind_conf *bind_conf, struct crtlist **crtlist, char **err)
651{
652 struct crtlist *dir;
653 struct dirent **de_list;
654 int i, n;
655 struct stat buf;
656 char *end;
657 char fp[MAXPATHLEN+1];
658 int cfgerr = 0;
659 struct ckch_store *ckchs;
William Lallemand6e9556b2020-05-12 17:52:44 +0200660
661 dir = crtlist_new(path, 1);
662 if (dir == NULL) {
663 memprintf(err, "not enough memory");
664 return ERR_ALERT | ERR_FATAL;
665 }
666
667 n = scandir(path, &de_list, 0, alphasort);
668 if (n < 0) {
669 memprintf(err, "%sunable to scan directory '%s' : %s.\n",
670 err && *err ? *err : "", path, strerror(errno));
671 cfgerr |= ERR_ALERT | ERR_FATAL;
672 }
673 else {
674 for (i = 0; i < n; i++) {
675 struct crtlist_entry *entry;
676 struct dirent *de = de_list[i];
677
678 end = strrchr(de->d_name, '.');
Tim Duesterhuse5ff1412021-01-02 22:31:53 +0100679 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 +0200680 goto ignore_entry;
681
682 snprintf(fp, sizeof(fp), "%s/%s", path, de->d_name);
683 if (stat(fp, &buf) != 0) {
684 memprintf(err, "%sunable to stat SSL certificate from file '%s' : %s.\n",
685 err && *err ? *err : "", fp, strerror(errno));
686 cfgerr |= ERR_ALERT | ERR_FATAL;
687 goto ignore_entry;
688 }
689 if (!S_ISREG(buf.st_mode))
690 goto ignore_entry;
691
692 entry = crtlist_entry_new();
693 if (entry == NULL) {
694 memprintf(err, "not enough memory '%s'", fp);
695 cfgerr |= ERR_ALERT | ERR_FATAL;
696 goto ignore_entry;
697 }
698
William Lallemand6e9556b2020-05-12 17:52:44 +0200699 ckchs = ckchs_lookup(fp);
700 if (ckchs == NULL)
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200701 ckchs = ckchs_load_cert_file(fp, err);
William Lallemand6e9556b2020-05-12 17:52:44 +0200702 if (ckchs == NULL) {
703 free(de);
704 free(entry);
705 cfgerr |= ERR_ALERT | ERR_FATAL;
706 goto end;
707 }
708 entry->node.key = ckchs;
709 entry->crtlist = dir;
Willy Tarreau2b718102021-04-21 07:32:39 +0200710 LIST_APPEND(&ckchs->crtlist_entry, &entry->by_ckch_store);
711 LIST_APPEND(&dir->ord_entries, &entry->by_crtlist);
William Lallemand6e9556b2020-05-12 17:52:44 +0200712 ebpt_insert(&dir->entries, &entry->node);
713
714ignore_entry:
715 free(de);
716 }
717end:
718 free(de_list);
719 }
720
721 if (cfgerr & ERR_CODE) {
722 /* free the dir and entries on error */
723 crtlist_free(dir);
724 } else {
725 *crtlist = dir;
726 }
727 return cfgerr;
728
729}
730
William Lallemandc756bbd2020-05-13 17:23:59 +0200731/*
732 * Take an ssl_bind_conf structure and append the configuration line used to
733 * create it in the buffer
734 */
735static void dump_crtlist_sslconf(struct buffer *buf, const struct ssl_bind_conf *conf)
736{
737 int space = 0;
738
739 if (conf == NULL)
740 return;
741
742 chunk_appendf(buf, " [");
743#ifdef OPENSSL_NPN_NEGOTIATED
744 if (conf->npn_str) {
745 int len = conf->npn_len;
746 char *ptr = conf->npn_str;
747 int comma = 0;
748
749 if (space) chunk_appendf(buf, " ");
750 chunk_appendf(buf, "npn ");
751 while (len) {
752 unsigned short size;
753
754 size = *ptr;
755 ptr++;
756 if (comma)
757 chunk_memcat(buf, ",", 1);
758 chunk_memcat(buf, ptr, size);
759 ptr += size;
760 len -= size + 1;
761 comma = 1;
762 }
763 chunk_memcat(buf, "", 1); /* finish with a \0 */
764 space++;
765 }
766#endif
767#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
768 if (conf->alpn_str) {
769 int len = conf->alpn_len;
770 char *ptr = conf->alpn_str;
771 int comma = 0;
772
773 if (space) chunk_appendf(buf, " ");
774 chunk_appendf(buf, "alpn ");
775 while (len) {
776 unsigned short size;
777
778 size = *ptr;
779 ptr++;
780 if (comma)
781 chunk_memcat(buf, ",", 1);
782 chunk_memcat(buf, ptr, size);
783 ptr += size;
784 len -= size + 1;
785 comma = 1;
786 }
787 chunk_memcat(buf, "", 1); /* finish with a \0 */
788 space++;
789 }
790#endif
791 /* verify */
792 {
793 if (conf->verify == SSL_SOCK_VERIFY_NONE) {
794 if (space) chunk_appendf(buf, " ");
795 chunk_appendf(buf, "verify none");
796 space++;
797 } else if (conf->verify == SSL_SOCK_VERIFY_OPTIONAL) {
798 if (space) chunk_appendf(buf, " ");
799 chunk_appendf(buf, "verify optional");
800 space++;
801 } else if (conf->verify == SSL_SOCK_VERIFY_REQUIRED) {
802 if (space) chunk_appendf(buf, " ");
803 chunk_appendf(buf, "verify required");
804 space++;
805 }
806 }
807
808 if (conf->no_ca_names) {
809 if (space) chunk_appendf(buf, " ");
810 chunk_appendf(buf, "no-ca-names");
811 space++;
812 }
813
814 if (conf->early_data) {
815 if (space) chunk_appendf(buf, " ");
816 chunk_appendf(buf, "allow-0rtt");
817 space++;
818 }
819 if (conf->ca_file) {
820 if (space) chunk_appendf(buf, " ");
821 chunk_appendf(buf, "ca-file %s", conf->ca_file);
822 space++;
823 }
824 if (conf->crl_file) {
825 if (space) chunk_appendf(buf, " ");
826 chunk_appendf(buf, "crl-file %s", conf->crl_file);
827 space++;
828 }
829 if (conf->ciphers) {
830 if (space) chunk_appendf(buf, " ");
831 chunk_appendf(buf, "ciphers %s", conf->ciphers);
832 space++;
833 }
Ilya Shipitsinf34ed0b2020-11-21 14:37:34 +0500834#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES
William Lallemandc756bbd2020-05-13 17:23:59 +0200835 if (conf->ciphersuites) {
836 if (space) chunk_appendf(buf, " ");
837 chunk_appendf(buf, "ciphersuites %s", conf->ciphersuites);
838 space++;
839 }
840#endif
841 if (conf->curves) {
842 if (space) chunk_appendf(buf, " ");
843 chunk_appendf(buf, "curves %s", conf->curves);
844 space++;
845 }
846 if (conf->ecdhe) {
847 if (space) chunk_appendf(buf, " ");
848 chunk_appendf(buf, "ecdhe %s", conf->ecdhe);
849 space++;
850 }
851
852 /* the crt-lists only support ssl-min-ver and ssl-max-ver */
William Lallemand8177ad92020-05-20 16:49:02 +0200853 if (conf->ssl_methods_cfg.min) {
William Lallemandc756bbd2020-05-13 17:23:59 +0200854 if (space) chunk_appendf(buf, " ");
William Lallemand8177ad92020-05-20 16:49:02 +0200855 chunk_appendf(buf, "ssl-min-ver %s", methodVersions[conf->ssl_methods_cfg.min].name);
William Lallemandc756bbd2020-05-13 17:23:59 +0200856 space++;
857 }
858
William Lallemand8177ad92020-05-20 16:49:02 +0200859 if (conf->ssl_methods_cfg.max) {
William Lallemandc756bbd2020-05-13 17:23:59 +0200860 if (space) chunk_appendf(buf, " ");
William Lallemand8177ad92020-05-20 16:49:02 +0200861 chunk_appendf(buf, "ssl-max-ver %s", methodVersions[conf->ssl_methods_cfg.max].name);
William Lallemandc756bbd2020-05-13 17:23:59 +0200862 space++;
863 }
864
865 chunk_appendf(buf, "]");
866
867 return;
868}
869
870/* dump a list of filters */
871static void dump_crtlist_filters(struct buffer *buf, struct crtlist_entry *entry)
872{
873 int i;
874
875 if (!entry->fcount)
876 return;
877
878 for (i = 0; i < entry->fcount; i++) {
879 chunk_appendf(buf, " %s", entry->filters[i]);
880 }
881 return;
882}
883
884/************************** CLI functions ****************************/
885
886
887/* CLI IO handler for '(show|dump) ssl crt-list' */
888static int cli_io_handler_dump_crtlist(struct appctx *appctx)
889{
890 struct buffer *trash = alloc_trash_chunk();
Christopher Faulet908628c2022-03-25 16:43:49 +0100891 struct conn_stream *cs = appctx->owner;
William Lallemandc756bbd2020-05-13 17:23:59 +0200892 struct ebmb_node *lnode;
893
894 if (trash == NULL)
895 return 1;
896
897 /* dump the list of crt-lists */
898 lnode = appctx->ctx.cli.p1;
899 if (lnode == NULL)
900 lnode = ebmb_first(&crtlists_tree);
901 while (lnode) {
902 chunk_appendf(trash, "%s\n", lnode->key);
Christopher Faulet908628c2022-03-25 16:43:49 +0100903 if (ci_putchk(cs_ic(cs), trash) == -1) {
Christopher Fauleta0bdec32022-04-04 07:51:21 +0200904 cs_rx_room_blk(cs);
William Lallemandc756bbd2020-05-13 17:23:59 +0200905 goto yield;
906 }
907 lnode = ebmb_next(lnode);
908 }
909 free_trash_chunk(trash);
910 return 1;
911yield:
912 appctx->ctx.cli.p1 = lnode;
913 free_trash_chunk(trash);
914 return 0;
915}
916
917/* CLI IO handler for '(show|dump) ssl crt-list <filename>' */
918static int cli_io_handler_dump_crtlist_entries(struct appctx *appctx)
919{
920 struct buffer *trash = alloc_trash_chunk();
921 struct crtlist *crtlist;
Christopher Faulet908628c2022-03-25 16:43:49 +0100922 struct conn_stream *cs = appctx->owner;
William Lallemandc756bbd2020-05-13 17:23:59 +0200923 struct crtlist_entry *entry;
924
925 if (trash == NULL)
926 return 1;
927
928 crtlist = ebmb_entry(appctx->ctx.cli.p0, struct crtlist, node);
929
930 entry = appctx->ctx.cli.p1;
931 if (entry == NULL) {
932 entry = LIST_ELEM((crtlist->ord_entries).n, typeof(entry), by_crtlist);
933 chunk_appendf(trash, "# %s\n", crtlist->node.key);
Christopher Faulet908628c2022-03-25 16:43:49 +0100934 if (ci_putchk(cs_ic(cs), trash) == -1) {
Christopher Fauleta0bdec32022-04-04 07:51:21 +0200935 cs_rx_room_blk(cs);
William Lallemandc756bbd2020-05-13 17:23:59 +0200936 goto yield;
937 }
938 }
939
940 list_for_each_entry_from(entry, &crtlist->ord_entries, by_crtlist) {
941 struct ckch_store *store;
942 const char *filename;
943
944 store = entry->node.key;
945 filename = store->path;
946 chunk_appendf(trash, "%s", filename);
947 if (appctx->ctx.cli.i0 == 's') /* show */
948 chunk_appendf(trash, ":%d", entry->linenum);
949 dump_crtlist_sslconf(trash, entry->ssl_conf);
950 dump_crtlist_filters(trash, entry);
951 chunk_appendf(trash, "\n");
952
Christopher Faulet908628c2022-03-25 16:43:49 +0100953 if (ci_putchk(cs_ic(cs), trash) == -1) {
Christopher Fauleta0bdec32022-04-04 07:51:21 +0200954 cs_rx_room_blk(cs);
William Lallemandc756bbd2020-05-13 17:23:59 +0200955 goto yield;
956 }
957 }
958 free_trash_chunk(trash);
959 return 1;
960yield:
961 appctx->ctx.cli.p1 = entry;
962 free_trash_chunk(trash);
963 return 0;
964}
965
966/* CLI argument parser for '(show|dump) ssl crt-list' */
967static int cli_parse_dump_crtlist(char **args, char *payload, struct appctx *appctx, void *private)
968{
969 struct ebmb_node *lnode;
970 char *filename = NULL;
971 int mode;
William Lallemand99cc2182020-06-25 15:19:51 +0200972 char *end;
William Lallemandc756bbd2020-05-13 17:23:59 +0200973
974 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
975 return 1;
976
977 appctx->ctx.cli.p0 = NULL;
978 appctx->ctx.cli.p1 = NULL;
979
Tim Duesterhuse5ff1412021-01-02 22:31:53 +0100980 if (*args[3] && strcmp(args[3], "-n") == 0) {
William Lallemandc756bbd2020-05-13 17:23:59 +0200981 mode = 's';
982 filename = args[4];
983 } else {
984 mode = 'd';
985 filename = args[3];
986 }
987
988 if (mode == 's' && !*args[4])
989 return cli_err(appctx, "'show ssl crt-list -n' expects a filename or a directory\n");
990
991 if (filename && *filename) {
William Lallemand99cc2182020-06-25 15:19:51 +0200992
993
994 /* strip trailing slashes, including first one */
995 for (end = filename + strlen(filename) - 1; end >= filename && *end == '/'; end--)
996 *end = 0;
997
William Lallemandc756bbd2020-05-13 17:23:59 +0200998 lnode = ebst_lookup(&crtlists_tree, filename);
999 if (lnode == NULL)
1000 return cli_err(appctx, "didn't find the specified filename\n");
1001
1002 appctx->ctx.cli.p0 = lnode;
1003 appctx->io_handler = cli_io_handler_dump_crtlist_entries;
1004 }
1005 appctx->ctx.cli.i0 = mode;
1006
1007 return 0;
1008}
1009
1010/* release function of the "add ssl crt-list' command, free things and unlock
1011 the spinlock */
1012static void cli_release_add_crtlist(struct appctx *appctx)
1013{
1014 struct crtlist_entry *entry = appctx->ctx.cli.p1;
1015
1016 if (appctx->st2 != SETCERT_ST_FIN) {
1017 struct ckch_inst *inst, *inst_s;
1018 /* 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 }
1031
1032 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1033}
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;
Christopher Faulet908628c2022-03-25 16:43:49 +01001045 struct conn_stream *cs = appctx->owner;
William Lallemandc756bbd2020-05-13 17:23:59 +02001046 struct crtlist *crtlist = appctx->ctx.cli.p0;
1047 struct crtlist_entry *entry = appctx->ctx.cli.p1;
1048 struct ckch_store *store = entry->node.key;
1049 struct buffer *trash = alloc_trash_chunk();
1050 struct ckch_inst *new_inst;
1051 char *err = NULL;
1052 int i = 0;
1053 int errcode = 0;
1054
1055 if (trash == NULL)
1056 goto error;
1057
1058 /* for each bind_conf which use the crt-list, a new ckch_inst must be
1059 * created.
1060 */
Christopher Faulet908628c2022-03-25 16:43:49 +01001061 if (unlikely(cs_ic(cs)->flags & (CF_WRITE_ERROR|CF_SHUTW)))
William Lallemandc756bbd2020-05-13 17:23:59 +02001062 goto error;
1063
Willy Tarreau1b948ef2022-05-05 13:50:46 +02001064 switch (appctx->st2) {
1065 case SETCERT_ST_INIT:
1066 /* This state just print the update message */
1067 chunk_printf(trash, "Inserting certificate '%s' in crt-list '%s'", store->path, crtlist->node.key);
1068 if (ci_putchk(cs_ic(cs), trash) == -1) {
1069 cs_rx_room_blk(cs);
1070 goto yield;
1071 }
1072 appctx->st2 = SETCERT_ST_GEN;
1073 /* fallthrough */
1074 case SETCERT_ST_GEN:
1075 bind_conf_node = appctx->ctx.cli.p2; /* get the previous ptr from the yield */
1076 if (bind_conf_node == NULL)
1077 bind_conf_node = crtlist->bind_conf;
1078 for (; bind_conf_node; bind_conf_node = bind_conf_node->next) {
1079 struct bind_conf *bind_conf = bind_conf_node->bind_conf;
1080 struct sni_ctx *sni;
1081
1082 /* yield every 10 generations */
1083 if (i > 10) {
1084 appctx->ctx.cli.p2 = bind_conf_node;
1085 goto yield;
1086 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001087
Willy Tarreau1b948ef2022-05-05 13:50:46 +02001088 /* we don't support multi-cert bundles, only simple ones */
1089 errcode |= ckch_inst_new_load_store(store->path, store, bind_conf, entry->ssl_conf, entry->filters, entry->fcount, &new_inst, &err);
1090 if (errcode & ERR_CODE)
1091 goto error;
William Lallemandc756bbd2020-05-13 17:23:59 +02001092
Willy Tarreau1b948ef2022-05-05 13:50:46 +02001093 /* we need to initialize the SSL_CTX generated */
1094 /* this iterate on the newly generated SNIs in the new instance to prepare their SSL_CTX */
1095 list_for_each_entry(sni, &new_inst->sni_ctx, by_ckch_inst) {
1096 if (!sni->order) { /* we initialized only the first SSL_CTX because it's the same in the other sni_ctx's */
1097 errcode |= ssl_sock_prep_ctx_and_inst(bind_conf, new_inst->ssl_conf, sni->ctx, sni->ckch_inst, &err);
William Lallemandc756bbd2020-05-13 17:23:59 +02001098 if (errcode & ERR_CODE)
1099 goto error;
William Lallemandc756bbd2020-05-13 17:23:59 +02001100 }
Willy Tarreau1b948ef2022-05-05 13:50:46 +02001101 }
1102 /* display one dot for each new instance */
1103 chunk_appendf(trash, ".");
1104 i++;
1105 LIST_APPEND(&store->ckch_inst, &new_inst->by_ckchs);
1106 LIST_APPEND(&entry->ckch_inst, &new_inst->by_crtlist_entry);
1107 new_inst->crtlist_entry = entry;
William Lallemandc756bbd2020-05-13 17:23:59 +02001108 }
Willy Tarreau1b948ef2022-05-05 13:50:46 +02001109 appctx->st2 = SETCERT_ST_INSERT;
1110 /* fallthrough */
1111 case SETCERT_ST_INSERT:
1112 /* insert SNIs in bind_conf */
1113 list_for_each_entry(new_inst, &store->ckch_inst, by_ckchs) {
1114 HA_RWLOCK_WRLOCK(SNI_LOCK, &new_inst->bind_conf->sni_lock);
1115 ssl_sock_load_cert_sni(new_inst, new_inst->bind_conf);
1116 HA_RWLOCK_WRUNLOCK(SNI_LOCK, &new_inst->bind_conf->sni_lock);
1117 }
1118 entry->linenum = ++crtlist->linecount;
1119 appctx->st2 = SETCERT_ST_FIN;
William Lallemandc756bbd2020-05-13 17:23:59 +02001120 }
1121
William Lallemandc756bbd2020-05-13 17:23:59 +02001122 chunk_appendf(trash, "\n");
1123 if (errcode & ERR_WARN)
1124 chunk_appendf(trash, "%s", err);
1125 chunk_appendf(trash, "Success!\n");
Christopher Faulet908628c2022-03-25 16:43:49 +01001126 if (ci_putchk(cs_ic(cs), trash) == -1)
Christopher Fauleta0bdec32022-04-04 07:51:21 +02001127 cs_rx_room_blk(cs);
William Lallemandc756bbd2020-05-13 17:23:59 +02001128 free_trash_chunk(trash);
1129 /* success: call the release function and don't come back */
1130 return 1;
1131yield:
1132 /* store the state */
Christopher Faulet908628c2022-03-25 16:43:49 +01001133 if (ci_putchk(cs_ic(cs), trash) == -1)
Christopher Fauleta0bdec32022-04-04 07:51:21 +02001134 cs_rx_room_blk(cs);
William Lallemandc756bbd2020-05-13 17:23:59 +02001135 free_trash_chunk(trash);
Christopher Fauleta0bdec32022-04-04 07:51:21 +02001136 cs_rx_endp_more(cs); /* let's come back later */
William Lallemandc756bbd2020-05-13 17:23:59 +02001137 return 0; /* should come back */
1138
1139error:
1140 /* spin unlock and free are done in the release function */
1141 if (trash) {
1142 chunk_appendf(trash, "\n%sFailed!\n", err);
Christopher Faulet908628c2022-03-25 16:43:49 +01001143 if (ci_putchk(cs_ic(cs), trash) == -1)
Christopher Fauleta0bdec32022-04-04 07:51:21 +02001144 cs_rx_room_blk(cs);
William Lallemandc756bbd2020-05-13 17:23:59 +02001145 free_trash_chunk(trash);
1146 }
1147 /* error: call the release function and don't come back */
1148 return 1;
1149}
1150
1151
1152/*
1153 * Parse a "add ssl crt-list <crt-list> <certfile>" line.
1154 * Filters and option must be passed through payload:
1155 */
1156static int cli_parse_add_crtlist(char **args, char *payload, struct appctx *appctx, void *private)
1157{
1158 int cfgerr = 0;
1159 struct ckch_store *store;
1160 char *err = NULL;
1161 char path[MAXPATHLEN+1];
1162 char *crtlist_path;
1163 char *cert_path = NULL;
1164 struct ebmb_node *eb;
1165 struct ebpt_node *inserted;
1166 struct crtlist *crtlist;
1167 struct crtlist_entry *entry = NULL;
William Lallemand99cc2182020-06-25 15:19:51 +02001168 char *end;
William Lallemandc756bbd2020-05-13 17:23:59 +02001169
1170 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
1171 return 1;
1172
1173 if (!*args[3] || (!payload && !*args[4]))
1174 return cli_err(appctx, "'add ssl crtlist' expects a filename and a certificate name\n");
1175
1176 crtlist_path = args[3];
1177
William Lallemand99cc2182020-06-25 15:19:51 +02001178 /* strip trailing slashes, including first one */
1179 for (end = crtlist_path + strlen(crtlist_path) - 1; end >= crtlist_path && *end == '/'; end--)
1180 *end = 0;
1181
William Lallemandc756bbd2020-05-13 17:23:59 +02001182 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
1183 return cli_err(appctx, "Operations on certificates are currently locked!\n");
1184
1185 eb = ebst_lookup(&crtlists_tree, crtlist_path);
1186 if (!eb) {
1187 memprintf(&err, "crt-list '%s' does not exist!", crtlist_path);
1188 goto error;
1189 }
1190 crtlist = ebmb_entry(eb, struct crtlist, node);
1191
1192 entry = crtlist_entry_new();
1193 if (entry == NULL) {
1194 memprintf(&err, "Not enough memory!");
1195 goto error;
1196 }
1197
1198 if (payload) {
1199 char *lf;
1200
1201 lf = strrchr(payload, '\n');
1202 if (lf) {
1203 memprintf(&err, "only one line of payload is supported!");
1204 goto error;
1205 }
1206 /* cert_path is filled here */
Remi Tricot-Le Bretonfb00f312021-03-23 16:41:53 +01001207 cfgerr |= crtlist_parse_line(payload, &cert_path, entry, "CLI", 1, 1, &err);
William Lallemandc756bbd2020-05-13 17:23:59 +02001208 if (cfgerr & ERR_CODE)
1209 goto error;
1210 } else {
1211 cert_path = args[4];
1212 }
1213
1214 if (!cert_path) {
1215 memprintf(&err, "'add ssl crtlist' should contain the certificate name in the payload");
1216 cfgerr |= ERR_ALERT | ERR_FATAL;
1217 goto error;
1218 }
1219
1220 if (eb_gettag(crtlist->entries.b[EB_RGHT])) {
1221 char *slash;
1222
1223 slash = strrchr(cert_path, '/');
1224 if (!slash) {
1225 memprintf(&err, "'%s' is a directory, certificate path '%s' must contain the directory path", (char *)crtlist->node.key, cert_path);
1226 goto error;
1227 }
1228 /* temporary replace / by 0 to do an strcmp */
1229 *slash = '\0';
1230 if (strcmp(cert_path, (char*)crtlist->node.key) != 0) {
1231 *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 *slash = '/';
1236 }
1237
1238 if (*cert_path != '/' && global_ssl.crt_base) {
1239 if ((strlen(global_ssl.crt_base) + 1 + strlen(cert_path)) > MAXPATHLEN) {
1240 memprintf(&err, "'%s' : path too long", cert_path);
1241 cfgerr |= ERR_ALERT | ERR_FATAL;
1242 goto error;
1243 }
1244 snprintf(path, sizeof(path), "%s/%s", global_ssl.crt_base, cert_path);
1245 cert_path = path;
1246 }
1247
1248 store = ckchs_lookup(cert_path);
1249 if (store == NULL) {
1250 memprintf(&err, "certificate '%s' does not exist!", cert_path);
1251 goto error;
1252 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001253 if (store->ckch == NULL || store->ckch->cert == NULL) {
1254 memprintf(&err, "certificate '%s' is empty!", cert_path);
1255 goto error;
1256 }
1257
1258 /* check if it's possible to insert this new crtlist_entry */
1259 entry->node.key = store;
1260 inserted = ebpt_insert(&crtlist->entries, &entry->node);
1261 if (inserted != &entry->node) {
1262 memprintf(&err, "file already exists in this directory!");
1263 goto error;
1264 }
1265
1266 /* this is supposed to be a directory (EB_ROOT_UNIQUE), so no ssl_conf are allowed */
1267 if ((entry->ssl_conf || entry->filters) && eb_gettag(crtlist->entries.b[EB_RGHT])) {
1268 memprintf(&err, "this is a directory, SSL configuration and filters are not allowed");
1269 goto error;
1270 }
1271
Willy Tarreau2b718102021-04-21 07:32:39 +02001272 LIST_APPEND(&crtlist->ord_entries, &entry->by_crtlist);
William Lallemandc756bbd2020-05-13 17:23:59 +02001273 entry->crtlist = crtlist;
Willy Tarreau2b718102021-04-21 07:32:39 +02001274 LIST_APPEND(&store->crtlist_entry, &entry->by_ckch_store);
William Lallemandc756bbd2020-05-13 17:23:59 +02001275
1276 appctx->st2 = SETCERT_ST_INIT;
1277 appctx->ctx.cli.p0 = crtlist;
1278 appctx->ctx.cli.p1 = entry;
1279
1280 /* unlock is done in the release handler */
1281 return 0;
1282
1283error:
1284 crtlist_entry_free(entry);
1285 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1286 err = memprintf(&err, "Can't edit the crt-list: %s\n", err ? err : "");
1287 return cli_dynerr(appctx, err);
1288}
1289
1290/* Parse a "del ssl crt-list <crt-list> <certfile>" line. */
1291static int cli_parse_del_crtlist(char **args, char *payload, struct appctx *appctx, void *private)
1292{
1293 struct ckch_store *store;
1294 char *err = NULL;
1295 char *crtlist_path, *cert_path;
1296 struct ebmb_node *ebmb;
1297 struct ebpt_node *ebpt;
1298 struct crtlist *crtlist;
1299 struct crtlist_entry *entry = NULL;
1300 struct ckch_inst *inst, *inst_s;
1301 int linenum = 0;
1302 char *colons;
William Lallemand99cc2182020-06-25 15:19:51 +02001303 char *end;
Remi Tricot-Le Bretonbc2c3862021-03-26 10:47:50 +01001304 int error_message_dumped = 0;
William Lallemandc756bbd2020-05-13 17:23:59 +02001305
1306 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
1307 return 1;
1308
1309 if (!*args[3] || !*args[4])
1310 return cli_err(appctx, "'del ssl crtlist' expects a filename and a certificate name\n");
1311
1312 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
1313 return cli_err(appctx, "Can't delete!\nOperations on certificates are currently locked!\n");
1314
1315 crtlist_path = args[3];
1316 cert_path = args[4];
1317
1318 colons = strchr(cert_path, ':');
1319 if (colons) {
1320 char *endptr;
1321
1322 linenum = strtol(colons + 1, &endptr, 10);
1323 if (colons + 1 == endptr || *endptr != '\0') {
1324 memprintf(&err, "wrong line number after colons in '%s'!", cert_path);
1325 goto error;
1326 }
1327 *colons = '\0';
1328 }
William Lallemand99cc2182020-06-25 15:19:51 +02001329
1330 /* strip trailing slashes, including first one */
1331 for (end = crtlist_path + strlen(crtlist_path) - 1; end >= crtlist_path && *end == '/'; end--)
1332 *end = 0;
1333
William Lallemandc756bbd2020-05-13 17:23:59 +02001334 /* look for crtlist */
1335 ebmb = ebst_lookup(&crtlists_tree, crtlist_path);
1336 if (!ebmb) {
1337 memprintf(&err, "crt-list '%s' does not exist!", crtlist_path);
1338 goto error;
1339 }
1340 crtlist = ebmb_entry(ebmb, struct crtlist, node);
1341
1342 /* look for store */
1343 store = ckchs_lookup(cert_path);
1344 if (store == NULL) {
1345 memprintf(&err, "certificate '%s' does not exist!", cert_path);
1346 goto error;
1347 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001348 if (store->ckch == NULL || store->ckch->cert == NULL) {
1349 memprintf(&err, "certificate '%s' is empty!", cert_path);
1350 goto error;
1351 }
1352
1353 ebpt = ebpt_lookup(&crtlist->entries, store);
1354 if (!ebpt) {
1355 memprintf(&err, "certificate '%s' can't be found in crt-list '%s'!", cert_path, crtlist_path);
1356 goto error;
1357 }
1358
1359 /* list the line number of entries for errors in err, and select the right ebpt */
1360 for (; ebpt; ebpt = ebpt_next_dup(ebpt)) {
1361 struct crtlist_entry *tmp;
1362
1363 tmp = ebpt_entry(ebpt, struct crtlist_entry, node);
1364 memprintf(&err, "%s%s%d", err ? err : "", err ? ", " : "", tmp->linenum);
1365
1366 /* select the entry we wanted */
1367 if (linenum == 0 || tmp->linenum == linenum) {
1368 if (!entry)
1369 entry = tmp;
1370 }
1371 }
1372
1373 /* we didn't found the specified entry */
1374 if (!entry) {
1375 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);
1376 goto error;
1377 }
1378
1379 /* we didn't specified a line number but there were several entries */
1380 if (linenum == 0 && ebpt_next_dup(&entry->node)) {
1381 memprintf(&err, "found the certificate '%s' in several entries, please specify a line number preceded by colons (%s)!", cert_path, err ? err : NULL);
1382 goto error;
1383 }
1384
Remi Tricot-Le Bretonbc2c3862021-03-26 10:47:50 +01001385 /* Iterate over all the instances in order to see if any of them is a
1386 * default instance. If this is the case, the entry won't be suppressed. */
1387 list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_crtlist_entry) {
1388 if (inst->is_default && !inst->bind_conf->strict_sni) {
1389 if (!error_message_dumped) {
1390 memprintf(&err, "certificate '%s' cannot be deleted, it is used as default certificate by the following frontends:\n", cert_path);
1391 error_message_dumped = 1;
1392 }
1393 memprintf(&err, "%s\t- %s:%d\n", err, inst->bind_conf->file, inst->bind_conf->line);
1394 }
1395 }
1396 if (error_message_dumped)
1397 goto error;
1398
William Lallemandc756bbd2020-05-13 17:23:59 +02001399 /* upon error free the ckch_inst and everything inside */
1400
1401 ebpt_delete(&entry->node);
Willy Tarreau2b718102021-04-21 07:32:39 +02001402 LIST_DELETE(&entry->by_crtlist);
1403 LIST_DELETE(&entry->by_ckch_store);
William Lallemandc756bbd2020-05-13 17:23:59 +02001404
1405 list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_crtlist_entry) {
1406 struct sni_ctx *sni, *sni_s;
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001407 struct ckch_inst_link_ref *link_ref, *link_ref_s;
William Lallemandc756bbd2020-05-13 17:23:59 +02001408
1409 HA_RWLOCK_WRLOCK(SNI_LOCK, &inst->bind_conf->sni_lock);
1410 list_for_each_entry_safe(sni, sni_s, &inst->sni_ctx, by_ckch_inst) {
1411 ebmb_delete(&sni->name);
Willy Tarreau2b718102021-04-21 07:32:39 +02001412 LIST_DELETE(&sni->by_ckch_inst);
William Lallemandc756bbd2020-05-13 17:23:59 +02001413 SSL_CTX_free(sni->ctx);
1414 free(sni);
1415 }
1416 HA_RWLOCK_WRUNLOCK(SNI_LOCK, &inst->bind_conf->sni_lock);
Willy Tarreau2b718102021-04-21 07:32:39 +02001417 LIST_DELETE(&inst->by_ckchs);
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001418 list_for_each_entry_safe(link_ref, link_ref_s, &inst->cafile_link_refs, list) {
1419 LIST_DELETE(&link_ref->link->list);
1420 LIST_DELETE(&link_ref->list);
1421 free(link_ref);
1422 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001423 free(inst);
1424 }
1425
1426 crtlist_free_filters(entry->filters);
1427 ssl_sock_free_ssl_conf(entry->ssl_conf);
1428 free(entry->ssl_conf);
1429 free(entry);
1430
1431 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1432 err = memprintf(&err, "Entry '%s' deleted in crtlist '%s'!\n", cert_path, crtlist_path);
1433 return cli_dynmsg(appctx, LOG_NOTICE, err);
1434
1435error:
1436 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1437 err = memprintf(&err, "Can't delete the entry: %s\n", err ? err : "");
1438 return cli_dynerr(appctx, err);
1439}
1440
1441
William Lallemandee8530c2020-06-23 18:19:42 +02001442/* unlink and free all crt-list and crt-list entries */
1443void crtlist_deinit()
1444{
1445 struct eb_node *node, *next;
1446 struct crtlist *crtlist;
1447
1448 node = eb_first(&crtlists_tree);
1449 while (node) {
1450 next = eb_next(node);
1451 crtlist = ebmb_entry(node, struct crtlist, node);
1452 crtlist_free(crtlist);
1453 node = next;
1454 }
1455}
1456
William Lallemandc756bbd2020-05-13 17:23:59 +02001457
1458/* register cli keywords */
1459static struct cli_kw_list cli_kws = {{ },{
Willy Tarreaub205bfd2021-05-07 11:38:37 +02001460 { { "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 },
1461 { { "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 },
1462 { { "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 +02001463 { { NULL }, NULL, NULL, NULL } }
1464};
1465
1466INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws);
1467