25RCSID(
"$Id: eda5843eeb0802451d2a638e203ce466574780b8 $")
27#include <freeradius-devel/server/base.h>
28#include <freeradius-devel/server/module_rlm.h>
30#include <freeradius-devel/tls/strerror.h>
31#include <freeradius-devel/tls/utils.h>
33#include <openssl/x509v3.h>
34#include <openssl/pem.h>
35#include <openssl/asn1.h>
36#include <openssl/bn.h>
137 fr_value_box_list_head_t *
cdp;
165 .pair.dflt =
"session-state.TLS-Certificate.Serial", .pair.dflt_quote =
T_BARE_WORD },
167 .pair.dflt =
"session-state.TLS-Certificate.X509v3-CRL-Distribution-Points[*]", .pair.dflt_quote =
T_BARE_WORD },
172static int8_t crl_cmp(
void const *a,
void const *b)
180static void crl_free(
void *
data)
185static int8_t crl_pending_cmp(
void const *a,
void const *b)
206 ERROR(
"Failed inserting CRL expiry event");
235 pthread_mutex_unlock(&
inst->mutable->mutex);
245 X509_REVOKED *revoked;
246 ASN1_INTEGER *asn1_serial = NULL;
249 asn1_serial = d2i_ASN1_INTEGER(NULL, (
unsigned char const **)&serial, talloc_array_length(serial));
250 ret = X509_CRL_get0_by_serial(crl_entry->
crl, &revoked, asn1_serial);
251 ASN1_INTEGER_free(asn1_serial);
255 RDEBUG3(
"Certificate not in CRL");
286 while ((vb = fr_value_box_list_next(&(*found)->delta_urls, vb))) {
287 find.
cdp_url = vb->vb_strvalue;
290 ret = crl_check_entry(delta, request, serial);
304 return crl_check_entry(*found, request, serial);
309 X509_CRL_free(crl_entry->
crl);
327 STACK_OF(DIST_POINT) *dps;
328 X509_STORE_CTX *verify_ctx = NULL;
334 crl->
crl = d2i_X509_CRL(NULL, (
const unsigned char **)&our_data, talloc_array_length(our_data));
335 if (crl->
crl == NULL) {
336 fr_tls_strerror_printf(
"Failed to parse CRL from %s",
url);
339 if (verify_ctx) X509_STORE_CTX_free(verify_ctx);
342 talloc_set_destructor(crl, _crl_entry_free);
344 verify_ctx = X509_STORE_CTX_new();
345 if (!verify_ctx || !X509_STORE_CTX_init(verify_ctx,
inst->verify_store, NULL, NULL)) {
346 fr_tls_strerror_printf(
"Error initialising X509 store");
350 xobj = X509_STORE_CTX_get_obj_by_subject(verify_ctx, X509_LU_X509,
351 X509_CRL_get_issuer(crl->
crl));
353 fr_tls_strerror_printf(
"CRL issuer certificate not in trusted store");
356 pkey = X509_get_pubkey(X509_OBJECT_get0_X509(xobj));
357 X509_OBJECT_free(xobj);
359 fr_tls_strerror_printf(
"Error getting CRL issuer public key");
362 i = X509_CRL_verify(crl->
crl, pkey);
366 fr_tls_strerror_printf(
"Could not verify CRL signature");
370 fr_tls_strerror_printf(
"CRL certificate signature failed");
374 crl->
crl_num = X509_CRL_get_ext_d2i(crl->
crl, NID_crl_number, &i, NULL);
381 ASN1_INTEGER *base_num = X509_CRL_get_ext_d2i(crl->
crl, NID_delta_crl, &i, NULL);
383 fr_tls_strerror_printf(
"Delta CRL missing Delta CRL Indicator extension");
386 if (ASN1_INTEGER_cmp(base_num, base_crl->
crl_num) > 0) {
387 uint64_t delta_base, crl_num;
388 ASN1_INTEGER_get_uint64(&delta_base, base_num);
389 ASN1_INTEGER_get_uint64(&crl_num, base_crl->
crl_num);
390 fr_tls_strerror_printf(
"Delta CRL referrs to base CRL number %"PRIu64
", current base is %"PRIu64,
391 delta_base, crl_num);
392 ASN1_INTEGER_free(base_num);
395 ASN1_INTEGER_free(base_num);
397 uint64_t delta_num, crl_num;
398 ASN1_INTEGER_get_uint64(&delta_num, crl->
crl_num);
399 ASN1_INTEGER_get_uint64(&crl_num, base_crl->
crl_num);
400 fr_tls_strerror_printf(
"Delta CRL number %"PRIu64
" is less than base CRL number %"PRIu64,
407 fr_tls_strerror_printf(
"Failed to parse nextUpdate from CRL");
412 ERROR(
"Failed to insert CRL into tree of CRLs");
421 if (!base_crl && (dps = X509_CRL_get_ext_d2i(crl->
crl, NID_freshest_crl, NULL, NULL))) {
423 STACK_OF(GENERAL_NAME) *
names;
428 for (i = 0; i < sk_DIST_POINT_num(dps); i++) {
429 dp = sk_DIST_POINT_value(dps, i);
430 names = dp->distpoint->name.fullname;
431 for (j = 0; j < sk_GENERAL_NAME_num(
names); j++) {
433 if (
name->type != GEN_URI)
continue;
436 (
char const *)ASN1_STRING_get0_data(
name->d.uniformResourceIdentifier),
437 ASN1_STRING_length(
name->d.uniformResourceIdentifier),
true);
438 DEBUG3(
"CRL references delta URI %pV", vb);
439 fr_value_box_list_insert_tail(&crl->
delta_urls, vb);
442 CRL_DIST_POINTS_free(dps);
446 if (base_crl &&
inst->force_delta_expiry_is_set) {
449 if (
inst->force_expiry_is_set &&
454 if (
fr_timer_in(crl, tl, &crl->
ev, expiry_time,
false, crl_expire, crl) <0) {
455 ERROR(
"Failed to set timer to expire CRL");
458 X509_STORE_CTX_free(verify_ctx);
487 if (strncmp(rctx->
cdp_url->vb_strvalue,
"http", 4) == 0) {
489 }
else if (strncmp(rctx->
cdp_url->vb_strvalue,
"ldap", 4) == 0) {
491 RWARN(
"CRL URI %pV requires LDAP, but the crl module ldap expansion is not configured", rctx->
cdp_url);
495 }
else if (strncmp(rctx->
cdp_url->vb_strvalue,
"ftp", 3) == 0) {
497 RWARN(
"CRL URI %pV requires FTP, but the crl module ftp expansion is not configured", rctx->
cdp_url);
502 RERROR(
"Unsupported URI scheme in CRL URI %pV", rctx->
cdp_url);
507 &request->request_pairs);
510 NULL, crl_process_cdp_data, crl_signal, 0, rctx) < 0)
return -1;
511 inst->mutable->fetching = thread;
532 inst->mutable->fetching = NULL;
533 switch (fr_value_box_list_num_elements(&rctx->
crl_data)) {
537 inst->trigger_rate_limit, &request->request_pairs);
546 switch (crl_tmpl_yield(request,
inst, t, env, rctx)) {
556 fr_value_box_list_talloc_free(&rctx->
crl_data);
567 crl_data->vb_octets, rctx->
base_crl);
570 RPERROR(
"Failed to process returned CRL data");
572 inst->trigger_rate_limit, &request->request_pairs);
582 if (fr_value_box_list_num_elements(&crl_entry->
delta_urls) > 0) {
587 while ((vb = fr_value_box_list_next(&crl_entry->
delta_urls, vb))) {
588 find.
cdp_url = vb->vb_strvalue;
591 ret = crl_check_entry(delta, request, env->
serial.vb_octets);
599 fr_value_box_list_insert_tail(&rctx->
missing_crls, delta_uri);
628 RDEBUG3(
"Certificate not in delta CRL, checking base CRL");
630 ret = crl_check_entry(rctx->
base_crl, request, env->
serial.vb_octets);
656 REDEBUG(
"Too many CRL values returned, failing");
661 pthread_mutex_unlock(&
inst->mutable->mutex);
675 fr_value_box_list_init(&rctx->missing_crls);
677 pthread_mutex_lock(&
inst->mutable->mutex);
684 while ((rctx->cdp_url = fr_value_box_list_pop_head(env->cdp))) {
685 switch (crl_check_serial(
inst->mutable->crls, request, rctx->cdp_url->vb_strvalue,
686 env->serial.vb_octets, &found)) {
700 fr_value_box_list_insert_tail(&rctx->missing_crls, rctx->cdp_url);
712 rctx->base_crl = found;
714 fr_value_box_list_talloc_free(&rctx->missing_crls);
715 while ((vb = fr_value_box_list_next(&found->
delta_urls, vb))) {
717 fr_value_box_list_insert_tail(&rctx->missing_crls, delta_uri);
727 pthread_mutex_unlock(&
inst->mutable->mutex);
742 fr_value_box_list_init(&rctx->crl_data);
745 rctx->cdp_url = fr_value_box_list_pop_head(&rctx->missing_crls);
747 switch (crl_tmpl_yield(request,
inst, t, env, rctx)) {
774 return crl_by_url(p_result,
inst, t, env, mctx->
rctx, request);
799 if (
inst->mutable->fetching != t)
return crl_by_url(p_result,
inst, t, env, mctx->
rctx, request);
805 RDEBUG3(
"Yielding request until CRL fetching completed");
820 pthread_mutex_destroy(&mutable->mutex);
828 if (
inst->verify_store) X509_STORE_free(
inst->verify_store);
844 pthread_mutex_init(&
inst->mutable->mutex, NULL);
847 if (!
inst->ca_file && !
inst->ca_path) {
848 cf_log_err(mctx->
mi->
conf,
"Missing ca_file / ca_path option. One or other (or both) must be specified.");
854 inst->verify_store = X509_STORE_new();
855 if (!X509_STORE_load_locations(
inst->verify_store,
inst->ca_file,
inst->ca_path)) {
856 cf_log_err(mctx->
mi->
conf,
"Failed reading Trusted root CA file \"%s\" and path \"%s\"",
861 X509_STORE_set_purpose(
inst->verify_store, X509_PURPOSE_SSL_CLIENT);
unlang_action_t
Returned by unlang_op_t calls, determine the next action of the interpreter.
@ UNLANG_ACTION_PUSHED_CHILD
unlang_t pushed a new child onto the stack, execute it instead of continuing.
#define FALL_THROUGH
clang 10 doesn't recognised the FALL-THROUGH comment anymore
#define CMP(_a, _b)
Same as CMP_PREFER_SMALLER use when you don't really care about ordering, you just want an ordering.
#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.
#define FR_CALL_ENV_SUBSECTION(_name, _name2, _flags, _subcs)
Specify a call_env_parser_t which defines a nested subsection.
@ CALL_ENV_FLAG_SUBSECTION
This is a subsection.
@ 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_MULTI
Multiple instances of the conf pairs are allowed.
@ CALL_ENV_FLAG_REQUIRED
Associated conf pair or section is required.
@ CALL_ENV_FLAG_PARSE_MISSING
If this subsection is missing, still parse it.
@ CALL_ENV_FLAG_BARE_WORD_ATTRIBUTE
bare words are treated as an attribute, but strings may be xlats.
@ 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 FR_CALL_ENV_PARSE_ONLY_OFFSET(_name, _cast_type, _flags, _struct, _parse_field)
Specify a call_env_parser_t which writes out the result of the parsing phase to the field specified.
#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_IS_SET(_name, _type, _flags, _struct, _field)
conf_parser_t which parses a single CONF_PAIR, writing the result to a field in a struct,...
Defines a CONF_PAIR to C data type mapping.
A section grouping multiple CONF_PAIR.
#define cf_log_err(_cf, _fmt,...)
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.
void unlang_interpret_mark_runnable(request_t *request)
Mark a request as resumable.
TALLOC_CTX * unlang_interpret_frame_talloc_ctx(request_t *request)
Get a talloc_ctx which is valid only for this frame.
unlang_interpret_t * unlang_interpret_get_thread_default(void)
Get the default interpreter for this thread.
fr_event_list_t * unlang_interpret_event_list(request_t *request)
Get the event list for the current interpreter.
#define REDEBUG2(fmt,...)
@ FR_TYPE_TIME_DELTA
A period of time measured in nanoseconds.
@ FR_TYPE_STRING
String of printable characters.
@ FR_TYPE_OCTETS
Raw octets.
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.
module_instance_t * mi
Module instance to detach.
void * thread
Thread instance data.
module_instance_t * mi
Instance of the module being instantiated.
Temporary structure to hold arguments for module calls.
Temporary structure to hold arguments for detach 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.
void fr_pair_list_init(fr_pair_list_t *list)
Initialise a pair list header.
fr_pair_t * fr_pair_afrom_da_nested(TALLOC_CTX *ctx, fr_pair_list_t *list, fr_dict_attr_t const *da)
Create a pair (and all intermediate parents), and append it to the list.
static const conf_parser_t config[]
static char const * url[FR_RADIUS_FAIL_MAX+1]
#define pair_update_request(_attr, _da)
void * fr_rb_remove(fr_rb_tree_t *tree, void const *data)
Remove an entry from the tree, without freeing the data.
void * fr_rb_find(fr_rb_tree_t const *tree, void const *data)
Find an element in the tree, returning the data, not the node.
void * fr_rb_first(fr_rb_tree_t *tree)
bool fr_rb_insert(fr_rb_tree_t *tree, void const *data)
Insert data into a tree.
bool fr_rb_delete(fr_rb_tree_t *tree, void const *data)
Remove node and free data (if a free function was specified)
#define fr_rb_inline_talloc_alloc(_ctx, _type, _field, _data_cmp, _data_free)
Allocs a red black that verifies elements are of a specific talloc type.
#define fr_rb_inline_init(_tree, _type, _field, _data_cmp, _data_free)
Initialises a red black tree.
The main red black tree structure.
#define RETURN_UNLANG_RCODE(_rcode)
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_REJECT
Immediately reject the request.
@ RLM_MODULE_NOTFOUND
User not found.
@ RLM_MODULE_NOOP
Module succeeded without doing anything.
#define RETURN_UNLANG_NOOP
static int mod_detach(module_detach_ctx_t const *mctx)
static int mod_thread_instantiate(module_thread_inst_ctx_t const *mctx)
rlm_crl_mutable_t * mutable
Mutable data that's shared between all threads.
fr_value_box_list_t crl_data
Data from CRL expansion.
tmpl_t * ldap_exp
The xlat expansion used to retrieve the CRL via ldap://.
fr_value_box_list_t delta_urls
URLs from which a delta CRL can be retrieved.
bool force_delta_expiry_is_set
rlm_crl_thread_t * fetching
Pointer to thread instance data of thread which is fetching a CRL.
char const * ca_file
File containing certs for verifying CRL signatures.
crl_check_status_t
A status used to track which CRL is being checked.
@ CRL_CHECK_BASE
The base CRL is being checked.
@ CRL_CHECK_DELTA
The delta CRL exists and is being checked.
@ CRL_CHECK_FETCH_DELTA
The delta CRL is being fetched.
fr_rb_tree_t * crls
A tree of CRLs organised by CDP URL.
fr_value_box_t serial
The serial to check.
rlm_crl_thread_t * thread
The thread which fetched this entry.
@ CRL_MISSING_DELTA
Need to load a delta CRL to supplement this CRL.
@ CRL_ENTRY_FOUND
Serial was found in this CRL.
@ CRL_ENTRY_REMOVED
Serial was "un-revoked" in this delta CRL.
@ CRL_ERROR
Unspecified error ocurred.
@ CRL_NOT_FOUND
No CRL found, need to load it from the CDP URL.
@ CRL_ENTRY_NOT_FOUND
Serial not found in this CRL.
fr_value_box_t * cdp_url
The URL we're currently attempting to load.
fr_dict_attr_autoload_t rlm_crl_dict_attr[]
static fr_dict_t const * dict_freeradius
rlm_crl_t const * inst
The instance of the CRL module.
fr_rb_node_t node
The node in the tree.
char const * cdp_url
The URL of the CRL.
X509_STORE * verify_store
Store of certificates to verify CRL signatures.
fr_time_delta_t force_delta_expiry
Force expiry of delta CRLs after this time.
bool trigger_rate_limit
Rate limit triggers.
fr_value_box_list_t missing_crls
CRLs missing from the tree.
fr_timer_list_t * timer_list
The timer list to use for CRL expiry.
static fr_dict_attr_t const * attr_crl_cdp_url
crl_entry_t * base_crl
The base CRL relating to the delta currently being fetched.
fr_time_delta_t force_expiry
Force expiry of CRLs after this time.
fr_rb_tree_t pending
Requests yielded while the CRL is being fetched.
fr_dict_autoload_t rlm_crl_dict[]
fr_timer_t * ev
When to expire the CRL.
tmpl_t * ftp_exp
The xlat expansion used to retrieve the CRL via ftp://.
char const * ca_path
Directory containing certs for verifying CRL signatures.
fr_value_box_list_head_t * cdp
The CRL distribution points.
tmpl_t * http_exp
The xlat expansion used to retrieve the CRL via http://.
static int mod_instantiate(module_inst_ctx_t const *mctx)
Instantiate the module.
static fr_dict_attr_t const * attr_crl_data
CONF_SECTION * virtual_server
Virtual server to use when retrieving CRLs.
static conf_parser_t module_config[]
crl_check_status_t status
Status of the current CRL check.
ASN1_INTEGER * crl_num
The CRL number.
fr_time_delta_t early_refresh
Time interval before nextUpdate to refresh.
CONF_SECTION * cs
Module instance config.
A single CRL in the global list of CRLs.
Structure to record a request which is waiting for CRL fetching to complete.
Thread specific structure to hold requests awaiting CRL fetching.
static int mod_mutable_free(rlm_kv_mutable_t *mutable)
#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 pair_delete_request(_pair_or_da)
Delete a fr_pair_t in the request list.
fr_signal_t
Signals that can be generated/processed by request signal handlers.
@ FR_SIGNAL_CANCEL
Request has been cancelled.
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.
unlang_action_t unlang_module_yield_to_tmpl(TALLOC_CTX *ctx, fr_value_box_list_t *out, request_t *request, tmpl_t const *vpt, unlang_tmpl_args_t *args, module_method_t resume, unlang_module_signal_t signal, fr_signal_t sigmask, void *rctx)
Push a pre-compiled tmpl and resumption state onto the stack for evaluation.
eap_aka_sim_process_conf_t * inst
Stores an attribute, a value and various bits of other data.
char * talloc_bstrdup(TALLOC_CTX *ctx, char const *in)
Binary safe strdup function.
#define talloc_get_type_abort_const
static const char * names[8]
static int8_t fr_time_delta_cmp(fr_time_delta_t a, fr_time_delta_t b)
Compare two fr_time_delta_t values.
static fr_time_delta_t fr_time_delta_from_sec(int64_t sec)
static fr_time_t fr_time_from_sec(time_t when)
Convert a time_t (wallclock time) to a fr_time_t (internal time)
static fr_time_delta_t fr_time_delta_sub(fr_time_delta_t a, fr_time_delta_t b)
#define fr_time_sub(_a, _b)
Subtract one time from another.
A time delta, a difference in time measured in nanoseconds.
bool trigger_enabled(void)
Return whether triggers are enabled.
int trigger(unlang_interpret_t *intp, CONF_SECTION const *cs, CONF_PAIR **trigger_cp, char const *name, bool rate_limit, fr_pair_list_t *args)
Execute a trigger - call an executable to process an event.
int fr_tls_utils_asn1time_to_epoch(time_t *out, ASN1_TIME const *asn1)
Convert OpenSSL's ASN1_TIME to an epoch time.
int fr_value_box_copy(TALLOC_CTX *ctx, fr_value_box_t *dst, const fr_value_box_t *src)
Copy value data verbatim duplicating any buffers.
void fr_value_box_strdup_shallow(fr_value_box_t *dst, fr_dict_attr_t const *enumv, char const *src, bool tainted)
Assign a buffer containing a nul terminated string to a box, but don't copy it.
int fr_value_box_bstrndup(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_dict_attr_t const *enumv, char const *src, size_t len, bool tainted)
Copy a string to to a fr_value_box_t.
static fr_value_box_t * fr_value_box_acopy(TALLOC_CTX *ctx, fr_value_box_t const *src)
Copy an existing box, allocating a new box to hold its contents.
#define fr_box_time_delta(_val)
#define fr_value_box_alloc_null(_ctx)
Allocate a value box for later use with a value assignment function.