use crate::types::SharedSizedLockedArray;
use crate::*;
use futures::future::BoxFuture;
use one_err::OneErr;
use std::future::Future;
use std::sync::{Arc, Mutex};
fn is_false(b: impl std::borrow::Borrow<bool>) -> bool {
!b.borrow()
}
pub mod traits {
use super::*;
pub trait AsLairStore: 'static + Send + Sync {
fn get_bidi_ctx_key(&self) -> SharedSizedLockedArray<32>;
fn list_entries(
&self,
) -> BoxFuture<'static, LairResult<Vec<LairEntryInfo>>>;
fn write_entry(
&self,
entry: LairEntry,
) -> BoxFuture<'static, LairResult<()>>;
fn get_entry_by_tag(
&self,
tag: Arc<str>,
) -> BoxFuture<'static, LairResult<LairEntry>>;
fn get_entry_by_ed25519_pub_key(
&self,
ed25519_pub_key: Ed25519PubKey,
) -> BoxFuture<'static, LairResult<LairEntry>>;
fn get_entry_by_x25519_pub_key(
&self,
x25519_pub_key: X25519PubKey,
) -> BoxFuture<'static, LairResult<LairEntry>>;
}
pub trait AsLairStoreFactory: 'static + Send + Sync {
fn connect_to_store(
&self,
unlock_secret: SharedSizedLockedArray<32>,
) -> BoxFuture<'static, LairResult<LairStore>>;
}
}
use traits::*;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct SeedInfo {
pub ed25519_pub_key: Ed25519PubKey,
pub x25519_pub_key: X25519PubKey,
#[serde(skip_serializing_if = "is_false", default)]
pub exportable: bool,
}
pub type CertDigest = BinDataSized<32>;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct CertInfo {
pub sni: Arc<str>,
pub digest: CertDigest,
pub cert: BinData,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(tag = "type", rename_all = "camelCase")]
#[non_exhaustive]
pub enum LairEntryInfo {
Seed {
tag: Arc<str>,
seed_info: SeedInfo,
},
DeepLockedSeed {
tag: Arc<str>,
seed_info: SeedInfo,
},
WkaTlsCert {
tag: Arc<str>,
cert_info: CertInfo,
},
}
pub type Seed = SecretDataSized<32, 49>;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(tag = "type", rename_all = "camelCase")]
#[non_exhaustive]
pub enum LairEntryInner {
Seed {
tag: Arc<str>,
seed_info: SeedInfo,
seed: Seed,
},
DeepLockedSeed {
tag: Arc<str>,
seed_info: SeedInfo,
salt: BinDataSized<16>,
ops_limit: u32,
mem_limit: u32,
seed: Seed,
},
WkaTlsCert {
tag: Arc<str>,
cert_info: CertInfo,
priv_key: SecretData,
},
}
impl LairEntryInner {
pub fn encode(&self) -> LairResult<Box<[u8]>> {
use serde::Serialize;
let mut se =
rmp_serde::encode::Serializer::new(Vec::new()).with_struct_map();
self.serialize(&mut se).map_err(one_err::OneErr::new)?;
Ok(se.into_inner().into_boxed_slice())
}
pub fn decode(bytes: &[u8]) -> LairResult<LairEntryInner> {
let item: LairEntryInner =
rmp_serde::from_read(bytes).map_err(one_err::OneErr::new)?;
Ok(item)
}
pub fn tag(&self) -> Arc<str> {
match self {
Self::Seed { tag, .. } => tag.clone(),
Self::DeepLockedSeed { tag, .. } => tag.clone(),
Self::WkaTlsCert { tag, .. } => tag.clone(),
}
}
}
pub type LairEntry = Arc<LairEntryInner>;
#[derive(Clone)]
pub struct LairStore(pub Arc<dyn AsLairStore>);
impl LairStore {
pub fn get_bidi_ctx_key(&self) -> SharedSizedLockedArray<32> {
AsLairStore::get_bidi_ctx_key(&*self.0)
}
pub fn insert_seed(
&self,
seed: SharedSizedLockedArray<32>,
tag: Arc<str>,
exportable: bool,
) -> impl Future<Output = LairResult<SeedInfo>> + 'static + Send {
let inner = self.0.clone();
async move {
let mut ed_pk = [0; sodoken::sign::PUBLICKEYBYTES];
let mut ed_sk = sodoken::SizedLockedArray::<
{ sodoken::sign::SECRETKEYBYTES },
>::new()?;
sodoken::sign::seed_keypair(
&mut ed_pk,
&mut ed_sk.lock(),
&seed.lock().unwrap().lock(),
)?;
let mut x_pk = [0; sodoken::crypto_box::XSALSA_PUBLICKEYBYTES];
let mut x_sk = sodoken::SizedLockedArray::<
{ sodoken::crypto_box::XSALSA_SECRETKEYBYTES },
>::new()?;
sodoken::crypto_box::xsalsa_seed_keypair(
&mut x_pk,
&mut x_sk.lock(),
&seed.lock().unwrap().lock(),
)?;
let key = inner.get_bidi_ctx_key();
let seed = SecretDataSized::encrypt(key, seed).await?;
let seed_info = SeedInfo {
ed25519_pub_key: ed_pk.into(),
x25519_pub_key: x_pk.into(),
exportable,
};
let entry = LairEntryInner::Seed {
tag,
seed_info: seed_info.clone(),
seed,
};
inner.write_entry(Arc::new(entry)).await?;
Ok(seed_info)
}
}
pub fn new_seed(
&self,
tag: Arc<str>,
exportable: bool,
) -> impl Future<Output = LairResult<SeedInfo>> + 'static + Send {
let this = self.clone();
async move {
let mut seed = sodoken::SizedLockedArray::<32>::new()?;
sodoken::random::randombytes_buf(&mut *seed.lock())?;
let seed = Arc::new(Mutex::new(seed));
this.insert_seed(seed, tag, exportable).await
}
}
pub fn insert_deep_locked_seed(
&self,
seed: SharedSizedLockedArray<32>,
tag: Arc<str>,
ops_limit: u32,
mem_limit: u32,
mut deep_lock_passphrase: sodoken::SizedLockedArray<64>,
exportable: bool,
) -> impl Future<Output = LairResult<SeedInfo>> + 'static + Send {
let inner = self.0.clone();
async move {
let mut ed_pk = [0; sodoken::sign::PUBLICKEYBYTES];
let mut ed_sk = sodoken::SizedLockedArray::<
{ sodoken::sign::SECRETKEYBYTES },
>::new()?;
sodoken::sign::seed_keypair(
&mut ed_pk,
&mut ed_sk.lock(),
&seed.lock().unwrap().lock(),
)?;
let mut x_pk = [0; sodoken::crypto_box::XSALSA_PUBLICKEYBYTES];
let mut x_sk = sodoken::SizedLockedArray::<
{ sodoken::crypto_box::XSALSA_SECRETKEYBYTES },
>::new()?;
sodoken::crypto_box::xsalsa_seed_keypair(
&mut x_pk,
&mut x_sk.lock(),
&seed.lock().unwrap().lock(),
)?;
let (salt, key) = tokio::task::spawn_blocking({
move || -> std::io::Result<([u8; sodoken::argon2::ARGON2_ID_SALTBYTES], sodoken::SizedLockedArray<32>)> {
let mut salt = [0; sodoken::argon2::ARGON2_ID_SALTBYTES];
sodoken::random::randombytes_buf(&mut salt)?;
let mut key = sodoken::SizedLockedArray::<32>::new()?;
sodoken::argon2::blocking_argon2id(
&mut *key.lock(),
&*deep_lock_passphrase.lock(),
&salt,
ops_limit,
mem_limit,
)?;
Ok((salt, key))
}
})
.await
.map_err(OneErr::new)??;
let seed =
SecretDataSized::encrypt(Arc::new(Mutex::new(key)), seed)
.await?;
let seed_info = SeedInfo {
ed25519_pub_key: ed_pk.into(),
x25519_pub_key: x_pk.into(),
exportable,
};
let entry = LairEntryInner::DeepLockedSeed {
tag,
seed_info: seed_info.clone(),
salt: salt.into(),
ops_limit,
mem_limit,
seed,
};
inner.write_entry(Arc::new(entry)).await?;
Ok(seed_info)
}
}
pub fn new_deep_locked_seed(
&self,
tag: Arc<str>,
ops_limit: u32,
mem_limit: u32,
deep_lock_passphrase: sodoken::SizedLockedArray<64>,
exportable: bool,
) -> impl Future<Output = LairResult<SeedInfo>> + 'static + Send {
let this = self.clone();
async move {
let mut seed = sodoken::SizedLockedArray::<32>::new()?;
sodoken::random::randombytes_buf(&mut *seed.lock())?;
let seed = Arc::new(Mutex::new(seed));
this.insert_deep_locked_seed(
seed,
tag,
ops_limit,
mem_limit,
deep_lock_passphrase,
exportable,
)
.await
}
}
pub fn new_wka_tls_cert(
&self,
tag: Arc<str>,
) -> impl Future<Output = LairResult<CertInfo>> + 'static + Send {
let inner = self.0.clone();
async move {
use crate::internal::tls::*;
let TlsCertGenResult {
sni,
priv_key,
cert,
digest,
} = tls_cert_self_signed_new().await?;
let key = inner.get_bidi_ctx_key();
let priv_key = SecretData::encrypt(key, priv_key).await?;
let cert_info = CertInfo {
sni,
digest: digest.into(),
cert: cert.into(),
};
let entry = LairEntryInner::WkaTlsCert {
tag,
cert_info: cert_info.clone(),
priv_key,
};
inner.write_entry(Arc::new(entry)).await?;
Ok(cert_info)
}
}
pub fn list_entries(
&self,
) -> impl Future<Output = LairResult<Vec<LairEntryInfo>>> + 'static + Send
{
AsLairStore::list_entries(&*self.0)
}
pub fn get_entry_by_tag(
&self,
tag: Arc<str>,
) -> impl Future<Output = LairResult<LairEntry>> + 'static + Send {
AsLairStore::get_entry_by_tag(&*self.0, tag)
}
pub fn get_entry_by_ed25519_pub_key(
&self,
ed25519_pub_key: Ed25519PubKey,
) -> impl Future<Output = LairResult<LairEntry>> + 'static + Send {
AsLairStore::get_entry_by_ed25519_pub_key(&*self.0, ed25519_pub_key)
}
pub fn get_entry_by_x25519_pub_key(
&self,
x25519_pub_key: X25519PubKey,
) -> impl Future<Output = LairResult<LairEntry>> + 'static + Send {
AsLairStore::get_entry_by_x25519_pub_key(&*self.0, x25519_pub_key)
}
}
#[derive(Clone)]
pub struct LairStoreFactory(pub Arc<dyn AsLairStoreFactory>);
impl LairStoreFactory {
pub fn connect_to_store(
&self,
unlock_secret: sodoken::SizedLockedArray<32>,
) -> impl Future<Output = LairResult<LairStore>> + 'static + Send {
AsLairStoreFactory::connect_to_store(
&*self.0,
Arc::new(Mutex::new(unlock_secret)),
)
}
}