1use anyhow::{Context, Result};
8use rcgen::{CertificateParams, DistinguishedName, KeyPair};
9use std::fs;
10use std::path::Path;
11
12pub fn generate_certificates(cert_dir: &Path) -> Result<CertificateFiles> {
17 fs::create_dir_all(cert_dir).context("Failed to create certificate directory")?;
18
19 let cert_path = cert_dir.join("cert.pem");
20 let key_path = cert_dir.join("key.pem");
21
22 if cert_path.exists() && key_path.exists() {
24 tracing::info!("Certificates already exist at {:?}", cert_dir);
25 return Ok(CertificateFiles {
26 cert_path,
27 key_path,
28 });
29 }
30
31 tracing::info!("Generating self-signed certificates for Lore QUIC");
32
33 let mut params = CertificateParams::default();
34
35 let mut dn = DistinguishedName::new();
37 dn.push(rcgen::DnType::CommonName, "localhost");
38 dn.push(rcgen::DnType::OrganizationName, "NAP SDK");
39 params.distinguished_name = dn;
40
41 params.subject_alt_names = vec![
43 rcgen::SanType::DnsName(rcgen::Ia5String::try_from("localhost").unwrap()),
44 rcgen::SanType::IpAddress("127.0.0.1".parse().unwrap()),
45 rcgen::SanType::IpAddress("::1".parse().unwrap()),
46 ];
47
48 let key_pair = KeyPair::generate()?;
50 let cert = params.self_signed(&key_pair)?;
51
52 let cert_pem = cert.pem();
54 fs::write(&cert_path, cert_pem).context("Failed to write certificate file")?;
55
56 let key_pem = key_pair.serialize_pem();
58 fs::write(&key_path, key_pem).context("Failed to write private key file")?;
59
60 #[cfg(unix)]
62 {
63 use std::os::unix::fs::PermissionsExt;
64 let mut perms = fs::metadata(&key_path)
65 .with_context(|| {
66 format!(
67 "Failed to read key file permissions at {}",
68 key_path.display()
69 )
70 })?
71 .permissions();
72 perms.set_mode(0o600); fs::set_permissions(&key_path, perms).with_context(|| {
74 format!(
75 "Failed to set restrictive permissions on key file at {}",
76 key_path.display()
77 )
78 })?;
79 tracing::debug!("Set key file permissions to 0600 (owner-only)");
80 }
81
82 #[cfg(windows)]
83 {
84 use std::os::windows::ffi::OsStrExt;
87 use windows_sys::Win32::Storage::FileSystem::{
88 FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_SYSTEM, SetFileAttributesW,
89 };
90
91 let key_wide: Vec<u16> = key_path
92 .as_os_str()
93 .encode_wide()
94 .chain(std::iter::once(0))
95 .collect();
96 unsafe {
97 let result = SetFileAttributesW(
98 key_wide.as_ptr(),
99 FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM,
100 );
101 if result == 0 {
102 tracing::warn!(
103 "Failed to set hidden/system attributes on key file at {}. \
104 The key file may be visible in Explorer.",
105 key_path.display()
106 );
107 } else {
108 tracing::debug!("Set key file attributes to hidden+system (Windows)");
109 }
110 }
111 }
112
113 tracing::info!("Certificates generated successfully at {:?}", cert_dir);
114
115 Ok(CertificateFiles {
116 cert_path,
117 key_path,
118 })
119}
120
121#[derive(Debug, Clone)]
123pub struct CertificateFiles {
124 pub cert_path: std::path::PathBuf,
125 pub key_path: std::path::PathBuf,
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131 use tempfile::TempDir;
132
133 #[test]
134 fn test_generate_certificates() {
135 let temp_dir = TempDir::new().unwrap();
136 let cert_dir = temp_dir.path();
137
138 let files = generate_certificates(cert_dir).unwrap();
139
140 assert!(files.cert_path.exists());
141 assert!(files.key_path.exists());
142
143 let files2 = generate_certificates(cert_dir).unwrap();
145 assert_eq!(files.cert_path, files2.cert_path);
146 assert_eq!(files.key_path, files2.key_path);
147 }
148}