use better_default::Default;
use derivative::Derivative;
use openssl::{
hash::{hash, MessageDigest},
nid::Nid,
pkey::PKey,
sign::Signer,
};
use super::generate_uuid;
use crate::{
errors::RvError,
storage::{Storage, StorageEntry},
};
static DEFAULT_LOCATION: &str = "salt";
#[derive(Debug, Clone, Default)]
pub struct Salt {
pub config: Config,
#[default(generate_uuid())]
pub salt: String,
#[default(true)]
pub generated: bool,
}
#[derive(Derivative, Default)]
#[derivative(Debug, Clone)]
pub struct Config {
#[default(DEFAULT_LOCATION.to_string())]
pub location: String,
#[derivative(Debug = "ignore")]
#[default(MessageDigest::sha256())]
pub hash_type: MessageDigest,
#[derivative(Debug = "ignore")]
#[default(MessageDigest::sha256())]
pub hmac_type: MessageDigest,
}
impl Salt {
pub fn new(storage: Option<&dyn Storage>, config: Option<&Config>) -> Result<Self, RvError> {
let mut salt = Salt::default();
if let Some(c) = config {
if salt.config.location != c.location && c.location != "" {
salt.config.location = c.location.clone();
}
if salt.config.hash_type != c.hash_type {
salt.config.hash_type = c.hash_type.clone();
}
if salt.config.hmac_type != c.hmac_type {
salt.config.hmac_type = c.hmac_type.clone();
}
}
if let Some(s) = storage {
if let Some(raw) = s.get(&salt.config.location)? {
salt.salt = String::from_utf8_lossy(&raw.value).to_string();
salt.generated = false;
} else {
let entry = StorageEntry { key: salt.config.location.clone(), value: salt.salt.as_bytes().to_vec() };
s.put(&entry)?;
}
}
Ok(salt)
}
pub fn new_nonpersistent() -> Self {
let mut salt = Salt::default();
salt.config.location = "".to_string();
salt
}
pub fn get_hmac(&self, data: &str) -> Result<String, RvError> {
let pkey = PKey::hmac(self.salt.as_bytes())?;
let mut signer = Signer::new(self.config.hmac_type, &pkey)?;
signer.update(data.as_bytes())?;
let hmac = signer.sign_to_vec()?;
Ok(hex::encode(hmac.as_slice()))
}
pub fn get_identified_hamc(&self, data: &str) -> Result<String, RvError> {
let hmac_type = match self.config.hmac_type.type_() {
Nid::SHA256 => "hmac-sha256",
Nid::SM3 => "hmac-sm3",
Nid::MD5 => "hmac-md5",
_ => "hmac-unknown",
};
let hmac = self.get_hmac(data)?;
Ok(format!("{}:{}", hmac_type, hmac))
}
pub fn get_hash(&self, data: &str) -> Result<String, RvError> {
let ret = hash(self.config.hash_type, data.as_bytes())?;
let bytes = ret.to_vec();
Ok(hex::encode(bytes.as_slice()))
}
pub fn salt_id(&self, id: &str) -> Result<String, RvError> {
let comb = format!("{}{}", self.salt, id);
self.get_hash(&comb)
}
pub fn did_generate(&self) -> bool {
self.generated
}
}
#[cfg(test)]
mod test {
use std::sync::Arc;
use rand::{thread_rng, Rng};
use super::*;
use crate::{
storage::{barrier::SecurityBarrier, barrier_aes_gcm, barrier_view},
test_utils::test_backend,
};
#[test]
fn test_salt() {
let backend = test_backend("test_salt");
let mut key = vec![0u8; 32];
thread_rng().fill(key.as_mut_slice());
let aes_gcm_view = barrier_aes_gcm::AESGCMBarrier::new(Arc::clone(&backend));
let init = aes_gcm_view.init(key.as_slice());
assert!(init.is_ok());
assert!(aes_gcm_view.unseal(key.as_slice()).is_ok());
let view = barrier_view::BarrierView::new(Arc::new(aes_gcm_view), "test");
let salt = Salt::new(Some(&view), None);
assert!(salt.is_ok());
let salt = salt.unwrap();
assert!(salt.did_generate());
let ss = view.get(DEFAULT_LOCATION);
assert!(ss.is_ok());
assert!(ss.unwrap().is_some());
let salt2 = Salt::new(Some(&view), Some(&salt.config));
assert!(salt2.is_ok());
let salt2 = salt2.unwrap();
assert!(!salt2.did_generate());
assert_eq!(salt.salt, salt2.salt);
let id = "foobarbaz";
let sid1 = salt.salt_id(id);
let sid2 = salt2.salt_id(id);
assert!(sid1.is_ok());
assert!(sid2.is_ok());
let sid1 = sid1.unwrap();
let sid2 = sid2.unwrap();
assert_eq!(sid1, sid2);
assert_eq!(sid1.len(), salt.config.hash_type.size() * 2);
}
}