Generate server certificate

This commit is contained in:
Pierre HUBERT 2024-06-28 21:34:18 +02:00
parent f4fde9bc46
commit 09f526bfb7
3 changed files with 81 additions and 7 deletions

View File

@ -7,15 +7,15 @@ use std::path::{Path, PathBuf};
pub struct AppConfig { pub struct AppConfig {
/// The port the server will listen to (using HTTPS) /// The port the server will listen to (using HTTPS)
#[arg(short, long, env, default_value = "0.0.0.0:8443")] #[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) /// The port the server will listen to (using HTTP, for unsecure connections)
#[arg(short, long, env, default_value = "0.0.0.0:8080")] #[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) /// Public server hostname (assuming that the ports used are the same for listen address)
#[arg(short('H'), long, env, default_value = "localhost")] #[arg(short('H'), long, env, default_value = "localhost")]
hostname: String, pub hostname: String,
/// Server storage path /// Server storage path
#[arg(short, long, env, default_value = "storage")] #[arg(short, long, env, default_value = "storage")]
@ -106,6 +106,16 @@ impl AppConfig {
pub fn devices_ca_priv_key_path(&self) -> PathBuf { pub fn devices_ca_priv_key_path(&self) -> PathBuf {
self.pki_path().join("devices_ca.key") 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)] #[cfg(test)]

View File

@ -10,7 +10,9 @@ use openssl::ec::EcGroup;
use openssl::hash::MessageDigest; use openssl::hash::MessageDigest;
use openssl::nid::Nid; use openssl::nid::Nid;
use openssl::pkey::{PKey, Private}; 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::x509::{X509Crl, X509NameBuilder, X509};
use openssl_sys::{ use openssl_sys::{
X509_CRL_add0_revoked, X509_CRL_set1_lastUpdate, X509_CRL_set1_nextUpdate, X509_CRL_add0_revoked, X509_CRL_set1_lastUpdate, X509_CRL_set1_nextUpdate,
@ -31,7 +33,7 @@ pub enum PKIError {
} }
/// Certificate and private key /// Certificate and private key
struct CertData { pub struct CertData {
cert: X509, cert: X509,
key: PKey<Private>, key: PKey<Private>,
crl: Option<PathBuf>, crl: Option<PathBuf>,
@ -64,6 +66,15 @@ impl CertData {
crl: Some(AppConfig::get().devices_ca_crl_path()), 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 /// Generate private key
@ -96,6 +107,8 @@ struct GenCertificateReq<'a> {
cn: &'a str, cn: &'a str,
issuer: Option<&'a CertData>, issuer: Option<&'a CertData>,
ca: bool, ca: bool,
web_server: bool,
subject_alternative_names: Vec<&'a str>,
} }
/// Generate certificate /// Generate certificate
@ -122,7 +135,7 @@ fn gen_certificate(req: GenCertificateReq) -> anyhow::Result<(Vec<u8>, Vec<u8>)>
// Self-signed certificate // Self-signed certificate
None => cert_builder.set_issuer_name(&x509_name)?, None => cert_builder.set_issuer_name(&x509_name)?,
// Certificate signed by another CA // 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)?; cert_builder.set_pubkey(&key_pair)?;
@ -154,11 +167,34 @@ fn gen_certificate(req: GenCertificateReq) -> anyhow::Result<(Vec<u8>, Vec<u8>)>
// Key usage // Key usage
let mut key_usage = KeyUsage::new(); let mut key_usage = KeyUsage::new();
let mut eku = None;
if req.ca { if req.ca {
key_usage.key_cert_sign().crl_sign(); 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()?)?; 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 // Subject key identifier
let subject_key_identifier = let subject_key_identifier =
SubjectKeyIdentifier::new().build(&cert_builder.x509v3_context(None, None))?; SubjectKeyIdentifier::new().build(&cert_builder.x509v3_context(None, None))?;
@ -191,6 +227,7 @@ pub fn initialize_root_ca() -> anyhow::Result<()> {
cn: "SolarEnergy Root CA", cn: "SolarEnergy Root CA",
issuer: None, issuer: None,
ca: true, ca: true,
..Default::default()
})?; })?;
// Serialize generated web CA // Serialize generated web CA
@ -214,6 +251,7 @@ pub fn initialize_web_ca() -> anyhow::Result<()> {
cn: "SolarEnergy Web CA", cn: "SolarEnergy Web CA",
issuer: Some(&CertData::load_root_ca()?), issuer: Some(&CertData::load_root_ca()?),
ca: true, ca: true,
..Default::default()
})?; })?;
// Serialize generated web CA // Serialize generated web CA
@ -237,6 +275,7 @@ pub fn initialize_devices_ca() -> anyhow::Result<()> {
cn: "SolarEnergy Devices CA", cn: "SolarEnergy Devices CA",
issuer: Some(&CertData::load_root_ca()?), issuer: Some(&CertData::load_root_ca()?),
ca: true, ca: true,
..Default::default()
})?; })?;
// Serialize generated devices CA // Serialize generated devices CA
@ -246,7 +285,31 @@ pub fn initialize_devices_ca() -> anyhow::Result<()> {
Ok(()) 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<()> { fn refresh_crl(d: &CertData) -> anyhow::Result<()> {
let crl_path = d.crl.as_ref().ok_or(PKIError::MissingCRL)?; let crl_path = d.crl.as_ref().ok_or(PKIError::MissingCRL)?;

View File

@ -15,6 +15,7 @@ fn main() {
pki::initialize_root_ca().expect("Failed to initialize Root CA!"); pki::initialize_root_ca().expect("Failed to initialize Root CA!");
pki::initialize_web_ca().expect("Failed to initialize web CA!"); pki::initialize_web_ca().expect("Failed to initialize web CA!");
pki::initialize_devices_ca().expect("Failed to initialize devices 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!"); pki::refresh_crls().expect("Failed to initialize Root CA!");
} }