#![forbid(unsafe_code)]
#![cfg_attr(
test,
allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)
)]
pub mod error;
#[cfg(feature = "aws-kms")]
pub mod aws;
#[cfg(feature = "azure-key-vault")]
pub mod azure;
#[cfg(feature = "gcp-kms")]
pub mod gcp;
#[cfg(feature = "hashicorp-vault")]
pub mod vault;
use std::collections::BTreeMap;
use std::fmt;
use hmac::{Hmac, Mac};
use sha2::Sha256;
use zeroize::Zeroize;
pub use error::PepperError;
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct KeyVersion(u32);
impl KeyVersion {
#[must_use]
pub const fn new(v: u32) -> Self {
Self(v)
}
#[must_use]
pub const fn get(self) -> u32 {
self.0
}
}
impl Default for KeyVersion {
fn default() -> Self {
Self(1)
}
}
impl fmt::Display for KeyVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
pub trait Pepper: fmt::Debug + Send + Sync {
fn apply(
&self,
version: KeyVersion,
password: &[u8],
) -> Result<[u8; 32], PepperError>;
fn current(&self) -> KeyVersion;
}
pub struct LocalPepper {
keys: BTreeMap<KeyVersion, Vec<u8>>,
current: KeyVersion,
}
impl LocalPepper {
#[must_use]
pub fn builder() -> LocalPepperBuilder {
LocalPepperBuilder::default()
}
#[must_use]
pub fn versions(&self) -> Vec<KeyVersion> {
self.keys.keys().copied().collect()
}
}
impl Pepper for LocalPepper {
fn apply(
&self,
version: KeyVersion,
password: &[u8],
) -> Result<[u8; 32], PepperError> {
let key = self
.keys
.get(&version)
.ok_or(PepperError::UnknownVersion(version))?;
let mut mac = <Hmac<Sha256> as Mac>::new_from_slice(key)
.map_err(|e| PepperError::Backend(e.to_string()))?;
mac.update(password);
let tag = mac.finalize().into_bytes();
let mut out = [0u8; 32];
out.copy_from_slice(&tag);
Ok(out)
}
fn current(&self) -> KeyVersion {
self.current
}
}
impl fmt::Debug for LocalPepper {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("LocalPepper")
.field("versions", &self.keys.keys().collect::<Vec<_>>())
.field("current", &self.current)
.finish()
}
}
impl Drop for LocalPepper {
fn drop(&mut self) {
for k in self.keys.values_mut() {
k.zeroize();
}
}
}
#[derive(Debug, Default)]
pub struct LocalPepperBuilder {
keys: BTreeMap<KeyVersion, Vec<u8>>,
current: Option<KeyVersion>,
}
impl LocalPepperBuilder {
#[must_use]
pub fn add(mut self, version: KeyVersion, key: Vec<u8>) -> Self {
let _ = self.keys.insert(version, key);
self
}
#[must_use]
pub fn current(mut self, version: KeyVersion) -> Self {
self.current = Some(version);
self
}
pub fn build(self) -> Result<LocalPepper, PepperError> {
if self.keys.is_empty() {
return Err(PepperError::EmptyKeyset);
}
for (v, k) in &self.keys {
if k.len() < 16 {
return Err(PepperError::KeyTooShort {
version: *v,
actual: k.len(),
minimum: 16,
});
}
}
let current = self
.current
.or_else(|| self.keys.keys().last().copied())
.ok_or(PepperError::EmptyKeyset)?;
if !self.keys.contains_key(¤t) {
return Err(PepperError::UnknownVersion(current));
}
Ok(LocalPepper {
keys: self.keys,
current,
})
}
}