diff --git a/central_backend/src/app_config.rs b/central_backend/src/app_config.rs index 95ebec4..089dd82 100644 --- a/central_backend/src/app_config.rs +++ b/central_backend/src/app_config.rs @@ -7,15 +7,15 @@ use std::path::{Path, PathBuf}; pub struct AppConfig { /// The port the server will listen to (using HTTPS) #[arg(short, long, env, default_value = "0.0.0.0:8443")] - listen_address: String, + pub listen_address: String, /// The port the server will listen to (using HTTP, for unsecure connections) #[arg(short, long, env, default_value = "0.0.0.0:8080")] - unsecure_listen_address: String, + pub unsecure_listen_address: String, /// Public server hostname (assuming that the ports used are the same for listen address) #[arg(short('H'), long, env, default_value = "localhost")] - hostname: String, + pub hostname: String, /// Server storage path #[arg(short, long, env, default_value = "storage")] @@ -106,6 +106,16 @@ impl AppConfig { pub fn devices_ca_priv_key_path(&self) -> PathBuf { self.pki_path().join("devices_ca.key") } + + /// Get PKI server cert path + pub fn server_cert_path(&self) -> PathBuf { + self.pki_path().join("server.pem") + } + + /// Get PKI server private key path + pub fn server_priv_key_path(&self) -> PathBuf { + self.pki_path().join("server.key") + } } #[cfg(test)] diff --git a/central_backend/src/crypto/pki.rs b/central_backend/src/crypto/pki.rs index 06b1d99..46f5462 100644 --- a/central_backend/src/crypto/pki.rs +++ b/central_backend/src/crypto/pki.rs @@ -10,7 +10,9 @@ use openssl::ec::EcGroup; use openssl::hash::MessageDigest; use openssl::nid::Nid; use openssl::pkey::{PKey, Private}; -use openssl::x509::extension::{BasicConstraints, KeyUsage, SubjectKeyIdentifier}; +use openssl::x509::extension::{ + BasicConstraints, ExtendedKeyUsage, KeyUsage, SubjectAlternativeName, SubjectKeyIdentifier, +}; use openssl::x509::{X509Crl, X509NameBuilder, X509}; use openssl_sys::{ X509_CRL_add0_revoked, X509_CRL_set1_lastUpdate, X509_CRL_set1_nextUpdate, @@ -31,7 +33,7 @@ pub enum PKIError { } /// Certificate and private key -struct CertData { +pub struct CertData { cert: X509, key: PKey, crl: Option, @@ -64,6 +66,15 @@ impl CertData { crl: Some(AppConfig::get().devices_ca_crl_path()), }) } + + /// Load server CA + pub fn load_server() -> anyhow::Result { + Ok(Self { + cert: load_certificate_from_file(AppConfig::get().server_cert_path())?, + key: load_priv_key_from_file(AppConfig::get().server_priv_key_path())?, + crl: None, + }) + } } /// Generate private key @@ -96,6 +107,8 @@ struct GenCertificateReq<'a> { cn: &'a str, issuer: Option<&'a CertData>, ca: bool, + web_server: bool, + subject_alternative_names: Vec<&'a str>, } /// Generate certificate @@ -122,7 +135,7 @@ fn gen_certificate(req: GenCertificateReq) -> anyhow::Result<(Vec, Vec)> // Self-signed certificate None => cert_builder.set_issuer_name(&x509_name)?, // Certificate signed by another CA - Some(i) => cert_builder.set_issuer_name(i.cert.issuer_name())?, + Some(i) => cert_builder.set_issuer_name(i.cert.subject_name())?, } cert_builder.set_pubkey(&key_pair)?; @@ -154,11 +167,34 @@ fn gen_certificate(req: GenCertificateReq) -> anyhow::Result<(Vec, Vec)> // Key usage let mut key_usage = KeyUsage::new(); + let mut eku = None; if req.ca { key_usage.key_cert_sign().crl_sign(); } + if req.web_server { + key_usage.digital_signature().key_encipherment(); + eku = Some( + ExtendedKeyUsage::new() + .server_auth() + .client_auth() + .build()?, + ); + } cert_builder.append_extension(key_usage.critical().build()?)?; + if let Some(eku) = eku { + cert_builder.append_extension(eku)?; + } + + // Subject alternative names + if !req.subject_alternative_names.is_empty() { + let mut ext = SubjectAlternativeName::new(); + for subj in req.subject_alternative_names { + ext.dns(subj); + } + cert_builder.append_extension(ext.build(&cert_builder.x509v3_context(None, None))?)?; + } + // Subject key identifier let subject_key_identifier = SubjectKeyIdentifier::new().build(&cert_builder.x509v3_context(None, None))?; @@ -191,6 +227,7 @@ pub fn initialize_root_ca() -> anyhow::Result<()> { cn: "SolarEnergy Root CA", issuer: None, ca: true, + ..Default::default() })?; // Serialize generated web CA @@ -214,6 +251,7 @@ pub fn initialize_web_ca() -> anyhow::Result<()> { cn: "SolarEnergy Web CA", issuer: Some(&CertData::load_root_ca()?), ca: true, + ..Default::default() })?; // Serialize generated web CA @@ -237,6 +275,7 @@ pub fn initialize_devices_ca() -> anyhow::Result<()> { cn: "SolarEnergy Devices CA", issuer: Some(&CertData::load_root_ca()?), ca: true, + ..Default::default() })?; // Serialize generated devices CA @@ -246,7 +285,31 @@ pub fn initialize_devices_ca() -> anyhow::Result<()> { Ok(()) } -/// Intialize or refresh a CRL +/// Initialize server certificate, if required +pub fn initialize_server_ca() -> anyhow::Result<()> { + if AppConfig::get().server_cert_path().exists() + && AppConfig::get().server_priv_key_path().exists() + { + return Ok(()); + } + + log::info!("Generating server certificate..."); + + let (key, cert) = gen_certificate(GenCertificateReq { + cn: &AppConfig::get().hostname, + issuer: Some(&CertData::load_web_ca()?), + web_server: true, + subject_alternative_names: vec![AppConfig::get().hostname.as_str()], + ..Default::default() + })?; + + std::fs::write(AppConfig::get().server_priv_key_path(), key)?; + std::fs::write(AppConfig::get().server_cert_path(), cert)?; + + Ok(()) +} + +/// Initialize or refresh a CRL fn refresh_crl(d: &CertData) -> anyhow::Result<()> { let crl_path = d.crl.as_ref().ok_or(PKIError::MissingCRL)?; diff --git a/central_backend/src/main.rs b/central_backend/src/main.rs index 08b91ff..cc69a15 100644 --- a/central_backend/src/main.rs +++ b/central_backend/src/main.rs @@ -15,6 +15,7 @@ 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_server_ca().expect("Failed to initialize server certificate!"); pki::refresh_crls().expect("Failed to initialize Root CA!"); }