mod errors;
mod serial;
mod shared;
#[cfg(test)]
mod tests;
use self::shared::{CryptoKeys, EncryptedBlob, Vault};
pub use crate::errors::{Error, ErrorKind};
pub use crate::serial::{BinaryDeserializable, BinarySerializable};
use openssl::rand;
use std::fs::File;
use std::path::{Path, PathBuf};
#[non_exhaustive]
pub enum KeySource<'a, P: AsRef<Path> = &'a Path> {
File(P),
Password(&'a str),
Csprng,
}
pub struct SecretsManager {
vault: Vault,
path: PathBuf,
cryptokeys: CryptoKeys,
}
impl SecretsManager {
fn create_sentinel(keys: &CryptoKeys) -> EncryptedBlob {
let mut random = [0u8; shared::IV_SIZE * 2];
rand::rand_bytes(&mut random).expect("Failed to create sentinel");
EncryptedBlob::encrypt(&keys, &random)
}
pub fn new<P1: AsRef<Path>, P2: AsRef<Path>>(
path: P1,
key_source: KeySource<P2>,
) -> Result<Self, Error> {
let path = path.as_ref();
let mut vault = Vault::new();
let keys = key_source.extract_keys(&vault.iv)?;
vault.sentinel = Some(Self::create_sentinel(&keys));
Ok(SecretsManager {
cryptokeys: keys,
path: PathBuf::from(path),
vault,
})
}
pub fn load<P1: AsRef<Path>, P2: AsRef<Path>>(
path: P1,
key_source: KeySource<P2>,
) -> Result<Self, Error> {
match &key_source {
KeySource::Csprng => debug_assert!(false,
"It is incorrect to call SecretsManager::load() except with an existing key source!"),
_ => {}
};
let path = path.as_ref();
let mut vault = Vault::from_file(path)?;
let keys = key_source.extract_keys(&vault.iv)?;
if let Some(ref sentinel) = vault.sentinel {
sentinel.decrypt(&keys)?;
} else {
vault.sentinel = Some(Self::create_sentinel(&keys));
}
let sman = SecretsManager {
cryptokeys: keys,
path: PathBuf::from(path),
vault,
};
Ok(sman)
}
pub fn save(&self) -> Result<(), Error> {
self.vault.save(&self.path)
}
pub fn export_keyfile<P: AsRef<Path>>(&self, path: P) -> Result<(), Error> {
self.cryptokeys.export(path)
}
pub fn get<T: BinaryDeserializable>(&self, name: &str) -> Result<T, Error> {
match self.vault.secrets.get(name) {
None => ErrorKind::SecretNotFound.into(),
Some(blob) => {
let decrypted = blob.decrypt(&self.cryptokeys)?;
T::deserialize(decrypted)
.map_err(|e| Error::from_inner(ErrorKind::DeserializationError, e))
}
}
}
pub fn set<T: BinarySerializable>(&mut self, name: &str, value: T) -> () {
let encrypted = EncryptedBlob::encrypt(&self.cryptokeys, T::serialize(&value));
self.vault.secrets.insert(name.to_string(), encrypted);
}
pub fn remove(&mut self, name: &str) -> Result<(), Error> {
self.vault
.secrets
.remove(name)
.ok_or(ErrorKind::SecretNotFound.into())
.map(|_| ())
}
pub fn keys<'a>(&'a self) -> impl Iterator<Item = &'a str> {
self.vault.secrets.keys().map(|s| s.as_str())
}
}
impl<'a, P: AsRef<Path>> KeySource<'a, P> {
fn extract_keys(&self, iv: &[u8; shared::IV_SIZE]) -> Result<CryptoKeys, Error> {
match &self {
KeySource::Csprng => {
let mut buffer = [0u8; shared::KEY_COUNT * shared::KEY_LENGTH];
rand::rand_bytes(&mut buffer).expect("Key generation failure!");
CryptoKeys::import(&buffer[..])
}
KeySource::File(path) => {
let attr = std::fs::metadata(path)?;
if attr.len() as usize != shared::KEY_COUNT * shared::KEY_LENGTH {
return ErrorKind::InvalidKeyfile.into();
}
let file = File::open(path)?;
CryptoKeys::import(&file)
}
KeySource::Password(password) => {
use openssl::pkcs5::pbkdf2_hmac;
let mut key_data = [0u8; shared::KEY_COUNT * shared::KEY_LENGTH];
pbkdf2_hmac(
password.as_bytes(),
iv,
shared::PBKDF2_ROUNDS,
shared::PBKDF2_DIGEST(),
&mut key_data,
)
.expect("PBKDF2 key generation failed!");
CryptoKeys::import(&key_data[..])
}
}
}
}