parent
49ac208286
commit
eadb53db0b
@ -1,2 +1,3 @@ |
||||
pub mod account_key_store; |
||||
pub mod manager; |
||||
pub mod store; |
||||
|
@ -0,0 +1,86 @@ |
||||
use std::io::{Read, Write}; |
||||
use std::{fs, path}; |
||||
|
||||
use crate::domain::tls::PrivateKey; |
||||
use crate::error::unexpected::{self, Mappable}; |
||||
use crate::util; |
||||
|
||||
#[mockall::automock] |
||||
pub trait Store { |
||||
fn set(&self, k: &PrivateKey) -> Result<(), unexpected::Error>; |
||||
fn get(&self) -> unexpected::Result<Option<PrivateKey>>; |
||||
} |
||||
|
||||
pub struct FSStore { |
||||
dir_path: path::PathBuf, |
||||
} |
||||
|
||||
impl FSStore { |
||||
pub fn new(dir_path: &path::Path) -> Result<Self, unexpected::Error> { |
||||
fs::create_dir_all(dir_path).or_unexpected()?; |
||||
Ok(Self { |
||||
dir_path: dir_path.into(), |
||||
}) |
||||
} |
||||
|
||||
fn account_key_path(&self) -> path::PathBuf { |
||||
self.dir_path.join("account.key") |
||||
} |
||||
} |
||||
|
||||
impl Store for FSStore { |
||||
fn set(&self, k: &PrivateKey) -> Result<(), unexpected::Error> { |
||||
let path = self.account_key_path(); |
||||
{ |
||||
let mut file = fs::File::create(&path).or_unexpected_while("creating file")?; |
||||
file.write_all(k.to_string().as_bytes()) |
||||
.or_unexpected_while("writing file") |
||||
} |
||||
.map_unexpected_while(|| format!("path is {}", path.display()))?; |
||||
Ok(()) |
||||
} |
||||
|
||||
fn get(&self) -> unexpected::Result<Option<PrivateKey>> { |
||||
let path = self.account_key_path(); |
||||
{ |
||||
let mut file = |
||||
match util::open_file(path.as_path()).or_unexpected_while("opening_file")? { |
||||
Some(file) => file, |
||||
None => return Ok(None), |
||||
}; |
||||
|
||||
let mut key = String::new(); |
||||
file.read_to_string(&mut key) |
||||
.or_unexpected_while("reading file")?; |
||||
|
||||
let key: PrivateKey = key.parse().or_unexpected_while("parsing private key")?; |
||||
|
||||
unexpected::Result::<Option<PrivateKey>>::Ok(Some(key)) |
||||
} |
||||
.map_unexpected_while(|| format!("path is {}", path.display())) |
||||
} |
||||
} |
||||
|
||||
#[cfg(test)] |
||||
mod tests { |
||||
use super::*; |
||||
use crate::domain::tls; |
||||
use tempdir::TempDir; |
||||
|
||||
#[test] |
||||
fn account_key() { |
||||
let tmp_dir = TempDir::new("domain_acme_account_key_store").unwrap(); |
||||
let store = FSStore::new(tmp_dir.path()).expect("store created"); |
||||
|
||||
assert!(matches!( |
||||
store.get(), |
||||
unexpected::Result::<Option<PrivateKey>>::Ok(None) |
||||
)); |
||||
|
||||
let k = tls::PrivateKey::new(); |
||||
|
||||
store.set(&k).expect("account private key set"); |
||||
|
||||
assert_eq!(Some(k), store.get().expect("account private key retrieved")); |
||||
} |
||||
} |
@ -1,193 +1,24 @@ |
||||
use std::io::{Read, Write}; |
||||
use std::str::FromStr; |
||||
use std::{fs, path}; |
||||
mod json_fs_store; |
||||
pub use json_fs_store::*; |
||||
|
||||
use crate::domain::tls::{Certificate, PrivateKey}; |
||||
use crate::error::unexpected::{self, Mappable}; |
||||
use crate::util; |
||||
mod direct_fs_store; |
||||
pub use direct_fs_store::*; |
||||
|
||||
use serde::{Deserialize, Serialize}; |
||||
|
||||
#[derive(thiserror::Error, Debug)] |
||||
pub enum GetAccountKeyError { |
||||
#[error("not found")] |
||||
NotFound, |
||||
|
||||
#[error(transparent)] |
||||
Unexpected(#[from] unexpected::Error), |
||||
} |
||||
|
||||
#[derive(thiserror::Error, Debug)] |
||||
pub enum GetCertificateError { |
||||
#[error("not found")] |
||||
NotFound, |
||||
|
||||
#[error(transparent)] |
||||
Unexpected(#[from] unexpected::Error), |
||||
} |
||||
use crate::domain::tls::{CertificateChain, PrivateKey}; |
||||
use crate::error::unexpected; |
||||
|
||||
#[mockall::automock] |
||||
pub trait Store { |
||||
fn set_account_key(&self, k: &PrivateKey) -> Result<(), unexpected::Error>; |
||||
fn get_account_key(&self) -> Result<PrivateKey, GetAccountKeyError>; |
||||
|
||||
fn set_certificate( |
||||
&self, |
||||
domain: &str, |
||||
key: PrivateKey, |
||||
cert: Vec<Certificate>, |
||||
cert: CertificateChain, |
||||
) -> Result<(), unexpected::Error>; |
||||
|
||||
/// Returned vec is guaranteed to have len > 0
|
||||
fn get_certificate( |
||||
&self, |
||||
domain: &str, |
||||
) -> Result<(PrivateKey, Vec<Certificate>), GetCertificateError>; |
||||
} |
||||
|
||||
#[derive(Debug, Serialize, Deserialize)] |
||||
struct StoredPKeyCert { |
||||
private_key: PrivateKey, |
||||
cert: Vec<Certificate>, |
||||
} |
||||
|
||||
pub struct FSStore { |
||||
dir_path: path::PathBuf, |
||||
} |
||||
|
||||
impl FSStore { |
||||
pub fn new(dir_path: &path::Path) -> Result<Self, unexpected::Error> { |
||||
vec![ |
||||
dir_path, |
||||
dir_path.join("http01_challenge_keys").as_ref(), |
||||
dir_path.join("certificates").as_ref(), |
||||
] |
||||
.iter() |
||||
.map(|dir| { |
||||
fs::create_dir_all(dir) |
||||
.map_unexpected_while(|| format!("creating dir {}", dir.display())) |
||||
}) |
||||
.try_collect()?; |
||||
|
||||
Ok(Self { |
||||
dir_path: dir_path.into(), |
||||
}) |
||||
} |
||||
|
||||
fn account_key_path(&self) -> path::PathBuf { |
||||
self.dir_path.join("account.key") |
||||
} |
||||
|
||||
fn certificate_path(&self, domain: &str) -> path::PathBuf { |
||||
let mut domain = domain.to_string(); |
||||
domain.push_str(".json"); |
||||
|
||||
self.dir_path.join("certificates").join(domain) |
||||
} |
||||
} |
||||
|
||||
impl Store for FSStore { |
||||
fn set_account_key(&self, k: &PrivateKey) -> Result<(), unexpected::Error> { |
||||
let path = self.account_key_path(); |
||||
{ |
||||
let mut file = fs::File::create(&path).or_unexpected_while("creating file")?; |
||||
file.write_all(k.to_string().as_bytes()) |
||||
.or_unexpected_while("writing file") |
||||
} |
||||
.map_unexpected_while(|| format!("path is {}", path.display()))?; |
||||
Ok(()) |
||||
} |
||||
|
||||
fn get_account_key(&self) -> Result<PrivateKey, GetAccountKeyError> { |
||||
let path = self.account_key_path(); |
||||
{ |
||||
let mut file = |
||||
match util::open_file(path.as_path()).or_unexpected_while("opening_file")? { |
||||
Some(file) => file, |
||||
None => return Err(GetAccountKeyError::NotFound), |
||||
}; |
||||
|
||||
let mut key = String::new(); |
||||
file.read_to_string(&mut key) |
||||
.or_unexpected_while("reading file")?; |
||||
|
||||
let key = PrivateKey::from_str(&key).or_unexpected_while("parsing private key")?; |
||||
|
||||
Ok::<PrivateKey, unexpected::Error>(key) |
||||
} |
||||
.map_unexpected_while(|| format!("path is {}", path.display())) |
||||
.map_err(|err| err.into()) |
||||
} |
||||
|
||||
fn set_certificate( |
||||
&self, |
||||
domain: &str, |
||||
key: PrivateKey, |
||||
cert: Vec<Certificate>, |
||||
) -> Result<(), unexpected::Error> { |
||||
let to_store = StoredPKeyCert { |
||||
private_key: key, |
||||
cert, |
||||
}; |
||||
|
||||
let path = self.certificate_path(domain); |
||||
{ |
||||
let cert_file = |
||||
fs::File::create(path.as_path()).or_unexpected_while("creating file")?; |
||||
serde_json::to_writer(cert_file, &to_store).or_unexpected_while("writing cert to file") |
||||
} |
||||
.map_unexpected_while(|| format!("path is {}", path.display())) |
||||
} |
||||
|
||||
/// Returned chain is guaranteed to have len > 0
|
||||
fn get_certificate( |
||||
&self, |
||||
domain: &str, |
||||
) -> Result<(PrivateKey, Vec<Certificate>), GetCertificateError> { |
||||
let path = self.certificate_path(domain); |
||||
{ |
||||
let file = match util::open_file(path.as_path()).or_unexpected_while("opening_file")? { |
||||
Some(file) => file, |
||||
None => return Err(GetCertificateError::NotFound), |
||||
}; |
||||
|
||||
let stored: StoredPKeyCert = |
||||
serde_json::from_reader(file).or_unexpected_while("parsing json")?; |
||||
|
||||
Ok::<(PrivateKey, Vec<Certificate>), unexpected::Error>(( |
||||
stored.private_key, |
||||
stored.cert, |
||||
)) |
||||
} |
||||
.map_unexpected_while(|| format!("path is {}", path.display())) |
||||
.map_err(|err| err.into()) |
||||
} |
||||
} |
||||
|
||||
#[cfg(test)] |
||||
mod tests { |
||||
use super::*; |
||||
use crate::domain::tls; |
||||
use tempdir::TempDir; |
||||
|
||||
#[test] |
||||
fn account_key() { |
||||
let tmp_dir = TempDir::new("domain_acme_store_account_key").unwrap(); |
||||
let store = FSStore::new(tmp_dir.path()).expect("store created"); |
||||
|
||||
assert!(matches!( |
||||
store.get_account_key(), |
||||
Err::<PrivateKey, GetAccountKeyError>(GetAccountKeyError::NotFound) |
||||
)); |
||||
|
||||
let k = tls::PrivateKey::new(); |
||||
|
||||
store.set_account_key(&k).expect("account private key set"); |
||||
|
||||
assert_eq!( |
||||
k, |
||||
store |
||||
.get_account_key() |
||||
.expect("account private key retrieved") |
||||
); |
||||
} |
||||
) -> unexpected::Result<Option<(PrivateKey, CertificateChain)>>; |
||||
} |
||||
|
@ -0,0 +1,70 @@ |
||||
use std::{fs, path}; |
||||
|
||||
use crate::domain::tls::{CertificateChain, PrivateKey}; |
||||
use crate::error::unexpected::{self, Mappable}; |
||||
use crate::util; |
||||
|
||||
pub struct DirectFSStore { |
||||
key_file_path: path::PathBuf, |
||||
cert_file_path: path::PathBuf, |
||||
} |
||||
|
||||
impl DirectFSStore { |
||||
pub fn new( key_file_path: &path::Path, cert_file_path: &path::Path,) -> Self { |
||||
Self { |
||||
key_file_path: key_file_path.into(), |
||||
cert_file_path: cert_file_path.into(), |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl super::Store for DirectFSStore { |
||||
fn set_certificate( |
||||
&self, |
||||
_domain: &str, |
||||
key: PrivateKey, |
||||
cert: CertificateChain, |
||||
) -> unexpected::Result<()> { |
||||
fs::write(&self.key_file_path, key.to_string()).map_unexpected_while(|| { |
||||
format!("writing private key to {}", &self.key_file_path.display()) |
||||
})?; |
||||
|
||||
fs::write(&self.cert_file_path, cert.to_string()).map_unexpected_while(|| { |
||||
format!("writing certificate to {}", &self.cert_file_path.display()) |
||||
})?; |
||||
|
||||
Ok(()) |
||||
} |
||||
|
||||
fn get_certificate( |
||||
&self, |
||||
_domain: &str, |
||||
) -> unexpected::Result<Option<(PrivateKey, CertificateChain)>> { |
||||
let key: Option<PrivateKey> = |
||||
util::parse_file(&self.key_file_path).map_unexpected_while(|| { |
||||
format!("reading private key from {}", &self.key_file_path.display()) |
||||
})?; |
||||
|
||||
let certs: Option<CertificateChain> = util::parse_file(&self.cert_file_path) |
||||
.map_unexpected_while(|| { |
||||
format!( |
||||
"reading certificate from {}", |
||||
&self.cert_file_path.display() |
||||
) |
||||
})?; |
||||
|
||||
if key.is_none() != certs.is_none() { |
||||
} |
||||
|
||||
match (key, certs) { |
||||
(None, None) => Ok(None), |
||||
(Some(key), Some(certs)) => Ok(Some((key, certs))), |
||||
_ =>
|
||||
Err(unexpected::Error::from(format!( |
||||
"private key file {} and cert file {} are in inconsistent state, one exists but the other doesn't", |
||||
&self.key_file_path.display(), |
||||
&self.cert_file_path.display(), |
||||
).as_str())) |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,78 @@ |
||||
use std::{fs, path}; |
||||
|
||||
use crate::domain::tls::{Certificate, CertificateChain, PrivateKey}; |
||||
use crate::error::unexpected::{self, Mappable}; |
||||
use crate::util; |
||||
|
||||
use serde::{Deserialize, Serialize}; |
||||
|
||||
#[derive(Debug, Serialize, Deserialize)] |
||||
struct StoredPKeyCert { |
||||
private_key: PrivateKey, |
||||
cert: Vec<Certificate>, |
||||
} |
||||
|
||||
pub struct JSONFSStore { |
||||
dir_path: path::PathBuf, |
||||
} |
||||
|
||||
impl JSONFSStore { |
||||
pub fn new(dir_path: &path::Path) -> unexpected::Result<Self> { |
||||
fs::create_dir_all(dir_path).or_unexpected()?; |
||||
Ok(Self { |
||||
dir_path: dir_path.into(), |
||||
}) |
||||
} |
||||
|
||||
fn certificate_path(&self, domain: &str) -> path::PathBuf { |
||||
let mut domain = domain.to_string(); |
||||
domain.push_str(".json"); |
||||
|
||||
self.dir_path.join(domain) |
||||
} |
||||
} |
||||
|
||||
impl super::Store for JSONFSStore { |
||||
fn set_certificate( |
||||
&self, |
||||
domain: &str, |
||||
key: PrivateKey, |
||||
certs: CertificateChain, |
||||
) -> Result<(), unexpected::Error> { |
||||
let to_store = StoredPKeyCert { |
||||
private_key: key, |
||||
cert: certs.into(), |
||||
}; |
||||
|
||||
let path = self.certificate_path(domain); |
||||
{ |
||||
let cert_file = |
||||
fs::File::create(path.as_path()).or_unexpected_while("creating file")?; |
||||
serde_json::to_writer(cert_file, &to_store).or_unexpected_while("writing cert to file") |
||||
} |
||||
.map_unexpected_while(|| format!("path is {}", path.display())) |
||||
} |
||||
|
||||
fn get_certificate( |
||||
&self, |
||||
domain: &str, |
||||
) -> unexpected::Result<Option<(PrivateKey, CertificateChain)>> { |
||||
let path = self.certificate_path(domain); |
||||
{ |
||||
let file = match util::open_file(path.as_path()).or_unexpected_while("opening_file")? { |
||||
Some(file) => file, |
||||
None => return Ok(None), |
||||
}; |
||||
|
||||
let stored: StoredPKeyCert = |
||||
serde_json::from_reader(file).or_unexpected_while("parsing json")?; |
||||
|
||||
unexpected::Result::<Option<(PrivateKey, CertificateChain)>>::Ok(Some(( |
||||
stored.private_key, |
||||
stored.cert.into(), |
||||
))) |
||||
} |
||||
.map_unexpected_while(|| format!("path is {}", path.display())) |
||||
.map_err(|err| err.into()) |
||||
} |
||||
} |
Loading…
Reference in new issue