Improve certificates issuance
This commit is contained in:
		| @@ -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") | ||||
|   | ||||
							
								
								
									
										43
									
								
								central_backend/src/crypto/crl_extension.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								central_backend/src/crypto/crl_extension.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -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<X509Extension> { | ||||
|         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(), | ||||
|         )?) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										2
									
								
								central_backend/src/crypto/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								central_backend/src/crypto/mod.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| pub mod crl_extension; | ||||
| pub mod pki; | ||||
| @@ -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<Private>); | ||||
| struct CertData { | ||||
|     cert: X509, | ||||
|     key: PKey<Private>, | ||||
|     crl: Option<PathBuf>, | ||||
| } | ||||
| 
 | ||||
| impl CertAndKey { | ||||
| impl CertData { | ||||
|     /// Load root CA
 | ||||
|     fn load_root_ca() -> anyhow::Result<Self> { | ||||
|         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<P: AsRef<Path>>(path: P) -> anyhow::Result<X509> { | ||||
|     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<u8>, Vec<u8>)> { | ||||
| #[derive(Default)] | ||||
| struct GenCertificateReq<'a> { | ||||
|     cn: &'a str, | ||||
|     issuer: Option<&'a CertData>, | ||||
|     ca: bool, | ||||
| } | ||||
| 
 | ||||
| /// Generate certificate
 | ||||
| fn gen_certificate(req: GenCertificateReq) -> anyhow::Result<(Vec<u8>, Vec<u8>)> { | ||||
|     // 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)?; | ||||
| @@ -1,3 +1,3 @@ | ||||
| pub mod app_config; | ||||
| pub mod pki; | ||||
| pub mod crypto; | ||||
| pub mod utils; | ||||
|   | ||||
| @@ -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!"); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user