Files
socktop/socktop_agent/src/tls.rs
T
2025-08-22 14:09:02 -07:00

92 lines
3.1 KiB
Rust

use rcgen::{CertificateParams, DistinguishedName, DnType, IsCa, SanType};
use std::{
fs,
io::Write,
net::{IpAddr, Ipv4Addr},
path::{Path, PathBuf},
};
use time::{Duration, OffsetDateTime};
fn config_dir() -> PathBuf {
std::env::var_os("XDG_CONFIG_HOME")
.map(PathBuf::from)
.or_else(|| std::env::var_os("HOME").map(|h| Path::new(&h).join(".config")))
.unwrap_or_else(|| PathBuf::from("."))
.join("socktop_agent")
.join("tls")
}
pub fn cert_paths() -> (PathBuf, PathBuf) {
let dir = config_dir();
(dir.join("cert.pem"), dir.join("key.pem"))
}
pub fn ensure_self_signed_cert() -> anyhow::Result<(PathBuf, PathBuf)> {
let (cert_path, key_path) = cert_paths();
if cert_path.exists() && key_path.exists() {
return Ok((cert_path, key_path));
}
fs::create_dir_all(cert_path.parent().unwrap())?;
let hostname = hostname::get()
.ok()
.and_then(|s| s.into_string().ok())
.unwrap_or_else(|| "localhost".to_string());
let mut params = CertificateParams::new(vec![hostname.clone(), "localhost".into()])?;
params
.subject_alt_names
.push(SanType::IpAddress(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))));
params.subject_alt_names.push(SanType::IpAddress(IpAddr::V6(
::std::net::Ipv6Addr::LOCALHOST,
)));
params
.subject_alt_names
.push(SanType::IpAddress(IpAddr::V4(Ipv4Addr::UNSPECIFIED)));
// Allow operator to provide extra SANs (comma-separated), e.g. IPs or DNS names
if let Ok(extra) = std::env::var("SOCKTOP_AGENT_EXTRA_SANS") {
for raw in extra.split(',') {
let s = raw.trim();
if s.is_empty() {
continue;
}
if let Ok(ip) = s.parse::<IpAddr>() {
params.subject_alt_names.push(SanType::IpAddress(ip));
} else {
match s.to_string().try_into() {
Ok(dns) => params.subject_alt_names.push(SanType::DnsName(dns)),
Err(_) => eprintln!("socktop_agent: ignoring invalid SAN entry: {s}"),
}
}
}
}
let mut dn = DistinguishedName::new();
dn.push(DnType::CommonName, hostname.clone());
params.distinguished_name = dn;
params.is_ca = IsCa::NoCa;
// Dynamic validity: start slightly in the past to avoid clock skew issues, end ~397 days later
let now = OffsetDateTime::now_utc();
params.not_before = now - Duration::minutes(5);
params.not_after = now + Duration::days(397);
// Generate key pair (default is ECDSA P256 SHA256)
let key_pair = rcgen::KeyPair::generate()?; // defaults to ECDSA P256 SHA256
let cert = params.self_signed(&key_pair)?;
let cert_pem = cert.pem();
let key_pem = key_pair.serialize_pem();
let mut f = fs::File::create(&cert_path)?;
f.write_all(cert_pem.as_bytes())?;
let mut k = fs::File::create(&key_path)?;
k.write_all(key_pem.as_bytes())?;
println!(
"socktop_agent: generated self-signed TLS certificate at {}",
cert_path.display()
);
println!("socktop_agent: private key at {}", key_path.display());
Ok((cert_path, key_path))
}