blob: 9e507997c97f0acbab3a0d26c3f62f17eb31aad8 [file] [log] [blame]
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001
2/*
3 * SSL/TLS OCSP-related functions
4 *
5 * Copyright (C) 2022 HAProxy Technologies, Remi Tricot-Le Breton <rlebreton@haproxy.com>
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version
10 * 2 of the License, or (at your option) any later version.
11 *
12 * Acknowledgement:
13 * We'd like to specially thank the Stud project authors for a very clean
14 * and well documented code which helped us understand how the OpenSSL API
15 * ought to be used in non-blocking mode. This is one difficult part which
16 * is not easy to get from the OpenSSL doc, and reading the Stud code made
17 * it much more obvious than the examples in the OpenSSL package. Keep up
18 * the good works, guys !
19 *
20 * Stud is an extremely efficient and scalable SSL/TLS proxy which combines
21 * particularly well with haproxy. For more info about this project, visit :
22 * https://github.com/bumptech/stud
23 *
24 */
25
26/* Note: do NOT include openssl/xxx.h here, do it in openssl-compat.h */
27#define _GNU_SOURCE
28#include <ctype.h>
29#include <dirent.h>
30#include <errno.h>
31#include <stdio.h>
32#include <stdlib.h>
33#include <string.h>
34#include <unistd.h>
35
36#include <sys/socket.h>
37#include <sys/stat.h>
38#include <sys/types.h>
39#include <netdb.h>
40#include <netinet/tcp.h>
41
42#include <import/ebpttree.h>
43#include <import/ebsttree.h>
44#include <import/lru.h>
45
46#include <haproxy/api.h>
47#include <haproxy/applet.h>
48#include <haproxy/arg.h>
49#include <haproxy/base64.h>
50#include <haproxy/channel.h>
51#include <haproxy/chunk.h>
52#include <haproxy/cli.h>
53#include <haproxy/connection.h>
54#include <haproxy/dynbuf.h>
55#include <haproxy/errors.h>
56#include <haproxy/fd.h>
57#include <haproxy/freq_ctr.h>
58#include <haproxy/frontend.h>
59#include <haproxy/global.h>
60#include <haproxy/http_rules.h>
61#include <haproxy/log.h>
62#include <haproxy/openssl-compat.h>
63#include <haproxy/pattern-t.h>
64#include <haproxy/proto_tcp.h>
65#include <haproxy/proxy.h>
66#include <haproxy/sample.h>
67#include <haproxy/sc_strm.h>
68#include <haproxy/quic_conn.h>
69#include <haproxy/quic_tp.h>
70#include <haproxy/server.h>
71#include <haproxy/shctx.h>
72#include <haproxy/ssl_ckch.h>
73#include <haproxy/ssl_crtlist.h>
74#include <haproxy/ssl_sock.h>
75#include <haproxy/ssl_utils.h>
76#include <haproxy/stats.h>
77#include <haproxy/stconn.h>
78#include <haproxy/stream-t.h>
79#include <haproxy/task.h>
80#include <haproxy/ticks.h>
81#include <haproxy/time.h>
82#include <haproxy/tools.h>
83#include <haproxy/vars.h>
84#include <haproxy/xxhash.h>
85#include <haproxy/istbuf.h>
86#include <haproxy/ssl_ocsp-t.h>
87#include <haproxy/http_client.h>
88
89
90/* ***** READ THIS before adding code here! *****
91 *
92 * Due to API incompatibilities between multiple OpenSSL versions and their
93 * derivatives, it's often tempting to add macros to (re-)define certain
94 * symbols. Please do not do this here, and do it in common/openssl-compat.h
95 * exclusively so that the whole code consistently uses the same macros.
96 *
97 * Whenever possible if a macro is missing in certain versions, it's better
98 * to conditionally define it in openssl-compat.h than using lots of ifdefs.
99 */
100
101#ifndef OPENSSL_NO_OCSP
102int ocsp_ex_index = -1;
103
104int ssl_sock_get_ocsp_arg_kt_index(int evp_keytype)
105{
106 switch (evp_keytype) {
107 case EVP_PKEY_RSA:
108 return 2;
109 case EVP_PKEY_DSA:
110 return 0;
111 case EVP_PKEY_EC:
112 return 1;
113 }
114
115 return -1;
116}
117
118/*
119 * Callback used to set OCSP status extension content in server hello.
120 */
121int ssl_sock_ocsp_stapling_cbk(SSL *ssl, void *arg)
122{
123 struct certificate_ocsp *ocsp;
124 struct ocsp_cbk_arg *ocsp_arg;
125 char *ssl_buf;
126 SSL_CTX *ctx;
127 EVP_PKEY *ssl_pkey;
128 int key_type;
129 int index;
130
131 ctx = SSL_get_SSL_CTX(ssl);
132 if (!ctx)
133 return SSL_TLSEXT_ERR_NOACK;
134
135 ocsp_arg = SSL_CTX_get_ex_data(ctx, ocsp_ex_index);
136 if (!ocsp_arg)
137 return SSL_TLSEXT_ERR_NOACK;
138
139 ssl_pkey = SSL_get_privatekey(ssl);
140 if (!ssl_pkey)
141 return SSL_TLSEXT_ERR_NOACK;
142
143 key_type = EVP_PKEY_base_id(ssl_pkey);
144
145 if (ocsp_arg->is_single && ocsp_arg->single_kt == key_type)
146 ocsp = ocsp_arg->s_ocsp;
147 else {
148 /* For multiple certs per context, we have to find the correct OCSP response based on
149 * the certificate type
150 */
151 index = ssl_sock_get_ocsp_arg_kt_index(key_type);
152
153 if (index < 0)
154 return SSL_TLSEXT_ERR_NOACK;
155
156 ocsp = ocsp_arg->m_ocsp[index];
157
158 }
159
160 if (!ocsp ||
161 !ocsp->response.area ||
162 !ocsp->response.data ||
Remi Tricot-Le Breton8c20a742023-03-02 15:49:55 +0100163 (ocsp->expire < date.tv_sec))
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100164 return SSL_TLSEXT_ERR_NOACK;
165
166 ssl_buf = OPENSSL_malloc(ocsp->response.data);
167 if (!ssl_buf)
168 return SSL_TLSEXT_ERR_NOACK;
169
170 memcpy(ssl_buf, ocsp->response.area, ocsp->response.data);
171 SSL_set_tlsext_status_ocsp_resp(ssl, (unsigned char*)ssl_buf, ocsp->response.data);
172
173 return SSL_TLSEXT_ERR_OK;
174}
175
176#endif /* !defined(OPENSSL_NO_OCSP) */
177
178
179#if (defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP)
180
181struct eb_root cert_ocsp_tree = EB_ROOT_UNIQUE;
182
183__decl_thread(HA_SPINLOCK_T ocsp_tree_lock);
184
185struct eb_root ocsp_update_tree = EB_ROOT; /* updatable ocsp responses sorted by next_update in absolute time */
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100186
187/* This function starts to check if the OCSP response (in DER format) contained
188 * in chunk 'ocsp_response' is valid (else exits on error).
189 * If 'cid' is not NULL, it will be compared to the OCSP certificate ID
190 * contained in the OCSP Response and exits on error if no match.
191 * If it's a valid OCSP Response:
192 * If 'ocsp' is not NULL, the chunk is copied in the OCSP response's container
193 * pointed by 'ocsp'.
194 * If 'ocsp' is NULL, the function looks up into the OCSP response's
195 * containers tree (using as index the ASN1 form of the OCSP Certificate ID extracted
196 * from the response) and exits on error if not found. Finally, If an OCSP response is
197 * already present in the container, it will be overwritten.
198 *
199 * Note: OCSP response containing more than one OCSP Single response is not
200 * considered valid.
201 *
202 * Returns 0 on success, 1 in error case.
203 */
204int ssl_sock_load_ocsp_response(struct buffer *ocsp_response,
205 struct certificate_ocsp *ocsp,
206 OCSP_CERTID *cid, char **err)
207{
208 OCSP_RESPONSE *resp;
209 OCSP_BASICRESP *bs = NULL;
210 OCSP_SINGLERESP *sr;
211 OCSP_CERTID *id;
212 unsigned char *p = (unsigned char *) ocsp_response->area;
213 int rc , count_sr;
214 ASN1_GENERALIZEDTIME *revtime, *thisupd, *nextupd = NULL;
215 int reason;
216 int ret = 1;
217#ifdef HAVE_ASN1_TIME_TO_TM
218 struct tm nextupd_tm = {0};
219#endif
220
221 resp = d2i_OCSP_RESPONSE(NULL, (const unsigned char **)&p,
222 ocsp_response->data);
223 if (!resp) {
224 memprintf(err, "Unable to parse OCSP response");
225 goto out;
226 }
227
228 rc = OCSP_response_status(resp);
229 if (rc != OCSP_RESPONSE_STATUS_SUCCESSFUL) {
230 memprintf(err, "OCSP response status not successful");
231 goto out;
232 }
233
234 bs = OCSP_response_get1_basic(resp);
235 if (!bs) {
236 memprintf(err, "Failed to get basic response from OCSP Response");
237 goto out;
238 }
239
240 count_sr = OCSP_resp_count(bs);
241 if (count_sr > 1) {
242 memprintf(err, "OCSP response ignored because contains multiple single responses (%d)", count_sr);
243 goto out;
244 }
245
246 sr = OCSP_resp_get0(bs, 0);
247 if (!sr) {
248 memprintf(err, "Failed to get OCSP single response");
249 goto out;
250 }
251
252 id = (OCSP_CERTID*)OCSP_SINGLERESP_get0_id(sr);
253
254 rc = OCSP_single_get0_status(sr, &reason, &revtime, &thisupd, &nextupd);
255 if (rc != V_OCSP_CERTSTATUS_GOOD && rc != V_OCSP_CERTSTATUS_REVOKED) {
256 memprintf(err, "OCSP single response: certificate status is unknown");
257 goto out;
258 }
259
260 if (!nextupd) {
261 memprintf(err, "OCSP single response: missing nextupdate");
262 goto out;
263 }
264
265 rc = OCSP_check_validity(thisupd, nextupd, OCSP_MAX_RESPONSE_TIME_SKEW, -1);
266 if (!rc) {
267 memprintf(err, "OCSP single response: no longer valid.");
268 goto out;
269 }
270
271 if (cid) {
272 if (OCSP_id_cmp(id, cid)) {
273 memprintf(err, "OCSP single response: Certificate ID does not match certificate and issuer");
274 goto out;
275 }
276 }
277
278 if (!ocsp) {
279 unsigned char key[OCSP_MAX_CERTID_ASN1_LENGTH];
280 unsigned char *p;
281
282 rc = i2d_OCSP_CERTID(id, NULL);
283 if (!rc) {
284 memprintf(err, "OCSP single response: Unable to encode Certificate ID");
285 goto out;
286 }
287
288 if (rc > OCSP_MAX_CERTID_ASN1_LENGTH) {
289 memprintf(err, "OCSP single response: Certificate ID too long");
290 goto out;
291 }
292
293 p = key;
294 memset(key, 0, OCSP_MAX_CERTID_ASN1_LENGTH);
295 i2d_OCSP_CERTID(id, &p);
296 HA_SPIN_LOCK(OCSP_LOCK, &ocsp_tree_lock);
297 ocsp = (struct certificate_ocsp *)ebmb_lookup(&cert_ocsp_tree, key, OCSP_MAX_CERTID_ASN1_LENGTH);
298 if (!ocsp) {
299 HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
300 memprintf(err, "OCSP single response: Certificate ID does not match any certificate or issuer");
301 goto out;
302 }
303 HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
304 }
305
306 /* According to comments on "chunk_dup", the
307 previous chunk buffer will be freed */
308 if (!chunk_dup(&ocsp->response, ocsp_response)) {
309 memprintf(err, "OCSP response: Memory allocation error");
310 goto out;
311 }
312
313#ifdef HAVE_ASN1_TIME_TO_TM
314 if (ASN1_TIME_to_tm(nextupd, &nextupd_tm) == 0) {
315 memprintf(err, "OCSP single response: Invalid \"Next Update\" time");
316 goto out;
317 }
318 ocsp->expire = my_timegm(&nextupd_tm) - OCSP_MAX_RESPONSE_TIME_SKEW;
319#else
320 ocsp->expire = asn1_generalizedtime_to_epoch(nextupd) - OCSP_MAX_RESPONSE_TIME_SKEW;
321 if (ocsp->expire < 0) {
322 memprintf(err, "OCSP single response: Invalid \"Next Update\" time");
323 goto out;
324 }
325#endif
326
327 ret = 0;
328out:
329 ERR_clear_error();
330
331 if (bs)
332 OCSP_BASICRESP_free(bs);
333
334 if (resp)
335 OCSP_RESPONSE_free(resp);
336
337 return ret;
338}
339/*
340 * External function use to update the OCSP response in the OCSP response's
341 * containers tree. The chunk 'ocsp_response' must contain the OCSP response
342 * to update in DER format.
343 *
344 * Returns 0 on success, 1 in error case.
345 */
346int ssl_sock_update_ocsp_response(struct buffer *ocsp_response, char **err)
347{
348 return ssl_sock_load_ocsp_response(ocsp_response, NULL, NULL, err);
349}
350
351
352
353#if !defined OPENSSL_IS_BORINGSSL
354/*
355 * Decrease the refcount of the struct ocsp_response and frees it if it's not
356 * used anymore. Also removes it from the tree if free'd.
357 */
358void ssl_sock_free_ocsp(struct certificate_ocsp *ocsp)
359{
360 if (!ocsp)
361 return;
362
Remi Tricot-Le Breton57f60c22023-01-09 12:02:42 +0100363 HA_SPIN_LOCK(OCSP_LOCK, &ocsp_tree_lock);
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100364 ocsp->refcount--;
365 if (ocsp->refcount <= 0) {
366 ebmb_delete(&ocsp->key);
367 eb64_delete(&ocsp->next_update);
368 X509_free(ocsp->issuer);
369 ocsp->issuer = NULL;
370 sk_X509_pop_free(ocsp->chain, X509_free);
371 ocsp->chain = NULL;
372 chunk_destroy(&ocsp->response);
Remi Tricot-Le Breton648c83e2023-01-09 12:02:48 +0100373 if (ocsp->uri) {
374 ha_free(&ocsp->uri->area);
375 ha_free(&ocsp->uri);
376 }
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100377
378 free(ocsp);
379 }
Remi Tricot-Le Breton57f60c22023-01-09 12:02:42 +0100380 HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100381}
382
383
384/*
385 * This function dumps the details of an OCSP_CERTID. It is based on
386 * ocsp_certid_print in OpenSSL.
387 */
388static inline int ocsp_certid_print(BIO *bp, OCSP_CERTID *certid, int indent)
389{
390 ASN1_OCTET_STRING *piNameHash = NULL;
391 ASN1_OCTET_STRING *piKeyHash = NULL;
392 ASN1_INTEGER *pSerial = NULL;
393
394 if (OCSP_id_get0_info(&piNameHash, NULL, &piKeyHash, &pSerial, certid)) {
395
396 BIO_printf(bp, "%*sCertificate ID:\n", indent, "");
397 indent += 2;
398 BIO_printf(bp, "%*sIssuer Name Hash: ", indent, "");
399#ifndef USE_OPENSSL_WOLFSSL
400 i2a_ASN1_STRING(bp, piNameHash, 0);
401#else
402 wolfSSL_ASN1_STRING_print(bp, piNameHash);
403#endif
404 BIO_printf(bp, "\n%*sIssuer Key Hash: ", indent, "");
405#ifndef USE_OPENSSL_WOLFSSL
406 i2a_ASN1_STRING(bp, piKeyHash, 0);
407#else
408 wolfSSL_ASN1_STRING_print(bp, piNameHash);
409#endif
410 BIO_printf(bp, "\n%*sSerial Number: ", indent, "");
411 i2a_ASN1_INTEGER(bp, pSerial);
412 }
413 return 1;
414}
415
Remi Tricot-Le Breton9c4437d2023-02-28 17:46:28 +0100416
417enum {
418 SHOW_OCSPRESP_FMT_DFLT,
419 SHOW_OCSPRESP_FMT_TEXT,
420 SHOW_OCSPRESP_FMT_B64
421};
422
423struct show_ocspresp_cli_ctx {
424 struct certificate_ocsp *ocsp;
425 int format;
426};
427
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100428/*
429 * Dump the details about an OCSP response in DER format stored in
430 * <ocsp_response> into buffer <out>.
431 * Returns 0 in case of success.
432 */
433int ssl_ocsp_response_print(struct buffer *ocsp_response, struct buffer *out)
434{
435 BIO *bio = NULL;
436 int write = -1;
437 OCSP_RESPONSE *resp;
438 const unsigned char *p;
439 int retval = -1;
440
441 if (!ocsp_response)
442 return -1;
443
444 if ((bio = BIO_new(BIO_s_mem())) == NULL)
445 return -1;
446
447 p = (const unsigned char*)ocsp_response->area;
448
449 resp = d2i_OCSP_RESPONSE(NULL, &p, ocsp_response->data);
450 if (!resp) {
451 chunk_appendf(out, "Unable to parse OCSP response");
452 goto end;
453 }
454
455#ifndef USE_OPENSSL_WOLFSSL
456 if (OCSP_RESPONSE_print(bio, resp, 0) != 0) {
457#else
458 if (wolfSSL_d2i_OCSP_RESPONSE_bio(bio, &resp) != 0) {
459#endif
460 struct buffer *trash = get_trash_chunk();
461 struct ist ist_block = IST_NULL;
462 struct ist ist_double_lf = IST_NULL;
463 static struct ist double_lf = IST("\n\n");
464
465 write = BIO_read(bio, trash->area, trash->size - 1);
466 if (write <= 0)
467 goto end;
468 trash->data = write;
469
470 /* Look for empty lines in the 'trash' buffer and add a space to
471 * the beginning to avoid having empty lines in the output
472 * (without changing the appearance of the information
473 * displayed).
474 */
475 ist_block = ist2(b_orig(trash), b_data(trash));
476
477 ist_double_lf = istist(ist_block, double_lf);
478
479 while (istlen(ist_double_lf)) {
480 /* istptr(ist_double_lf) points to the first \n of a
481 * \n\n pattern.
482 */
483 uint empty_line_offset = istptr(ist_double_lf) + 1 - istptr(ist_block);
484
485 /* Write up to the first '\n' of the "\n\n" pattern into
486 * the output buffer.
487 */
488 b_putblk(out, istptr(ist_block), empty_line_offset);
489 /* Add an extra space. */
490 b_putchr(out, ' ');
491
492 /* Keep looking for empty lines in the rest of the data. */
493 ist_block = istadv(ist_block, empty_line_offset);
494
495 ist_double_lf = istist(ist_block, double_lf);
496 }
497
498 retval = (b_istput(out, ist_block) <= 0);
499 }
500
501end:
502 if (bio)
503 BIO_free(bio);
504
505 OCSP_RESPONSE_free(resp);
506
507 return retval;
508}
509
510/*
Remi Tricot-Le Breton9c4437d2023-02-28 17:46:28 +0100511 * Dump the contents of an OCSP response in DER format stored in
512 * <ocsp_response> into buffer <out> after converting it to base64.
513 * Returns 0 in case of success.
514 */
515static int ssl_ocsp_response_print_base64(struct buffer *ocsp_response, struct buffer *out)
516{
517 int b64len = 0;
518
519 b64len = a2base64(b_orig(ocsp_response), b_data(ocsp_response),
520 b_orig(out), b_size(out));
521
522 if (b64len < 0)
523 return 1;
524
525 out->data = b64len;
526
527 /* Add empty line */
528 chunk_appendf(ocsp_response, "\n");
529
530 return 0;
531}
532
533/*
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100534 * Dump the details of the OCSP response of ID <ocsp_certid> into buffer <out>.
535 * Returns 0 in case of success.
536 */
537int ssl_get_ocspresponse_detail(unsigned char *ocsp_certid, struct buffer *out)
538{
539 struct certificate_ocsp *ocsp;
540 int ret = 0;
541
542 HA_SPIN_LOCK(OCSP_LOCK, &ocsp_tree_lock);
543 ocsp = (struct certificate_ocsp *)ebmb_lookup(&cert_ocsp_tree, ocsp_certid, OCSP_MAX_CERTID_ASN1_LENGTH);
544 if (!ocsp) {
545 HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
546 return -1;
547 }
548
549 ret = ssl_ocsp_response_print(&ocsp->response, out);
550
551 HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
552
553 return ret;
554}
555
556
557/* IO handler of details "show ssl ocsp-response <id>".
558 * The current entry is taken from appctx->svcctx.
559 */
560static int cli_io_handler_show_ocspresponse_detail(struct appctx *appctx)
561{
Remi Tricot-Le Breton9c4437d2023-02-28 17:46:28 +0100562 struct buffer *trash = get_trash_chunk();
563 struct show_ocspresp_cli_ctx *ctx = appctx->svcctx;
564 struct certificate_ocsp *ocsp = ctx->ocsp;
565 int retval = 0;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100566
Remi Tricot-Le Breton9c4437d2023-02-28 17:46:28 +0100567 switch (ctx->format) {
568 case SHOW_OCSPRESP_FMT_DFLT:
569 case SHOW_OCSPRESP_FMT_TEXT:
570 retval = ssl_ocsp_response_print(&ocsp->response, trash);
571 break;
572 case SHOW_OCSPRESP_FMT_B64:
573 retval = ssl_ocsp_response_print_base64(&ocsp->response, trash);
574 break;
575 }
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100576
Remi Tricot-Le Breton9c4437d2023-02-28 17:46:28 +0100577 if (retval)
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100578 return 1;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100579
580 if (applet_putchk(appctx, trash) == -1)
581 goto yield;
582
583 appctx->svcctx = NULL;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100584 return 1;
585
586yield:
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100587 return 0;
588}
589
590void ssl_sock_ocsp_free_func(void *parent, void *ptr, CRYPTO_EX_DATA *ad, int idx, long argl, void *argp)
591{
592 struct ocsp_cbk_arg *ocsp_arg;
593
594 if (ptr) {
595 ocsp_arg = ptr;
596
597 if (ocsp_arg->is_single) {
598 ssl_sock_free_ocsp(ocsp_arg->s_ocsp);
599 ocsp_arg->s_ocsp = NULL;
600 } else {
601 int i;
602
603 for (i = 0; i < SSL_SOCK_NUM_KEYTYPES; i++) {
604 ssl_sock_free_ocsp(ocsp_arg->m_ocsp[i]);
605 ocsp_arg->m_ocsp[i] = NULL;
606 }
607 }
608 free(ocsp_arg);
609 }
610}
611
612/*
613 * Extract the first OCSP URI (if any) contained in <cert> and write it into
614 * <out>.
615 * Returns 0 in case of success, 1 otherwise.
616 */
617int ssl_ocsp_get_uri_from_cert(X509 *cert, struct buffer *out, char **err)
618{
619 STACK_OF(OPENSSL_STRING) *ocsp_uri_stk = NULL;
620 int ret = 1;
621
622 if (!cert || !out)
623 goto end;
624
625 ocsp_uri_stk = X509_get1_ocsp(cert);
626 if (ocsp_uri_stk == NULL) {
627 memprintf(err, "%sNo OCSP URL stack!\n", *err ? *err : "");
628 goto end;
629 }
630
William Lallemand8bc00f82022-12-22 10:09:11 +0100631 if (!chunk_strcpy(out, sk_OPENSSL_STRING_value(ocsp_uri_stk, 0))) {
632 memprintf(err, "%sOCSP URI too long!\n", *err ? *err : "");
633 goto end;
634 }
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100635 if (b_data(out) == 0) {
636 memprintf(err, "%sNo OCSP URL!\n", *err ? *err : "");
637 goto end;
638 }
639
640 ret = 0;
641
642end:
643 X509_email_free(ocsp_uri_stk);
644 return ret;
645}
646
647/*
648 * Create the url and request body that make a proper OCSP request for the
649 * <certid>. The <req_url> parameter should already hold the OCSP URI that was
650 * extracted from the corresponding certificate. Depending on the size of the
651 * certid we will either append data to the <req_url> to create a proper URL
652 * that will be sent with a GET command, or the <req_body> will be constructed
653 * in case of a POST.
654 * Returns 0 in case of success.
655 */
656int ssl_ocsp_create_request_details(const OCSP_CERTID *certid, struct buffer *req_url,
657 struct buffer *req_body, char **err)
658{
659 int errcode = -1;
660 OCSP_REQUEST *ocsp;
661 struct buffer *bin_request = get_trash_chunk();
662 unsigned char *outbuf = (unsigned char*)b_orig(bin_request);
663
664 ocsp = OCSP_REQUEST_new();
665 if (ocsp == NULL) {
666 memprintf(err, "%sCan't create OCSP_REQUEST\n", *err ? *err : "");
667 goto end;
668 }
669
670 if (OCSP_request_add0_id(ocsp, (OCSP_CERTID*)certid) == NULL) {
671 memprintf(err, "%sOCSP_request_add0_id() error\n", *err ? *err : "");
672 goto end;
673 }
674
675 bin_request->data = i2d_OCSP_REQUEST(ocsp, &outbuf);
676 if (b_data(bin_request) <= 0) {
677 memprintf(err, "%si2d_OCSP_REQUEST() error\n", *err ? *err : "");
678 goto end;
679 }
680
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100681 /* HTTP based OCSP requests can use either the GET or the POST method to
682 * submit their requests. To enable HTTP caching, small requests (that
683 * after encoding are less than 255 bytes), MAY be submitted using GET.
684 * If HTTP caching is not important, or the request is greater than 255
685 * bytes, the request SHOULD be submitted using POST.
686 */
William Lallemandeb530202022-12-22 10:19:07 +0100687 if (b_data(bin_request) + b_data(req_url) < 0xff) {
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100688 struct buffer *b64buf = get_trash_chunk();
689 char *ret = NULL;
690 int base64_ret = 0;
691
692 chunk_strcat(req_url, "/");
693
694 base64_ret = a2base64(b_orig(bin_request), b_data(bin_request),
695 b_orig(b64buf), b_size(b64buf));
696
697 if (base64_ret < 0) {
698 memprintf(err, "%sa2base64() error\n", *err ? *err : "");
Remi Tricot-Le Bretonc389b042023-01-02 15:01:16 +0100699 goto end;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100700 }
701
702 b64buf->data = base64_ret;
703
William Lallemandeb530202022-12-22 10:19:07 +0100704 ret = encode_chunk((char*)b_stop(req_url), b_orig(req_url) + b_size(req_url), '%',
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100705 query_encode_map, b64buf);
706 if (ret && *ret == '\0') {
William Lallemandeb530202022-12-22 10:19:07 +0100707 req_url->data = ret - b_orig(req_url);
Remi Tricot-Le Bretonc389b042023-01-02 15:01:16 +0100708 errcode = 0;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100709 }
710 }
711 else {
712 chunk_cpy(req_body, bin_request);
Remi Tricot-Le Bretonc389b042023-01-02 15:01:16 +0100713 errcode = 0;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100714 }
715
Remi Tricot-Le Bretonc389b042023-01-02 15:01:16 +0100716
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100717end:
718 OCSP_REQUEST_free(ocsp);
719
720 return errcode;
721}
722
723/*
724 * Parse an OCSP_RESPONSE contained in <respbuf> and check its validity in
725 * regard to the contents of <ckch> or the <issuer> certificate.
726 * Certificate_ocsp structure does not keep a reference to the corresponding
727 * ckch_store so outside of a CLI context (see "send ssl ocsp-response"
728 * command), we only have an easy access to the issuer's certificate whose
729 * reference is held in the structure.
730 * Return 0 in case of success, 1 otherwise.
731 */
732int ssl_ocsp_check_response(STACK_OF(X509) *chain, X509 *issuer,
733 struct buffer *respbuf, char **err)
734{
735 int ret = 1;
736 int n;
737 OCSP_RESPONSE *response = NULL;
738 OCSP_BASICRESP *basic = NULL;
739 X509_STORE *store = NULL;
740 const unsigned char *start = (const unsigned char*)b_orig(respbuf);
741
742 if (!chain && !issuer) {
743 memprintf(err, "check_ocsp_response needs a certificate validation chain or an issuer certificate");
744 goto end;
745 }
746
747 response = d2i_OCSP_RESPONSE(NULL, &start, b_data(respbuf));
748 if (!response) {
749 memprintf(err, "d2i_OCSP_RESPONSE() failed");
750 goto end;
751 }
752
753 n = OCSP_response_status(response);
754
755 if (n != OCSP_RESPONSE_STATUS_SUCCESSFUL) {
756 memprintf(err, "OCSP response not successful (%d: %s)",
757 n, OCSP_response_status_str(n));
758 goto end;
759 }
760
761 basic = OCSP_response_get1_basic(response);
762 if (basic == NULL) {
763 memprintf(err, "OCSP_response_get1_basic() failed");
764 goto end;
765 }
766
Remi Tricot-Le Breton8bdd0052023-01-09 12:02:43 +0100767 /* Create a temporary store in which we add the certificate's chain
768 * certificates. We assume that all those certificates can be trusted
769 * because they were provided by the user.
770 * The only ssl item that needs to be verified here is the OCSP
771 * response.
772 */
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100773 store = X509_STORE_new();
774 if (!store) {
775 memprintf(err, "X509_STORE_new() failed");
776 goto end;
777 }
Remi Tricot-Le Breton8bdd0052023-01-09 12:02:43 +0100778
779 if (chain) {
780 int i = 0;
781 for (i = 0; i < sk_X509_num(chain); i++) {
782 X509 *cert = sk_X509_value(chain, i);
783 X509_STORE_add_cert(store, cert);
784 }
785 }
786
787 if (issuer)
788 X509_STORE_add_cert(store, issuer);
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100789
Remi Tricot-Le Breton8bdd0052023-01-09 12:02:43 +0100790 if (OCSP_basic_verify(basic, chain, store, OCSP_TRUSTOTHER) != 1) {
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100791 memprintf(err, "OCSP_basic_verify() failed");
792 goto end;
793 }
794
795 ret = 0;
796
797end:
798 X509_STORE_free(store);
799 OCSP_RESPONSE_free(response);
800 OCSP_BASICRESP_free(basic);
801 return ret;
802}
803
804
805/*
806 * OCSP-UPDATE RELATED FUNCTIONS AND STRUCTURES
807 */
808
809struct task *ocsp_update_task __read_mostly = NULL;
Remi Tricot-Le Bretonb33fe2f2023-02-28 17:46:25 +0100810static struct proxy *httpclient_ocsp_update_px;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100811
812static struct ssl_ocsp_task_ctx {
813 struct certificate_ocsp *cur_ocsp;
814 struct httpclient *hc;
Remi Tricot-Le Bretonad6cba82023-02-28 17:46:21 +0100815 struct appctx *appctx;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100816 int flags;
Remi Tricot-Le Bretonad6cba82023-02-28 17:46:21 +0100817 int update_status;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100818} ssl_ocsp_task_ctx;
819
820const struct http_hdr ocsp_request_hdrs[] = {
821 { IST("Content-Type"), IST("application/ocsp-request") },
822 { IST_NULL, IST_NULL }
823};
824
Remi Tricot-Le Bretonad6cba82023-02-28 17:46:21 +0100825enum {
826 OCSP_UPDT_UNKNOWN = 0,
827 OCSP_UPDT_OK = 1,
828 OCSP_UPDT_ERR_HTTP_STATUS = 2,
829 OCSP_UPDT_ERR_HTTP_HDR = 3,
830 OCSP_UPDT_ERR_CHECK = 4,
831 OCSP_UPDT_ERR_INSERT = 5,
832 OCSP_UPDT_ERR_LAST /* Must be last */
833};
834
835const struct ist ocsp_update_errors[] = {
836 [OCSP_UPDT_UNKNOWN] = IST("Unknown"),
837 [OCSP_UPDT_OK] = IST("Update successful"),
838 [OCSP_UPDT_ERR_HTTP_STATUS] = IST("HTTP error"),
839 [OCSP_UPDT_ERR_HTTP_HDR] = IST("Missing \"ocsp-response\" header"),
840 [OCSP_UPDT_ERR_CHECK] = IST("OCSP response check failure"),
841 [OCSP_UPDT_ERR_INSERT] = IST("Error during insertion")
842};
843
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100844static struct task *ssl_ocsp_update_responses(struct task *task, void *context, unsigned int state);
845
846/*
847 * Create the main OCSP update task that will iterate over the OCSP responses
848 * stored in ocsp_update_tree and send an OCSP request via the http_client
849 * applet to the corresponding OCSP responder. The task will then be in charge
850 * of processing the response, verifying it and resinserting it in the actual
851 * ocsp response tree if the response is valid.
852 * Returns 0 in case of success.
853 */
854int ssl_create_ocsp_update_task(char **err)
855{
856 if (ocsp_update_task)
857 return 0; /* Already created */
858
859 ocsp_update_task = task_new_anywhere();
860 if (!ocsp_update_task) {
861 memprintf(err, "parsing : failed to allocate global ocsp update task.");
862 return -1;
863 }
864
865 ocsp_update_task->process = ssl_ocsp_update_responses;
866 ocsp_update_task->context = NULL;
867
868 return 0;
869}
870
871static int ssl_ocsp_task_schedule()
872{
873 if (ocsp_update_task)
874 task_schedule(ocsp_update_task, now_ms);
875
876 return 0;
877}
878REGISTER_POST_CHECK(ssl_ocsp_task_schedule);
879
880void ssl_sock_free_ocsp(struct certificate_ocsp *ocsp);
881
882void ssl_destroy_ocsp_update_task(void)
883{
884 struct eb64_node *node, *next;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100885 if (!ocsp_update_task)
886 return;
887
888 HA_SPIN_LOCK(OCSP_LOCK, &ocsp_tree_lock);
889
890 node = eb64_first(&ocsp_update_tree);
891 while (node) {
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100892 next = eb64_next(node);
893 eb64_delete(node);
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100894 node = next;
895 }
896
897 HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
898
899 task_destroy(ocsp_update_task);
900 ocsp_update_task = NULL;
Remi Tricot-Le Breton14d7f0e2023-01-09 12:02:45 +0100901
902 ssl_sock_free_ocsp(ssl_ocsp_task_ctx.cur_ocsp);
903 ssl_ocsp_task_ctx.cur_ocsp = NULL;
Remi Tricot-Le Breton926f34b2023-02-28 17:46:18 +0100904
905 if (ssl_ocsp_task_ctx.hc) {
906 httpclient_stop_and_destroy(ssl_ocsp_task_ctx.hc);
907 ssl_ocsp_task_ctx.hc = NULL;
908 }
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100909}
910
Remi Tricot-Le Breton10f113e2023-01-12 09:49:11 +0100911static inline void ssl_ocsp_set_next_update(struct certificate_ocsp *ocsp)
912{
913 int update_margin = (ocsp->expire >= SSL_OCSP_UPDATE_MARGIN) ? SSL_OCSP_UPDATE_MARGIN : 0;
914
Remi Tricot-Le Breton56ab6072023-03-02 15:49:54 +0100915 ocsp->next_update.key = MIN(date.tv_sec + global_ssl.ocsp_update.delay_max,
Remi Tricot-Le Breton10f113e2023-01-12 09:49:11 +0100916 ocsp->expire - update_margin);
917
918 /* An already existing valid OCSP response that expires within less than
919 * SSL_OCSP_UPDATE_DELAY_MIN or has no 'Next Update' field should not be
920 * updated more than once every 5 minutes in order to avoid continuous
921 * update of the same response. */
922 if (b_data(&ocsp->response))
923 ocsp->next_update.key = MAX(ocsp->next_update.key,
Remi Tricot-Le Breton56ab6072023-03-02 15:49:54 +0100924 date.tv_sec + global_ssl.ocsp_update.delay_min);
Remi Tricot-Le Breton10f113e2023-01-12 09:49:11 +0100925}
926
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100927/*
928 * Insert a certificate_ocsp structure into the ocsp_update_tree tree, in which
929 * entries are sorted by absolute date of the next update. The next_update key
930 * will be the smallest out of the actual expire value of the response and
931 * now+1H. This arbitrary 1H value ensures that ocsp responses are updated
932 * periodically even when they have a long expire time, while not overloading
933 * the system too much (in theory). Likewise, a minimum 5 minutes interval is
934 * defined in order to avoid updating too often responses that have a really
935 * short expire time or even no 'Next Update' at all.
936 */
937int ssl_ocsp_update_insert(struct certificate_ocsp *ocsp)
938{
Remi Tricot-Le Breton10f113e2023-01-12 09:49:11 +0100939 /* Set next_update based on current time and the various OCSP
940 * minimum/maximum update times.
941 */
942 ssl_ocsp_set_next_update(ocsp);
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100943
Remi Tricot-Le Breton7e1a62e2023-02-28 17:46:27 +0100944 ocsp->fail_count = 0;
945
Remi Tricot-Le Breton10f113e2023-01-12 09:49:11 +0100946 HA_SPIN_LOCK(OCSP_LOCK, &ocsp_tree_lock);
947 eb64_insert(&ocsp_update_tree, &ocsp->next_update);
948 HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100949
Remi Tricot-Le Breton10f113e2023-01-12 09:49:11 +0100950 return 0;
951}
952
953/*
954 * Reinsert an entry in the update tree. The entry's next update time can not
955 * occur before now+SSL_OCSP_HTTP_ERR_REPLAY.
956 * This is supposed to be used in case of http error (ocsp responder unreachable
957 * for instance). This ensures that the entry does not get reinserted at the
958 * beginning of the tree every time.
959 */
960int ssl_ocsp_update_insert_after_error(struct certificate_ocsp *ocsp)
961{
Remi Tricot-Le Breton7e1a62e2023-02-28 17:46:27 +0100962 int replay_delay = 0;
963 /*
964 * Set next_update based on current time and the various OCSP
Remi Tricot-Le Breton10f113e2023-01-12 09:49:11 +0100965 * minimum/maximum update times.
966 */
967 ssl_ocsp_set_next_update(ocsp);
968
Remi Tricot-Le Breton7e1a62e2023-02-28 17:46:27 +0100969 ++ocsp->fail_count;
970
971 /*
972 * The replay delay will be increased for every consecutive update
973 * failure, up to the SSL_OCSP_UPDATE_DELAY_MAX delay. It will ensure
974 * that the replay delay will be one minute for the first failure and
975 * will be multiplied by 2 for every subsequent failures, while still
976 * being at most 1 hour (with the current default values).
977 */
978 replay_delay = MIN(SSL_OCSP_HTTP_ERR_REPLAY * (1 << ocsp->fail_count),
Remi Tricot-Le Breton58432372023-02-28 17:46:29 +0100979 global_ssl.ocsp_update.delay_max);
Remi Tricot-Le Breton7e1a62e2023-02-28 17:46:27 +0100980
Remi Tricot-Le Breton56ab6072023-03-02 15:49:54 +0100981 if (ocsp->next_update.key < date.tv_sec + replay_delay)
982 ocsp->next_update.key = date.tv_sec + replay_delay;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100983
984 HA_SPIN_LOCK(OCSP_LOCK, &ocsp_tree_lock);
985 eb64_insert(&ocsp_update_tree, &ocsp->next_update);
986 HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
987
988 return 0;
989}
990
991void ocsp_update_response_stline_cb(struct httpclient *hc)
992{
993 struct task *task = hc->caller;
994
995 if (!task)
996 return;
997
998 ssl_ocsp_task_ctx.flags |= HC_F_RES_STLINE;
999 task_wakeup(task, TASK_WOKEN_MSG);
1000}
1001
1002void ocsp_update_response_headers_cb(struct httpclient *hc)
1003{
1004 struct task *task = hc->caller;
1005
1006 if (!task)
1007 return;
1008
1009 ssl_ocsp_task_ctx.flags |= HC_F_RES_HDR;
1010 task_wakeup(task, TASK_WOKEN_MSG);
1011}
1012
1013void ocsp_update_response_body_cb(struct httpclient *hc)
1014{
1015 struct task *task = hc->caller;
1016
1017 if (!task)
1018 return;
1019
1020 ssl_ocsp_task_ctx.flags |= HC_F_RES_BODY;
1021 task_wakeup(task, TASK_WOKEN_MSG);
1022}
1023
1024void ocsp_update_response_end_cb(struct httpclient *hc)
1025{
1026 struct task *task = hc->caller;
1027
1028 if (!task)
1029 return;
1030
1031 ssl_ocsp_task_ctx.flags |= HC_F_RES_END;
1032 task_wakeup(task, TASK_WOKEN_MSG);
1033}
1034
Remi Tricot-Le Bretonb33fe2f2023-02-28 17:46:25 +01001035
1036/*
1037 * Send a log line that will use the dedicated proxy's error_logformat string.
1038 * It uses the sess_log function instead of app_log for instance in order to
1039 * benefit from the "generic" items that can be added to a log format line such
1040 * as the date and frontend name that can be found at the beginning of the
1041 * ocspupdate_log_format line.
1042 */
1043static void ssl_ocsp_send_log()
1044{
1045 if (!ssl_ocsp_task_ctx.appctx)
1046 return;
1047
1048 sess_log(ssl_ocsp_task_ctx.appctx->sess);
1049}
1050
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001051/*
1052 * This is the main function of the ocsp auto update mechanism. It has two
1053 * distinct parts and the branching to one or the other is completely based on
1054 * the fact that the cur_ocsp pointer of the ssl_ocsp_task_ctx member is set.
1055 *
1056 * If the pointer is not set, we need to look at the first item of the update
1057 * tree and see if it needs to be updated. If it does not we simply wait until
1058 * the time is right and let the task asleep. If it does need to be updated, we
1059 * simply build and send the corresponding ocsp request thanks to the
1060 * http_client. The task is then sent to sleep with an expire time set to
1061 * infinity. The http_client will wake it back up once the response is received
1062 * (or a timeout occurs). Just note that during this whole process the
1063 * cetificate_ocsp object corresponding to the entry being updated is taken out
1064 * of the update tree and only stored in the ssl_ocsp_task_ctx context.
1065 *
1066 * Once the task is waken up by the http_client, it branches on the response
1067 * processing part of the function which basically checks that the response is
1068 * valid and inserts it into the ocsp_response tree. The task then goes back to
1069 * sleep until another entry needs to be updated.
1070 */
1071static struct task *ssl_ocsp_update_responses(struct task *task, void *context, unsigned int state)
1072{
Remi Tricot-Le Breton10f113e2023-01-12 09:49:11 +01001073 unsigned int next_wakeup = 0;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001074 struct eb64_node *eb;
1075 struct certificate_ocsp *ocsp;
1076 struct httpclient *hc = NULL;
1077 struct buffer *req_url = NULL;
1078 struct buffer *req_body = NULL;
1079 OCSP_CERTID *certid = NULL;
1080 struct ssl_ocsp_task_ctx *ctx = &ssl_ocsp_task_ctx;
1081
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001082 if (ctx->cur_ocsp) {
1083 /* An update is in process */
1084 ocsp = ctx->cur_ocsp;
1085 hc = ctx->hc;
1086 if (ctx->flags & HC_F_RES_STLINE) {
1087 if (hc->res.status != 200) {
Remi Tricot-Le Bretonad6cba82023-02-28 17:46:21 +01001088 ctx->update_status = OCSP_UPDT_ERR_HTTP_STATUS;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001089 goto http_error;
1090 }
1091 ctx->flags &= ~HC_F_RES_STLINE;
1092 }
1093
1094 if (ctx->flags & HC_F_RES_HDR) {
1095 struct http_hdr *hdr;
1096 int found = 0;
1097 /* Look for "Content-Type" header which should have
1098 * "application/ocsp-response" value. */
1099 for (hdr = hc->res.hdrs; isttest(hdr->v); hdr++) {
1100 if (isteqi(hdr->n, ist("Content-Type")) &&
1101 isteqi(hdr->v, ist("application/ocsp-response"))) {
1102 found = 1;
1103 break;
1104 }
1105 }
1106 if (!found) {
Remi Tricot-Le Bretonad6cba82023-02-28 17:46:21 +01001107 ctx->update_status = OCSP_UPDT_ERR_HTTP_HDR;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001108 goto http_error;
1109 }
1110 ctx->flags &= ~HC_F_RES_HDR;
1111 }
1112
1113 /* If the HC_F_RES_BODY is set, we still need for the
1114 * HC_F_RES_END flag to be set as well in order to be sure that
1115 * the body is complete. */
1116
1117 /* we must close only if F_RES_END is the last flag */
1118 if (ctx->flags & HC_F_RES_END) {
1119
1120 /* Process the body that must be complete since
1121 * HC_F_RES_END is set. */
1122 if (ctx->flags & HC_F_RES_BODY) {
Remi Tricot-Le Bretonad6cba82023-02-28 17:46:21 +01001123 if (ssl_ocsp_check_response(ocsp->chain, ocsp->issuer, &hc->res.buf, NULL)) {
1124 ctx->update_status = OCSP_UPDT_ERR_CHECK;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001125 goto http_error;
Remi Tricot-Le Bretonad6cba82023-02-28 17:46:21 +01001126 }
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001127
1128 if (ssl_sock_update_ocsp_response(&hc->res.buf, NULL) != 0) {
Remi Tricot-Le Bretonad6cba82023-02-28 17:46:21 +01001129 ctx->update_status = OCSP_UPDT_ERR_INSERT;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001130 goto http_error;
1131 }
1132
1133 ctx->flags &= ~HC_F_RES_BODY;
1134 }
1135
1136 ctx->flags &= ~HC_F_RES_END;
1137
Remi Tricot-Le Breton9e94df32023-02-28 17:46:20 +01001138 ++ocsp->num_success;
Remi Tricot-Le Breton56ab6072023-03-02 15:49:54 +01001139 ocsp->last_update = date.tv_sec;
Remi Tricot-Le Bretonad6cba82023-02-28 17:46:21 +01001140 ctx->update_status = OCSP_UPDT_OK;
1141 ocsp->last_update_status = ctx->update_status;
Remi Tricot-Le Breton9e94df32023-02-28 17:46:20 +01001142
Remi Tricot-Le Bretonb33fe2f2023-02-28 17:46:25 +01001143 ssl_ocsp_send_log();
1144
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001145 /* Reinsert the entry into the update list so that it can be updated later */
1146 ssl_ocsp_update_insert(ocsp);
Remi Tricot-Le Breton57f60c22023-01-09 12:02:42 +01001147 /* Release the reference kept on the updated ocsp response. */
1148 ssl_sock_free_ocsp(ctx->cur_ocsp);
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001149 ctx->cur_ocsp = NULL;
1150
1151 HA_SPIN_LOCK(OCSP_LOCK, &ocsp_tree_lock);
1152 /* Set next_wakeup to the new first entry of the tree */
1153 eb = eb64_first(&ocsp_update_tree);
1154 if (eb) {
Remi Tricot-Le Breton56ab6072023-03-02 15:49:54 +01001155 if (eb->key > date.tv_sec)
1156 next_wakeup = (eb->key - date.tv_sec)*1000;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001157 else
1158 next_wakeup = 0;
1159 }
1160 HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
1161 goto leave;
1162 }
1163
1164 /* We did not receive the HC_F_RES_END flag yet, wait for it
1165 * before trying to update a new ocsp response. */
1166 goto wait;
1167 } else {
1168 /* Look for next entry that needs to be updated. */
1169 const unsigned char *p = NULL;
1170
1171 HA_SPIN_LOCK(OCSP_LOCK, &ocsp_tree_lock);
1172
1173 eb = eb64_first(&ocsp_update_tree);
1174 if (!eb) {
1175 HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
Remi Tricot-Le Breton1c647ad2023-01-12 09:49:10 +01001176 goto wait;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001177 }
1178
Remi Tricot-Le Breton56ab6072023-03-02 15:49:54 +01001179 if (eb->key > date.tv_sec) {
1180 next_wakeup = (eb->key - date.tv_sec)*1000;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001181 HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
1182 goto leave;
1183 }
1184
1185 ocsp = eb64_entry(eb, struct certificate_ocsp, next_update);
1186
1187 /* Take the current entry out of the update tree, it will be
1188 * reinserted after the response is processed. */
1189 eb64_delete(&ocsp->next_update);
1190
Remi Tricot-Le Breton57f60c22023-01-09 12:02:42 +01001191 ++ocsp->refcount;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001192 ctx->cur_ocsp = ocsp;
Remi Tricot-Le Bretonad6cba82023-02-28 17:46:21 +01001193 ocsp->last_update_status = OCSP_UPDT_UNKNOWN;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001194
1195 HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
1196
1197 req_url = alloc_trash_chunk();
1198 if (!req_url) {
1199 goto leave;
1200 }
1201 req_body = alloc_trash_chunk();
1202 if (!req_body) {
1203 goto leave;
1204 }
1205
1206 p = ocsp->key_data;
1207
1208 d2i_OCSP_CERTID(&certid, &p, ocsp->key_length);
1209 if (!certid)
1210 goto leave;
1211
1212 /* Copy OCSP URI stored in ocsp structure into req_url */
1213 chunk_cpy(req_url, ocsp->uri);
1214
1215 /* Create ocsp request */
1216 if (ssl_ocsp_create_request_details(certid, req_url, req_body, NULL) != 0) {
1217 goto leave;
1218 }
1219
1220 /* Depending on the processing that occurred in
1221 * ssl_ocsp_create_request_details we could either have to send
1222 * a GET or a POST request. */
Remi Tricot-Le Bretonb33fe2f2023-02-28 17:46:25 +01001223 hc = httpclient_new_from_proxy(httpclient_ocsp_update_px, task,
1224 b_data(req_body) ? HTTP_METH_POST : HTTP_METH_GET,
1225 ist2(b_orig(req_url), b_data(req_url)));
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001226 if (!hc) {
1227 goto leave;
1228 }
1229
1230 if (httpclient_req_gen(hc, hc->req.url, hc->req.meth,
1231 b_data(req_body) ? ocsp_request_hdrs : NULL,
William Lallemand70601c52022-12-22 14:34:01 +01001232 b_data(req_body) ? ist2(b_orig(req_body), b_data(req_body)) : IST_NULL) != ERR_NONE) {
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001233 goto leave;
1234 }
1235
1236 hc->ops.res_stline = ocsp_update_response_stline_cb;
1237 hc->ops.res_headers = ocsp_update_response_headers_cb;
1238 hc->ops.res_payload = ocsp_update_response_body_cb;
1239 hc->ops.res_end = ocsp_update_response_end_cb;
1240
Remi Tricot-Le Bretonad6cba82023-02-28 17:46:21 +01001241 if (!(ctx->appctx = httpclient_start(hc))) {
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001242 goto leave;
1243 }
1244
1245 ctx->flags = 0;
1246 ctx->hc = hc;
1247
1248 /* We keep the lock, this indicates that an update is in process. */
1249 goto wait;
1250 }
1251
1252leave:
1253 if (ctx->cur_ocsp) {
1254 /* Something went wrong, reinsert the entry in the tree. */
Remi Tricot-Le Breton9e94df32023-02-28 17:46:20 +01001255 ++ctx->cur_ocsp->num_failure;
Remi Tricot-Le Breton6de7b782023-02-28 17:46:19 +01001256 ssl_ocsp_update_insert_after_error(ctx->cur_ocsp);
Remi Tricot-Le Breton57f60c22023-01-09 12:02:42 +01001257 /* Release the reference kept on the updated ocsp response. */
1258 ssl_sock_free_ocsp(ctx->cur_ocsp);
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001259 ctx->cur_ocsp = NULL;
1260 }
1261 if (hc)
1262 httpclient_stop_and_destroy(hc);
1263 free_trash_chunk(req_url);
1264 free_trash_chunk(req_body);
1265 task->expire = tick_add(now_ms, next_wakeup);
1266 return task;
1267
1268wait:
1269 free_trash_chunk(req_url);
1270 free_trash_chunk(req_body);
1271 task->expire = TICK_ETERNITY;
1272 return task;
1273
1274http_error:
Remi Tricot-Le Bretonb33fe2f2023-02-28 17:46:25 +01001275 ssl_ocsp_send_log();
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001276 /* Reinsert certificate into update list so that it can be updated later */
Remi Tricot-Le Breton9e94df32023-02-28 17:46:20 +01001277 if (ocsp) {
1278 ++ocsp->num_failure;
Remi Tricot-Le Bretonad6cba82023-02-28 17:46:21 +01001279 ocsp->last_update_status = ctx->update_status;
Remi Tricot-Le Breton10f113e2023-01-12 09:49:11 +01001280 ssl_ocsp_update_insert_after_error(ocsp);
Remi Tricot-Le Breton9e94df32023-02-28 17:46:20 +01001281 }
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001282
1283 if (hc)
1284 httpclient_stop_and_destroy(hc);
Remi Tricot-Le Breton57f60c22023-01-09 12:02:42 +01001285 /* Release the reference kept on the updated ocsp response. */
1286 ssl_sock_free_ocsp(ctx->cur_ocsp);
Remi Tricot-Le Breton10f113e2023-01-12 09:49:11 +01001287 HA_SPIN_LOCK(OCSP_LOCK, &ocsp_tree_lock);
1288 /* Set next_wakeup to the new first entry of the tree */
1289 eb = eb64_first(&ocsp_update_tree);
1290 if (eb) {
Remi Tricot-Le Breton56ab6072023-03-02 15:49:54 +01001291 if (eb->key > date.tv_sec)
1292 next_wakeup = (eb->key - date.tv_sec)*1000;
Remi Tricot-Le Breton10f113e2023-01-12 09:49:11 +01001293 else
1294 next_wakeup = 0;
1295 }
1296 HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001297 ctx->cur_ocsp = NULL;
1298 ctx->hc = NULL;
1299 ctx->flags = 0;
1300 task->expire = tick_add(now_ms, next_wakeup);
1301 return task;
1302}
1303
Remi Tricot-Le Bretonb33fe2f2023-02-28 17:46:25 +01001304char ocspupdate_log_format[] = "%ci:%cp [%tr] %ft %[ssl_ocsp_certid] %[ssl_ocsp_status] %{+Q}[ssl_ocsp_status_str] %[ssl_ocsp_fail_cnt] %[ssl_ocsp_success_cnt]";
1305
1306/*
1307 * Initialize the proxy for the OCSP update HTTP client with 2 servers, one for
1308 * raw HTTP, the other for HTTPS.
1309 */
1310static int ssl_ocsp_update_precheck()
1311{
1312 /* initialize the OCSP update dedicated httpclient */
1313 httpclient_ocsp_update_px = httpclient_create_proxy("<HC_OCSP>");
1314 if (!httpclient_ocsp_update_px)
1315 return 1;
1316 httpclient_ocsp_update_px->conf.error_logformat_string = strdup(ocspupdate_log_format);
1317 httpclient_ocsp_update_px->conf.logformat_string = httpclient_log_format;
1318 httpclient_ocsp_update_px->options2 |= PR_O2_NOLOGNORM;
1319
1320 return 0;
1321}
1322
1323/* initialize the proxy and servers for the HTTP client */
1324
1325REGISTER_PRE_CHECK(ssl_ocsp_update_precheck);
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001326
1327
1328
1329struct ocsp_cli_ctx {
1330 struct httpclient *hc;
1331 struct ckch_data *ckch_data;
Remi Tricot-Le Breton15dc0e22023-01-09 12:02:41 +01001332 X509 *ocsp_issuer;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001333 uint flags;
1334 uint do_update;
1335};
1336
1337
1338void cli_ocsp_res_stline_cb(struct httpclient *hc)
1339{
1340 struct appctx *appctx = hc->caller;
1341 struct ocsp_cli_ctx *ctx;
1342
1343 if (!appctx)
1344 return;
1345
1346 ctx = appctx->svcctx;
1347 ctx->flags |= HC_F_RES_STLINE;
1348 appctx_wakeup(appctx);
1349}
1350
1351void cli_ocsp_res_headers_cb(struct httpclient *hc)
1352{
1353 struct appctx *appctx = hc->caller;
1354 struct ocsp_cli_ctx *ctx;
1355
1356 if (!appctx)
1357 return;
1358
1359 ctx = appctx->svcctx;
1360 ctx->flags |= HC_F_RES_HDR;
1361 appctx_wakeup(appctx);
1362}
1363
1364void cli_ocsp_res_body_cb(struct httpclient *hc)
1365{
1366 struct appctx *appctx = hc->caller;
1367 struct ocsp_cli_ctx *ctx;
1368
1369 if (!appctx)
1370 return;
1371
1372 ctx = appctx->svcctx;
1373 ctx->flags |= HC_F_RES_BODY;
1374 appctx_wakeup(appctx);
1375}
1376
1377void cli_ocsp_res_end_cb(struct httpclient *hc)
1378{
1379 struct appctx *appctx = hc->caller;
1380 struct ocsp_cli_ctx *ctx;
1381
1382 if (!appctx)
1383 return;
1384
1385 ctx = appctx->svcctx;
1386 ctx->flags |= HC_F_RES_END;
1387 appctx_wakeup(appctx);
1388}
1389
1390static int cli_parse_update_ocsp_response(char **args, char *payload, struct appctx *appctx, void *private)
1391{
1392 int errcode = 0;
1393 char *err = NULL;
1394 struct ckch_store *ckch_store = NULL;
1395 X509 *cert = NULL;
1396 struct ocsp_cli_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
1397 struct httpclient *hc = NULL;
1398 struct buffer *req_url = NULL;
1399 struct buffer *req_body = NULL;
1400 OCSP_CERTID *certid = NULL;
1401
1402 if (!*args[3]) {
1403 memprintf(&err, "'update ssl ocsp-response' expects a filename\n");
1404 return cli_dynerr(appctx, err);
1405 }
1406
1407 req_url = alloc_trash_chunk();
1408 if (!req_url) {
1409 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
1410 errcode |= ERR_ALERT | ERR_FATAL;
1411 goto end;
1412 }
1413
1414 req_body = alloc_trash_chunk();
1415 if (!req_body) {
1416 memprintf(&err, "%sCan't allocate memory\n", err ? err : "");
1417 errcode |= ERR_ALERT | ERR_FATAL;
1418 goto end;
1419 }
1420
1421 /* The operations on the CKCH architecture are locked so we can
1422 * manipulate ckch_store and ckch_inst */
1423 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock)) {
1424 memprintf(&err, "%sCan't update the certificate!\nOperations on certificates are currently locked!\n", err ? err : "");
1425 errcode |= ERR_ALERT | ERR_FATAL;
1426 goto end;
1427 }
1428
1429 ckch_store = ckchs_lookup(args[3]);
1430
1431 if (!ckch_store) {
Remi Tricot-Le Breton14419eb2023-01-09 12:02:49 +01001432 memprintf(&err, "%sUnknown certificate! 'update ssl ocsp-response' expects an already known certificate file name.\n", err ? err : "");
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001433 errcode |= ERR_ALERT | ERR_FATAL;
1434 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1435 goto end;
1436 }
1437
1438 ctx->ckch_data = ckch_store->data;
1439
1440 cert = ckch_store->data->cert;
1441
1442 if (ssl_ocsp_get_uri_from_cert(cert, req_url, &err)) {
1443 errcode |= ERR_ALERT | ERR_FATAL;
1444 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1445 goto end;
1446 }
1447
Remi Tricot-Le Breton15dc0e22023-01-09 12:02:41 +01001448 /* Look for the ocsp issuer in the ckch_data or in the certificate
1449 * chain, the same way it is done in ssl_sock_load_ocsp. */
1450 ctx->ocsp_issuer = ctx->ckch_data->ocsp_issuer;
1451
1452 /* take issuer from chain over ocsp_issuer, is what is done historicaly */
1453 if (ctx->ckch_data->chain) {
1454 int i = 0;
1455 /* check if one of the certificate of the chain is the issuer */
1456 for (i = 0; i < sk_X509_num(ctx->ckch_data->chain); i++) {
1457 X509 *ti = sk_X509_value(ctx->ckch_data->chain, i);
1458 if (X509_check_issued(ti, cert) == X509_V_OK) {
1459 ctx->ocsp_issuer = ti;
1460 break;
1461 }
1462 }
1463 }
1464
1465 if (!ctx->ocsp_issuer) {
1466 memprintf(&err, "%sOCSP issuer not found\n", err ? err : "");
1467 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1468 goto end;
1469 }
1470
1471 X509_up_ref(ctx->ocsp_issuer);
1472
1473 certid = OCSP_cert_to_id(NULL, cert, ctx->ocsp_issuer);
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001474 if (certid == NULL) {
1475 memprintf(&err, "%sOCSP_cert_to_id() error\n", err ? err : "");
1476 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1477 goto end;
1478 }
1479
1480 /* From here on the lock is not needed anymore. */
1481 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1482
1483 /* Create ocsp request */
1484 if (ssl_ocsp_create_request_details(certid, req_url, req_body, &err) != 0) {
1485 memprintf(&err, "%sCreate ocsp request error\n", err ? err : "");
1486 goto end;
1487 }
1488
Remi Tricot-Le Bretonb33fe2f2023-02-28 17:46:25 +01001489 hc = httpclient_new_from_proxy(httpclient_ocsp_update_px, appctx,
1490 b_data(req_body) ? HTTP_METH_POST : HTTP_METH_GET,
1491 ist2(b_orig(req_url), b_data(req_url)));
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001492 if (!hc) {
1493 memprintf(&err, "%sCan't allocate httpclient\n", err ? err : "");
1494 goto end;
1495 }
1496
1497 if (httpclient_req_gen(hc, hc->req.url, hc->req.meth, b_data(req_body) ? ocsp_request_hdrs : NULL,
1498 ist2(b_orig(req_body), b_data(req_body))) != ERR_NONE) {
1499 memprintf(&err, "%shttpclient_req_gen() error\n", err ? err : "");
1500 goto end;
1501 }
1502
1503 hc->ops.res_stline = cli_ocsp_res_stline_cb;
1504 hc->ops.res_headers = cli_ocsp_res_headers_cb;
1505 hc->ops.res_payload = cli_ocsp_res_body_cb;
1506 hc->ops.res_end = cli_ocsp_res_end_cb;
1507
1508 ctx->hc = hc; /* store the httpclient ptr in the applet */
1509 ctx->flags = 0;
1510
1511 if (!httpclient_start(hc)) {
1512 memprintf(&err, "%shttpclient_start() error\n", err ? err : "");
1513 goto end;
1514 }
1515
1516 free_trash_chunk(req_url);
Remi Tricot-Le Breton305a4f32023-01-23 15:57:13 +01001517 free_trash_chunk(req_body);
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001518
1519 return 0;
1520
1521end:
1522 free_trash_chunk(req_url);
Remi Tricot-Le Breton305a4f32023-01-23 15:57:13 +01001523 free_trash_chunk(req_body);
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001524
1525 if (errcode & ERR_CODE) {
1526 return cli_dynerr(appctx, memprintf(&err, "%sCan't send ocsp request for %s!\n", err ? err : "", args[3]));
1527 }
1528 return cli_dynmsg(appctx, LOG_NOTICE, err);
1529}
1530
1531static int cli_io_handler_update_ocsp_response(struct appctx *appctx)
1532{
1533 struct ocsp_cli_ctx *ctx = appctx->svcctx;
1534 struct httpclient *hc = ctx->hc;
1535
1536 if (ctx->flags & HC_F_RES_STLINE) {
1537 if (hc->res.status != 200) {
1538 chunk_printf(&trash, "OCSP response error (status %d)\n", hc->res.status);
1539 if (applet_putchk(appctx, &trash) == -1)
1540 goto more;
1541 goto end;
1542 }
1543 ctx->flags &= ~HC_F_RES_STLINE;
1544 }
1545
1546 if (ctx->flags & HC_F_RES_HDR) {
1547 struct http_hdr *hdr;
1548 int found = 0;
1549 /* Look for "Content-Type" header which should have
1550 * "application/ocsp-response" value. */
1551 for (hdr = hc->res.hdrs; isttest(hdr->v); hdr++) {
1552 if (isteqi(hdr->n, ist("Content-Type")) &&
1553 isteqi(hdr->v, ist("application/ocsp-response"))) {
1554 found = 1;
1555 break;
1556 }
1557 }
1558 if (!found) {
Remi Tricot-Le Breton083b2302023-01-23 15:57:14 +01001559 chunk_printf(&trash, "Missing 'Content-Type: application/ocsp-response' header\n");
1560 if (applet_putchk(appctx, &trash) == -1)
1561 goto more;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001562 goto end;
1563 }
1564 ctx->flags &= ~HC_F_RES_HDR;
1565 }
1566
1567 if (ctx->flags & HC_F_RES_BODY) {
1568 /* Wait until the full body is received and HC_F_RES_END flag is
1569 * set. */
1570 }
1571
1572 /* we must close only if F_END is the last flag */
1573 if (ctx->flags & HC_F_RES_END) {
1574 char *err = NULL;
1575
Remi Tricot-Le Breton15dc0e22023-01-09 12:02:41 +01001576 if (ssl_ocsp_check_response(ctx->ckch_data->chain, ctx->ocsp_issuer, &hc->res.buf, &err)) {
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001577 chunk_printf(&trash, "%s", err);
Remi Tricot-Le Breton305a4f32023-01-23 15:57:13 +01001578 free(err);
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001579 if (applet_putchk(appctx, &trash) == -1)
1580 goto more;
1581 goto end;
1582 }
1583
1584 if (ssl_sock_update_ocsp_response(&hc->res.buf, &err) != 0) {
1585 chunk_printf(&trash, "%s", err);
Remi Tricot-Le Breton305a4f32023-01-23 15:57:13 +01001586 free(err);
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001587 if (applet_putchk(appctx, &trash) == -1)
1588 goto more;
1589 goto end;
1590 }
1591
Remi Tricot-Le Breton305a4f32023-01-23 15:57:13 +01001592 free(err);
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001593 chunk_reset(&trash);
1594
1595 if (ssl_ocsp_response_print(&hc->res.buf, &trash))
1596 goto end;
1597
1598 if (applet_putchk(appctx, &trash) == -1)
1599 goto more;
1600 ctx->flags &= ~HC_F_RES_BODY;
1601 ctx->flags &= ~HC_F_RES_END;
1602 goto end;
1603 }
1604
1605more:
1606 if (!ctx->flags)
1607 applet_have_no_more_data(appctx);
1608 return 0;
1609end:
1610 return 1;
1611}
1612
1613static void cli_release_update_ocsp_response(struct appctx *appctx)
1614{
1615 struct ocsp_cli_ctx *ctx = appctx->svcctx;
1616 struct httpclient *hc = ctx->hc;
1617
Remi Tricot-Le Breton71237a12023-01-10 11:21:40 +01001618 X509_free(ctx->ocsp_issuer);
Remi Tricot-Le Breton15dc0e22023-01-09 12:02:41 +01001619
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001620 /* Everything possible was printed on the CLI, we can destroy the client */
1621 httpclient_stop_and_destroy(hc);
1622
1623 return;
1624}
1625
1626
1627#endif /* !defined OPENSSL_IS_BORINGSSL */
1628
1629
1630#endif /* (defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) */
1631
1632
1633static int cli_parse_set_ocspresponse(char **args, char *payload, struct appctx *appctx, void *private)
1634{
1635#if (defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP)
1636 char *err = NULL;
1637 int i, j, ret;
1638
1639 if (!payload)
1640 payload = args[3];
1641
1642 /* Expect one parameter: the new response in base64 encoding */
1643 if (!*payload)
1644 return cli_err(appctx, "'set ssl ocsp-response' expects response in base64 encoding.\n");
1645
1646 /* remove \r and \n from the payload */
1647 for (i = 0, j = 0; payload[i]; i++) {
1648 if (payload[i] == '\r' || payload[i] == '\n')
1649 continue;
1650 payload[j++] = payload[i];
1651 }
1652 payload[j] = 0;
1653
1654 ret = base64dec(payload, j, trash.area, trash.size);
1655 if (ret < 0)
1656 return cli_err(appctx, "'set ssl ocsp-response' received invalid base64 encoded response.\n");
1657
1658 trash.data = ret;
1659 if (ssl_sock_update_ocsp_response(&trash, &err)) {
1660 if (err)
1661 return cli_dynerr(appctx, memprintf(&err, "%s.\n", err));
1662 else
1663 return cli_err(appctx, "Failed to update OCSP response.\n");
1664 }
1665
1666 return cli_msg(appctx, LOG_INFO, "OCSP Response updated!\n");
1667#else
1668 return cli_err(appctx, "HAProxy was compiled against a version of OpenSSL that doesn't support OCSP stapling.\n");
1669#endif
1670
1671}
1672
1673/* parsing function for 'show ssl ocsp-response [id]'. If an entry is forced,
1674 * it's set into appctx->svcctx.
1675 */
1676static int cli_parse_show_ocspresponse(char **args, char *payload, struct appctx *appctx, void *private)
1677{
1678#if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) && !defined OPENSSL_IS_BORINGSSL)
Remi Tricot-Le Breton9c4437d2023-02-28 17:46:28 +01001679
1680 struct show_ocspresp_cli_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
1681 int certid_arg_idx = 3;
1682
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001683 if (*args[3]) {
1684 struct certificate_ocsp *ocsp = NULL;
1685 char key[OCSP_MAX_CERTID_ASN1_LENGTH] = {};
1686 int key_length = OCSP_MAX_CERTID_ASN1_LENGTH;
1687 char *key_ptr = key;
1688
Remi Tricot-Le Breton9c4437d2023-02-28 17:46:28 +01001689 if (strcmp(args[3], "text") == 0) {
1690 ctx->format = SHOW_OCSPRESP_FMT_TEXT;
1691 ++certid_arg_idx;
1692 } else if (strcmp(args[3], "base64") == 0) {
1693 ctx->format = SHOW_OCSPRESP_FMT_B64;
1694 ++certid_arg_idx;
1695 }
1696
1697 if (ctx->format != SHOW_OCSPRESP_FMT_DFLT && !*args[certid_arg_idx])
1698 return cli_err(appctx, "'show ssl ocsp-response [text|base64]' expects a valid certid.\n");
1699
1700 if (strlen(args[certid_arg_idx]) > OCSP_MAX_CERTID_ASN1_LENGTH*2) {
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001701 return cli_err(appctx, "'show ssl ocsp-response' received a too big key.\n");
1702 }
1703
Remi Tricot-Le Breton9c4437d2023-02-28 17:46:28 +01001704 if (!parse_binary(args[certid_arg_idx], &key_ptr, &key_length, NULL)) {
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001705 return cli_err(appctx, "'show ssl ocsp-response' received an invalid key.\n");
1706 }
1707
1708 HA_SPIN_LOCK(OCSP_LOCK, &ocsp_tree_lock);
1709 ocsp = (struct certificate_ocsp *)ebmb_lookup(&cert_ocsp_tree, key, OCSP_MAX_CERTID_ASN1_LENGTH);
1710
1711 if (!ocsp) {
1712 HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
1713 return cli_err(appctx, "Certificate ID does not match any certificate.\n");
1714 }
1715 ++ocsp->refcount;
1716 HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
1717
Remi Tricot-Le Breton9c4437d2023-02-28 17:46:28 +01001718 ctx->ocsp = ocsp;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001719 appctx->io_handler = cli_io_handler_show_ocspresponse_detail;
1720 }
1721
1722 return 0;
1723
1724#else
1725 return cli_err(appctx, "HAProxy was compiled against a version of OpenSSL that doesn't support OCSP stapling.\n");
1726#endif
1727}
1728
1729/*
1730 * IO handler of "show ssl ocsp-response". The command taking a specific ID
1731 * is managed in cli_io_handler_show_ocspresponse_detail.
1732 * The current entry is taken from appctx->svcctx.
1733 */
1734static int cli_io_handler_show_ocspresponse(struct appctx *appctx)
1735{
1736#if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) && !defined OPENSSL_IS_BORINGSSL)
1737 struct buffer *trash = alloc_trash_chunk();
1738 struct buffer *tmp = NULL;
1739 struct ebmb_node *node;
1740 struct certificate_ocsp *ocsp = NULL;
1741 BIO *bio = NULL;
1742 int write = -1;
Remi Tricot-Le Breton9c4437d2023-02-28 17:46:28 +01001743 struct show_ocspresp_cli_ctx *ctx = appctx->svcctx;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001744
1745 if (trash == NULL)
1746 return 1;
1747
1748 HA_SPIN_LOCK(OCSP_LOCK, &ocsp_tree_lock);
1749
1750 tmp = alloc_trash_chunk();
1751 if (!tmp)
1752 goto end;
1753
1754 if ((bio = BIO_new(BIO_s_mem())) == NULL)
1755 goto end;
1756
Remi Tricot-Le Breton9c4437d2023-02-28 17:46:28 +01001757 if (!ctx->ocsp) {
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001758 chunk_appendf(trash, "# Certificate IDs\n");
1759 node = ebmb_first(&cert_ocsp_tree);
1760 } else {
Remi Tricot-Le Breton9c4437d2023-02-28 17:46:28 +01001761 node = &ctx->ocsp->key;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001762 }
1763
1764 while (node) {
1765 OCSP_CERTID *certid = NULL;
1766 const unsigned char *p = NULL;
1767 int i;
1768
1769 ocsp = ebmb_entry(node, struct certificate_ocsp, key);
1770
1771 /* Dump the key in hexadecimal */
1772 chunk_appendf(trash, "Certificate ID key : ");
1773 for (i = 0; i < ocsp->key_length; ++i) {
1774 chunk_appendf(trash, "%02x", ocsp->key_data[i]);
1775 }
1776 chunk_appendf(trash, "\n");
1777
1778 p = ocsp->key_data;
1779
1780 /* Decode the certificate ID (serialized into the key). */
1781 d2i_OCSP_CERTID(&certid, &p, ocsp->key_length);
1782 if (!certid)
1783 goto end;
1784
1785 /* Dump the CERTID info */
1786 ocsp_certid_print(bio, certid, 1);
1787 OCSP_CERTID_free(certid);
1788 write = BIO_read(bio, tmp->area, tmp->size-1);
1789 /* strip trailing LFs */
1790 while (write > 0 && tmp->area[write-1] == '\n')
1791 write--;
1792 tmp->area[write] = '\0';
1793
1794 chunk_appendf(trash, "%s\n", tmp->area);
1795
1796 node = ebmb_next(node);
1797 if (applet_putchk(appctx, trash) == -1)
1798 goto yield;
1799 }
1800
1801end:
1802 HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001803 free_trash_chunk(trash);
1804 free_trash_chunk(tmp);
1805 BIO_free(bio);
1806 return 1;
1807
1808yield:
1809 free_trash_chunk(trash);
1810 free_trash_chunk(tmp);
1811 BIO_free(bio);
1812
1813 ++ocsp->refcount;
Remi Tricot-Le Breton9c4437d2023-02-28 17:46:28 +01001814 ctx->ocsp = ocsp;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001815 HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
1816 return 0;
1817#else
1818 return cli_err(appctx, "HAProxy was compiled against a version of OpenSSL that doesn't support OCSP stapling.\n");
1819#endif
1820}
1821
William Lallemanda14686d2023-02-07 18:38:05 +01001822/* Check if the ckch_store and the entry does have the same configuration */
1823int ocsp_update_check_cfg_consistency(struct ckch_store *store, struct crtlist_entry *entry, char *crt_path, char **err)
1824{
1825 int err_code = ERR_NONE;
1826
1827 if (store->data->ocsp_update_mode != SSL_SOCK_OCSP_UPDATE_DFLT || entry->ssl_conf) {
1828 if ((!entry->ssl_conf && store->data->ocsp_update_mode == SSL_SOCK_OCSP_UPDATE_ON)
1829 || (entry->ssl_conf && store->data->ocsp_update_mode != entry->ssl_conf->ocsp_update)) {
1830 memprintf(err, "%sIncompatibilities found in OCSP update mode for certificate %s\n", err && *err ? *err : "", crt_path);
1831 err_code |= ERR_ALERT | ERR_FATAL;
1832 }
1833 }
1834 return err_code;
1835}
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001836
Remi Tricot-Le Bretond14fc512023-02-28 17:46:23 +01001837struct show_ocsp_updates_ctx {
1838 struct certificate_ocsp *cur_ocsp;
1839};
1840
1841/*
1842 * Parsing function for 'show ssl ocsp-updates [nb]'.
1843 */
1844static int cli_parse_show_ocsp_updates(char **args, char *payload, struct appctx *appctx, void *private)
1845{
1846#if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) && !defined OPENSSL_IS_BORINGSSL)
1847 struct show_ocsp_updates_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
1848
1849 HA_SPIN_LOCK(OCSP_LOCK, &ocsp_tree_lock);
1850
1851 return 0;
1852#else
1853 return cli_err(appctx, "HAProxy was compiled against a version of OpenSSL that doesn't support OCSP stapling.\n");
1854#endif
1855}
1856
1857/*
1858 * Dump information about an ocsp response concerning ocsp auto update.
1859 * It follows the following format :
1860 * OCSP Certid | Path | Next Update | Last Update | Successes | Failures | Last Update Status | Last Update Status (str)
1861 * Return 0 in case of success.
1862 */
1863static int dump_ocsp_update_info(struct certificate_ocsp *ocsp, struct buffer *out)
1864{
1865 struct tm tm = {};
1866 char *ret;
1867 int i;
1868 time_t next_update;
1869
1870 /* Dump OCSP certid */
1871 for (i = 0; i < ocsp->key_length; ++i) {
1872 chunk_appendf(out, "%02x", ocsp->key_data[i]);
1873 }
1874
1875 chunk_appendf(out, " | ");
1876
1877 /* Dump path */
1878 chunk_appendf(out, "%s", ocsp->path);
1879
1880 chunk_appendf(out, " | ");
1881
1882 /* Dump next update time */
1883 if (ocsp->next_update.key != 0) {
1884 next_update = ocsp->next_update.key;
1885 get_localtime(ocsp->next_update.key, &tm);
1886 } else {
1887 next_update = date.tv_sec;
1888 get_localtime(date.tv_sec, &tm);
1889 }
1890 ret = localdate2str_log(b_orig(out)+b_data(out), next_update, &tm, b_size(out)-b_data(out));
1891
1892 if (ret == NULL)
1893 return 1;
1894
1895 out->data = (ret - out->area);
1896
1897 chunk_appendf(out, " | ");
1898
1899 /* Dump last update time or "-" if no update occurred yet */
1900 if (ocsp->last_update) {
1901 get_localtime(ocsp->last_update, &tm);
1902 ret = localdate2str_log(b_orig(out)+b_data(out), ocsp->last_update, &tm, b_size(out)-b_data(out));
1903
1904 if (ret == NULL)
1905 return 1;
1906
1907 out->data = (ret - out->area);
1908 } else
1909 chunk_appendf(out, "-");
1910
1911 chunk_appendf(out, " | ");
1912
1913 /* Number of successful updates */
1914 chunk_appendf(out, "%d", ocsp->num_success);
1915
1916 chunk_appendf(out, " | ");
1917
1918 /* Number of failed updates */
1919 chunk_appendf(out, "%d", ocsp->num_failure);
1920
1921 chunk_appendf(out, " | ");
1922
1923 /* Last update status */
1924 chunk_appendf(out, "%d", ocsp->last_update_status);
1925
1926 chunk_appendf(out, " | ");
1927
1928 /* Last update status str */
1929 if (ocsp->last_update_status >= OCSP_UPDT_ERR_LAST)
1930 chunk_appendf(out, "-");
1931 else
1932 chunk_appendf(out, "%s", istptr(ocsp_update_errors[ocsp->last_update_status]));
1933
1934 chunk_appendf(out, "\n");
1935
1936 return 0;
1937}
1938
1939static int cli_io_handler_show_ocsp_updates(struct appctx *appctx)
1940{
1941 struct show_ocsp_updates_ctx *ctx = appctx->svcctx;
1942 struct eb64_node *node;
1943 struct certificate_ocsp *ocsp = NULL;
1944 struct buffer *trash = get_trash_chunk();
1945
1946 if (!ctx->cur_ocsp) {
1947 node = eb64_first(&ocsp_update_tree);
1948 chunk_appendf(trash, "OCSP Certid | Path | Next Update | Last Update | Successes | Failures | Last Update Status | Last Update Status (str)\n");
1949
1950 /* Look for an entry currently being updated */
1951 ocsp = ssl_ocsp_task_ctx.cur_ocsp;
1952 if (ocsp) {
1953 if (dump_ocsp_update_info(ocsp, trash))
1954 goto end;
1955 }
1956
1957 if (applet_putchk(appctx, trash) == -1)
1958 goto yield;
1959
1960 } else {
1961 node = &((struct certificate_ocsp*)ctx->cur_ocsp)->next_update;
1962 }
1963
1964 while (node) {
1965 ocsp = eb64_entry(node, struct certificate_ocsp, next_update);
1966
1967 chunk_reset(trash);
1968 if (dump_ocsp_update_info(ocsp, trash))
1969 goto end;
1970
1971 if (applet_putchk(appctx, trash) == -1) {
1972 ctx->cur_ocsp = ocsp;
1973 goto yield;
1974 }
1975
1976 node = eb64_next(node);
1977 }
1978
1979end:
1980 return 1;
1981
1982yield:
1983 return 0; /* should come back */
1984}
1985
1986static void cli_release_show_ocsp_updates(struct appctx *appctx)
1987{
1988 HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
1989}
1990
1991
Remi Tricot-Le Bretond42c8962023-02-28 17:46:24 +01001992static int
1993smp_fetch_ssl_ocsp_certid(const struct arg *args, struct sample *smp, const char *kw, void *private)
1994{
1995 struct buffer *data = get_trash_chunk();
1996 struct certificate_ocsp *ocsp = ssl_ocsp_task_ctx.cur_ocsp;
1997
1998 if (!ocsp)
1999 return 0;
2000
2001 dump_binary(data, (char *)ocsp->key_data, ocsp->key_length);
2002
2003 smp->data.type = SMP_T_STR;
2004 smp->data.u.str = *data;
2005 return 1;
2006}
2007
2008static int
2009smp_fetch_ssl_ocsp_status(const struct arg *args, struct sample *smp, const char *kw, void *private)
2010{
2011 struct certificate_ocsp *ocsp = ssl_ocsp_task_ctx.cur_ocsp;
2012
2013 if (!ocsp)
2014 return 0;
2015
2016 smp->data.type = SMP_T_SINT;
2017 smp->data.u.sint = ssl_ocsp_task_ctx.update_status;
2018 return 1;
2019}
2020
2021static int
2022smp_fetch_ssl_ocsp_status_str(const struct arg *args, struct sample *smp, const char *kw, void *private)
2023{
2024 struct certificate_ocsp *ocsp = ssl_ocsp_task_ctx.cur_ocsp;
2025
2026 if (!ocsp)
2027 return 0;
2028
2029 if (ssl_ocsp_task_ctx.update_status >= OCSP_UPDT_ERR_LAST)
2030 return 0;
2031
2032 smp->data.type = SMP_T_STR;
2033 smp->data.u.str = ist2buf(ocsp_update_errors[ssl_ocsp_task_ctx.update_status]);
2034
2035 return 1;
2036}
2037
2038static int
2039smp_fetch_ssl_ocsp_fail_cnt(const struct arg *args, struct sample *smp, const char *kw, void *private)
2040{
2041 struct certificate_ocsp *ocsp = ssl_ocsp_task_ctx.cur_ocsp;
2042
2043 if (!ocsp)
2044 return 0;
2045
2046 smp->data.type = SMP_T_SINT;
2047 smp->data.u.sint = ocsp->num_failure;
2048 return 1;
2049}
2050
2051static int
2052smp_fetch_ssl_ocsp_success_cnt(const struct arg *args, struct sample *smp, const char *kw, void *private)
2053{
2054 struct certificate_ocsp *ocsp = ssl_ocsp_task_ctx.cur_ocsp;
2055
2056 if (!ocsp)
2057 return 0;
2058
2059 smp->data.type = SMP_T_SINT;
2060 smp->data.u.sint = ocsp->num_success;
2061 return 1;
2062}
2063
2064
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01002065static struct cli_kw_list cli_kws = {{ },{
Remi Tricot-Le Breton9c4437d2023-02-28 17:46:28 +01002066 { { "set", "ssl", "ocsp-response", NULL }, "set ssl ocsp-response <resp|payload> : update a certificate's OCSP Response from a base64-encode DER", cli_parse_set_ocspresponse, NULL },
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01002067
Remi Tricot-Le Breton9c4437d2023-02-28 17:46:28 +01002068 { { "show", "ssl", "ocsp-response", NULL },"show ssl ocsp-response [[text|base64] id] : display the IDs of the OCSP responses used in memory, or the details of a single OCSP response (in text or base64 format)", cli_parse_show_ocspresponse, cli_io_handler_show_ocspresponse, NULL },
2069 { { "show", "ssl", "ocsp-updates", NULL }, "show ssl ocsp-updates : display information about the next 'nb' ocsp responses that will be updated automatically", cli_parse_show_ocsp_updates, cli_io_handler_show_ocsp_updates, cli_release_show_ocsp_updates },
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01002070#if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) && !defined OPENSSL_IS_BORINGSSL)
Remi Tricot-Le Breton9c4437d2023-02-28 17:46:28 +01002071 { { "update", "ssl", "ocsp-response", NULL }, "update ssl ocsp-response <certfile> : send ocsp request and update stored ocsp response", cli_parse_update_ocsp_response, cli_io_handler_update_ocsp_response, cli_release_update_ocsp_response },
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01002072#endif
2073 { { NULL }, NULL, NULL, NULL }
2074}};
2075
2076INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws);
2077
Remi Tricot-Le Bretond42c8962023-02-28 17:46:24 +01002078
2079/* Note: must not be declared <const> as its list will be overwritten.
2080 * Please take care of keeping this list alphabetically sorted.
2081 *
2082 * Those fetches only have a valid value during an OCSP update process so they
2083 * can only be used in a log format of a log line built by the update process
2084 * task itself.
2085 */
2086static struct sample_fetch_kw_list sample_fetch_keywords = {ILH, {
2087 { "ssl_ocsp_certid", smp_fetch_ssl_ocsp_certid, 0, NULL, SMP_T_STR, SMP_USE_L5SRV },
2088 { "ssl_ocsp_status", smp_fetch_ssl_ocsp_status, 0, NULL, SMP_T_SINT, SMP_USE_L5SRV },
2089 { "ssl_ocsp_status_str", smp_fetch_ssl_ocsp_status_str, 0, NULL, SMP_T_STR, SMP_USE_L5SRV },
2090 { "ssl_ocsp_fail_cnt", smp_fetch_ssl_ocsp_fail_cnt, 0, NULL, SMP_T_SINT, SMP_USE_L5SRV },
2091 { "ssl_ocsp_success_cnt", smp_fetch_ssl_ocsp_success_cnt, 0, NULL, SMP_T_SINT, SMP_USE_L5SRV },
2092 { NULL, NULL, 0, 0, 0 },
2093}};
2094
2095INITCALL1(STG_REGISTER, sample_register_fetches, &sample_fetch_keywords);
2096
2097
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01002098/*
2099 * Local variables:
2100 * c-indent-level: 8
2101 * c-basic-offset: 8
2102 * End:
2103 */