From 8bac18155286d31028ff16d4d9e085352f78879a Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Fri, 28 Jun 2024 17:21:40 +0200 Subject: [PATCH] Improve certificates issuance --- central_backend/src/app_config.rs | 15 ++ central_backend/src/crypto/crl_extension.rs | 43 ++++++ central_backend/src/crypto/mod.rs | 2 + central_backend/src/{ => crypto}/pki.rs | 146 ++++++++++---------- central_backend/src/lib.rs | 2 +- central_backend/src/main.rs | 4 +- 6 files changed, 134 insertions(+), 78 deletions(-) create mode 100644 central_backend/src/crypto/crl_extension.rs create mode 100644 central_backend/src/crypto/mod.rs rename central_backend/src/{ => crypto}/pki.rs (56%) diff --git a/central_backend/src/app_config.rs b/central_backend/src/app_config.rs index 5fc3c53..95ebec4 100644 --- a/central_backend/src/app_config.rs +++ b/central_backend/src/app_config.rs @@ -67,6 +67,11 @@ impl AppConfig { self.pki_path().join("root_ca.pem") } + /// Get PKI root CA CRL path + pub fn root_ca_crl_path(&self) -> PathBuf { + self.pki_path().join("root_ca.crl") + } + /// Get PKI root CA private key path pub fn root_ca_priv_key_path(&self) -> PathBuf { self.pki_path().join("root_ca.key") @@ -77,6 +82,11 @@ impl AppConfig { self.pki_path().join("web_ca.pem") } + /// Get PKI web CA CRL path + pub fn web_ca_crl_path(&self) -> PathBuf { + self.pki_path().join("web_ca.crl") + } + /// Get PKI web CA private key path pub fn web_ca_priv_key_path(&self) -> PathBuf { self.pki_path().join("web_ca.key") @@ -87,6 +97,11 @@ impl AppConfig { self.pki_path().join("devices_ca.pem") } + /// Get PKI devices CA CRL path + pub fn devices_ca_crl_path(&self) -> PathBuf { + self.pki_path().join("devices_ca.crl") + } + /// Get PKI devices CA private key path pub fn devices_ca_priv_key_path(&self) -> PathBuf { self.pki_path().join("devices_ca.key") diff --git a/central_backend/src/crypto/crl_extension.rs b/central_backend/src/crypto/crl_extension.rs new file mode 100644 index 0000000..bf1236d --- /dev/null +++ b/central_backend/src/crypto/crl_extension.rs @@ -0,0 +1,43 @@ +use asn1::Tag; +use openssl::asn1::{Asn1Object, Asn1OctetString}; +use openssl::x509::X509Extension; + +pub struct CRLDistributionPointExt { + pub url: String, +} + +impl CRLDistributionPointExt { + pub fn as_extension(&self) -> anyhow::Result { + let crl_obj = Asn1Object::from_str("2.5.29.31")?; + + let tag_a0 = Tag::from_bytes(&[0xa0]).unwrap().0; + let tag_86 = Tag::from_bytes(&[0x86]).unwrap().0; + + let crl_bytes = asn1::write(|w| { + w.write_element(&asn1::SequenceWriter::new(&|w| { + w.write_element(&asn1::SequenceWriter::new(&|w| { + w.write_tlv(tag_a0, |w| { + w.push_slice(&asn1::write(|w| { + w.write_tlv(tag_a0, |w| { + w.push_slice(&asn1::write(|w| { + w.write_tlv(tag_86, |b| b.push_slice(self.url.as_bytes()))?; + Ok(()) + })?) + })?; + + Ok(()) + })?) + })?; + Ok(()) + }))?; + Ok(()) + })) + })?; + + Ok(X509Extension::new_from_der( + crl_obj.as_ref(), + false, + Asn1OctetString::new_from_bytes(&crl_bytes)?.as_ref(), + )?) + } +} diff --git a/central_backend/src/crypto/mod.rs b/central_backend/src/crypto/mod.rs new file mode 100644 index 0000000..b77ad43 --- /dev/null +++ b/central_backend/src/crypto/mod.rs @@ -0,0 +1,2 @@ +pub mod crl_extension; +pub mod pki; diff --git a/central_backend/src/pki.rs b/central_backend/src/crypto/pki.rs similarity index 56% rename from central_backend/src/pki.rs rename to central_backend/src/crypto/pki.rs index aa5cf15..0f36b50 100644 --- a/central_backend/src/pki.rs +++ b/central_backend/src/crypto/pki.rs @@ -1,29 +1,30 @@ use crate::app_config::AppConfig; -use asn1::{ - parse_single, Asn1Readable, Asn1Writable, Explicit, Implicit, OctetStringEncoded, ParseResult, - SimpleAsn1Readable, SimpleAsn1Writable, Tag, WriteBuf, WriteResult, Writer, -}; -use openssl::asn1::{Asn1Object, Asn1OctetString, Asn1OctetStringRef, Asn1Time}; +use crate::crypto::crl_extension::CRLDistributionPointExt; +use openssl::asn1::Asn1Time; use openssl::bn::{BigNum, MsbOption}; use openssl::ec::EcGroup; use openssl::hash::MessageDigest; use openssl::nid::Nid; use openssl::pkey::{PKey, Private}; -use openssl::x509; use openssl::x509::extension::{BasicConstraints, KeyUsage, SubjectKeyIdentifier}; -use openssl::x509::{X509Extension, X509NameBuilder, X509}; -use std::path::Path; +use openssl::x509::{X509NameBuilder, X509}; +use std::path::{Path, PathBuf}; /// Certificate and private key -struct CertAndKey(X509, PKey); +struct CertData { + cert: X509, + key: PKey, + crl: Option, +} -impl CertAndKey { +impl CertData { /// Load root CA fn load_root_ca() -> anyhow::Result { - Ok(Self( - load_certificate_from_file(AppConfig::get().root_ca_cert_path())?, - load_priv_key_from_file(AppConfig::get().root_ca_priv_key_path())?, - )) + Ok(Self { + cert: load_certificate_from_file(AppConfig::get().root_ca_cert_path())?, + key: load_priv_key_from_file(AppConfig::get().root_ca_priv_key_path())?, + crl: Some(AppConfig::get().root_ca_crl_path()), + }) } } @@ -47,17 +48,21 @@ fn load_certificate_from_file>(path: P) -> anyhow::Result { Ok(X509::from_pem(&std::fs::read(path)?)?) } -/// Generate intermediate or root CA -fn gen_intermediate_or_root_ca( - cn: &str, - issuer: Option<&CertAndKey>, -) -> anyhow::Result<(Vec, Vec)> { +#[derive(Default)] +struct GenCertificateReq<'a> { + cn: &'a str, + issuer: Option<&'a CertData>, + ca: bool, +} + +/// Generate certificate +fn gen_certificate(req: GenCertificateReq) -> anyhow::Result<(Vec, Vec)> { // Generate root private key let key_pair = gen_private_key()?; let mut x509_name = X509NameBuilder::new()?; x509_name.append_entry_by_text("C", "FR")?; - x509_name.append_entry_by_text("CN", cn)?; + x509_name.append_entry_by_text("CN", req.cn)?; let x509_name = x509_name.build(); let mut cert_builder = X509::builder()?; @@ -69,77 +74,58 @@ fn gen_intermediate_or_root_ca( }; cert_builder.set_serial_number(&serial_number)?; cert_builder.set_subject_name(&x509_name)?; - match issuer { + + match req.issuer { // Self-signed certificate None => cert_builder.set_issuer_name(&x509_name)?, // Certificate signed by another CA - Some(i) => cert_builder.set_issuer_name(i.0.issuer_name())?, + Some(i) => cert_builder.set_issuer_name(i.cert.issuer_name())?, } + cert_builder.set_pubkey(&key_pair)?; + let not_before = Asn1Time::days_from_now(0)?; cert_builder.set_not_before(¬_before)?; + let not_after = Asn1Time::days_from_now(365 * 30)?; cert_builder.set_not_after(¬_after)?; - if let Some(issuer) = issuer { - let crl_url = format!( - "{}/crl/{}.crl", - AppConfig::get().unsecure_origin(), - "FIXME_TODO" - ); - - let crl_obj = Asn1Object::from_str("2.5.29.31")?; + // Specify CRL URL + if let Some(issuer) = req.issuer { + if let Some(crl) = &issuer.crl { + let crl_url = format!( + "{}/crl/{}", + AppConfig::get().unsecure_origin(), + crl.file_name().unwrap().to_string_lossy() + ); - let TAG_A0 = Tag::from_bytes(&[0xa0]).unwrap().0; - let TAG_86 = Tag::from_bytes(&[0x86]).unwrap().0; - - let crl_bytes = asn1::write(|w| { - w.write_element(&asn1::SequenceWriter::new(&|w| { - w.write_element(&asn1::SequenceWriter::new(&|w| { - w.write_tlv(TAG_A0, |w| { - w.push_slice(&asn1::write(|w| { - w.write_tlv(TAG_A0, |w| { - w.push_slice(&asn1::write(|w| { - w.write_tlv(TAG_86, |b| b.push_slice(crl_url.as_bytes()))?; - Ok(()) - })?) - })?; - - Ok(()) - })?) - })?; - Ok(()) - }))?; - Ok(()) - })) - })?; - - cert_builder.append_extension(X509Extension::new_from_der( - crl_obj.as_ref(), - false, - Asn1OctetString::new_from_bytes(&crl_bytes)?.as_ref(), - )?)?; + cert_builder + .append_extension(CRLDistributionPointExt { url: crl_url }.as_extension()?)?; + } } - cert_builder.append_extension(BasicConstraints::new().critical().ca().build()?)?; + // If cert is a CA or not + if req.ca { + cert_builder.append_extension(BasicConstraints::new().critical().ca().build()?)?; + } - cert_builder.append_extension( - KeyUsage::new() - .critical() - .key_cert_sign() - .crl_sign() - .build()?, - )?; + // Key usage + let mut key_usage = KeyUsage::new(); + if req.ca { + key_usage.key_cert_sign().crl_sign(); + } + cert_builder.append_extension(key_usage.critical().build()?)?; + // Subject key identifier let subject_key_identifier = SubjectKeyIdentifier::new().build(&cert_builder.x509v3_context(None, None))?; - cert_builder.append_extension(subject_key_identifier)?; + // Sign certificate cert_builder.sign( - match issuer { + match req.issuer { None => &key_pair, - Some(i) => &i.1, + Some(i) => &i.key, }, MessageDigest::sha256(), )?; @@ -158,7 +144,11 @@ pub fn initialize_root_ca() -> anyhow::Result<()> { log::info!("Generating root ca..."); - let (key, cert) = gen_intermediate_or_root_ca("SolarEnergy Root CA", None)?; + let (key, cert) = gen_certificate(GenCertificateReq { + cn: "SolarEnergy Root CA", + issuer: None, + ca: true, + })?; // Serialize generated web CA std::fs::write(AppConfig::get().root_ca_priv_key_path(), key)?; @@ -177,8 +167,11 @@ pub fn initialize_web_ca() -> anyhow::Result<()> { log::info!("Generating web ca..."); - let (key, cert) = - gen_intermediate_or_root_ca("SolarEnergy Web CA", Some(&CertAndKey::load_root_ca()?))?; + let (key, cert) = gen_certificate(GenCertificateReq { + cn: "SolarEnergy Web CA", + issuer: Some(&CertData::load_root_ca()?), + ca: true, + })?; // Serialize generated web CA std::fs::write(AppConfig::get().web_ca_priv_key_path(), key)?; @@ -197,8 +190,11 @@ pub fn initialize_devices_ca() -> anyhow::Result<()> { log::info!("Generating devices ca..."); - let (key, cert) = - gen_intermediate_or_root_ca("SolarEnergy Devices CA", Some(&CertAndKey::load_root_ca()?))?; + let (key, cert) = gen_certificate(GenCertificateReq { + cn: "SolarEnergy Devices CA", + issuer: Some(&CertData::load_root_ca()?), + ca: true, + })?; // Serialize generated devices CA std::fs::write(AppConfig::get().devices_ca_priv_key_path(), key)?; diff --git a/central_backend/src/lib.rs b/central_backend/src/lib.rs index bb4e167..f9c77f4 100644 --- a/central_backend/src/lib.rs +++ b/central_backend/src/lib.rs @@ -1,3 +1,3 @@ pub mod app_config; -pub mod pki; +pub mod crypto; pub mod utils; diff --git a/central_backend/src/main.rs b/central_backend/src/main.rs index a4a03e9..ced4092 100644 --- a/central_backend/src/main.rs +++ b/central_backend/src/main.rs @@ -1,12 +1,12 @@ use central_backend::app_config::AppConfig; -use central_backend::pki; +use central_backend::crypto::pki; use central_backend::utils::files_utils::create_directory_if_missing; fn main() { env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); // Initialize storage - create_directory_if_missing(&AppConfig::get().pki_path()).unwrap(); + create_directory_if_missing(AppConfig::get().pki_path()).unwrap(); // Initialize PKI pki::initialize_root_ca().expect("Failed to initialize Root CA!");