use std::{borrow::Borrow, fmt::Display, str::FromStr}; use chacha20::cipher::{generic_array::GenericArray, ArrayLength}; use rand::{distributions, Rng}; use sha3::{Digest, Sha3_256}; use crate::{error::Error, PHRASE_LENGTH, SALT_LENGTH}; #[derive(Debug, PartialEq)] pub(crate) struct Phrase(String); #[derive(Debug, PartialEq, Eq, Clone)] pub(crate) struct Id(String); #[derive(Debug, Clone, PartialEq)] pub(crate) struct IdSalt(Vec); #[derive(Debug, PartialEq)] pub(crate) struct Key(Vec); #[derive(Debug, Clone, PartialEq)] pub(crate) struct KeySalt(Vec); #[derive(Debug, PartialEq)] pub(crate) struct Nonce(Vec); impl Phrase { pub(crate) fn random() -> Self { let phrase = rand::thread_rng() .sample_iter(distributions::Alphanumeric) .take(PHRASE_LENGTH) .map(char::from) .collect::(); Self(phrase) } } impl FromStr for Phrase { type Err = Error; fn from_str(s: &str) -> Result { if s.chars().any(|x| !x.is_ascii_alphanumeric()) { Err(Error::PhraseInvalid) } else { Ok(Self(s.to_string())) } } } impl Display for Phrase { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.0.fmt(f) } } impl Id { pub(crate) fn from_phrase(phrase: &Phrase, salt: &IdSalt) -> Self { let mut hasher = Sha3_256::new(); hasher.update(&phrase.0); hasher.update(&salt.0); let id = hex::encode(hasher.finalize()); Self(id) } pub(crate) fn from_str(s: &str) -> Self { Self(s.to_string()) } pub(crate) fn raw(&self) -> &str { &self.0 } } impl Display for Id { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.0.fmt(f) } } impl IdSalt { pub(crate) fn random() -> Self { let salt = rand::thread_rng() .sample_iter(distributions::Standard) .take(SALT_LENGTH) .collect(); Self(salt) } pub(crate) fn raw(&self) -> &[u8] { &self.0 } } impl FromStr for IdSalt { type Err = hex::FromHexError; fn from_str(s: &str) -> Result { Ok(Self(hex::decode(s)?)) } } impl Key { pub(crate) fn from_phrase(phrase: &Phrase, salt: &KeySalt) -> Self { let mut hasher = Sha3_256::new(); hasher.update(&phrase.0); hasher.update(&salt.0); Self(hasher.finalize().to_vec()) } } impl> Borrow> for Key { fn borrow(&self) -> &GenericArray { GenericArray::::from_slice(&self.0) } } impl KeySalt { pub(crate) fn random() -> Self { let salt = rand::thread_rng() .sample_iter(distributions::Standard) .take(SALT_LENGTH) .collect(); Self(salt) } pub(crate) fn raw(&self) -> &[u8] { &self.0 } } impl FromStr for KeySalt { type Err = hex::FromHexError; fn from_str(s: &str) -> Result { Ok(Self(hex::decode(s)?)) } } impl Nonce { pub(crate) fn random() -> Self { // generate a 12 byte / 96 bit nonce for chacha20 as defined in rfc7539 let nonce = rand::thread_rng() .sample_iter(distributions::Standard) .take(12) .collect(); Self(nonce) } pub(crate) fn from_hex(hex_value: &str) -> Result { Ok(Self(hex::decode(hex_value)?)) } pub(crate) fn to_hex(&self) -> String { hex::encode(&self.0) } } impl> Borrow> for Nonce { fn borrow(&self) -> &GenericArray { GenericArray::from_slice(&self.0) } } #[cfg(test)] mod test { use crate::{ util::{Id, IdSalt, Key, KeySalt, Nonce, Phrase}, PHRASE_LENGTH, SALT_LENGTH, }; #[test] fn phrase() { assert_eq!(PHRASE_LENGTH, Phrase::random().0.len()); assert_ne!(Phrase::random(), Phrase::random()); } #[test] fn id() { let phrase = Phrase::random(); let salt = IdSalt::random(); let phrase2 = Phrase::random(); let salt2 = IdSalt::random(); assert_eq!( Id::from_phrase(&phrase, &salt), Id::from_phrase(&phrase, &salt) ); assert_ne!( Id::from_phrase(&phrase, &salt), Id::from_phrase(&phrase, &salt2) ); assert_ne!( Id::from_phrase(&phrase, &salt), Id::from_phrase(&phrase2, &salt) ); } #[test] fn key() { let phrase = Phrase::random(); let salt = KeySalt::random(); let phrase2 = Phrase::random(); let salt2 = KeySalt::random(); assert_eq!( Key::from_phrase(&phrase, &salt), Key::from_phrase(&phrase, &salt) ); assert_ne!( Key::from_phrase(&phrase, &salt), Key::from_phrase(&phrase, &salt2) ); assert_ne!( Key::from_phrase(&phrase, &salt), Key::from_phrase(&phrase2, &salt) ); } #[test] #[allow(clippy::unwrap_used)] fn key_id_collision() { let phrase = Phrase::random(); let id_salt = IdSalt::random(); let key_salt = KeySalt::random(); assert_ne!( hex::decode(Id::from_phrase(&phrase, &id_salt).0).unwrap(), Key::from_phrase(&phrase, &key_salt).0 ); } #[test] fn id_salt() { assert_eq!(SALT_LENGTH, IdSalt::random().0.len()); assert_ne!(IdSalt::random(), IdSalt::random()); } #[test] fn key_salt() { assert_eq!(SALT_LENGTH, KeySalt::random().0.len()); assert_ne!(KeySalt::random(), KeySalt::random()); } #[test] fn nonce() { assert_eq!(12, Nonce::random().0.len()); assert_ne!(Nonce::random(), Nonce::random()); } }