use anyhow::{anyhow, Error}; use rcgen::{CertificateParams, DistinguishedName, Ia5String, KeyPair}; use rustls::pki_types::pem::PemObject; use rustls::pki_types::{CertificateDer, PrivateKeyDer}; use rustls::ServerConfig; use std::fs; use std::net::IpAddr; use std::path::{Path, PathBuf}; use std::time::Duration; use crate::options::WssproxyOptions; pub fn generate_certificates(certs_path: &PathBuf, wss_host: &[String]) -> Result<(), Error> { /* Generate the CA certificate */ let mut ca_params = CertificateParams::new(vec![ "cln Root wss-proxy CA".to_string(), "cln".to_string(), "localhost".to_string(), ])?; ca_params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained); ca_params.key_usages.push(rcgen::KeyUsagePurpose::KeyCertSign); ca_params.use_authority_key_identifier_extension = true; let ca_key = KeyPair::generate()?; let ca_cert = ca_params.self_signed(&ca_key)?; fs::create_dir_all(certs_path)?; fs::write(certs_path.join("ca.pem"), ca_cert.pem())?; fs::write( certs_path.join("ca-key.pem"), ca_key.serialize_pem().as_bytes(), )?; /* Generate the server certificate signed by the CA */ let mut server_params = CertificateParams::new(vec![ format!("cln wss-proxy server"), "cln".to_string(), "localhost".to_string(), ])?; server_params.is_ca = rcgen::IsCa::NoCa; server_params.key_usages.push(rcgen::KeyUsagePurpose::DigitalSignature); server_params.key_usages.push(rcgen::KeyUsagePurpose::KeyEncipherment); server_params.key_usages.push(rcgen::KeyUsagePurpose::KeyAgreement); server_params.use_authority_key_identifier_extension = true; server_params.distinguished_name = DistinguishedName::new(); server_params .distinguished_name .push(rcgen::DnType::CommonName, "cln wss-proxy server"); /* It is convention to not include [] for ipv6 addresses in certificate SAN's */ for host in wss_host.iter() { let host_stripped = if host.starts_with('[') && host.ends_with(']') { host[1..host.len() - 1].to_string() } else { host.to_owned() }; if let Ok(ip) = host_stripped.parse::() { server_params .subject_alt_names .push(rcgen::SanType::IpAddress(ip)); } else if let Ok(dns) = Ia5String::try_from(host.to_owned()) { server_params .subject_alt_names .push(rcgen::SanType::DnsName(dns)); } } let server_key = KeyPair::generate()?; let server_pem = server_params .signed_by(&server_key, &ca_cert, &ca_key)? .pem(); fs::write(certs_path.join("server.pem"), server_pem)?; fs::write( certs_path.join("server-key.pem"), server_key.serialize_pem().as_bytes(), )?; /* Generate the client certificate signed by the CA */ let mut client_params = CertificateParams::new(vec![ format!("cln wss-proxy client"), "cln".to_string(), "localhost".to_string(), ])?; client_params.is_ca = rcgen::IsCa::NoCa; client_params.distinguished_name = DistinguishedName::new(); client_params .distinguished_name .push(rcgen::DnType::CommonName, "cln wss-proxy client"); let client_key = KeyPair::generate()?; let client_pem = client_params .signed_by(&client_key, &ca_cert, &ca_key)? .pem(); fs::write(certs_path.join("client.pem"), client_pem)?; fs::write( certs_path.join("client-key.pem"), client_key.serialize_pem().as_bytes(), )?; Ok(()) } pub fn do_certificates_exist(cert_dir: &Path) -> bool { let required_files = [ "server.pem", "server-key.pem", "client.pem", "client-key.pem", "ca.pem", "ca-key.pem", ]; required_files.iter().all(|file| { let path = cert_dir.join(file); path.exists() && path.metadata().map(|m| m.len() > 0).unwrap_or(false) }) } pub async fn get_tls_config(wss_proxy_options: &WssproxyOptions) -> Result { let max_retries = 20; let mut retries = 0; while retries < max_retries && !do_certificates_exist(&wss_proxy_options.certs_dir) { log::debug!("Certificates incomplete. Retrying..."); tokio::time::sleep(Duration::from_millis(500)).await; retries += 1; } if !do_certificates_exist(&wss_proxy_options.certs_dir) { log::debug!("Certificates still not existing after retries. Generating..."); generate_certificates(&wss_proxy_options.certs_dir, &wss_proxy_options.wss_domains)?; } let certs = CertificateDer::pem_file_iter(wss_proxy_options.certs_dir.join("server.pem")) .unwrap() .map(|cert| cert.unwrap()) .collect(); let private_key = PrivateKeyDer::from_pem_file(wss_proxy_options.certs_dir.join("server-key.pem")).unwrap(); rustls::ServerConfig::builder() .with_no_client_auth() .with_single_cert(certs, private_key) .map_err(|e| anyhow!("{}", e)) }