]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
Add verification of CRL signatures
authorNick Porter <nick@portercomputing.co.uk>
Mon, 2 Jun 2025 11:12:19 +0000 (12:12 +0100)
committerNick Porter <nick@portercomputing.co.uk>
Wed, 4 Jun 2025 10:05:26 +0000 (11:05 +0100)
raddb/mods-available/crl
src/modules/rlm_crl/rlm_crl.c

index 05c12b42df7e76bed7edaf3fb2fa184a44c9305a..3ac3538a8aa5bc2974fff25392a4987657e883ab 100644 (file)
@@ -58,4 +58,21 @@ crl {
        #  early_refresh:: Time before `nextUpdate` which the CRL will be refreshed
        #
        early_refresh = 1h
+
+       #
+       #  ca_file:: File containing trusted CA, used to sign CRLs
+       #
+       #  This can reference the setting in the `eap` module, but in that
+       #  case, the eap module must be instantiate before the `crl` module
+       #  by adding it to the list of explicitly instantiated modules
+       #  in `radiusd.conf`
+       #
+#      ca_file = ${modules.eap.tls-config[tls-common].ca_file}
+       ca_file = ${cadir}/rsa/ca.pem
+
+       #
+       #  ca_path:: Directory containing trusted CAs, used to sign CRLs
+       #
+#      ca_path = ${modules.eap.tls-config[tls-common].ca_path}
+       ca_path = ${cadir}
 }
index adf61210b66ed5ed149825562cec86fe20cf937d..d4ca2ee31cc02d88e912135b8b2ad5ba4b24a3dc 100644 (file)
@@ -67,6 +67,9 @@ typedef struct {
        fr_time_delta_t                 force_expiry;                   //!< Force expiry of CRLs after this time
        bool                            force_expiry_is_set;
        fr_time_delta_t                 early_refresh;                  //!< Time interval before nextUpdate to refresh
+       char const                      *ca_file;                       //!< File containing certs for verifying CRL signatures.
+       char const                      *ca_path;                       //!< Directory containing certs for verifying CRL signatures.
+       X509_STORE                      *verify_store;                  //!< Store of certificates to verify CRL signatures.
        rlm_crl_mutable_t               *mutable;                       //!< Mutable data that's shared between all threads.
 } rlm_crl_t;
 
@@ -89,6 +92,8 @@ typedef struct {
 static conf_parser_t module_config[] = {
        { FR_CONF_OFFSET_IS_SET("force_expiry", FR_TYPE_TIME_DELTA, 0, rlm_crl_t, force_expiry) },
        { FR_CONF_OFFSET("early_refresh", rlm_crl_t, early_refresh) },
+       { FR_CONF_OFFSET("ca_file", rlm_crl_t, ca_file) },
+       { FR_CONF_OFFSET("ca_path", rlm_crl_t, ca_path) },
        CONF_PARSER_TERMINATOR
 };
 
@@ -244,6 +249,9 @@ static crl_entry_t *crl_entry_create(rlm_crl_t const *inst, fr_timer_list_t *tl,
        fr_time_delta_t expiry_time;
        int             i;
        STACK_OF(DIST_POINT)    *dps;
+       X509_STORE_CTX  *verify_ctx = NULL;
+       X509_OBJECT     *xobj;
+       EVP_PKEY        *pkey;
 
        MEM(crl = talloc_zero(inst->mutable->crls, crl_entry_t));
        crl->cdp_url = talloc_bstrdup(crl, url);
@@ -252,10 +260,41 @@ static crl_entry_t *crl_entry_create(rlm_crl_t const *inst, fr_timer_list_t *tl,
                fr_tls_strerror_printf("Failed to parse CRL from %s", url);
        error:
                talloc_free(crl);
+               if (verify_ctx) X509_STORE_CTX_free(verify_ctx);
                return NULL;
        }
        talloc_set_destructor(crl, _crl_entry_free);
 
+       verify_ctx = X509_STORE_CTX_new();
+        if (!verify_ctx || !X509_STORE_CTX_init(verify_ctx, inst->verify_store, NULL, NULL)) {
+               fr_tls_strerror_printf("Error initialising X509 store");
+               goto error;
+        }
+
+        xobj = X509_STORE_CTX_get_obj_by_subject(verify_ctx, X509_LU_X509,
+                                                 X509_CRL_get_issuer(crl->crl));
+        if (!xobj) {
+               fr_tls_strerror_printf("CRL issuer certificate not in trusted store");
+               goto error;
+        }
+        pkey = X509_get_pubkey(X509_OBJECT_get0_X509(xobj));
+        X509_OBJECT_free(xobj);
+        if (!pkey) {
+               fr_tls_strerror_printf("Error getting CRL issuer public key");
+               goto error;
+        }
+        i = X509_CRL_verify(crl->crl, pkey);
+        EVP_PKEY_free(pkey);
+
+       if (i < 0) {
+               fr_tls_strerror_printf("Could not verify CRL signature");
+               goto error;
+       }
+        if (i == 0) {
+               fr_tls_strerror_printf("CRL certificate signature failed");
+               goto error;
+       }
+
        crl->crl_num = X509_CRL_get_ext_d2i(crl->crl, NID_crl_number, &i, NULL);
 
        if (fr_tls_utils_asn1time_to_epoch(&next_update, X509_CRL_get0_nextUpdate(crl->crl)) < 0) {
@@ -304,6 +343,7 @@ static crl_entry_t *crl_entry_create(rlm_crl_t const *inst, fr_timer_list_t *tl,
        DEBUG3("CRL from %s will expire in %pVs", url, fr_box_time_delta(expiry_time));
        fr_timer_in(crl, tl, &crl->ev, expiry_time, false, crl_expire, crl);
 
+       X509_STORE_CTX_free(verify_ctx);
        return crl;
 }
 
@@ -459,6 +499,20 @@ static int mod_instantiate(module_inst_ctx_t const *mctx)
        pthread_mutex_init(&inst->mutable->mutex, NULL);
        talloc_set_destructor(inst->mutable, mod_mutable_free);
 
+       if (!inst->ca_file && !inst->ca_path) {
+               cf_log_err(mctx->mi->conf, "Missing ca_file / ca_path option.  One or other (or both) must be specified.");
+               return -1;
+       }
+
+       inst->verify_store = X509_STORE_new();
+       if (!X509_STORE_load_locations(inst->verify_store, inst->ca_file, inst->ca_path)) {
+               cf_log_err(mctx->mi->conf, "Failed reading Trusted root CA file \"%s\" and path \"%s\"",
+                          inst->ca_file, inst->ca_path);
+               return -1;
+       }
+
+       X509_STORE_set_purpose(inst->verify_store, X509_PURPOSE_SSL_CLIENT);
+
        return 0;
 }
 
@@ -466,6 +520,7 @@ static int mod_detach(module_detach_ctx_t const *mctx)
 {
        rlm_crl_t       *inst = talloc_get_type_abort(mctx->mi->data, rlm_crl_t);
 
+       if (inst->verify_store) X509_STORE_free(inst->verify_store);
        talloc_free(inst->mutable);
        return 0;
 }