use std::collections::HashMap;
use std::fs;
use std::io::{self, BufRead, Read, Write};
use std::os::unix::fs::DirBuilderExt;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use log::{info, warn};
use openssl::{
pkey::{Private, Public},
rsa::Rsa,
};
use serde::{Deserialize, Serialize};
use crate::crypt::master_key::MasterKey;
use crate::support::error::Error;
use crate::support::file_ops::{self, IgnoreKinds};
use crate::support::safe_name::is_safe_name;
const RSA_BITS: u32 = 4096;
const MAX_KEY_FILE_SIZE: u64 = 256 * 1024;
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct KeyStoreConfig {
pub internal_key_pattern: String,
pub external_key_pattern: String,
}
impl Default for KeyStoreConfig {
fn default() -> Self {
KeyStoreConfig {
internal_key_pattern: "internal-%Y-%m".to_owned(),
external_key_pattern: "external-%Y-%m".to_owned(),
}
}
}
pub struct KeyStore {
log_prefix: String,
root: PathBuf,
tmp: PathBuf,
master_key: Option<Arc<MasterKey>>,
private_keys: HashMap<String, Arc<Rsa<Private>>>,
public_key: Option<(String, Rsa<Public>)>,
preferred_private_key: Option<String>,
rsa_bits: u32,
}
impl KeyStore {
pub fn new(
log_prefix: String,
root: PathBuf,
tmp: PathBuf,
master_key: Option<Arc<MasterKey>>,
) -> Self {
KeyStore {
log_prefix,
root,
tmp,
master_key,
private_keys: HashMap::new(),
public_key: None,
preferred_private_key: None,
rsa_bits: RSA_BITS,
}
}
#[cfg(test)]
pub fn set_rsa_bits(&mut self, bits: u32) {
self.rsa_bits = bits;
}
pub fn init(&mut self, config: &KeyStoreConfig) -> Result<(), Error> {
fs::DirBuilder::new()
.mode(0o750)
.create(&self.root)
.ignore_already_exists()?;
let now = chrono::Utc::now();
let preferred_internal =
now.format(&config.internal_key_pattern).to_string();
let preferred_external =
now.format(&config.external_key_pattern).to_string();
let created_external =
self.create_key_if_not_exists(&preferred_external)?;
self.create_key_if_not_exists(&preferred_internal)?;
self.preferred_private_key = Some(preferred_internal);
if let Some(created_external) = created_external {
let mut public_data = Vec::<u8>::new();
writeln!(public_data, "{}", preferred_external)?;
let mut pem = created_external.public_key_to_pem()?;
public_data.append(&mut pem);
file_ops::spit(
&self.tmp,
self.root.join("public"),
true,
0o440,
&public_data,
)?;
info!(
"{} Default public key is now '{}'",
self.log_prefix, preferred_external
);
}
Ok(())
}
fn create_key_if_not_exists(
&mut self,
name: &str,
) -> Result<Option<Rsa<Private>>, Error> {
if !is_safe_name(name) {
return Err(Error::UnsafeName);
}
let master_key = self
.master_key
.as_ref()
.ok_or(Error::MasterKeyUnavailable)?;
let filename = format!("{}.pem", name);
let path = self.root.join(&filename);
if path.is_file() {
return Ok(None);
}
info!(
"{} Generating new {}-bit RSA key '{}'",
self.log_prefix, self.rsa_bits, name
);
let generated_key = Rsa::generate(self.rsa_bits)?;
let generated_key_bytes = generated_key.private_key_to_pem_passphrase(
openssl::symm::Cipher::aes_128_cbc(),
master_key.pem_passphrase(name).as_bytes(),
)?;
match file_ops::spit(
&self.tmp,
path,
false,
0o400,
&generated_key_bytes,
) {
Ok(_) => {
info!(
"{} Created '{}' successfully",
self.log_prefix, filename
);
Ok(Some(generated_key))
}
Err(e) if io::ErrorKind::AlreadyExists == e.kind() => {
info!("{} Lost race to create '{}'", self.log_prefix, filename);
Ok(None)
}
Err(e) => {
warn!(
"{} Failed to create '{}': {}",
self.log_prefix, filename, e
);
Err(e.into())
}
}
}
pub fn get_default_public_key(
&mut self,
) -> Result<(&str, &Rsa<Public>), Error> {
if let Some((ref name, ref key)) = self.public_key {
return Ok((name, key));
}
if let (&Some(ref master_key), &Some(ref name)) =
(&self.master_key, &self.preferred_private_key)
{
let priv_key = load_private_key(
master_key,
name,
&self.root,
&mut self.private_keys,
)?;
let pub_key = Rsa::from_public_components(
priv_key.n().to_owned()?,
priv_key.e().to_owned()?,
)?;
self.public_key = Some((name.to_owned(), pub_key));
} else {
let mut reader = io::BufReader::new(
fs::File::open(self.root.join("public"))?
.take(MAX_KEY_FILE_SIZE),
);
let mut name = String::new();
reader.read_line(&mut name)?;
name.truncate(name.len() - 1);
if !is_safe_name(&name) {
return Err(Error::UnsafeName);
}
let mut pem_data = Vec::new();
reader.read_to_end(&mut pem_data)?;
let pub_key = Rsa::public_key_from_pem(&pem_data)?;
self.public_key = Some((name, pub_key));
}
Ok(self
.public_key
.as_ref()
.map(|&(ref name, ref key)| (&**name, key))
.unwrap())
}
pub fn get_private_key(
&mut self,
name: &str,
) -> Result<Arc<Rsa<Private>>, Error> {
let master_key = self
.master_key
.as_ref()
.ok_or(Error::MasterKeyUnavailable)?;
match load_private_key(
master_key,
name,
&self.root,
&mut self.private_keys,
) {
Ok(k) => Ok(k),
Err(Error::Io(e)) if io::ErrorKind::NotFound == e.kind() => {
Err(Error::NamedKeyNotFound)
}
Err(e) => Err(e),
}
}
pub fn clear_cache(&mut self) {
self.public_key = None;
self.private_keys.clear();
}
}
fn load_private_key<'a>(
master_key: &MasterKey,
name: &str,
root: &Path,
cache: &'a mut HashMap<String, Arc<Rsa<Private>>>,
) -> Result<Arc<Rsa<Private>>, Error> {
if !is_safe_name(name) {
return Err(Error::UnsafeName);
}
if cache.contains_key(name) {
return Ok(Arc::clone(cache.get(name).unwrap()));
}
let mut pem_data = Vec::new();
fs::File::open(root.join(format!("{}.pem", name)))?
.take(MAX_KEY_FILE_SIZE)
.read_to_end(&mut pem_data)?;
let priv_key = Rsa::private_key_from_pem_passphrase(
&pem_data,
master_key.pem_passphrase(name).as_bytes(),
)?;
Ok(Arc::clone(
cache
.entry(name.to_owned())
.or_insert_with(|| Arc::new(priv_key)),
))
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_key_store() {
let root = tempfile::tempdir().unwrap();
let tmp = tempfile::tempdir().unwrap();
let master_key = Arc::new(MasterKey::new());
let mut authed_store = KeyStore::new(
"authed".to_owned(),
root.path().to_owned(),
tmp.path().to_owned(),
Some(Arc::clone(&master_key)),
);
let mut anon_store = KeyStore::new(
"anon".to_owned(),
root.path().to_owned(),
tmp.path().to_owned(),
None,
);
authed_store
.init(&KeyStoreConfig {
internal_key_pattern: "internal1".to_owned(),
external_key_pattern: "external1".to_owned(),
})
.unwrap();
authed_store
.init(&KeyStoreConfig {
internal_key_pattern: "internal1".to_owned(),
external_key_pattern: "external1".to_owned(),
})
.unwrap();
{
let (name, _) = anon_store.get_default_public_key().unwrap();
assert_eq!("external1", name);
}
{
let (name, _) = authed_store.get_default_public_key().unwrap();
assert_eq!("internal1", name);
}
authed_store.get_private_key("internal1").unwrap();
authed_store.get_private_key("external1").unwrap();
authed_store.clear_cache();
anon_store.clear_cache();
authed_store
.init(&KeyStoreConfig {
internal_key_pattern: "internal2".to_owned(),
external_key_pattern: "external2".to_owned(),
})
.unwrap();
{
let (name, _) = anon_store.get_default_public_key().unwrap();
assert_eq!("external2", name);
}
{
let (name, _) = authed_store.get_default_public_key().unwrap();
assert_eq!("internal2", name);
}
authed_store.get_private_key("internal1").unwrap();
authed_store.get_private_key("external1").unwrap();
authed_store.get_private_key("internal2").unwrap();
authed_store.get_private_key("external2").unwrap();
assert!(matches!(
authed_store.get_private_key("nx"),
Err(Error::NamedKeyNotFound)
));
assert!(matches!(
anon_store.get_private_key("internal1"),
Err(Error::MasterKeyUnavailable)
));
}
}