The FreeRADIUS server $Id: f3670dba8951ca10eb4948feb3dc3db9423a334f $
Loading...
Searching...
No Matches
rlm_ocsp.c
Go to the documentation of this file.
1/*
2 * This program is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or (at
5 * your option) any later version.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15 */
16
17/**
18 * $Id: dba85ac6d27cc1f54e3983cb048fa743461907ee $
19 * @file rlm_ocsp.c
20 * @brief Check a certificate against an OCSP responder.
21 *
22 * @copyright 2026 Network RADIUS SAS (legal@networkradius.com)
23 */
24RCSID("$Id: dba85ac6d27cc1f54e3983cb048fa743461907ee $")
25
26#include <freeradius-devel/server/base.h>
27#include <freeradius-devel/curl/base.h>
28#include <freeradius-devel/server/module_rlm.h>
29
30#include <freeradius-devel/tls/bio.h>
31#include <freeradius-devel/tls/log.h>
32#include <freeradius-devel/tls/strerror.h>
33#include <freeradius-devel/tls/utils.h>
34#include <freeradius-devel/util/slab.h>
35
36#ifdef WITH_TLS
37#include <openssl/ocsp.h>
38#endif
39
40typedef struct {
41 bool override_url; //!< Always use the configured OCSP URL even if the
42 //!< certificate contains one.
43 char const *url; //!< Override / fallback OCSP URL.
44 bool use_nonce; //!< Include a nonce in OCSP requests/
45 bool softfail; //!< Should the module soft fail if the responder is not available.
46
47 int leeway; //!< Seconds of leeway allowed in checking response `thisUpdate`
48 int max_age; //!< Maximum age of `thisUpdate` allowed in response checks.
49
50 bool verifycert; //!< Should the certificate in responses be verified.
51 char const *ca_file; //!< File containing certs for verifying OCSP responses.
52 char const *ca_path; //!< Directory containing certs for verifying OCSP responses.
53
54 uint64_t max_body_in; //!< Largest response we accept.
55 fr_curl_conn_config_t conn_config; //!< Reusable CURL handle config
57
60
61#define REST_BODY_ALLOC_CHUNK 1024
62
69
75
76/*
77 * Curl inbound data context (passed to CURLOPT_WRITEFUNCTION and
78 * CURLOPT_HEADERFUNCTION as CURLOPT_WRITEDATA and CURLOPT_HEADERDATA)
79 */
80typedef struct {
81 rlm_ocsp_t const *inst; //!< Module instance.
82 request_t *request; //!< Current request.
83 write_state_t state; //!< Decoder state.
84
85 char *buffer; //!< Raw incoming HTTP data.
86 size_t alloc; //!< Space allocated for buffer.
87 size_t used; //!< Space used in buffer.
88
89 int code; //!< HTTP Status Code.
91
92
93typedef struct {
94 struct curl_slist *headers; //!< Any HTTP headers which will be sent with the
95 //!< request.
96
97 char *body; //!< Pointer to the buffer which contains body data
98
99 rlm_ocsp_response_t response; //!< Response context data.
101
103 { FR_CONF_OFFSET("override_cert_url", rlm_ocsp_t, override_url), .dflt = "no" },
104 { FR_CONF_OFFSET("url", rlm_ocsp_t, url) },
105 { FR_CONF_OFFSET("use_nonce", rlm_ocsp_t, use_nonce), .dflt = "yes" },
106 { FR_CONF_OFFSET("softfail", rlm_ocsp_t, softfail), .dflt = "no" },
107 { FR_CONF_OFFSET("leeway", rlm_ocsp_t, leeway), .dflt = "300" },
108 { FR_CONF_OFFSET("max_age", rlm_ocsp_t, max_age), .dflt = "-1" },
109 { FR_CONF_OFFSET("verifycert", rlm_ocsp_t, verifycert), .dflt = "yes" },
112 { FR_CONF_OFFSET("max_body_in", rlm_ocsp_t, max_body_in) },
113 { FR_CONF_OFFSET_SUBSECTION("connection", 0, rlm_ocsp_t, conn_config, fr_curl_conn_config) },
114
116};
117
118typedef struct {
119 fr_value_box_t certuri; //!< Certificate provided OCSP endpoint
120 fr_value_box_t certid; //!< The certificate ID being checked.
122
124
127 { .out = &dict_freeradius, .proto = "freeradius" },
129};
130
133
136 { .out = &attr_tls_ocsp_cert_valid, .name = "TLS-OCSP-Cert-Valid", .type = FR_TYPE_UINT32, .dict = &dict_freeradius },
137 { .out = &attr_tls_ocsp_next_update, .name = "TLS-OCSP-Next-Update", .type = FR_TYPE_UINT32, .dict = &dict_freeradius },
139};
140
141#ifdef WITH_TLS
142typedef struct {
143 ocsp_slab_list_t *slab;
144 fr_curl_handle_t *mhandle;
145 X509_STORE *store;
146} rlm_ocsp_thread_t;
147
148typedef struct {
149 OCSP_CERTID *cert_id;
150 OCSP_REQUEST *req;
151 uint8_t *reqasn1;
152 int req_size;
153 fr_curl_io_request_t *handle;
154} rlm_ocsp_rctx_t;
155
156static const call_env_method_t ocsp_env = {
158 .env = (call_env_parser_t[]){
160 .pair.dflt = "session-state.TLS-Certificate.OCSP-Uri", .pair.dflt_quote = T_BARE_WORD },
162 .pair.dflt = "session-state.TLS-Certificate.OCSP-Cert-Id", .pair.dflt_quote = T_BARE_WORD },
164 },
165};
166
167/** Processes incoming HTTP header data from libcurl.
168 *
169 * Processes the status line, and Content-Type headers from the incoming HTTP
170 * response.
171 *
172 * Matches prototype for CURLOPT_HEADERFUNCTION, and will be called directly
173 * by libcurl.
174 *
175 * A simplified version of the equivalent function in modules/rlm_rest/rest.c
176 *
177 * @param[in] in Char buffer where inbound header data is written.
178 * @param[in] size Multiply by nmemb to get the length of ptr.
179 * @param[in] nmemb Multiply by size to get the length of ptr.
180 * @param[in] userdata rlm_ocsp_response_t to keep parsing state between calls.
181 * @return
182 * - Length of data processed.
183 * - 0 on error.
184 */
185static size_t ocsp_response_header(void *in, size_t size, size_t nmemb, void *userdata)
186{
187 rlm_ocsp_response_t *ctx = userdata;
188 request_t *request = ctx->request; /* Used by RDEBUG */
189
190 char const *start = (char *)in, *p = start, *end = p + (size * nmemb);
191 char const *q;
192 size_t len;
193
194 if (((end - p) == 2) && ((p[0] == '\r') && (p[1] == '\n'))) {
195 if (ctx->code == 100) {
196 RDEBUG2("Continuing...");
197 ctx->state = WRITE_STATE_INIT;
198 }
199
200 return (end - start);
201 }
202
203 switch (ctx->state) {
204 case WRITE_STATE_INIT:
205 RDEBUG2("Processing response header");
206
207 /*
208 * HTTP/<version> <reason_code>[ <reason_phrase>]\r\n
209 *
210 * "HTTP/1.1 " (9) + "100" (3) + "\r\n" (2) = 14
211 * "HTTP/2 " (7) + "100" (3) + "\r\n" (2) = 12
212 */
213 if ((end - p) < 12) {
214 REDEBUG("Malformed HTTP header: Status line too short");
215 malformed:
216 REDEBUG("Received %zu bytes of invalid header data: %pV",
217 (end - start), fr_box_strvalue_len(in, (end - start)));
218 ctx->code = 0;
219
220 return (end - start);
221 }
222
223 if (strncasecmp("HTTP/", p, 5) != 0) {
224 REDEBUG("Malformed HTTP header: Missing HTTP version");
225 goto malformed;
226 }
227 p += 5;
228
229 /*
230 * Skip the version field, next space should mark start of reason_code.
231 */
232 q = memchr(p, ' ', (end - p));
233 if (!q) {
234 REDEBUG("Malformed HTTP header: Missing reason code");
235 goto malformed;
236 }
237
238 p = q;
239
240 /*
241 * Process reason_code.
242 *
243 * " 100" (4) + "\r\n" (2) = 6
244 */
245 if ((end - p) < 6) {
246 REDEBUG("Malformed HTTP header: Reason code too short");
247 goto malformed;
248 }
249 p++;
250
251 /*
252 * "xxx( |\r)" status code and terminator.
253 */
254 if (!isdigit(p[0]) || !isdigit(p[1]) || !isdigit(p[2]) || !((p[3] == ' ') || (p[3] == '\r'))) {
255 REDEBUG("Malformed HTTP header: Reason code malformed. "
256 "Expected three digits then space or end of header, got \"%pV\"",
257 fr_box_strvalue_len(p, 4));
258 goto malformed;
259 }
260
261 /*
262 * Convert status code into an integer value. strtoul needs
263 * a writable endptr, so shadow q in a local scope.
264 */
265 {
266 char *qq = NULL;
267
268 ctx->code = (int)strtoul(p, &qq, 10);
269 fr_assert(qq == (p + 3)); /* We check this above */
270 p = qq;
271 }
272
273 /*
274 * Process reason_phrase (if present).
275 */
276 RINDENT();
277 if (*p == ' ') {
278 p++;
279 q = memchr(p, '\r', (end - p));
280 if (!q) goto malformed;
281 RDEBUG2("Status : %i (%pV)", ctx->code, fr_box_strvalue_len(p, q - p));
282 } else {
283 RDEBUG2("Status : %i", ctx->code);
284 }
285 REXDENT();
286
288
289 break;
290
292 if (((end - p) >= 14) &&
293 (strncasecmp("Content-Type: ", p, 14) == 0)) {
294 p += 14;
295
296 /*
297 * Check to see if there's a parameter separator.
298 */
299 q = memchr(p, ';', (end - p));
300
301 /*
302 * If there's not, find the end of this header.
303 */
304 if (!q) q = memchr(p, '\r', (end - p));
305
306 len = (size_t)(!q ? (end - p) : (q - p));
307 if (strncmp(p, "application/ocsp-response", len) != 0) {
308 REDEBUG("Expected Content-Type application/ocsp-response, got %pV", fr_box_strvalue_len(p, len));
309 }
310 }
311 break;
312
313 default:
314 break;
315 }
316
317 return (end - start);
318}
319
320/** Processes incoming HTTP body data from libcurl.
321 *
322 * Writes incoming body data to an intermediary buffer for later parsing
323 *
324 * @param[in] in Char buffer where inbound header data is written
325 * @param[in] size Multiply by nmemb to get the length of ptr.
326 * @param[in] nmemb Multiply by size to get the length of ptr.
327 * @param[in] userdata rlm_ocsp_response_t to keep parsing state between calls.
328 * @return
329 * - Length of data processed.
330 * - 0 on error.
331 */
332static size_t ocsp_response_body(void *in, size_t size, size_t nmemb, void *userdata)
333{
334 rlm_ocsp_response_t *ctx = userdata;
335 request_t *request = ctx->request; /* Used by RDEBUG */
336
337 char const *start = in, *p = start, *end = p + (size * nmemb);
338 char *out_p;
339 size_t needed;
340
341 if (start == end) return 0; /* Nothing to process */
342
343 /*
344 * Any post processing of headers should go here...
345 */
347
348 if ((ctx->inst->max_body_in > 0) && ((ctx->used + (end - p)) > ctx->inst->max_body_in)) {
349 REDEBUG("Incoming data (%zu bytes) exceeds max_body_in (%"PRIu64" bytes).",
350 ctx->used + (end - p), ctx->inst->max_body_in);
351 TALLOC_FREE(ctx->buffer);
352 goto finish;
353 }
354
355 needed = ROUND_UP(ctx->used + (end - p), REST_BODY_ALLOC_CHUNK);
356 if (needed > ctx->alloc) {
357 MEM(ctx->buffer = talloc_bstr_realloc(NULL, ctx->buffer, needed));
358 ctx->alloc = needed;
359 }
360
361 out_p = ctx->buffer + ctx->used;
362 memcpy(out_p, p, (end - p));
363 out_p += (end - p);
364 *out_p = '\0';
365 ctx->used += (end - p);
366
367finish:
368 return (end - start);
369}
370
371static int ocsp_request_config(module_ctx_t const *mctx, request_t *request, fr_curl_io_request_t *randle,
372 uint8_t *body, size_t body_len, char const *uri)
373{
374 rlm_ocsp_t const *inst = talloc_get_type_abort(mctx->mi->data, rlm_ocsp_t);
375 rlm_ocsp_curl_context_t *uctx = talloc_get_type_abort(randle->uctx, rlm_ocsp_curl_context_t);
376 fr_time_delta_t timeout = inst->conn_config.connect_timeout;
377 struct curl_slist *headers;
378
379 FR_CURL_REQUEST_SET_OPTION(CURLOPT_URL, uri);
380#if CURL_AT_LEAST_VERSION(7,85,0)
381 FR_CURL_REQUEST_SET_OPTION(CURLOPT_PROTOCOLS_STR, "http,https");
382#else
383 FR_CURL_REQUEST_SET_OPTION(CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
384#endif
385
386 FR_CURL_REQUEST_SET_OPTION(CURLOPT_NOSIGNAL, 1L);
387
388 RDEBUG3("Connect timeout is %pVs", fr_box_time_delta(timeout));
389 FR_CURL_REQUEST_SET_OPTION(CURLOPT_CONNECTTIMEOUT_MS, fr_time_delta_to_msec(timeout));
390
391 uctx->body = (char *)body;
392 FR_CURL_REQUEST_SET_OPTION(CURLOPT_POST, 1L);
393 FR_CURL_REQUEST_SET_OPTION(CURLOPT_POSTFIELDS, uctx->body);
394 FR_CURL_REQUEST_SET_OPTION(CURLOPT_POSTFIELDSIZE, body_len);
395
396 FR_CURL_REQUEST_SET_OPTION(CURLOPT_HEADERFUNCTION, ocsp_response_header);
397 FR_CURL_REQUEST_SET_OPTION(CURLOPT_HEADERDATA, &uctx->response);
398 FR_CURL_REQUEST_SET_OPTION(CURLOPT_WRITEFUNCTION, ocsp_response_body);
399 FR_CURL_REQUEST_SET_OPTION(CURLOPT_WRITEDATA, &uctx->response);
400
401 headers = curl_slist_append(uctx->headers, "Content-Type: application/ocsp-request");
402 if (unlikely(!headers)) {
403 REDEBUG("Failed to add Content-Type header");
404 goto error;
405 }
406 uctx->headers = headers;
407
408 FR_CURL_REQUEST_SET_OPTION(CURLOPT_HTTPHEADER, uctx->headers);
409
410 TALLOC_FREE(uctx->response.buffer);
411 uctx->response = (rlm_ocsp_response_t) {
412 .inst = inst,
413 .request = request,
414 .state = WRITE_STATE_INIT
415 };
416
417 return 0;
418
419error:
420 return -1;
421}
422
423static int rlm_ocsp_rctx_free(rlm_ocsp_rctx_t *to_free)
424{
425 OCSP_REQUEST_free(to_free->req);
426 OPENSSL_free(to_free->reqasn1);
427 if (to_free->handle) ocsp_slab_release(to_free->handle);
428 return 0;
429}
430
431static unlang_action_t mod_ocsp_resume(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
432{
434 rlm_ocsp_thread_t *thread = talloc_get_type_abort(mctx->thread, rlm_ocsp_thread_t);
435 rlm_ocsp_rctx_t *rctx = talloc_get_type_abort(mctx->rctx, rlm_ocsp_rctx_t);
436 fr_curl_io_request_t *handle = rctx->handle;
437 rlm_ocsp_curl_context_t *uctx = talloc_get_type_abort(handle->uctx, rlm_ocsp_curl_context_t);
438 BIO *ssl_log = NULL;
439 OCSP_RESPONSE *resp = NULL;
440 OCSP_BASICRESP *bresp = NULL;
441 uint8_t const *buffer = (uint8_t *)uctx->response.buffer;
442 int status, reason;
443 ASN1_GENERALIZEDTIME *rev, *this_update, *next_update;
444 ocsp_status_t ocsp_status = OCSP_STATUS_FAILED;
445 fr_pair_t *vp;
447
448 if (uctx->response.code != 200) {
449 RERROR("Invalid HTTP response code");
450 ocsp_status = OCSP_STATUS_SKIPPED;
451 goto finish;
452 }
453
454 resp = d2i_OCSP_RESPONSE(NULL, &buffer, uctx->response.used);
455 if (!resp) {
456 RPERROR("Failed parsing response body as an OCSP response");
457 ocsp_status = OCSP_STATUS_SKIPPED;
458 goto finish;
459 }
460
461 /* Verify OCSP response status */
462 status = OCSP_response_status(resp);
463 if (status != OCSP_RESPONSE_STATUS_SUCCESSFUL) {
464 REDEBUG("Response status: %s", OCSP_response_status_str(status));
465 goto finish;
466 }
467
468 bresp = OCSP_response_get1_basic(resp);
469 if (inst->use_nonce && OCSP_check_nonce(rctx->req, bresp) != 1) {
470 REDEBUG("Response has wrong nonce value");
471 goto finish;
472 }
473
474 if (inst->verifycert) {
475 if (OCSP_basic_verify(bresp, NULL, thread->store, 0) != 1){
476 REDEBUG("Couldn't verify OCSP basic response");
477 goto finish;
478 }
479 }
480
481 /* Verify OCSP cert status */
482 if (!OCSP_resp_find_status(bresp, rctx->cert_id, (int *)&status, &reason, &rev, &this_update, &next_update)) {
483 REDEBUG("No Status found");
484 goto finish;
485 }
486
487 /*
488 * Here we check the fields 'thisUpdate' and 'nextUpdate'
489 * from the OCSP response against the server's time.
490 *
491 * leewaysec is the number of seconds +- between the current
492 * time and this_update.
493 */
494 if (!OCSP_check_validity(this_update, next_update, inst->leeway, inst->max_age)) {
495 /*
496 * We want this to show up in the global log
497 * so someone will fix it...
498 */
499 RATE_LIMIT_GLOBAL(RERROR, "Delta +/- between OCSP response time and our time is greater than %i "
500 "seconds. Check servers are synchronised to a common time source",
501 inst->leeway);
502 goto finish;
503 }
504
505 ssl_log = BIO_new(BIO_s_mem());
506 if (RDEBUG_ENABLED) {
507 RDEBUG2("OCSP response valid from:");
508 ASN1_GENERALIZEDTIME_print(ssl_log, this_update);
509 RINDENT();
510 FR_OPENSSL_DRAIN_LOG_QUEUE(RDEBUG2, "", ssl_log);
511 REXDENT();
512
513 if (next_update) {
514 RDEBUG2("New information available at:");
515 ASN1_GENERALIZEDTIME_print(ssl_log, next_update);
516 RINDENT();
517 FR_OPENSSL_DRAIN_LOG_QUEUE(RDEBUG2, "", ssl_log);
518 REXDENT();
519 }
520 }
521
522 /*
523 * When an OCSP validation command is used with OpenSSL
524 * next_update is NULL.
525 */
526 if (next_update) {
527 fr_time_t now;
528 time_t next;
529
530 now = fr_time();
531
532 if (fr_tls_utils_asn1time_to_epoch(&next, next_update) < 0) {
533 RPEDEBUG("Failed parsing next_update time");
534 ocsp_status = OCSP_STATUS_SKIPPED;
535 goto finish;
536 }
537 if (fr_time_to_sec(now) < next){
538 RDEBUG2("Adding OCSP TTL attribute");
539
541 vp->vp_uint32 = next - fr_time_to_sec(now);
542 RINDENT();
543 RDEBUG2("%pP", vp);
544 REXDENT();
545 } else {
546 RDEBUG2("Update time is in the past. Not adding TLS-OCSP-Next-Update");
547 }
548 } else {
549 RDEBUG2("Update time not provided. Not adding TLS-OCSP-Next-Update");
550 }
551
552 switch (status) {
553 case V_OCSP_CERTSTATUS_GOOD:
554 RDEBUG2("Cert status: good");
555 ocsp_status = OCSP_STATUS_OK;
556 break;
557
558 default:
559 /* REVOKED / UNKNOWN */
560 REDEBUG("Cert status: %s", OCSP_cert_status_str(status));
561 if (reason != -1) REDEBUG("Reason: %s", OCSP_crl_reason_str(reason));
562
563 /*
564 * Print any messages we may have accumulated
565 */
566 FR_OPENSSL_DRAIN_LOG_QUEUE(RDEBUG, "", ssl_log);
567 if (RDEBUG_ENABLED2) {
568 RDEBUG2("Revocation time:");
569 ASN1_GENERALIZEDTIME_print(ssl_log, rev);
570 RINDENT();
571 FR_OPENSSL_DRAIN_LOG_QUEUE(RDEBUG2, "", ssl_log);
572 REXDENT();
573 }
574 break;
575 }
576
577finish:
578 switch (ocsp_status) {
579 case OCSP_STATUS_OK:
580 RDEBUG2("Certificate is valid");
581
583 vp->vp_uint32 = FR_TLS_OCSP_CERT_VALID_VALUE_YES;
584 rcode = RLM_MODULE_OK;
585
586 break;
587
590 vp->vp_uint32 = FR_TLS_OCSP_CERT_VALID_VALUE_SKIPPED;
591 if (inst->softfail) {
592 RWDEBUG("Unable to check certificate: "
593 "TLS clients presenting revoked certificates may be granted access");
594 rcode = RLM_MODULE_NOOP;
595
596 /* Remove OpenSSL errors from queue or handshake will fail */
597 while (ERR_get_error()); /* Not always debugging */
598 } else {
599 REDEBUG("Unable to check certificate, failing");
600 }
601 break;
602
603 default:
605 vp->vp_uint32 = FR_TLS_OCSP_CERT_VALID_VALUE_NO;
606 REDEBUG("Failed to validate certificate");
607 break;
608 }
609
610 OCSP_BASICRESP_free(bresp);
611 OCSP_RESPONSE_free(resp);
612 BIO_free(ssl_log);
613
614 RETURN_UNLANG_RCODE(rcode);
615}
616
617static unlang_action_t CC_HINT(nonnull) mod_ocsp(unlang_result_t *p_result, module_ctx_t const *mctx,
618 request_t *request)
619{
620 rlm_ocsp_t const *inst = talloc_get_type_abort(mctx->mi->data, rlm_ocsp_t);
621 rlm_ocsp_env_t *env = talloc_get_type_abort(mctx->env_data, rlm_ocsp_env_t);
622 rlm_ocsp_thread_t *thread = talloc_get_type_abort(mctx->thread, rlm_ocsp_thread_t);
623 char const *uri;
624 fr_pair_t *vp;
626 rlm_ocsp_rctx_t *rctx;
627 uint8_t const *certid;
628
629 /*
630 * Check for control.TLS-OCSP-Cert-Valid retrieved from cache.
631 */
632 vp = fr_pair_find_by_da(&request->control_pairs, NULL, attr_tls_ocsp_cert_valid);
633 if (vp) switch (vp->vp_uint32) {
634 case FR_TLS_OCSP_CERT_VALID_VALUE_NO:
635 RDEBUG2("Found control.%s = no, forcing OCSP failure", attr_tls_ocsp_cert_valid->name);
637
638 case FR_TLS_OCSP_CERT_VALID_VALUE_YES:
639 RDEBUG2("Found control.%s = yes, forcing OCSP success", attr_tls_ocsp_cert_valid->name);
641
642 case FR_TLS_OCSP_CERT_VALID_VALUE_SKIPPED:
643 RDEBUG2("Found control.%s = skipped, skipping OCSP check", attr_tls_ocsp_cert_valid->name);
644 if (inst->softfail) RETURN_UNLANG_NOOP;
646
647 case FR_TLS_OCSP_CERT_VALID_VALUE_UNKNOWN:
648 default:
649 break;
650 }
651
652 MEM(rctx = talloc_zero(unlang_interpret_frame_talloc_ctx(request), rlm_ocsp_rctx_t));
653 talloc_set_destructor(rctx, rlm_ocsp_rctx_free);
654
655 certid = env->certid.vb_octets;
656 rctx->cert_id = d2i_OCSP_CERTID(NULL, &certid, env->certid.vb_length);
657
658 rctx->req = OCSP_REQUEST_new();
659 OCSP_request_add0_id(rctx->req, rctx->cert_id);
660 if (inst->use_nonce) OCSP_request_add1_nonce(rctx->req, NULL, 8);
661
662 /* Get OCSP responder URL */
663 if (inst->override_url) {
664 use_url:
665 uri = inst->url;
666 } else {
667 if (env->certuri.type == FR_TYPE_NULL) {
668 if (inst->url) {
669 RWDEBUG("No OCSP URL in certificate, falling back to configured URL");
670 goto use_url;
671 }
672 RWDEBUG("No OCSP URL in certificate. Not doing OCSP");
673 goto finish;
674 }
675 uri = env->certuri.vb_strvalue;
676 }
677
678 RDEBUG2("Using responder URL \"%s\"", uri);
679
680 rctx->req_size = ASN1_item_i2d((ASN1_VALUE *)rctx->req, &rctx->reqasn1, ASN1_ITEM_rptr(OCSP_REQUEST));
681
682 rctx->handle = ocsp_slab_reserve(thread->slab);
683
684 if (ocsp_request_config(mctx, request, rctx->handle, rctx->reqasn1, rctx->req_size, uri) < 0) {
685 rcode = RLM_MODULE_FAIL;
686 goto finish;
687 }
688
689 if (fr_curl_io_request_enqueue(thread->mhandle, request, rctx->handle) < 0) {
690 rcode = RLM_MODULE_FAIL;
691 goto finish;
692 }
693
694 rcode = RLM_MODULE_NOT_SET;
695
696finish:
697
698 if (rcode == RLM_MODULE_NOT_SET) return unlang_module_yield(request, mod_ocsp_resume, NULL, 0, rctx);
699
700 RETURN_UNLANG_RCODE(rcode);
701}
702
703static int _ocsp_request_cleanup(fr_curl_io_request_t *randle, UNUSED void *uctx)
704{
705 rlm_ocsp_curl_context_t *ctx = talloc_get_type_abort(randle->uctx, rlm_ocsp_curl_context_t);
706
707 if (randle->candle) curl_easy_reset(randle->candle);
708
709 if (ctx->headers != NULL) {
710 curl_slist_free_all(ctx->headers);
711 ctx->headers = NULL;
712 }
713
714 TALLOC_FREE(ctx->response.buffer);
715
716 randle->request = NULL;
717 return 0;
718}
719
720static int _mod_conn_free(fr_curl_io_request_t *randle)
721{
722 curl_easy_cleanup(randle->candle);
723 return 0;
724}
725
726static int ocsp_conn_alloc(fr_curl_io_request_t *randle, UNUSED void *uctx)
727{
728 rlm_ocsp_curl_context_t *curl_ctx;
729
730 randle->candle = curl_easy_init();
731 if (unlikely(!randle->candle)) {
732 fr_strerror_printf("Unable to initialise CURL handle");
733 return -1;
734 }
735
736 MEM(curl_ctx = talloc_zero(randle, rlm_ocsp_curl_context_t));
737
738 randle->uctx = curl_ctx;
739 talloc_set_destructor(randle, _mod_conn_free);
740
741 ocsp_slab_element_set_destructor(randle, _ocsp_request_cleanup, NULL);
742
743 return 0;
744}
745
746/** Create a thread specific multihandle
747 *
748 * Easy handles representing requests are added to the curl multihandle
749 * with the multihandle used for mux/demux.
750 *
751 * @param[in] mctx Thread instantiation data.
752 * @return
753 * - 0 on success.
754 * - -1 on failure.
755 */
757{
758 rlm_ocsp_t const *inst = talloc_get_type_abort(mctx->mi->data, rlm_ocsp_t);
759 rlm_ocsp_thread_t *t = talloc_get_type_abort(mctx->thread, rlm_ocsp_thread_t);
760 fr_curl_handle_t *mhandle;
761
762 if (!(t->slab = ocsp_slab_list_alloc(t, mctx->el, &inst->conn_config.reuse,
763 ocsp_conn_alloc, NULL, NULL, false, true))) {
764 ERROR("Connection handle pool instantiation failed");
765 return -1;
766 }
767
768 mhandle = fr_curl_io_init(t, mctx->el, false);
769 if (!mhandle) return -1;
770
771 t->mhandle = mhandle;
772
773 if (!inst->verifycert) return 0;
774
775 t->store = X509_STORE_new();
776 if (!t->store) return -1;
777 if (!X509_STORE_load_locations(t->store, inst->ca_file, inst->ca_path)) {
778 cf_log_err(mctx->mi->conf, "Failed reading Trusted root CA file \"%s\" and path \"%s\"",
779 inst->ca_file, inst->ca_path);
780 return -1;
781 }
782
783 return 0;
784}
785
786/*
787 * Close the thread and free the memory
788 */
789static int mod_thread_detach(module_thread_inst_ctx_t const *mctx)
790{
791 rlm_ocsp_thread_t *t = talloc_get_type_abort(mctx->thread, rlm_ocsp_thread_t);
792
793 talloc_free(t->mhandle);
794 talloc_free(t->slab);
795 if (t->store) X509_STORE_free(t->store);
796 return 0;
797}
798#endif
799
800/** Instantiate the module
801 *
802 */
803static int mod_instantiate(module_inst_ctx_t const *mctx)
804{
805#ifdef WITH_TLS
806 rlm_ocsp_t *inst = talloc_get_type_abort(mctx->mi->data, rlm_ocsp_t);
807
808 if (!inst->verifycert) return 0;
809
810 if (!inst->ca_file && !inst->ca_path) {
811 cf_log_err(mctx->mi->conf, "ca_file or ca_path required when verifycert = yes");
812 return -1;
813 }
814
815 return 0;
816#else
817 cf_log_err(mctx->mi->conf, "rlm_ocsp requires OpenSSL");
818 return -1;
819#endif
820}
821
824 .common = {
825 .magic = MODULE_MAGIC_INIT,
826 .inst_size = sizeof(rlm_ocsp_t),
827 .name = "ocsp",
829 .instantiate = mod_instantiate,
830#ifdef WITH_TLS
831 MODULE_THREAD_INST(rlm_ocsp_thread_t),
832 .thread_instantiate = mod_thread_instantiate,
833 .thread_detach = mod_thread_detach,
834#endif
835 },
836#ifdef WITH_TLS
837 .method_group = {
838 .bindings = (module_method_binding_t[]){
839 { .section = SECTION_NAME(CF_IDENT_ANY, CF_IDENT_ANY), .method = mod_ocsp, .method_env = &ocsp_env },
841 }
842 }
843#endif
844};
unlang_action_t
Returned by unlang_op_t calls, determine the next action of the interpreter.
Definition action.h:35
static int const char char buffer[256]
Definition acutest.h:576
#define store(_store, _var)
#define RCSID(id)
Definition build.h:560
#define unlikely(_x)
Definition build.h:455
#define UNUSED
Definition build.h:384
#define CALL_ENV_TERMINATOR
Definition call_env.h:236
#define FR_CALL_ENV_METHOD_OUT(_inst)
Helper macro for populating the size/type fields of a call_env_method_t from the output structure typ...
Definition call_env.h:240
call_env_parser_t const * env
Parsing rules for call method env.
Definition call_env.h:247
@ CALL_ENV_FLAG_SINGLE
If the tmpl produces more than one box this is an error.
Definition call_env.h:77
@ CALL_ENV_FLAG_ATTRIBUTE
Tmpl MUST contain an attribute reference.
Definition call_env.h:86
@ CALL_ENV_FLAG_REQUIRED
Associated conf pair or section is required.
Definition call_env.h:75
@ CALL_ENV_FLAG_NULLABLE
Tmpl expansions are allowed to produce no output.
Definition call_env.h:80
#define FR_CALL_ENV_OFFSET(_name, _cast_type, _flags, _struct, _field)
Specify a call_env_parser_t which writes out runtime results to the specified field.
Definition call_env.h:340
Per method call config.
Definition call_env.h:180
#define CONF_PARSER_TERMINATOR
Definition cf_parse.h:673
#define FR_CONF_OFFSET(_name, _struct, _field)
conf_parser_t which parses a single CONF_PAIR, writing the result to a field in a struct
Definition cf_parse.h:280
#define FR_CONF_OFFSET_FLAGS(_name, _flags, _struct, _field)
conf_parser_t which parses a single CONF_PAIR, writing the result to a field in a struct
Definition cf_parse.h:268
#define FR_CONF_OFFSET_SUBSECTION(_name, _flags, _struct, _field, _subcs)
conf_parser_t which populates a sub-struct using a CONF_SECTION
Definition cf_parse.h:309
@ CONF_FLAG_FILE_READABLE
File matching value must exist, and must be readable.
Definition cf_parse.h:439
Defines a CONF_PAIR to C data type mapping.
Definition cf_parse.h:610
#define cf_log_err(_cf, _fmt,...)
Definition cf_util.h:343
#define CF_IDENT_ANY
Definition cf_util.h:80
fr_curl_handle_t * fr_curl_io_init(TALLOC_CTX *ctx, fr_event_list_t *el, bool multiplex)
#define FR_CURL_REQUEST_SET_OPTION(_x, _y)
Definition base.h:67
request_t * request
Current request.
Definition base.h:104
void * uctx
Private data for the module using the API.
Definition base.h:105
int fr_curl_io_request_enqueue(fr_curl_handle_t *mhandle, request_t *request, fr_curl_io_request_t *creq)
Sends a request using libcurl.
Definition io.c:478
CURL * candle
Request specific handle.
Definition base.h:102
Uctx data for timer and I/O functions.
Definition base.h:91
Structure representing an individual request being passed to curl for processing.
Definition base.h:101
#define MEM(x)
Definition debug.h:38
#define ERROR(fmt,...)
Definition dhcpclient.c:40
fr_dict_attr_t const ** out
Where to write a pointer to the resolved fr_dict_attr_t.
Definition dict.h:316
fr_dict_t const ** out
Where to write a pointer to the loaded/resolved fr_dict_t.
Definition dict.h:329
#define DICT_AUTOLOAD_TERMINATOR
Definition dict.h:335
static fr_slen_t in
Definition dict.h:904
Specifies an attribute which must be present for the module to function.
Definition dict.h:315
Specifies a dictionary which must be loaded/loadable for the module to function.
Definition dict.h:328
#define MODULE_MAGIC_INIT
Stop people using different module/library/server versions together.
Definition dl_module.h:63
talloc_free(hp)
TALLOC_CTX * unlang_interpret_frame_talloc_ctx(request_t *request)
Get a talloc_ctx which is valid only for this frame.
Definition interpret.c:2053
conf_parser_t fr_curl_conn_config[]
Definition base.c:97
#define REXDENT()
Exdent (unindent) R* messages by one level.
Definition log.h:460
#define RWDEBUG(fmt,...)
Definition log.h:378
#define RDEBUG3(fmt,...)
Definition log.h:360
#define RERROR(fmt,...)
Definition log.h:315
#define RPERROR(fmt,...)
Definition log.h:319
#define RPEDEBUG(fmt,...)
Definition log.h:393
#define RATE_LIMIT_GLOBAL(_log, _fmt,...)
Rate limit messages using a global limiting entry.
Definition log.h:658
#define RINDENT()
Indent R* messages by one level.
Definition log.h:447
#define fr_time()
Definition event.c:60
#define ROUND_UP(_num, _mul)
Round up - Works in all cases, but is slower.
Definition math.h:206
@ FR_TYPE_STRING
String of printable characters.
@ FR_TYPE_NULL
Invalid (uninitialised) attribute type.
@ FR_TYPE_UINT32
32 Bit unsigned integer.
@ FR_TYPE_OCTETS
Raw octets.
unsigned char uint8_t
unsigned long int size_t
int strncasecmp(char *s1, char *s2, int n)
Definition missing.c:35
void * env_data
Per call environment data.
Definition module_ctx.h:44
module_instance_t const * mi
Instance of the module being instantiated.
Definition module_ctx.h:42
void * thread
Thread specific instance data.
Definition module_ctx.h:43
void * rctx
Resume ctx that a module previously set.
Definition module_ctx.h:45
fr_event_list_t * el
Event list to register any IO handlers and timers against.
Definition module_ctx.h:68
void * thread
Thread instance data.
Definition module_ctx.h:67
module_instance_t const * mi
Instance of the module being instantiated.
Definition module_ctx.h:64
module_instance_t * mi
Instance of the module being instantiated.
Definition module_ctx.h:51
Temporary structure to hold arguments for module calls.
Definition module_ctx.h:41
Temporary structure to hold arguments for instantiation calls.
Definition module_ctx.h:50
Temporary structure to hold arguments for thread_instantiation calls.
Definition module_ctx.h:63
module_t common
Common fields presented by all modules.
Definition module_rlm.h:39
fr_pair_t * fr_pair_find_by_da(fr_pair_list_t const *list, fr_pair_t const *prev, fr_dict_attr_t const *da)
Find the first pair with a matching da.
Definition pair.c:708
static const conf_parser_t config[]
Definition base.c:162
static char const * url[FR_RADIUS_FAIL_MAX+1]
#define fr_assert(_expr)
Definition rad_assert.h:37
#define pair_update_request(_attr, _da)
#define REDEBUG(fmt,...)
#define RDEBUG_ENABLED2()
#define RDEBUG2(fmt,...)
#define RDEBUG(fmt,...)
#define RDEBUG_ENABLED()
#define RETURN_UNLANG_RCODE(_rcode)
Definition rcode.h:61
#define RETURN_UNLANG_FAIL
Definition rcode.h:63
#define RETURN_UNLANG_OK
Definition rcode.h:64
rlm_rcode_t
Return codes indicating the result of the module call.
Definition rcode.h:44
@ RLM_MODULE_OK
The module is OK, continue.
Definition rcode.h:49
@ RLM_MODULE_FAIL
Module failed, don't reply.
Definition rcode.h:48
@ RLM_MODULE_NOT_SET
Error resolving rcode (should not be returned by modules).
Definition rcode.h:45
@ RLM_MODULE_NOOP
Module succeeded without doing anything.
Definition rcode.h:54
#define RETURN_UNLANG_NOOP
Definition rcode.h:69
static int _mod_conn_free(rlm_cache_memcached_handle_t *mandle)
Free a connection handle.
static int mod_thread_instantiate(module_thread_inst_ctx_t const *mctx)
static int mod_thread_detach(module_thread_inst_ctx_t const *mctx)
fr_dict_attr_autoload_t rlm_ocsp_dict_attr[]
Definition rlm_ocsp.c:135
bool softfail
Should the module soft fail if the responder is not available.
Definition rlm_ocsp.c:45
char * buffer
Raw incoming HTTP data.
Definition rlm_ocsp.c:85
struct curl_slist * headers
Any HTTP headers which will be sent with the request.
Definition rlm_ocsp.c:94
bool override_url
Always use the configured OCSP URL even if the certificate contains one.
Definition rlm_ocsp.c:41
write_state_t state
Decoder state.
Definition rlm_ocsp.c:83
rlm_ocsp_response_t response
Response context data.
Definition rlm_ocsp.c:99
bool use_nonce
Include a nonce in OCSP requests/.
Definition rlm_ocsp.c:44
static fr_dict_attr_t const * attr_tls_ocsp_next_update
Definition rlm_ocsp.c:132
static fr_dict_t const * dict_freeradius
Definition rlm_ocsp.c:123
fr_dict_autoload_t rlm_ocsp_dict[]
Definition rlm_ocsp.c:126
static fr_dict_attr_t const * attr_tls_ocsp_cert_valid
Definition rlm_ocsp.c:131
#define REST_BODY_ALLOC_CHUNK
Definition rlm_ocsp.c:61
size_t alloc
Space allocated for buffer.
Definition rlm_ocsp.c:86
int max_age
Maximum age of thisUpdate allowed in response checks.
Definition rlm_ocsp.c:48
fr_value_box_t certuri
Certificate provided OCSP endpoint.
Definition rlm_ocsp.c:119
char const * ca_file
File containing certs for verifying OCSP responses.
Definition rlm_ocsp.c:51
request_t * request
Current request.
Definition rlm_ocsp.c:82
bool verifycert
Should the certificate in responses be verified.
Definition rlm_ocsp.c:50
ocsp_status_t
Definition rlm_ocsp.c:70
@ OCSP_STATUS_FAILED
Definition rlm_ocsp.c:71
@ OCSP_STATUS_SKIPPED
Definition rlm_ocsp.c:73
@ OCSP_STATUS_OK
Definition rlm_ocsp.c:72
char * body
Pointer to the buffer which contains body data.
Definition rlm_ocsp.c:97
rlm_ocsp_t const * inst
Module instance.
Definition rlm_ocsp.c:81
write_state_t
Definition rlm_ocsp.c:63
@ WRITE_STATE_INIT
Definition rlm_ocsp.c:64
@ WRITE_STATE_PARSE_HEADERS
Definition rlm_ocsp.c:65
@ WRITE_STATE_PARSE_CONTENT
Definition rlm_ocsp.c:66
@ WRITE_STATE_DISCARD
Definition rlm_ocsp.c:67
int code
HTTP Status Code.
Definition rlm_ocsp.c:89
module_rlm_t rlm_ocsp
Definition rlm_ocsp.c:823
int leeway
Seconds of leeway allowed in checking response thisUpdate
Definition rlm_ocsp.c:47
char const * url
Override / fallback OCSP URL.
Definition rlm_ocsp.c:43
size_t used
Space used in buffer.
Definition rlm_ocsp.c:87
static int mod_instantiate(module_inst_ctx_t const *mctx)
Instantiate the module.
Definition rlm_ocsp.c:803
fr_curl_conn_config_t conn_config
Reusable CURL handle config.
Definition rlm_ocsp.c:55
char const * ca_path
Directory containing certs for verifying OCSP responses.
Definition rlm_ocsp.c:52
uint64_t max_body_in
Largest response we accept.
Definition rlm_ocsp.c:54
static conf_parser_t module_config[]
Definition rlm_ocsp.c:102
fr_value_box_t certid
The certificate ID being checked.
Definition rlm_ocsp.c:120
static char const * name
#define SECTION_NAME(_name1, _name2)
Define a section name consisting of a verb and a noun.
Definition section.h:39
#define MODULE_THREAD_INST(_ctype)
Definition module.h:260
CONF_SECTION * conf
Module's instance configuration.
Definition module.h:353
size_t inst_size
Size of the module's instance data.
Definition module.h:212
void * data
Module's instance data.
Definition module.h:295
#define MODULE_BINDING_TERMINATOR
Terminate a module binding list.
Definition module.h:152
Named methods exported by a module.
Definition module.h:174
#define FR_SLAB_FUNCS(_name, _type)
Define type specific wrapper functions for slabs and slab elements.
Definition slab.h:124
#define FR_SLAB_TYPES(_name, _type)
Define type specific wrapper structs for slabs and slab elements.
Definition slab.h:75
unlang_action_t unlang_module_yield(request_t *request, module_method_t resume, unlang_module_signal_t signal, fr_signal_t sigmask, void *rctx)
Yield a request back to the interpreter from within a module.
Definition module.c:431
eap_aka_sim_process_conf_t * inst
fr_pair_t * vp
Stores an attribute, a value and various bits of other data.
Definition pair.h:68
char * talloc_bstr_realloc(TALLOC_CTX *ctx, char *in, size_t inlen)
Trim a bstr (char) buffer.
Definition talloc.c:682
#define talloc_get_type_abort_const
Definition talloc.h:117
static int64_t fr_time_to_sec(fr_time_t when)
Convert an fr_time_t (internal time) to number of sec since the unix epoch (wallclock time)
Definition time.h:731
static int64_t fr_time_delta_to_msec(fr_time_delta_t delta)
Definition time.h:637
A time delta, a difference in time measured in nanoseconds.
Definition time.h:80
"server local" time.
Definition time.h:69
@ T_BARE_WORD
Definition token.h:118
#define fr_strerror_printf(_fmt,...)
Log to thread local error buffer.
Definition strerror.h:64
int fr_tls_utils_asn1time_to_epoch(time_t *out, ASN1_TIME const *asn1)
Convert OpenSSL's ASN1_TIME to an epoch time.
Definition utils.c:115
#define fr_box_strvalue_len(_val, _len)
Definition value.h:334
static fr_sbuff_err_t char size_t * len
Definition value.h:1062
#define fr_box_time_delta(_val)
Definition value.h:391
int nonnull(2, 5))