Generate server certificate
This commit is contained in:
		@@ -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)]
 | 
			
		||||
 
 | 
			
		||||
@@ -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<Private>,
 | 
			
		||||
    crl: Option<PathBuf>,
 | 
			
		||||
@@ -64,6 +66,15 @@ impl CertData {
 | 
			
		||||
            crl: Some(AppConfig::get().devices_ca_crl_path()),
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Load server CA
 | 
			
		||||
    pub fn load_server() -> anyhow::Result<Self> {
 | 
			
		||||
        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<u8>, Vec<u8>)>
 | 
			
		||||
        // 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<u8>, Vec<u8>)>
 | 
			
		||||
 | 
			
		||||
    // 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)?;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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!");
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user