save private key generated during acme handshake
This commit is contained in:
parent
06cda77772
commit
209daacf1b
11
TODO
Normal file
11
TODO
Normal file
@ -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
|
@ -1,5 +1,6 @@
|
||||
pub mod manager;
|
||||
pub mod store;
|
||||
|
||||
pub type AccountKey = openssl::pkey::PKey<openssl::pkey::Private>;
|
||||
pub type PrivateKey = openssl::pkey::PKey<openssl::pkey::Private>;
|
||||
|
||||
pub type Certificate = openssl::x509::X509;
|
||||
|
@ -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(())
|
||||
|
@ -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<AccountKey, GetAccountKeyError>;
|
||||
fn set_account_key(&self, k: &PrivateKey) -> Result<(), error::Unexpected>;
|
||||
fn get_account_key(&self) -> Result<PrivateKey, GetAccountKeyError>;
|
||||
|
||||
fn set_http01_challenge_key(&self, token: &str, key: &str) -> Result<(), error::Unexpected>;
|
||||
fn get_http01_challenge_key(&self, token: &str) -> Result<String, GetHttp01ChallengeKeyError>;
|
||||
@ -47,15 +48,25 @@ pub trait Store {
|
||||
fn set_certificate(
|
||||
&self,
|
||||
domain: &str,
|
||||
key: &PrivateKey,
|
||||
cert: Vec<Certificate>,
|
||||
) -> Result<(), error::Unexpected>;
|
||||
|
||||
/// Returned vec is guaranteed to have len > 0
|
||||
fn get_certificate(&self, domain: &str) -> Result<Vec<Certificate>, GetCertificateError>;
|
||||
fn get_certificate(
|
||||
&self,
|
||||
domain: &str,
|
||||
) -> Result<(PrivateKey, Vec<Certificate>), GetCertificateError>;
|
||||
}
|
||||
|
||||
pub trait BoxedStore: Store + Send + Sync + Clone + 'static {}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct StoredPKeyCert {
|
||||
private_key_pem: String,
|
||||
cert_pems: Vec<String>,
|
||||
}
|
||||
|
||||
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<FSStore> {}
|
||||
|
||||
impl Store for sync::Arc<FSStore> {
|
||||
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<AccountKey, GetAccountKeyError> {
|
||||
fn get_account_key(&self) -> Result<PrivateKey, GetAccountKeyError> {
|
||||
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<FSStore> {
|
||||
let mut pem = Vec::<u8>::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<FSStore> {
|
||||
fn set_certificate(
|
||||
&self,
|
||||
domain: &str,
|
||||
key: &PrivateKey,
|
||||
cert: Vec<Certificate>,
|
||||
) -> Result<(), error::Unexpected> {
|
||||
let cert: Vec<String> = cert
|
||||
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::<String, error::Unexpected>(cert_pem)
|
||||
})
|
||||
.try_collect()?;
|
||||
.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<Vec<Certificate>, GetCertificateError> {
|
||||
fn get_certificate(
|
||||
&self,
|
||||
domain: &str,
|
||||
) -> Result<(PrivateKey, Vec<Certificate>), 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<String> = serde_json::from_reader(file).map_unexpected()?;
|
||||
let stored: StoredPKeyCert = serde_json::from_reader(file).map_unexpected()?;
|
||||
|
||||
let cert: Vec<Certificate> = cert
|
||||
let key =
|
||||
PrivateKey::private_key_from_pem(stored.private_key_pem.as_bytes()).map_unexpected()?;
|
||||
|
||||
let cert: Vec<Certificate> = 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::<AccountKey, GetAccountKeyError>(GetAccountKeyError::NotFound)
|
||||
Err::<PrivateKey, GetAccountKeyError>(GetAccountKeyError::NotFound)
|
||||
));
|
||||
|
||||
let k = acme2::gen_rsa_private_key(4096).expect("private key generated");
|
||||
|
Loading…
Reference in New Issue
Block a user