Skip to main content

proxyapi/ca/
mod.rs

1pub mod cert_server;
2
3use std::{
4    path::Path,
5    sync::Arc,
6    time::{Duration, SystemTime},
7};
8
9use async_trait::async_trait;
10use bytes::Bytes;
11use http::uri::Authority;
12use moka::future::Cache;
13use openssl::{
14    asn1::{Asn1Integer, Asn1Time},
15    bn::BigNum,
16    hash::MessageDigest,
17    pkey::{PKey, Private},
18    rand,
19    x509::{
20        extension::{BasicConstraints, KeyUsage, SubjectAlternativeName},
21        X509Builder, X509NameBuilder, X509,
22    },
23};
24use rustls_pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs1KeyDer};
25use tokio_rustls::rustls::ServerConfig;
26
27const TTL_SECS: i64 = 365 * 24 * 60 * 60;
28const CACHE_TTL: u64 = TTL_SECS as u64 / 2;
29const NOT_BEFORE_OFFSET: i64 = 60;
30const CA_TTL_SECS: i64 = 10 * 365 * 24 * 60 * 60;
31
32#[async_trait]
33pub trait CertificateAuthority: Send + Sync + 'static {
34    async fn gen_server_config(
35        &self,
36        authority: &Authority,
37    ) -> Result<Arc<ServerConfig>, crate::error::Error>;
38}
39
40#[derive(Clone)]
41pub struct Ssl {
42    pkey: PKey<Private>,
43    private_key_der: Vec<u8>,
44    ca_cert: X509,
45    ca_cert_pem: Bytes,
46    hash: MessageDigest,
47    cache: Cache<Authority, Arc<ServerConfig>>,
48}
49
50impl Ssl {
51    pub fn load_or_generate(dir: &Path) -> Result<Self, crate::error::Error> {
52        std::fs::create_dir_all(dir)?;
53
54        let cert_path = dir.join("proxelar-ca.pem");
55        let key_path = dir.join("proxelar-ca.key");
56
57        let (pkey, ca_cert) = if cert_path.exists() && key_path.exists() {
58            tracing::info!("Loading CA certificate from {}", dir.display());
59            let key_pem = std::fs::read(&key_path)?;
60            let cert_pem = std::fs::read(&cert_path)?;
61            let pkey = PKey::private_key_from_pem(&key_pem)?;
62            let ca_cert = X509::from_pem(&cert_pem)?;
63
64            // Verify the loaded key matches the certificate
65            if !ca_cert.public_key()?.public_eq(&pkey) {
66                return Err(crate::error::Error::Other(
67                    "CA certificate does not match private key".into(),
68                ));
69            }
70
71            (pkey, ca_cert)
72        } else {
73            tracing::info!("Generating new CA certificate in {}", dir.display());
74            let (pkey, ca_cert) = generate_ca()?;
75
76            let key_pem = pkey.private_key_to_pem_pkcs8()?;
77            let cert_pem = ca_cert.to_pem()?;
78
79            std::fs::write(&key_path, &key_pem)?;
80            #[cfg(unix)]
81            {
82                use std::os::unix::fs::PermissionsExt;
83                std::fs::set_permissions(&key_path, std::fs::Permissions::from_mode(0o600))?;
84            }
85            std::fs::write(&cert_path, &cert_pem)?;
86
87            (pkey, ca_cert)
88        };
89
90        let ca_cert_pem = Bytes::from(ca_cert.to_pem()?);
91
92        let private_key_der = pkey.rsa()?.private_key_to_der()?;
93
94        Ok(Self {
95            pkey,
96            private_key_der,
97            ca_cert,
98            ca_cert_pem,
99            hash: MessageDigest::sha256(),
100            cache: Cache::builder()
101                .max_capacity(1_000)
102                .time_to_live(Duration::from_secs(CACHE_TTL))
103                .build(),
104        })
105    }
106
107    pub fn ca_cert_pem(&self) -> Bytes {
108        self.ca_cert_pem.clone()
109    }
110
111    fn gen_cert(
112        &self,
113        authority: &Authority,
114    ) -> Result<CertificateDer<'static>, crate::error::Error> {
115        let mut name_builder = X509NameBuilder::new()?;
116        name_builder.append_entry_by_text("CN", authority.host())?;
117        let name = name_builder.build();
118
119        let mut x509_builder = X509Builder::new()?;
120        x509_builder.set_subject_name(&name)?;
121        x509_builder.set_version(2)?;
122
123        let not_before = SystemTime::now()
124            .duration_since(SystemTime::UNIX_EPOCH)?
125            .as_secs() as i64
126            - NOT_BEFORE_OFFSET;
127        x509_builder.set_not_before(Asn1Time::from_unix(not_before)?.as_ref())?;
128        x509_builder.set_not_after(Asn1Time::from_unix(not_before + TTL_SECS)?.as_ref())?;
129
130        x509_builder.set_pubkey(&self.pkey)?;
131        x509_builder.set_issuer_name(self.ca_cert.subject_name())?;
132
133        let alternative_name = SubjectAlternativeName::new()
134            .dns(authority.host())
135            .build(&x509_builder.x509v3_context(Some(&self.ca_cert), None))?;
136        x509_builder.append_extension(alternative_name)?;
137
138        let mut serial_number = [0; 16];
139        rand::rand_bytes(&mut serial_number)?;
140
141        let serial_number = BigNum::from_slice(&serial_number)?;
142        let serial_number = Asn1Integer::from_bn(&serial_number)?;
143        x509_builder.set_serial_number(&serial_number)?;
144
145        x509_builder.sign(&self.pkey, self.hash)?;
146        let x509 = x509_builder.build();
147        Ok(CertificateDer::from(x509.to_der()?))
148    }
149}
150
151fn generate_ca() -> Result<(PKey<Private>, X509), crate::error::Error> {
152    let rsa = openssl::rsa::Rsa::generate(4096)?;
153    let pkey = PKey::from_rsa(rsa)?;
154
155    let mut name_builder = X509NameBuilder::new()?;
156    name_builder.append_entry_by_text("CN", "proxelar")?;
157    name_builder.append_entry_by_text("O", "Proxelar")?;
158    let name = name_builder.build();
159
160    let mut builder = X509Builder::new()?;
161    builder.set_version(2)?;
162    builder.set_subject_name(&name)?;
163    builder.set_issuer_name(&name)?;
164    builder.set_pubkey(&pkey)?;
165
166    let not_before = SystemTime::now()
167        .duration_since(SystemTime::UNIX_EPOCH)?
168        .as_secs() as i64
169        - NOT_BEFORE_OFFSET;
170    builder.set_not_before(Asn1Time::from_unix(not_before)?.as_ref())?;
171    builder.set_not_after(Asn1Time::from_unix(not_before + CA_TTL_SECS)?.as_ref())?;
172
173    let mut serial_number = [0; 16];
174    rand::rand_bytes(&mut serial_number)?;
175    let serial_number = BigNum::from_slice(&serial_number)?;
176    let serial_number = Asn1Integer::from_bn(&serial_number)?;
177    builder.set_serial_number(&serial_number)?;
178
179    let basic_constraints = BasicConstraints::new().critical().ca().build()?;
180    builder.append_extension(basic_constraints)?;
181
182    let key_usage = KeyUsage::new()
183        .critical()
184        .key_cert_sign()
185        .crl_sign()
186        .build()?;
187    builder.append_extension(key_usage)?;
188
189    builder.sign(&pkey, MessageDigest::sha512())?;
190    let cert = builder.build();
191
192    Ok((pkey, cert))
193}
194
195#[async_trait]
196impl CertificateAuthority for Ssl {
197    async fn gen_server_config(
198        &self,
199        authority: &Authority,
200    ) -> Result<Arc<ServerConfig>, crate::error::Error> {
201        if let Some(server_cfg) = self.cache.get(authority).await {
202            tracing::debug!("Using cached server config for {authority}");
203            return Ok(server_cfg);
204        }
205        tracing::debug!("Generating server config for {authority}");
206
207        let certs = vec![self.gen_cert(authority)?];
208
209        let private_key =
210            PrivateKeyDer::Pkcs1(PrivatePkcs1KeyDer::from(self.private_key_der.clone()));
211
212        let mut server_cfg = ServerConfig::builder()
213            .with_no_client_auth()
214            .with_single_cert(certs, private_key)?;
215
216        server_cfg.alpn_protocols = vec![b"http/1.1".to_vec()];
217
218        let server_cfg = Arc::new(server_cfg);
219
220        self.cache
221            .insert(authority.clone(), Arc::clone(&server_cfg))
222            .await;
223
224        Ok(server_cfg)
225    }
226}