blob: c9408294f3674dc315f837b6a5addde8887b49fe [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 Tarreau5e539c92020-06-04 20:45:39 +020031#include <haproxy/stream_interface.h>
Willy Tarreau48fbcae2020-06-03 18:09:46 +020032#include <haproxy/tools.h>
William Lallemand6e9556b2020-05-12 17:52:44 +020033
William Lallemand6e9556b2020-05-12 17:52:44 +020034
William Lallemand6e9556b2020-05-12 17:52:44 +020035/* release ssl bind conf */
36void ssl_sock_free_ssl_conf(struct ssl_bind_conf *conf)
37{
38 if (conf) {
39#if defined(OPENSSL_NPN_NEGOTIATED) && !defined(OPENSSL_NO_NEXTPROTONEG)
Willy Tarreau61cfdf42021-02-20 10:46:51 +010040 ha_free(&conf->npn_str);
William Lallemand6e9556b2020-05-12 17:52:44 +020041#endif
42#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
Willy Tarreau61cfdf42021-02-20 10:46:51 +010043 ha_free(&conf->alpn_str);
William Lallemand6e9556b2020-05-12 17:52:44 +020044#endif
Willy Tarreau61cfdf42021-02-20 10:46:51 +010045 ha_free(&conf->ca_file);
46 ha_free(&conf->ca_verify_file);
47 ha_free(&conf->crl_file);
48 ha_free(&conf->ciphers);
Ilya Shipitsinf34ed0b2020-11-21 14:37:34 +050049#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES
Willy Tarreau61cfdf42021-02-20 10:46:51 +010050 ha_free(&conf->ciphersuites);
William Lallemand6e9556b2020-05-12 17:52:44 +020051#endif
Willy Tarreau61cfdf42021-02-20 10:46:51 +010052 ha_free(&conf->curves);
53 ha_free(&conf->ecdhe);
William Lallemand6e9556b2020-05-12 17:52:44 +020054 }
55}
56
William Lallemand82f2d2f2020-09-10 19:06:43 +020057/*
58 * Allocate and copy a ssl_bind_conf structure
59 */
60struct ssl_bind_conf *crtlist_dup_ssl_conf(struct ssl_bind_conf *src)
61{
62 struct ssl_bind_conf *dst;
63
64 if (!src)
65 return NULL;
66
67 dst = calloc(1, sizeof(*dst));
68 if (!dst)
69 return NULL;
70
71#if defined(OPENSSL_NPN_NEGOTIATED) && !defined(OPENSSL_NO_NEXTPROTONEG)
72 if (src->npn_str) {
73 dst->npn_str = strdup(src->npn_str);
74 if (!dst->npn_str)
75 goto error;
76 }
77#endif
78#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
79 if (src->alpn_str) {
80 dst->alpn_str = strdup(src->alpn_str);
81 if (!dst->alpn_str)
82 goto error;
83 }
84#endif
85 if (src->ca_file) {
86 dst->ca_file = strdup(src->ca_file);
87 if (!dst->ca_file)
88 goto error;
89 }
90 if (src->ca_verify_file) {
91 dst->ca_verify_file = strdup(src->ca_verify_file);
92 if (!dst->ca_verify_file)
93 goto error;
94 }
95 if (src->crl_file) {
96 dst->crl_file = strdup(src->crl_file);
97 if (!dst->crl_file)
98 goto error;
99 }
100 if (src->ciphers) {
101 dst->ciphers = strdup(src->ciphers);
102 if (!dst->ciphers)
103 goto error;
104 }
Ilya Shipitsinf34ed0b2020-11-21 14:37:34 +0500105#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES
William Lallemand82f2d2f2020-09-10 19:06:43 +0200106 if (src->ciphersuites) {
107 dst->ciphersuites = strdup(src->ciphersuites);
108 if (!dst->ciphersuites)
109 goto error;
110 }
111#endif
112 if (src->curves) {
113 dst->curves = strdup(src->curves);
114 if (!dst->curves)
115 goto error;
116 }
117 if (src->ecdhe) {
118 dst->ecdhe = strdup(src->ecdhe);
119 if (!dst->ecdhe)
120 goto error;
121 }
122 return dst;
123
124error:
125 ssl_sock_free_ssl_conf(dst);
126 free(dst);
127
128 return NULL;
129}
William Lallemand6e9556b2020-05-12 17:52:44 +0200130
131/* free sni filters */
132void crtlist_free_filters(char **args)
133{
134 int i;
135
136 if (!args)
137 return;
138
139 for (i = 0; args[i]; i++)
140 free(args[i]);
141
142 free(args);
143}
144
145/* Alloc and duplicate a char ** array */
146char **crtlist_dup_filters(char **args, int fcount)
147{
148 char **dst;
149 int i;
150
151 if (fcount == 0)
152 return NULL;
153
154 dst = calloc(fcount + 1, sizeof(*dst));
155 if (!dst)
156 return NULL;
157
158 for (i = 0; i < fcount; i++) {
159 dst[i] = strdup(args[i]);
160 if (!dst[i])
161 goto error;
162 }
163 return dst;
164
165error:
166 crtlist_free_filters(dst);
167 return NULL;
168}
169
170/*
171 * Detach and free a crtlist_entry.
172 * Free the filters, the ssl_conf and call ckch_inst_free() for each ckch_inst
173 */
174void crtlist_entry_free(struct crtlist_entry *entry)
175{
176 struct ckch_inst *inst, *inst_s;
177
178 if (entry == NULL)
179 return;
180
181 ebpt_delete(&entry->node);
Willy Tarreau2b718102021-04-21 07:32:39 +0200182 LIST_DELETE(&entry->by_crtlist);
183 LIST_DELETE(&entry->by_ckch_store);
William Lallemand6e9556b2020-05-12 17:52:44 +0200184 crtlist_free_filters(entry->filters);
185 ssl_sock_free_ssl_conf(entry->ssl_conf);
186 free(entry->ssl_conf);
187 list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_crtlist_entry) {
188 ckch_inst_free(inst);
189 }
190 free(entry);
191}
William Lallemand5622c452020-09-10 19:08:49 +0200192/*
193 * Duplicate a crt_list entry and its content (ssl_conf, filters/fcount)
194 * Return a pointer to the new entry
195 */
196struct crtlist_entry *crtlist_entry_dup(struct crtlist_entry *src)
197{
198 struct crtlist_entry *entry;
199
200 if (src == NULL)
201 return NULL;
202
203 entry = crtlist_entry_new();
204 if (entry == NULL)
205 return NULL;
206
207 if (src->filters) {
208 entry->filters = crtlist_dup_filters(src->filters, src->fcount);
209 if (!entry->filters)
210 goto error;
211 }
212 entry->fcount = src->fcount;
213 if (src->ssl_conf) {
214 entry->ssl_conf = crtlist_dup_ssl_conf(src->ssl_conf);
215 if (!entry->ssl_conf)
216 goto error;
217 }
218 entry->crtlist = src->crtlist;
219
220 return entry;
221
222error:
223
224 crtlist_free_filters(entry->filters);
225 ssl_sock_free_ssl_conf(entry->ssl_conf);
226 free(entry->ssl_conf);
227 free(entry);
228
229 return NULL;
230}
William Lallemand6e9556b2020-05-12 17:52:44 +0200231
232/*
233 * Allocate and initialize a crtlist_entry
234 */
235struct crtlist_entry *crtlist_entry_new()
236{
237 struct crtlist_entry *entry;
238
239 entry = calloc(1, sizeof(*entry));
240 if (entry == NULL)
241 return NULL;
242
243 LIST_INIT(&entry->ckch_inst);
244
Willy Tarreau2b718102021-04-21 07:32:39 +0200245 /* initialize the nodes so we can LIST_DELETE in any cases */
William Lallemand6e9556b2020-05-12 17:52:44 +0200246 LIST_INIT(&entry->by_crtlist);
247 LIST_INIT(&entry->by_ckch_store);
248
249 return entry;
250}
251
252/* Free a crtlist, from the crt_entry to the content of the ssl_conf */
253void crtlist_free(struct crtlist *crtlist)
254{
255 struct crtlist_entry *entry, *s_entry;
William Lallemand6a3168a2020-06-23 11:43:35 +0200256 struct bind_conf_list *bind_conf_node;
William Lallemand6e9556b2020-05-12 17:52:44 +0200257
258 if (crtlist == NULL)
259 return;
260
William Lallemand6a3168a2020-06-23 11:43:35 +0200261 bind_conf_node = crtlist->bind_conf;
262 while (bind_conf_node) {
263 struct bind_conf_list *next = bind_conf_node->next;
264 free(bind_conf_node);
265 bind_conf_node = next;
266 }
267
William Lallemand6e9556b2020-05-12 17:52:44 +0200268 list_for_each_entry_safe(entry, s_entry, &crtlist->ord_entries, by_crtlist) {
269 crtlist_entry_free(entry);
270 }
271 ebmb_delete(&crtlist->node);
272 free(crtlist);
273}
274
275/* Alloc and initialize a struct crtlist
276 * <filename> is the key of the ebmb_node
277 * <unique> initialize the list of entries to be unique (1) or not (0)
278 */
279struct crtlist *crtlist_new(const char *filename, int unique)
280{
281 struct crtlist *newlist;
282
283 newlist = calloc(1, sizeof(*newlist) + strlen(filename) + 1);
284 if (newlist == NULL)
285 return NULL;
286
287 memcpy(newlist->node.key, filename, strlen(filename) + 1);
288 if (unique)
289 newlist->entries = EB_ROOT_UNIQUE;
290 else
291 newlist->entries = EB_ROOT;
292
293 LIST_INIT(&newlist->ord_entries);
294
295 return newlist;
296}
297
298/*
299 * Read a single crt-list line. /!\ alter the <line> string.
300 * Fill <crt_path> and <crtlist_entry>
301 * <crtlist_entry> must be alloc and free by the caller
302 * <crtlist_entry->ssl_conf> is alloc by the function
303 * <crtlist_entry->filters> is alloc by the function
304 * <crt_path> is a ptr in <line>
305 * Return an error code
306 */
Remi Tricot-Le Bretonfb00f312021-03-23 16:41:53 +0100307int 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 +0200308{
309 int cfgerr = 0;
310 int arg, newarg, cur_arg, i, ssl_b = 0, ssl_e = 0;
311 char *end;
312 char *args[MAX_CRT_ARGS + 1];
313 struct ssl_bind_conf *ssl_conf = NULL;
314
315 if (!line || !crt_path || !entry)
316 return ERR_ALERT | ERR_FATAL;
317
318 end = line + strlen(line);
319 if (end-line >= CRT_LINESIZE-1 && *(end-1) != '\n') {
320 /* Check if we reached the limit and the last char is not \n.
321 * Watch out for the last line without the terminating '\n'!
322 */
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200323 memprintf(err, "parsing [%s:%d]: line too long, limit is %d characters",
324 file, linenum, CRT_LINESIZE-1);
William Lallemand6e9556b2020-05-12 17:52:44 +0200325 cfgerr |= ERR_ALERT | ERR_FATAL;
326 goto error;
327 }
328 arg = 0;
329 newarg = 1;
330 while (*line) {
331 if (isspace((unsigned char)*line)) {
332 newarg = 1;
333 *line = 0;
334 } else if (*line == '[') {
335 if (ssl_b) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200336 memprintf(err, "parsing [%s:%d]: too many '['", file, linenum);
William Lallemand6e9556b2020-05-12 17:52:44 +0200337 cfgerr |= ERR_ALERT | ERR_FATAL;
338 goto error;
339 }
340 if (!arg) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200341 memprintf(err, "parsing [%s:%d]: file must start with a cert", file, linenum);
William Lallemand6e9556b2020-05-12 17:52:44 +0200342 cfgerr |= ERR_ALERT | ERR_FATAL;
343 goto error;
344 }
345 ssl_b = arg;
346 newarg = 1;
347 *line = 0;
348 } else if (*line == ']') {
349 if (ssl_e) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200350 memprintf(err, "parsing [%s:%d]: too many ']'", file, linenum);
William Lallemand6e9556b2020-05-12 17:52:44 +0200351 cfgerr |= ERR_ALERT | ERR_FATAL;
352 goto error;
353 }
354 if (!ssl_b) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200355 memprintf(err, "parsing [%s:%d]: missing '['", file, linenum);
William Lallemand6e9556b2020-05-12 17:52:44 +0200356 cfgerr |= ERR_ALERT | ERR_FATAL;
357 goto error;
358 }
359 ssl_e = arg;
360 newarg = 1;
361 *line = 0;
362 } else if (newarg) {
363 if (arg == MAX_CRT_ARGS) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200364 memprintf(err, "parsing [%s:%d]: too many args ", file, linenum);
William Lallemand6e9556b2020-05-12 17:52:44 +0200365 cfgerr |= ERR_ALERT | ERR_FATAL;
366 goto error;
367 }
368 newarg = 0;
369 args[arg++] = line;
370 }
371 line++;
372 }
373 args[arg++] = line;
374
375 /* empty line */
376 if (!*args[0]) {
377 cfgerr |= ERR_NONE;
378 goto error;
379 }
380
381 *crt_path = args[0];
382
383 if (ssl_b) {
384 ssl_conf = calloc(1, sizeof *ssl_conf);
385 if (!ssl_conf) {
386 memprintf(err, "not enough memory!");
387 cfgerr |= ERR_ALERT | ERR_FATAL;
388 goto error;
389 }
390 }
Remi Tricot-Le Bretonfb00f312021-03-23 16:41:53 +0100391
William Lallemand6e9556b2020-05-12 17:52:44 +0200392 cur_arg = ssl_b ? ssl_b : 1;
393 while (cur_arg < ssl_e) {
394 newarg = 0;
395 for (i = 0; ssl_bind_kws[i].kw != NULL; i++) {
396 if (strcmp(ssl_bind_kws[i].kw, args[cur_arg]) == 0) {
397 newarg = 1;
Remi Tricot-Le Bretonfb00f312021-03-23 16:41:53 +0100398 cfgerr |= ssl_bind_kws[i].parse(args, cur_arg, NULL, ssl_conf, from_cli, err);
William Lallemand6e9556b2020-05-12 17:52:44 +0200399 if (cur_arg + 1 + ssl_bind_kws[i].skip > ssl_e) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200400 memprintf(err, "parsing [%s:%d]: ssl args out of '[]' for %s",
401 file, linenum, args[cur_arg]);
William Lallemand6e9556b2020-05-12 17:52:44 +0200402 cfgerr |= ERR_ALERT | ERR_FATAL;
403 goto error;
404 }
405 cur_arg += 1 + ssl_bind_kws[i].skip;
406 break;
407 }
408 }
409 if (!cfgerr && !newarg) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200410 memprintf(err, "parsing [%s:%d]: unknown ssl keyword %s",
411 file, linenum, args[cur_arg]);
William Lallemand6e9556b2020-05-12 17:52:44 +0200412 cfgerr |= ERR_ALERT | ERR_FATAL;
413 goto error;
414 }
415 }
416 entry->linenum = linenum;
417 entry->ssl_conf = ssl_conf;
418 entry->filters = crtlist_dup_filters(&args[cur_arg], arg - cur_arg - 1);
419 entry->fcount = arg - cur_arg - 1;
420
421 return cfgerr;
422
423error:
424 crtlist_free_filters(entry->filters);
425 entry->filters = NULL;
426 ssl_sock_free_ssl_conf(entry->ssl_conf);
Willy Tarreau61cfdf42021-02-20 10:46:51 +0100427 ha_free(&entry->ssl_conf);
William Lallemand6e9556b2020-05-12 17:52:44 +0200428 return cfgerr;
429}
430
431
432
433/* This function parse a crt-list file and store it in a struct crtlist, each line is a crtlist_entry structure
434 * Fill the <crtlist> argument with a pointer to a new crtlist struct
435 *
436 * This function tries to open and store certificate files.
437 */
438int crtlist_parse_file(char *file, struct bind_conf *bind_conf, struct proxy *curproxy, struct crtlist **crtlist, char **err)
439{
440 struct crtlist *newlist;
441 struct crtlist_entry *entry = NULL;
442 char thisline[CRT_LINESIZE];
William Lallemand6e9556b2020-05-12 17:52:44 +0200443 FILE *f;
444 struct stat buf;
445 int linenum = 0;
446 int cfgerr = 0;
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200447 int missing_lf = -1;
William Lallemand6e9556b2020-05-12 17:52:44 +0200448
449 if ((f = fopen(file, "r")) == NULL) {
450 memprintf(err, "cannot open file '%s' : %s", file, strerror(errno));
451 return ERR_ALERT | ERR_FATAL;
452 }
453
454 newlist = crtlist_new(file, 0);
455 if (newlist == NULL) {
456 memprintf(err, "Not enough memory!");
457 cfgerr |= ERR_ALERT | ERR_FATAL;
458 goto error;
459 }
460
461 while (fgets(thisline, sizeof(thisline), f) != NULL) {
462 char *end;
463 char *line = thisline;
464 char *crt_path;
William Lallemand86c2dd62020-11-20 14:23:38 +0100465 char path[MAXPATHLEN+1];
William Lallemand6e9556b2020-05-12 17:52:44 +0200466 struct ckch_store *ckchs;
William Lallemand77e1c6f2020-11-20 18:26:09 +0100467 int found = 0;
William Lallemand6e9556b2020-05-12 17:52:44 +0200468
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200469 if (missing_lf != -1) {
470 memprintf(err, "parsing [%s:%d]: Stray NUL character at position %d.\n",
471 file, linenum, (missing_lf + 1));
472 cfgerr |= ERR_ALERT | ERR_FATAL;
473 missing_lf = -1;
474 break;
475 }
476
William Lallemand6e9556b2020-05-12 17:52:44 +0200477 linenum++;
478 end = line + strlen(line);
479 if (end-line == sizeof(thisline)-1 && *(end-1) != '\n') {
480 /* Check if we reached the limit and the last char is not \n.
481 * Watch out for the last line without the terminating '\n'!
482 */
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200483 memprintf(err, "parsing [%s:%d]: line too long, limit is %d characters",
484 file, linenum, (int)sizeof(thisline)-1);
William Lallemand6e9556b2020-05-12 17:52:44 +0200485 cfgerr |= ERR_ALERT | ERR_FATAL;
486 break;
487 }
488
489 if (*line == '#' || *line == '\n' || *line == '\r')
490 continue;
491
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200492 if (end > line && *(end-1) == '\n') {
493 /* kill trailing LF */
494 *(end - 1) = 0;
495 }
496 else {
497 /* mark this line as truncated */
498 missing_lf = end - line;
499 }
500
William Lallemand6e9556b2020-05-12 17:52:44 +0200501 entry = crtlist_entry_new();
502 if (entry == NULL) {
503 memprintf(err, "Not enough memory!");
504 cfgerr |= ERR_ALERT | ERR_FATAL;
505 goto error;
506 }
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200507
Remi Tricot-Le Bretonfb00f312021-03-23 16:41:53 +0100508 cfgerr |= crtlist_parse_line(thisline, &crt_path, entry, file, linenum, 0, err);
William Lallemand20b0fed2020-09-28 15:45:16 +0200509 if (cfgerr & ERR_CODE)
William Lallemand6e9556b2020-05-12 17:52:44 +0200510 goto error;
511
512 /* empty line */
513 if (!crt_path || !*crt_path) {
514 crtlist_entry_free(entry);
515 entry = NULL;
516 continue;
517 }
518
519 if (*crt_path != '/' && global_ssl.crt_base) {
520 if ((strlen(global_ssl.crt_base) + 1 + strlen(crt_path)) > MAXPATHLEN) {
Tim Duesterhus6d07fae2020-09-29 18:00:27 +0200521 memprintf(err, "parsing [%s:%d]: '%s' : path too long",
522 file, linenum, crt_path);
William Lallemand6e9556b2020-05-12 17:52:44 +0200523 cfgerr |= ERR_ALERT | ERR_FATAL;
524 goto error;
525 }
526 snprintf(path, sizeof(path), "%s/%s", global_ssl.crt_base, crt_path);
527 crt_path = path;
528 }
529
530 /* Look for a ckch_store or create one */
531 ckchs = ckchs_lookup(crt_path);
532 if (ckchs == NULL) {
William Lallemand47da8212020-09-10 19:13:27 +0200533 if (stat(crt_path, &buf) == 0) {
William Lallemand77e1c6f2020-11-20 18:26:09 +0100534 found++;
William Lallemand47da8212020-09-10 19:13:27 +0200535
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200536 ckchs = ckchs_load_cert_file(crt_path, err);
William Lallemand47da8212020-09-10 19:13:27 +0200537 if (ckchs == NULL) {
538 cfgerr |= ERR_ALERT | ERR_FATAL;
539 goto error;
540 }
541
542 entry->node.key = ckchs;
543 entry->crtlist = newlist;
544 ebpt_insert(&newlist->entries, &entry->node);
Willy Tarreau2b718102021-04-21 07:32:39 +0200545 LIST_APPEND(&newlist->ord_entries, &entry->by_crtlist);
546 LIST_APPEND(&ckchs->crtlist_entry, &entry->by_ckch_store);
William Lallemand6e9556b2020-05-12 17:52:44 +0200547
William Lallemand73404572020-11-20 18:23:40 +0100548 } else if (global_ssl.extra_files & SSL_GF_BUNDLE) {
William Lallemand47da8212020-09-10 19:13:27 +0200549 /* If we didn't find the file, this could be a
William Lallemand51f784b2020-10-02 18:08:18 +0200550 bundle, since 2.3 we don't support multiple
551 certificate in the same OpenSSL store, so we
552 emulate it by loading each file separately. To
553 do so we need to duplicate the entry in the
554 crt-list because it becomes independent */
William Lallemand47da8212020-09-10 19:13:27 +0200555 char fp[MAXPATHLEN+1] = {0};
556 int n = 0;
557 struct crtlist_entry *entry_dup = entry; /* use the previous created entry */
558 for (n = 0; n < SSL_SOCK_NUM_KEYTYPES; n++) {
559 struct stat buf;
560 int ret;
561
William Lallemand86c2dd62020-11-20 14:23:38 +0100562 ret = snprintf(fp, sizeof(fp), "%s.%s", crt_path, SSL_SOCK_KEYTYPE_NAMES[n]);
William Lallemand47da8212020-09-10 19:13:27 +0200563 if (ret > sizeof(fp))
564 continue;
565
566 ckchs = ckchs_lookup(fp);
William Lallemand77e1c6f2020-11-20 18:26:09 +0100567 if (!ckchs) {
568 if (stat(fp, &buf) == 0) {
569 ckchs = ckchs_load_cert_file(fp, err);
570 if (!ckchs) {
William Lallemand47da8212020-09-10 19:13:27 +0200571 cfgerr |= ERR_ALERT | ERR_FATAL;
572 goto error;
573 }
William Lallemand77e1c6f2020-11-20 18:26:09 +0100574 } else {
575 continue; /* didn't find this extension, skip */
576 }
577 }
578 found++;
579 linenum++; /* we duplicate the line for this entry in the bundle */
580 if (!entry_dup) { /* if the entry was used, duplicate one */
581 linenum++;
582 entry_dup = crtlist_entry_dup(entry);
583 if (!entry_dup) {
584 cfgerr |= ERR_ALERT | ERR_FATAL;
585 goto error;
William Lallemand47da8212020-09-10 19:13:27 +0200586 }
William Lallemand77e1c6f2020-11-20 18:26:09 +0100587 entry_dup->linenum = linenum;
588 }
William Lallemand47da8212020-09-10 19:13:27 +0200589
William Lallemand77e1c6f2020-11-20 18:26:09 +0100590 entry_dup->node.key = ckchs;
591 entry_dup->crtlist = newlist;
592 ebpt_insert(&newlist->entries, &entry_dup->node);
Willy Tarreau2b718102021-04-21 07:32:39 +0200593 LIST_APPEND(&newlist->ord_entries, &entry_dup->by_crtlist);
594 LIST_APPEND(&ckchs->crtlist_entry, &entry_dup->by_ckch_store);
William Lallemand47da8212020-09-10 19:13:27 +0200595
William Lallemand77e1c6f2020-11-20 18:26:09 +0100596 entry_dup = NULL; /* the entry was used, we need a new one next round */
William Lallemand47da8212020-09-10 19:13:27 +0200597 }
William Lallemandb7fdfdf2020-12-04 15:45:02 +0100598#if HA_OPENSSL_VERSION_NUMBER < 0x10101000L
599 if (found) {
600 memprintf(err, "%sCan't load '%s'. Loading a multi certificates bundle requires OpenSSL >= 1.1.1\n",
601 err && *err ? *err : "", crt_path);
602 cfgerr |= ERR_ALERT | ERR_FATAL;
603 }
604#endif
William Lallemand47da8212020-09-10 19:13:27 +0200605 }
William Lallemand77e1c6f2020-11-20 18:26:09 +0100606 if (!found) {
607 memprintf(err, "%sunable to stat SSL certificate from file '%s' : %s.\n",
608 err && *err ? *err : "", crt_path, strerror(errno));
609 cfgerr |= ERR_ALERT | ERR_FATAL;
610 }
611
William Lallemand50c03aa2020-11-06 16:24:07 +0100612 } else {
613 entry->node.key = ckchs;
614 entry->crtlist = newlist;
615 ebpt_insert(&newlist->entries, &entry->node);
Willy Tarreau2b718102021-04-21 07:32:39 +0200616 LIST_APPEND(&newlist->ord_entries, &entry->by_crtlist);
617 LIST_APPEND(&ckchs->crtlist_entry, &entry->by_ckch_store);
William Lallemand77e1c6f2020-11-20 18:26:09 +0100618 found++;
William Lallemand47da8212020-09-10 19:13:27 +0200619 }
William Lallemand6e9556b2020-05-12 17:52:44 +0200620 entry = NULL;
621 }
Tim Duesterhusb9f6acc2020-09-29 18:00:28 +0200622
623 if (missing_lf != -1) {
624 memprintf(err, "parsing [%s:%d]: Missing LF on last line, file might have been truncated at position %d.\n",
625 file, linenum, (missing_lf + 1));
626 cfgerr |= ERR_ALERT | ERR_FATAL;
627 }
628
William Lallemand6e9556b2020-05-12 17:52:44 +0200629 if (cfgerr & ERR_CODE)
630 goto error;
631
632 newlist->linecount = linenum;
633
634 fclose(f);
635 *crtlist = newlist;
636
637 return cfgerr;
638error:
639 crtlist_entry_free(entry);
640
641 fclose(f);
642 crtlist_free(newlist);
643 return cfgerr;
644}
645
646/* This function reads a directory and stores it in a struct crtlist, each file is a crtlist_entry structure
647 * Fill the <crtlist> argument with a pointer to a new crtlist struct
648 *
649 * This function tries to open and store certificate files.
650 */
651int crtlist_load_cert_dir(char *path, struct bind_conf *bind_conf, struct crtlist **crtlist, char **err)
652{
653 struct crtlist *dir;
654 struct dirent **de_list;
655 int i, n;
656 struct stat buf;
657 char *end;
658 char fp[MAXPATHLEN+1];
659 int cfgerr = 0;
660 struct ckch_store *ckchs;
William Lallemand6e9556b2020-05-12 17:52:44 +0200661
662 dir = crtlist_new(path, 1);
663 if (dir == NULL) {
664 memprintf(err, "not enough memory");
665 return ERR_ALERT | ERR_FATAL;
666 }
667
668 n = scandir(path, &de_list, 0, alphasort);
669 if (n < 0) {
670 memprintf(err, "%sunable to scan directory '%s' : %s.\n",
671 err && *err ? *err : "", path, strerror(errno));
672 cfgerr |= ERR_ALERT | ERR_FATAL;
673 }
674 else {
675 for (i = 0; i < n; i++) {
676 struct crtlist_entry *entry;
677 struct dirent *de = de_list[i];
678
679 end = strrchr(de->d_name, '.');
Tim Duesterhuse5ff1412021-01-02 22:31:53 +0100680 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 +0200681 goto ignore_entry;
682
683 snprintf(fp, sizeof(fp), "%s/%s", path, de->d_name);
684 if (stat(fp, &buf) != 0) {
685 memprintf(err, "%sunable to stat SSL certificate from file '%s' : %s.\n",
686 err && *err ? *err : "", fp, strerror(errno));
687 cfgerr |= ERR_ALERT | ERR_FATAL;
688 goto ignore_entry;
689 }
690 if (!S_ISREG(buf.st_mode))
691 goto ignore_entry;
692
693 entry = crtlist_entry_new();
694 if (entry == NULL) {
695 memprintf(err, "not enough memory '%s'", fp);
696 cfgerr |= ERR_ALERT | ERR_FATAL;
697 goto ignore_entry;
698 }
699
William Lallemand6e9556b2020-05-12 17:52:44 +0200700 ckchs = ckchs_lookup(fp);
701 if (ckchs == NULL)
William Lallemandbd8e6ed2020-09-16 16:08:08 +0200702 ckchs = ckchs_load_cert_file(fp, err);
William Lallemand6e9556b2020-05-12 17:52:44 +0200703 if (ckchs == NULL) {
704 free(de);
705 free(entry);
706 cfgerr |= ERR_ALERT | ERR_FATAL;
707 goto end;
708 }
709 entry->node.key = ckchs;
710 entry->crtlist = dir;
Willy Tarreau2b718102021-04-21 07:32:39 +0200711 LIST_APPEND(&ckchs->crtlist_entry, &entry->by_ckch_store);
712 LIST_APPEND(&dir->ord_entries, &entry->by_crtlist);
William Lallemand6e9556b2020-05-12 17:52:44 +0200713 ebpt_insert(&dir->entries, &entry->node);
714
715ignore_entry:
716 free(de);
717 }
718end:
719 free(de_list);
720 }
721
722 if (cfgerr & ERR_CODE) {
723 /* free the dir and entries on error */
724 crtlist_free(dir);
725 } else {
726 *crtlist = dir;
727 }
728 return cfgerr;
729
730}
731
William Lallemandc756bbd2020-05-13 17:23:59 +0200732/*
733 * Take an ssl_bind_conf structure and append the configuration line used to
734 * create it in the buffer
735 */
736static void dump_crtlist_sslconf(struct buffer *buf, const struct ssl_bind_conf *conf)
737{
738 int space = 0;
739
740 if (conf == NULL)
741 return;
742
743 chunk_appendf(buf, " [");
744#ifdef OPENSSL_NPN_NEGOTIATED
745 if (conf->npn_str) {
746 int len = conf->npn_len;
747 char *ptr = conf->npn_str;
748 int comma = 0;
749
750 if (space) chunk_appendf(buf, " ");
751 chunk_appendf(buf, "npn ");
752 while (len) {
753 unsigned short size;
754
755 size = *ptr;
756 ptr++;
757 if (comma)
758 chunk_memcat(buf, ",", 1);
759 chunk_memcat(buf, ptr, size);
760 ptr += size;
761 len -= size + 1;
762 comma = 1;
763 }
764 chunk_memcat(buf, "", 1); /* finish with a \0 */
765 space++;
766 }
767#endif
768#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
769 if (conf->alpn_str) {
770 int len = conf->alpn_len;
771 char *ptr = conf->alpn_str;
772 int comma = 0;
773
774 if (space) chunk_appendf(buf, " ");
775 chunk_appendf(buf, "alpn ");
776 while (len) {
777 unsigned short size;
778
779 size = *ptr;
780 ptr++;
781 if (comma)
782 chunk_memcat(buf, ",", 1);
783 chunk_memcat(buf, ptr, size);
784 ptr += size;
785 len -= size + 1;
786 comma = 1;
787 }
788 chunk_memcat(buf, "", 1); /* finish with a \0 */
789 space++;
790 }
791#endif
792 /* verify */
793 {
794 if (conf->verify == SSL_SOCK_VERIFY_NONE) {
795 if (space) chunk_appendf(buf, " ");
796 chunk_appendf(buf, "verify none");
797 space++;
798 } else if (conf->verify == SSL_SOCK_VERIFY_OPTIONAL) {
799 if (space) chunk_appendf(buf, " ");
800 chunk_appendf(buf, "verify optional");
801 space++;
802 } else if (conf->verify == SSL_SOCK_VERIFY_REQUIRED) {
803 if (space) chunk_appendf(buf, " ");
804 chunk_appendf(buf, "verify required");
805 space++;
806 }
807 }
808
809 if (conf->no_ca_names) {
810 if (space) chunk_appendf(buf, " ");
811 chunk_appendf(buf, "no-ca-names");
812 space++;
813 }
814
815 if (conf->early_data) {
816 if (space) chunk_appendf(buf, " ");
817 chunk_appendf(buf, "allow-0rtt");
818 space++;
819 }
820 if (conf->ca_file) {
821 if (space) chunk_appendf(buf, " ");
822 chunk_appendf(buf, "ca-file %s", conf->ca_file);
823 space++;
824 }
825 if (conf->crl_file) {
826 if (space) chunk_appendf(buf, " ");
827 chunk_appendf(buf, "crl-file %s", conf->crl_file);
828 space++;
829 }
830 if (conf->ciphers) {
831 if (space) chunk_appendf(buf, " ");
832 chunk_appendf(buf, "ciphers %s", conf->ciphers);
833 space++;
834 }
Ilya Shipitsinf34ed0b2020-11-21 14:37:34 +0500835#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES
William Lallemandc756bbd2020-05-13 17:23:59 +0200836 if (conf->ciphersuites) {
837 if (space) chunk_appendf(buf, " ");
838 chunk_appendf(buf, "ciphersuites %s", conf->ciphersuites);
839 space++;
840 }
841#endif
842 if (conf->curves) {
843 if (space) chunk_appendf(buf, " ");
844 chunk_appendf(buf, "curves %s", conf->curves);
845 space++;
846 }
847 if (conf->ecdhe) {
848 if (space) chunk_appendf(buf, " ");
849 chunk_appendf(buf, "ecdhe %s", conf->ecdhe);
850 space++;
851 }
852
853 /* the crt-lists only support ssl-min-ver and ssl-max-ver */
William Lallemand8177ad92020-05-20 16:49:02 +0200854 if (conf->ssl_methods_cfg.min) {
William Lallemandc756bbd2020-05-13 17:23:59 +0200855 if (space) chunk_appendf(buf, " ");
William Lallemand8177ad92020-05-20 16:49:02 +0200856 chunk_appendf(buf, "ssl-min-ver %s", methodVersions[conf->ssl_methods_cfg.min].name);
William Lallemandc756bbd2020-05-13 17:23:59 +0200857 space++;
858 }
859
William Lallemand8177ad92020-05-20 16:49:02 +0200860 if (conf->ssl_methods_cfg.max) {
William Lallemandc756bbd2020-05-13 17:23:59 +0200861 if (space) chunk_appendf(buf, " ");
William Lallemand8177ad92020-05-20 16:49:02 +0200862 chunk_appendf(buf, "ssl-max-ver %s", methodVersions[conf->ssl_methods_cfg.max].name);
William Lallemandc756bbd2020-05-13 17:23:59 +0200863 space++;
864 }
865
866 chunk_appendf(buf, "]");
867
868 return;
869}
870
871/* dump a list of filters */
872static void dump_crtlist_filters(struct buffer *buf, struct crtlist_entry *entry)
873{
874 int i;
875
876 if (!entry->fcount)
877 return;
878
879 for (i = 0; i < entry->fcount; i++) {
880 chunk_appendf(buf, " %s", entry->filters[i]);
881 }
882 return;
883}
884
885/************************** CLI functions ****************************/
886
887
888/* CLI IO handler for '(show|dump) ssl crt-list' */
889static int cli_io_handler_dump_crtlist(struct appctx *appctx)
890{
891 struct buffer *trash = alloc_trash_chunk();
Christopher Faulet908628c2022-03-25 16:43:49 +0100892 struct conn_stream *cs = appctx->owner;
William Lallemandc756bbd2020-05-13 17:23:59 +0200893 struct ebmb_node *lnode;
894
895 if (trash == NULL)
896 return 1;
897
898 /* dump the list of crt-lists */
899 lnode = appctx->ctx.cli.p1;
900 if (lnode == NULL)
901 lnode = ebmb_first(&crtlists_tree);
902 while (lnode) {
903 chunk_appendf(trash, "%s\n", lnode->key);
Christopher Faulet908628c2022-03-25 16:43:49 +0100904 if (ci_putchk(cs_ic(cs), trash) == -1) {
Christopher Fauleta0bdec32022-04-04 07:51:21 +0200905 cs_rx_room_blk(cs);
William Lallemandc756bbd2020-05-13 17:23:59 +0200906 goto yield;
907 }
908 lnode = ebmb_next(lnode);
909 }
910 free_trash_chunk(trash);
911 return 1;
912yield:
913 appctx->ctx.cli.p1 = lnode;
914 free_trash_chunk(trash);
915 return 0;
916}
917
918/* CLI IO handler for '(show|dump) ssl crt-list <filename>' */
919static int cli_io_handler_dump_crtlist_entries(struct appctx *appctx)
920{
921 struct buffer *trash = alloc_trash_chunk();
922 struct crtlist *crtlist;
Christopher Faulet908628c2022-03-25 16:43:49 +0100923 struct conn_stream *cs = appctx->owner;
William Lallemandc756bbd2020-05-13 17:23:59 +0200924 struct crtlist_entry *entry;
925
926 if (trash == NULL)
927 return 1;
928
929 crtlist = ebmb_entry(appctx->ctx.cli.p0, struct crtlist, node);
930
931 entry = appctx->ctx.cli.p1;
932 if (entry == NULL) {
933 entry = LIST_ELEM((crtlist->ord_entries).n, typeof(entry), by_crtlist);
934 chunk_appendf(trash, "# %s\n", crtlist->node.key);
Christopher Faulet908628c2022-03-25 16:43:49 +0100935 if (ci_putchk(cs_ic(cs), trash) == -1) {
Christopher Fauleta0bdec32022-04-04 07:51:21 +0200936 cs_rx_room_blk(cs);
William Lallemandc756bbd2020-05-13 17:23:59 +0200937 goto yield;
938 }
939 }
940
941 list_for_each_entry_from(entry, &crtlist->ord_entries, by_crtlist) {
942 struct ckch_store *store;
943 const char *filename;
944
945 store = entry->node.key;
946 filename = store->path;
947 chunk_appendf(trash, "%s", filename);
948 if (appctx->ctx.cli.i0 == 's') /* show */
949 chunk_appendf(trash, ":%d", entry->linenum);
950 dump_crtlist_sslconf(trash, entry->ssl_conf);
951 dump_crtlist_filters(trash, entry);
952 chunk_appendf(trash, "\n");
953
Christopher Faulet908628c2022-03-25 16:43:49 +0100954 if (ci_putchk(cs_ic(cs), trash) == -1) {
Christopher Fauleta0bdec32022-04-04 07:51:21 +0200955 cs_rx_room_blk(cs);
William Lallemandc756bbd2020-05-13 17:23:59 +0200956 goto yield;
957 }
958 }
959 free_trash_chunk(trash);
960 return 1;
961yield:
962 appctx->ctx.cli.p1 = entry;
963 free_trash_chunk(trash);
964 return 0;
965}
966
967/* CLI argument parser for '(show|dump) ssl crt-list' */
968static int cli_parse_dump_crtlist(char **args, char *payload, struct appctx *appctx, void *private)
969{
970 struct ebmb_node *lnode;
971 char *filename = NULL;
972 int mode;
William Lallemand99cc2182020-06-25 15:19:51 +0200973 char *end;
William Lallemandc756bbd2020-05-13 17:23:59 +0200974
975 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
976 return 1;
977
978 appctx->ctx.cli.p0 = NULL;
979 appctx->ctx.cli.p1 = NULL;
980
Tim Duesterhuse5ff1412021-01-02 22:31:53 +0100981 if (*args[3] && strcmp(args[3], "-n") == 0) {
William Lallemandc756bbd2020-05-13 17:23:59 +0200982 mode = 's';
983 filename = args[4];
984 } else {
985 mode = 'd';
986 filename = args[3];
987 }
988
989 if (mode == 's' && !*args[4])
990 return cli_err(appctx, "'show ssl crt-list -n' expects a filename or a directory\n");
991
992 if (filename && *filename) {
William Lallemand99cc2182020-06-25 15:19:51 +0200993
994
995 /* strip trailing slashes, including first one */
996 for (end = filename + strlen(filename) - 1; end >= filename && *end == '/'; end--)
997 *end = 0;
998
William Lallemandc756bbd2020-05-13 17:23:59 +0200999 lnode = ebst_lookup(&crtlists_tree, filename);
1000 if (lnode == NULL)
1001 return cli_err(appctx, "didn't find the specified filename\n");
1002
1003 appctx->ctx.cli.p0 = lnode;
1004 appctx->io_handler = cli_io_handler_dump_crtlist_entries;
1005 }
1006 appctx->ctx.cli.i0 = mode;
1007
1008 return 0;
1009}
1010
1011/* release function of the "add ssl crt-list' command, free things and unlock
1012 the spinlock */
1013static void cli_release_add_crtlist(struct appctx *appctx)
1014{
1015 struct crtlist_entry *entry = appctx->ctx.cli.p1;
1016
1017 if (appctx->st2 != SETCERT_ST_FIN) {
1018 struct ckch_inst *inst, *inst_s;
1019 /* upon error free the ckch_inst and everything inside */
1020 ebpt_delete(&entry->node);
Willy Tarreau2b718102021-04-21 07:32:39 +02001021 LIST_DELETE(&entry->by_crtlist);
1022 LIST_DELETE(&entry->by_ckch_store);
William Lallemandc756bbd2020-05-13 17:23:59 +02001023
1024 list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_ckchs) {
1025 ckch_inst_free(inst);
1026 }
1027 crtlist_free_filters(entry->filters);
1028 ssl_sock_free_ssl_conf(entry->ssl_conf);
1029 free(entry->ssl_conf);
1030 free(entry);
1031 }
1032
1033 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1034}
1035
1036
1037/* IO Handler for the "add ssl crt-list" command It adds a new entry in the
1038 * crt-list and generates the ckch_insts for each bind_conf that uses this crt-list
1039 *
1040 * The logic is the same as the "commit ssl cert" command but without the
1041 * freeing of the old structures, because there are none.
1042 */
1043static int cli_io_handler_add_crtlist(struct appctx *appctx)
1044{
1045 struct bind_conf_list *bind_conf_node;
Christopher Faulet908628c2022-03-25 16:43:49 +01001046 struct conn_stream *cs = appctx->owner;
William Lallemandc756bbd2020-05-13 17:23:59 +02001047 struct crtlist *crtlist = appctx->ctx.cli.p0;
1048 struct crtlist_entry *entry = appctx->ctx.cli.p1;
1049 struct ckch_store *store = entry->node.key;
1050 struct buffer *trash = alloc_trash_chunk();
1051 struct ckch_inst *new_inst;
1052 char *err = NULL;
1053 int i = 0;
1054 int errcode = 0;
1055
1056 if (trash == NULL)
1057 goto error;
1058
1059 /* for each bind_conf which use the crt-list, a new ckch_inst must be
1060 * created.
1061 */
Christopher Faulet908628c2022-03-25 16:43:49 +01001062 if (unlikely(cs_ic(cs)->flags & (CF_WRITE_ERROR|CF_SHUTW)))
William Lallemandc756bbd2020-05-13 17:23:59 +02001063 goto error;
1064
1065 while (1) {
1066 switch (appctx->st2) {
1067 case SETCERT_ST_INIT:
1068 /* This state just print the update message */
1069 chunk_printf(trash, "Inserting certificate '%s' in crt-list '%s'", store->path, crtlist->node.key);
Christopher Faulet908628c2022-03-25 16:43:49 +01001070 if (ci_putchk(cs_ic(cs), trash) == -1) {
Christopher Fauleta0bdec32022-04-04 07:51:21 +02001071 cs_rx_room_blk(cs);
William Lallemandc756bbd2020-05-13 17:23:59 +02001072 goto yield;
1073 }
1074 appctx->st2 = SETCERT_ST_GEN;
1075 /* fallthrough */
1076 case SETCERT_ST_GEN:
1077 bind_conf_node = appctx->ctx.cli.p2; /* get the previous ptr from the yield */
1078 if (bind_conf_node == NULL)
1079 bind_conf_node = crtlist->bind_conf;
1080 for (; bind_conf_node; bind_conf_node = bind_conf_node->next) {
1081 struct bind_conf *bind_conf = bind_conf_node->bind_conf;
1082 struct sni_ctx *sni;
1083
1084 /* yield every 10 generations */
1085 if (i > 10) {
1086 appctx->ctx.cli.p2 = bind_conf_node;
1087 goto yield;
1088 }
1089
1090 /* we don't support multi-cert bundles, only simple ones */
1091 errcode |= ckch_inst_new_load_store(store->path, store, bind_conf, entry->ssl_conf, entry->filters, entry->fcount, &new_inst, &err);
1092 if (errcode & ERR_CODE)
1093 goto error;
1094
1095 /* we need to initialize the SSL_CTX generated */
1096 /* this iterate on the newly generated SNIs in the new instance to prepare their SSL_CTX */
1097 list_for_each_entry(sni, &new_inst->sni_ctx, by_ckch_inst) {
1098 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 +01001099 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 +02001100 if (errcode & ERR_CODE)
1101 goto error;
1102 }
1103 }
1104 /* display one dot for each new instance */
1105 chunk_appendf(trash, ".");
1106 i++;
Willy Tarreau2b718102021-04-21 07:32:39 +02001107 LIST_APPEND(&store->ckch_inst, &new_inst->by_ckchs);
1108 LIST_APPEND(&entry->ckch_inst, &new_inst->by_crtlist_entry);
William Lallemand9ab8f8d2020-06-24 01:00:52 +02001109 new_inst->crtlist_entry = entry;
William Lallemandc756bbd2020-05-13 17:23:59 +02001110 }
1111 appctx->st2 = SETCERT_ST_INSERT;
1112 /* fallthrough */
1113 case SETCERT_ST_INSERT:
1114 /* insert SNIs in bind_conf */
1115 list_for_each_entry(new_inst, &store->ckch_inst, by_ckchs) {
1116 HA_RWLOCK_WRLOCK(SNI_LOCK, &new_inst->bind_conf->sni_lock);
1117 ssl_sock_load_cert_sni(new_inst, new_inst->bind_conf);
1118 HA_RWLOCK_WRUNLOCK(SNI_LOCK, &new_inst->bind_conf->sni_lock);
1119 }
1120 entry->linenum = ++crtlist->linecount;
1121 appctx->st2 = SETCERT_ST_FIN;
1122 goto end;
1123 }
1124 }
1125
1126end:
1127 chunk_appendf(trash, "\n");
1128 if (errcode & ERR_WARN)
1129 chunk_appendf(trash, "%s", err);
1130 chunk_appendf(trash, "Success!\n");
Christopher Faulet908628c2022-03-25 16:43:49 +01001131 if (ci_putchk(cs_ic(cs), trash) == -1)
Christopher Fauleta0bdec32022-04-04 07:51:21 +02001132 cs_rx_room_blk(cs);
William Lallemandc756bbd2020-05-13 17:23:59 +02001133 free_trash_chunk(trash);
1134 /* success: call the release function and don't come back */
1135 return 1;
1136yield:
1137 /* store the state */
Christopher Faulet908628c2022-03-25 16:43:49 +01001138 if (ci_putchk(cs_ic(cs), trash) == -1)
Christopher Fauleta0bdec32022-04-04 07:51:21 +02001139 cs_rx_room_blk(cs);
William Lallemandc756bbd2020-05-13 17:23:59 +02001140 free_trash_chunk(trash);
Christopher Fauleta0bdec32022-04-04 07:51:21 +02001141 cs_rx_endp_more(cs); /* let's come back later */
William Lallemandc756bbd2020-05-13 17:23:59 +02001142 return 0; /* should come back */
1143
1144error:
1145 /* spin unlock and free are done in the release function */
1146 if (trash) {
1147 chunk_appendf(trash, "\n%sFailed!\n", err);
Christopher Faulet908628c2022-03-25 16:43:49 +01001148 if (ci_putchk(cs_ic(cs), trash) == -1)
Christopher Fauleta0bdec32022-04-04 07:51:21 +02001149 cs_rx_room_blk(cs);
William Lallemandc756bbd2020-05-13 17:23:59 +02001150 free_trash_chunk(trash);
1151 }
1152 /* error: call the release function and don't come back */
1153 return 1;
1154}
1155
1156
1157/*
1158 * Parse a "add ssl crt-list <crt-list> <certfile>" line.
1159 * Filters and option must be passed through payload:
1160 */
1161static int cli_parse_add_crtlist(char **args, char *payload, struct appctx *appctx, void *private)
1162{
1163 int cfgerr = 0;
1164 struct ckch_store *store;
1165 char *err = NULL;
1166 char path[MAXPATHLEN+1];
1167 char *crtlist_path;
1168 char *cert_path = NULL;
1169 struct ebmb_node *eb;
1170 struct ebpt_node *inserted;
1171 struct crtlist *crtlist;
1172 struct crtlist_entry *entry = NULL;
William Lallemand99cc2182020-06-25 15:19:51 +02001173 char *end;
William Lallemandc756bbd2020-05-13 17:23:59 +02001174
1175 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
1176 return 1;
1177
1178 if (!*args[3] || (!payload && !*args[4]))
1179 return cli_err(appctx, "'add ssl crtlist' expects a filename and a certificate name\n");
1180
1181 crtlist_path = args[3];
1182
William Lallemand99cc2182020-06-25 15:19:51 +02001183 /* strip trailing slashes, including first one */
1184 for (end = crtlist_path + strlen(crtlist_path) - 1; end >= crtlist_path && *end == '/'; end--)
1185 *end = 0;
1186
William Lallemandc756bbd2020-05-13 17:23:59 +02001187 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
1188 return cli_err(appctx, "Operations on certificates are currently locked!\n");
1189
1190 eb = ebst_lookup(&crtlists_tree, crtlist_path);
1191 if (!eb) {
1192 memprintf(&err, "crt-list '%s' does not exist!", crtlist_path);
1193 goto error;
1194 }
1195 crtlist = ebmb_entry(eb, struct crtlist, node);
1196
1197 entry = crtlist_entry_new();
1198 if (entry == NULL) {
1199 memprintf(&err, "Not enough memory!");
1200 goto error;
1201 }
1202
1203 if (payload) {
1204 char *lf;
1205
1206 lf = strrchr(payload, '\n');
1207 if (lf) {
1208 memprintf(&err, "only one line of payload is supported!");
1209 goto error;
1210 }
1211 /* cert_path is filled here */
Remi Tricot-Le Bretonfb00f312021-03-23 16:41:53 +01001212 cfgerr |= crtlist_parse_line(payload, &cert_path, entry, "CLI", 1, 1, &err);
William Lallemandc756bbd2020-05-13 17:23:59 +02001213 if (cfgerr & ERR_CODE)
1214 goto error;
1215 } else {
1216 cert_path = args[4];
1217 }
1218
1219 if (!cert_path) {
1220 memprintf(&err, "'add ssl crtlist' should contain the certificate name in the payload");
1221 cfgerr |= ERR_ALERT | ERR_FATAL;
1222 goto error;
1223 }
1224
1225 if (eb_gettag(crtlist->entries.b[EB_RGHT])) {
1226 char *slash;
1227
1228 slash = strrchr(cert_path, '/');
1229 if (!slash) {
1230 memprintf(&err, "'%s' is a directory, certificate path '%s' must contain the directory path", (char *)crtlist->node.key, cert_path);
1231 goto error;
1232 }
1233 /* temporary replace / by 0 to do an strcmp */
1234 *slash = '\0';
1235 if (strcmp(cert_path, (char*)crtlist->node.key) != 0) {
1236 *slash = '/';
1237 memprintf(&err, "'%s' is a directory, certificate path '%s' must contain the directory path", (char *)crtlist->node.key, cert_path);
1238 goto error;
1239 }
1240 *slash = '/';
1241 }
1242
1243 if (*cert_path != '/' && global_ssl.crt_base) {
1244 if ((strlen(global_ssl.crt_base) + 1 + strlen(cert_path)) > MAXPATHLEN) {
1245 memprintf(&err, "'%s' : path too long", cert_path);
1246 cfgerr |= ERR_ALERT | ERR_FATAL;
1247 goto error;
1248 }
1249 snprintf(path, sizeof(path), "%s/%s", global_ssl.crt_base, cert_path);
1250 cert_path = path;
1251 }
1252
1253 store = ckchs_lookup(cert_path);
1254 if (store == NULL) {
1255 memprintf(&err, "certificate '%s' does not exist!", cert_path);
1256 goto error;
1257 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001258 if (store->ckch == NULL || store->ckch->cert == NULL) {
1259 memprintf(&err, "certificate '%s' is empty!", cert_path);
1260 goto error;
1261 }
1262
1263 /* check if it's possible to insert this new crtlist_entry */
1264 entry->node.key = store;
1265 inserted = ebpt_insert(&crtlist->entries, &entry->node);
1266 if (inserted != &entry->node) {
1267 memprintf(&err, "file already exists in this directory!");
1268 goto error;
1269 }
1270
1271 /* this is supposed to be a directory (EB_ROOT_UNIQUE), so no ssl_conf are allowed */
1272 if ((entry->ssl_conf || entry->filters) && eb_gettag(crtlist->entries.b[EB_RGHT])) {
1273 memprintf(&err, "this is a directory, SSL configuration and filters are not allowed");
1274 goto error;
1275 }
1276
Willy Tarreau2b718102021-04-21 07:32:39 +02001277 LIST_APPEND(&crtlist->ord_entries, &entry->by_crtlist);
William Lallemandc756bbd2020-05-13 17:23:59 +02001278 entry->crtlist = crtlist;
Willy Tarreau2b718102021-04-21 07:32:39 +02001279 LIST_APPEND(&store->crtlist_entry, &entry->by_ckch_store);
William Lallemandc756bbd2020-05-13 17:23:59 +02001280
1281 appctx->st2 = SETCERT_ST_INIT;
1282 appctx->ctx.cli.p0 = crtlist;
1283 appctx->ctx.cli.p1 = entry;
1284
1285 /* unlock is done in the release handler */
1286 return 0;
1287
1288error:
1289 crtlist_entry_free(entry);
1290 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1291 err = memprintf(&err, "Can't edit the crt-list: %s\n", err ? err : "");
1292 return cli_dynerr(appctx, err);
1293}
1294
1295/* Parse a "del ssl crt-list <crt-list> <certfile>" line. */
1296static int cli_parse_del_crtlist(char **args, char *payload, struct appctx *appctx, void *private)
1297{
1298 struct ckch_store *store;
1299 char *err = NULL;
1300 char *crtlist_path, *cert_path;
1301 struct ebmb_node *ebmb;
1302 struct ebpt_node *ebpt;
1303 struct crtlist *crtlist;
1304 struct crtlist_entry *entry = NULL;
1305 struct ckch_inst *inst, *inst_s;
1306 int linenum = 0;
1307 char *colons;
William Lallemand99cc2182020-06-25 15:19:51 +02001308 char *end;
Remi Tricot-Le Bretonbc2c3862021-03-26 10:47:50 +01001309 int error_message_dumped = 0;
William Lallemandc756bbd2020-05-13 17:23:59 +02001310
1311 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
1312 return 1;
1313
1314 if (!*args[3] || !*args[4])
1315 return cli_err(appctx, "'del ssl crtlist' expects a filename and a certificate name\n");
1316
1317 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
1318 return cli_err(appctx, "Can't delete!\nOperations on certificates are currently locked!\n");
1319
1320 crtlist_path = args[3];
1321 cert_path = args[4];
1322
1323 colons = strchr(cert_path, ':');
1324 if (colons) {
1325 char *endptr;
1326
1327 linenum = strtol(colons + 1, &endptr, 10);
1328 if (colons + 1 == endptr || *endptr != '\0') {
1329 memprintf(&err, "wrong line number after colons in '%s'!", cert_path);
1330 goto error;
1331 }
1332 *colons = '\0';
1333 }
William Lallemand99cc2182020-06-25 15:19:51 +02001334
1335 /* strip trailing slashes, including first one */
1336 for (end = crtlist_path + strlen(crtlist_path) - 1; end >= crtlist_path && *end == '/'; end--)
1337 *end = 0;
1338
William Lallemandc756bbd2020-05-13 17:23:59 +02001339 /* look for crtlist */
1340 ebmb = ebst_lookup(&crtlists_tree, crtlist_path);
1341 if (!ebmb) {
1342 memprintf(&err, "crt-list '%s' does not exist!", crtlist_path);
1343 goto error;
1344 }
1345 crtlist = ebmb_entry(ebmb, struct crtlist, node);
1346
1347 /* look for store */
1348 store = ckchs_lookup(cert_path);
1349 if (store == NULL) {
1350 memprintf(&err, "certificate '%s' does not exist!", cert_path);
1351 goto error;
1352 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001353 if (store->ckch == NULL || store->ckch->cert == NULL) {
1354 memprintf(&err, "certificate '%s' is empty!", cert_path);
1355 goto error;
1356 }
1357
1358 ebpt = ebpt_lookup(&crtlist->entries, store);
1359 if (!ebpt) {
1360 memprintf(&err, "certificate '%s' can't be found in crt-list '%s'!", cert_path, crtlist_path);
1361 goto error;
1362 }
1363
1364 /* list the line number of entries for errors in err, and select the right ebpt */
1365 for (; ebpt; ebpt = ebpt_next_dup(ebpt)) {
1366 struct crtlist_entry *tmp;
1367
1368 tmp = ebpt_entry(ebpt, struct crtlist_entry, node);
1369 memprintf(&err, "%s%s%d", err ? err : "", err ? ", " : "", tmp->linenum);
1370
1371 /* select the entry we wanted */
1372 if (linenum == 0 || tmp->linenum == linenum) {
1373 if (!entry)
1374 entry = tmp;
1375 }
1376 }
1377
1378 /* we didn't found the specified entry */
1379 if (!entry) {
1380 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);
1381 goto error;
1382 }
1383
1384 /* we didn't specified a line number but there were several entries */
1385 if (linenum == 0 && ebpt_next_dup(&entry->node)) {
1386 memprintf(&err, "found the certificate '%s' in several entries, please specify a line number preceded by colons (%s)!", cert_path, err ? err : NULL);
1387 goto error;
1388 }
1389
Remi Tricot-Le Bretonbc2c3862021-03-26 10:47:50 +01001390 /* Iterate over all the instances in order to see if any of them is a
1391 * default instance. If this is the case, the entry won't be suppressed. */
1392 list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_crtlist_entry) {
1393 if (inst->is_default && !inst->bind_conf->strict_sni) {
1394 if (!error_message_dumped) {
1395 memprintf(&err, "certificate '%s' cannot be deleted, it is used as default certificate by the following frontends:\n", cert_path);
1396 error_message_dumped = 1;
1397 }
1398 memprintf(&err, "%s\t- %s:%d\n", err, inst->bind_conf->file, inst->bind_conf->line);
1399 }
1400 }
1401 if (error_message_dumped)
1402 goto error;
1403
William Lallemandc756bbd2020-05-13 17:23:59 +02001404 /* upon error free the ckch_inst and everything inside */
1405
1406 ebpt_delete(&entry->node);
Willy Tarreau2b718102021-04-21 07:32:39 +02001407 LIST_DELETE(&entry->by_crtlist);
1408 LIST_DELETE(&entry->by_ckch_store);
William Lallemandc756bbd2020-05-13 17:23:59 +02001409
1410 list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_crtlist_entry) {
1411 struct sni_ctx *sni, *sni_s;
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001412 struct ckch_inst_link_ref *link_ref, *link_ref_s;
William Lallemandc756bbd2020-05-13 17:23:59 +02001413
1414 HA_RWLOCK_WRLOCK(SNI_LOCK, &inst->bind_conf->sni_lock);
1415 list_for_each_entry_safe(sni, sni_s, &inst->sni_ctx, by_ckch_inst) {
1416 ebmb_delete(&sni->name);
Willy Tarreau2b718102021-04-21 07:32:39 +02001417 LIST_DELETE(&sni->by_ckch_inst);
William Lallemandc756bbd2020-05-13 17:23:59 +02001418 SSL_CTX_free(sni->ctx);
1419 free(sni);
1420 }
1421 HA_RWLOCK_WRUNLOCK(SNI_LOCK, &inst->bind_conf->sni_lock);
Willy Tarreau2b718102021-04-21 07:32:39 +02001422 LIST_DELETE(&inst->by_ckchs);
Remi Tricot-Le Breton4458b972021-02-19 17:41:55 +01001423 list_for_each_entry_safe(link_ref, link_ref_s, &inst->cafile_link_refs, list) {
1424 LIST_DELETE(&link_ref->link->list);
1425 LIST_DELETE(&link_ref->list);
1426 free(link_ref);
1427 }
William Lallemandc756bbd2020-05-13 17:23:59 +02001428 free(inst);
1429 }
1430
1431 crtlist_free_filters(entry->filters);
1432 ssl_sock_free_ssl_conf(entry->ssl_conf);
1433 free(entry->ssl_conf);
1434 free(entry);
1435
1436 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1437 err = memprintf(&err, "Entry '%s' deleted in crtlist '%s'!\n", cert_path, crtlist_path);
1438 return cli_dynmsg(appctx, LOG_NOTICE, err);
1439
1440error:
1441 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1442 err = memprintf(&err, "Can't delete the entry: %s\n", err ? err : "");
1443 return cli_dynerr(appctx, err);
1444}
1445
1446
William Lallemandee8530c2020-06-23 18:19:42 +02001447/* unlink and free all crt-list and crt-list entries */
1448void crtlist_deinit()
1449{
1450 struct eb_node *node, *next;
1451 struct crtlist *crtlist;
1452
1453 node = eb_first(&crtlists_tree);
1454 while (node) {
1455 next = eb_next(node);
1456 crtlist = ebmb_entry(node, struct crtlist, node);
1457 crtlist_free(crtlist);
1458 node = next;
1459 }
1460}
1461
William Lallemandc756bbd2020-05-13 17:23:59 +02001462
1463/* register cli keywords */
1464static struct cli_kw_list cli_kws = {{ },{
Willy Tarreaub205bfd2021-05-07 11:38:37 +02001465 { { "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 },
1466 { { "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 },
1467 { { "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 +02001468 { { NULL }, NULL, NULL, NULL } }
1469};
1470
1471INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws);
1472