blob: 3e7408a667ec9ebabf2bded5f79b22d658246f75 [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;
Remi Tricot-Le Bretonad6cba82023-02-28 17:46:21 +0100846 struct appctx *appctx;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100847 int flags;
Remi Tricot-Le Bretonad6cba82023-02-28 17:46:21 +0100848 int update_status;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100849} ssl_ocsp_task_ctx;
850
851const struct http_hdr ocsp_request_hdrs[] = {
852 { IST("Content-Type"), IST("application/ocsp-request") },
853 { IST_NULL, IST_NULL }
854};
855
Remi Tricot-Le Bretonad6cba82023-02-28 17:46:21 +0100856enum {
857 OCSP_UPDT_UNKNOWN = 0,
858 OCSP_UPDT_OK = 1,
859 OCSP_UPDT_ERR_HTTP_STATUS = 2,
860 OCSP_UPDT_ERR_HTTP_HDR = 3,
861 OCSP_UPDT_ERR_CHECK = 4,
862 OCSP_UPDT_ERR_INSERT = 5,
863 OCSP_UPDT_ERR_LAST /* Must be last */
864};
865
866const struct ist ocsp_update_errors[] = {
867 [OCSP_UPDT_UNKNOWN] = IST("Unknown"),
868 [OCSP_UPDT_OK] = IST("Update successful"),
869 [OCSP_UPDT_ERR_HTTP_STATUS] = IST("HTTP error"),
870 [OCSP_UPDT_ERR_HTTP_HDR] = IST("Missing \"ocsp-response\" header"),
871 [OCSP_UPDT_ERR_CHECK] = IST("OCSP response check failure"),
872 [OCSP_UPDT_ERR_INSERT] = IST("Error during insertion")
873};
874
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100875static struct task *ssl_ocsp_update_responses(struct task *task, void *context, unsigned int state);
876
877/*
878 * Create the main OCSP update task that will iterate over the OCSP responses
879 * stored in ocsp_update_tree and send an OCSP request via the http_client
880 * applet to the corresponding OCSP responder. The task will then be in charge
881 * of processing the response, verifying it and resinserting it in the actual
882 * ocsp response tree if the response is valid.
883 * Returns 0 in case of success.
884 */
885int ssl_create_ocsp_update_task(char **err)
886{
887 if (ocsp_update_task)
888 return 0; /* Already created */
889
890 ocsp_update_task = task_new_anywhere();
891 if (!ocsp_update_task) {
892 memprintf(err, "parsing : failed to allocate global ocsp update task.");
893 return -1;
894 }
895
896 ocsp_update_task->process = ssl_ocsp_update_responses;
897 ocsp_update_task->context = NULL;
898
899 return 0;
900}
901
902static int ssl_ocsp_task_schedule()
903{
904 if (ocsp_update_task)
905 task_schedule(ocsp_update_task, now_ms);
906
907 return 0;
908}
909REGISTER_POST_CHECK(ssl_ocsp_task_schedule);
910
911void ssl_sock_free_ocsp(struct certificate_ocsp *ocsp);
912
913void ssl_destroy_ocsp_update_task(void)
914{
915 struct eb64_node *node, *next;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100916 if (!ocsp_update_task)
917 return;
918
919 HA_SPIN_LOCK(OCSP_LOCK, &ocsp_tree_lock);
920
921 node = eb64_first(&ocsp_update_tree);
922 while (node) {
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100923 next = eb64_next(node);
924 eb64_delete(node);
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100925 node = next;
926 }
927
928 HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
929
930 task_destroy(ocsp_update_task);
931 ocsp_update_task = NULL;
Remi Tricot-Le Breton14d7f0e2023-01-09 12:02:45 +0100932
933 ssl_sock_free_ocsp(ssl_ocsp_task_ctx.cur_ocsp);
934 ssl_ocsp_task_ctx.cur_ocsp = NULL;
Remi Tricot-Le Breton926f34b2023-02-28 17:46:18 +0100935
936 if (ssl_ocsp_task_ctx.hc) {
937 httpclient_stop_and_destroy(ssl_ocsp_task_ctx.hc);
938 ssl_ocsp_task_ctx.hc = NULL;
939 }
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100940}
941
Remi Tricot-Le Breton10f113e2023-01-12 09:49:11 +0100942static inline void ssl_ocsp_set_next_update(struct certificate_ocsp *ocsp)
943{
944 int update_margin = (ocsp->expire >= SSL_OCSP_UPDATE_MARGIN) ? SSL_OCSP_UPDATE_MARGIN : 0;
945
Remi Tricot-Le Breton56ab6072023-03-02 15:49:54 +0100946 ocsp->next_update.key = MIN(date.tv_sec + global_ssl.ocsp_update.delay_max,
Remi Tricot-Le Breton10f113e2023-01-12 09:49:11 +0100947 ocsp->expire - update_margin);
948
949 /* An already existing valid OCSP response that expires within less than
950 * SSL_OCSP_UPDATE_DELAY_MIN or has no 'Next Update' field should not be
951 * updated more than once every 5 minutes in order to avoid continuous
952 * update of the same response. */
953 if (b_data(&ocsp->response))
954 ocsp->next_update.key = MAX(ocsp->next_update.key,
Remi Tricot-Le Breton56ab6072023-03-02 15:49:54 +0100955 date.tv_sec + global_ssl.ocsp_update.delay_min);
Remi Tricot-Le Breton10f113e2023-01-12 09:49:11 +0100956}
957
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100958/*
959 * Insert a certificate_ocsp structure into the ocsp_update_tree tree, in which
960 * entries are sorted by absolute date of the next update. The next_update key
961 * will be the smallest out of the actual expire value of the response and
962 * now+1H. This arbitrary 1H value ensures that ocsp responses are updated
963 * periodically even when they have a long expire time, while not overloading
964 * the system too much (in theory). Likewise, a minimum 5 minutes interval is
965 * defined in order to avoid updating too often responses that have a really
966 * short expire time or even no 'Next Update' at all.
967 */
968int ssl_ocsp_update_insert(struct certificate_ocsp *ocsp)
969{
William Lallemand60289bf2024-02-26 17:53:02 +0100970 /* This entry was only supposed to be updated once, it does not need to
971 * be reinserted into the update tree.
972 */
973 if (ocsp->update_once)
974 return 0;
975
Remi Tricot-Le Breton10f113e2023-01-12 09:49:11 +0100976 /* Set next_update based on current time and the various OCSP
977 * minimum/maximum update times.
978 */
979 ssl_ocsp_set_next_update(ocsp);
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100980
Remi Tricot-Le Breton7e1a62e2023-02-28 17:46:27 +0100981 ocsp->fail_count = 0;
982
Remi Tricot-Le Breton10f113e2023-01-12 09:49:11 +0100983 HA_SPIN_LOCK(OCSP_LOCK, &ocsp_tree_lock);
William Lallemand60289bf2024-02-26 17:53:02 +0100984 eb64_insert(&ocsp_update_tree, &ocsp->next_update);
Remi Tricot-Le Breton10f113e2023-01-12 09:49:11 +0100985 HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +0100986
Remi Tricot-Le Breton10f113e2023-01-12 09:49:11 +0100987 return 0;
988}
989
990/*
991 * Reinsert an entry in the update tree. The entry's next update time can not
992 * occur before now+SSL_OCSP_HTTP_ERR_REPLAY.
993 * This is supposed to be used in case of http error (ocsp responder unreachable
994 * for instance). This ensures that the entry does not get reinserted at the
995 * beginning of the tree every time.
996 */
997int ssl_ocsp_update_insert_after_error(struct certificate_ocsp *ocsp)
998{
Remi Tricot-Le Breton7e1a62e2023-02-28 17:46:27 +0100999 int replay_delay = 0;
Remi Tricot-Le Bretona6c0a592023-03-13 15:56:32 +01001000
William Lallemand60289bf2024-02-26 17:53:02 +01001001 /* This entry was only supposed to be updated once, it does not need to
1002 * be reinserted into the update tree.
1003 */
1004 if (ocsp->update_once)
1005 return 0;
1006
Remi Tricot-Le Breton7e1a62e2023-02-28 17:46:27 +01001007 /*
1008 * Set next_update based on current time and the various OCSP
Remi Tricot-Le Breton10f113e2023-01-12 09:49:11 +01001009 * minimum/maximum update times.
1010 */
1011 ssl_ocsp_set_next_update(ocsp);
1012
Remi Tricot-Le Breton7e1a62e2023-02-28 17:46:27 +01001013 ++ocsp->fail_count;
1014
1015 /*
1016 * The replay delay will be increased for every consecutive update
1017 * failure, up to the SSL_OCSP_UPDATE_DELAY_MAX delay. It will ensure
1018 * that the replay delay will be one minute for the first failure and
1019 * will be multiplied by 2 for every subsequent failures, while still
1020 * being at most 1 hour (with the current default values).
1021 */
1022 replay_delay = MIN(SSL_OCSP_HTTP_ERR_REPLAY * (1 << ocsp->fail_count),
Remi Tricot-Le Breton58432372023-02-28 17:46:29 +01001023 global_ssl.ocsp_update.delay_max);
Remi Tricot-Le Breton7e1a62e2023-02-28 17:46:27 +01001024
Remi Tricot-Le Breton56ab6072023-03-02 15:49:54 +01001025 if (ocsp->next_update.key < date.tv_sec + replay_delay)
1026 ocsp->next_update.key = date.tv_sec + replay_delay;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001027
1028 HA_SPIN_LOCK(OCSP_LOCK, &ocsp_tree_lock);
William Lallemand60289bf2024-02-26 17:53:02 +01001029 eb64_insert(&ocsp_update_tree, &ocsp->next_update);
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001030 HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
1031
1032 return 0;
1033}
1034
1035void ocsp_update_response_stline_cb(struct httpclient *hc)
1036{
1037 struct task *task = hc->caller;
1038
1039 if (!task)
1040 return;
1041
1042 ssl_ocsp_task_ctx.flags |= HC_F_RES_STLINE;
1043 task_wakeup(task, TASK_WOKEN_MSG);
1044}
1045
1046void ocsp_update_response_headers_cb(struct httpclient *hc)
1047{
1048 struct task *task = hc->caller;
1049
1050 if (!task)
1051 return;
1052
1053 ssl_ocsp_task_ctx.flags |= HC_F_RES_HDR;
1054 task_wakeup(task, TASK_WOKEN_MSG);
1055}
1056
1057void ocsp_update_response_body_cb(struct httpclient *hc)
1058{
1059 struct task *task = hc->caller;
1060
1061 if (!task)
1062 return;
1063
1064 ssl_ocsp_task_ctx.flags |= HC_F_RES_BODY;
1065 task_wakeup(task, TASK_WOKEN_MSG);
1066}
1067
1068void ocsp_update_response_end_cb(struct httpclient *hc)
1069{
1070 struct task *task = hc->caller;
1071
1072 if (!task)
1073 return;
1074
1075 ssl_ocsp_task_ctx.flags |= HC_F_RES_END;
1076 task_wakeup(task, TASK_WOKEN_MSG);
1077}
1078
Remi Tricot-Le Bretonb33fe2f2023-02-28 17:46:25 +01001079
1080/*
1081 * Send a log line that will use the dedicated proxy's error_logformat string.
1082 * It uses the sess_log function instead of app_log for instance in order to
1083 * benefit from the "generic" items that can be added to a log format line such
1084 * as the date and frontend name that can be found at the beginning of the
1085 * ocspupdate_log_format line.
1086 */
1087static void ssl_ocsp_send_log()
1088{
1089 if (!ssl_ocsp_task_ctx.appctx)
1090 return;
1091
1092 sess_log(ssl_ocsp_task_ctx.appctx->sess);
1093}
1094
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001095/*
1096 * This is the main function of the ocsp auto update mechanism. It has two
1097 * distinct parts and the branching to one or the other is completely based on
1098 * the fact that the cur_ocsp pointer of the ssl_ocsp_task_ctx member is set.
1099 *
1100 * If the pointer is not set, we need to look at the first item of the update
1101 * tree and see if it needs to be updated. If it does not we simply wait until
1102 * the time is right and let the task asleep. If it does need to be updated, we
1103 * simply build and send the corresponding ocsp request thanks to the
1104 * http_client. The task is then sent to sleep with an expire time set to
1105 * infinity. The http_client will wake it back up once the response is received
1106 * (or a timeout occurs). Just note that during this whole process the
1107 * cetificate_ocsp object corresponding to the entry being updated is taken out
1108 * of the update tree and only stored in the ssl_ocsp_task_ctx context.
1109 *
1110 * Once the task is waken up by the http_client, it branches on the response
1111 * processing part of the function which basically checks that the response is
1112 * valid and inserts it into the ocsp_response tree. The task then goes back to
1113 * sleep until another entry needs to be updated.
1114 */
1115static struct task *ssl_ocsp_update_responses(struct task *task, void *context, unsigned int state)
1116{
Remi Tricot-Le Breton10f113e2023-01-12 09:49:11 +01001117 unsigned int next_wakeup = 0;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001118 struct eb64_node *eb;
1119 struct certificate_ocsp *ocsp;
1120 struct httpclient *hc = NULL;
1121 struct buffer *req_url = NULL;
1122 struct buffer *req_body = NULL;
1123 OCSP_CERTID *certid = NULL;
1124 struct ssl_ocsp_task_ctx *ctx = &ssl_ocsp_task_ctx;
1125
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001126 if (ctx->cur_ocsp) {
1127 /* An update is in process */
1128 ocsp = ctx->cur_ocsp;
1129 hc = ctx->hc;
1130 if (ctx->flags & HC_F_RES_STLINE) {
1131 if (hc->res.status != 200) {
Remi Tricot-Le Bretonad6cba82023-02-28 17:46:21 +01001132 ctx->update_status = OCSP_UPDT_ERR_HTTP_STATUS;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001133 goto http_error;
1134 }
1135 ctx->flags &= ~HC_F_RES_STLINE;
1136 }
1137
1138 if (ctx->flags & HC_F_RES_HDR) {
1139 struct http_hdr *hdr;
1140 int found = 0;
1141 /* Look for "Content-Type" header which should have
1142 * "application/ocsp-response" value. */
1143 for (hdr = hc->res.hdrs; isttest(hdr->v); hdr++) {
1144 if (isteqi(hdr->n, ist("Content-Type")) &&
1145 isteqi(hdr->v, ist("application/ocsp-response"))) {
1146 found = 1;
1147 break;
1148 }
1149 }
1150 if (!found) {
Remi Tricot-Le Bretonad6cba82023-02-28 17:46:21 +01001151 ctx->update_status = OCSP_UPDT_ERR_HTTP_HDR;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001152 goto http_error;
1153 }
1154 ctx->flags &= ~HC_F_RES_HDR;
1155 }
1156
1157 /* If the HC_F_RES_BODY is set, we still need for the
1158 * HC_F_RES_END flag to be set as well in order to be sure that
1159 * the body is complete. */
1160
1161 /* we must close only if F_RES_END is the last flag */
1162 if (ctx->flags & HC_F_RES_END) {
1163
1164 /* Process the body that must be complete since
1165 * HC_F_RES_END is set. */
1166 if (ctx->flags & HC_F_RES_BODY) {
Remi Tricot-Le Bretonad6cba82023-02-28 17:46:21 +01001167 if (ssl_ocsp_check_response(ocsp->chain, ocsp->issuer, &hc->res.buf, NULL)) {
1168 ctx->update_status = OCSP_UPDT_ERR_CHECK;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001169 goto http_error;
Remi Tricot-Le Bretonad6cba82023-02-28 17:46:21 +01001170 }
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001171
1172 if (ssl_sock_update_ocsp_response(&hc->res.buf, NULL) != 0) {
Remi Tricot-Le Bretonad6cba82023-02-28 17:46:21 +01001173 ctx->update_status = OCSP_UPDT_ERR_INSERT;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001174 goto http_error;
1175 }
1176
1177 ctx->flags &= ~HC_F_RES_BODY;
1178 }
1179
1180 ctx->flags &= ~HC_F_RES_END;
1181
Remi Tricot-Le Breton9e94df32023-02-28 17:46:20 +01001182 ++ocsp->num_success;
Remi Tricot-Le Breton56ab6072023-03-02 15:49:54 +01001183 ocsp->last_update = date.tv_sec;
Remi Tricot-Le Bretonad6cba82023-02-28 17:46:21 +01001184 ctx->update_status = OCSP_UPDT_OK;
1185 ocsp->last_update_status = ctx->update_status;
Remi Tricot-Le Breton9e94df32023-02-28 17:46:20 +01001186
Remi Tricot-Le Bretonb33fe2f2023-02-28 17:46:25 +01001187 ssl_ocsp_send_log();
1188
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001189 /* Reinsert the entry into the update list so that it can be updated later */
1190 ssl_ocsp_update_insert(ocsp);
Remi Tricot-Le Breton57f60c22023-01-09 12:02:42 +01001191 /* Release the reference kept on the updated ocsp response. */
William Lallemand60289bf2024-02-26 17:53:02 +01001192 ssl_sock_free_ocsp(ctx->cur_ocsp);
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001193 ctx->cur_ocsp = NULL;
1194
1195 HA_SPIN_LOCK(OCSP_LOCK, &ocsp_tree_lock);
1196 /* Set next_wakeup to the new first entry of the tree */
1197 eb = eb64_first(&ocsp_update_tree);
1198 if (eb) {
Remi Tricot-Le Breton56ab6072023-03-02 15:49:54 +01001199 if (eb->key > date.tv_sec)
1200 next_wakeup = (eb->key - date.tv_sec)*1000;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001201 else
1202 next_wakeup = 0;
1203 }
1204 HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
1205 goto leave;
1206 }
1207
1208 /* We did not receive the HC_F_RES_END flag yet, wait for it
1209 * before trying to update a new ocsp response. */
1210 goto wait;
1211 } else {
1212 /* Look for next entry that needs to be updated. */
1213 const unsigned char *p = NULL;
1214
1215 HA_SPIN_LOCK(OCSP_LOCK, &ocsp_tree_lock);
1216
1217 eb = eb64_first(&ocsp_update_tree);
1218 if (!eb) {
1219 HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
Remi Tricot-Le Breton1c647ad2023-01-12 09:49:10 +01001220 goto wait;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001221 }
1222
Remi Tricot-Le Breton56ab6072023-03-02 15:49:54 +01001223 if (eb->key > date.tv_sec) {
1224 next_wakeup = (eb->key - date.tv_sec)*1000;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001225 HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
1226 goto leave;
1227 }
1228
1229 ocsp = eb64_entry(eb, struct certificate_ocsp, next_update);
1230
1231 /* Take the current entry out of the update tree, it will be
1232 * reinserted after the response is processed. */
1233 eb64_delete(&ocsp->next_update);
1234
William Lallemand60289bf2024-02-26 17:53:02 +01001235 ++ocsp->refcount;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001236 ctx->cur_ocsp = ocsp;
Remi Tricot-Le Bretonad6cba82023-02-28 17:46:21 +01001237 ocsp->last_update_status = OCSP_UPDT_UNKNOWN;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001238
1239 HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
1240
1241 req_url = alloc_trash_chunk();
1242 if (!req_url) {
1243 goto leave;
1244 }
1245 req_body = alloc_trash_chunk();
1246 if (!req_body) {
1247 goto leave;
1248 }
1249
1250 p = ocsp->key_data;
1251
1252 d2i_OCSP_CERTID(&certid, &p, ocsp->key_length);
1253 if (!certid)
1254 goto leave;
1255
1256 /* Copy OCSP URI stored in ocsp structure into req_url */
1257 chunk_cpy(req_url, ocsp->uri);
1258
1259 /* Create ocsp request */
1260 if (ssl_ocsp_create_request_details(certid, req_url, req_body, NULL) != 0) {
1261 goto leave;
1262 }
1263
1264 /* Depending on the processing that occurred in
1265 * ssl_ocsp_create_request_details we could either have to send
1266 * a GET or a POST request. */
Remi Tricot-Le Bretonb33fe2f2023-02-28 17:46:25 +01001267 hc = httpclient_new_from_proxy(httpclient_ocsp_update_px, task,
1268 b_data(req_body) ? HTTP_METH_POST : HTTP_METH_GET,
1269 ist2(b_orig(req_url), b_data(req_url)));
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001270 if (!hc) {
1271 goto leave;
1272 }
1273
1274 if (httpclient_req_gen(hc, hc->req.url, hc->req.meth,
1275 b_data(req_body) ? ocsp_request_hdrs : NULL,
William Lallemand70601c52022-12-22 14:34:01 +01001276 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 +01001277 goto leave;
1278 }
1279
1280 hc->ops.res_stline = ocsp_update_response_stline_cb;
1281 hc->ops.res_headers = ocsp_update_response_headers_cb;
1282 hc->ops.res_payload = ocsp_update_response_body_cb;
1283 hc->ops.res_end = ocsp_update_response_end_cb;
1284
Remi Tricot-Le Bretonad6cba82023-02-28 17:46:21 +01001285 if (!(ctx->appctx = httpclient_start(hc))) {
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001286 goto leave;
1287 }
1288
1289 ctx->flags = 0;
1290 ctx->hc = hc;
1291
1292 /* We keep the lock, this indicates that an update is in process. */
1293 goto wait;
1294 }
1295
1296leave:
1297 if (ctx->cur_ocsp) {
1298 /* Something went wrong, reinsert the entry in the tree. */
Remi Tricot-Le Breton9e94df32023-02-28 17:46:20 +01001299 ++ctx->cur_ocsp->num_failure;
Remi Tricot-Le Breton6de7b782023-02-28 17:46:19 +01001300 ssl_ocsp_update_insert_after_error(ctx->cur_ocsp);
Remi Tricot-Le Breton57f60c22023-01-09 12:02:42 +01001301 /* Release the reference kept on the updated ocsp response. */
William Lallemand60289bf2024-02-26 17:53:02 +01001302 ssl_sock_free_ocsp(ctx->cur_ocsp);
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001303 ctx->cur_ocsp = NULL;
1304 }
1305 if (hc)
1306 httpclient_stop_and_destroy(hc);
Remi Tricot-Le Bretonf64a0592023-03-13 15:56:33 +01001307 ctx->hc = NULL;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001308 free_trash_chunk(req_url);
1309 free_trash_chunk(req_body);
1310 task->expire = tick_add(now_ms, next_wakeup);
1311 return task;
1312
1313wait:
1314 free_trash_chunk(req_url);
1315 free_trash_chunk(req_body);
1316 task->expire = TICK_ETERNITY;
1317 return task;
1318
1319http_error:
Remi Tricot-Le Bretonb33fe2f2023-02-28 17:46:25 +01001320 ssl_ocsp_send_log();
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001321 /* Reinsert certificate into update list so that it can be updated later */
Remi Tricot-Le Breton9e94df32023-02-28 17:46:20 +01001322 if (ocsp) {
1323 ++ocsp->num_failure;
Remi Tricot-Le Bretonad6cba82023-02-28 17:46:21 +01001324 ocsp->last_update_status = ctx->update_status;
Remi Tricot-Le Breton10f113e2023-01-12 09:49:11 +01001325 ssl_ocsp_update_insert_after_error(ocsp);
Remi Tricot-Le Breton9e94df32023-02-28 17:46:20 +01001326 }
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001327
1328 if (hc)
1329 httpclient_stop_and_destroy(hc);
Remi Tricot-Le Breton57f60c22023-01-09 12:02:42 +01001330 /* Release the reference kept on the updated ocsp response. */
William Lallemand60289bf2024-02-26 17:53:02 +01001331 ssl_sock_free_ocsp(ctx->cur_ocsp);
Remi Tricot-Le Breton10f113e2023-01-12 09:49:11 +01001332 HA_SPIN_LOCK(OCSP_LOCK, &ocsp_tree_lock);
1333 /* Set next_wakeup to the new first entry of the tree */
1334 eb = eb64_first(&ocsp_update_tree);
1335 if (eb) {
Remi Tricot-Le Breton56ab6072023-03-02 15:49:54 +01001336 if (eb->key > date.tv_sec)
1337 next_wakeup = (eb->key - date.tv_sec)*1000;
Remi Tricot-Le Breton10f113e2023-01-12 09:49:11 +01001338 else
1339 next_wakeup = 0;
1340 }
1341 HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001342 ctx->cur_ocsp = NULL;
1343 ctx->hc = NULL;
1344 ctx->flags = 0;
1345 task->expire = tick_add(now_ms, next_wakeup);
1346 return task;
1347}
1348
Remi Tricot-Le Bretonc9bfe322023-03-13 15:56:31 +01001349char ocspupdate_log_format[] = "%ci:%cp [%tr] %ft %[ssl_ocsp_certname] %[ssl_ocsp_status] %{+Q}[ssl_ocsp_status_str] %[ssl_ocsp_fail_cnt] %[ssl_ocsp_success_cnt]";
Remi Tricot-Le Bretonb33fe2f2023-02-28 17:46:25 +01001350
1351/*
1352 * Initialize the proxy for the OCSP update HTTP client with 2 servers, one for
1353 * raw HTTP, the other for HTTPS.
1354 */
1355static int ssl_ocsp_update_precheck()
1356{
1357 /* initialize the OCSP update dedicated httpclient */
Remi Tricot-Le Bretonc9bfe322023-03-13 15:56:31 +01001358 httpclient_ocsp_update_px = httpclient_create_proxy("<OCSP-UPDATE>");
Remi Tricot-Le Bretonb33fe2f2023-02-28 17:46:25 +01001359 if (!httpclient_ocsp_update_px)
1360 return 1;
1361 httpclient_ocsp_update_px->conf.error_logformat_string = strdup(ocspupdate_log_format);
1362 httpclient_ocsp_update_px->conf.logformat_string = httpclient_log_format;
1363 httpclient_ocsp_update_px->options2 |= PR_O2_NOLOGNORM;
1364
1365 return 0;
1366}
1367
1368/* initialize the proxy and servers for the HTTP client */
1369
1370REGISTER_PRE_CHECK(ssl_ocsp_update_precheck);
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001371
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001372
1373static int cli_parse_update_ocsp_response(char **args, char *payload, struct appctx *appctx, void *private)
1374{
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001375 char *err = NULL;
1376 struct ckch_store *ckch_store = NULL;
Remi Tricot-Le Bretona6c0a592023-03-13 15:56:32 +01001377 struct certificate_ocsp *ocsp = NULL;
1378 int update_once = 0;
1379 unsigned char key[OCSP_MAX_CERTID_ASN1_LENGTH] = {};
1380 unsigned char *p;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001381
1382 if (!*args[3]) {
1383 memprintf(&err, "'update ssl ocsp-response' expects a filename\n");
1384 return cli_dynerr(appctx, err);
1385 }
1386
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001387 /* The operations on the CKCH architecture are locked so we can
1388 * manipulate ckch_store and ckch_inst */
1389 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock)) {
1390 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 +01001391 goto end;
1392 }
1393
1394 ckch_store = ckchs_lookup(args[3]);
1395
1396 if (!ckch_store) {
Remi Tricot-Le Breton14419eb2023-01-09 12:02:49 +01001397 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 +01001398 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1399 goto end;
1400 }
1401
Remi Tricot-Le Bretona6c0a592023-03-13 15:56:32 +01001402 p = key;
1403 i2d_OCSP_CERTID(ckch_store->data->ocsp_cid, &p);
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001404
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001405 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
1406
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001407
Remi Tricot-Le Bretona6c0a592023-03-13 15:56:32 +01001408 HA_SPIN_LOCK(OCSP_LOCK, &ocsp_tree_lock);
1409 ocsp = (struct certificate_ocsp *)ebmb_lookup(&cert_ocsp_tree, key, OCSP_MAX_CERTID_ASN1_LENGTH);
1410 if (!ocsp) {
1411 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 +01001412 HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001413 goto end;
1414 }
1415
William Lallemand60289bf2024-02-26 17:53:02 +01001416 update_once = (ocsp->next_update.node.leaf_p == NULL);
1417 eb64_delete(&ocsp->next_update);
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001418
William Lallemand60289bf2024-02-26 17:53:02 +01001419 /* Insert the entry at the beginning of the update tree. */
1420 ocsp->next_update.key = 0;
1421 eb64_insert(&ocsp_update_tree, &ocsp->next_update);
1422 ocsp->update_once = update_once;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001423
Remi Tricot-Le Bretona6c0a592023-03-13 15:56:32 +01001424 HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001425
Remi Tricot-Le Bretona6c0a592023-03-13 15:56:32 +01001426 if (!ocsp_update_task)
1427 ssl_create_ocsp_update_task(&err);
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001428
Remi Tricot-Le Bretona6c0a592023-03-13 15:56:32 +01001429 task_wakeup(ocsp_update_task, TASK_WOKEN_MSG);
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001430
Remi Tricot-Le Bretond32c8e32023-03-21 10:28:34 +01001431 free(err);
1432
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001433 return 0;
1434
1435end:
Remi Tricot-Le Bretonae518772023-03-21 10:26:20 +01001436 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 +01001437}
1438
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001439#endif /* !defined OPENSSL_IS_BORINGSSL */
1440
1441
1442#endif /* (defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) */
1443
1444
1445static int cli_parse_set_ocspresponse(char **args, char *payload, struct appctx *appctx, void *private)
1446{
1447#if (defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP)
1448 char *err = NULL;
1449 int i, j, ret;
1450
1451 if (!payload)
1452 payload = args[3];
1453
1454 /* Expect one parameter: the new response in base64 encoding */
1455 if (!*payload)
1456 return cli_err(appctx, "'set ssl ocsp-response' expects response in base64 encoding.\n");
1457
1458 /* remove \r and \n from the payload */
1459 for (i = 0, j = 0; payload[i]; i++) {
1460 if (payload[i] == '\r' || payload[i] == '\n')
1461 continue;
1462 payload[j++] = payload[i];
1463 }
1464 payload[j] = 0;
1465
1466 ret = base64dec(payload, j, trash.area, trash.size);
1467 if (ret < 0)
1468 return cli_err(appctx, "'set ssl ocsp-response' received invalid base64 encoded response.\n");
1469
1470 trash.data = ret;
1471 if (ssl_sock_update_ocsp_response(&trash, &err)) {
1472 if (err)
1473 return cli_dynerr(appctx, memprintf(&err, "%s.\n", err));
1474 else
1475 return cli_err(appctx, "Failed to update OCSP response.\n");
1476 }
1477
1478 return cli_msg(appctx, LOG_INFO, "OCSP Response updated!\n");
1479#else
1480 return cli_err(appctx, "HAProxy was compiled against a version of OpenSSL that doesn't support OCSP stapling.\n");
1481#endif
1482
1483}
1484
1485/* parsing function for 'show ssl ocsp-response [id]'. If an entry is forced,
1486 * it's set into appctx->svcctx.
1487 */
1488static int cli_parse_show_ocspresponse(char **args, char *payload, struct appctx *appctx, void *private)
1489{
1490#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 +01001491
1492 struct show_ocspresp_cli_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
Remi Tricot-Le Bretondafc0682023-03-13 15:56:34 +01001493 int arg_idx = 3;
Remi Tricot-Le Breton9c4437d2023-02-28 17:46:28 +01001494
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001495 if (*args[3]) {
1496 struct certificate_ocsp *ocsp = NULL;
1497 char key[OCSP_MAX_CERTID_ASN1_LENGTH] = {};
1498 int key_length = OCSP_MAX_CERTID_ASN1_LENGTH;
1499 char *key_ptr = key;
Remi Tricot-Le Bretondafc0682023-03-13 15:56:34 +01001500 unsigned char *p;
1501 struct ckch_store *ckch_store = NULL;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001502
Remi Tricot-Le Breton9c4437d2023-02-28 17:46:28 +01001503 if (strcmp(args[3], "text") == 0) {
1504 ctx->format = SHOW_OCSPRESP_FMT_TEXT;
Remi Tricot-Le Bretondafc0682023-03-13 15:56:34 +01001505 ++arg_idx;
Remi Tricot-Le Breton9c4437d2023-02-28 17:46:28 +01001506 } else if (strcmp(args[3], "base64") == 0) {
1507 ctx->format = SHOW_OCSPRESP_FMT_B64;
Remi Tricot-Le Bretondafc0682023-03-13 15:56:34 +01001508 ++arg_idx;
Remi Tricot-Le Breton9c4437d2023-02-28 17:46:28 +01001509 }
1510
Remi Tricot-Le Bretondafc0682023-03-13 15:56:34 +01001511 if (ctx->format != SHOW_OCSPRESP_FMT_DFLT && !*args[arg_idx])
Remi Tricot-Le Breton9c4437d2023-02-28 17:46:28 +01001512 return cli_err(appctx, "'show ssl ocsp-response [text|base64]' expects a valid certid.\n");
1513
Remi Tricot-Le Bretondafc0682023-03-13 15:56:34 +01001514 /* Try to convert parameter into an OCSP certid first, and consider it
1515 * as a filename if it fails. */
1516 if (strlen(args[arg_idx]) > OCSP_MAX_CERTID_ASN1_LENGTH*2 ||
1517 !parse_binary(args[arg_idx], &key_ptr, &key_length, NULL)) {
1518
1519 key_ptr = key;
1520 key_length = 0;
1521
1522 /* The operations on the CKCH architecture are locked so we can
1523 * manipulate ckch_store and ckch_inst */
1524 if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock)) {
1525 return cli_err(appctx, "Operations on certificates are currently locked!\n");
1526 }
1527
1528 ckch_store = ckchs_lookup(args[arg_idx]);
1529
1530 if (ckch_store) {
1531 p = (unsigned char*)key;
1532 key_length = i2d_OCSP_CERTID(ckch_store->data->ocsp_cid, &p);
1533 }
1534 HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001535 }
1536
Remi Tricot-Le Bretondafc0682023-03-13 15:56:34 +01001537 if (key_length == 0) {
1538 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 +01001539 }
1540
1541 HA_SPIN_LOCK(OCSP_LOCK, &ocsp_tree_lock);
1542 ocsp = (struct certificate_ocsp *)ebmb_lookup(&cert_ocsp_tree, key, OCSP_MAX_CERTID_ASN1_LENGTH);
1543
1544 if (!ocsp) {
1545 HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
Remi Tricot-Le Bretondafc0682023-03-13 15:56:34 +01001546 return cli_err(appctx, "Certificate ID or path does not match any certificate.\n");
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001547 }
William Lallemand60289bf2024-02-26 17:53:02 +01001548 ++ocsp->refcount;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001549 HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
1550
Remi Tricot-Le Breton9c4437d2023-02-28 17:46:28 +01001551 ctx->ocsp = ocsp;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001552 appctx->io_handler = cli_io_handler_show_ocspresponse_detail;
1553 }
1554
1555 return 0;
1556
1557#else
1558 return cli_err(appctx, "HAProxy was compiled against a version of OpenSSL that doesn't support OCSP stapling.\n");
1559#endif
1560}
1561
1562/*
1563 * IO handler of "show ssl ocsp-response". The command taking a specific ID
1564 * is managed in cli_io_handler_show_ocspresponse_detail.
1565 * The current entry is taken from appctx->svcctx.
1566 */
1567static int cli_io_handler_show_ocspresponse(struct appctx *appctx)
1568{
1569#if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) && !defined OPENSSL_IS_BORINGSSL)
1570 struct buffer *trash = alloc_trash_chunk();
1571 struct buffer *tmp = NULL;
1572 struct ebmb_node *node;
1573 struct certificate_ocsp *ocsp = NULL;
1574 BIO *bio = NULL;
1575 int write = -1;
Remi Tricot-Le Breton9c4437d2023-02-28 17:46:28 +01001576 struct show_ocspresp_cli_ctx *ctx = appctx->svcctx;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001577
1578 if (trash == NULL)
1579 return 1;
1580
1581 HA_SPIN_LOCK(OCSP_LOCK, &ocsp_tree_lock);
1582
1583 tmp = alloc_trash_chunk();
1584 if (!tmp)
1585 goto end;
1586
1587 if ((bio = BIO_new(BIO_s_mem())) == NULL)
1588 goto end;
1589
Remi Tricot-Le Breton9c4437d2023-02-28 17:46:28 +01001590 if (!ctx->ocsp) {
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001591 chunk_appendf(trash, "# Certificate IDs\n");
1592 node = ebmb_first(&cert_ocsp_tree);
1593 } else {
Remi Tricot-Le Breton9c4437d2023-02-28 17:46:28 +01001594 node = &ctx->ocsp->key;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001595 }
1596
1597 while (node) {
1598 OCSP_CERTID *certid = NULL;
1599 const unsigned char *p = NULL;
1600 int i;
1601
1602 ocsp = ebmb_entry(node, struct certificate_ocsp, key);
1603
1604 /* Dump the key in hexadecimal */
1605 chunk_appendf(trash, "Certificate ID key : ");
1606 for (i = 0; i < ocsp->key_length; ++i) {
1607 chunk_appendf(trash, "%02x", ocsp->key_data[i]);
1608 }
1609 chunk_appendf(trash, "\n");
1610
Remi Tricot-Le Breton7716f272023-03-13 15:56:35 +01001611 /* Dump the certificate path */
1612 chunk_appendf(trash, "Certificate path : %s\n", ocsp->path);
1613
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001614 p = ocsp->key_data;
1615
1616 /* Decode the certificate ID (serialized into the key). */
1617 d2i_OCSP_CERTID(&certid, &p, ocsp->key_length);
1618 if (!certid)
1619 goto end;
1620
1621 /* Dump the CERTID info */
1622 ocsp_certid_print(bio, certid, 1);
1623 OCSP_CERTID_free(certid);
1624 write = BIO_read(bio, tmp->area, tmp->size-1);
1625 /* strip trailing LFs */
1626 while (write > 0 && tmp->area[write-1] == '\n')
1627 write--;
1628 tmp->area[write] = '\0';
1629
1630 chunk_appendf(trash, "%s\n", tmp->area);
1631
1632 node = ebmb_next(node);
1633 if (applet_putchk(appctx, trash) == -1)
1634 goto yield;
1635 }
1636
1637end:
1638 HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001639 free_trash_chunk(trash);
1640 free_trash_chunk(tmp);
1641 BIO_free(bio);
1642 return 1;
1643
1644yield:
1645 free_trash_chunk(trash);
1646 free_trash_chunk(tmp);
1647 BIO_free(bio);
1648
William Lallemand60289bf2024-02-26 17:53:02 +01001649 ++ocsp->refcount;
Remi Tricot-Le Breton9c4437d2023-02-28 17:46:28 +01001650 ctx->ocsp = ocsp;
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001651 HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
1652 return 0;
1653#else
1654 return cli_err(appctx, "HAProxy was compiled against a version of OpenSSL that doesn't support OCSP stapling.\n");
1655#endif
1656}
1657
William Lallemanda14686d2023-02-07 18:38:05 +01001658/* Check if the ckch_store and the entry does have the same configuration */
1659int ocsp_update_check_cfg_consistency(struct ckch_store *store, struct crtlist_entry *entry, char *crt_path, char **err)
1660{
1661 int err_code = ERR_NONE;
1662
1663 if (store->data->ocsp_update_mode != SSL_SOCK_OCSP_UPDATE_DFLT || entry->ssl_conf) {
1664 if ((!entry->ssl_conf && store->data->ocsp_update_mode == SSL_SOCK_OCSP_UPDATE_ON)
1665 || (entry->ssl_conf && store->data->ocsp_update_mode != entry->ssl_conf->ocsp_update)) {
1666 memprintf(err, "%sIncompatibilities found in OCSP update mode for certificate %s\n", err && *err ? *err : "", crt_path);
1667 err_code |= ERR_ALERT | ERR_FATAL;
1668 }
1669 }
1670 return err_code;
1671}
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001672
Remi Tricot-Le Bretond14fc512023-02-28 17:46:23 +01001673struct show_ocsp_updates_ctx {
1674 struct certificate_ocsp *cur_ocsp;
1675};
1676
1677/*
1678 * Parsing function for 'show ssl ocsp-updates [nb]'.
1679 */
1680static int cli_parse_show_ocsp_updates(char **args, char *payload, struct appctx *appctx, void *private)
1681{
1682#if ((defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP) && !defined OPENSSL_IS_BORINGSSL)
1683 struct show_ocsp_updates_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
1684
1685 HA_SPIN_LOCK(OCSP_LOCK, &ocsp_tree_lock);
1686
1687 return 0;
1688#else
1689 return cli_err(appctx, "HAProxy was compiled against a version of OpenSSL that doesn't support OCSP stapling.\n");
1690#endif
1691}
1692
1693/*
1694 * Dump information about an ocsp response concerning ocsp auto update.
1695 * It follows the following format :
1696 * OCSP Certid | Path | Next Update | Last Update | Successes | Failures | Last Update Status | Last Update Status (str)
1697 * Return 0 in case of success.
1698 */
1699static int dump_ocsp_update_info(struct certificate_ocsp *ocsp, struct buffer *out)
1700{
1701 struct tm tm = {};
1702 char *ret;
1703 int i;
1704 time_t next_update;
1705
1706 /* Dump OCSP certid */
1707 for (i = 0; i < ocsp->key_length; ++i) {
1708 chunk_appendf(out, "%02x", ocsp->key_data[i]);
1709 }
1710
1711 chunk_appendf(out, " | ");
1712
1713 /* Dump path */
1714 chunk_appendf(out, "%s", ocsp->path);
1715
1716 chunk_appendf(out, " | ");
1717
1718 /* Dump next update time */
1719 if (ocsp->next_update.key != 0) {
1720 next_update = ocsp->next_update.key;
1721 get_localtime(ocsp->next_update.key, &tm);
1722 } else {
1723 next_update = date.tv_sec;
1724 get_localtime(date.tv_sec, &tm);
1725 }
1726 ret = localdate2str_log(b_orig(out)+b_data(out), next_update, &tm, b_size(out)-b_data(out));
1727
1728 if (ret == NULL)
1729 return 1;
1730
1731 out->data = (ret - out->area);
1732
1733 chunk_appendf(out, " | ");
1734
1735 /* Dump last update time or "-" if no update occurred yet */
1736 if (ocsp->last_update) {
1737 get_localtime(ocsp->last_update, &tm);
1738 ret = localdate2str_log(b_orig(out)+b_data(out), ocsp->last_update, &tm, b_size(out)-b_data(out));
1739
1740 if (ret == NULL)
1741 return 1;
1742
1743 out->data = (ret - out->area);
1744 } else
1745 chunk_appendf(out, "-");
1746
1747 chunk_appendf(out, " | ");
1748
1749 /* Number of successful updates */
1750 chunk_appendf(out, "%d", ocsp->num_success);
1751
1752 chunk_appendf(out, " | ");
1753
1754 /* Number of failed updates */
1755 chunk_appendf(out, "%d", ocsp->num_failure);
1756
1757 chunk_appendf(out, " | ");
1758
1759 /* Last update status */
1760 chunk_appendf(out, "%d", ocsp->last_update_status);
1761
1762 chunk_appendf(out, " | ");
1763
1764 /* Last update status str */
1765 if (ocsp->last_update_status >= OCSP_UPDT_ERR_LAST)
1766 chunk_appendf(out, "-");
1767 else
1768 chunk_appendf(out, "%s", istptr(ocsp_update_errors[ocsp->last_update_status]));
1769
1770 chunk_appendf(out, "\n");
1771
1772 return 0;
1773}
1774
1775static int cli_io_handler_show_ocsp_updates(struct appctx *appctx)
1776{
1777 struct show_ocsp_updates_ctx *ctx = appctx->svcctx;
1778 struct eb64_node *node;
1779 struct certificate_ocsp *ocsp = NULL;
1780 struct buffer *trash = get_trash_chunk();
1781
1782 if (!ctx->cur_ocsp) {
1783 node = eb64_first(&ocsp_update_tree);
1784 chunk_appendf(trash, "OCSP Certid | Path | Next Update | Last Update | Successes | Failures | Last Update Status | Last Update Status (str)\n");
1785
1786 /* Look for an entry currently being updated */
1787 ocsp = ssl_ocsp_task_ctx.cur_ocsp;
1788 if (ocsp) {
1789 if (dump_ocsp_update_info(ocsp, trash))
1790 goto end;
1791 }
1792
1793 if (applet_putchk(appctx, trash) == -1)
1794 goto yield;
1795
1796 } else {
1797 node = &((struct certificate_ocsp*)ctx->cur_ocsp)->next_update;
1798 }
1799
1800 while (node) {
1801 ocsp = eb64_entry(node, struct certificate_ocsp, next_update);
1802
1803 chunk_reset(trash);
1804 if (dump_ocsp_update_info(ocsp, trash))
1805 goto end;
1806
1807 if (applet_putchk(appctx, trash) == -1) {
1808 ctx->cur_ocsp = ocsp;
1809 goto yield;
1810 }
1811
1812 node = eb64_next(node);
1813 }
1814
1815end:
1816 return 1;
1817
1818yield:
1819 return 0; /* should come back */
1820}
1821
1822static void cli_release_show_ocsp_updates(struct appctx *appctx)
1823{
1824 HA_SPIN_UNLOCK(OCSP_LOCK, &ocsp_tree_lock);
1825}
1826
1827
Remi Tricot-Le Bretond42c8962023-02-28 17:46:24 +01001828static int
1829smp_fetch_ssl_ocsp_certid(const struct arg *args, struct sample *smp, const char *kw, void *private)
1830{
1831 struct buffer *data = get_trash_chunk();
1832 struct certificate_ocsp *ocsp = ssl_ocsp_task_ctx.cur_ocsp;
1833
1834 if (!ocsp)
1835 return 0;
1836
1837 dump_binary(data, (char *)ocsp->key_data, ocsp->key_length);
1838
1839 smp->data.type = SMP_T_STR;
1840 smp->data.u.str = *data;
1841 return 1;
1842}
1843
1844static int
Remi Tricot-Le Bretonc9bfe322023-03-13 15:56:31 +01001845smp_fetch_ssl_ocsp_certname(const struct arg *args, struct sample *smp, const char *kw, void *private)
1846{
1847 struct certificate_ocsp *ocsp = ssl_ocsp_task_ctx.cur_ocsp;
1848
1849 if (!ocsp)
1850 return 0;
1851
1852 smp->data.type = SMP_T_STR;
1853 smp->data.u.str.area = ocsp->path;
1854 smp->data.u.str.data = strlen(ocsp->path);
1855 return 1;
1856}
1857
1858static int
Remi Tricot-Le Bretond42c8962023-02-28 17:46:24 +01001859smp_fetch_ssl_ocsp_status(const struct arg *args, struct sample *smp, const char *kw, void *private)
1860{
1861 struct certificate_ocsp *ocsp = ssl_ocsp_task_ctx.cur_ocsp;
1862
1863 if (!ocsp)
1864 return 0;
1865
1866 smp->data.type = SMP_T_SINT;
1867 smp->data.u.sint = ssl_ocsp_task_ctx.update_status;
1868 return 1;
1869}
1870
1871static int
1872smp_fetch_ssl_ocsp_status_str(const struct arg *args, struct sample *smp, const char *kw, void *private)
1873{
1874 struct certificate_ocsp *ocsp = ssl_ocsp_task_ctx.cur_ocsp;
1875
1876 if (!ocsp)
1877 return 0;
1878
1879 if (ssl_ocsp_task_ctx.update_status >= OCSP_UPDT_ERR_LAST)
1880 return 0;
1881
1882 smp->data.type = SMP_T_STR;
1883 smp->data.u.str = ist2buf(ocsp_update_errors[ssl_ocsp_task_ctx.update_status]);
1884
1885 return 1;
1886}
1887
1888static int
1889smp_fetch_ssl_ocsp_fail_cnt(const struct arg *args, struct sample *smp, const char *kw, void *private)
1890{
1891 struct certificate_ocsp *ocsp = ssl_ocsp_task_ctx.cur_ocsp;
1892
1893 if (!ocsp)
1894 return 0;
1895
1896 smp->data.type = SMP_T_SINT;
1897 smp->data.u.sint = ocsp->num_failure;
1898 return 1;
1899}
1900
1901static int
1902smp_fetch_ssl_ocsp_success_cnt(const struct arg *args, struct sample *smp, const char *kw, void *private)
1903{
1904 struct certificate_ocsp *ocsp = ssl_ocsp_task_ctx.cur_ocsp;
1905
1906 if (!ocsp)
1907 return 0;
1908
1909 smp->data.type = SMP_T_SINT;
1910 smp->data.u.sint = ocsp->num_success;
1911 return 1;
1912}
1913
1914
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001915static struct cli_kw_list cli_kws = {{ },{
Remi Tricot-Le Breton9c4437d2023-02-28 17:46:28 +01001916 { { "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 +01001917
William Lallemand60289bf2024-02-26 17:53:02 +01001918 { { "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 +01001919 { { "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 +01001920#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 +01001921 { { "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 +01001922#endif
1923 { { NULL }, NULL, NULL, NULL }
1924}};
1925
1926INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws);
1927
Remi Tricot-Le Bretond42c8962023-02-28 17:46:24 +01001928
1929/* Note: must not be declared <const> as its list will be overwritten.
1930 * Please take care of keeping this list alphabetically sorted.
1931 *
1932 * Those fetches only have a valid value during an OCSP update process so they
1933 * can only be used in a log format of a log line built by the update process
1934 * task itself.
1935 */
1936static struct sample_fetch_kw_list sample_fetch_keywords = {ILH, {
1937 { "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 +01001938 { "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 +01001939 { "ssl_ocsp_status", smp_fetch_ssl_ocsp_status, 0, NULL, SMP_T_SINT, SMP_USE_L5SRV },
1940 { "ssl_ocsp_status_str", smp_fetch_ssl_ocsp_status_str, 0, NULL, SMP_T_STR, SMP_USE_L5SRV },
1941 { "ssl_ocsp_fail_cnt", smp_fetch_ssl_ocsp_fail_cnt, 0, NULL, SMP_T_SINT, SMP_USE_L5SRV },
1942 { "ssl_ocsp_success_cnt", smp_fetch_ssl_ocsp_success_cnt, 0, NULL, SMP_T_SINT, SMP_USE_L5SRV },
1943 { NULL, NULL, 0, 0, 0 },
1944}};
1945
1946INITCALL1(STG_REGISTER, sample_register_fetches, &sample_fetch_keywords);
1947
1948
Remi Tricot-Le Bretonc8d814e2022-12-20 11:11:17 +01001949/*
1950 * Local variables:
1951 * c-indent-level: 8
1952 * c-basic-offset: 8
1953 * End:
1954 */