blob: afb7aba5b7f5cac818d3f31efed2eb51bd4cc45f [file] [log] [blame]
William Lallemand6e9556b2020-05-12 17:52:44 +02001/*
2 *
3 * Copyright (C) 2020 HAProxy Technologies, William Lallemand <wlallemand@haproxy.com>
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version
8 * 2 of the License, or (at your option) any later version.
9 *
10 */
Willy Tarreaub2551052020-06-09 09:07:15 +020011#include <sys/stat.h>
12#include <sys/types.h>
William Lallemand6e9556b2020-05-12 17:52:44 +020013
Willy Tarreaub2551052020-06-09 09:07:15 +020014#include <dirent.h>
William Lallemand212e9932020-05-18 08:33:09 +020015#include <errno.h>
William Lallemand6e9556b2020-05-12 17:52:44 +020016#include <stdlib.h>
17#include <string.h>
Willy Tarreauaeed4a82020-06-04 22:01:04 +020018#include <syslog.h>
Willy Tarreaub2551052020-06-09 09:07:15 +020019
20#include <import/ebpttree.h>
21#include <import/ebsttree.h>
William Lallemand6e9556b2020-05-12 17:52:44 +020022
Willy Tarreauf1d32c42020-06-04 21:07:02 +020023#include <haproxy/channel.h>
Willy Tarreau83487a82020-06-04 20:19:54 +020024#include <haproxy/cli.h>
Willy Tarreau8d366972020-05-27 16:10:29 +020025#include <haproxy/errors.h>
Willy Tarreau47d7f902020-06-04 14:25:47 +020026#include <haproxy/ssl_ckch.h>
Willy Tarreau52d88722020-06-04 14:29:23 +020027#include <haproxy/ssl_crtlist.h>
Willy Tarreau209108d2020-06-04 20:30:20 +020028#include <haproxy/ssl_sock.h>
Willy Tarreau5e539c92020-06-04 20:45:39 +020029#include <haproxy/stream_interface.h>
Willy Tarreau48fbcae2020-06-03 18:09:46 +020030#include <haproxy/tools.h>
William Lallemand6e9556b2020-05-12 17:52:44 +020031
William Lallemand6e9556b2020-05-12 17:52:44 +020032
William Lallemand6e9556b2020-05-12 17:52:44 +020033/* release ssl bind conf */
34void ssl_sock_free_ssl_conf(struct ssl_bind_conf *conf)
35{
36 if (conf) {
37#if defined(OPENSSL_NPN_NEGOTIATED) && !defined(OPENSSL_NO_NEXTPROTONEG)
Willy Tarreau61cfdf42021-02-20 10:46:51 +010038 ha_free(&conf->npn_str);
William Lallemand6e9556b2020-05-12 17:52:44 +020039#endif
40#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
Willy Tarreau61cfdf42021-02-20 10:46:51 +010041 ha_free(&conf->alpn_str);
William Lallemand6e9556b2020-05-12 17:52:44 +020042#endif
Willy Tarreau61cfdf42021-02-20 10:46:51 +010043 ha_free(&conf->ca_file);
44 ha_free(&conf->ca_verify_file);
45 ha_free(&conf->crl_file);
46 ha_free(&conf->ciphers);
Ilya Shipitsinf34ed0b2020-11-21 14:37:34 +050047#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES
Willy Tarreau61cfdf42021-02-20 10:46:51 +010048 ha_free(&conf->ciphersuites);
William Lallemand6e9556b2020-05-12 17:52:44 +020049#endif
Willy Tarreau61cfdf42021-02-20 10:46:51 +010050 ha_free(&conf->curves);
51 ha_free(&conf->ecdhe);
William Lallemand6e9556b2020-05-12 17:52:44 +020052 }
53}
54
William Lallemand82f2d2f2020-09-10 19:06:43 +020055/*
56 * Allocate and copy a ssl_bind_conf structure
57 */
58struct ssl_bind_conf *crtlist_dup_ssl_conf(struct ssl_bind_conf *src)
59{
60 struct ssl_bind_conf *dst;
61
62 if (!src)
63 return NULL;
64
65 dst = calloc(1, sizeof(*dst));
66 if (!dst)
67 return NULL;
68
69#if defined(OPENSSL_NPN_NEGOTIATED) && !defined(OPENSSL_NO_NEXTPROTONEG)
70 if (src->npn_str) {
71 dst->npn_str = strdup(src->npn_str);
72 if (!dst->npn_str)
73 goto error;
74 }
75#endif
76#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
77 if (src->alpn_str) {
78 dst->alpn_str = strdup(src->alpn_str);
79 if (!dst->alpn_str)
80 goto error;
81 }
82#endif
83 if (src->ca_file) {
84 dst->ca_file = strdup(src->ca_file);
85 if (!dst->ca_file)
86 goto error;
87 }
88 if (src->ca_verify_file) {
89 dst->ca_verify_file = strdup(src->ca_verify_file);
90 if (!dst->ca_verify_file)
91 goto error;
92 }
93 if (src->crl_file) {
94 dst->crl_file = strdup(src->crl_file);
95 if (!dst->crl_file)
96 goto error;
97 }
98 if (src->ciphers) {
99 dst->ciphers = strdup(src->ciphers);
100 if (!dst->ciphers)
101 goto error;
102 }
Ilya Shipitsinf34ed0b2020-11-21 14:37:34 +0500103#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES
William Lallemand82f2d2f2020-09-10 19:06:43 +0200104 if (src->ciphersuites) {
105 dst->ciphersuites = strdup(src->ciphersuites);
106 if (!dst->ciphersuites)
107 goto error;
108 }
109#endif
110 if (src->curves) {
111 dst->curves = strdup(src->curves);
112 if (!dst->curves)
113 goto error;
114 }
115 if (src->ecdhe) {
116 dst->ecdhe = strdup(src->ecdhe);
117 if (!dst->ecdhe)
118 goto error;
119 }
120 return dst;
121
122error:
123 ssl_sock_free_ssl_conf(dst);
124 free(dst);
125
126 return NULL;
127}
William Lallemand6e9556b2020-05-12 17:52:44 +0200128
129/* free sni filters */
130void crtlist_free_filters(char **args)
131{
132 int i;
133
134 if (!args)
135 return;
136
137 for (i = 0; args[i]; i++)
138 free(args[i]);
139
140 free(args);
141}
142
143/* Alloc and duplicate a char ** array */
144char **crtlist_dup_filters(char **args, int fcount)
145{
146 char **dst;
147 int i;
148
149 if (fcount == 0)
150 return NULL;
151
152 dst = calloc(fcount + 1, sizeof(*dst));
153 if (!dst)
154 return NULL;
155
156 for (i = 0; i < fcount; i++) {
157 dst[i] = strdup(args[i]);
158 if (!dst[i])
159 goto error;
160 }
161 return dst;
162
163error:
164 crtlist_free_filters(dst);
165 return NULL;
166}
167
168/*
169 * Detach and free a crtlist_entry.
170 * Free the filters, the ssl_conf and call ckch_inst_free() for each ckch_inst
171 */
172void crtlist_entry_free(struct crtlist_entry *entry)
173{
174 struct ckch_inst *inst, *inst_s;
175
176 if (entry == NULL)
177 return;
178
179 ebpt_delete(&entry->node);
Willy Tarreau2b718102021-04-21 07:32:39 +0200180 LIST_DELETE(&entry->by_crtlist);
181 LIST_DELETE(&entry->by_ckch_store);
William Lallemand6e9556b2020-05-12 17:52:44 +0200182 crtlist_free_filters(entry->filters);
183 ssl_sock_free_ssl_conf(entry->ssl_conf);
184 free(entry->ssl_conf);
185 list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_crtlist_entry) {
186 ckch_inst_free(inst);
187 }
188 free(entry);
189}
William Lallemand5622c452020-09-10 19:08:49 +0200190/*
191 * Duplicate a crt_list entry and its content (ssl_conf, filters/fcount)
192 * Return a pointer to the new entry
193 */
194struct crtlist_entry *crtlist_entry_dup(struct crtlist_entry *src)
195{
196 struct crtlist_entry *entry;
197
198 if (src == NULL)
199 return NULL;
200
201 entry = crtlist_entry_new();
202 if (entry == NULL)
203 return NULL;
204
205 if (src->filters) {
206 entry->filters = crtlist_dup_filters(src->filters, src->fcount);
207 if (!entry->filters)
208 goto error;
209 }
210 entry->fcount = src->fcount;
211 if (src->ssl_conf) {
212 entry->ssl_conf = crtlist_dup_ssl_conf(src->ssl_conf);
213 if (!entry->ssl_conf)
214 goto error;
215 }
216 entry->crtlist = src->crtlist;
217
218 return entry;
219
220error:
221
222 crtlist_free_filters(entry->filters);
223 ssl_sock_free_ssl_conf(entry->ssl_conf);
224 free(entry->ssl_conf);
225 free(entry);
226
227 return NULL;
228}
William Lallemand6e9556b2020-05-12 17:52:44 +0200229
230/*
231 * Allocate and initialize a crtlist_entry
232 */
233struct crtlist_entry *crtlist_entry_new()
234{
235 struct crtlist_entry *entry;
236
237 entry = calloc(1, sizeof(*entry));
238 if (entry == NULL)
239 return NULL;
240
241 LIST_INIT(&entry->ckch_inst);
242
Willy Tarreau2b718102021-04-21 07:32:39 +0200243 /* initialize the nodes so we can LIST_DELETE in any cases */
William Lallemand6e9556b2020-05-12 17:52:44 +0200244 LIST_INIT(&entry->by_crtlist);
245 LIST_INIT(&entry->by_ckch_store);
246
247 return entry;
248}
249
250/* Free a crtlist, from the crt_entry to the content of the ssl_conf */
251void crtlist_free(struct crtlist *crtlist)
252{
253 struct crtlist_entry *entry, *s_entry;
William Lallemand6a3168a2020-06-23 11:43:35 +0200254 struct bind_conf_list *bind_conf_node;
William Lallemand6e9556b2020-05-12 17:52:44 +0200255
256 if (crtlist == NULL)
257 return;
258
William Lallemand6a3168a2020-06-23 11:43:35 +0200259 bind_conf_node = crtlist->bind_conf;
260 while (bind_conf_node) {
261 struct bind_conf_list *next = bind_conf_node->next;
262 free(bind_conf_node);
263 bind_conf_node = next;
264 }
265
William Lallemand6e9556b2020-05-12 17:52:44 +0200266 list_for_each_entry_safe(entry, s_entry, &crtlist->ord_entries, by_crtlist) {
267 crtlist_entry_free(entry);
268 }
269 ebmb_delete(&crtlist->node);
270 free(crtlist);
271}
272
273/* Alloc and initialize a struct crtlist
274 * <filename> is the key of the ebmb_node
275 * <unique> initialize the list of entries to be unique (1) or not (0)
276 */
277struct crtlist *crtlist_new(const char *filename, int unique)
278{
279 struct crtlist *newlist;
280
281 newlist = calloc(1, sizeof(*newlist) + strlen(filename) + 1);
282 if (newlist == NULL)
283 return NULL;
284
285 memcpy(newlist->node.key, filename, strlen(filename) + 1);
286 if (unique)
287 newlist->entries = EB_ROOT_UNIQUE;
288 else
289 newlist->entries = EB_ROOT;
290
291 LIST_INIT(&newlist->ord_entries);
292
293 return newlist;
294}
295
296/*
297 * Read a single crt-list line. /!\ alter the <line> string.
298 * Fill <crt_path> and <crtlist_entry>
299 * <crtlist_entry> must be alloc and free by the caller
300 * <crtlist_entry->ssl_conf> is alloc by the function
301 * <crtlist_entry->filters> is alloc by the function
302 * <crt_path> is a ptr in <line>
303 * Return an error code
304 */
Remi Tricot-Le Bretonfb00f312021-03-23 16:41:53 +0100305int crtlist_parse_line(char *line, char **crt_path, struct crtlist_entry *entry, const char *file, int linenum, int from_cli, char **err)
William Lallemand6e9556b2020-05-12 17:52:44 +0200306{
307 int cfgerr = 0;
308 int arg, newarg, cur_arg, i, ssl_b = 0, ssl_e = 0;
309 char *end;
310 char *args[MAX_CRT_ARGS + 1];
311 struct ssl_bind_conf *ssl_conf = NULL;
312
313 if (!line || !crt_path || !entry)
314 return ERR_ALERT | ERR_FATAL;
315
316 end = line + strlen(line);
317 if (end-line >= CRT_LINESIZE-1 && *(end-1) != '\n') {
318 /* Check if we reached the limit and the last char is not \n.
319 * Watch out for the last line without the terminating '\n'!
320 */
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200321 memprintf(err, "parsing [%s:%d]: line too long, limit is %d characters",
322 file, linenum, CRT_LINESIZE-1);
William Lallemand6e9556b2020-05-12 17:52:44 +0200323 cfgerr |= ERR_ALERT | ERR_FATAL;
324 goto error;
325 }
326 arg = 0;
327 newarg = 1;
328 while (*line) {
329 if (isspace((unsigned char)*line)) {
330 newarg = 1;
331 *line = 0;
332 } else if (*line == '[') {
333 if (ssl_b) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200334 memprintf(err, "parsing [%s:%d]: too many '['", file, linenum);
William Lallemand6e9556b2020-05-12 17:52:44 +0200335 cfgerr |= ERR_ALERT | ERR_FATAL;
336 goto error;
337 }
338 if (!arg) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200339 memprintf(err, "parsing [%s:%d]: file must start with a cert", file, linenum);
William Lallemand6e9556b2020-05-12 17:52:44 +0200340 cfgerr |= ERR_ALERT | ERR_FATAL;
341 goto error;
342 }
343 ssl_b = arg;
344 newarg = 1;
345 *line = 0;
346 } else if (*line == ']') {
347 if (ssl_e) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200348 memprintf(err, "parsing [%s:%d]: too many ']'", file, linenum);
William Lallemand6e9556b2020-05-12 17:52:44 +0200349 cfgerr |= ERR_ALERT | ERR_FATAL;
350 goto error;
351 }
352 if (!ssl_b) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200353 memprintf(err, "parsing [%s:%d]: missing '['", file, linenum);
William Lallemand6e9556b2020-05-12 17:52:44 +0200354 cfgerr |= ERR_ALERT | ERR_FATAL;
355 goto error;
356 }
357 ssl_e = arg;
358 newarg = 1;
359 *line = 0;
360 } else if (newarg) {
361 if (arg == MAX_CRT_ARGS) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200362 memprintf(err, "parsing [%s:%d]: too many args ", file, linenum);
William Lallemand6e9556b2020-05-12 17:52:44 +0200363 cfgerr |= ERR_ALERT | ERR_FATAL;
364 goto error;
365 }
366 newarg = 0;
367 args[arg++] = line;
368 }
369 line++;
370 }
371 args[arg++] = line;
372
373 /* empty line */
374 if (!*args[0]) {
375 cfgerr |= ERR_NONE;
376 goto error;
377 }
378
379 *crt_path = args[0];
380
381 if (ssl_b) {
William Lallemand32072012023-02-07 17:06:35 +0100382 if (ssl_b > 1) {
383 memprintf(err, "parsing [%s:%d]: malformated line, filters can't be between filename and options!", file, linenum);
384 cfgerr |= ERR_WARN;
385 }
386
William Lallemand6e9556b2020-05-12 17:52:44 +0200387 ssl_conf = calloc(1, sizeof *ssl_conf);
388 if (!ssl_conf) {
389 memprintf(err, "not enough memory!");
390 cfgerr |= ERR_ALERT | ERR_FATAL;
391 goto error;
392 }
393 }
Remi Tricot-Le Bretonfb00f312021-03-23 16:41:53 +0100394
William Lallemand6e9556b2020-05-12 17:52:44 +0200395 cur_arg = ssl_b ? ssl_b : 1;
396 while (cur_arg < ssl_e) {
397 newarg = 0;
398 for (i = 0; ssl_bind_kws[i].kw != NULL; i++) {
399 if (strcmp(ssl_bind_kws[i].kw, args[cur_arg]) == 0) {
400 newarg = 1;
Remi Tricot-Le Bretonfb00f312021-03-23 16:41:53 +0100401 cfgerr |= ssl_bind_kws[i].parse(args, cur_arg, NULL, ssl_conf, from_cli, err);
William Lallemand6e9556b2020-05-12 17:52:44 +0200402 if (cur_arg + 1 + ssl_bind_kws[i].skip > ssl_e) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200403 memprintf(err, "parsing [%s:%d]: ssl args out of '[]' for %s",
404 file, linenum, args[cur_arg]);
William Lallemand6e9556b2020-05-12 17:52:44 +0200405 cfgerr |= ERR_ALERT | ERR_FATAL;
406 goto error;
407 }
408 cur_arg += 1 + ssl_bind_kws[i].skip;
409 break;
410 }
411 }
412 if (!cfgerr && !newarg) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200413 memprintf(err, "parsing [%s:%d]: unknown ssl keyword %s",
414 file, linenum, args[cur_arg]);
William Lallemand6e9556b2020-05-12 17:52:44 +0200415 cfgerr |= ERR_ALERT | ERR_FATAL;
416 goto error;
417 }
418 }
419 entry->linenum = linenum;
420 entry->ssl_conf = ssl_conf;
421 entry->filters = crtlist_dup_filters(&args[cur_arg], arg - cur_arg - 1);
422 entry->fcount = arg - cur_arg - 1;
423
424 return cfgerr;
425
426error:
427 crtlist_free_filters(entry->filters);
428 entry->filters = NULL;
429 ssl_sock_free_ssl_conf(entry->ssl_conf);
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100430 ha_free(&entry->ssl_conf);
William Lallemand6e9556b2020-05-12 17:52:44 +0200431 return cfgerr;
432}
433
434
435
436/* This function parse a crt-list file and store it in a struct crtlist, each line is a crtlist_entry structure
437 * Fill the <crtlist> argument with a pointer to a new crtlist struct
438 *
439 * This function tries to open and store certificate files.
440 */
441int crtlist_parse_file(char *file, struct bind_conf *bind_conf, struct proxy *curproxy, struct crtlist **crtlist, char **err)
442{
443 struct crtlist *newlist;
444 struct crtlist_entry *entry = NULL;
445 char thisline[CRT_LINESIZE];
William Lallemand6e9556b2020-05-12 17:52:44 +0200446 FILE *f;
447 struct stat buf;
448 int linenum = 0;
449 int cfgerr = 0;
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200450 int missing_lf = -1;
William Lallemand6e9556b2020-05-12 17:52:44 +0200451
452 if ((f = fopen(file, "r")) == NULL) {
453 memprintf(err, "cannot open file '%s' : %s", file, strerror(errno));
454 return ERR_ALERT | ERR_FATAL;
455 }
456
457 newlist = crtlist_new(file, 0);
458 if (newlist == NULL) {
459 memprintf(err, "Not enough memory!");
460 cfgerr |= ERR_ALERT | ERR_FATAL;
461 goto error;
462 }
463
464 while (fgets(thisline, sizeof(thisline), f) != NULL) {
465 char *end;
466 char *line = thisline;
467 char *crt_path;
William Lallemand86c2dd62020-11-20 14:23:38 +0100468 char path[MAXPATHLEN+1];
William Lallemand6e9556b2020-05-12 17:52:44 +0200469 struct ckch_store *ckchs;
William Lallemand77e1c6f2020-11-20 18:26:09 +0100470 int found = 0;
William Lallemand6e9556b2020-05-12 17:52:44 +0200471
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200472 if (missing_lf != -1) {
473 memprintf(err, "parsing [%s:%d]: Stray NUL character at position %d.\n",
474 file, linenum, (missing_lf + 1));
475 cfgerr |= ERR_ALERT | ERR_FATAL;
476 missing_lf = -1;
477 break;
478 }
479
William Lallemand6e9556b2020-05-12 17:52:44 +0200480 linenum++;
481 end = line + strlen(line);
482 if (end-line == sizeof(thisline)-1 && *(end-1) != '\n') {
483 /* Check if we reached the limit and the last char is not \n.
484 * Watch out for the last line without the terminating '\n'!
485 */
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200486 memprintf(err, "parsing [%s:%d]: line too long, limit is %d characters",
487 file, linenum, (int)sizeof(thisline)-1);
William Lallemand6e9556b2020-05-12 17:52:44 +0200488 cfgerr |= ERR_ALERT | ERR_FATAL;
489 break;
490 }
491
492 if (*line == '#' || *line == '\n' || *line == '\r')
493 continue;
494
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200495 if (end > line && *(end-1) == '\n') {
496 /* kill trailing LF */
497 *(end - 1) = 0;
498 }
499 else {
500 /* mark this line as truncated */
501 missing_lf = end - line;
502 }
503
William Lallemand6e9556b2020-05-12 17:52:44 +0200504 entry = crtlist_entry_new();
505 if (entry == NULL) {
506 memprintf(err, "Not enough memory!");
507 cfgerr |= ERR_ALERT | ERR_FATAL;
508 goto error;
509 }
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200510
Remi Tricot-Le Bretonfb00f312021-03-23 16:41:53 +0100511 cfgerr |= crtlist_parse_line(thisline, &crt_path, entry, file, linenum, 0, err);
William Lallemand20b0fed2020-09-28 15:45:16 +0200512 if (cfgerr & ERR_CODE)
William Lallemand6e9556b2020-05-12 17:52:44 +0200513 goto error;
514
515 /* empty line */
516 if (!crt_path || !*crt_path) {
517 crtlist_entry_free(entry);
518 entry = NULL;
519 continue;
520 }
521
522 if (*crt_path != '/' && global_ssl.crt_base) {
Willy Tarreau326a8662022-05-09 10:31:28 +0200523 if ((strlen(global_ssl.crt_base) + 1 + strlen(crt_path)) > sizeof(path) ||
Willy Tarreau6c3cc562022-05-09 21:14:04 +0200524 snprintf(path, sizeof(path), "%s/%s", global_ssl.crt_base, crt_path) > sizeof(path)) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200525 memprintf(err, "parsing [%s:%d]: '%s' : path too long",
526 file, linenum, crt_path);
William Lallemand6e9556b2020-05-12 17:52:44 +0200527 cfgerr |= ERR_ALERT | ERR_FATAL;
528 goto error;
529 }
William Lallemand6e9556b2020-05-12 17:52:44 +0200530 crt_path = path;
531 }
532
533 /* Look for a ckch_store or create one */
534 ckchs = ckchs_lookup(crt_path);
535 if (ckchs == NULL) {
William Lallemand47da8212020-09-10 19:13:27 +0200536 if (stat(crt_path, &buf) == 0) {
William Lallemand77e1c6f2020-11-20 18:26:09 +0100537 found++;
William Lallemand47da8212020-09-10 19:13:27 +0200538
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200539 ckchs = ckchs_load_cert_file(crt_path, err);
William Lallemand47da8212020-09-10 19:13:27 +0200540 if (ckchs == NULL) {
541 cfgerr |= ERR_ALERT | ERR_FATAL;
542 goto error;
543 }
544
545 entry->node.key = ckchs;
546 entry->crtlist = newlist;
547 ebpt_insert(&newlist->entries, &entry->node);
Willy Tarreau2b718102021-04-21 07:32:39 +0200548 LIST_APPEND(&newlist->ord_entries, &entry->by_crtlist);
549 LIST_APPEND(&ckchs->crtlist_entry, &entry->by_ckch_store);
William Lallemand6e9556b2020-05-12 17:52:44 +0200550
William Lallemand73404572020-11-20 18:23:40 +0100551 } else if (global_ssl.extra_files & SSL_GF_BUNDLE) {
William Lallemand47da8212020-09-10 19:13:27 +0200552 /* If we didn't find the file, this could be a
William Lallemand51f784b2020-10-02 18:08:18 +0200553 bundle, since 2.3 we don't support multiple
554 certificate in the same OpenSSL store, so we
555 emulate it by loading each file separately. To
556 do so we need to duplicate the entry in the
557 crt-list because it becomes independent */
William Lallemand47da8212020-09-10 19:13:27 +0200558 char fp[MAXPATHLEN+1] = {0};
559 int n = 0;
560 struct crtlist_entry *entry_dup = entry; /* use the previous created entry */
561 for (n = 0; n < SSL_SOCK_NUM_KEYTYPES; n++) {
562 struct stat buf;
563 int ret;
564
William Lallemand86c2dd62020-11-20 14:23:38 +0100565 ret = snprintf(fp, sizeof(fp), "%s.%s", crt_path, SSL_SOCK_KEYTYPE_NAMES[n]);
William Lallemand47da8212020-09-10 19:13:27 +0200566 if (ret > sizeof(fp))
567 continue;
568
569 ckchs = ckchs_lookup(fp);
William Lallemand77e1c6f2020-11-20 18:26:09 +0100570 if (!ckchs) {
571 if (stat(fp, &buf) == 0) {
572 ckchs = ckchs_load_cert_file(fp, err);
573 if (!ckchs) {
William Lallemand47da8212020-09-10 19:13:27 +0200574 cfgerr |= ERR_ALERT | ERR_FATAL;
575 goto error;
576 }
William Lallemand77e1c6f2020-11-20 18:26:09 +0100577 } else {
578 continue; /* didn't find this extension, skip */
579 }
580 }
581 found++;
582 linenum++; /* we duplicate the line for this entry in the bundle */
583 if (!entry_dup) { /* if the entry was used, duplicate one */
584 linenum++;
585 entry_dup = crtlist_entry_dup(entry);
586 if (!entry_dup) {
587 cfgerr |= ERR_ALERT | ERR_FATAL;
588 goto error;
William Lallemand47da8212020-09-10 19:13:27 +0200589 }
William Lallemand77e1c6f2020-11-20 18:26:09 +0100590 entry_dup->linenum = linenum;
591 }
William Lallemand47da8212020-09-10 19:13:27 +0200592
William Lallemand77e1c6f2020-11-20 18:26:09 +0100593 entry_dup->node.key = ckchs;
594 entry_dup->crtlist = newlist;
595 ebpt_insert(&newlist->entries, &entry_dup->node);
Willy Tarreau2b718102021-04-21 07:32:39 +0200596 LIST_APPEND(&newlist->ord_entries, &entry_dup->by_crtlist);
597 LIST_APPEND(&ckchs->crtlist_entry, &entry_dup->by_ckch_store);
William Lallemand47da8212020-09-10 19:13:27 +0200598
William Lallemand77e1c6f2020-11-20 18:26:09 +0100599 entry_dup = NULL; /* the entry was used, we need a new one next round */
William Lallemand47da8212020-09-10 19:13:27 +0200600 }
William Lallemandb7fdfdf2020-12-04 15:45:02 +0100601#if HA_OPENSSL_VERSION_NUMBER < 0x10101000L
602 if (found) {
603 memprintf(err, "%sCan't load '%s'. Loading a multi certificates bundle requires OpenSSL >= 1.1.1\n",
604 err && *err ? *err : "", crt_path);
605 cfgerr |= ERR_ALERT | ERR_FATAL;
606 }
607#endif
William Lallemand47da8212020-09-10 19:13:27 +0200608 }
William Lallemand77e1c6f2020-11-20 18:26:09 +0100609 if (!found) {
610 memprintf(err, "%sunable to stat SSL certificate from file '%s' : %s.\n",
611 err && *err ? *err : "", crt_path, strerror(errno));
612 cfgerr |= ERR_ALERT | ERR_FATAL;
613 }
614
William Lallemand50c03aa2020-11-06 16:24:07 +0100615 } else {
616 entry->node.key = ckchs;
617 entry->crtlist = newlist;
618 ebpt_insert(&newlist->entries, &entry->node);
Willy Tarreau2b718102021-04-21 07:32:39 +0200619 LIST_APPEND(&newlist->ord_entries, &entry->by_crtlist);
620 LIST_APPEND(&ckchs->crtlist_entry, &entry->by_ckch_store);
William Lallemand77e1c6f2020-11-20 18:26:09 +0100621 found++;
William Lallemand47da8212020-09-10 19:13:27 +0200622 }
William Lallemand6e9556b2020-05-12 17:52:44 +0200623 entry = NULL;
624 }
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200625
626 if (missing_lf != -1) {
627 memprintf(err, "parsing [%s:%d]: Missing LF on last line, file might have been truncated at position %d.\n",
628 file, linenum, (missing_lf + 1));
629 cfgerr |= ERR_ALERT | ERR_FATAL;
630 }
631
William Lallemand6e9556b2020-05-12 17:52:44 +0200632 if (cfgerr & ERR_CODE)
633 goto error;
634
635 newlist->linecount = linenum;
636
637 fclose(f);
638 *crtlist = newlist;
639
640 return cfgerr;
641error:
642 crtlist_entry_free(entry);
643
644 fclose(f);
645 crtlist_free(newlist);
646 return cfgerr;
647}
648
649/* This function reads a directory and stores it in a struct crtlist, each file is a crtlist_entry structure
650 * Fill the <crtlist> argument with a pointer to a new crtlist struct
651 *
652 * This function tries to open and store certificate files.
653 */
654int crtlist_load_cert_dir(char *path, struct bind_conf *bind_conf, struct crtlist **crtlist, char **err)
655{
656 struct crtlist *dir;
657 struct dirent **de_list;
658 int i, n;
659 struct stat buf;
660 char *end;
661 char fp[MAXPATHLEN+1];
662 int cfgerr = 0;
663 struct ckch_store *ckchs;
William Lallemand6e9556b2020-05-12 17:52:44 +0200664
665 dir = crtlist_new(path, 1);
666 if (dir == NULL) {
667 memprintf(err, "not enough memory");
668 return ERR_ALERT | ERR_FATAL;
669 }
670
671 n = scandir(path, &de_list, 0, alphasort);
672 if (n < 0) {
673 memprintf(err, "%sunable to scan directory '%s' : %s.\n",
674 err && *err ? *err : "", path, strerror(errno));
675 cfgerr |= ERR_ALERT | ERR_FATAL;
676 }
677 else {
678 for (i = 0; i < n; i++) {
679 struct crtlist_entry *entry;
680 struct dirent *de = de_list[i];
681
682 end = strrchr(de->d_name, '.');
Tim Duesterhuse5ff1412021-01-02 22:31:53 +0100683 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 +0200684 goto ignore_entry;
685
686 snprintf(fp, sizeof(fp), "%s/%s", path, de->d_name);
687 if (stat(fp, &buf) != 0) {
688 memprintf(err, "%sunable to stat SSL certificate from file '%s' : %s.\n",
689 err && *err ? *err : "", fp, strerror(errno));
690 cfgerr |= ERR_ALERT | ERR_FATAL;
691 goto ignore_entry;
692 }
693 if (!S_ISREG(buf.st_mode))
694 goto ignore_entry;
695
696 entry = crtlist_entry_new();
697 if (entry == NULL) {
698 memprintf(err, "not enough memory '%s'", fp);
699 cfgerr |= ERR_ALERT | ERR_FATAL;
700 goto ignore_entry;
701 }
702
William Lallemand6e9556b2020-05-12 17:52:44 +0200703 ckchs = ckchs_lookup(fp);
704 if (ckchs == NULL)
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200705 ckchs = ckchs_load_cert_file(fp, err);
William Lallemand6e9556b2020-05-12 17:52:44 +0200706 if (ckchs == NULL) {
707 free(de);
708 free(entry);
709 cfgerr |= ERR_ALERT | ERR_FATAL;
710 goto end;
711 }
712 entry->node.key = ckchs;
713 entry->crtlist = dir;
Willy Tarreau2b718102021-04-21 07:32:39 +0200714 LIST_APPEND(&ckchs->crtlist_entry, &entry->by_ckch_store);
715 LIST_APPEND(&dir->ord_entries, &entry->by_crtlist);
William Lallemand6e9556b2020-05-12 17:52:44 +0200716 ebpt_insert(&dir->entries, &entry->node);
717
718ignore_entry:
719 free(de);
720 }
721end:
722 free(de_list);
723 }
724
725 if (cfgerr & ERR_CODE) {
726 /* free the dir and entries on error */
727 crtlist_free(dir);
728 } else {
729 *crtlist = dir;
730 }
731 return cfgerr;
732
733}
734
William Lallemandc756bbd2020-05-13 17:23:59 +0200735/*
736 * Take an ssl_bind_conf structure and append the configuration line used to
737 * create it in the buffer
738 */
739static void dump_crtlist_sslconf(struct buffer *buf, const struct ssl_bind_conf *conf)
740{
741 int space = 0;
742
743 if (conf == NULL)
744 return;
745
746 chunk_appendf(buf, " [");
747#ifdef OPENSSL_NPN_NEGOTIATED
748 if (conf->npn_str) {
749 int len = conf->npn_len;
750 char *ptr = conf->npn_str;
751 int comma = 0;
752
753 if (space) chunk_appendf(buf, " ");
754 chunk_appendf(buf, "npn ");
755 while (len) {
756 unsigned short size;
757
758 size = *ptr;
759 ptr++;
760 if (comma)
761 chunk_memcat(buf, ",", 1);
762 chunk_memcat(buf, ptr, size);
763 ptr += size;
764 len -= size + 1;
765 comma = 1;
766 }
767 chunk_memcat(buf, "", 1); /* finish with a \0 */
768 space++;
769 }
770#endif
771#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
772 if (conf->alpn_str) {
773 int len = conf->alpn_len;
774 char *ptr = conf->alpn_str;
775 int comma = 0;
776
777 if (space) chunk_appendf(buf, " ");
778 chunk_appendf(buf, "alpn ");
779 while (len) {
780 unsigned short size;
781
782 size = *ptr;
783 ptr++;
784 if (comma)
785 chunk_memcat(buf, ",", 1);
786 chunk_memcat(buf, ptr, size);
787 ptr += size;
788 len -= size + 1;
789 comma = 1;
790 }
791 chunk_memcat(buf, "", 1); /* finish with a \0 */
792 space++;
793 }
794#endif
795 /* verify */
796 {
797 if (conf->verify == SSL_SOCK_VERIFY_NONE) {
798 if (space) chunk_appendf(buf, " ");
799 chunk_appendf(buf, "verify none");
800 space++;
801 } else if (conf->verify == SSL_SOCK_VERIFY_OPTIONAL) {
802 if (space) chunk_appendf(buf, " ");
803 chunk_appendf(buf, "verify optional");
804 space++;
805 } else if (conf->verify == SSL_SOCK_VERIFY_REQUIRED) {
806 if (space) chunk_appendf(buf, " ");
807 chunk_appendf(buf, "verify required");
808 space++;
809 }
810 }
811
812 if (conf->no_ca_names) {
813 if (space) chunk_appendf(buf, " ");
814 chunk_appendf(buf, "no-ca-names");
815 space++;
816 }
817
818 if (conf->early_data) {
819 if (space) chunk_appendf(buf, " ");
820 chunk_appendf(buf, "allow-0rtt");
821 space++;
822 }
823 if (conf->ca_file) {
824 if (space) chunk_appendf(buf, " ");
825 chunk_appendf(buf, "ca-file %s", conf->ca_file);
826 space++;
827 }
828 if (conf->crl_file) {
829 if (space) chunk_appendf(buf, " ");
830 chunk_appendf(buf, "crl-file %s", conf->crl_file);
831 space++;
832 }
833 if (conf->ciphers) {
834 if (space) chunk_appendf(buf, " ");
835 chunk_appendf(buf, "ciphers %s", conf->ciphers);
836 space++;
837 }
Ilya Shipitsinf34ed0b2020-11-21 14:37:34 +0500838#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES
William Lallemandc756bbd2020-05-13 17:23:59 +0200839 if (conf->ciphersuites) {
840 if (space) chunk_appendf(buf, " ");
841 chunk_appendf(buf, "ciphersuites %s", conf->ciphersuites);
842 space++;
843 }
844#endif
845 if (conf->curves) {
846 if (space) chunk_appendf(buf, " ");
847 chunk_appendf(buf, "curves %s", conf->curves);
848 space++;
849 }
850 if (conf->ecdhe) {
851 if (space) chunk_appendf(buf, " ");
852 chunk_appendf(buf, "ecdhe %s", conf->ecdhe);
853 space++;
854 }
855
856 /* the crt-lists only support ssl-min-ver and ssl-max-ver */
William Lallemand8177ad92020-05-20 16:49:02 +0200857 if (conf->ssl_methods_cfg.min) {
William Lallemandc756bbd2020-05-13 17:23:59 +0200858 if (space) chunk_appendf(buf, " ");
William Lallemand8177ad92020-05-20 16:49:02 +0200859 chunk_appendf(buf, "ssl-min-ver %s", methodVersions[conf->ssl_methods_cfg.min].name);
William Lallemandc756bbd2020-05-13 17:23:59 +0200860 space++;
861 }
862
William Lallemand8177ad92020-05-20 16:49:02 +0200863 if (conf->ssl_methods_cfg.max) {
William Lallemandc756bbd2020-05-13 17:23:59 +0200864 if (space) chunk_appendf(buf, " ");
William Lallemand8177ad92020-05-20 16:49:02 +0200865 chunk_appendf(buf, "ssl-max-ver %s", methodVersions[conf->ssl_methods_cfg.max].name);
William Lallemandc756bbd2020-05-13 17:23:59 +0200866 space++;
867 }
868
869 chunk_appendf(buf, "]");
870
871 return;
872}
873
874/* dump a list of filters */
875static void dump_crtlist_filters(struct buffer *buf, struct crtlist_entry *entry)
876{
877 int i;
878
879 if (!entry->fcount)
880 return;
881
882 for (i = 0; i < entry->fcount; i++) {
883 chunk_appendf(buf, " %s", entry->filters[i]);
884 }
885 return;
886}
887
888/************************** CLI functions ****************************/
889
890
891/* CLI IO handler for '(show|dump) ssl crt-list' */
892static int cli_io_handler_dump_crtlist(struct appctx *appctx)
893{
894 struct buffer *trash = alloc_trash_chunk();
895 struct stream_interface *si = appctx->owner;
896 struct ebmb_node *lnode;
897
898 if (trash == NULL)
899 return 1;
900
901 /* dump the list of crt-lists */
902 lnode = appctx->ctx.cli.p1;
903 if (lnode == NULL)
904 lnode = ebmb_first(&crtlists_tree);
905 while (lnode) {
906 chunk_appendf(trash, "%s\n", lnode->key);
907 if (ci_putchk(si_ic(si), trash) == -1) {
908 si_rx_room_blk(si);
909 goto yield;
910 }
911 lnode = ebmb_next(lnode);
912 }
913 free_trash_chunk(trash);
914 return 1;
915yield:
916 appctx->ctx.cli.p1 = lnode;
917 free_trash_chunk(trash);
918 return 0;
919}
920
921/* CLI IO handler for '(show|dump) ssl crt-list <filename>' */
922static int cli_io_handler_dump_crtlist_entries(struct appctx *appctx)
923{
924 struct buffer *trash = alloc_trash_chunk();
925 struct crtlist *crtlist;
926 struct stream_interface *si = appctx->owner;
927 struct crtlist_entry *entry;
928
929 if (trash == NULL)
930 return 1;
931
932 crtlist = ebmb_entry(appctx->ctx.cli.p0, struct crtlist, node);
933
934 entry = appctx->ctx.cli.p1;
935 if (entry == NULL) {
936 entry = LIST_ELEM((crtlist->ord_entries).n, typeof(entry), by_crtlist);
937 chunk_appendf(trash, "# %s\n", crtlist->node.key);
938 if (ci_putchk(si_ic(si), trash) == -1) {
939 si_rx_room_blk(si);
940 goto yield;
941 }
942 }
943
944 list_for_each_entry_from(entry, &crtlist->ord_entries, by_crtlist) {
945 struct ckch_store *store;
946 const char *filename;
947
948 store = entry->node.key;
949 filename = store->path;
950 chunk_appendf(trash, "%s", filename);
951 if (appctx->ctx.cli.i0 == 's') /* show */
952 chunk_appendf(trash, ":%d", entry->linenum);
953 dump_crtlist_sslconf(trash, entry->ssl_conf);
954 dump_crtlist_filters(trash, entry);
955 chunk_appendf(trash, "\n");
956
957 if (ci_putchk(si_ic(si), trash) == -1) {
958 si_rx_room_blk(si);
959 goto yield;
960 }
961 }
962 free_trash_chunk(trash);
963 return 1;
964yield:
965 appctx->ctx.cli.p1 = entry;
966 free_trash_chunk(trash);
967 return 0;
968}
969
970/* CLI argument parser for '(show|dump) ssl crt-list' */
971static int cli_parse_dump_crtlist(char **args, char *payload, struct appctx *appctx, void *private)
972{
973 struct ebmb_node *lnode;
974 char *filename = NULL;
975 int mode;
William Lallemand99cc2182020-06-25 15:19:51 +0200976 char *end;
William Lallemandc756bbd2020-05-13 17:23:59 +0200977
978 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
979 return 1;
980
981 appctx->ctx.cli.p0 = NULL;
982 appctx->ctx.cli.p1 = NULL;
983
Tim Duesterhuse5ff1412021-01-02 22:31:53 +0100984 if (*args[3] && strcmp(args[3], "-n") == 0) {
William Lallemandc756bbd2020-05-13 17:23:59 +0200985 mode = 's';
986 filename = args[4];
987 } else {
988 mode = 'd';
989 filename = args[3];
990 }
991
992 if (mode == 's' && !*args[4])
993 return cli_err(appctx, "'show ssl crt-list -n' expects a filename or a directory\n");
994
995 if (filename && *filename) {
William Lallemand99cc2182020-06-25 15:19:51 +0200996
997
998 /* strip trailing slashes, including first one */
999 for (end = filename + strlen(filename) - 1; end >= filename && *end == '/'; end--)
1000 *end = 0;
1001
William Lallemandc756bbd2020-05-13 17:23:59 +02001002 lnode = ebst_lookup(&crtlists_tree, filename);
1003 if (lnode == NULL)
1004 return cli_err(appctx, "didn't find the specified filename\n");
1005
1006 appctx->ctx.cli.p0 = lnode;
1007 appctx->io_handler = cli_io_handler_dump_crtlist_entries;
1008 }
1009 appctx->ctx.cli.i0 = mode;
1010
1011 return 0;
1012}
1013
1014/* release function of the "add ssl crt-list' command, free things and unlock
1015 the spinlock */
1016static void cli_release_add_crtlist(struct appctx *appctx)
1017{
1018 struct crtlist_entry *entry = appctx->ctx.cli.p1;
1019
Christopher Faulet59b4ef52022-06-01 16:31:09 +02001020 if (entry) {
William Lallemandc756bbd2020-05-13 17:23:59 +02001021 struct ckch_inst *inst, *inst_s;
Christopher Faulet59b4ef52022-06-01 16:31:09 +02001022
William Lallemandc756bbd2020-05-13 17:23:59 +02001023 /* upon error free the ckch_inst and everything inside */
1024 ebpt_delete(&entry->node);
Willy Tarreau2b718102021-04-21 07:32:39 +02001025 LIST_DELETE(&entry->by_crtlist);
1026 LIST_DELETE(&entry->by_ckch_store);
William Lallemandc756bbd2020-05-13 17:23:59 +02001027
1028 list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_ckchs) {
1029 ckch_inst_free(inst);
1030 }
1031 crtlist_free_filters(entry->filters);
1032 ssl_sock_free_ssl_conf(entry->ssl_conf);
1033 free(entry->ssl_conf);
1034 free(entry);
1035 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001036 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
Christopher Faulet59b4ef52022-06-01 16:31:09 +02001037 ha_free(&appctx->ctx.cli.err);
William Lallemandc756bbd2020-05-13 17:23:59 +02001038}
1039
1040
1041/* IO Handler for the "add ssl crt-list" command It adds a new entry in the
1042 * crt-list and generates the ckch_insts for each bind_conf that uses this crt-list
1043 *
1044 * The logic is the same as the "commit ssl cert" command but without the
1045 * freeing of the old structures, because there are none.
1046 */
1047static int cli_io_handler_add_crtlist(struct appctx *appctx)
1048{
1049 struct bind_conf_list *bind_conf_node;
1050 struct stream_interface *si = appctx->owner;
1051 struct crtlist *crtlist = appctx->ctx.cli.p0;
1052 struct crtlist_entry *entry = appctx->ctx.cli.p1;
1053 struct ckch_store *store = entry->node.key;
William Lallemandc756bbd2020-05-13 17:23:59 +02001054 struct ckch_inst *new_inst;
William Lallemandc756bbd2020-05-13 17:23:59 +02001055 int i = 0;
1056 int errcode = 0;
1057
William Lallemandc756bbd2020-05-13 17:23:59 +02001058 /* for each bind_conf which use the crt-list, a new ckch_inst must be
1059 * created.
1060 */
1061 if (unlikely(si_ic(si)->flags & (CF_WRITE_ERROR|CF_SHUTW)))
Christopher Faulet59b4ef52022-06-01 16:31:09 +02001062 goto end;
William Lallemandc756bbd2020-05-13 17:23:59 +02001063
1064 while (1) {
1065 switch (appctx->st2) {
1066 case SETCERT_ST_INIT:
1067 /* This state just print the update message */
Christopher Faulet59b4ef52022-06-01 16:31:09 +02001068 chunk_printf(&trash, "Inserting certificate '%s' in crt-list '%s'", store->path, crtlist->node.key);
1069 if (ci_putchk(si_ic(si), &trash) == -1)
William Lallemandc756bbd2020-05-13 17:23:59 +02001070 goto yield;
William Lallemandc756bbd2020-05-13 17:23:59 +02001071 appctx->st2 = SETCERT_ST_GEN;
1072 /* fallthrough */
1073 case SETCERT_ST_GEN:
1074 bind_conf_node = appctx->ctx.cli.p2; /* get the previous ptr from the yield */
1075 if (bind_conf_node == NULL)
1076 bind_conf_node = crtlist->bind_conf;
1077 for (; bind_conf_node; bind_conf_node = bind_conf_node->next) {
1078 struct bind_conf *bind_conf = bind_conf_node->bind_conf;
1079 struct sni_ctx *sni;
1080
Christopher Faulet59b4ef52022-06-01 16:31:09 +02001081 appctx->ctx.cli.p2 = bind_conf_node;
1082
William Lallemandc756bbd2020-05-13 17:23:59 +02001083 /* yield every 10 generations */
1084 if (i > 10) {
Christopher Faulet59b4ef52022-06-01 16:31:09 +02001085 si_rx_endp_more(si); /* let's come back later */
William Lallemandc756bbd2020-05-13 17:23:59 +02001086 goto yield;
1087 }
1088
Christopher Faulet59b4ef52022-06-01 16:31:09 +02001089 /* display one dot for each new instance */
1090 if (ci_putstr(si_ic(si), ".") == -1)
1091 goto yield;
1092
William Lallemandc756bbd2020-05-13 17:23:59 +02001093 /* we don't support multi-cert bundles, only simple ones */
Christopher Faulet59b4ef52022-06-01 16:31:09 +02001094 appctx->ctx.cli.err = NULL;
1095 errcode |= ckch_inst_new_load_store(store->path, store, bind_conf, entry->ssl_conf, entry->filters, entry->fcount, &new_inst, &appctx->ctx.cli.err);
1096 if (errcode & ERR_CODE) {
1097 appctx->st2 = SETCERT_ST_ERROR;
William Lallemandc756bbd2020-05-13 17:23:59 +02001098 goto error;
Christopher Faulet59b4ef52022-06-01 16:31:09 +02001099 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001100
1101 /* we need to initialize the SSL_CTX generated */
1102 /* this iterate on the newly generated SNIs in the new instance to prepare their SSL_CTX */
1103 list_for_each_entry(sni, &new_inst->sni_ctx, by_ckch_inst) {
1104 if (!sni->order) { /* we initialized only the first SSL_CTX because it's the same in the other sni_ctx's */
Christopher Faulet59b4ef52022-06-01 16:31:09 +02001105 appctx->ctx.cli.err = NULL;
1106 errcode |= ssl_sock_prepare_ctx(bind_conf, new_inst->ssl_conf, sni->ctx, &appctx->ctx.cli.err);
1107 if (errcode & ERR_CODE) {
1108 appctx->st2 = SETCERT_ST_ERROR;
William Lallemandc756bbd2020-05-13 17:23:59 +02001109 goto error;
Christopher Faulet59b4ef52022-06-01 16:31:09 +02001110 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001111 }
1112 }
Christopher Faulet59b4ef52022-06-01 16:31:09 +02001113
William Lallemandc756bbd2020-05-13 17:23:59 +02001114 i++;
Willy Tarreau2b718102021-04-21 07:32:39 +02001115 LIST_APPEND(&store->ckch_inst, &new_inst->by_ckchs);
1116 LIST_APPEND(&entry->ckch_inst, &new_inst->by_crtlist_entry);
William Lallemand9ab8f8d2020-06-24 01:00:52 +02001117 new_inst->crtlist_entry = entry;
William Lallemandc756bbd2020-05-13 17:23:59 +02001118 }
1119 appctx->st2 = SETCERT_ST_INSERT;
1120 /* fallthrough */
1121 case SETCERT_ST_INSERT:
William Lallemand31d463d2022-06-20 16:51:53 +02001122 /* the insertion is called for every instance of the store, not
1123 * only the one we generated.
1124 * But the ssl_sock_load_cert_sni() skip the sni already
1125 * inserted. Not every instance has a bind_conf, it could be
1126 * the store of a server so we should be careful */
1127
William Lallemandc756bbd2020-05-13 17:23:59 +02001128 list_for_each_entry(new_inst, &store->ckch_inst, by_ckchs) {
William Lallemand31d463d2022-06-20 16:51:53 +02001129 if (!new_inst->bind_conf) /* this is a server instance */
1130 continue;
William Lallemandc756bbd2020-05-13 17:23:59 +02001131 HA_RWLOCK_WRLOCK(SNI_LOCK, &new_inst->bind_conf->sni_lock);
1132 ssl_sock_load_cert_sni(new_inst, new_inst->bind_conf);
1133 HA_RWLOCK_WRUNLOCK(SNI_LOCK, &new_inst->bind_conf->sni_lock);
1134 }
1135 entry->linenum = ++crtlist->linecount;
Christopher Faulet59b4ef52022-06-01 16:31:09 +02001136 appctx->ctx.cli.p1 = NULL;
1137 appctx->st2 = SETCERT_ST_SUCCESS;
1138 /* fallthrough */
1139 case SETCERT_ST_SUCCESS:
1140 chunk_reset(&trash);
1141 chunk_appendf(&trash, "\n");
1142 if (appctx->ctx.cli.err)
1143 chunk_appendf(&trash, "%s", appctx->ctx.cli.err);
1144 chunk_appendf(&trash, "Success!\n");
1145 if (ci_putchk(si_ic(si), &trash) == -1)
1146 goto yield;
William Lallemandc756bbd2020-05-13 17:23:59 +02001147 appctx->st2 = SETCERT_ST_FIN;
1148 goto end;
Christopher Faulet59b4ef52022-06-01 16:31:09 +02001149
1150 case SETCERT_ST_ERROR:
1151 error:
1152 chunk_printf(&trash, "\n%sFailed!\n", appctx->ctx.cli.err);
1153 if (ci_putchk(si_ic(si), &trash) == -1)
1154 goto yield;
1155 goto end;
1156
1157 default:
1158 goto end;
William Lallemandc756bbd2020-05-13 17:23:59 +02001159 }
1160 }
1161
1162end:
William Lallemandc756bbd2020-05-13 17:23:59 +02001163 /* success: call the release function and don't come back */
1164 return 1;
1165yield:
Christopher Faulet59b4ef52022-06-01 16:31:09 +02001166 si_rx_room_blk(si);
William Lallemandc756bbd2020-05-13 17:23:59 +02001167 return 0; /* should come back */
William Lallemandc756bbd2020-05-13 17:23:59 +02001168}
1169
1170
1171/*
1172 * Parse a "add ssl crt-list <crt-list> <certfile>" line.
1173 * Filters and option must be passed through payload:
1174 */
1175static int cli_parse_add_crtlist(char **args, char *payload, struct appctx *appctx, void *private)
1176{
1177 int cfgerr = 0;
1178 struct ckch_store *store;
1179 char *err = NULL;
1180 char path[MAXPATHLEN+1];
1181 char *crtlist_path;
1182 char *cert_path = NULL;
1183 struct ebmb_node *eb;
1184 struct ebpt_node *inserted;
1185 struct crtlist *crtlist;
1186 struct crtlist_entry *entry = NULL;
William Lallemand99cc2182020-06-25 15:19:51 +02001187 char *end;
William Lallemandc756bbd2020-05-13 17:23:59 +02001188
1189 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
1190 return 1;
1191
1192 if (!*args[3] || (!payload && !*args[4]))
1193 return cli_err(appctx, "'add ssl crtlist' expects a filename and a certificate name\n");
1194
1195 crtlist_path = args[3];
1196
William Lallemand99cc2182020-06-25 15:19:51 +02001197 /* strip trailing slashes, including first one */
1198 for (end = crtlist_path + strlen(crtlist_path) - 1; end >= crtlist_path && *end == '/'; end--)
1199 *end = 0;
1200
William Lallemandc756bbd2020-05-13 17:23:59 +02001201 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
1202 return cli_err(appctx, "Operations on certificates are currently locked!\n");
1203
1204 eb = ebst_lookup(&crtlists_tree, crtlist_path);
1205 if (!eb) {
1206 memprintf(&err, "crt-list '%s' does not exist!", crtlist_path);
1207 goto error;
1208 }
1209 crtlist = ebmb_entry(eb, struct crtlist, node);
1210
1211 entry = crtlist_entry_new();
1212 if (entry == NULL) {
1213 memprintf(&err, "Not enough memory!");
1214 goto error;
1215 }
1216
1217 if (payload) {
1218 char *lf;
1219
1220 lf = strrchr(payload, '\n');
1221 if (lf) {
1222 memprintf(&err, "only one line of payload is supported!");
1223 goto error;
1224 }
1225 /* cert_path is filled here */
Remi Tricot-Le Bretonfb00f312021-03-23 16:41:53 +01001226 cfgerr |= crtlist_parse_line(payload, &cert_path, entry, "CLI", 1, 1, &err);
William Lallemandc756bbd2020-05-13 17:23:59 +02001227 if (cfgerr & ERR_CODE)
1228 goto error;
1229 } else {
1230 cert_path = args[4];
1231 }
1232
1233 if (!cert_path) {
1234 memprintf(&err, "'add ssl crtlist' should contain the certificate name in the payload");
1235 cfgerr |= ERR_ALERT | ERR_FATAL;
1236 goto error;
1237 }
1238
1239 if (eb_gettag(crtlist->entries.b[EB_RGHT])) {
1240 char *slash;
1241
1242 slash = strrchr(cert_path, '/');
1243 if (!slash) {
1244 memprintf(&err, "'%s' is a directory, certificate path '%s' must contain the directory path", (char *)crtlist->node.key, cert_path);
1245 goto error;
1246 }
1247 /* temporary replace / by 0 to do an strcmp */
1248 *slash = '\0';
1249 if (strcmp(cert_path, (char*)crtlist->node.key) != 0) {
1250 *slash = '/';
1251 memprintf(&err, "'%s' is a directory, certificate path '%s' must contain the directory path", (char *)crtlist->node.key, cert_path);
1252 goto error;
1253 }
1254 *slash = '/';
1255 }
1256
1257 if (*cert_path != '/' && global_ssl.crt_base) {
Willy Tarreau326a8662022-05-09 10:31:28 +02001258 if ((strlen(global_ssl.crt_base) + 1 + strlen(cert_path)) > sizeof(path) ||
1259 snprintf(path, sizeof(path), "%s/%s", global_ssl.crt_base, cert_path) > sizeof(path)) {
William Lallemandc756bbd2020-05-13 17:23:59 +02001260 memprintf(&err, "'%s' : path too long", cert_path);
1261 cfgerr |= ERR_ALERT | ERR_FATAL;
1262 goto error;
1263 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001264 cert_path = path;
1265 }
1266
1267 store = ckchs_lookup(cert_path);
1268 if (store == NULL) {
1269 memprintf(&err, "certificate '%s' does not exist!", cert_path);
1270 goto error;
1271 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001272 if (store->ckch == NULL || store->ckch->cert == NULL) {
1273 memprintf(&err, "certificate '%s' is empty!", cert_path);
1274 goto error;
1275 }
1276
1277 /* check if it's possible to insert this new crtlist_entry */
1278 entry->node.key = store;
1279 inserted = ebpt_insert(&crtlist->entries, &entry->node);
1280 if (inserted != &entry->node) {
1281 memprintf(&err, "file already exists in this directory!");
1282 goto error;
1283 }
1284
1285 /* this is supposed to be a directory (EB_ROOT_UNIQUE), so no ssl_conf are allowed */
1286 if ((entry->ssl_conf || entry->filters) && eb_gettag(crtlist->entries.b[EB_RGHT])) {
1287 memprintf(&err, "this is a directory, SSL configuration and filters are not allowed");
1288 goto error;
1289 }
1290
Willy Tarreau2b718102021-04-21 07:32:39 +02001291 LIST_APPEND(&crtlist->ord_entries, &entry->by_crtlist);
William Lallemandc756bbd2020-05-13 17:23:59 +02001292 entry->crtlist = crtlist;
Willy Tarreau2b718102021-04-21 07:32:39 +02001293 LIST_APPEND(&store->crtlist_entry, &entry->by_ckch_store);
William Lallemandc756bbd2020-05-13 17:23:59 +02001294
1295 appctx->st2 = SETCERT_ST_INIT;
1296 appctx->ctx.cli.p0 = crtlist;
1297 appctx->ctx.cli.p1 = entry;
1298
1299 /* unlock is done in the release handler */
1300 return 0;
1301
1302error:
1303 crtlist_entry_free(entry);
1304 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1305 err = memprintf(&err, "Can't edit the crt-list: %s\n", err ? err : "");
1306 return cli_dynerr(appctx, err);
1307}
1308
1309/* Parse a "del ssl crt-list <crt-list> <certfile>" line. */
1310static int cli_parse_del_crtlist(char **args, char *payload, struct appctx *appctx, void *private)
1311{
1312 struct ckch_store *store;
1313 char *err = NULL;
1314 char *crtlist_path, *cert_path;
1315 struct ebmb_node *ebmb;
1316 struct ebpt_node *ebpt;
1317 struct crtlist *crtlist;
1318 struct crtlist_entry *entry = NULL;
1319 struct ckch_inst *inst, *inst_s;
1320 int linenum = 0;
1321 char *colons;
William Lallemand99cc2182020-06-25 15:19:51 +02001322 char *end;
Remi Tricot-Le Bretonbc2c3862021-03-26 10:47:50 +01001323 int error_message_dumped = 0;
William Lallemandc756bbd2020-05-13 17:23:59 +02001324
1325 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
1326 return 1;
1327
1328 if (!*args[3] || !*args[4])
1329 return cli_err(appctx, "'del ssl crtlist' expects a filename and a certificate name\n");
1330
1331 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
1332 return cli_err(appctx, "Can't delete!\nOperations on certificates are currently locked!\n");
1333
1334 crtlist_path = args[3];
1335 cert_path = args[4];
1336
1337 colons = strchr(cert_path, ':');
1338 if (colons) {
1339 char *endptr;
1340
1341 linenum = strtol(colons + 1, &endptr, 10);
1342 if (colons + 1 == endptr || *endptr != '\0') {
1343 memprintf(&err, "wrong line number after colons in '%s'!", cert_path);
1344 goto error;
1345 }
1346 *colons = '\0';
1347 }
William Lallemand99cc2182020-06-25 15:19:51 +02001348
1349 /* strip trailing slashes, including first one */
1350 for (end = crtlist_path + strlen(crtlist_path) - 1; end >= crtlist_path && *end == '/'; end--)
1351 *end = 0;
1352
William Lallemandc756bbd2020-05-13 17:23:59 +02001353 /* look for crtlist */
1354 ebmb = ebst_lookup(&crtlists_tree, crtlist_path);
1355 if (!ebmb) {
1356 memprintf(&err, "crt-list '%s' does not exist!", crtlist_path);
1357 goto error;
1358 }
1359 crtlist = ebmb_entry(ebmb, struct crtlist, node);
1360
1361 /* look for store */
1362 store = ckchs_lookup(cert_path);
1363 if (store == NULL) {
1364 memprintf(&err, "certificate '%s' does not exist!", cert_path);
1365 goto error;
1366 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001367 if (store->ckch == NULL || store->ckch->cert == NULL) {
1368 memprintf(&err, "certificate '%s' is empty!", cert_path);
1369 goto error;
1370 }
1371
1372 ebpt = ebpt_lookup(&crtlist->entries, store);
1373 if (!ebpt) {
1374 memprintf(&err, "certificate '%s' can't be found in crt-list '%s'!", cert_path, crtlist_path);
1375 goto error;
1376 }
1377
1378 /* list the line number of entries for errors in err, and select the right ebpt */
1379 for (; ebpt; ebpt = ebpt_next_dup(ebpt)) {
1380 struct crtlist_entry *tmp;
1381
1382 tmp = ebpt_entry(ebpt, struct crtlist_entry, node);
1383 memprintf(&err, "%s%s%d", err ? err : "", err ? ", " : "", tmp->linenum);
1384
1385 /* select the entry we wanted */
1386 if (linenum == 0 || tmp->linenum == linenum) {
1387 if (!entry)
1388 entry = tmp;
1389 }
1390 }
1391
1392 /* we didn't found the specified entry */
1393 if (!entry) {
1394 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);
1395 goto error;
1396 }
1397
1398 /* we didn't specified a line number but there were several entries */
1399 if (linenum == 0 && ebpt_next_dup(&entry->node)) {
1400 memprintf(&err, "found the certificate '%s' in several entries, please specify a line number preceded by colons (%s)!", cert_path, err ? err : NULL);
1401 goto error;
1402 }
1403
Remi Tricot-Le Bretonbc2c3862021-03-26 10:47:50 +01001404 /* Iterate over all the instances in order to see if any of them is a
1405 * default instance. If this is the case, the entry won't be suppressed. */
1406 list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_crtlist_entry) {
1407 if (inst->is_default && !inst->bind_conf->strict_sni) {
1408 if (!error_message_dumped) {
1409 memprintf(&err, "certificate '%s' cannot be deleted, it is used as default certificate by the following frontends:\n", cert_path);
1410 error_message_dumped = 1;
1411 }
1412 memprintf(&err, "%s\t- %s:%d\n", err, inst->bind_conf->file, inst->bind_conf->line);
1413 }
1414 }
1415 if (error_message_dumped)
1416 goto error;
1417
William Lallemandc756bbd2020-05-13 17:23:59 +02001418 /* upon error free the ckch_inst and everything inside */
1419
1420 ebpt_delete(&entry->node);
Willy Tarreau2b718102021-04-21 07:32:39 +02001421 LIST_DELETE(&entry->by_crtlist);
1422 LIST_DELETE(&entry->by_ckch_store);
William Lallemandc756bbd2020-05-13 17:23:59 +02001423
1424 list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_crtlist_entry) {
1425 struct sni_ctx *sni, *sni_s;
1426
1427 HA_RWLOCK_WRLOCK(SNI_LOCK, &inst->bind_conf->sni_lock);
1428 list_for_each_entry_safe(sni, sni_s, &inst->sni_ctx, by_ckch_inst) {
1429 ebmb_delete(&sni->name);
Willy Tarreau2b718102021-04-21 07:32:39 +02001430 LIST_DELETE(&sni->by_ckch_inst);
William Lallemandc756bbd2020-05-13 17:23:59 +02001431 SSL_CTX_free(sni->ctx);
1432 free(sni);
1433 }
1434 HA_RWLOCK_WRUNLOCK(SNI_LOCK, &inst->bind_conf->sni_lock);
Willy Tarreau2b718102021-04-21 07:32:39 +02001435 LIST_DELETE(&inst->by_ckchs);
William Lallemandc756bbd2020-05-13 17:23:59 +02001436 free(inst);
1437 }
1438
1439 crtlist_free_filters(entry->filters);
1440 ssl_sock_free_ssl_conf(entry->ssl_conf);
1441 free(entry->ssl_conf);
1442 free(entry);
1443
1444 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1445 err = memprintf(&err, "Entry '%s' deleted in crtlist '%s'!\n", cert_path, crtlist_path);
1446 return cli_dynmsg(appctx, LOG_NOTICE, err);
1447
1448error:
1449 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1450 err = memprintf(&err, "Can't delete the entry: %s\n", err ? err : "");
1451 return cli_dynerr(appctx, err);
1452}
1453
1454
William Lallemandee8530c2020-06-23 18:19:42 +02001455/* unlink and free all crt-list and crt-list entries */
1456void crtlist_deinit()
1457{
1458 struct eb_node *node, *next;
1459 struct crtlist *crtlist;
1460
1461 node = eb_first(&crtlists_tree);
1462 while (node) {
1463 next = eb_next(node);
1464 crtlist = ebmb_entry(node, struct crtlist, node);
1465 crtlist_free(crtlist);
1466 node = next;
1467 }
1468}
1469
William Lallemandc756bbd2020-05-13 17:23:59 +02001470
1471/* register cli keywords */
1472static struct cli_kw_list cli_kws = {{ },{
Willy Tarreaub205bfd2021-05-07 11:38:37 +02001473 { { "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 },
1474 { { "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 },
1475 { { "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 +02001476 { { NULL }, NULL, NULL, NULL } }
1477};
1478
1479INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws);
1480