diff --git a/TODO b/TODO new file mode 100644 index 0000000..e87a43e --- /dev/null +++ b/TODO @@ -0,0 +1,11 @@ +- make acme store implement https://docs.rs/rustls/latest/rustls/server/trait.ResolvesServerCert.html + +- pass that into https://docs.rs/rustls/latest/rustls/struct.ConfigBuilder.html# + +- turn that into a TlsAcceptor (From is implemented here: + https://docs.rs/tokio-rustls/latest/tokio_rustls/struct.TlsAcceptor.html#impl-From%3CArc%3CServerConfig%3E%3E-for-TlsAcceptor) + +- use tls-listener crate to wrap hyper accepter: https://github.com/tmccombs/tls-listener/blob/main/examples/http.rs#L24 + - https://github.com/tmccombs/tls-listener/blob/main/examples/tls_config/mod.rs + +- logging diff --git a/src/domain/acme.rs b/src/domain/acme.rs index e85d37b..3dd4acb 100644 --- a/src/domain/acme.rs +++ b/src/domain/acme.rs @@ -1,5 +1,6 @@ pub mod manager; pub mod store; -pub type AccountKey = openssl::pkey::PKey; +pub type PrivateKey = openssl::pkey::PKey; + pub type Certificate = openssl::x509::X509; diff --git a/src/domain/acme/manager.rs b/src/domain/acme/manager.rs index cf5aaee..a877181 100644 --- a/src/domain/acme/manager.rs +++ b/src/domain/acme/manager.rs @@ -84,7 +84,7 @@ where // if there's an existing cert, and its expiry (determined by the soonest value of // not_after amongst its parts) is later than 30 days from now, then we consider it to be // synced. - if let Ok(cert) = self.store.get_certificate(domain.as_str()) { + if let Ok((_, cert)) = self.store.get_certificate(domain.as_str()) { let thirty_days = openssl::asn1::Asn1Time::days_from_now(30) .expect("parsed thirty days from now as Asn1Time"); @@ -211,7 +211,7 @@ where // Create a certificate signing request for the order, and request // the certificate. let order = order - .finalize(acme2::Csr::Automatic(pkey)) + .finalize(acme2::Csr::Automatic(pkey.clone())) .await .map_unexpected()?; @@ -260,7 +260,7 @@ where println!("certificate for {} successfully retrieved", domain.as_str()); self.store - .set_certificate(domain.as_str(), cert) + .set_certificate(domain.as_str(), &pkey, cert) .map_unexpected()?; Ok(()) diff --git a/src/domain/acme/store.rs b/src/domain/acme/store.rs index 3fa8641..1ae46f3 100644 --- a/src/domain/acme/store.rs +++ b/src/domain/acme/store.rs @@ -1,11 +1,12 @@ use std::io::{Read, Write}; use std::{fs, io, path, sync}; -use crate::domain::acme::{AccountKey, Certificate}; +use crate::domain::acme::{Certificate, PrivateKey}; use crate::error; use crate::error::{MapUnexpected, ToUnexpected}; use hex::ToHex; +use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; #[derive(thiserror::Error, Debug)] @@ -37,8 +38,8 @@ pub enum GetCertificateError { #[mockall::automock] pub trait Store { - fn set_account_key(&self, k: &AccountKey) -> Result<(), error::Unexpected>; - fn get_account_key(&self) -> Result; + fn set_account_key(&self, k: &PrivateKey) -> Result<(), error::Unexpected>; + fn get_account_key(&self) -> Result; fn set_http01_challenge_key(&self, token: &str, key: &str) -> Result<(), error::Unexpected>; fn get_http01_challenge_key(&self, token: &str) -> Result; @@ -47,15 +48,25 @@ pub trait Store { fn set_certificate( &self, domain: &str, + key: &PrivateKey, cert: Vec, ) -> Result<(), error::Unexpected>; /// Returned vec is guaranteed to have len > 0 - fn get_certificate(&self, domain: &str) -> Result, GetCertificateError>; + fn get_certificate( + &self, + domain: &str, + ) -> Result<(PrivateKey, Vec), GetCertificateError>; } pub trait BoxedStore: Store + Send + Sync + Clone + 'static {} +#[derive(Debug, Serialize, Deserialize)] +struct StoredPKeyCert { + private_key_pem: String, + cert_pems: Vec, +} + struct FSStore { dir_path: path::PathBuf, } @@ -86,21 +97,24 @@ impl FSStore { } fn certificate_path(&self, domain: &str) -> path::PathBuf { - self.dir_path.join("certificates").join(domain) + self.dir_path + .join("certificates") + .join(domain) + .with_extension("json") } } impl BoxedStore for sync::Arc {} impl Store for sync::Arc { - fn set_account_key(&self, k: &AccountKey) -> Result<(), error::Unexpected> { + fn set_account_key(&self, k: &PrivateKey) -> Result<(), error::Unexpected> { let mut file = fs::File::create(self.account_key_path()).map_unexpected()?; let pem = k.private_key_to_pem_pkcs8().map_unexpected()?; file.write_all(&pem).map_unexpected()?; Ok(()) } - fn get_account_key(&self) -> Result { + fn get_account_key(&self) -> Result { let mut file = fs::File::open(self.account_key_path()).map_err(|e| match e.kind() { io::ErrorKind::NotFound => GetAccountKeyError::NotFound, _ => e.to_unexpected().into(), @@ -109,7 +123,7 @@ impl Store for sync::Arc { let mut pem = Vec::::new(); file.read_to_end(&mut pem).map_unexpected()?; - let k = AccountKey::private_key_from_pem(&pem).map_unexpected()?; + let k = PrivateKey::private_key_from_pem(&pem).map_unexpected()?; Ok(k) } @@ -140,38 +154,50 @@ impl Store for sync::Arc { fn set_certificate( &self, domain: &str, + key: &PrivateKey, cert: Vec, ) -> Result<(), error::Unexpected> { - let cert: Vec = cert - .into_iter() - .map(|cert| { - let cert_pem = cert.to_pem().map_unexpected()?; - let cert_pem = String::from_utf8(cert_pem).map_unexpected()?; - Ok::(cert_pem) - }) - .try_collect()?; + let to_store = StoredPKeyCert { + private_key_pem: String::from_utf8(key.private_key_to_pem_pkcs8().map_unexpected()?) + .map_unexpected()?, + cert_pems: cert + .into_iter() + .map(|cert| { + let cert_pem = cert.to_pem().map_unexpected()?; + let cert_pem = String::from_utf8(cert_pem).map_unexpected()?; + Ok::(cert_pem) + }) + .try_collect()?, + }; let cert_file = fs::File::create(self.certificate_path(domain)).map_unexpected()?; - serde_json::to_writer(cert_file, &cert).map_unexpected()?; + serde_json::to_writer(cert_file, &to_store).map_unexpected()?; Ok(()) } - fn get_certificate(&self, domain: &str) -> Result, GetCertificateError> { + fn get_certificate( + &self, + domain: &str, + ) -> Result<(PrivateKey, Vec), GetCertificateError> { let file = fs::File::open(self.certificate_path(domain)).map_err(|e| match e.kind() { io::ErrorKind::NotFound => GetCertificateError::NotFound, _ => e.to_unexpected().into(), })?; - let cert: Vec = serde_json::from_reader(file).map_unexpected()?; + let stored: StoredPKeyCert = serde_json::from_reader(file).map_unexpected()?; - let cert: Vec = cert + let key = + PrivateKey::private_key_from_pem(stored.private_key_pem.as_bytes()).map_unexpected()?; + + let cert: Vec = stored + .cert_pems .into_iter() .map(|cert| openssl::x509::X509::from_pem(cert.as_bytes()).map_unexpected()) .try_collect()?; - Ok(cert) + Ok((key, cert)) } } @@ -187,7 +213,7 @@ mod tests { assert!(matches!( store.get_account_key(), - Err::(GetAccountKeyError::NotFound) + Err::(GetAccountKeyError::NotFound) )); let k = acme2::gen_rsa_private_key(4096).expect("private key generated");