blob: 5b103afd62543227e19813d0249478dd185eb4b1 [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
Remi Tricot-Le Bretona1b3d8e2024-02-07 16:38:41 +0100187/*
188 * Convert an OCSP_CERTID structure into a char buffer that can be used as a key
189 * in the OCSP response tree. It takes an <ocsp_cid> as parameter and builds a
190 * key of length <key_length> into the <certid> buffer. The key length cannot
191 * exceed OCSP_MAX_CERTID_ASN1_LENGTH bytes.
192 * Returns a negative value in case of error.
193 */
194int ssl_ocsp_build_response_key(OCSP_CERTID *ocsp_cid, unsigned char certid[OCSP_MAX_CERTID_ASN1_LENGTH], unsigned int *key_length)
195{
196 unsigned char *p = NULL;
197 int i;
198
199 if (!key_length)
200 return -1;
201
202 *key_length = 0;
203
204 if (!ocsp_cid)
205 return 0;
206
207 i = i2d_OCSP_CERTID(ocsp_cid, NULL);
208 if (!i || (i > OCSP_MAX_CERTID_ASN1_LENGTH))
209 return 0;
210
211 p = certid;
212 *key_length = i2d_OCSP_CERTID(ocsp_cid, &p);
213
214end:
215 return *key_length > 0;
216}
217
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100218/* This function starts to check if the OCSP response (in DER format) contained
219 * in chunk 'ocsp_response' is valid (else exits on error).
220 * If 'cid' is not NULL, it will be compared to the OCSP certificate ID
221 * contained in the OCSP Response and exits on error if no match.
222 * If it's a valid OCSP Response:
223 * If 'ocsp' is not NULL, the chunk is copied in the OCSP response's container
224 * pointed by 'ocsp'.
225 * If 'ocsp' is NULL, the function looks up into the OCSP response's
226 * containers tree (using as index the ASN1 form of the OCSP Certificate ID extracted
227 * from the response) and exits on error if not found. Finally, If an OCSP response is
228 * already present in the container, it will be overwritten.
229 *
230 * Note: OCSP response containing more than one OCSP Single response is not
231 * considered valid.
232 *
233 * Returns 0 on success, 1 in error case.
234 */
235int ssl_sock_load_ocsp_response(struct buffer *ocsp_response,
236 struct certificate_ocsp *ocsp,
237 OCSP_CERTID *cid, char **err)
238{
239 OCSP_RESPONSE *resp;
240 OCSP_BASICRESP *bs = NULL;
241 OCSP_SINGLERESP *sr;
242 OCSP_CERTID *id;
243 unsigned char *p = (unsigned char *) ocsp_response->area;
244 int rc , count_sr;
245 ASN1_GENERALIZEDTIME *revtime, *thisupd, *nextupd = NULL;
246 int reason;
247 int ret = 1;
248#ifdef HAVE_ASN1_TIME_TO_TM
249 struct tm nextupd_tm = {0};
250#endif
251
252 resp = d2i_OCSP_RESPONSE(NULL, (const unsigned char **)&p,
253 ocsp_response->data);
254 if (!resp) {
255 memprintf(err, "Unable to parse OCSP response");
256 goto out;
257 }
258
259 rc = OCSP_response_status(resp);
260 if (rc != OCSP_RESPONSE_STATUS_SUCCESSFUL) {
261 memprintf(err, "OCSP response status not successful");
262 goto out;
263 }
264
265 bs = OCSP_response_get1_basic(resp);
266 if (!bs) {
267 memprintf(err, "Failed to get basic response from OCSP Response");
268 goto out;
269 }
270
271 count_sr = OCSP_resp_count(bs);
272 if (count_sr > 1) {
273 memprintf(err, "OCSP response ignored because contains multiple single responses (%d)", count_sr);
274 goto out;
275 }
276
277 sr = OCSP_resp_get0(bs, 0);
278 if (!sr) {
279 memprintf(err, "Failed to get OCSP single response");
280 goto out;
281 }
282
283 id = (OCSP_CERTID*)OCSP_SINGLERESP_get0_id(sr);
284
285 rc = OCSP_single_get0_status(sr, &reason, &revtime, &thisupd, &nextupd);
286 if (rc != V_OCSP_CERTSTATUS_GOOD && rc != V_OCSP_CERTSTATUS_REVOKED) {
287 memprintf(err, "OCSP single response: certificate status is unknown");
288 goto out;
289 }
290
291 if (!nextupd) {
292 memprintf(err, "OCSP single response: missing nextupdate");
293 goto out;
294 }
295
296 rc = OCSP_check_validity(thisupd, nextupd, OCSP_MAX_RESPONSE_TIME_SKEW, -1);
297 if (!rc) {
298 memprintf(err, "OCSP single response: no longer valid.");
299 goto out;
300 }
301
302 if (cid) {
303 if (OCSP_id_cmp(id, cid)) {
304 memprintf(err, "OCSP single response: Certificate ID does not match certificate and issuer");
305 goto out;
306 }
307 }
308
309 if (!ocsp) {
310 unsigned char key[OCSP_MAX_CERTID_ASN1_LENGTH];
311 unsigned char *p;
312
313 rc = i2d_OCSP_CERTID(id, NULL);
314 if (!rc) {
315 memprintf(err, "OCSP single response: Unable to encode Certificate ID");
316 goto out;
317 }
318
319 if (rc > OCSP_MAX_CERTID_ASN1_LENGTH) {
320 memprintf(err, "OCSP single response: Certificate ID too long");
321 goto out;
322 }
323
324 p = key;
325 memset(key, 0, OCSP_MAX_CERTID_ASN1_LENGTH);
326 i2d_OCSP_CERTID(id, &p);
327 HA_SPIN_LOCK(OCSP_LOCK, &ocsp_tree_lock);
328 ocsp = (struct certificate_ocsp *)ebmb_lookup(&cert_ocsp_tree, key, OCSP_MAX_CERTID_ASN1_LENGTH);
329 if (!ocsp) {
330 HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
331 memprintf(err, "OCSP single response: Certificate ID does not match any certificate or issuer");
332 goto out;
333 }
334 HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
335 }
336
337 /* According to comments on "chunk_dup", the
338 previous chunk buffer will be freed */
339 if (!chunk_dup(&ocsp->response, ocsp_response)) {
340 memprintf(err, "OCSP response: Memory allocation error");
341 goto out;
342 }
343
344#ifdef HAVE_ASN1_TIME_TO_TM
345 if (ASN1_TIME_to_tm(nextupd, &nextupd_tm) == 0) {
346 memprintf(err, "OCSP single response: Invalid \"Next Update\" time");
347 goto out;
348 }
349 ocsp->expire = my_timegm(&nextupd_tm) - OCSP_MAX_RESPONSE_TIME_SKEW;
350#else
351 ocsp->expire = asn1_generalizedtime_to_epoch(nextupd) - OCSP_MAX_RESPONSE_TIME_SKEW;
352 if (ocsp->expire < 0) {
353 memprintf(err, "OCSP single response: Invalid \"Next Update\" time");
354 goto out;
355 }
356#endif
357
358 ret = 0;
359out:
360 ERR_clear_error();
361
362 if (bs)
363 OCSP_BASICRESP_free(bs);
364
365 if (resp)
366 OCSP_RESPONSE_free(resp);
367
368 return ret;
369}
370/*
371 * External function use to update the OCSP response in the OCSP response's
372 * containers tree. The chunk 'ocsp_response' must contain the OCSP response
373 * to update in DER format.
374 *
375 * Returns 0 on success, 1 in error case.
376 */
377int ssl_sock_update_ocsp_response(struct buffer *ocsp_response, char **err)
378{
379 return ssl_sock_load_ocsp_response(ocsp_response, NULL, NULL, err);
380}
381
382
383
384#if !defined OPENSSL_IS_BORINGSSL
385/*
386 * Decrease the refcount of the struct ocsp_response and frees it if it's not
387 * used anymore. Also removes it from the tree if free'd.
388 */
389void ssl_sock_free_ocsp(struct certificate_ocsp *ocsp)
390{
391 if (!ocsp)
392 return;
393
Remi Tricot-Le Breton57f60c22023-01-09 12:02:42 +0100394 HA_SPIN_LOCK(OCSP_LOCK, &ocsp_tree_lock);
William Lallemand60289bf2024-02-26 17:53:02 +0100395 ocsp->refcount--;
396 if (ocsp->refcount <= 0) {
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100397 ebmb_delete(&ocsp->key);
398 eb64_delete(&ocsp->next_update);
399 X509_free(ocsp->issuer);
400 ocsp->issuer = NULL;
401 sk_X509_pop_free(ocsp->chain, X509_free);
402 ocsp->chain = NULL;
403 chunk_destroy(&ocsp->response);
Remi Tricot-Le Breton648c83e2023-01-09 12:02:48 +0100404 if (ocsp->uri) {
405 ha_free(&ocsp->uri->area);
406 ha_free(&ocsp->uri);
407 }
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100408
409 free(ocsp);
410 }
Remi Tricot-Le Breton57f60c22023-01-09 12:02:42 +0100411 HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100412}
413
414
415/*
416 * This function dumps the details of an OCSP_CERTID. It is based on
417 * ocsp_certid_print in OpenSSL.
418 */
419static inline int ocsp_certid_print(BIO *bp, OCSP_CERTID *certid, int indent)
420{
421 ASN1_OCTET_STRING *piNameHash = NULL;
422 ASN1_OCTET_STRING *piKeyHash = NULL;
423 ASN1_INTEGER *pSerial = NULL;
424
425 if (OCSP_id_get0_info(&piNameHash, NULL, &piKeyHash, &pSerial, certid)) {
426
427 BIO_printf(bp, "%*sCertificate ID:\n", indent, "");
428 indent += 2;
429 BIO_printf(bp, "%*sIssuer Name Hash: ", indent, "");
430#ifndef USE_OPENSSL_WOLFSSL
431 i2a_ASN1_STRING(bp, piNameHash, 0);
432#else
433 wolfSSL_ASN1_STRING_print(bp, piNameHash);
434#endif
435 BIO_printf(bp, "\n%*sIssuer Key Hash: ", indent, "");
436#ifndef USE_OPENSSL_WOLFSSL
437 i2a_ASN1_STRING(bp, piKeyHash, 0);
438#else
439 wolfSSL_ASN1_STRING_print(bp, piNameHash);
440#endif
441 BIO_printf(bp, "\n%*sSerial Number: ", indent, "");
442 i2a_ASN1_INTEGER(bp, pSerial);
443 }
444 return 1;
445}
446
Remi Tricot-Le Breton9c4437d2023-02-28 17:46:28 +0100447
448enum {
449 SHOW_OCSPRESP_FMT_DFLT,
450 SHOW_OCSPRESP_FMT_TEXT,
451 SHOW_OCSPRESP_FMT_B64
452};
453
454struct show_ocspresp_cli_ctx {
455 struct certificate_ocsp *ocsp;
456 int format;
457};
458
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100459/*
460 * Dump the details about an OCSP response in DER format stored in
461 * <ocsp_response> into buffer <out>.
462 * Returns 0 in case of success.
463 */
464int ssl_ocsp_response_print(struct buffer *ocsp_response, struct buffer *out)
465{
466 BIO *bio = NULL;
467 int write = -1;
468 OCSP_RESPONSE *resp;
469 const unsigned char *p;
470 int retval = -1;
471
472 if (!ocsp_response)
473 return -1;
474
475 if ((bio = BIO_new(BIO_s_mem())) == NULL)
476 return -1;
477
478 p = (const unsigned char*)ocsp_response->area;
479
480 resp = d2i_OCSP_RESPONSE(NULL, &p, ocsp_response->data);
481 if (!resp) {
482 chunk_appendf(out, "Unable to parse OCSP response");
483 goto end;
484 }
485
486#ifndef USE_OPENSSL_WOLFSSL
487 if (OCSP_RESPONSE_print(bio, resp, 0) != 0) {
488#else
489 if (wolfSSL_d2i_OCSP_RESPONSE_bio(bio, &resp) != 0) {
490#endif
491 struct buffer *trash = get_trash_chunk();
492 struct ist ist_block = IST_NULL;
493 struct ist ist_double_lf = IST_NULL;
494 static struct ist double_lf = IST("\n\n");
495
496 write = BIO_read(bio, trash->area, trash->size - 1);
497 if (write <= 0)
498 goto end;
499 trash->data = write;
500
501 /* Look for empty lines in the 'trash' buffer and add a space to
502 * the beginning to avoid having empty lines in the output
503 * (without changing the appearance of the information
504 * displayed).
505 */
506 ist_block = ist2(b_orig(trash), b_data(trash));
507
508 ist_double_lf = istist(ist_block, double_lf);
509
510 while (istlen(ist_double_lf)) {
511 /* istptr(ist_double_lf) points to the first \n of a
512 * \n\n pattern.
513 */
514 uint empty_line_offset = istptr(ist_double_lf) + 1 - istptr(ist_block);
515
516 /* Write up to the first '\n' of the "\n\n" pattern into
517 * the output buffer.
518 */
519 b_putblk(out, istptr(ist_block), empty_line_offset);
520 /* Add an extra space. */
521 b_putchr(out, ' ');
522
523 /* Keep looking for empty lines in the rest of the data. */
524 ist_block = istadv(ist_block, empty_line_offset);
525
526 ist_double_lf = istist(ist_block, double_lf);
527 }
528
529 retval = (b_istput(out, ist_block) <= 0);
530 }
531
532end:
533 if (bio)
534 BIO_free(bio);
535
536 OCSP_RESPONSE_free(resp);
537
538 return retval;
539}
540
541/*
Remi Tricot-Le Breton9c4437d2023-02-28 17:46:28 +0100542 * Dump the contents of an OCSP response in DER format stored in
543 * <ocsp_response> into buffer <out> after converting it to base64.
544 * Returns 0 in case of success.
545 */
546static int ssl_ocsp_response_print_base64(struct buffer *ocsp_response, struct buffer *out)
547{
548 int b64len = 0;
549
550 b64len = a2base64(b_orig(ocsp_response), b_data(ocsp_response),
551 b_orig(out), b_size(out));
552
553 if (b64len < 0)
554 return 1;
555
556 out->data = b64len;
557
558 /* Add empty line */
559 chunk_appendf(ocsp_response, "\n");
560
561 return 0;
562}
563
564/*
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100565 * Dump the details of the OCSP response of ID <ocsp_certid> into buffer <out>.
566 * Returns 0 in case of success.
567 */
568int ssl_get_ocspresponse_detail(unsigned char *ocsp_certid, struct buffer *out)
569{
570 struct certificate_ocsp *ocsp;
571 int ret = 0;
572
573 HA_SPIN_LOCK(OCSP_LOCK, &ocsp_tree_lock);
574 ocsp = (struct certificate_ocsp *)ebmb_lookup(&cert_ocsp_tree, ocsp_certid, OCSP_MAX_CERTID_ASN1_LENGTH);
575 if (!ocsp) {
576 HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
577 return -1;
578 }
579
580 ret = ssl_ocsp_response_print(&ocsp->response, out);
581
582 HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
583
584 return ret;
585}
586
587
588/* IO handler of details "show ssl ocsp-response <id>".
589 * The current entry is taken from appctx->svcctx.
590 */
591static int cli_io_handler_show_ocspresponse_detail(struct appctx *appctx)
592{
Remi Tricot-Le Breton9c4437d2023-02-28 17:46:28 +0100593 struct buffer *trash = get_trash_chunk();
594 struct show_ocspresp_cli_ctx *ctx = appctx->svcctx;
595 struct certificate_ocsp *ocsp = ctx->ocsp;
596 int retval = 0;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100597
Remi Tricot-Le Breton9c4437d2023-02-28 17:46:28 +0100598 switch (ctx->format) {
599 case SHOW_OCSPRESP_FMT_DFLT:
600 case SHOW_OCSPRESP_FMT_TEXT:
601 retval = ssl_ocsp_response_print(&ocsp->response, trash);
602 break;
603 case SHOW_OCSPRESP_FMT_B64:
604 retval = ssl_ocsp_response_print_base64(&ocsp->response, trash);
605 break;
606 }
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100607
Remi Tricot-Le Breton9c4437d2023-02-28 17:46:28 +0100608 if (retval)
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100609 return 1;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100610
611 if (applet_putchk(appctx, trash) == -1)
612 goto yield;
613
614 appctx->svcctx = NULL;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100615 return 1;
616
617yield:
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100618 return 0;
619}
620
621void ssl_sock_ocsp_free_func(void *parent, void *ptr, CRYPTO_EX_DATA *ad, int idx, long argl, void *argp)
622{
623 struct ocsp_cbk_arg *ocsp_arg;
624
625 if (ptr) {
626 ocsp_arg = ptr;
627
628 if (ocsp_arg->is_single) {
William Lallemand60289bf2024-02-26 17:53:02 +0100629 ssl_sock_free_ocsp(ocsp_arg->s_ocsp);
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100630 ocsp_arg->s_ocsp = NULL;
631 } else {
632 int i;
633
634 for (i = 0; i < SSL_SOCK_NUM_KEYTYPES; i++) {
William Lallemand60289bf2024-02-26 17:53:02 +0100635 ssl_sock_free_ocsp(ocsp_arg->m_ocsp[i]);
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100636 ocsp_arg->m_ocsp[i] = NULL;
637 }
638 }
639 free(ocsp_arg);
640 }
641}
642
643/*
644 * Extract the first OCSP URI (if any) contained in <cert> and write it into
645 * <out>.
646 * Returns 0 in case of success, 1 otherwise.
647 */
648int ssl_ocsp_get_uri_from_cert(X509 *cert, struct buffer *out, char **err)
649{
650 STACK_OF(OPENSSL_STRING) *ocsp_uri_stk = NULL;
651 int ret = 1;
652
653 if (!cert || !out)
654 goto end;
655
656 ocsp_uri_stk = X509_get1_ocsp(cert);
657 if (ocsp_uri_stk == NULL) {
658 memprintf(err, "%sNo OCSP URL stack!\n", *err ? *err : "");
659 goto end;
660 }
661
William Lallemand8bc00f82022-12-22 10:09:11 +0100662 if (!chunk_strcpy(out, sk_OPENSSL_STRING_value(ocsp_uri_stk, 0))) {
663 memprintf(err, "%sOCSP URI too long!\n", *err ? *err : "");
664 goto end;
665 }
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100666 if (b_data(out) == 0) {
667 memprintf(err, "%sNo OCSP URL!\n", *err ? *err : "");
668 goto end;
669 }
670
671 ret = 0;
672
673end:
674 X509_email_free(ocsp_uri_stk);
675 return ret;
676}
677
678/*
679 * Create the url and request body that make a proper OCSP request for the
680 * <certid>. The <req_url> parameter should already hold the OCSP URI that was
681 * extracted from the corresponding certificate. Depending on the size of the
682 * certid we will either append data to the <req_url> to create a proper URL
683 * that will be sent with a GET command, or the <req_body> will be constructed
684 * in case of a POST.
685 * Returns 0 in case of success.
686 */
687int ssl_ocsp_create_request_details(const OCSP_CERTID *certid, struct buffer *req_url,
688 struct buffer *req_body, char **err)
689{
690 int errcode = -1;
691 OCSP_REQUEST *ocsp;
692 struct buffer *bin_request = get_trash_chunk();
693 unsigned char *outbuf = (unsigned char*)b_orig(bin_request);
694
695 ocsp = OCSP_REQUEST_new();
696 if (ocsp == NULL) {
697 memprintf(err, "%sCan't create OCSP_REQUEST\n", *err ? *err : "");
698 goto end;
699 }
700
701 if (OCSP_request_add0_id(ocsp, (OCSP_CERTID*)certid) == NULL) {
702 memprintf(err, "%sOCSP_request_add0_id() error\n", *err ? *err : "");
703 goto end;
704 }
705
706 bin_request->data = i2d_OCSP_REQUEST(ocsp, &outbuf);
707 if (b_data(bin_request) <= 0) {
708 memprintf(err, "%si2d_OCSP_REQUEST() error\n", *err ? *err : "");
709 goto end;
710 }
711
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100712 /* HTTP based OCSP requests can use either the GET or the POST method to
713 * submit their requests. To enable HTTP caching, small requests (that
714 * after encoding are less than 255 bytes), MAY be submitted using GET.
715 * If HTTP caching is not important, or the request is greater than 255
716 * bytes, the request SHOULD be submitted using POST.
717 */
William Lallemandeb530202022-12-22 10:19:07 +0100718 if (b_data(bin_request) + b_data(req_url) < 0xff) {
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100719 struct buffer *b64buf = get_trash_chunk();
720 char *ret = NULL;
721 int base64_ret = 0;
722
723 chunk_strcat(req_url, "/");
724
725 base64_ret = a2base64(b_orig(bin_request), b_data(bin_request),
726 b_orig(b64buf), b_size(b64buf));
727
728 if (base64_ret < 0) {
729 memprintf(err, "%sa2base64() error\n", *err ? *err : "");
Remi Tricot-Le Bretonc389b042023-01-02 15:01:16 +0100730 goto end;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100731 }
732
733 b64buf->data = base64_ret;
734
William Lallemandeb530202022-12-22 10:19:07 +0100735 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 +0100736 query_encode_map, b64buf);
737 if (ret && *ret == '\0') {
William Lallemandeb530202022-12-22 10:19:07 +0100738 req_url->data = ret - b_orig(req_url);
Remi Tricot-Le Bretonc389b042023-01-02 15:01:16 +0100739 errcode = 0;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100740 }
741 }
742 else {
743 chunk_cpy(req_body, bin_request);
Remi Tricot-Le Bretonc389b042023-01-02 15:01:16 +0100744 errcode = 0;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100745 }
746
Remi Tricot-Le Bretonc389b042023-01-02 15:01:16 +0100747
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100748end:
749 OCSP_REQUEST_free(ocsp);
750
751 return errcode;
752}
753
754/*
755 * Parse an OCSP_RESPONSE contained in <respbuf> and check its validity in
756 * regard to the contents of <ckch> or the <issuer> certificate.
757 * Certificate_ocsp structure does not keep a reference to the corresponding
758 * ckch_store so outside of a CLI context (see "send ssl ocsp-response"
759 * command), we only have an easy access to the issuer's certificate whose
760 * reference is held in the structure.
761 * Return 0 in case of success, 1 otherwise.
762 */
763int ssl_ocsp_check_response(STACK_OF(X509) *chain, X509 *issuer,
764 struct buffer *respbuf, char **err)
765{
766 int ret = 1;
767 int n;
768 OCSP_RESPONSE *response = NULL;
769 OCSP_BASICRESP *basic = NULL;
770 X509_STORE *store = NULL;
771 const unsigned char *start = (const unsigned char*)b_orig(respbuf);
772
773 if (!chain && !issuer) {
774 memprintf(err, "check_ocsp_response needs a certificate validation chain or an issuer certificate");
775 goto end;
776 }
777
778 response = d2i_OCSP_RESPONSE(NULL, &start, b_data(respbuf));
779 if (!response) {
780 memprintf(err, "d2i_OCSP_RESPONSE() failed");
781 goto end;
782 }
783
784 n = OCSP_response_status(response);
785
786 if (n != OCSP_RESPONSE_STATUS_SUCCESSFUL) {
787 memprintf(err, "OCSP response not successful (%d: %s)",
788 n, OCSP_response_status_str(n));
789 goto end;
790 }
791
792 basic = OCSP_response_get1_basic(response);
793 if (basic == NULL) {
794 memprintf(err, "OCSP_response_get1_basic() failed");
795 goto end;
796 }
797
Remi Tricot-Le Breton8bdd0052023-01-09 12:02:43 +0100798 /* Create a temporary store in which we add the certificate's chain
799 * certificates. We assume that all those certificates can be trusted
800 * because they were provided by the user.
801 * The only ssl item that needs to be verified here is the OCSP
802 * response.
803 */
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100804 store = X509_STORE_new();
805 if (!store) {
806 memprintf(err, "X509_STORE_new() failed");
807 goto end;
808 }
Remi Tricot-Le Breton8bdd0052023-01-09 12:02:43 +0100809
810 if (chain) {
811 int i = 0;
812 for (i = 0; i < sk_X509_num(chain); i++) {
813 X509 *cert = sk_X509_value(chain, i);
814 X509_STORE_add_cert(store, cert);
815 }
816 }
817
818 if (issuer)
819 X509_STORE_add_cert(store, issuer);
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100820
Remi Tricot-Le Breton8bdd0052023-01-09 12:02:43 +0100821 if (OCSP_basic_verify(basic, chain, store, OCSP_TRUSTOTHER) != 1) {
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100822 memprintf(err, "OCSP_basic_verify() failed");
823 goto end;
824 }
825
826 ret = 0;
827
828end:
829 X509_STORE_free(store);
830 OCSP_RESPONSE_free(response);
831 OCSP_BASICRESP_free(basic);
832 return ret;
833}
834
835
836/*
837 * OCSP-UPDATE RELATED FUNCTIONS AND STRUCTURES
838 */
839
840struct task *ocsp_update_task __read_mostly = NULL;
Remi Tricot-Le Bretonb33fe2f2023-02-28 17:46:25 +0100841static struct proxy *httpclient_ocsp_update_px;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100842
843static struct ssl_ocsp_task_ctx {
844 struct certificate_ocsp *cur_ocsp;
845 struct httpclient *hc;
846 int flags;
Remi Tricot-Le Bretonad6cba82023-02-28 17:46:21 +0100847 int update_status;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100848} ssl_ocsp_task_ctx;
849
850const struct http_hdr ocsp_request_hdrs[] = {
851 { IST("Content-Type"), IST("application/ocsp-request") },
852 { IST_NULL, IST_NULL }
853};
854
Remi Tricot-Le Bretonad6cba82023-02-28 17:46:21 +0100855enum {
856 OCSP_UPDT_UNKNOWN = 0,
857 OCSP_UPDT_OK = 1,
858 OCSP_UPDT_ERR_HTTP_STATUS = 2,
859 OCSP_UPDT_ERR_HTTP_HDR = 3,
860 OCSP_UPDT_ERR_CHECK = 4,
861 OCSP_UPDT_ERR_INSERT = 5,
862 OCSP_UPDT_ERR_LAST /* Must be last */
863};
864
865const struct ist ocsp_update_errors[] = {
866 [OCSP_UPDT_UNKNOWN] = IST("Unknown"),
867 [OCSP_UPDT_OK] = IST("Update successful"),
868 [OCSP_UPDT_ERR_HTTP_STATUS] = IST("HTTP error"),
869 [OCSP_UPDT_ERR_HTTP_HDR] = IST("Missing \"ocsp-response\" header"),
870 [OCSP_UPDT_ERR_CHECK] = IST("OCSP response check failure"),
871 [OCSP_UPDT_ERR_INSERT] = IST("Error during insertion")
872};
873
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100874static struct task *ssl_ocsp_update_responses(struct task *task, void *context, unsigned int state);
875
876/*
877 * Create the main OCSP update task that will iterate over the OCSP responses
878 * stored in ocsp_update_tree and send an OCSP request via the http_client
879 * applet to the corresponding OCSP responder. The task will then be in charge
880 * of processing the response, verifying it and resinserting it in the actual
881 * ocsp response tree if the response is valid.
882 * Returns 0 in case of success.
883 */
884int ssl_create_ocsp_update_task(char **err)
885{
886 if (ocsp_update_task)
887 return 0; /* Already created */
888
889 ocsp_update_task = task_new_anywhere();
890 if (!ocsp_update_task) {
891 memprintf(err, "parsing : failed to allocate global ocsp update task.");
892 return -1;
893 }
894
895 ocsp_update_task->process = ssl_ocsp_update_responses;
896 ocsp_update_task->context = NULL;
897
898 return 0;
899}
900
901static int ssl_ocsp_task_schedule()
902{
903 if (ocsp_update_task)
904 task_schedule(ocsp_update_task, now_ms);
905
906 return 0;
907}
908REGISTER_POST_CHECK(ssl_ocsp_task_schedule);
909
910void ssl_sock_free_ocsp(struct certificate_ocsp *ocsp);
911
912void ssl_destroy_ocsp_update_task(void)
913{
914 struct eb64_node *node, *next;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100915 if (!ocsp_update_task)
916 return;
917
918 HA_SPIN_LOCK(OCSP_LOCK, &ocsp_tree_lock);
919
920 node = eb64_first(&ocsp_update_tree);
921 while (node) {
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100922 next = eb64_next(node);
923 eb64_delete(node);
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100924 node = next;
925 }
926
927 HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
928
929 task_destroy(ocsp_update_task);
930 ocsp_update_task = NULL;
Remi Tricot-Le Breton14d7f0e2023-01-09 12:02:45 +0100931
932 ssl_sock_free_ocsp(ssl_ocsp_task_ctx.cur_ocsp);
933 ssl_ocsp_task_ctx.cur_ocsp = NULL;
Remi Tricot-Le Breton926f34b2023-02-28 17:46:18 +0100934
935 if (ssl_ocsp_task_ctx.hc) {
936 httpclient_stop_and_destroy(ssl_ocsp_task_ctx.hc);
937 ssl_ocsp_task_ctx.hc = NULL;
938 }
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100939}
940
Remi Tricot-Le Breton10f113e2023-01-12 09:49:11 +0100941static inline void ssl_ocsp_set_next_update(struct certificate_ocsp *ocsp)
942{
943 int update_margin = (ocsp->expire >= SSL_OCSP_UPDATE_MARGIN) ? SSL_OCSP_UPDATE_MARGIN : 0;
944
Remi Tricot-Le Breton56ab6072023-03-02 15:49:54 +0100945 ocsp->next_update.key = MIN(date.tv_sec + global_ssl.ocsp_update.delay_max,
Remi Tricot-Le Breton10f113e2023-01-12 09:49:11 +0100946 ocsp->expire - update_margin);
947
948 /* An already existing valid OCSP response that expires within less than
949 * SSL_OCSP_UPDATE_DELAY_MIN or has no 'Next Update' field should not be
950 * updated more than once every 5 minutes in order to avoid continuous
951 * update of the same response. */
952 if (b_data(&ocsp->response))
953 ocsp->next_update.key = MAX(ocsp->next_update.key,
Remi Tricot-Le Breton56ab6072023-03-02 15:49:54 +0100954 date.tv_sec + global_ssl.ocsp_update.delay_min);
Remi Tricot-Le Breton10f113e2023-01-12 09:49:11 +0100955}
956
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100957/*
958 * Insert a certificate_ocsp structure into the ocsp_update_tree tree, in which
959 * entries are sorted by absolute date of the next update. The next_update key
960 * will be the smallest out of the actual expire value of the response and
961 * now+1H. This arbitrary 1H value ensures that ocsp responses are updated
962 * periodically even when they have a long expire time, while not overloading
963 * the system too much (in theory). Likewise, a minimum 5 minutes interval is
964 * defined in order to avoid updating too often responses that have a really
965 * short expire time or even no 'Next Update' at all.
966 */
967int ssl_ocsp_update_insert(struct certificate_ocsp *ocsp)
968{
William Lallemand60289bf2024-02-26 17:53:02 +0100969 /* This entry was only supposed to be updated once, it does not need to
970 * be reinserted into the update tree.
971 */
972 if (ocsp->update_once)
973 return 0;
974
Remi Tricot-Le Breton10f113e2023-01-12 09:49:11 +0100975 /* Set next_update based on current time and the various OCSP
976 * minimum/maximum update times.
977 */
978 ssl_ocsp_set_next_update(ocsp);
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100979
Remi Tricot-Le Breton7e1a62e2023-02-28 17:46:27 +0100980 ocsp->fail_count = 0;
981
Remi Tricot-Le Breton10f113e2023-01-12 09:49:11 +0100982 HA_SPIN_LOCK(OCSP_LOCK, &ocsp_tree_lock);
William Lallemand60289bf2024-02-26 17:53:02 +0100983 eb64_insert(&ocsp_update_tree, &ocsp->next_update);
Remi Tricot-Le Breton10f113e2023-01-12 09:49:11 +0100984 HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100985
Remi Tricot-Le Breton10f113e2023-01-12 09:49:11 +0100986 return 0;
987}
988
989/*
990 * Reinsert an entry in the update tree. The entry's next update time can not
991 * occur before now+SSL_OCSP_HTTP_ERR_REPLAY.
992 * This is supposed to be used in case of http error (ocsp responder unreachable
993 * for instance). This ensures that the entry does not get reinserted at the
994 * beginning of the tree every time.
995 */
996int ssl_ocsp_update_insert_after_error(struct certificate_ocsp *ocsp)
997{
Remi Tricot-Le Breton7e1a62e2023-02-28 17:46:27 +0100998 int replay_delay = 0;
Remi Tricot-Le Bretona6c0a592023-03-13 15:56:32 +0100999
William Lallemand60289bf2024-02-26 17:53:02 +01001000 /* This entry was only supposed to be updated once, it does not need to
1001 * be reinserted into the update tree.
1002 */
1003 if (ocsp->update_once)
1004 return 0;
1005
Remi Tricot-Le Breton7e1a62e2023-02-28 17:46:27 +01001006 /*
1007 * Set next_update based on current time and the various OCSP
Remi Tricot-Le Breton10f113e2023-01-12 09:49:11 +01001008 * minimum/maximum update times.
1009 */
1010 ssl_ocsp_set_next_update(ocsp);
1011
Remi Tricot-Le Breton7e1a62e2023-02-28 17:46:27 +01001012 ++ocsp->fail_count;
1013
1014 /*
1015 * The replay delay will be increased for every consecutive update
1016 * failure, up to the SSL_OCSP_UPDATE_DELAY_MAX delay. It will ensure
1017 * that the replay delay will be one minute for the first failure and
1018 * will be multiplied by 2 for every subsequent failures, while still
1019 * being at most 1 hour (with the current default values).
1020 */
1021 replay_delay = MIN(SSL_OCSP_HTTP_ERR_REPLAY * (1 << ocsp->fail_count),
Remi Tricot-Le Breton58432372023-02-28 17:46:29 +01001022 global_ssl.ocsp_update.delay_max);
Remi Tricot-Le Breton7e1a62e2023-02-28 17:46:27 +01001023
Remi Tricot-Le Breton56ab6072023-03-02 15:49:54 +01001024 if (ocsp->next_update.key < date.tv_sec + replay_delay)
1025 ocsp->next_update.key = date.tv_sec + replay_delay;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001026
1027 HA_SPIN_LOCK(OCSP_LOCK, &ocsp_tree_lock);
William Lallemand60289bf2024-02-26 17:53:02 +01001028 eb64_insert(&ocsp_update_tree, &ocsp->next_update);
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001029 HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
1030
1031 return 0;
1032}
1033
1034void ocsp_update_response_stline_cb(struct httpclient *hc)
1035{
1036 struct task *task = hc->caller;
1037
1038 if (!task)
1039 return;
1040
1041 ssl_ocsp_task_ctx.flags |= HC_F_RES_STLINE;
1042 task_wakeup(task, TASK_WOKEN_MSG);
1043}
1044
1045void ocsp_update_response_headers_cb(struct httpclient *hc)
1046{
1047 struct task *task = hc->caller;
1048
1049 if (!task)
1050 return;
1051
1052 ssl_ocsp_task_ctx.flags |= HC_F_RES_HDR;
1053 task_wakeup(task, TASK_WOKEN_MSG);
1054}
1055
1056void ocsp_update_response_body_cb(struct httpclient *hc)
1057{
1058 struct task *task = hc->caller;
1059
1060 if (!task)
1061 return;
1062
1063 ssl_ocsp_task_ctx.flags |= HC_F_RES_BODY;
1064 task_wakeup(task, TASK_WOKEN_MSG);
1065}
1066
1067void ocsp_update_response_end_cb(struct httpclient *hc)
1068{
1069 struct task *task = hc->caller;
1070
1071 if (!task)
1072 return;
1073
1074 ssl_ocsp_task_ctx.flags |= HC_F_RES_END;
1075 task_wakeup(task, TASK_WOKEN_MSG);
1076}
1077
Remi Tricot-Le Bretonb33fe2f2023-02-28 17:46:25 +01001078
1079/*
Remi Tricot-Le Breton377c7592024-03-20 14:13:35 +01001080 * Send a log line that will mimic this previously used logformat :
1081 * char ocspupdate_log_format[] = "%ci:%cp [%tr] %ft %[ssl_ocsp_certname] \
1082 * %[ssl_ocsp_status] %{+Q}[ssl_ocsp_status_str] %[ssl_ocsp_fail_cnt] \
1083 * %[ssl_ocsp_success_cnt]";
1084 * We can't use the regular sess_log function because we don't have any control
1085 * over the stream and session used by the httpclient which might not exist
1086 * anymore by the time we call this function.
Remi Tricot-Le Bretonb33fe2f2023-02-28 17:46:25 +01001087 */
1088static void ssl_ocsp_send_log()
1089{
Remi Tricot-Le Breton377c7592024-03-20 14:13:35 +01001090 int status_str_len = 0;
1091 char *status_str = NULL;
1092 struct certificate_ocsp *ocsp = ssl_ocsp_task_ctx.cur_ocsp;
1093 struct tm tm;
1094 char timebuf[25];
1095
1096 if (!httpclient_ocsp_update_px)
Remi Tricot-Le Bretonb33fe2f2023-02-28 17:46:25 +01001097 return;
1098
Remi Tricot-Le Breton377c7592024-03-20 14:13:35 +01001099 if (ocsp && ssl_ocsp_task_ctx.update_status < OCSP_UPDT_ERR_LAST) {
1100 status_str_len = istlen(ocsp_update_errors[ssl_ocsp_task_ctx.update_status]);
1101 status_str = istptr(ocsp_update_errors[ssl_ocsp_task_ctx.update_status]);
1102 }
1103
1104 get_localtime(date.tv_sec, &tm);
1105 date2str_log(timebuf, &tm, &date, 25);
1106
1107 send_log(httpclient_ocsp_update_px, LOG_INFO, "-:- [%s] %s %s %u \"%.*s\" %u %u",
1108 timebuf,
1109 httpclient_ocsp_update_px->id,
1110 ocsp->path,
1111 ssl_ocsp_task_ctx.update_status,
1112 status_str_len, status_str,
1113 ocsp ? ocsp->num_failure : 0,
1114 ocsp ? ocsp->num_success : 0);
Remi Tricot-Le Bretonb33fe2f2023-02-28 17:46:25 +01001115}
1116
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001117/*
1118 * This is the main function of the ocsp auto update mechanism. It has two
1119 * distinct parts and the branching to one or the other is completely based on
1120 * the fact that the cur_ocsp pointer of the ssl_ocsp_task_ctx member is set.
1121 *
1122 * If the pointer is not set, we need to look at the first item of the update
1123 * tree and see if it needs to be updated. If it does not we simply wait until
1124 * the time is right and let the task asleep. If it does need to be updated, we
1125 * simply build and send the corresponding ocsp request thanks to the
1126 * http_client. The task is then sent to sleep with an expire time set to
1127 * infinity. The http_client will wake it back up once the response is received
1128 * (or a timeout occurs). Just note that during this whole process the
1129 * cetificate_ocsp object corresponding to the entry being updated is taken out
1130 * of the update tree and only stored in the ssl_ocsp_task_ctx context.
1131 *
1132 * Once the task is waken up by the http_client, it branches on the response
1133 * processing part of the function which basically checks that the response is
1134 * valid and inserts it into the ocsp_response tree. The task then goes back to
1135 * sleep until another entry needs to be updated.
1136 */
1137static struct task *ssl_ocsp_update_responses(struct task *task, void *context, unsigned int state)
1138{
Remi Tricot-Le Breton10f113e2023-01-12 09:49:11 +01001139 unsigned int next_wakeup = 0;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001140 struct eb64_node *eb;
1141 struct certificate_ocsp *ocsp;
1142 struct httpclient *hc = NULL;
1143 struct buffer *req_url = NULL;
1144 struct buffer *req_body = NULL;
1145 OCSP_CERTID *certid = NULL;
1146 struct ssl_ocsp_task_ctx *ctx = &ssl_ocsp_task_ctx;
1147
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001148 if (ctx->cur_ocsp) {
1149 /* An update is in process */
1150 ocsp = ctx->cur_ocsp;
1151 hc = ctx->hc;
1152 if (ctx->flags & HC_F_RES_STLINE) {
1153 if (hc->res.status != 200) {
Remi Tricot-Le Bretonad6cba82023-02-28 17:46:21 +01001154 ctx->update_status = OCSP_UPDT_ERR_HTTP_STATUS;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001155 goto http_error;
1156 }
1157 ctx->flags &= ~HC_F_RES_STLINE;
1158 }
1159
1160 if (ctx->flags & HC_F_RES_HDR) {
1161 struct http_hdr *hdr;
1162 int found = 0;
1163 /* Look for "Content-Type" header which should have
1164 * "application/ocsp-response" value. */
1165 for (hdr = hc->res.hdrs; isttest(hdr->v); hdr++) {
1166 if (isteqi(hdr->n, ist("Content-Type")) &&
1167 isteqi(hdr->v, ist("application/ocsp-response"))) {
1168 found = 1;
1169 break;
1170 }
1171 }
1172 if (!found) {
Remi Tricot-Le Bretonad6cba82023-02-28 17:46:21 +01001173 ctx->update_status = OCSP_UPDT_ERR_HTTP_HDR;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001174 goto http_error;
1175 }
1176 ctx->flags &= ~HC_F_RES_HDR;
1177 }
1178
1179 /* If the HC_F_RES_BODY is set, we still need for the
1180 * HC_F_RES_END flag to be set as well in order to be sure that
1181 * the body is complete. */
1182
1183 /* we must close only if F_RES_END is the last flag */
1184 if (ctx->flags & HC_F_RES_END) {
1185
1186 /* Process the body that must be complete since
1187 * HC_F_RES_END is set. */
1188 if (ctx->flags & HC_F_RES_BODY) {
Remi Tricot-Le Bretonad6cba82023-02-28 17:46:21 +01001189 if (ssl_ocsp_check_response(ocsp->chain, ocsp->issuer, &hc->res.buf, NULL)) {
1190 ctx->update_status = OCSP_UPDT_ERR_CHECK;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001191 goto http_error;
Remi Tricot-Le Bretonad6cba82023-02-28 17:46:21 +01001192 }
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001193
1194 if (ssl_sock_update_ocsp_response(&hc->res.buf, NULL) != 0) {
Remi Tricot-Le Bretonad6cba82023-02-28 17:46:21 +01001195 ctx->update_status = OCSP_UPDT_ERR_INSERT;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001196 goto http_error;
1197 }
1198
1199 ctx->flags &= ~HC_F_RES_BODY;
1200 }
1201
1202 ctx->flags &= ~HC_F_RES_END;
1203
Remi Tricot-Le Breton9e94df32023-02-28 17:46:20 +01001204 ++ocsp->num_success;
Remi Tricot-Le Breton56ab6072023-03-02 15:49:54 +01001205 ocsp->last_update = date.tv_sec;
Remi Tricot-Le Bretonad6cba82023-02-28 17:46:21 +01001206 ctx->update_status = OCSP_UPDT_OK;
1207 ocsp->last_update_status = ctx->update_status;
Remi Tricot-Le Breton9e94df32023-02-28 17:46:20 +01001208
Remi Tricot-Le Bretonb33fe2f2023-02-28 17:46:25 +01001209 ssl_ocsp_send_log();
1210
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001211 /* Reinsert the entry into the update list so that it can be updated later */
1212 ssl_ocsp_update_insert(ocsp);
Remi Tricot-Le Breton57f60c22023-01-09 12:02:42 +01001213 /* Release the reference kept on the updated ocsp response. */
William Lallemand60289bf2024-02-26 17:53:02 +01001214 ssl_sock_free_ocsp(ctx->cur_ocsp);
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001215 ctx->cur_ocsp = NULL;
1216
1217 HA_SPIN_LOCK(OCSP_LOCK, &ocsp_tree_lock);
1218 /* Set next_wakeup to the new first entry of the tree */
1219 eb = eb64_first(&ocsp_update_tree);
1220 if (eb) {
Remi Tricot-Le Breton56ab6072023-03-02 15:49:54 +01001221 if (eb->key > date.tv_sec)
1222 next_wakeup = (eb->key - date.tv_sec)*1000;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001223 else
1224 next_wakeup = 0;
1225 }
1226 HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
1227 goto leave;
1228 }
1229
1230 /* We did not receive the HC_F_RES_END flag yet, wait for it
1231 * before trying to update a new ocsp response. */
1232 goto wait;
1233 } else {
1234 /* Look for next entry that needs to be updated. */
1235 const unsigned char *p = NULL;
1236
1237 HA_SPIN_LOCK(OCSP_LOCK, &ocsp_tree_lock);
1238
1239 eb = eb64_first(&ocsp_update_tree);
1240 if (!eb) {
1241 HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
Remi Tricot-Le Breton1c647ad2023-01-12 09:49:10 +01001242 goto wait;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001243 }
1244
Remi Tricot-Le Breton56ab6072023-03-02 15:49:54 +01001245 if (eb->key > date.tv_sec) {
1246 next_wakeup = (eb->key - date.tv_sec)*1000;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001247 HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
1248 goto leave;
1249 }
1250
1251 ocsp = eb64_entry(eb, struct certificate_ocsp, next_update);
1252
1253 /* Take the current entry out of the update tree, it will be
1254 * reinserted after the response is processed. */
1255 eb64_delete(&ocsp->next_update);
1256
William Lallemand60289bf2024-02-26 17:53:02 +01001257 ++ocsp->refcount;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001258 ctx->cur_ocsp = ocsp;
Remi Tricot-Le Bretonad6cba82023-02-28 17:46:21 +01001259 ocsp->last_update_status = OCSP_UPDT_UNKNOWN;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001260
1261 HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
1262
1263 req_url = alloc_trash_chunk();
1264 if (!req_url) {
1265 goto leave;
1266 }
1267 req_body = alloc_trash_chunk();
1268 if (!req_body) {
1269 goto leave;
1270 }
1271
1272 p = ocsp->key_data;
1273
1274 d2i_OCSP_CERTID(&certid, &p, ocsp->key_length);
1275 if (!certid)
1276 goto leave;
1277
1278 /* Copy OCSP URI stored in ocsp structure into req_url */
1279 chunk_cpy(req_url, ocsp->uri);
1280
1281 /* Create ocsp request */
1282 if (ssl_ocsp_create_request_details(certid, req_url, req_body, NULL) != 0) {
1283 goto leave;
1284 }
1285
1286 /* Depending on the processing that occurred in
1287 * ssl_ocsp_create_request_details we could either have to send
1288 * a GET or a POST request. */
Remi Tricot-Le Bretonb33fe2f2023-02-28 17:46:25 +01001289 hc = httpclient_new_from_proxy(httpclient_ocsp_update_px, task,
1290 b_data(req_body) ? HTTP_METH_POST : HTTP_METH_GET,
1291 ist2(b_orig(req_url), b_data(req_url)));
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001292 if (!hc) {
1293 goto leave;
1294 }
1295
1296 if (httpclient_req_gen(hc, hc->req.url, hc->req.meth,
1297 b_data(req_body) ? ocsp_request_hdrs : NULL,
William Lallemand70601c52022-12-22 14:34:01 +01001298 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 +01001299 goto leave;
1300 }
1301
1302 hc->ops.res_stline = ocsp_update_response_stline_cb;
1303 hc->ops.res_headers = ocsp_update_response_headers_cb;
1304 hc->ops.res_payload = ocsp_update_response_body_cb;
1305 hc->ops.res_end = ocsp_update_response_end_cb;
1306
Remi Tricot-Le Breton377c7592024-03-20 14:13:35 +01001307 if (!httpclient_start(hc)) {
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001308 goto leave;
1309 }
1310
1311 ctx->flags = 0;
1312 ctx->hc = hc;
1313
1314 /* We keep the lock, this indicates that an update is in process. */
1315 goto wait;
1316 }
1317
1318leave:
1319 if (ctx->cur_ocsp) {
1320 /* Something went wrong, reinsert the entry in the tree. */
Remi Tricot-Le Breton9e94df32023-02-28 17:46:20 +01001321 ++ctx->cur_ocsp->num_failure;
Remi Tricot-Le Breton6de7b782023-02-28 17:46:19 +01001322 ssl_ocsp_update_insert_after_error(ctx->cur_ocsp);
Remi Tricot-Le Breton57f60c22023-01-09 12:02:42 +01001323 /* Release the reference kept on the updated ocsp response. */
William Lallemand60289bf2024-02-26 17:53:02 +01001324 ssl_sock_free_ocsp(ctx->cur_ocsp);
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001325 ctx->cur_ocsp = NULL;
1326 }
1327 if (hc)
1328 httpclient_stop_and_destroy(hc);
Remi Tricot-Le Bretonf64a0592023-03-13 15:56:33 +01001329 ctx->hc = NULL;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001330 free_trash_chunk(req_url);
1331 free_trash_chunk(req_body);
1332 task->expire = tick_add(now_ms, next_wakeup);
1333 return task;
1334
1335wait:
1336 free_trash_chunk(req_url);
1337 free_trash_chunk(req_body);
1338 task->expire = TICK_ETERNITY;
1339 return task;
1340
1341http_error:
Remi Tricot-Le Bretonb33fe2f2023-02-28 17:46:25 +01001342 ssl_ocsp_send_log();
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001343 /* Reinsert certificate into update list so that it can be updated later */
Remi Tricot-Le Breton9e94df32023-02-28 17:46:20 +01001344 if (ocsp) {
1345 ++ocsp->num_failure;
Remi Tricot-Le Bretonad6cba82023-02-28 17:46:21 +01001346 ocsp->last_update_status = ctx->update_status;
Remi Tricot-Le Breton10f113e2023-01-12 09:49:11 +01001347 ssl_ocsp_update_insert_after_error(ocsp);
Remi Tricot-Le Breton9e94df32023-02-28 17:46:20 +01001348 }
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001349
1350 if (hc)
1351 httpclient_stop_and_destroy(hc);
Remi Tricot-Le Breton57f60c22023-01-09 12:02:42 +01001352 /* Release the reference kept on the updated ocsp response. */
William Lallemand60289bf2024-02-26 17:53:02 +01001353 ssl_sock_free_ocsp(ctx->cur_ocsp);
Remi Tricot-Le Breton10f113e2023-01-12 09:49:11 +01001354 HA_SPIN_LOCK(OCSP_LOCK, &ocsp_tree_lock);
1355 /* Set next_wakeup to the new first entry of the tree */
1356 eb = eb64_first(&ocsp_update_tree);
1357 if (eb) {
Remi Tricot-Le Breton56ab6072023-03-02 15:49:54 +01001358 if (eb->key > date.tv_sec)
1359 next_wakeup = (eb->key - date.tv_sec)*1000;
Remi Tricot-Le Breton10f113e2023-01-12 09:49:11 +01001360 else
1361 next_wakeup = 0;
1362 }
1363 HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001364 ctx->cur_ocsp = NULL;
1365 ctx->hc = NULL;
1366 ctx->flags = 0;
1367 task->expire = tick_add(now_ms, next_wakeup);
1368 return task;
1369}
1370
Remi Tricot-Le Bretonb33fe2f2023-02-28 17:46:25 +01001371
1372/*
1373 * Initialize the proxy for the OCSP update HTTP client with 2 servers, one for
1374 * raw HTTP, the other for HTTPS.
1375 */
1376static int ssl_ocsp_update_precheck()
1377{
1378 /* initialize the OCSP update dedicated httpclient */
Remi Tricot-Le Bretonc9bfe322023-03-13 15:56:31 +01001379 httpclient_ocsp_update_px = httpclient_create_proxy("<OCSP-UPDATE>");
Remi Tricot-Le Bretonb33fe2f2023-02-28 17:46:25 +01001380 if (!httpclient_ocsp_update_px)
1381 return 1;
Remi Tricot-Le Bretonb33fe2f2023-02-28 17:46:25 +01001382 httpclient_ocsp_update_px->conf.logformat_string = httpclient_log_format;
1383 httpclient_ocsp_update_px->options2 |= PR_O2_NOLOGNORM;
1384
1385 return 0;
1386}
1387
1388/* initialize the proxy and servers for the HTTP client */
1389
1390REGISTER_PRE_CHECK(ssl_ocsp_update_precheck);
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001391
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001392
1393static int cli_parse_update_ocsp_response(char **args, char *payload, struct appctx *appctx, void *private)
1394{
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001395 char *err = NULL;
1396 struct ckch_store *ckch_store = NULL;
Remi Tricot-Le Bretona6c0a592023-03-13 15:56:32 +01001397 struct certificate_ocsp *ocsp = NULL;
1398 int update_once = 0;
1399 unsigned char key[OCSP_MAX_CERTID_ASN1_LENGTH] = {};
1400 unsigned char *p;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001401
1402 if (!*args[3]) {
1403 memprintf(&err, "'update ssl ocsp-response' expects a filename\n");
1404 return cli_dynerr(appctx, err);
1405 }
1406
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001407 /* The operations on the CKCH architecture are locked so we can
1408 * manipulate ckch_store and ckch_inst */
1409 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock)) {
1410 memprintf(&err, "%sCan't update the certificate!\nOperations on certificates are currently locked!\n", err ? err : "");
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001411 goto end;
1412 }
1413
1414 ckch_store = ckchs_lookup(args[3]);
1415
1416 if (!ckch_store) {
Remi Tricot-Le Breton14419eb2023-01-09 12:02:49 +01001417 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 +01001418 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1419 goto end;
1420 }
1421
Remi Tricot-Le Bretona6c0a592023-03-13 15:56:32 +01001422 p = key;
1423 i2d_OCSP_CERTID(ckch_store->data->ocsp_cid, &p);
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001424
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001425 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1426
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001427
Remi Tricot-Le Bretona6c0a592023-03-13 15:56:32 +01001428 HA_SPIN_LOCK(OCSP_LOCK, &ocsp_tree_lock);
1429 ocsp = (struct certificate_ocsp *)ebmb_lookup(&cert_ocsp_tree, key, OCSP_MAX_CERTID_ASN1_LENGTH);
1430 if (!ocsp) {
1431 memprintf(&err, "%s'update ssl ocsp-response' only works on certificates that already have a known OCSP response.\n", err ? err : "");
Remi Tricot-Le Bretona6c0a592023-03-13 15:56:32 +01001432 HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001433 goto end;
1434 }
1435
William Lallemand60289bf2024-02-26 17:53:02 +01001436 update_once = (ocsp->next_update.node.leaf_p == NULL);
1437 eb64_delete(&ocsp->next_update);
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001438
William Lallemand60289bf2024-02-26 17:53:02 +01001439 /* Insert the entry at the beginning of the update tree. */
1440 ocsp->next_update.key = 0;
1441 eb64_insert(&ocsp_update_tree, &ocsp->next_update);
1442 ocsp->update_once = update_once;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001443
Remi Tricot-Le Bretona6c0a592023-03-13 15:56:32 +01001444 HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001445
Remi Tricot-Le Bretona6c0a592023-03-13 15:56:32 +01001446 if (!ocsp_update_task)
1447 ssl_create_ocsp_update_task(&err);
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001448
Remi Tricot-Le Bretona6c0a592023-03-13 15:56:32 +01001449 task_wakeup(ocsp_update_task, TASK_WOKEN_MSG);
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001450
Remi Tricot-Le Bretond32c8e32023-03-21 10:28:34 +01001451 free(err);
1452
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001453 return 0;
1454
1455end:
Remi Tricot-Le Bretonae518772023-03-21 10:26:20 +01001456 return cli_dynerr(appctx, memprintf(&err, "%sCan't send ocsp request for %s!\n", err ? err : "", args[3]));
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001457}
1458
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001459#endif /* !defined OPENSSL_IS_BORINGSSL */
1460
1461
1462#endif /* (defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) */
1463
1464
1465static int cli_parse_set_ocspresponse(char **args, char *payload, struct appctx *appctx, void *private)
1466{
1467#if (defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP)
1468 char *err = NULL;
1469 int i, j, ret;
1470
1471 if (!payload)
1472 payload = args[3];
1473
1474 /* Expect one parameter: the new response in base64 encoding */
1475 if (!*payload)
1476 return cli_err(appctx, "'set ssl ocsp-response' expects response in base64 encoding.\n");
1477
1478 /* remove \r and \n from the payload */
1479 for (i = 0, j = 0; payload[i]; i++) {
1480 if (payload[i] == '\r' || payload[i] == '\n')
1481 continue;
1482 payload[j++] = payload[i];
1483 }
1484 payload[j] = 0;
1485
1486 ret = base64dec(payload, j, trash.area, trash.size);
1487 if (ret < 0)
1488 return cli_err(appctx, "'set ssl ocsp-response' received invalid base64 encoded response.\n");
1489
1490 trash.data = ret;
1491 if (ssl_sock_update_ocsp_response(&trash, &err)) {
1492 if (err)
1493 return cli_dynerr(appctx, memprintf(&err, "%s.\n", err));
1494 else
1495 return cli_err(appctx, "Failed to update OCSP response.\n");
1496 }
1497
1498 return cli_msg(appctx, LOG_INFO, "OCSP Response updated!\n");
1499#else
1500 return cli_err(appctx, "HAProxy was compiled against a version of OpenSSL that doesn't support OCSP stapling.\n");
1501#endif
1502
1503}
1504
1505/* parsing function for 'show ssl ocsp-response [id]'. If an entry is forced,
1506 * it's set into appctx->svcctx.
1507 */
1508static int cli_parse_show_ocspresponse(char **args, char *payload, struct appctx *appctx, void *private)
1509{
1510#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 +01001511
1512 struct show_ocspresp_cli_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
Remi Tricot-Le Bretondafc0682023-03-13 15:56:34 +01001513 int arg_idx = 3;
Remi Tricot-Le Breton9c4437d2023-02-28 17:46:28 +01001514
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001515 if (*args[3]) {
1516 struct certificate_ocsp *ocsp = NULL;
1517 char key[OCSP_MAX_CERTID_ASN1_LENGTH] = {};
1518 int key_length = OCSP_MAX_CERTID_ASN1_LENGTH;
1519 char *key_ptr = key;
Remi Tricot-Le Bretondafc0682023-03-13 15:56:34 +01001520 unsigned char *p;
1521 struct ckch_store *ckch_store = NULL;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001522
Remi Tricot-Le Breton9c4437d2023-02-28 17:46:28 +01001523 if (strcmp(args[3], "text") == 0) {
1524 ctx->format = SHOW_OCSPRESP_FMT_TEXT;
Remi Tricot-Le Bretondafc0682023-03-13 15:56:34 +01001525 ++arg_idx;
Remi Tricot-Le Breton9c4437d2023-02-28 17:46:28 +01001526 } else if (strcmp(args[3], "base64") == 0) {
1527 ctx->format = SHOW_OCSPRESP_FMT_B64;
Remi Tricot-Le Bretondafc0682023-03-13 15:56:34 +01001528 ++arg_idx;
Remi Tricot-Le Breton9c4437d2023-02-28 17:46:28 +01001529 }
1530
Remi Tricot-Le Bretondafc0682023-03-13 15:56:34 +01001531 if (ctx->format != SHOW_OCSPRESP_FMT_DFLT && !*args[arg_idx])
Remi Tricot-Le Breton9c4437d2023-02-28 17:46:28 +01001532 return cli_err(appctx, "'show ssl ocsp-response [text|base64]' expects a valid certid.\n");
1533
Remi Tricot-Le Bretondafc0682023-03-13 15:56:34 +01001534 /* Try to convert parameter into an OCSP certid first, and consider it
1535 * as a filename if it fails. */
1536 if (strlen(args[arg_idx]) > OCSP_MAX_CERTID_ASN1_LENGTH*2 ||
1537 !parse_binary(args[arg_idx], &key_ptr, &key_length, NULL)) {
1538
1539 key_ptr = key;
1540 key_length = 0;
1541
1542 /* The operations on the CKCH architecture are locked so we can
1543 * manipulate ckch_store and ckch_inst */
1544 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock)) {
1545 return cli_err(appctx, "Operations on certificates are currently locked!\n");
1546 }
1547
1548 ckch_store = ckchs_lookup(args[arg_idx]);
1549
1550 if (ckch_store) {
1551 p = (unsigned char*)key;
1552 key_length = i2d_OCSP_CERTID(ckch_store->data->ocsp_cid, &p);
1553 }
1554 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001555 }
1556
Remi Tricot-Le Bretondafc0682023-03-13 15:56:34 +01001557 if (key_length == 0) {
1558 return cli_err(appctx, "'show ssl ocsp-response' expects a valid certid or certificate path.\n");
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001559 }
1560
1561 HA_SPIN_LOCK(OCSP_LOCK, &ocsp_tree_lock);
1562 ocsp = (struct certificate_ocsp *)ebmb_lookup(&cert_ocsp_tree, key, OCSP_MAX_CERTID_ASN1_LENGTH);
1563
1564 if (!ocsp) {
1565 HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
Remi Tricot-Le Bretondafc0682023-03-13 15:56:34 +01001566 return cli_err(appctx, "Certificate ID or path does not match any certificate.\n");
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001567 }
William Lallemand60289bf2024-02-26 17:53:02 +01001568 ++ocsp->refcount;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001569 HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
1570
Remi Tricot-Le Breton9c4437d2023-02-28 17:46:28 +01001571 ctx->ocsp = ocsp;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001572 appctx->io_handler = cli_io_handler_show_ocspresponse_detail;
1573 }
1574
1575 return 0;
1576
1577#else
1578 return cli_err(appctx, "HAProxy was compiled against a version of OpenSSL that doesn't support OCSP stapling.\n");
1579#endif
1580}
1581
1582/*
1583 * IO handler of "show ssl ocsp-response". The command taking a specific ID
1584 * is managed in cli_io_handler_show_ocspresponse_detail.
1585 * The current entry is taken from appctx->svcctx.
1586 */
1587static int cli_io_handler_show_ocspresponse(struct appctx *appctx)
1588{
1589#if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) && !defined OPENSSL_IS_BORINGSSL)
1590 struct buffer *trash = alloc_trash_chunk();
1591 struct buffer *tmp = NULL;
1592 struct ebmb_node *node;
1593 struct certificate_ocsp *ocsp = NULL;
1594 BIO *bio = NULL;
1595 int write = -1;
Remi Tricot-Le Breton9c4437d2023-02-28 17:46:28 +01001596 struct show_ocspresp_cli_ctx *ctx = appctx->svcctx;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001597
1598 if (trash == NULL)
1599 return 1;
1600
1601 HA_SPIN_LOCK(OCSP_LOCK, &ocsp_tree_lock);
1602
1603 tmp = alloc_trash_chunk();
1604 if (!tmp)
1605 goto end;
1606
1607 if ((bio = BIO_new(BIO_s_mem())) == NULL)
1608 goto end;
1609
Remi Tricot-Le Breton9c4437d2023-02-28 17:46:28 +01001610 if (!ctx->ocsp) {
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001611 chunk_appendf(trash, "# Certificate IDs\n");
1612 node = ebmb_first(&cert_ocsp_tree);
1613 } else {
Remi Tricot-Le Breton9c4437d2023-02-28 17:46:28 +01001614 node = &ctx->ocsp->key;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001615 }
1616
1617 while (node) {
1618 OCSP_CERTID *certid = NULL;
1619 const unsigned char *p = NULL;
1620 int i;
1621
1622 ocsp = ebmb_entry(node, struct certificate_ocsp, key);
1623
1624 /* Dump the key in hexadecimal */
1625 chunk_appendf(trash, "Certificate ID key : ");
1626 for (i = 0; i < ocsp->key_length; ++i) {
1627 chunk_appendf(trash, "%02x", ocsp->key_data[i]);
1628 }
1629 chunk_appendf(trash, "\n");
1630
Remi Tricot-Le Breton7716f272023-03-13 15:56:35 +01001631 /* Dump the certificate path */
1632 chunk_appendf(trash, "Certificate path : %s\n", ocsp->path);
1633
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001634 p = ocsp->key_data;
1635
1636 /* Decode the certificate ID (serialized into the key). */
1637 d2i_OCSP_CERTID(&certid, &p, ocsp->key_length);
1638 if (!certid)
1639 goto end;
1640
1641 /* Dump the CERTID info */
1642 ocsp_certid_print(bio, certid, 1);
1643 OCSP_CERTID_free(certid);
1644 write = BIO_read(bio, tmp->area, tmp->size-1);
1645 /* strip trailing LFs */
1646 while (write > 0 && tmp->area[write-1] == '\n')
1647 write--;
1648 tmp->area[write] = '\0';
1649
1650 chunk_appendf(trash, "%s\n", tmp->area);
1651
1652 node = ebmb_next(node);
1653 if (applet_putchk(appctx, trash) == -1)
1654 goto yield;
1655 }
1656
1657end:
1658 HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001659 free_trash_chunk(trash);
1660 free_trash_chunk(tmp);
1661 BIO_free(bio);
1662 return 1;
1663
1664yield:
1665 free_trash_chunk(trash);
1666 free_trash_chunk(tmp);
1667 BIO_free(bio);
1668
William Lallemand60289bf2024-02-26 17:53:02 +01001669 ++ocsp->refcount;
Remi Tricot-Le Breton9c4437d2023-02-28 17:46:28 +01001670 ctx->ocsp = ocsp;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001671 HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
1672 return 0;
1673#else
1674 return cli_err(appctx, "HAProxy was compiled against a version of OpenSSL that doesn't support OCSP stapling.\n");
1675#endif
1676}
1677
William Lallemanda14686d2023-02-07 18:38:05 +01001678/* Check if the ckch_store and the entry does have the same configuration */
1679int ocsp_update_check_cfg_consistency(struct ckch_store *store, struct crtlist_entry *entry, char *crt_path, char **err)
1680{
1681 int err_code = ERR_NONE;
1682
1683 if (store->data->ocsp_update_mode != SSL_SOCK_OCSP_UPDATE_DFLT || entry->ssl_conf) {
1684 if ((!entry->ssl_conf && store->data->ocsp_update_mode == SSL_SOCK_OCSP_UPDATE_ON)
Remi Tricot-Le Breton65bfd172024-03-25 16:50:23 +01001685 || (entry->ssl_conf && entry->ssl_conf->ocsp_update != SSL_SOCK_OCSP_UPDATE_OFF &&
1686 store->data->ocsp_update_mode != entry->ssl_conf->ocsp_update)) {
William Lallemanda14686d2023-02-07 18:38:05 +01001687 memprintf(err, "%sIncompatibilities found in OCSP update mode for certificate %s\n", err && *err ? *err : "", crt_path);
1688 err_code |= ERR_ALERT | ERR_FATAL;
1689 }
1690 }
1691 return err_code;
1692}
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001693
Remi Tricot-Le Bretond14fc512023-02-28 17:46:23 +01001694struct show_ocsp_updates_ctx {
1695 struct certificate_ocsp *cur_ocsp;
1696};
1697
1698/*
1699 * Parsing function for 'show ssl ocsp-updates [nb]'.
1700 */
1701static int cli_parse_show_ocsp_updates(char **args, char *payload, struct appctx *appctx, void *private)
1702{
1703#if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) && !defined OPENSSL_IS_BORINGSSL)
1704 struct show_ocsp_updates_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
1705
1706 HA_SPIN_LOCK(OCSP_LOCK, &ocsp_tree_lock);
1707
1708 return 0;
1709#else
1710 return cli_err(appctx, "HAProxy was compiled against a version of OpenSSL that doesn't support OCSP stapling.\n");
1711#endif
1712}
1713
1714/*
1715 * Dump information about an ocsp response concerning ocsp auto update.
1716 * It follows the following format :
1717 * OCSP Certid | Path | Next Update | Last Update | Successes | Failures | Last Update Status | Last Update Status (str)
1718 * Return 0 in case of success.
1719 */
1720static int dump_ocsp_update_info(struct certificate_ocsp *ocsp, struct buffer *out)
1721{
1722 struct tm tm = {};
1723 char *ret;
1724 int i;
1725 time_t next_update;
1726
1727 /* Dump OCSP certid */
1728 for (i = 0; i < ocsp->key_length; ++i) {
1729 chunk_appendf(out, "%02x", ocsp->key_data[i]);
1730 }
1731
1732 chunk_appendf(out, " | ");
1733
1734 /* Dump path */
1735 chunk_appendf(out, "%s", ocsp->path);
1736
1737 chunk_appendf(out, " | ");
1738
1739 /* Dump next update time */
1740 if (ocsp->next_update.key != 0) {
1741 next_update = ocsp->next_update.key;
1742 get_localtime(ocsp->next_update.key, &tm);
1743 } else {
1744 next_update = date.tv_sec;
1745 get_localtime(date.tv_sec, &tm);
1746 }
1747 ret = localdate2str_log(b_orig(out)+b_data(out), next_update, &tm, b_size(out)-b_data(out));
1748
1749 if (ret == NULL)
1750 return 1;
1751
1752 out->data = (ret - out->area);
1753
1754 chunk_appendf(out, " | ");
1755
1756 /* Dump last update time or "-" if no update occurred yet */
1757 if (ocsp->last_update) {
1758 get_localtime(ocsp->last_update, &tm);
1759 ret = localdate2str_log(b_orig(out)+b_data(out), ocsp->last_update, &tm, b_size(out)-b_data(out));
1760
1761 if (ret == NULL)
1762 return 1;
1763
1764 out->data = (ret - out->area);
1765 } else
1766 chunk_appendf(out, "-");
1767
1768 chunk_appendf(out, " | ");
1769
1770 /* Number of successful updates */
1771 chunk_appendf(out, "%d", ocsp->num_success);
1772
1773 chunk_appendf(out, " | ");
1774
1775 /* Number of failed updates */
1776 chunk_appendf(out, "%d", ocsp->num_failure);
1777
1778 chunk_appendf(out, " | ");
1779
1780 /* Last update status */
1781 chunk_appendf(out, "%d", ocsp->last_update_status);
1782
1783 chunk_appendf(out, " | ");
1784
1785 /* Last update status str */
1786 if (ocsp->last_update_status >= OCSP_UPDT_ERR_LAST)
1787 chunk_appendf(out, "-");
1788 else
1789 chunk_appendf(out, "%s", istptr(ocsp_update_errors[ocsp->last_update_status]));
1790
1791 chunk_appendf(out, "\n");
1792
1793 return 0;
1794}
1795
1796static int cli_io_handler_show_ocsp_updates(struct appctx *appctx)
1797{
1798 struct show_ocsp_updates_ctx *ctx = appctx->svcctx;
1799 struct eb64_node *node;
1800 struct certificate_ocsp *ocsp = NULL;
1801 struct buffer *trash = get_trash_chunk();
1802
1803 if (!ctx->cur_ocsp) {
1804 node = eb64_first(&ocsp_update_tree);
1805 chunk_appendf(trash, "OCSP Certid | Path | Next Update | Last Update | Successes | Failures | Last Update Status | Last Update Status (str)\n");
1806
1807 /* Look for an entry currently being updated */
1808 ocsp = ssl_ocsp_task_ctx.cur_ocsp;
1809 if (ocsp) {
1810 if (dump_ocsp_update_info(ocsp, trash))
1811 goto end;
1812 }
1813
1814 if (applet_putchk(appctx, trash) == -1)
1815 goto yield;
1816
1817 } else {
1818 node = &((struct certificate_ocsp*)ctx->cur_ocsp)->next_update;
1819 }
1820
1821 while (node) {
1822 ocsp = eb64_entry(node, struct certificate_ocsp, next_update);
1823
1824 chunk_reset(trash);
1825 if (dump_ocsp_update_info(ocsp, trash))
1826 goto end;
1827
1828 if (applet_putchk(appctx, trash) == -1) {
1829 ctx->cur_ocsp = ocsp;
1830 goto yield;
1831 }
1832
1833 node = eb64_next(node);
1834 }
1835
1836end:
1837 return 1;
1838
1839yield:
1840 return 0; /* should come back */
1841}
1842
1843static void cli_release_show_ocsp_updates(struct appctx *appctx)
1844{
1845 HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
1846}
1847
1848
Remi Tricot-Le Bretond42c8962023-02-28 17:46:24 +01001849static int
1850smp_fetch_ssl_ocsp_certid(const struct arg *args, struct sample *smp, const char *kw, void *private)
1851{
1852 struct buffer *data = get_trash_chunk();
1853 struct certificate_ocsp *ocsp = ssl_ocsp_task_ctx.cur_ocsp;
1854
1855 if (!ocsp)
1856 return 0;
1857
1858 dump_binary(data, (char *)ocsp->key_data, ocsp->key_length);
1859
1860 smp->data.type = SMP_T_STR;
1861 smp->data.u.str = *data;
1862 return 1;
1863}
1864
1865static int
Remi Tricot-Le Bretonc9bfe322023-03-13 15:56:31 +01001866smp_fetch_ssl_ocsp_certname(const struct arg *args, struct sample *smp, const char *kw, void *private)
1867{
1868 struct certificate_ocsp *ocsp = ssl_ocsp_task_ctx.cur_ocsp;
1869
1870 if (!ocsp)
1871 return 0;
1872
1873 smp->data.type = SMP_T_STR;
1874 smp->data.u.str.area = ocsp->path;
1875 smp->data.u.str.data = strlen(ocsp->path);
1876 return 1;
1877}
1878
1879static int
Remi Tricot-Le Bretond42c8962023-02-28 17:46:24 +01001880smp_fetch_ssl_ocsp_status(const struct arg *args, struct sample *smp, const char *kw, void *private)
1881{
1882 struct certificate_ocsp *ocsp = ssl_ocsp_task_ctx.cur_ocsp;
1883
1884 if (!ocsp)
1885 return 0;
1886
1887 smp->data.type = SMP_T_SINT;
1888 smp->data.u.sint = ssl_ocsp_task_ctx.update_status;
1889 return 1;
1890}
1891
1892static int
1893smp_fetch_ssl_ocsp_status_str(const struct arg *args, struct sample *smp, const char *kw, void *private)
1894{
1895 struct certificate_ocsp *ocsp = ssl_ocsp_task_ctx.cur_ocsp;
1896
1897 if (!ocsp)
1898 return 0;
1899
1900 if (ssl_ocsp_task_ctx.update_status >= OCSP_UPDT_ERR_LAST)
1901 return 0;
1902
1903 smp->data.type = SMP_T_STR;
1904 smp->data.u.str = ist2buf(ocsp_update_errors[ssl_ocsp_task_ctx.update_status]);
1905
1906 return 1;
1907}
1908
1909static int
1910smp_fetch_ssl_ocsp_fail_cnt(const struct arg *args, struct sample *smp, const char *kw, void *private)
1911{
1912 struct certificate_ocsp *ocsp = ssl_ocsp_task_ctx.cur_ocsp;
1913
1914 if (!ocsp)
1915 return 0;
1916
1917 smp->data.type = SMP_T_SINT;
1918 smp->data.u.sint = ocsp->num_failure;
1919 return 1;
1920}
1921
1922static int
1923smp_fetch_ssl_ocsp_success_cnt(const struct arg *args, struct sample *smp, const char *kw, void *private)
1924{
1925 struct certificate_ocsp *ocsp = ssl_ocsp_task_ctx.cur_ocsp;
1926
1927 if (!ocsp)
1928 return 0;
1929
1930 smp->data.type = SMP_T_SINT;
1931 smp->data.u.sint = ocsp->num_success;
1932 return 1;
1933}
1934
1935
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001936static struct cli_kw_list cli_kws = {{ },{
Remi Tricot-Le Breton9c4437d2023-02-28 17:46:28 +01001937 { { "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 +01001938
William Lallemand60289bf2024-02-26 17:53:02 +01001939 { { "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 },
Remi Tricot-Le Breton9c4437d2023-02-28 17:46:28 +01001940 { { "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 +01001941#if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) && !defined OPENSSL_IS_BORINGSSL)
Remi Tricot-Le Bretona6c0a592023-03-13 15:56:32 +01001942 { { "update", "ssl", "ocsp-response", NULL }, "update ssl ocsp-response <certfile> : send ocsp request and update stored ocsp response", cli_parse_update_ocsp_response, NULL, NULL },
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001943#endif
1944 { { NULL }, NULL, NULL, NULL }
1945}};
1946
1947INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws);
1948
Remi Tricot-Le Bretond42c8962023-02-28 17:46:24 +01001949
1950/* Note: must not be declared <const> as its list will be overwritten.
1951 * Please take care of keeping this list alphabetically sorted.
1952 *
1953 * Those fetches only have a valid value during an OCSP update process so they
1954 * can only be used in a log format of a log line built by the update process
1955 * task itself.
1956 */
1957static struct sample_fetch_kw_list sample_fetch_keywords = {ILH, {
1958 { "ssl_ocsp_certid", smp_fetch_ssl_ocsp_certid, 0, NULL, SMP_T_STR, SMP_USE_L5SRV },
Remi Tricot-Le Bretonc9bfe322023-03-13 15:56:31 +01001959 { "ssl_ocsp_certname", smp_fetch_ssl_ocsp_certname, 0, NULL, SMP_T_STR, SMP_USE_L5SRV },
Remi Tricot-Le Bretond42c8962023-02-28 17:46:24 +01001960 { "ssl_ocsp_status", smp_fetch_ssl_ocsp_status, 0, NULL, SMP_T_SINT, SMP_USE_L5SRV },
1961 { "ssl_ocsp_status_str", smp_fetch_ssl_ocsp_status_str, 0, NULL, SMP_T_STR, SMP_USE_L5SRV },
1962 { "ssl_ocsp_fail_cnt", smp_fetch_ssl_ocsp_fail_cnt, 0, NULL, SMP_T_SINT, SMP_USE_L5SRV },
1963 { "ssl_ocsp_success_cnt", smp_fetch_ssl_ocsp_success_cnt, 0, NULL, SMP_T_SINT, SMP_USE_L5SRV },
1964 { NULL, NULL, 0, 0, 0 },
1965}};
1966
1967INITCALL1(STG_REGISTER, sample_register_fetches, &sample_fetch_keywords);
1968
1969
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001970/*
1971 * Local variables:
1972 * c-indent-level: 8
1973 * c-basic-offset: 8
1974 * End:
1975 */