#[cfg(feature = "blocking")]
use crate::blocking::BlockingIronOxide;
use crate::{
IronOxide, IronOxideErr, Result,
document::{
DocumentEncryptOpts,
advanced::{DocumentAdvancedOps, DocumentEncryptUnmanagedResult},
},
group::GroupId,
internal::take_lock,
};
use futures::Future;
pub use ironcore_search_helpers::transliterate_string;
use ironcore_search_helpers::{
generate_hashes_for_string, generate_hashes_for_string_with_padding,
};
use rand::Rng as _;
use recrypt::api::DefaultRng;
use serde::{Deserialize, Serialize};
use std::{
collections::HashSet,
convert::{TryFrom, TryInto},
fmt,
ops::DerefMut,
sync::Mutex,
};
const REQUIRED_LEN: usize = 32;
#[derive(Debug, PartialEq, Clone, Hash, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct EncryptedBlindIndexSalt {
pub encrypted_deks: Vec<u8>,
pub encrypted_salt_bytes: Vec<u8>,
}
impl EncryptedBlindIndexSalt {
pub async fn initialize_search(&self, ironoxide: &IronOxide) -> Result<BlindIndexSearch> {
let decrypted_value = ironoxide
.document_decrypt_unmanaged(&self.encrypted_salt_bytes[..], &self.encrypted_deks[..])
.await?;
decrypted_value.decrypted_data().try_into()
}
#[cfg(feature = "blocking")]
pub fn initialize_search_blocking(&self, bio: &BlockingIronOxide) -> Result<BlindIndexSearch> {
bio.runtime.block_on(self.initialize_search(&bio.ironoxide))
}
}
pub trait BlindIndexSearchInitialize {
fn create_blind_index(
&self,
group_id: &GroupId,
) -> impl Future<Output = Result<EncryptedBlindIndexSalt>> + Send;
}
impl BlindIndexSearchInitialize for IronOxide {
async fn create_blind_index(&self, group_id: &GroupId) -> Result<EncryptedBlindIndexSalt> {
let salt = {
let mut mut_salt = [0u8; 32];
take_lock(&self.rng).deref_mut().fill_bytes(&mut mut_salt);
mut_salt
};
let encrypted_salt = self
.document_encrypt_unmanaged(
salt.into(),
&DocumentEncryptOpts::with_explicit_grants(
None,
None,
false,
vec![group_id.into()],
),
)
.await?;
encrypted_salt.try_into()
}
}
pub struct BlindIndexSearch {
decrypted_salt: [u8; 32],
rng: Mutex<DefaultRng>,
}
impl fmt::Debug for BlindIndexSearch {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("BlindIndexSearch")
.field("decrypted_salt", &self.decrypted_salt)
.finish()
}
}
impl TryFrom<&[u8]> for BlindIndexSearch {
type Error = IronOxideErr;
fn try_from(bytes: &[u8]) -> Result<BlindIndexSearch> {
let decrypted_len = bytes.len();
if decrypted_len != REQUIRED_LEN {
Err(IronOxideErr::WrongSizeError(
Some(decrypted_len),
Some(REQUIRED_LEN),
))
} else {
let mut a = [0u8; 32];
a.copy_from_slice(&bytes[0..32]);
Ok(BlindIndexSearch::new(a))
}
}
}
impl TryFrom<DocumentEncryptUnmanagedResult> for EncryptedBlindIndexSalt {
type Error = IronOxideErr;
fn try_from(r: DocumentEncryptUnmanagedResult) -> Result<EncryptedBlindIndexSalt> {
match r.access_errs().get(0) {
None => Ok(EncryptedBlindIndexSalt {
encrypted_deks: r.encrypted_deks().to_vec(),
encrypted_salt_bytes: r.encrypted_data().to_vec(),
}),
Some(err) => Err(IronOxideErr::UserOrGroupDoesNotExist(
err.user_or_group.clone(),
)),
}
}
}
impl BlindIndexSearch {
fn new(decrypted_salt: [u8; 32]) -> BlindIndexSearch {
let rng = Mutex::new(DefaultRng::default());
BlindIndexSearch {
decrypted_salt,
rng,
}
}
pub fn tokenize_query(&self, query: &str, partition_id: Option<&str>) -> Result<HashSet<u32>> {
generate_hashes_for_string(query, partition_id, &self.decrypted_salt[..])
.map_err(|message| IronOxideErr::ValidationError("query".to_string(), message))
}
pub fn tokenize_data(&self, data: &str, partition_id: Option<&str>) -> Result<HashSet<u32>> {
generate_hashes_for_string_with_padding(
data,
partition_id,
&self.decrypted_salt[..],
&self.rng,
)
.map_err(|message| IronOxideErr::ValidationError("data".to_string(), message))
}
}
#[cfg(test)]
mod tests {
use super::*;
use galvanic_assert::*;
#[test]
fn try_from_works_for_correct_size() -> Result<()> {
let bytes = [0u8; 32];
let _: BlindIndexSearch = (&bytes[..]).try_into()?;
Ok(())
}
#[test]
fn try_from_errors_for_incorrect_size() -> Result<()> {
let bytes = [0u8; 100];
let maybe_error: Result<BlindIndexSearch> = (&bytes[..]).try_into();
let error = maybe_error.unwrap_err();
assert_that!(&error, is_variant!(IronOxideErr::WrongSizeError));
Ok(())
}
}