diff --git a/central_backend/Cargo.lock b/central_backend/Cargo.lock index 9c35800..5fa1ae1 100644 --- a/central_backend/Cargo.lock +++ b/central_backend/Cargo.lock @@ -106,9 +106,12 @@ dependencies = [ "asn1", "clap", "env_logger", + "foreign-types-shared", "lazy_static", + "libc", "log", "openssl", + "openssl-sys", "thiserror", ] diff --git a/central_backend/Cargo.toml b/central_backend/Cargo.toml index d3646fe..b80489c 100644 --- a/central_backend/Cargo.toml +++ b/central_backend/Cargo.toml @@ -11,4 +11,7 @@ clap = { version = "4.5.7", features = ["derive", "env"] } anyhow = "1.0.86" thiserror = "1.0.61" openssl = { version = "0.10.64" } +openssl-sys = "0.9.102" +libc = "0.2.155" +foreign-types-shared = "0.1.1" asn1 = "0.16" \ No newline at end of file diff --git a/central_backend/src/crypto/mod.rs b/central_backend/src/crypto/mod.rs index b77ad43..851cea5 100644 --- a/central_backend/src/crypto/mod.rs +++ b/central_backend/src/crypto/mod.rs @@ -1,2 +1,3 @@ pub mod crl_extension; +pub mod openssl_utils; pub mod pki; diff --git a/central_backend/src/crypto/openssl_utils.rs b/central_backend/src/crypto/openssl_utils.rs new file mode 100644 index 0000000..469066b --- /dev/null +++ b/central_backend/src/crypto/openssl_utils.rs @@ -0,0 +1,24 @@ +use openssl::asn1::{Asn1Time, Asn1TimeRef}; + +/// Clone Asn1 time +pub fn clone_asn1_time(time: &Asn1TimeRef) -> anyhow::Result { + let diff = time.diff(Asn1Time::from_unix(0)?.as_ref())?; + let days = diff.days.abs(); + let secs = diff.secs.abs(); + + Ok(Asn1Time::from_unix((days * 3600 * 24 + secs) as i64)?) +} + +#[cfg(test)] +mod test { + use crate::crypto::openssl_utils::clone_asn1_time; + use openssl::asn1::Asn1Time; + use std::cmp::Ordering; + + #[test] + fn test_clone_asn1_time() { + let a = Asn1Time::from_unix(10).unwrap(); + let b = clone_asn1_time(a.as_ref()).unwrap(); + assert_eq!(a.compare(&b).unwrap(), Ordering::Equal); + } +} diff --git a/central_backend/src/crypto/pki.rs b/central_backend/src/crypto/pki.rs index 0f36b50..c141f7e 100644 --- a/central_backend/src/crypto/pki.rs +++ b/central_backend/src/crypto/pki.rs @@ -1,5 +1,9 @@ -use crate::app_config::AppConfig; -use crate::crypto::crl_extension::CRLDistributionPointExt; +use std::cmp::Ordering; +use std::path::{Path, PathBuf}; + +use foreign_types_shared::ForeignType; +use foreign_types_shared::ForeignTypeRef; +use libc::c_long; use openssl::asn1::Asn1Time; use openssl::bn::{BigNum, MsbOption}; use openssl::ec::EcGroup; @@ -7,8 +11,22 @@ use openssl::hash::MessageDigest; use openssl::nid::Nid; use openssl::pkey::{PKey, Private}; use openssl::x509::extension::{BasicConstraints, KeyUsage, SubjectKeyIdentifier}; -use openssl::x509::{X509NameBuilder, X509}; -use std::path::{Path, PathBuf}; +use openssl::x509::{ReasonCode, X509Crl, X509NameBuilder, X509}; +use openssl_sys::{X509_CRL_free, X509_CRL_set1_lastUpdate, X509_CRL_set1_nextUpdate, X509_CRL_set_issuer_name, X509_CRL_set_version}; + +use crate::app_config::AppConfig; +use crate::crypto::crl_extension::CRLDistributionPointExt; +use crate::crypto::openssl_utils::clone_asn1_time; + +#[derive(thiserror::Error, Debug)] +pub enum PKIError { + #[error("Certification Authority does not have a CRL")] + MissingCRL, + #[error("Certification Authority does not have a CRL next update time")] + MissingCRLNextUpdate, + #[error("Failed to initialize CRL! {0}")] + GenCRLError(&'static str), +} /// Certificate and private key struct CertData { @@ -48,6 +66,11 @@ fn load_certificate_from_file>(path: P) -> anyhow::Result { Ok(X509::from_pem(&std::fs::read(path)?)?) } +/// Load CRL from PEM file +fn load_crl_from_file>(path: P) -> anyhow::Result { + Ok(X509Crl::from_pem(&std::fs::read(path)?)?) +} + #[derive(Default)] struct GenCertificateReq<'a> { cn: &'a str, @@ -202,3 +225,72 @@ pub fn initialize_devices_ca() -> anyhow::Result<()> { Ok(()) } + +/// Intialize or refresh a CRL +fn refresh_crl(d: &CertData) -> anyhow::Result<()> { + let crl_path = d.crl.as_ref().ok_or(PKIError::MissingCRL)?; + + let old_list = if crl_path.exists() { + let crl = load_crl_from_file(crl_path)?; + + // Check if revocation is un-needed + let next_update = crl.next_update().ok_or(PKIError::MissingCRLNextUpdate)?; + if next_update.compare(Asn1Time::days_from_now(0)?.as_ref())? < Ordering::Greater { + return Ok(()); + } + + match crl.get_revoked() { + Some(l) => Some( + l.iter() + .map(|r| { + Ok(( + r.serial_number().to_owned()?, + clone_asn1_time(r.revocation_date())?, + r.extension::()?, + )) + }) + .collect::>>()?, + ), + None => None, + } + } else { + None + }; + + log::info!("Generating a new CRL..."); + + // based on https://github.com/openssl/openssl/blob/master/crypto/x509/x509_vfy.c + unsafe { + let crl = openssl_sys::X509_CRL_new(); + if crl.is_null() { + return Err(PKIError::GenCRLError("Could not construct CRL!").into()); + } + + const X509_CRL_VERSION_2: c_long = 1; + if X509_CRL_set_version(crl, X509_CRL_VERSION_2) == 0 { + return Err(PKIError::GenCRLError("X509_CRL_set_version").into()); + } + if X509_CRL_set_issuer_name(crl, d.cert.issuer_name().as_ptr()) == 0 { + return Err(PKIError::GenCRLError("X509_CRL_set_issuer_name").into()); + } + + let last_update = Asn1Time::days_from_now(0)?; + if X509_CRL_set1_lastUpdate(crl, last_update.as_ptr()) == 0 { + return Err(PKIError::GenCRLError("X509_CRL_set1_lastUpdate").into()); + } + + let next_update = Asn1Time::days_from_now(10)?; + if X509_CRL_set1_nextUpdate(crl, next_update.as_ptr()) == 0 { + return Err(PKIError::GenCRLError("X509_CRL_set1_nextUpdate").into()); + } + + X509_CRL_free(crl); + } + + Ok(()) +} + +/// Initialize or refresh Root CA CRL, if needed +pub fn initialize_root_ca_crl() -> anyhow::Result<()> { + refresh_crl(&CertData::load_root_ca()?) +} diff --git a/central_backend/src/main.rs b/central_backend/src/main.rs index ced4092..c5cf5ed 100644 --- a/central_backend/src/main.rs +++ b/central_backend/src/main.rs @@ -3,6 +3,9 @@ use central_backend::crypto::pki; use central_backend::utils::files_utils::create_directory_if_missing; fn main() { + // Initialize OpenSSL + openssl_sys::init(); + env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); // Initialize storage @@ -12,4 +15,6 @@ fn main() { pki::initialize_root_ca().expect("Failed to initialize Root CA!"); pki::initialize_web_ca().expect("Failed to initialize web CA!"); pki::initialize_devices_ca().expect("Failed to initialize devices CA!"); + + pki::initialize_root_ca_crl().expect("Failed to initialize Root CA!"); }