blob: 1a3867e0c6487a62a3bb8e8b6e01850042f0980e [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
1064 while (1) {
1065 switch (appctx->st2) {
1066 case SETCERT_ST_INIT:
1067 /* This state just print the update message */
1068 chunk_printf(trash, "Inserting certificate '%s' in crt-list '%s'", store->path, crtlist->node.key);
Christopher Faulet908628c2022-03-25 16:43:49 +01001069 if (ci_putchk(cs_ic(cs), trash) == -1) {
Christopher Fauleta0bdec32022-04-04 07:51:21 +02001070 cs_rx_room_blk(cs);
William Lallemandc756bbd2020-05-13 17:23:59 +02001071 goto yield;
1072 }
1073 appctx->st2 = SETCERT_ST_GEN;
1074 /* fallthrough */
1075 case SETCERT_ST_GEN:
1076 bind_conf_node = appctx->ctx.cli.p2; /* get the previous ptr from the yield */
1077 if (bind_conf_node == NULL)
1078 bind_conf_node = crtlist->bind_conf;
1079 for (; bind_conf_node; bind_conf_node = bind_conf_node->next) {
1080 struct bind_conf *bind_conf = bind_conf_node->bind_conf;
1081 struct sni_ctx *sni;
1082
1083 /* yield every 10 generations */
1084 if (i > 10) {
1085 appctx->ctx.cli.p2 = bind_conf_node;
1086 goto yield;
1087 }
1088
1089 /* we don't support multi-cert bundles, only simple ones */
1090 errcode |= ckch_inst_new_load_store(store->path, store, bind_conf, entry->ssl_conf, entry->filters, entry->fcount, &new_inst, &err);
1091 if (errcode & ERR_CODE)
1092 goto error;
1093
1094 /* we need to initialize the SSL_CTX generated */
1095 /* this iterate on the newly generated SNIs in the new instance to prepare their SSL_CTX */
1096 list_for_each_entry(sni, &new_inst->sni_ctx, by_ckch_inst) {
1097 if (!sni->order) { /* we initialized only the first SSL_CTX because it's the same in the other sni_ctx's */
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001098 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 +02001099 if (errcode & ERR_CODE)
1100 goto error;
1101 }
1102 }
1103 /* display one dot for each new instance */
1104 chunk_appendf(trash, ".");
1105 i++;
Willy Tarreau2b718102021-04-21 07:32:39 +02001106 LIST_APPEND(&store->ckch_inst, &new_inst->by_ckchs);
1107 LIST_APPEND(&entry->ckch_inst, &new_inst->by_crtlist_entry);
William Lallemand9ab8f8d2020-06-24 01:00:52 +02001108 new_inst->crtlist_entry = entry;
William Lallemandc756bbd2020-05-13 17:23:59 +02001109 }
1110 appctx->st2 = SETCERT_ST_INSERT;
1111 /* fallthrough */
1112 case SETCERT_ST_INSERT:
1113 /* insert SNIs in bind_conf */
1114 list_for_each_entry(new_inst, &store->ckch_inst, by_ckchs) {
1115 HA_RWLOCK_WRLOCK(SNI_LOCK, &new_inst->bind_conf->sni_lock);
1116 ssl_sock_load_cert_sni(new_inst, new_inst->bind_conf);
1117 HA_RWLOCK_WRUNLOCK(SNI_LOCK, &new_inst->bind_conf->sni_lock);
1118 }
1119 entry->linenum = ++crtlist->linecount;
1120 appctx->st2 = SETCERT_ST_FIN;
1121 goto end;
1122 }
1123 }
1124
1125end:
1126 chunk_appendf(trash, "\n");
1127 if (errcode & ERR_WARN)
1128 chunk_appendf(trash, "%s", err);
1129 chunk_appendf(trash, "Success!\n");
Christopher Faulet908628c2022-03-25 16:43:49 +01001130 if (ci_putchk(cs_ic(cs), trash) == -1)
Christopher Fauleta0bdec32022-04-04 07:51:21 +02001131 cs_rx_room_blk(cs);
William Lallemandc756bbd2020-05-13 17:23:59 +02001132 free_trash_chunk(trash);
1133 /* success: call the release function and don't come back */
1134 return 1;
1135yield:
1136 /* store the state */
Christopher Faulet908628c2022-03-25 16:43:49 +01001137 if (ci_putchk(cs_ic(cs), trash) == -1)
Christopher Fauleta0bdec32022-04-04 07:51:21 +02001138 cs_rx_room_blk(cs);
William Lallemandc756bbd2020-05-13 17:23:59 +02001139 free_trash_chunk(trash);
Christopher Fauleta0bdec32022-04-04 07:51:21 +02001140 cs_rx_endp_more(cs); /* let's come back later */
William Lallemandc756bbd2020-05-13 17:23:59 +02001141 return 0; /* should come back */
1142
1143error:
1144 /* spin unlock and free are done in the release function */
1145 if (trash) {
1146 chunk_appendf(trash, "\n%sFailed!\n", err);
Christopher Faulet908628c2022-03-25 16:43:49 +01001147 if (ci_putchk(cs_ic(cs), trash) == -1)
Christopher Fauleta0bdec32022-04-04 07:51:21 +02001148 cs_rx_room_blk(cs);
William Lallemandc756bbd2020-05-13 17:23:59 +02001149 free_trash_chunk(trash);
1150 }
1151 /* error: call the release function and don't come back */
1152 return 1;
1153}
1154
1155
1156/*
1157 * Parse a "add ssl crt-list <crt-list> <certfile>" line.
1158 * Filters and option must be passed through payload:
1159 */
1160static int cli_parse_add_crtlist(char **args, char *payload, struct appctx *appctx, void *private)
1161{
1162 int cfgerr = 0;
1163 struct ckch_store *store;
1164 char *err = NULL;
1165 char path[MAXPATHLEN+1];
1166 char *crtlist_path;
1167 char *cert_path = NULL;
1168 struct ebmb_node *eb;
1169 struct ebpt_node *inserted;
1170 struct crtlist *crtlist;
1171 struct crtlist_entry *entry = NULL;
William Lallemand99cc2182020-06-25 15:19:51 +02001172 char *end;
William Lallemandc756bbd2020-05-13 17:23:59 +02001173
1174 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
1175 return 1;
1176
1177 if (!*args[3] || (!payload && !*args[4]))
1178 return cli_err(appctx, "'add ssl crtlist' expects a filename and a certificate name\n");
1179
1180 crtlist_path = args[3];
1181
William Lallemand99cc2182020-06-25 15:19:51 +02001182 /* strip trailing slashes, including first one */
1183 for (end = crtlist_path + strlen(crtlist_path) - 1; end >= crtlist_path && *end == '/'; end--)
1184 *end = 0;
1185
William Lallemandc756bbd2020-05-13 17:23:59 +02001186 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
1187 return cli_err(appctx, "Operations on certificates are currently locked!\n");
1188
1189 eb = ebst_lookup(&crtlists_tree, crtlist_path);
1190 if (!eb) {
1191 memprintf(&err, "crt-list '%s' does not exist!", crtlist_path);
1192 goto error;
1193 }
1194 crtlist = ebmb_entry(eb, struct crtlist, node);
1195
1196 entry = crtlist_entry_new();
1197 if (entry == NULL) {
1198 memprintf(&err, "Not enough memory!");
1199 goto error;
1200 }
1201
1202 if (payload) {
1203 char *lf;
1204
1205 lf = strrchr(payload, '\n');
1206 if (lf) {
1207 memprintf(&err, "only one line of payload is supported!");
1208 goto error;
1209 }
1210 /* cert_path is filled here */
Remi Tricot-Le Bretonfb00f312021-03-23 16:41:53 +01001211 cfgerr |= crtlist_parse_line(payload, &cert_path, entry, "CLI", 1, 1, &err);
William Lallemandc756bbd2020-05-13 17:23:59 +02001212 if (cfgerr & ERR_CODE)
1213 goto error;
1214 } else {
1215 cert_path = args[4];
1216 }
1217
1218 if (!cert_path) {
1219 memprintf(&err, "'add ssl crtlist' should contain the certificate name in the payload");
1220 cfgerr |= ERR_ALERT | ERR_FATAL;
1221 goto error;
1222 }
1223
1224 if (eb_gettag(crtlist->entries.b[EB_RGHT])) {
1225 char *slash;
1226
1227 slash = strrchr(cert_path, '/');
1228 if (!slash) {
1229 memprintf(&err, "'%s' is a directory, certificate path '%s' must contain the directory path", (char *)crtlist->node.key, cert_path);
1230 goto error;
1231 }
1232 /* temporary replace / by 0 to do an strcmp */
1233 *slash = '\0';
1234 if (strcmp(cert_path, (char*)crtlist->node.key) != 0) {
1235 *slash = '/';
1236 memprintf(&err, "'%s' is a directory, certificate path '%s' must contain the directory path", (char *)crtlist->node.key, cert_path);
1237 goto error;
1238 }
1239 *slash = '/';
1240 }
1241
1242 if (*cert_path != '/' && global_ssl.crt_base) {
1243 if ((strlen(global_ssl.crt_base) + 1 + strlen(cert_path)) > MAXPATHLEN) {
1244 memprintf(&err, "'%s' : path too long", cert_path);
1245 cfgerr |= ERR_ALERT | ERR_FATAL;
1246 goto error;
1247 }
1248 snprintf(path, sizeof(path), "%s/%s", global_ssl.crt_base, cert_path);
1249 cert_path = path;
1250 }
1251
1252 store = ckchs_lookup(cert_path);
1253 if (store == NULL) {
1254 memprintf(&err, "certificate '%s' does not exist!", cert_path);
1255 goto error;
1256 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001257 if (store->ckch == NULL || store->ckch->cert == NULL) {
1258 memprintf(&err, "certificate '%s' is empty!", cert_path);
1259 goto error;
1260 }
1261
1262 /* check if it's possible to insert this new crtlist_entry */
1263 entry->node.key = store;
1264 inserted = ebpt_insert(&crtlist->entries, &entry->node);
1265 if (inserted != &entry->node) {
1266 memprintf(&err, "file already exists in this directory!");
1267 goto error;
1268 }
1269
1270 /* this is supposed to be a directory (EB_ROOT_UNIQUE), so no ssl_conf are allowed */
1271 if ((entry->ssl_conf || entry->filters) && eb_gettag(crtlist->entries.b[EB_RGHT])) {
1272 memprintf(&err, "this is a directory, SSL configuration and filters are not allowed");
1273 goto error;
1274 }
1275
Willy Tarreau2b718102021-04-21 07:32:39 +02001276 LIST_APPEND(&crtlist->ord_entries, &entry->by_crtlist);
William Lallemandc756bbd2020-05-13 17:23:59 +02001277 entry->crtlist = crtlist;
Willy Tarreau2b718102021-04-21 07:32:39 +02001278 LIST_APPEND(&store->crtlist_entry, &entry->by_ckch_store);
William Lallemandc756bbd2020-05-13 17:23:59 +02001279
1280 appctx->st2 = SETCERT_ST_INIT;
1281 appctx->ctx.cli.p0 = crtlist;
1282 appctx->ctx.cli.p1 = entry;
1283
1284 /* unlock is done in the release handler */
1285 return 0;
1286
1287error:
1288 crtlist_entry_free(entry);
1289 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1290 err = memprintf(&err, "Can't edit the crt-list: %s\n", err ? err : "");
1291 return cli_dynerr(appctx, err);
1292}
1293
1294/* Parse a "del ssl crt-list <crt-list> <certfile>" line. */
1295static int cli_parse_del_crtlist(char **args, char *payload, struct appctx *appctx, void *private)
1296{
1297 struct ckch_store *store;
1298 char *err = NULL;
1299 char *crtlist_path, *cert_path;
1300 struct ebmb_node *ebmb;
1301 struct ebpt_node *ebpt;
1302 struct crtlist *crtlist;
1303 struct crtlist_entry *entry = NULL;
1304 struct ckch_inst *inst, *inst_s;
1305 int linenum = 0;
1306 char *colons;
William Lallemand99cc2182020-06-25 15:19:51 +02001307 char *end;
Remi Tricot-Le Bretonbc2c3862021-03-26 10:47:50 +01001308 int error_message_dumped = 0;
William Lallemandc756bbd2020-05-13 17:23:59 +02001309
1310 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
1311 return 1;
1312
1313 if (!*args[3] || !*args[4])
1314 return cli_err(appctx, "'del ssl crtlist' expects a filename and a certificate name\n");
1315
1316 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
1317 return cli_err(appctx, "Can't delete!\nOperations on certificates are currently locked!\n");
1318
1319 crtlist_path = args[3];
1320 cert_path = args[4];
1321
1322 colons = strchr(cert_path, ':');
1323 if (colons) {
1324 char *endptr;
1325
1326 linenum = strtol(colons + 1, &endptr, 10);
1327 if (colons + 1 == endptr || *endptr != '\0') {
1328 memprintf(&err, "wrong line number after colons in '%s'!", cert_path);
1329 goto error;
1330 }
1331 *colons = '\0';
1332 }
William Lallemand99cc2182020-06-25 15:19:51 +02001333
1334 /* strip trailing slashes, including first one */
1335 for (end = crtlist_path + strlen(crtlist_path) - 1; end >= crtlist_path && *end == '/'; end--)
1336 *end = 0;
1337
William Lallemandc756bbd2020-05-13 17:23:59 +02001338 /* look for crtlist */
1339 ebmb = ebst_lookup(&crtlists_tree, crtlist_path);
1340 if (!ebmb) {
1341 memprintf(&err, "crt-list '%s' does not exist!", crtlist_path);
1342 goto error;
1343 }
1344 crtlist = ebmb_entry(ebmb, struct crtlist, node);
1345
1346 /* look for store */
1347 store = ckchs_lookup(cert_path);
1348 if (store == NULL) {
1349 memprintf(&err, "certificate '%s' does not exist!", cert_path);
1350 goto error;
1351 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001352 if (store->ckch == NULL || store->ckch->cert == NULL) {
1353 memprintf(&err, "certificate '%s' is empty!", cert_path);
1354 goto error;
1355 }
1356
1357 ebpt = ebpt_lookup(&crtlist->entries, store);
1358 if (!ebpt) {
1359 memprintf(&err, "certificate '%s' can't be found in crt-list '%s'!", cert_path, crtlist_path);
1360 goto error;
1361 }
1362
1363 /* list the line number of entries for errors in err, and select the right ebpt */
1364 for (; ebpt; ebpt = ebpt_next_dup(ebpt)) {
1365 struct crtlist_entry *tmp;
1366
1367 tmp = ebpt_entry(ebpt, struct crtlist_entry, node);
1368 memprintf(&err, "%s%s%d", err ? err : "", err ? ", " : "", tmp->linenum);
1369
1370 /* select the entry we wanted */
1371 if (linenum == 0 || tmp->linenum == linenum) {
1372 if (!entry)
1373 entry = tmp;
1374 }
1375 }
1376
1377 /* we didn't found the specified entry */
1378 if (!entry) {
1379 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);
1380 goto error;
1381 }
1382
1383 /* we didn't specified a line number but there were several entries */
1384 if (linenum == 0 && ebpt_next_dup(&entry->node)) {
1385 memprintf(&err, "found the certificate '%s' in several entries, please specify a line number preceded by colons (%s)!", cert_path, err ? err : NULL);
1386 goto error;
1387 }
1388
Remi Tricot-Le Bretonbc2c3862021-03-26 10:47:50 +01001389 /* Iterate over all the instances in order to see if any of them is a
1390 * default instance. If this is the case, the entry won't be suppressed. */
1391 list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_crtlist_entry) {
1392 if (inst->is_default && !inst->bind_conf->strict_sni) {
1393 if (!error_message_dumped) {
1394 memprintf(&err, "certificate '%s' cannot be deleted, it is used as default certificate by the following frontends:\n", cert_path);
1395 error_message_dumped = 1;
1396 }
1397 memprintf(&err, "%s\t- %s:%d\n", err, inst->bind_conf->file, inst->bind_conf->line);
1398 }
1399 }
1400 if (error_message_dumped)
1401 goto error;
1402
William Lallemandc756bbd2020-05-13 17:23:59 +02001403 /* upon error free the ckch_inst and everything inside */
1404
1405 ebpt_delete(&entry->node);
Willy Tarreau2b718102021-04-21 07:32:39 +02001406 LIST_DELETE(&entry->by_crtlist);
1407 LIST_DELETE(&entry->by_ckch_store);
William Lallemandc756bbd2020-05-13 17:23:59 +02001408
1409 list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_crtlist_entry) {
1410 struct sni_ctx *sni, *sni_s;
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001411 struct ckch_inst_link_ref *link_ref, *link_ref_s;
William Lallemandc756bbd2020-05-13 17:23:59 +02001412
1413 HA_RWLOCK_WRLOCK(SNI_LOCK, &inst->bind_conf->sni_lock);
1414 list_for_each_entry_safe(sni, sni_s, &inst->sni_ctx, by_ckch_inst) {
1415 ebmb_delete(&sni->name);
Willy Tarreau2b718102021-04-21 07:32:39 +02001416 LIST_DELETE(&sni->by_ckch_inst);
William Lallemandc756bbd2020-05-13 17:23:59 +02001417 SSL_CTX_free(sni->ctx);
1418 free(sni);
1419 }
1420 HA_RWLOCK_WRUNLOCK(SNI_LOCK, &inst->bind_conf->sni_lock);
Willy Tarreau2b718102021-04-21 07:32:39 +02001421 LIST_DELETE(&inst->by_ckchs);
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001422 list_for_each_entry_safe(link_ref, link_ref_s, &inst->cafile_link_refs, list) {
1423 LIST_DELETE(&link_ref->link->list);
1424 LIST_DELETE(&link_ref->list);
1425 free(link_ref);
1426 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001427 free(inst);
1428 }
1429
1430 crtlist_free_filters(entry->filters);
1431 ssl_sock_free_ssl_conf(entry->ssl_conf);
1432 free(entry->ssl_conf);
1433 free(entry);
1434
1435 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1436 err = memprintf(&err, "Entry '%s' deleted in crtlist '%s'!\n", cert_path, crtlist_path);
1437 return cli_dynmsg(appctx, LOG_NOTICE, err);
1438
1439error:
1440 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1441 err = memprintf(&err, "Can't delete the entry: %s\n", err ? err : "");
1442 return cli_dynerr(appctx, err);
1443}
1444
1445
William Lallemandee8530c2020-06-23 18:19:42 +02001446/* unlink and free all crt-list and crt-list entries */
1447void crtlist_deinit()
1448{
1449 struct eb_node *node, *next;
1450 struct crtlist *crtlist;
1451
1452 node = eb_first(&crtlists_tree);
1453 while (node) {
1454 next = eb_next(node);
1455 crtlist = ebmb_entry(node, struct crtlist, node);
1456 crtlist_free(crtlist);
1457 node = next;
1458 }
1459}
1460
William Lallemandc756bbd2020-05-13 17:23:59 +02001461
1462/* register cli keywords */
1463static struct cli_kw_list cli_kws = {{ },{
Willy Tarreaub205bfd2021-05-07 11:38:37 +02001464 { { "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 },
1465 { { "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 },
1466 { { "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 +02001467 { { NULL }, NULL, NULL, NULL } }
1468};
1469
1470INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws);
1471