diff --git a/include/secutils/certstatus/certstatus.h b/include/secutils/certstatus/certstatus.h index eb7948d..fc0da84 100644 --- a/include/secutils/certstatus/certstatus.h +++ b/include/secutils/certstatus/certstatus.h @@ -132,4 +132,12 @@ bool check_cert_revocation(X509_STORE_CTX* ctx, OPTIONAL OCSP_RESPONSE* resp); */ int check_revocation_any_method(X509_STORE_CTX* ctx); +/* + * Check revocation status on certs in ctx->chain. As a generalization of + * check_revocation() in crypto/x509/x509_vfy.c, considers local CRLs only. + * To be used as a callback function to be past to + * X509_STORE_set_check_revocation() + */ +int check_revocation_local_only_method(X509_STORE_CTX* ctx); + #endif /* SECUTILS_CERTSTATUS_H_ */ diff --git a/include/secutils/credentials/store.h b/include/secutils/credentials/store.h index 5d707c5..472c31d 100644 --- a/include/secutils/credentials/store.h +++ b/include/secutils/credentials/store.h @@ -208,6 +208,7 @@ bool STORE_load_check_dir(X509_STORE** pstore, const char* trust_dir, /*!***************************************************************************** * @brief search for files with CRLs in specified directory and add them to X509_STORE + * @note this function sets check_revocation_any_method() to be used as a callback during CRL validation * * @param pstore pointer to trust store to be augmented with CRLs. * CRL-based status checking will be enabled in it for the full certificate chain. @@ -220,6 +221,22 @@ bool STORE_load_check_dir(X509_STORE** pstore, const char* trust_dir, ******************************************************************************/ bool STORE_load_crl_dir(X509_STORE* pstore, const char* crl_dir, OPTIONAL const char* desc, bool recursive, OPTIONAL uta_ctx* ctx); +/*!***************************************************************************** + * @brief search for files with CRLs in specified directory and add them to X509_STORE + * @note this function sets check_revocation_local_only_method() to be used as a callback during CRL validation + * + * @param pstore pointer to trust store to be augmented with CRLs. + * CRL-based status checking will be enabled in it for the full certificate chain. + * @param crl_dir directory where to search for CRLs + * @param desc description of CRLs to use for error reporting, or null + * @param recursive if true, use recursive search in subdirectories + * @param ctx pointer to UTA context for checking file integrity&authenticity using ICV, or null + * @note at least one valid CRL file must be found in each visited directory + * @return true on success, false on error/failure + ******************************************************************************/ +bool STORE_load_crl_dir_local_only(X509_STORE* pstore, const char* crl_dir, OPTIONAL const char* desc, bool recursive, + OPTIONAL uta_ctx* ctx); + /*!***************************************************************************** * @brief release a trust store * diff --git a/src/certstatus/certstatus.c b/src/certstatus/certstatus.c index 5ee6c09..c46356a 100644 --- a/src/certstatus/certstatus.c +++ b/src/certstatus/certstatus.c @@ -30,7 +30,7 @@ #include -static unsigned int num_CDPs(const X509* cert) +static int num_CDPs(const X509* cert) { CRL_DIST_POINTS* cdps = X509_get_ext_d2i(cert, NID_crl_distribution_points, 0, 0); if(cdps is_eq 0) /* maybe there is still a CDP for delta CRLs */ @@ -42,7 +42,7 @@ static unsigned int num_CDPs(const X509* cert) return res; } -static unsigned int num_AIAs(const X509* cert) +static int num_AIAs(const X509* cert) { STACK_OF(OPENSSL_STRING) *aias = X509_get1_ocsp((X509 *)cert); int res = aias not_eq 0 ? sk_OPENSSL_STRING_num(aias) : 0; @@ -416,3 +416,159 @@ int check_revocation_any_method(X509_STORE_CTX* ctx) } return true; } + + +/* + * @brief Function to check certificate revocation of a certificate and its + * issuer at depth determined by X509_STORE_CTX error_depth + * @param ctx pointer to X509 store context + * @return X509_V_OK (0) on success + * X509_V_ERR_ (>0) error code on failure + */ +static int check_local_cert_crls(X509_STORE_CTX* ctx) +{ + if(0 is_eq ctx) + { + LOG(FL_ERR, "null parameter ctx"); + return X509_V_ERR_UNSPECIFIED; + } + + X509_STORE_CTX* tmp_ctx = X509_STORE_CTX_new(); + X509_STORE_CTX_check_revocation_fn check_revocation; + int cert_idx = X509_STORE_CTX_get_error_depth(ctx); + X509* cert = sk_X509_value(X509_STORE_CTX_get0_chain(ctx), cert_idx); + X509* issuer = sk_X509_value(X509_STORE_CTX_get0_chain(ctx), cert_idx+1); + int ssl_ex_idx = SSL_get_ex_data_X509_STORE_CTX_idx(); + SSL* ssl = X509_STORE_CTX_get_ex_data(ctx, ssl_ex_idx); + STACK_OF(X509) *certs; + int ret = X509_V_ERR_UNSPECIFIED; + + /* + * Unfortunately, check_revocation() in crypto/x509/x509_vfy.c is static, + * yet we can get hold of it via X509_STORE_CTX_get_check_revocation(). + */ + if(tmp_ctx is_eq 0 + or not X509_STORE_CTX_init(tmp_ctx, 0, 0, 0) + or ((check_revocation = X509_STORE_CTX_get_check_revocation(tmp_ctx)) is_eq 0)) + { + LOG(FL_ERR, "cannot get pointer to check_revocation()"); + goto err; + } + X509_STORE_CTX_set0_param(tmp_ctx, 0); /* free tmp_ctx->param */ + if(not X509_STORE_CTX_init(tmp_ctx, X509_STORE_CTX_get0_store(ctx), + 0, 0) /* inherits flags etc. of store */ + or not X509_STORE_CTX_set_ex_data(tmp_ctx, ssl_ex_idx, ssl)) + { + LOG(FL_ERR, "cannot set up tmp_ctx"); + goto err; + } + if((certs = sk_X509_new_reserve(0, 2)) is_eq 0 + or (X509_STORE_CTX_set0_verified_chain(tmp_ctx, certs), 0) + or cert is_eq 0 or not sk_X509_push(certs, X509_dup(cert)) + or issuer is_eq 0 or not sk_X509_push(certs, X509_dup(issuer))) + { + LOG(FL_ERR, "cannot set certs in tmp_ctx"); + goto err; + } + + X509_VERIFY_PARAM* tmp_vpm = X509_STORE_CTX_get0_param(tmp_ctx); + X509_VERIFY_PARAM_clear_flags(tmp_vpm, X509_V_FLAG_CRL_CHECK_ALL); + X509_VERIFY_PARAM_set_flags(tmp_vpm, X509_V_FLAG_NONFINAL_CHECK | X509_V_FLAG_NO_CHECK_TIME); + int res = (*check_revocation)(tmp_ctx); /* checks only depth 0 */ + + if(res is_eq 0) + { + ret = X509_STORE_CTX_get_error(tmp_ctx); + } + else + { + ret = X509_V_OK; + } + + err: + X509_STORE_CTX_free(tmp_ctx); + return ret; +} + +static bool crl_time_valid(const X509_CRL* crl, const X509_VERIFY_PARAM* vpm) +{ + time_t check_time, *ptime = 0; + const ASN1_TIME* crl_end_time; + const ASN1_TIME* crl_last_update; + unsigned long flags = X509_VERIFY_PARAM_get_flags((X509_VERIFY_PARAM*)vpm); + + if((flags bitand X509_V_FLAG_USE_CHECK_TIME) not_eq 0) + { + check_time = X509_VERIFY_PARAM_get_time(vpm); + ptime = &check_time; + } + crl_end_time = X509_CRL_get0_nextUpdate(crl); + crl_last_update = X509_CRL_get0_lastUpdate(crl); + + if ((crl_end_time not_eq 0 and X509_cmp_time(crl_end_time, ptime) not_eq 1) or + (crl_last_update not_eq 0 and X509_cmp_time(crl_last_update, ptime) not_eq -1)) + { + return false; + } + + return true; +} + +int check_revocation_local_only_method(X509_STORE_CTX* ctx) +{ + if(0 is_eq ctx) + { + LOG(FL_ERR, "null parameter ctx"); + return 0; + } + + STACK_OF(X509) *chain = X509_STORE_CTX_get0_chain(ctx); + int i = 0; + const int last = sk_X509_num(chain) - 1; + + for(i = 0; i <= last; i++) + { + X509* cert = sk_X509_value(chain, i); + if(i is_eq last and X509_check_issued(cert, cert) is_eq X509_V_OK) + { + LOG_cert(FL_DEBUG, "skipping revocation check for self-issued last", cert); + break; + } + + STACK_OF(X509_CRL)* crls = X509_STORE_CTX_get1_crls(ctx, X509_get_issuer_name(cert)); + if(not crls or 0 is_eq sk_X509_CRL_num(crls) or 0 is_eq sk_X509_CRL_value(crls, 0)) + { + char* issuer = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0); + if (0 not_eq issuer) + { + LOG(FL_ERR, "Local CRL for %s certificate %s not found", + (i + 1 is_eq last) ? "root" : "issuing", issuer); + OPENSSL_free(issuer); + } + return 0; + } + + if(true not_eq crl_time_valid(sk_X509_CRL_value(crls, 0), X509_STORE_CTX_get0_param(ctx))) + { + char* issuer = X509_NAME_oneline(X509_CRL_get_issuer(sk_X509_CRL_value(crls, 0)), 0, 0); + if(issuer not_eq 0) + { + LOG(FL_WARN, "CRL issued by %s is not valid in time", issuer); + OPENSSL_free(issuer); + } + } + + sk_X509_CRL_pop_free(crls, X509_CRL_free); + + X509_STORE_CTX_set_error_depth(ctx, i); + int res = check_local_cert_crls(ctx); + + if(X509_V_OK not_eq res) + { + verify_cb_cert(ctx, cert, res); + return 0; + } + chain = X509_STORE_CTX_get0_chain(ctx); /* for some reason need again */ + } + return 1; +} diff --git a/src/credentials/store.c b/src/credentials/store.c index 328ad07..4282d0f 100644 --- a/src/credentials/store.c +++ b/src/credentials/store.c @@ -304,8 +304,9 @@ bool STORE_load_check_dir(X509_STORE** pstore, const char* trust_dir, OPTIONAL const char* desc, bool recursive, OPTIONAL X509_VERIFY_PARAM *vpm, OPTIONAL uta_ctx* ctx) { - DIR* p_dir = 0; bool found = false; + int count = -1; + int i; if(0 is_eq pstore or 0 is_eq trust_dir) { @@ -313,16 +314,18 @@ bool STORE_load_check_dir(X509_STORE** pstore, const char* trust_dir, goto err; } - p_dir = opendir(trust_dir); - if(0 is_eq p_dir) + struct dirent** namelist = 0; + count = scandir(trust_dir, &namelist, 0, alphasort); + if(-1 is_eq count) { LOG(FL_ERR, "cannot read directory '%s'", trust_dir); goto err; } - struct dirent* p_dirent = readdir(p_dir); - while(0 not_eq p_dirent) + for(i = 0; i < count; ++i) { + const struct dirent* p_dirent = namelist[i]; + char full_path[UTIL_max_path_len + 1]; snprintf(full_path, sizeof(full_path), "%s/%s", trust_dir, p_dirent->d_name); @@ -330,7 +333,7 @@ bool STORE_load_check_dir(X509_STORE** pstore, const char* trust_dir, memset(&f_stat, 0x00, sizeof(struct stat)); if(-1 is_eq stat(full_path, &f_stat)) { - LOG(FL_INFO, "cannot read status of %s - %s", full_path, strerror(errno)); + LOG(FL_ERR, "cannot read status of %s - %s", full_path, strerror(errno)); } else { @@ -350,7 +353,6 @@ bool STORE_load_check_dir(X509_STORE** pstore, const char* trust_dir, } } } - p_dirent = readdir(p_dir); } if(not found) @@ -361,9 +363,13 @@ bool STORE_load_check_dir(X509_STORE** pstore, const char* trust_dir, desc not_eq 0 ? desc : "trusted certs", ctx not_eq 0 ? "with valid ICV " : "", trust_dir); } err: - if(p_dir not_eq 0) + for(i = 0; i < count; ++i) { - closedir(p_dir); + free(namelist[i]); + } + if (-1 not_eq count) + { + free(namelist); } int cert_num = STORE_certs_num(*pstore); @@ -375,10 +381,13 @@ bool STORE_load_check_dir(X509_STORE** pstore, const char* trust_dir, return found; } -bool STORE_load_crl_dir(X509_STORE* pstore, const char* crl_dir, OPTIONAL const char* desc, bool recursive, OPTIONAL uta_ctx* ctx) + +static bool STORE_load_crl_dir_impl(X509_STORE* pstore, const char* crl_dir, OPTIONAL const char* desc, bool recursive, + OPTIONAL uta_ctx* ctx, int (*callback)(X509_STORE_CTX*)) { - DIR* p_dir = 0; bool found = false; + int count = -1; + int i; if(0 is_eq pstore or 0 is_eq crl_dir) { @@ -386,16 +395,18 @@ bool STORE_load_crl_dir(X509_STORE* pstore, const char* crl_dir, OPTIONAL const goto err; } - p_dir = opendir(crl_dir); - if(0 is_eq p_dir) + struct dirent** namelist = 0; + count = scandir(crl_dir, &namelist, 0, alphasort); + if(-1 is_eq count) { LOG(FL_ERR, "cannot access directory '%s'", crl_dir); goto err; } - struct dirent* p_dirent = readdir(p_dir); - while(0 not_eq p_dirent) + for(i = 0; i < count; ++i) { + const struct dirent* p_dirent = namelist[i]; + char full_path[UTIL_max_path_len + 1]; snprintf(full_path, sizeof(full_path), "%s/%s", crl_dir, p_dirent->d_name); @@ -403,7 +414,7 @@ bool STORE_load_crl_dir(X509_STORE* pstore, const char* crl_dir, OPTIONAL const memset(&f_stat, 0x00, sizeof(struct stat)); if(-1 is_eq stat(full_path, &f_stat)) { - LOG(FL_INFO, "cannot read status of %s - %s", full_path, strerror(errno)); + LOG(FL_ERR, "cannot read status of %s - %s", full_path, strerror(errno)); } else { @@ -430,32 +441,49 @@ bool STORE_load_crl_dir(X509_STORE* pstore, const char* crl_dir, OPTIONAL const } else if(recursive and (f_stat.st_mode bitand S_IFDIR) and (0 not_eq strncmp(p_dirent->d_name, ".", 1))) { - if(not STORE_load_crl_dir(pstore, full_path, desc, recursive, ctx)) + if(not STORE_load_crl_dir_impl(pstore, full_path, desc, recursive, ctx, callback)) { found = false; goto err; } } } - p_dirent = readdir(p_dir); } // set up cert CRL check callback for checking full chain if(found) { X509_VERIFY_PARAM_set_flags(X509_STORE_get0_param(pstore), X509_V_FLAG_STATUS_CHECK_ALL); - X509_STORE_set_check_revocation(pstore, &check_revocation_any_method); + X509_STORE_set_check_revocation(pstore, callback); } err: - if(p_dir not_eq 0) + for(i = 0; i < count; ++i) + { + free(namelist[i]); + } + if (-1 not_eq count) { - closedir(p_dir); + free(namelist); } + return found; } +bool STORE_load_crl_dir(X509_STORE* pstore, const char* crl_dir, OPTIONAL const char* desc, bool recursive, OPTIONAL uta_ctx* ctx) +{ + return STORE_load_crl_dir_impl(pstore, crl_dir, desc, recursive, ctx, &check_revocation_any_method); +} + + +bool STORE_load_crl_dir_local_only(X509_STORE* pstore, const char* crl_dir, OPTIONAL const char* desc, bool recursive, + OPTIONAL uta_ctx* ctx) +{ + return STORE_load_crl_dir_impl(pstore, crl_dir, desc, recursive, ctx, &check_revocation_local_only_method); +} + + bool STORE_set1_host_ip(X509_STORE* ts, const char* name, const char* ip) { if(ts is_eq 0)