use crate::SharedSizedLockedArray;
use one_err::*;
use std::future::Future;
use std::sync::{Arc, Mutex};
const KDF_CONTEXT: &[u8; 8] = b"SeedBndl";
#[derive(Clone)]
pub struct UnlockedSeedBundle {
seed: SharedSizedLockedArray<32>,
sign_pub_key: Arc<[u8; sodoken::sign::PUBLICKEYBYTES]>,
sign_sec_key: SharedSizedLockedArray<{ sodoken::sign::SECRETKEYBYTES }>,
app_data: Arc<[u8]>,
}
impl UnlockedSeedBundle {
pub(crate) async fn priv_from_seed(
seed: sodoken::SizedLockedArray<32>,
) -> Result<Self, OneErr> {
let seed = Arc::new(Mutex::new(seed));
let mut pk = [0; sodoken::sign::PUBLICKEYBYTES];
let mut sk = sodoken::SizedLockedArray::<
{ sodoken::sign::SECRETKEYBYTES },
>::new()?;
sodoken::sign::seed_keypair(
&mut pk,
&mut sk.lock(),
&seed.lock().unwrap().lock(),
)?;
Ok(Self {
seed,
sign_pub_key: pk.into(),
sign_sec_key: Arc::new(Mutex::new(sk)),
app_data: Arc::new([]),
})
}
pub async fn new_random() -> Result<Self, OneErr> {
let mut seed = sodoken::SizedLockedArray::new()?;
sodoken::random::randombytes_buf(&mut *seed.lock())?;
Self::priv_from_seed(seed).await
}
pub async fn from_locked(
bytes: &[u8],
) -> Result<Vec<crate::LockedSeedCipher>, OneErr> {
crate::LockedSeedCipher::from_locked(bytes)
}
pub fn get_seed(&self) -> SharedSizedLockedArray<32> {
self.seed.clone()
}
pub fn derive(
&self,
index: u32,
) -> impl Future<Output = Result<Self, OneErr>> + 'static + Send {
let seed = self.seed.clone();
async move {
let mut new_seed = sodoken::SizedLockedArray::new()?;
sodoken::kdf::derive_from_key(
new_seed.lock().as_mut_slice(),
index as u64,
KDF_CONTEXT,
&seed.lock().unwrap().lock(),
)?;
Self::priv_from_seed(new_seed).await
}
}
pub fn get_sign_pub_key(&self) -> Arc<[u8; sodoken::sign::PUBLICKEYBYTES]> {
self.sign_pub_key.clone()
}
pub fn sign_detached(
&self,
message: Arc<[u8]>,
) -> impl Future<Output = Result<[u8; sodoken::sign::SIGNATUREBYTES], OneErr>>
+ 'static
+ Send {
let sign_sec_key = self.sign_sec_key.clone();
async move {
let mut sig = [0; sodoken::sign::SIGNATUREBYTES];
sodoken::sign::sign_detached(
&mut sig,
&message,
&sign_sec_key.lock().unwrap().lock(),
)?;
Ok(sig)
}
}
pub fn get_app_data_bytes(&self) -> &[u8] {
&self.app_data
}
pub fn set_app_data_bytes<B>(&mut self, app_data: B)
where
B: Into<Arc<[u8]>>,
{
self.app_data = app_data.into();
}
pub fn get_app_data<T>(&self) -> Result<T, OneErr>
where
T: 'static + for<'de> serde::Deserialize<'de>,
{
rmp_serde::from_slice(&self.app_data).map_err(OneErr::new)
}
pub fn set_app_data<T>(&mut self, t: &T) -> Result<(), OneErr>
where
T: serde::Serialize,
{
let mut se =
rmp_serde::encode::Serializer::new(Vec::new()).with_struct_map();
t.serialize(&mut se).map_err(OneErr::new)?;
self.app_data = se.into_inner().into_boxed_slice().into();
Ok(())
}
pub fn lock(&self) -> crate::SeedCipherBuilder {
crate::SeedCipherBuilder::new(self.seed.clone(), self.app_data.clone())
}
}
#[cfg(test)]
mod tests {
use crate::*;
use std::sync::{Arc, Mutex};
#[tokio::test(flavor = "multi_thread")]
async fn test_pwhash_cipher() {
let mut seed = UnlockedSeedBundle::new_random().await.unwrap();
seed.set_app_data(&42_isize).unwrap();
let orig_pub_key = seed.get_sign_pub_key();
let passphrase = Arc::new(Mutex::new(sodoken::LockedArray::from(
b"test-passphrase".to_vec(),
)));
let cipher = PwHashLimits::Minimum
.with_exec(|| seed.lock().add_pwhash_cipher(passphrase.clone()));
let encoded = cipher.lock().await.unwrap();
match UnlockedSeedBundle::from_locked(&encoded)
.await
.unwrap()
.remove(0)
{
LockedSeedCipher::PwHash(cipher) => {
let seed = cipher.unlock(passphrase).await.unwrap();
assert_eq!(&orig_pub_key, &seed.get_sign_pub_key());
assert_eq!(42, seed.get_app_data::<isize>().unwrap());
}
oth => panic!("unexpected cipher: {:?}", oth),
}
}
#[tokio::test(flavor = "multi_thread")]
async fn test_security_questions_cipher() {
let mut seed = UnlockedSeedBundle::new_random().await.unwrap();
seed.set_app_data(&42_isize).unwrap();
let orig_pub_key = seed.get_sign_pub_key();
let q1 = "What Color?";
let q2 = "What Flavor?";
let q3 = "What Hair?";
let a1 = sodoken::LockedArray::from(b"blUe".to_vec());
let a2 = sodoken::LockedArray::from(b"spicy ".to_vec());
let a3 = sodoken::LockedArray::from(b" big".to_vec());
let cipher = PwHashLimits::Minimum.with_exec(|| {
let q_list = (q1.to_string(), q2.to_string(), q3.to_string());
let a_list = (a1, a2, a3);
seed.lock().add_security_question_cipher(q_list, a_list)
});
let encoded = cipher.lock().await.unwrap();
match UnlockedSeedBundle::from_locked(&encoded)
.await
.unwrap()
.remove(0)
{
LockedSeedCipher::SecurityQuestions(cipher) => {
assert_eq!(q1, cipher.get_question_list().0);
assert_eq!(q2, cipher.get_question_list().1);
assert_eq!(q3, cipher.get_question_list().2);
let a1 = sodoken::LockedArray::from(b" blue".to_vec());
let a2 = sodoken::LockedArray::from(b" spicy".to_vec());
let a3 = sodoken::LockedArray::from(b" bIg".to_vec());
let seed = cipher.unlock((a1, a2, a3)).await.unwrap();
assert_eq!(&orig_pub_key, &seed.get_sign_pub_key());
assert_eq!(42, seed.get_app_data::<isize>().unwrap());
}
oth => panic!("unexpected cipher: {:?}", oth),
}
}
}