24RCSID(
"$Id: dba85ac6d27cc1f54e3983cb048fa743461907ee $")
26#include <freeradius-devel/server/base.h>
27#include <freeradius-devel/curl/base.h>
28#include <freeradius-devel/server/module_rlm.h>
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>
37#include <openssl/ocsp.h>
61#define REST_BODY_ALLOC_CHUNK 1024
143 ocsp_slab_list_t *slab;
149 OCSP_CERTID *cert_id;
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 },
185static size_t ocsp_response_header(
void *
in,
size_t size,
size_t nmemb,
void *userdata)
190 char const *start = (
char *)
in, *p = start, *end = p + (size * nmemb);
194 if (((end - p) == 2) && ((p[0] ==
'\r') && (p[1] ==
'\n'))) {
195 if (ctx->
code == 100) {
200 return (end - start);
203 switch (ctx->
state) {
205 RDEBUG2(
"Processing response header");
213 if ((end - p) < 12) {
214 REDEBUG(
"Malformed HTTP header: Status line too short");
216 REDEBUG(
"Received %zu bytes of invalid header data: %pV",
220 return (end - start);
224 REDEBUG(
"Malformed HTTP header: Missing HTTP version");
232 q = memchr(p,
' ', (end - p));
234 REDEBUG(
"Malformed HTTP header: Missing reason code");
246 REDEBUG(
"Malformed HTTP header: Reason code too short");
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\"",
268 ctx->
code = (int)strtoul(p, &qq, 10);
279 q = memchr(p,
'\r', (end - p));
280 if (!q)
goto malformed;
292 if (((end - p) >= 14) &&
299 q = memchr(p,
';', (end - p));
304 if (!q) q = memchr(p,
'\r', (end - p));
306 len = (
size_t)(!q ? (end - p) : (q - p));
307 if (strncmp(p,
"application/ocsp-response",
len) != 0) {
317 return (end - start);
332static size_t ocsp_response_body(
void *
in,
size_t size,
size_t nmemb,
void *userdata)
337 char const *start =
in, *p = start, *end = p + (size * nmemb);
341 if (start == end)
return 0;
349 REDEBUG(
"Incoming data (%zu bytes) exceeds max_body_in (%"PRIu64
" bytes).",
356 if (needed > ctx->
alloc) {
362 memcpy(out_p, p, (end - p));
365 ctx->
used += (end - p);
368 return (end - start);
372 uint8_t *body,
size_t body_len,
char const *uri)
377 struct curl_slist *headers;
380#if CURL_AT_LEAST_VERSION(7,85,0)
391 uctx->
body = (
char *)body;
401 headers = curl_slist_append(uctx->
headers,
"Content-Type: application/ocsp-request");
403 REDEBUG(
"Failed to add Content-Type header");
423static int rlm_ocsp_rctx_free(rlm_ocsp_rctx_t *to_free)
425 OCSP_REQUEST_free(to_free->req);
426 OPENSSL_free(to_free->reqasn1);
427 if (to_free->handle) ocsp_slab_release(to_free->handle);
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);
439 OCSP_RESPONSE *resp = NULL;
440 OCSP_BASICRESP *bresp = NULL;
443 ASN1_GENERALIZEDTIME *rev, *this_update, *next_update;
449 RERROR(
"Invalid HTTP response code");
456 RPERROR(
"Failed parsing response body as an OCSP response");
462 status = OCSP_response_status(resp);
463 if (status != OCSP_RESPONSE_STATUS_SUCCESSFUL) {
464 REDEBUG(
"Response status: %s", OCSP_response_status_str(status));
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");
474 if (
inst->verifycert) {
475 if (OCSP_basic_verify(bresp, NULL, thread->store, 0) != 1){
476 REDEBUG(
"Couldn't verify OCSP basic response");
482 if (!OCSP_resp_find_status(bresp, rctx->cert_id, (
int *)&status, &reason, &rev, &this_update, &next_update)) {
494 if (!OCSP_check_validity(this_update, next_update,
inst->leeway,
inst->max_age)) {
500 "seconds. Check servers are synchronised to a common time source",
505 ssl_log = BIO_new(BIO_s_mem());
507 RDEBUG2(
"OCSP response valid from:");
508 ASN1_GENERALIZEDTIME_print(ssl_log, this_update);
510 FR_OPENSSL_DRAIN_LOG_QUEUE(
RDEBUG2,
"", ssl_log);
514 RDEBUG2(
"New information available at:");
515 ASN1_GENERALIZEDTIME_print(ssl_log, next_update);
517 FR_OPENSSL_DRAIN_LOG_QUEUE(
RDEBUG2,
"", ssl_log);
533 RPEDEBUG(
"Failed parsing next_update time");
538 RDEBUG2(
"Adding OCSP TTL attribute");
546 RDEBUG2(
"Update time is in the past. Not adding TLS-OCSP-Next-Update");
549 RDEBUG2(
"Update time not provided. Not adding TLS-OCSP-Next-Update");
553 case V_OCSP_CERTSTATUS_GOOD:
560 REDEBUG(
"Cert status: %s", OCSP_cert_status_str(status));
561 if (reason != -1)
REDEBUG(
"Reason: %s", OCSP_crl_reason_str(reason));
566 FR_OPENSSL_DRAIN_LOG_QUEUE(
RDEBUG,
"", ssl_log);
569 ASN1_GENERALIZEDTIME_print(ssl_log, rev);
571 FR_OPENSSL_DRAIN_LOG_QUEUE(
RDEBUG2,
"", ssl_log);
578 switch (ocsp_status) {
580 RDEBUG2(
"Certificate is valid");
583 vp->vp_uint32 = FR_TLS_OCSP_CERT_VALID_VALUE_YES;
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");
597 while (ERR_get_error());
599 REDEBUG(
"Unable to check certificate, failing");
605 vp->vp_uint32 = FR_TLS_OCSP_CERT_VALID_VALUE_NO;
606 REDEBUG(
"Failed to validate certificate");
610 OCSP_BASICRESP_free(bresp);
611 OCSP_RESPONSE_free(resp);
622 rlm_ocsp_thread_t *thread = talloc_get_type_abort(mctx->
thread, rlm_ocsp_thread_t);
626 rlm_ocsp_rctx_t *rctx;
633 if (
vp)
switch (
vp->vp_uint32) {
634 case FR_TLS_OCSP_CERT_VALID_VALUE_NO:
638 case FR_TLS_OCSP_CERT_VALID_VALUE_YES:
642 case FR_TLS_OCSP_CERT_VALID_VALUE_SKIPPED:
647 case FR_TLS_OCSP_CERT_VALID_VALUE_UNKNOWN:
653 talloc_set_destructor(rctx, rlm_ocsp_rctx_free);
655 certid = env->
certid.vb_octets;
656 rctx->cert_id = d2i_OCSP_CERTID(NULL, &certid, env->
certid.vb_length);
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);
663 if (
inst->override_url) {
669 RWDEBUG(
"No OCSP URL in certificate, falling back to configured URL");
672 RWDEBUG(
"No OCSP URL in certificate. Not doing OCSP");
675 uri = env->
certuri.vb_strvalue;
678 RDEBUG2(
"Using responder URL \"%s\"", uri);
680 rctx->req_size = ASN1_item_i2d((ASN1_VALUE *)rctx->req, &rctx->reqasn1, ASN1_ITEM_rptr(OCSP_REQUEST));
682 rctx->handle = ocsp_slab_reserve(thread->slab);
684 if (ocsp_request_config(mctx, request, rctx->handle, rctx->reqasn1, rctx->req_size, uri) < 0) {
710 curl_slist_free_all(ctx->
headers);
722 curl_easy_cleanup(randle->
candle);
730 randle->
candle = curl_easy_init();
738 randle->
uctx = curl_ctx;
741 ocsp_slab_element_set_destructor(randle, _ocsp_request_cleanup, NULL);
759 rlm_ocsp_thread_t *t = talloc_get_type_abort(mctx->
thread, rlm_ocsp_thread_t);
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");
769 if (!mhandle)
return -1;
771 t->mhandle = mhandle;
773 if (!
inst->verifycert)
return 0;
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\"",
791 rlm_ocsp_thread_t *t = talloc_get_type_abort(mctx->
thread, rlm_ocsp_thread_t);
795 if (t->store) X509_STORE_free(t->store);
808 if (!
inst->verifycert)
return 0;
810 if (!
inst->ca_file && !
inst->ca_path) {
811 cf_log_err(mctx->
mi->
conf,
"ca_file or ca_path required when verifycert = yes");
unlang_action_t
Returned by unlang_op_t calls, determine the next action of the interpreter.
static int const char char buffer[256]
#define store(_store, _var)
#define CALL_ENV_TERMINATOR
#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...
call_env_parser_t const * env
Parsing rules for call method env.
@ CALL_ENV_FLAG_SINGLE
If the tmpl produces more than one box this is an error.
@ CALL_ENV_FLAG_ATTRIBUTE
Tmpl MUST contain an attribute reference.
@ CALL_ENV_FLAG_REQUIRED
Associated conf pair or section is required.
@ CALL_ENV_FLAG_NULLABLE
Tmpl expansions are allowed to produce no output.
#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.
#define CONF_PARSER_TERMINATOR
#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
#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
#define FR_CONF_OFFSET_SUBSECTION(_name, _flags, _struct, _field, _subcs)
conf_parser_t which populates a sub-struct using a CONF_SECTION
@ CONF_FLAG_FILE_READABLE
File matching value must exist, and must be readable.
Defines a CONF_PAIR to C data type mapping.
#define cf_log_err(_cf, _fmt,...)
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)
request_t * request
Current request.
void * uctx
Private data for the module using the API.
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.
CURL * candle
Request specific handle.
Uctx data for timer and I/O functions.
Structure representing an individual request being passed to curl for processing.
fr_dict_attr_t const ** out
Where to write a pointer to the resolved fr_dict_attr_t.
fr_dict_t const ** out
Where to write a pointer to the loaded/resolved fr_dict_t.
#define DICT_AUTOLOAD_TERMINATOR
Specifies an attribute which must be present for the module to function.
Specifies a dictionary which must be loaded/loadable for the module to function.
#define MODULE_MAGIC_INIT
Stop people using different module/library/server versions together.
TALLOC_CTX * unlang_interpret_frame_talloc_ctx(request_t *request)
Get a talloc_ctx which is valid only for this frame.
conf_parser_t fr_curl_conn_config[]
#define REXDENT()
Exdent (unindent) R* messages by one level.
#define RPEDEBUG(fmt,...)
#define RATE_LIMIT_GLOBAL(_log, _fmt,...)
Rate limit messages using a global limiting entry.
#define RINDENT()
Indent R* messages by one level.
#define ROUND_UP(_num, _mul)
Round up - Works in all cases, but is slower.
@ 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.
int strncasecmp(char *s1, char *s2, int n)
void * env_data
Per call environment data.
module_instance_t const * mi
Instance of the module being instantiated.
void * thread
Thread specific instance data.
void * rctx
Resume ctx that a module previously set.
fr_event_list_t * el
Event list to register any IO handlers and timers against.
void * thread
Thread instance data.
module_instance_t const * mi
Instance of the module being instantiated.
module_instance_t * mi
Instance of the module being instantiated.
Temporary structure to hold arguments for module calls.
Temporary structure to hold arguments for instantiation calls.
Temporary structure to hold arguments for thread_instantiation calls.
module_t common
Common fields presented by all modules.
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.
static const conf_parser_t config[]
static char const * url[FR_RADIUS_FAIL_MAX+1]
#define pair_update_request(_attr, _da)
#define RDEBUG_ENABLED2()
#define RETURN_UNLANG_RCODE(_rcode)
#define RETURN_UNLANG_FAIL
rlm_rcode_t
Return codes indicating the result of the module call.
@ RLM_MODULE_OK
The module is OK, continue.
@ RLM_MODULE_FAIL
Module failed, don't reply.
@ RLM_MODULE_NOT_SET
Error resolving rcode (should not be returned by modules).
@ RLM_MODULE_NOOP
Module succeeded without doing anything.
#define RETURN_UNLANG_NOOP
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[]
bool softfail
Should the module soft fail if the responder is not available.
char * buffer
Raw incoming HTTP data.
struct curl_slist * headers
Any HTTP headers which will be sent with the request.
bool override_url
Always use the configured OCSP URL even if the certificate contains one.
write_state_t state
Decoder state.
rlm_ocsp_response_t response
Response context data.
bool use_nonce
Include a nonce in OCSP requests/.
static fr_dict_attr_t const * attr_tls_ocsp_next_update
static fr_dict_t const * dict_freeradius
fr_dict_autoload_t rlm_ocsp_dict[]
static fr_dict_attr_t const * attr_tls_ocsp_cert_valid
#define REST_BODY_ALLOC_CHUNK
size_t alloc
Space allocated for buffer.
int max_age
Maximum age of thisUpdate allowed in response checks.
fr_value_box_t certuri
Certificate provided OCSP endpoint.
char const * ca_file
File containing certs for verifying OCSP responses.
request_t * request
Current request.
bool verifycert
Should the certificate in responses be verified.
char * body
Pointer to the buffer which contains body data.
rlm_ocsp_t const * inst
Module instance.
@ WRITE_STATE_PARSE_HEADERS
@ WRITE_STATE_PARSE_CONTENT
int code
HTTP Status Code.
int leeway
Seconds of leeway allowed in checking response thisUpdate
char const * url
Override / fallback OCSP URL.
size_t used
Space used in buffer.
static int mod_instantiate(module_inst_ctx_t const *mctx)
Instantiate the module.
fr_curl_conn_config_t conn_config
Reusable CURL handle config.
char const * ca_path
Directory containing certs for verifying OCSP responses.
uint64_t max_body_in
Largest response we accept.
static conf_parser_t module_config[]
fr_value_box_t certid
The certificate ID being checked.
#define SECTION_NAME(_name1, _name2)
Define a section name consisting of a verb and a noun.
#define MODULE_THREAD_INST(_ctype)
CONF_SECTION * conf
Module's instance configuration.
size_t inst_size
Size of the module's instance data.
void * data
Module's instance data.
#define MODULE_BINDING_TERMINATOR
Terminate a module binding list.
Named methods exported by a module.
#define FR_SLAB_FUNCS(_name, _type)
Define type specific wrapper functions for slabs and slab elements.
#define FR_SLAB_TYPES(_name, _type)
Define type specific wrapper structs for slabs and slab elements.
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.
eap_aka_sim_process_conf_t * inst
Stores an attribute, a value and various bits of other data.
char * talloc_bstr_realloc(TALLOC_CTX *ctx, char *in, size_t inlen)
Trim a bstr (char) buffer.
#define talloc_get_type_abort_const
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)
static int64_t fr_time_delta_to_msec(fr_time_delta_t delta)
A time delta, a difference in time measured in nanoseconds.
#define fr_strerror_printf(_fmt,...)
Log to thread local error buffer.
int fr_tls_utils_asn1time_to_epoch(time_t *out, ASN1_TIME const *asn1)
Convert OpenSSL's ASN1_TIME to an epoch time.
#define fr_box_strvalue_len(_val, _len)
static fr_sbuff_err_t char size_t * len
#define fr_box_time_delta(_val)