use anyhow::{Context, Result};
use rcgen::{CertificateParams, DistinguishedName, KeyPair};
use std::fs;
use std::path::Path;
pub fn generate_certificates(cert_dir: &Path) -> Result<CertificateFiles> {
fs::create_dir_all(cert_dir).context("Failed to create certificate directory")?;
let cert_path = cert_dir.join("cert.pem");
let key_path = cert_dir.join("key.pem");
if cert_path.exists() && key_path.exists() {
tracing::info!("Certificates already exist at {:?}", cert_dir);
return Ok(CertificateFiles {
cert_path,
key_path,
});
}
tracing::info!("Generating self-signed certificates for Lore QUIC");
let mut params = CertificateParams::default();
let mut dn = DistinguishedName::new();
dn.push(rcgen::DnType::CommonName, "localhost");
dn.push(rcgen::DnType::OrganizationName, "NAP SDK");
params.distinguished_name = dn;
params.subject_alt_names = vec![
rcgen::SanType::DnsName(rcgen::Ia5String::try_from("localhost").unwrap()),
rcgen::SanType::IpAddress("127.0.0.1".parse().unwrap()),
rcgen::SanType::IpAddress("::1".parse().unwrap()),
];
let key_pair = KeyPair::generate()?;
let cert = params.self_signed(&key_pair)?;
let cert_pem = cert.pem();
fs::write(&cert_path, cert_pem).context("Failed to write certificate file")?;
let key_pem = key_pair.serialize_pem();
fs::write(&key_path, key_pem).context("Failed to write private key file")?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(&key_path)
.with_context(|| {
format!(
"Failed to read key file permissions at {}",
key_path.display()
)
})?
.permissions();
perms.set_mode(0o600); fs::set_permissions(&key_path, perms).with_context(|| {
format!(
"Failed to set restrictive permissions on key file at {}",
key_path.display()
)
})?;
tracing::debug!("Set key file permissions to 0600 (owner-only)");
}
#[cfg(windows)]
{
use std::os::windows::ffi::OsStrExt;
use windows_sys::Win32::Storage::FileSystem::{
FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_SYSTEM, SetFileAttributesW,
};
let key_wide: Vec<u16> = key_path
.as_os_str()
.encode_wide()
.chain(std::iter::once(0))
.collect();
unsafe {
let result = SetFileAttributesW(
key_wide.as_ptr(),
FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM,
);
if result == 0 {
tracing::warn!(
"Failed to set hidden/system attributes on key file at {}. \
The key file may be visible in Explorer.",
key_path.display()
);
} else {
tracing::debug!("Set key file attributes to hidden+system (Windows)");
}
}
}
tracing::info!("Certificates generated successfully at {:?}", cert_dir);
Ok(CertificateFiles {
cert_path,
key_path,
})
}
#[derive(Debug, Clone)]
pub struct CertificateFiles {
pub cert_path: std::path::PathBuf,
pub key_path: std::path::PathBuf,
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[test]
fn test_generate_certificates() {
let temp_dir = TempDir::new().unwrap();
let cert_dir = temp_dir.path();
let files = generate_certificates(cert_dir).unwrap();
assert!(files.cert_path.exists());
assert!(files.key_path.exists());
let files2 = generate_certificates(cert_dir).unwrap();
assert_eq!(files.cert_path, files2.cert_path);
assert_eq!(files.key_path, files2.key_path);
}
}