25RCSID(
"$Id: 60f30d0a663de84e2d391f70e8edae8fd65251a4 $")
27#include <freeradius-devel/server/base.h>
28#include <freeradius-devel/server/module_rlm.h>
29#include <freeradius-devel/unlang/call_env.h>
31#include <freeradius-devel/tls/strerror.h>
32#include <freeradius-devel/tls/utils.h>
34#include <openssl/x509.h>
35#include <openssl/x509v3.h>
36#include <openssl/pem.h>
37#include <openssl/asn1.h>
38#include <openssl/bn.h>
139 fr_value_box_list_head_t *
cdp;
167 .pair.dflt =
"session-state.TLS-Certificate.Serial", .pair.dflt_quote =
T_BARE_WORD },
169 .pair.dflt =
"session-state.TLS-Certificate.X509v3-CRL-Distribution-Points[*]", .pair.dflt_quote =
T_BARE_WORD },
174static int8_t crl_cmp(
void const *a,
void const *b)
182static void crl_free(
void *
data)
187static int8_t crl_pending_cmp(
void const *a,
void const *b)
208 ERROR(
"Failed inserting CRL expiry event");
237 pthread_mutex_unlock(&
inst->mutable->mutex);
247 X509_REVOKED *revoked;
248 ASN1_INTEGER *asn1_serial = NULL;
251 asn1_serial = d2i_ASN1_INTEGER(NULL, (
unsigned char const **)&serial, talloc_array_length(serial));
252 ret = X509_CRL_get0_by_serial(crl_entry->
crl, &revoked, asn1_serial);
253 ASN1_INTEGER_free(asn1_serial);
257 RDEBUG3(
"Certificate not in CRL");
288 while ((vb = fr_value_box_list_next(&(*found)->delta_urls, vb))) {
289 find.
cdp_url = vb->vb_strvalue;
292 ret = crl_check_entry(delta, request, serial);
306 return crl_check_entry(*found, request, serial);
311 X509_CRL_free(crl_entry->
crl);
329 STACK_OF(DIST_POINT) *dps;
330 X509_STORE_CTX *verify_ctx = NULL;
336 crl->
crl = d2i_X509_CRL(NULL, (
const unsigned char **)&our_data, talloc_array_length(our_data));
337 if (crl->
crl == NULL) {
338 fr_tls_strerror_printf(
"Failed to parse CRL from %s", url);
341 if (verify_ctx) X509_STORE_CTX_free(verify_ctx);
344 talloc_set_destructor(crl, _crl_entry_free);
346 verify_ctx = X509_STORE_CTX_new();
347 if (!verify_ctx || !X509_STORE_CTX_init(verify_ctx,
inst->verify_store, NULL, NULL)) {
348 fr_tls_strerror_printf(
"Error initialising X509 store");
352 xobj = X509_STORE_CTX_get_obj_by_subject(verify_ctx, X509_LU_X509,
353 X509_CRL_get_issuer(crl->
crl));
355 fr_tls_strerror_printf(
"CRL issuer certificate not in trusted store");
358 pkey = X509_get_pubkey(X509_OBJECT_get0_X509(xobj));
359 X509_OBJECT_free(xobj);
361 fr_tls_strerror_printf(
"Error getting CRL issuer public key");
364 i = X509_CRL_verify(crl->
crl, pkey);
368 fr_tls_strerror_printf(
"Could not verify CRL signature");
372 fr_tls_strerror_printf(
"CRL certificate signature failed");
376 crl->
crl_num = X509_CRL_get_ext_d2i(crl->
crl, NID_crl_number, &i, NULL);
383 ASN1_INTEGER *base_num = X509_CRL_get_ext_d2i(crl->
crl, NID_delta_crl, &i, NULL);
385 fr_tls_strerror_printf(
"Delta CRL missing Delta CRL Indicator extension");
388 if (ASN1_INTEGER_cmp(base_num, base_crl->
crl_num) > 0) {
389 uint64_t delta_base, crl_num;
390 ASN1_INTEGER_get_uint64(&delta_base, base_num);
391 ASN1_INTEGER_get_uint64(&crl_num, base_crl->
crl_num);
392 fr_tls_strerror_printf(
"Delta CRL referrs to base CRL number %"PRIu64
", current base is %"PRIu64,
393 delta_base, crl_num);
394 ASN1_INTEGER_free(base_num);
397 ASN1_INTEGER_free(base_num);
399 uint64_t delta_num, crl_num;
400 ASN1_INTEGER_get_uint64(&delta_num, crl->
crl_num);
401 ASN1_INTEGER_get_uint64(&crl_num, base_crl->
crl_num);
402 fr_tls_strerror_printf(
"Delta CRL number %"PRIu64
" is less than base CRL number %"PRIu64,
409 fr_tls_strerror_printf(
"Failed to parse nextUpdate from CRL");
414 ERROR(
"Failed to insert CRL into tree of CRLs");
423 if (!base_crl && (dps = X509_CRL_get_ext_d2i(crl->
crl, NID_freshest_crl, NULL, NULL))) {
425 STACK_OF(GENERAL_NAME) *
names;
430 for (i = 0; i < sk_DIST_POINT_num(dps); i++) {
431 dp = sk_DIST_POINT_value(dps, i);
432 names = dp->distpoint->name.fullname;
433 for (j = 0; j < sk_GENERAL_NAME_num(
names); j++) {
435 if (
name->type != GEN_URI)
continue;
438 (
char const *)ASN1_STRING_get0_data(
name->d.uniformResourceIdentifier),
439 ASN1_STRING_length(
name->d.uniformResourceIdentifier),
true);
440 DEBUG3(
"CRL references delta URI %pV", vb);
441 fr_value_box_list_insert_tail(&crl->
delta_urls, vb);
444 CRL_DIST_POINTS_free(dps);
448 if (base_crl &&
inst->force_delta_expiry_is_set) {
451 if (
inst->force_expiry_is_set &&
456 if (
fr_timer_in(crl, tl, &crl->
ev, expiry_time,
false, crl_expire, crl) <0) {
457 ERROR(
"Failed to set timer to expire CRL");
460 X509_STORE_CTX_free(verify_ctx);
489 if (strncmp(rctx->
cdp_url->vb_strvalue,
"http", 4) == 0) {
491 }
else if (strncmp(rctx->
cdp_url->vb_strvalue,
"ldap", 4) == 0) {
493 RWARN(
"CRL URI %pV requires LDAP, but the crl module ldap expansion is not configured", rctx->
cdp_url);
497 }
else if (strncmp(rctx->
cdp_url->vb_strvalue,
"ftp", 3) == 0) {
499 RWARN(
"CRL URI %pV requires FTP, but the crl module ftp expansion is not configured", rctx->
cdp_url);
504 RERROR(
"Unsupported URI scheme in CRL URI %pV", rctx->
cdp_url);
509 &request->request_pairs);
512 NULL, crl_process_cdp_data, crl_signal, 0, rctx) < 0)
return -1;
513 inst->mutable->fetching = thread;
534 inst->mutable->fetching = NULL;
535 switch (fr_value_box_list_num_elements(&rctx->
crl_data)) {
539 inst->trigger_rate_limit, &request->request_pairs);
548 switch (crl_tmpl_yield(request,
inst, t, env, rctx)) {
558 fr_value_box_list_talloc_free(&rctx->
crl_data);
569 crl_data->vb_octets, rctx->
base_crl);
572 RPERROR(
"Failed to process returned CRL data");
574 inst->trigger_rate_limit, &request->request_pairs);
584 if (fr_value_box_list_num_elements(&crl_entry->
delta_urls) > 0) {
589 while ((vb = fr_value_box_list_next(&crl_entry->
delta_urls, vb))) {
590 find.
cdp_url = vb->vb_strvalue;
593 ret = crl_check_entry(delta, request, env->
serial.vb_octets);
601 fr_value_box_list_insert_tail(&rctx->
missing_crls, delta_uri);
630 RDEBUG3(
"Certificate not in delta CRL, checking base CRL");
632 ret = crl_check_entry(rctx->
base_crl, request, env->
serial.vb_octets);
658 REDEBUG(
"Too many CRL values returned, failing");
663 pthread_mutex_unlock(&
inst->mutable->mutex);
677 fr_value_box_list_init(&rctx->missing_crls);
679 pthread_mutex_lock(&
inst->mutable->mutex);
686 while ((rctx->cdp_url = fr_value_box_list_pop_head(env->cdp))) {
687 switch (crl_check_serial(
inst->mutable->crls, request, rctx->cdp_url->vb_strvalue,
688 env->serial.vb_octets, &found)) {
702 fr_value_box_list_insert_tail(&rctx->missing_crls, rctx->cdp_url);
714 rctx->base_crl = found;
716 fr_value_box_list_talloc_free(&rctx->missing_crls);
717 while ((vb = fr_value_box_list_next(&found->
delta_urls, vb))) {
719 fr_value_box_list_insert_tail(&rctx->missing_crls, delta_uri);
729 pthread_mutex_unlock(&
inst->mutable->mutex);
744 fr_value_box_list_init(&rctx->crl_data);
747 rctx->cdp_url = fr_value_box_list_pop_head(&rctx->missing_crls);
749 switch (crl_tmpl_yield(request,
inst, t, env, rctx)) {
776 return crl_by_url(p_result,
inst, t, env, mctx->
rctx, request);
801 if (
inst->mutable->fetching != t)
return crl_by_url(p_result,
inst, t, env, mctx->
rctx, request);
807 RDEBUG3(
"Yielding request until CRL fetching completed");
822 pthread_mutex_destroy(&mutable->mutex);
830 if (
inst->verify_store) X509_STORE_free(
inst->verify_store);
846 pthread_mutex_init(&
inst->mutable->mutex, NULL);
847 talloc_set_destructor(
inst->mutable, mod_mutable_free);
849 if (!
inst->ca_file && !
inst->ca_path) {
850 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.
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[]
#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.
#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
#define fr_time()
Allow us to arbitrarily manipulate time.
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.