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