Skip to main content

ironoxide/
internal.rs

1//! Common types, traits, and functions needed across user/group/document apis
2//! If it can be defined in API specific file, it should go there to keep this file's
3//! size to a minimum.
4
5use crate::Result;
6use base64::engine::Engine;
7use base64::prelude::BASE64_STANDARD;
8use futures::Future;
9use group_api::GroupId;
10use itertools::{Either, Itertools};
11use lazy_static::lazy_static;
12use log::error;
13use papaya::HashMap;
14use protobuf::{self, Error as ProtobufError};
15use quick_error::quick_error;
16use recrypt::api::{
17    CryptoOps, Ed25519, Ed25519Signature, Hashable, KeyGenOps, Plaintext,
18    PrivateKey as RecryptPrivateKey, PublicKey as RecryptPublicKey, RandomBytes, Recrypt,
19    RecryptErr, Sha256, SigningKeypair as RecryptSigningKeypair,
20};
21use regex::Regex;
22use reqwest::Method;
23use rest::{Authorization, SignatureUrlString};
24use serde::{Deserialize, Deserializer, Serialize, Serializer};
25use std::{
26    convert::{TryFrom, TryInto},
27    fmt::{Error, Formatter},
28    result::Result as StdResult,
29    sync::{Mutex, MutexGuard},
30};
31use time::OffsetDateTime;
32use user_api::UserId;
33
34pub mod document_api;
35pub mod group_api;
36mod rest;
37pub mod user_api;
38pub use rest::IronCoreRequest;
39
40const DEVICE_SIGNATURE_LENGTH: usize = 64;
41
42lazy_static! {
43    pub static ref URL_STRING: String = match std::env::var("IRONCORE_ENV") {
44        Ok(url) => match url.to_lowercase().as_ref() {
45            "stage" => "https://api-staging.ironcorelabs.com/api/1/",
46            "prod" => "https://api.ironcorelabs.com/api/1/",
47            url_choice => url_choice,
48        }
49        .to_string(),
50        _ => "https://api.ironcorelabs.com/api/1/".to_string(),
51    };
52}
53
54#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
55pub enum RequestErrorCode {
56    UserVerify,
57    UserCreate,
58    UserUpdate,
59    UserUpdateStatus,
60    UserDeviceAdd,
61    UserDeviceDelete,
62    UserDeviceList,
63    UserKeyList,
64    UserKeyUpdate,
65    UserGetCurrent,
66    GroupCreate,
67    GroupDelete,
68    GroupList,
69    GroupGet,
70    GroupAddMember,
71    GroupUpdate,
72    GroupMemberRemove,
73    GroupAdminRemove,
74    GroupKeyUpdate,
75    DocumentList,
76    DocumentGet,
77    DocumentCreate,
78    DocumentUpdate,
79    DocumentGrantAccess,
80    DocumentRevokeAccess,
81    EdekTransform,
82    PolicyGet,
83}
84
85/// Public SDK operations
86#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
87pub enum SdkOperation {
88    InitializeSdk,
89    InitializeSdkCheckRotation,
90    RotateAll,
91    DocumentList,
92    DocumentGetMetadata,
93    DocumentEncrypt,
94    DocumentUpdateBytes,
95    DocumentDecrypt,
96    DocumentUpdateName,
97    DocumentGrantAccess,
98    DocumentRevokeAccess,
99    DocumentEncryptUnmanaged,
100    DocumentDecryptUnmanaged,
101    DocumentFileEncrypt,
102    DocumentFileDecrypt,
103    DocumentFileEncryptUnmanaged,
104    DocumentFileDecryptUnmanaged,
105    UserCreate,
106    UserListDevices,
107    GenerateNewDevice,
108    UserDeleteDevice,
109    UserVerify,
110    UserUpdateStatus,
111    UserDisableSelf,
112    UserGetPublicKey,
113    UserRotatePrivateKey,
114    UserChangePassword,
115    GroupList,
116    GroupCreate,
117    GroupGetMetadata,
118    GroupDelete,
119    GroupUpdateName,
120    GroupAddMembers,
121    GroupRemoveMembers,
122    GroupAddAdmins,
123    GroupRemoveAdmins,
124    GroupRotatePrivateKey,
125}
126
127impl std::fmt::Display for SdkOperation {
128    fn fmt(&self, f: &mut Formatter<'_>) -> StdResult<(), Error> {
129        write!(f, "'{self:?}'")
130    }
131}
132
133quick_error! {
134    /// Errors generated by IronOxide SDK operations
135    #[derive(Debug)]
136    #[non_exhaustive]
137    pub enum IronOxideErr {
138        ValidationError(field_name: String, err: String) {
139            display("'{}' failed validation with the error '{}'", field_name, err)
140        }
141        DocumentHeaderParseFailure(message: String) {
142            display("{}", message)
143        }
144        WrongSizeError(actual_size: Option<usize>, expected_size: Option<usize>) {
145        }
146        KeyGenerationError {
147            display("Key generation failed")
148        }
149        AesError(err: aws_lc_rs::error::Unspecified) {
150            source(err)
151        }
152        AesEncryptedDocSizeError{
153            display("Provided document is not long enough to be an encrypted document.")
154        }
155        InvalidRecryptEncryptedValue(msg: String) {
156            display("Got an unexpected Recrypt EncryptedValue: '{}'", msg)
157        }
158        RecryptError(msg: String) {
159            display("Recrypt operation failed with error '{}'", msg)
160        }
161        UserDoesNotExist(msg: String) {
162            display("Operation failed with error '{}'", msg)
163        }
164        UserOrGroupDoesNotExist(user_or_group: document_api::UserOrGroup) {
165            display("User or group {} does not exist.", user_or_group)
166        }
167        InitializeError(cause: String) {
168            display("SDK initialization failed. Underlying cause '{}'", cause)
169        }
170        RequestError { message: String, code: RequestErrorCode, http_status: Option<u16> } {
171            display("Request failed with HTTP status code '{:?}' message '{}' and code '{:?}'", http_status, message, code)
172        }
173        ///This is used if the response from the server was an error. In that case we know that the format of the errors will be `ServerError`.
174        RequestServerErrors {errors: Vec<rest::ServerError>, code: RequestErrorCode, http_status: Option<u16> } {
175            display("Request failed with HTTP status code '{:?}' errors list is '{:?}' and code '{:?}'", http_status, errors, code)
176        }
177        MissingTransformBlocks {
178            display("Expected at least one TransformBlock in transformed value but received none.")
179        }
180        ///The operation failed because the accessing user was not a group admin, but must be for the operation to work.
181        NotGroupAdmin(id: GroupId) {
182            display("You are not an administrator of group '{}'", id.id())
183        }
184        /// No policy exists for the segment
185        PolicyDoesNotExist {
186            display("No policy is defined. Please visit https://admin.ironcorelabs.com/policy to set a policy")
187        }
188        /// Protobuf encode/decode error
189        ProtobufSerdeError(err: ProtobufError) {
190            source(err)
191        }
192        /// Protobuf decode succeeded, but the result is not valid
193        ProtobufValidationError(msg: String) {
194            display("Protobuf validation failed with '{}'", msg)
195        }
196        UnmanagedDecryptionError(edek_doc_id: String, edek_segment_id: i32,
197                                 edoc_doc_id: String, edoc_segment_id: i32) {
198            display("Edeks and EncryptedDocument do not match. \
199            Edeks are for DocumentId({}) and SegmentId({}) and\
200            Encrypted Document is DocumentId({}) and SegmentId({})",
201            edek_doc_id, edek_segment_id, edoc_doc_id, edoc_segment_id)
202        }
203        UserPrivateKeyRotationError(msg: String) {
204            display("User private key rotation failed with '{}'", msg)
205        }
206        GroupPrivateKeyRotationError(msg: String) {
207            display("Group private key rotation failed with '{}'", msg)
208        }
209        OperationTimedOut{operation: SdkOperation, duration: std::time::Duration} {
210            display("Operation {} timed out after {}ms", operation, duration.as_millis())
211        }
212        JoinError(msg: String) {
213            display("{}", msg)
214        }
215        CacheSerdeError(error: postcard::Error) {
216            source(error)
217        }
218        AesGcmDecryptError {
219            display("AES-GCM decryption failed: authentication tag verification failed")
220        }
221        FileIoError { path: Option<String>, operation: String, message: String } {
222            display("File I/O error {}during {}: {}", path.as_ref().map(|s| format!("for '{s}' ")).unwrap_or("".to_string()), operation, message)
223        }
224    }
225}
226
227/// A way to turn IronSdkErr into Strings for the Java binding
228impl From<IronOxideErr> for String {
229    fn from(err: IronOxideErr) -> Self {
230        err.to_string()
231    }
232}
233
234impl From<RecryptErr> for IronOxideErr {
235    fn from(recrypt_err: RecryptErr) -> Self {
236        match recrypt_err {
237            RecryptErr::InputWrongSize(_, expected_size) => {
238                IronOxideErr::WrongSizeError(None, Some(expected_size))
239            }
240            RecryptErr::InvalidPublicKey(_) => IronOxideErr::KeyGenerationError,
241            //Fallback for all other error types that Recrypt can have that we don't have specific mappings for
242            other_recrypt_err => IronOxideErr::RecryptError(other_recrypt_err.to_string()),
243        }
244    }
245}
246
247impl From<ProtobufError> for IronOxideErr {
248    fn from(e: ProtobufError) -> Self {
249        IronOxideErr::ProtobufSerdeError(e)
250    }
251}
252
253impl From<recrypt::nonemptyvec::NonEmptyVecError> for IronOxideErr {
254    fn from(_: recrypt::nonemptyvec::NonEmptyVecError) -> Self {
255        IronOxideErr::MissingTransformBlocks
256    }
257}
258
259impl From<tokio::task::JoinError> for IronOxideErr {
260    fn from(e: tokio::task::JoinError) -> Self {
261        IronOxideErr::JoinError(e.to_string())
262    }
263}
264
265const NAME_AND_ID_MAX_LEN: usize = 100;
266
267/// Validate that the provided id is valid for our user/document/group IDs. Validates that the
268/// ID has a length and that it matches our restricted set of characters. Also takes the readable
269/// type of ID for usage within any resulting error messages.
270pub fn validate_id(id: &str, id_type: &str) -> Result<String> {
271    let id_regex = Regex::new("^[a-zA-Z0-9_.$#|@/:;=+'-]+$").expect("regex is valid");
272    let trimmed_id = id.trim();
273    if trimmed_id.is_empty() || trimmed_id.len() > NAME_AND_ID_MAX_LEN {
274        Err(IronOxideErr::ValidationError(
275            id_type.to_string(),
276            format!("'{trimmed_id}' must have length between 1 and 100"),
277        ))
278    } else if !id_regex.is_match(trimmed_id) {
279        Err(IronOxideErr::ValidationError(
280            id_type.to_string(),
281            format!("'{trimmed_id}' contains invalid characters"),
282        ))
283    } else {
284        Ok(trimmed_id.to_string())
285    }
286}
287
288/// Validate that the provided document/group name is valid. Ensures that the length of
289/// the name is between 1-100 characters. Also takes the readable type of the name for
290/// usage within any resulting error messages.
291pub fn validate_name(name: &str, name_type: &str) -> Result<String> {
292    let trimmed_name = name.trim();
293    if trimmed_name.trim().is_empty() || trimmed_name.len() > NAME_AND_ID_MAX_LEN {
294        Err(IronOxideErr::ValidationError(
295            name_type.to_string(),
296            format!("'{trimmed_name}' must have length between 1 and 100"),
297        ))
298    } else {
299        Ok(trimmed_name.trim().to_string())
300    }
301}
302
303pub mod auth_v2 {
304    use time::OffsetDateTime;
305
306    use super::*;
307
308    /// API Auth version 2.
309    /// Fully constructing a valid auth v2 header is a two step process.
310    /// Step 1 is done on construction via `new`
311    /// Step 2 is done via `finish_with` as a request is being sent out and the bytes of the body are available.
312    pub struct AuthV2Builder<'a> {
313        pub(in crate::internal::auth_v2) req_auth: &'a RequestAuth,
314        pub(in crate::internal::auth_v2) timestamp: OffsetDateTime,
315    }
316
317    impl AuthV2Builder<'_> {
318        pub fn new(req_auth: &RequestAuth, timestamp: OffsetDateTime) -> AuthV2Builder<'_> {
319            AuthV2Builder {
320                req_auth,
321                timestamp,
322            }
323        }
324
325        /// Always returns Authorization::Version2
326        /// # Arguments
327        /// `sig_url`       URL path to be signed over
328        /// `method`        Method of request (POST, GET, PUT, etc)
329        /// `body_bytes`    Reference to the bytes of the body (or none)
330        ///
331        /// # Returns
332        /// Authorization::Version2 that contains all the information necessary to make an
333        /// IronCore authenticated request to the webservice.
334        pub fn finish_with<'a>(
335            &'a self,
336            sig_url: SignatureUrlString,
337            method: Method,
338            body_bytes: Option<&'a [u8]>,
339        ) -> Authorization<'a> {
340            self.req_auth
341                .create_signature_v2(self.timestamp, sig_url, method, body_bytes)
342        }
343    }
344}
345
346///Structure that contains all the info needed to make a signed API request from a device.
347#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
348#[serde(rename_all = "camelCase")]
349pub struct RequestAuth {
350    ///The user's given id, which uniquely identifies them inside the segment.
351    account_id: UserId,
352    ///The segment_id for the above user.
353    segment_id: usize,
354    ///The signing key which was generated for the device. “expanded private key” (both pub/priv)
355    signing_private_key: DeviceSigningKeyPair,
356    #[serde(skip_serializing, skip_deserializing)]
357    pub(crate) request: IronCoreRequest,
358}
359impl RequestAuth {
360    pub fn create_signature_v2<'a>(
361        &'a self,
362        current_time: OffsetDateTime,
363        sig_url: SignatureUrlString,
364        method: Method,
365        body: Option<&'a [u8]>,
366    ) -> Authorization<'a> {
367        Authorization::create_signatures_v2(
368            current_time,
369            self.segment_id,
370            &self.account_id,
371            method,
372            sig_url,
373            body,
374            &self.signing_private_key,
375        )
376    }
377
378    pub fn account_id(&self) -> &UserId {
379        &self.account_id
380    }
381
382    pub fn segment_id(&self) -> usize {
383        self.segment_id
384    }
385
386    pub fn signing_private_key(&self) -> &DeviceSigningKeyPair {
387        &self.signing_private_key
388    }
389}
390
391/// Signing and encryption key pairs and metadata for a device.
392///
393/// Required to initialize the SDK with a set of device keys (see [ironoxide::initialize](../fn.initialize.html)).
394///
395/// Can be generated by calling [generate_new_device](../user/trait.UserOps.html#tymethod.generate_new_device) and
396/// passing the result to `DeviceContext::from`.
397#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
398#[serde(rename_all = "camelCase")]
399pub struct DeviceContext {
400    #[serde(flatten)]
401    auth: RequestAuth,
402    /// The private key which was generated for a particular device for the user. Not the user's master private key.
403    device_private_key: PrivateKey,
404}
405impl DeviceContext {
406    /// Constructs a `DeviceContext` from its components.
407    ///
408    /// To instead generate a new `DeviceContext` for the user, call [generate_new_device](../user/trait.UserOps.html#tymethod.generate_new_device)
409    /// and pass the result to `DeviceContext::from`.
410    pub fn new(
411        account_id: UserId,
412        segment_id: usize,
413        device_private_key: PrivateKey,
414        signing_private_key: DeviceSigningKeyPair,
415    ) -> DeviceContext {
416        DeviceContext {
417            auth: RequestAuth {
418                account_id,
419                segment_id,
420                signing_private_key,
421                request: IronCoreRequest::default(),
422            },
423            device_private_key,
424        }
425    }
426
427    pub(crate) fn auth(&self) -> &RequestAuth {
428        &self.auth
429    }
430    /// ID of the device's owner
431    pub fn account_id(&self) -> &UserId {
432        &self.auth.account_id
433    }
434    /// ID of the segment
435    pub fn segment_id(&self) -> usize {
436        self.auth.segment_id
437    }
438    /// Private signing key of the device
439    pub fn signing_private_key(&self) -> &DeviceSigningKeyPair {
440        &self.auth.signing_private_key
441    }
442    /// Private encryption key of the device
443    pub fn device_private_key(&self) -> &PrivateKey {
444        &self.device_private_key
445    }
446}
447
448type UserPublicKeyCache = HashMap<UserId, PublicKey>;
449type GroupPublicKeyCache = HashMap<GroupId, PublicKey>;
450/// A cache recording the (id, public key) pairs that have been seen by the API. There are
451/// separate lists for users and groups.
452// Public keys don't go bad, so we don't need expiration, but we may want a default limit on size in the future.
453#[derive(Serialize, Deserialize, Debug)]
454pub(crate) struct PublicKeyCache {
455    /// The public key of the user that created this public key cache.
456    /// Public key caches are only valid in the context of the one user, and we need their public key to initialize.
457    creator_public_key: PublicKey,
458    #[serde(
459        serialize_with = "serialize_papaya_map",
460        deserialize_with = "deserialize_papaya_map"
461    )]
462    user_keys: UserPublicKeyCache,
463    #[serde(
464        serialize_with = "serialize_papaya_map",
465        deserialize_with = "deserialize_papaya_map"
466    )]
467    group_keys: GroupPublicKeyCache,
468}
469
470/// `papaya` can't provide `size_hint` (due to being concurrent, no reliable upper bound) to indicate to serde's
471/// `serialize_seq` how long it is during serialization. `postcard` (our data format) requires a length on
472/// `serialize_seq` or it errors.
473/// This manual serde gets around concurrency issues by snapshotting a Vec of entries at call time and serializing that.
474fn serialize_papaya_map<S, K, V>(map: &HashMap<K, V>, serializer: S) -> StdResult<S::Ok, S::Error>
475where
476    S: Serializer,
477    K: Serialize + core::hash::Hash + Eq,
478    V: Serialize,
479{
480    serializer.collect_seq(map.pin().iter().collect::<Vec<_>>())
481}
482fn deserialize_papaya_map<'de, D, K, V>(deserializer: D) -> StdResult<HashMap<K, V>, D::Error>
483where
484    D: Deserializer<'de>,
485    K: Deserialize<'de> + core::hash::Hash + Eq,
486    V: Deserialize<'de>,
487{
488    let entries: Vec<(K, V)> = Vec::deserialize(deserializer)?;
489    let map = HashMap::new();
490    {
491        // scoped to drop the pin's guard before return the map
492        let pinned_map = map.pin();
493        for (k, v) in entries {
494            pinned_map.insert(k, v);
495        }
496    }
497    Ok(map)
498}
499
500impl PublicKeyCache {
501    pub(crate) fn new(current_user_public_key: &PublicKey) -> Self {
502        Self {
503            creator_public_key: current_user_public_key.clone(),
504            user_keys: Default::default(),
505            group_keys: Default::default(),
506        }
507    }
508    /// Serialize the cache to bytes that can be persisted and reloaded
509    /// when the SDK is initialized
510    pub(crate) fn serialize(&self) -> Result<Vec<u8>> {
511        postcard::to_stdvec(&self).map_err(IronOxideErr::CacheSerdeError)
512    }
513    pub(crate) fn deserialize(serialized_cache: &[u8]) -> Result<Self> {
514        postcard::from_bytes(serialized_cache).map_err(IronOxideErr::CacheSerdeError)
515    }
516    /// The public key of the user that created this cache, used for offline SDK initialization
517    pub(crate) fn creator_public_key(&self) -> &PublicKey {
518        &self.creator_public_key
519    }
520    pub(crate) fn user_keys(&self) -> &HashMap<UserId, PublicKey> {
521        &self.user_keys
522    }
523    pub(crate) fn group_keys(&self) -> &HashMap<GroupId, PublicKey> {
524        &self.group_keys
525    }
526    pub(crate) fn deserialize_signed_public_key_cache(
527        device: &DeviceContext,
528        signed_cache_bytes: &[u8],
529    ) -> Result<PublicKeyCache> {
530        if signed_cache_bytes.len() < DEVICE_SIGNATURE_LENGTH {
531            return Err(IronOxideErr::WrongSizeError(
532                Some(signed_cache_bytes.len()),
533                Some(DEVICE_SIGNATURE_LENGTH),
534            ));
535        }
536        let (signature, cache) = signed_cache_bytes.split_at(DEVICE_SIGNATURE_LENGTH);
537        if device.signing_private_key().verify(&cache, signature)? {
538            Self::deserialize(cache)
539        } else {
540            Err(IronOxideErr::ValidationError(
541                "signed public key cache".to_string(),
542                "The signed public key cache failed signature verification.".to_string(),
543            ))
544        }
545    }
546    pub(crate) fn serialize_signed_public_key_cache(
547        &self,
548        device: &DeviceContext,
549    ) -> Result<Vec<u8>> {
550        self.serialize().map(|cache_bytes| {
551            // sign the bytes, then create a result of signature + cache
552            let signature = device.signing_private_key().sign(&cache_bytes);
553            let mut signed_cache = Vec::new();
554            signed_cache.extend_from_slice(&signature);
555            signed_cache.extend(cache_bytes);
556            signed_cache
557        })
558    }
559}
560
561// helper function for getting keys from an API via a cache first, and updating the cache after.
562pub(crate) async fn get_keys_with_cache<Id, F, Op>(
563    ids: &[Id],
564    cache: &HashMap<Id, PublicKey>,
565    fetch: F,
566) -> Result<(Vec<Id>, Vec<WithKey<Id>>)>
567where
568    Id: Clone + Eq + core::hash::Hash,
569    F: FnOnce(Vec<Id>) -> Op,
570    Op: Future<Output = Result<std::collections::HashMap<Id, PublicKey>>>,
571{
572    // if there aren't any ids in the list, just return with empty results
573    if ids.is_empty() {
574        return Ok((vec![], vec![]));
575    }
576
577    let (cached, uncached): (Vec<_>, Vec<_>) = ids.iter().cloned().partition_map(|id| match cache
578        .pin()
579        .get(&id)
580    {
581        Some(pub_key) => Either::Left(WithKey::new(id, pub_key.clone())),
582        None => Either::Right(id),
583    });
584
585    // everything requested was cached, we can return without making an API call
586    if uncached.is_empty() {
587        return Ok((vec![], cached));
588    }
589
590    // call the API for remaining missing ids
591    let ids_with_keys = fetch(uncached.clone()).await?;
592    let (not_found, found): (Vec<_>, Vec<_>) =
593        uncached
594            .into_iter()
595            .partition_map(|id| match ids_with_keys.get(&id).cloned() {
596                Some(pub_key) => {
597                    // cache the newly returned API values
598                    cache.pin().insert(id.clone(), pub_key.clone());
599                    Either::Right(WithKey::new(id, pub_key))
600                }
601                None => Either::Left(id),
602            });
603
604    Ok((not_found, [cached, found].concat()))
605}
606
607/// Newtype wrapper around Recrypt TransformKey type
608#[derive(Clone, PartialEq, Eq, Debug)]
609pub struct TransformKey(recrypt::api::TransformKey);
610impl From<recrypt::api::TransformKey> for TransformKey {
611    fn from(tk: recrypt::api::TransformKey) -> Self {
612        TransformKey(tk)
613    }
614}
615impl Hashable for TransformKey {
616    fn to_bytes(&self) -> Vec<u8> {
617        self.0.to_bytes()
618    }
619}
620
621/// Newtype wrapper around Recrypt SchnorrSignature type
622#[derive(Clone, PartialEq, Eq, Debug)]
623pub struct SchnorrSignature(recrypt::api::SchnorrSignature);
624impl From<recrypt::api::SchnorrSignature> for SchnorrSignature {
625    fn from(s: recrypt::api::SchnorrSignature) -> Self {
626        SchnorrSignature(s)
627    }
628}
629impl From<SchnorrSignature> for Vec<u8> {
630    fn from(sig: SchnorrSignature) -> Self {
631        sig.0.bytes().to_vec()
632    }
633}
634
635/// Asymmetric public encryption key.
636#[derive(Clone, Debug, Eq, Hash, PartialEq)]
637pub struct PublicKey(RecryptPublicKey);
638impl PublicKey {
639    fn to_bytes_x_y(&self) -> (Vec<u8>, Vec<u8>) {
640        let (x, y) = self.0.bytes_x_y();
641        (x.to_vec(), y.to_vec())
642    }
643    pub fn new_from_slice(bytes: (&[u8], &[u8])) -> Result<Self> {
644        let re_pub = RecryptPublicKey::new_from_slice(bytes)?;
645        Ok(PublicKey(re_pub))
646    }
647    /// Bytes of the public key
648    pub fn as_bytes(&self) -> Vec<u8> {
649        let (mut x, mut y) = self.to_bytes_x_y();
650        x.append(&mut y);
651        x
652    }
653}
654impl From<RecryptPublicKey> for PublicKey {
655    fn from(recrypt_pub: RecryptPublicKey) -> Self {
656        PublicKey(recrypt_pub)
657    }
658}
659impl From<PublicKey> for RecryptPublicKey {
660    fn from(public_key: PublicKey) -> Self {
661        public_key.0
662    }
663}
664impl From<&PublicKey> for RecryptPublicKey {
665    fn from(public_key: &PublicKey) -> Self {
666        public_key.0
667    }
668}
669impl From<PublicKey> for crate::proto::transform::PublicKey {
670    fn from(pubk: PublicKey) -> Self {
671        crate::proto::transform::PublicKey {
672            x: pubk.to_bytes_x_y().0.into(),
673            y: pubk.to_bytes_x_y().1.into(),
674            ..Default::default()
675        }
676    }
677}
678impl TryFrom<&[u8]> for PublicKey {
679    type Error = IronOxideErr;
680    fn try_from(key_bytes: &[u8]) -> Result<PublicKey> {
681        if key_bytes.len() == RecryptPublicKey::ENCODED_SIZE_BYTES {
682            PublicKey::new_from_slice(key_bytes.split_at(RecryptPublicKey::ENCODED_SIZE_BYTES / 2))
683        } else {
684            Err(IronOxideErr::WrongSizeError(
685                Some(RecryptPublicKey::ENCODED_SIZE_BYTES),
686                Some(key_bytes.len()),
687            ))
688        }
689    }
690}
691impl Serialize for PublicKey {
692    fn serialize<S: Serializer>(&self, serializer: S) -> StdResult<S::Ok, S::Error> {
693        self.as_bytes().serialize(serializer)
694    }
695}
696impl<'de> Deserialize<'de> for PublicKey {
697    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> StdResult<Self, D::Error> {
698        let bytes: Vec<u8> = Deserialize::deserialize(deserializer)?;
699        if bytes.len() != 64 {
700            return Err(serde::de::Error::invalid_length(bytes.len(), &"64 bytes"));
701        }
702        let (x, y) = bytes.split_at(32);
703        PublicKey::new_from_slice((x, y)).map_err(serde::de::Error::custom)
704    }
705}
706
707/// Asymmetric private encryption key.
708#[derive(Clone, Debug, Eq, Hash, PartialEq)]
709pub struct PrivateKey(RecryptPrivateKey);
710impl PrivateKey {
711    const BYTES_SIZE: usize = RecryptPrivateKey::ENCODED_SIZE_BYTES;
712    /// Bytes of the private key
713    pub fn as_bytes(&self) -> &[u8; PrivateKey::BYTES_SIZE] {
714        self.0.bytes()
715    }
716    fn recrypt_key(&self) -> &RecryptPrivateKey {
717        &self.0
718    }
719    /// Augment this private key with another, producing a new PrivateKey
720    fn augment<F: FnOnce(String) -> IronOxideErr>(
721        &self,
722        augmenting_key: &AugmentationFactor,
723        error_fn: F,
724    ) -> Result<PrivateKey> {
725        let zero: RecryptPrivateKey = RecryptPrivateKey::new([0u8; 32]);
726        if RecryptPrivateKey::from(augmenting_key.clone()) == zero {
727            Err(error_fn("Augmenting key cannot be zero".into()))
728        } else if RecryptPrivateKey::from(augmenting_key.clone()) == self.0 {
729            Err(error_fn(
730                "PrivateKey augmentation failed with a zero value".into(),
731            ))
732        } else {
733            // this subtraction needs to be the additive inverse of what the service is doing
734            let augmented_key = self.0.augment_minus(&augmenting_key.clone().into());
735            Ok(augmented_key.into())
736        }
737    }
738    /// A convenience function to pass a user rotation error to `augment()`
739    fn augment_user(&self, augmenting_key: &AugmentationFactor) -> Result<PrivateKey> {
740        self.augment(augmenting_key, IronOxideErr::UserPrivateKeyRotationError)
741    }
742    /// A convenience function to pass a user rotation error to `augment()`
743    fn augment_group(&self, augmenting_key: &AugmentationFactor) -> Result<PrivateKey> {
744        self.augment(augmenting_key, IronOxideErr::GroupPrivateKeyRotationError)
745    }
746}
747impl From<RecryptPrivateKey> for PrivateKey {
748    fn from(recrypt_priv: RecryptPrivateKey) -> Self {
749        PrivateKey(recrypt_priv)
750    }
751}
752impl From<PrivateKey> for RecryptPrivateKey {
753    fn from(priv_key: PrivateKey) -> Self {
754        priv_key.0
755    }
756}
757impl From<[u8; 32]> for PrivateKey {
758    fn from(bytes: [u8; 32]) -> Self {
759        PrivateKey(RecryptPrivateKey::new(bytes))
760    }
761}
762impl TryFrom<&[u8]> for PrivateKey {
763    type Error = IronOxideErr;
764    fn try_from(key_bytes: &[u8]) -> Result<PrivateKey> {
765        RecryptPrivateKey::new_from_slice(key_bytes)
766            .map(PrivateKey)
767            .map_err(|e| e.into())
768    }
769}
770impl Serialize for PrivateKey {
771    fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
772    where
773        S: Serializer,
774    {
775        serializer.serialize_str(&BASE64_STANDARD.encode(self.0.bytes()))
776    }
777}
778impl<'de> Deserialize<'de> for PrivateKey {
779    fn deserialize<D>(deserializer: D) -> StdResult<Self, D::Error>
780    where
781        D: Deserializer<'de>,
782    {
783        use serde::de::Error;
784        let s = String::deserialize(deserializer)?;
785        let keys_bytes = BASE64_STANDARD
786            .decode(s)
787            .map_err(|e| Error::custom(e.to_string()))?;
788        PrivateKey::try_from(&keys_bytes[..]).map_err(|e| Error::custom(e.to_string()))
789    }
790}
791
792/// Private key used to augment another PrivateKey
793#[derive(Clone, Debug)]
794pub(crate) struct AugmentationFactor(PrivateKey);
795impl AugmentationFactor {
796    /// Use recrypt to generate a new AugmentationFactor
797    pub fn generate_new<R: KeyGenOps>(recrypt: &R) -> AugmentationFactor {
798        AugmentationFactor(recrypt.random_private_key().into())
799    }
800
801    pub fn as_bytes(&self) -> &[u8; 32] {
802        self.0.as_bytes()
803    }
804}
805impl From<AugmentationFactor> for RecryptPrivateKey {
806    fn from(aug: AugmentationFactor) -> Self {
807        (aug.0).0
808    }
809}
810
811/// Key pair used to sign all requests to the IronCore API endpoints.
812#[derive(Clone, Debug, Eq, Hash, PartialEq)]
813pub struct DeviceSigningKeyPair(RecryptSigningKeypair);
814impl DeviceSigningKeyPair {
815    pub fn sign(&self, payload: &[u8]) -> [u8; DEVICE_SIGNATURE_LENGTH] {
816        self.0.sign(&payload).into()
817    }
818    /// Bytes of the signing key pair
819    pub fn as_bytes(&self) -> &[u8; 64] {
820        self.0.bytes()
821    }
822    pub fn public_key(&self) -> [u8; 32] {
823        self.0.public_key().into()
824    }
825    pub fn verify<A: Hashable>(&self, message: &A, signature: &[u8]) -> Result<bool> {
826        let ed25519_signature = Ed25519Signature::new_from_slice(signature)?;
827        Ok(self.0.public_key().verify(message, &ed25519_signature))
828    }
829}
830impl From<&DeviceSigningKeyPair> for RecryptSigningKeypair {
831    fn from(dsk: &DeviceSigningKeyPair) -> RecryptSigningKeypair {
832        dsk.0.clone()
833    }
834}
835impl From<RecryptSigningKeypair> for DeviceSigningKeyPair {
836    fn from(rsk: RecryptSigningKeypair) -> DeviceSigningKeyPair {
837        DeviceSigningKeyPair(rsk)
838    }
839}
840impl TryFrom<&[u8]> for DeviceSigningKeyPair {
841    type Error = IronOxideErr;
842    fn try_from(signing_key_bytes: &[u8]) -> Result<DeviceSigningKeyPair> {
843        RecryptSigningKeypair::from_byte_slice(signing_key_bytes)
844            .map(DeviceSigningKeyPair)
845            .map_err(|e| {
846                IronOxideErr::ValidationError("DeviceSigningKeyPair".to_string(), e.to_string())
847            })
848    }
849}
850impl Serialize for DeviceSigningKeyPair {
851    fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
852    where
853        S: Serializer,
854    {
855        let base64 = BASE64_STANDARD.encode(self.0.bytes());
856        serializer.serialize_str(&base64)
857    }
858}
859impl<'de> Deserialize<'de> for DeviceSigningKeyPair {
860    fn deserialize<D>(deserializer: D) -> StdResult<Self, D::Error>
861    where
862        D: Deserializer<'de>,
863    {
864        use serde::de::Error;
865        let s = String::deserialize(deserializer)?;
866        let keys_bytes = BASE64_STANDARD
867            .decode(s)
868            .map_err(|e| Error::custom(e.to_string()))?;
869        DeviceSigningKeyPair::try_from(&keys_bytes[..]).map_err(|e| Error::custom(e.to_string()))
870    }
871}
872
873/// Newtype wrapper around a string which represents the users master private key escrow password
874#[derive(Debug, PartialEq, Eq)]
875pub struct Password(String);
876impl TryFrom<&str> for Password {
877    type Error = IronOxideErr;
878    fn try_from(maybe_password: &str) -> Result<Self> {
879        if !maybe_password.trim().is_empty() {
880            Ok(Password(maybe_password.to_string()))
881        } else {
882            Err(IronOxideErr::ValidationError(
883                "maybe_password".to_string(),
884                "length must be > 0".to_string(),
885            ))
886        }
887    }
888}
889
890#[derive(Clone, Debug, Eq, Hash, PartialEq)]
891pub struct WithKey<T> {
892    pub(crate) id: T,
893    pub(crate) public_key: PublicKey,
894}
895impl<T> WithKey<T> {
896    pub fn new(id: T, public_key: PublicKey) -> WithKey<T> {
897        WithKey { id, public_key }
898    }
899}
900
901/// Acquire mutex in a blocking fashion. If the Mutex is or becomes poisoned, write out an error
902/// message and panic.
903///
904/// The lock is released when the returned MutexGuard falls out of scope.
905///
906/// # Usage:
907/// single statement (mut)
908/// `let result = take_lock(&t).deref_mut().call_method_on_t();`
909///
910/// multi-statement (mut)
911/// ```ignore
912/// let t = T {};
913/// let result = {
914///     let g = &mut *take_lock(&t);
915///     g.call_method_on_t()
916/// }; // lock released here
917/// ```
918///
919pub(crate) fn take_lock<T>(m: &Mutex<T>) -> MutexGuard<'_, T> {
920    m.lock().unwrap_or_else(|e| {
921        let error = format!("Error when acquiring lock: {e}");
922        error!("{}", error);
923        panic!("{}", error);
924    })
925}
926
927/// Attempts to augment an existing private key with a newly generated augmentation factor.
928/// There is a very small chance that an augmentation factor could not be compatible with
929/// the given PrivateKey, so we retry once internally before giving the caller an error.
930fn augment_private_key_with_retry<R: KeyGenOps>(
931    recrypt: &R,
932    priv_key: &PrivateKey,
933) -> Result<(PrivateKey, AugmentationFactor)> {
934    let aug_private_key = || {
935        let aug_factor = AugmentationFactor::generate_new(recrypt);
936        priv_key.augment_user(&aug_factor).map(|p| (p, aug_factor))
937    };
938    // retry generation of augmentation factor one time. If this fails twice there's something wrong.
939    aug_private_key().or_else(|_| aug_private_key())
940}
941
942/// Subtracts a generated private key from the provided PrivateKey, returning
943/// the result and the plaintext associated with the generated key.
944/// There is a very small chance that the generated private key could not be compatible with
945/// the given PrivateKey, so we retry once internally before giving the caller an error.
946fn gen_plaintext_and_aug_with_retry<R: CryptoOps>(
947    recrypt: &R,
948    priv_key: &PrivateKey,
949) -> Result<(Plaintext, AugmentationFactor)> {
950    let aug_private_key = || -> Result<(Plaintext, AugmentationFactor)> {
951        let new_plaintext = recrypt.gen_plaintext();
952        let new_group_private_key = recrypt.derive_private_key(&new_plaintext);
953        let new_key_aug = AugmentationFactor(new_group_private_key.into());
954        let aug_factor = priv_key.augment_group(&new_key_aug)?;
955        Ok((new_plaintext, AugmentationFactor(aug_factor)))
956    };
957    // retry generation of private key one time. If this fails twice there's something wrong.
958    aug_private_key().or_else(|_| aug_private_key())
959}
960
961/// Runs a future with a timeout or just runs the future, depending on if a timeout is specified.
962///
963/// If a timeout limit is reached, the result will be an IronOxideErr::OperationTimedOut.
964/// If no timeout is specified, or if the operation finishes before the timeout, the
965/// result is the result of the sdk operation.
966pub async fn add_optional_timeout<F: Future>(
967    f: F,
968    timeout: Option<std::time::Duration>,
969    op: SdkOperation,
970) -> Result<F::Output> {
971    use futures::future::TryFutureExt;
972    let result = match timeout {
973        Some(d) => {
974            tokio::time::timeout(d, f)
975                .map_err(|_| IronOxideErr::OperationTimedOut {
976                    operation: op,
977                    duration: d,
978                })
979                .await?
980        }
981
982        // no timeout, just run the Future and return
983        None => f.await,
984    };
985
986    Ok(result)
987}
988
989#[cfg(test)]
990pub(crate) mod tests {
991    use super::*;
992    use double::*;
993    use galvanic_assert::{matchers::*, *};
994    use recrypt::api::Ed25519Ops;
995    use std::fmt::Debug;
996    use tokio::time::Duration;
997    use vec1::vec1;
998
999    /// String contains matcher to assert that the provided substring exists in the provided value
1000    pub fn contains(expected: &str) -> Box<dyn Matcher<'_, String> + '_> {
1001        Box::new(move |actual: &String| {
1002            let builder = MatchResultBuilder::for_("contains");
1003            if actual.contains(expected) {
1004                builder.matched()
1005            } else {
1006                let expected_string: String = expected.to_string();
1007                builder.failed_comparison(actual, &expected_string)
1008            }
1009        })
1010    }
1011
1012    /// Length matcher to assert that the provided iterable value has the expected size
1013    pub fn length<'a, I, T>(expected: &'a usize) -> Box<dyn Matcher<'a, I> + 'a>
1014    where
1015        T: 'a,
1016        &'a I: Debug + Sized + IntoIterator<Item = &'a T> + 'a,
1017    {
1018        Box::new(move |actual: &'a I| {
1019            let actual_list: Vec<_> = actual.into_iter().collect();
1020            let builder = MatchResultBuilder::for_("contains");
1021            if &actual_list.len() == expected {
1022                builder.matched()
1023            } else {
1024                builder.failed_because(&format!(
1025                    "Expected '{:?}' to have length of {} but found length of {}",
1026                    actual,
1027                    expected,
1028                    actual_list.len()
1029                ))
1030            }
1031        })
1032    }
1033
1034    #[test]
1035    fn serde_devicecontext_roundtrip() -> Result<()> {
1036        let context = create_test_device_context();
1037        let json = serde_json::to_string(&context).unwrap();
1038        let expect_json = r#"{"accountId":"account_id","segmentId":22,"signingPrivateKey":"AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQGKiOPddAnxlf1S2y08ul1yymcJvx2UEhvzdIgBtA9vXA==","devicePrivateKey":"bzb0Rlg0u7gx9wDuk1ppRI77OH/0ferXleenJ3Ag6Jg="}"#;
1039
1040        assert_eq!(json, expect_json);
1041
1042        let de: DeviceContext = serde_json::from_str(&json).unwrap();
1043
1044        assert_eq!(context.account_id(), de.account_id());
1045        assert_eq!(
1046            context.auth.signing_private_key.as_bytes().to_vec(),
1047            de.auth.signing_private_key.as_bytes().to_vec()
1048        );
1049        assert_eq!(
1050            context.device_private_key.as_bytes().to_vec(),
1051            de.device_private_key.as_bytes().to_vec()
1052        );
1053        Ok(())
1054    }
1055
1056    #[test]
1057    fn validate_id_success() {
1058        let valid_id = "abcABC012_.$#|@/:;=+'-";
1059        let id = validate_id(valid_id, "id_type");
1060        assert_that!(&id, is_variant!(Ok));
1061        assert_that!(&id.unwrap(), eq(valid_id.to_string()))
1062    }
1063
1064    #[test]
1065    fn valid_id_whitespace() {
1066        let valid_id = " abc212     ";
1067        let id = validate_id(valid_id, "id_type");
1068        assert_that!(&id, is_variant!(Ok));
1069        assert_that!(&id.unwrap(), eq("abc212".to_string()))
1070    }
1071
1072    #[test]
1073    fn validate_id_failure() {
1074        let invalid_id = "with spaces";
1075        let id_type = "id_type";
1076        let id = validate_id(invalid_id, id_type);
1077        assert_that!(&id, is_variant!(Err));
1078        let validation_error = id.unwrap_err();
1079        assert_that!(
1080            &validation_error,
1081            is_variant!(IronOxideErr::ValidationError)
1082        );
1083        assert_that!(&format!("{}", validation_error), contains(id_type));
1084        assert_that!(&format!("{}", validation_error), contains(invalid_id));
1085    }
1086
1087    #[test]
1088    fn validate_id_all_whitespace() {
1089        let invalid_id = "     ";
1090        let id_type = "id_type";
1091        let id = validate_id(invalid_id, id_type);
1092        assert_that!(&id, is_variant!(Err));
1093        let validation_error = id.unwrap_err();
1094        assert_that!(
1095            &validation_error,
1096            is_variant!(IronOxideErr::ValidationError)
1097        );
1098        assert_that!(&format!("{}", validation_error), contains(id_type));
1099    }
1100
1101    #[test]
1102    fn validate_name_success() {
1103        let valid_name = "name with any char _.$#|@/:;=+'-";
1104        let id = validate_name(valid_name, "name_type");
1105        assert_that!(&id, is_variant!(Ok));
1106        assert_that!(&id.unwrap(), eq(valid_name.to_string()))
1107    }
1108
1109    #[test]
1110    fn validate_name_surrounding_whitespace() {
1111        let valid_name = "   a good name    ";
1112        let id = validate_name(valid_name, "name_type");
1113        assert_that!(&id, is_variant!(Ok));
1114        assert_that!(&id.unwrap(), eq("a good name".to_string()))
1115    }
1116
1117    #[test]
1118    fn validate_name_failure() {
1119        let name_type = "name_type";
1120        let invalid_name = "too many chars 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789";
1121        let name = validate_name(invalid_name, name_type);
1122        assert_that!(&name, is_variant!(Err));
1123        let validation_error = name.unwrap_err();
1124        assert_that!(
1125            &validation_error,
1126            is_variant!(IronOxideErr::ValidationError)
1127        );
1128        assert_that!(&format!("{}", validation_error), contains(invalid_name));
1129        assert_that!(&format!("{}", validation_error), contains(name_type));
1130    }
1131
1132    #[test]
1133    fn validate_name_all_whitespace() {
1134        let invalid_name = "        ";
1135        let name_type = "name_type";
1136
1137        let name = validate_name(invalid_name, name_type);
1138        assert_that!(&name, is_variant!(Err));
1139        let validation_error = name.unwrap_err();
1140        assert_that!(
1141            &validation_error,
1142            is_variant!(IronOxideErr::ValidationError)
1143        );
1144        assert_that!(&format!("{}", validation_error), contains(name_type));
1145    }
1146
1147    #[test]
1148    fn passphrase_validation() {
1149        let result = Password::try_from("");
1150        assert!(result.is_err())
1151    }
1152
1153    #[test]
1154    fn encode_proto_public_key() -> Result<()> {
1155        let recr = recrypt::api::Recrypt::new();
1156        let (_, re_pubk) = recr.generate_key_pair()?;
1157        let pubk: PublicKey = re_pubk.into();
1158
1159        let proto_pubk: crate::proto::transform::PublicKey = pubk.clone().into();
1160        assert_eq!(
1161            (&pubk.to_bytes_x_y().0, &pubk.to_bytes_x_y().1),
1162            (&proto_pubk.x.to_vec(), &proto_pubk.y.to_vec())
1163        );
1164        Ok(())
1165    }
1166    #[test]
1167    fn public_key_postcard_roundtrip() {
1168        let recr = Recrypt::new();
1169        let (_, re_pubk) = recr.generate_key_pair().unwrap();
1170        let pubk: PublicKey = re_pubk.into();
1171
1172        let bytes = postcard::to_stdvec(&pubk).unwrap();
1173        let deserialized: PublicKey = postcard::from_bytes(&bytes).unwrap();
1174        assert_eq!(pubk, deserialized);
1175    }
1176
1177    #[test]
1178    fn public_key_deserialize_wrong_length_fails() {
1179        let bytes = postcard::to_stdvec(&vec![0u8; 30]).unwrap(); // too short for a public key
1180        let result: StdResult<PublicKey, _> = postcard::from_bytes(&bytes);
1181        assert!(result.is_err());
1182    }
1183
1184    #[test]
1185    fn public_key_try_from_slice() -> Result<()> {
1186        let recr = recrypt::api::Recrypt::new();
1187        let (_, re_pubk) = recr.generate_key_pair()?;
1188        let pubk: PublicKey = re_pubk.into();
1189        let pubk2: PublicKey = pubk.as_bytes().as_slice().try_into()?;
1190        assert_eq!(pubk, pubk2);
1191        Ok(())
1192    }
1193
1194    #[test]
1195    fn public_key_try_from_slice_invalid() {
1196        let bytes = [1u8; 8];
1197        let maybe_public_key: Result<PublicKey> = bytes[..].try_into();
1198        assert!(maybe_public_key.is_err())
1199    }
1200
1201    pub fn gen_priv_key() -> PrivateKey {
1202        let recr = recrypt::api::Recrypt::new();
1203        let (re_privk, _) = recr.generate_key_pair().unwrap();
1204        re_privk.into()
1205    }
1206
1207    #[test]
1208    fn private_key_augment_with_self_is_none() {
1209        let privk = gen_priv_key();
1210
1211        let result = privk.augment_user(&AugmentationFactor(privk.clone()));
1212        assert_that!(&result, is_variant!(Err));
1213        assert_that!(
1214            &result.unwrap_err(),
1215            is_variant!(IronOxideErr::UserPrivateKeyRotationError)
1216        )
1217    }
1218
1219    #[test]
1220    fn private_key_augmentation_is_augment_minus() {
1221        let p1 = gen_priv_key();
1222        let p2 = gen_priv_key();
1223
1224        let p3 = p1.0.augment_minus(&p2.0);
1225
1226        let aug_p = p1.augment_user(&AugmentationFactor(p2)).unwrap();
1227        assert_eq!(aug_p.0, p3)
1228    }
1229
1230    #[test]
1231    fn private_key_augmentation_aug_key_of_zero_is_err() {
1232        let priv_key_orig = gen_priv_key();
1233        let zero_aug_factor = AugmentationFactor(PrivateKey(RecryptPrivateKey::new([0u8; 32])));
1234        let new_priv_key = priv_key_orig.augment_user(&zero_aug_factor);
1235        assert_that!(&new_priv_key, is_variant!(Err));
1236        assert_that!(
1237            &new_priv_key.unwrap_err(),
1238            is_variant!(IronOxideErr::UserPrivateKeyRotationError)
1239        )
1240    }
1241
1242    mock_trait!(
1243        MockKeyGenOps,
1244        random_private_key() -> recrypt::api::PrivateKey
1245    );
1246    impl KeyGenOps for MockKeyGenOps {
1247        fn compute_public_key(
1248            &self,
1249            _private_key: &RecryptPrivateKey,
1250        ) -> StdResult<RecryptPublicKey, RecryptErr> {
1251            unimplemented!()
1252        }
1253
1254        mock_method!(random_private_key(&self) -> RecryptPrivateKey);
1255
1256        fn generate_key_pair(
1257            &self,
1258        ) -> StdResult<(RecryptPrivateKey, RecryptPublicKey), RecryptErr> {
1259            unimplemented!()
1260        }
1261
1262        fn generate_transform_key(
1263            &self,
1264            _from_private_key: &RecryptPrivateKey,
1265            _to_public_key: &RecryptPublicKey,
1266            _signing_keypair: &recrypt::api::SigningKeypair,
1267        ) -> StdResult<recrypt::api::TransformKey, RecryptErr> {
1268            unimplemented!()
1269        }
1270    }
1271    mock_trait!(MockCryptoOps,
1272        gen_plaintext() -> recrypt::api::Plaintext
1273    );
1274    impl CryptoOps for MockCryptoOps {
1275        fn derive_symmetric_key(
1276            &self,
1277            _: &recrypt::api::Plaintext,
1278        ) -> recrypt::api::DerivedSymmetricKey {
1279            unimplemented!()
1280        }
1281        mock_method!(gen_plaintext(&self) -> recrypt::api::Plaintext);
1282        fn transform(
1283            &self,
1284            _: recrypt::api::EncryptedValue,
1285            _: recrypt::api::TransformKey,
1286            _: &recrypt::api::SigningKeypair,
1287        ) -> StdResult<recrypt::api::EncryptedValue, RecryptErr> {
1288            unimplemented!()
1289        }
1290        fn decrypt(
1291            &self,
1292            _: recrypt::api::EncryptedValue,
1293            _: &recrypt::api::PrivateKey,
1294        ) -> StdResult<recrypt::api::Plaintext, RecryptErr> {
1295            unimplemented!()
1296        }
1297        fn encrypt(
1298            &self,
1299            _: &recrypt::api::Plaintext,
1300            _: &recrypt::api::PublicKey,
1301            _: &recrypt::api::SigningKeypair,
1302        ) -> StdResult<recrypt::api::EncryptedValue, RecryptErr> {
1303            unimplemented!()
1304        }
1305        fn derive_private_key(&self, pt: &recrypt::api::Plaintext) -> recrypt::api::PrivateKey {
1306            let recrypt = recrypt::api::Recrypt::new();
1307            recrypt.derive_private_key(pt)
1308        }
1309    }
1310    #[test]
1311    fn augment_private_key_with_retry_retries_once() {
1312        let recrypt_mock = MockKeyGenOps::default();
1313        let good_re_private_key = RecryptPrivateKey::new([42u8; 32]); // good private key
1314        recrypt_mock.random_private_key.return_values(vec![
1315            RecryptPrivateKey::new([0u8; 32]), // bad private key, 0s
1316            good_re_private_key.clone(),       // good private key. Used for aug factor
1317        ]);
1318
1319        let curr_priv_key = PrivateKey::from([100u8; 32]);
1320        let expected_priv_key_bytes: [u8; 32] = [
1321            58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58,
1322            58, 58, 58, 58, 58, 58, 58, 58, 58, 58,
1323        ];
1324
1325        let result = augment_private_key_with_retry(&recrypt_mock, &curr_priv_key).unwrap();
1326        assert_eq!(
1327            (result.0).0,
1328            RecryptPrivateKey::new(expected_priv_key_bytes)
1329        );
1330        assert_eq!(((result.1).0).0, good_re_private_key)
1331    }
1332    #[test]
1333    fn augment_private_key_with_retry_retries_only_once() {
1334        let recrypt_mock = MockKeyGenOps::default();
1335        recrypt_mock.random_private_key.return_values(vec![
1336            RecryptPrivateKey::new([0u8; 32]),   // bad private key, 0s
1337            RecryptPrivateKey::new([100u8; 32]), // bad private key, matches current
1338            RecryptPrivateKey::new([42u8; 32]),  // good private key, never returned
1339        ]);
1340
1341        let curr_priv_key = PrivateKey::from([100u8; 32]);
1342
1343        let result = augment_private_key_with_retry(&recrypt_mock, &curr_priv_key);
1344        assert_that!(
1345            &result.unwrap_err(),
1346            is_variant!(IronOxideErr::UserPrivateKeyRotationError)
1347        );
1348    }
1349    #[test]
1350    fn gen_plaintext_and_diff_with_retry_retries_once() {
1351        let recrypt_mock = MockCryptoOps::default();
1352        // creating a real recrypt to make a valid plaintext
1353        let recrypt = recrypt::api::Recrypt::new();
1354        let bad_plaintext = recrypt.gen_plaintext();
1355        let bad_private_key = recrypt.derive_private_key(&bad_plaintext);
1356        let good_plaintext = recrypt.gen_plaintext();
1357        recrypt_mock
1358            .gen_plaintext
1359            .return_values(vec![bad_plaintext, good_plaintext.clone()]);
1360
1361        // since this will generate bad_plaintext, which bad_private_key is derived from,
1362        // the augmentation will result in zero, causing the function to retry.
1363        let result =
1364            gen_plaintext_and_aug_with_retry(&recrypt_mock, &bad_private_key.into()).unwrap();
1365        assert_eq!(result.0, good_plaintext);
1366    }
1367
1368    #[test]
1369    fn gen_plaintext_and_diff_with_retry_retries_only_once() {
1370        let recrypt_mock = MockCryptoOps::default();
1371        // creating a real recrypt to make a valid plaintext
1372        let recrypt = recrypt::api::Recrypt::new();
1373        let bad_plaintext = recrypt.gen_plaintext();
1374        let bad_private_key = recrypt.derive_private_key(&bad_plaintext);
1375        let good_plaintext = recrypt.gen_plaintext();
1376        // Ideally this would also check that it retries/fails when the generated private key is zero,
1377        // but I don't know the plaintext to return to force that to happen.
1378        // Mocking `derive_private_key()` doesn't appear to be possible without Eq and Hash on Plaintext.
1379        recrypt_mock.gen_plaintext.return_values(vec![
1380            bad_plaintext.clone(),
1381            bad_plaintext,
1382            good_plaintext,
1383        ]);
1384
1385        // since this will generate bad_plaintext, which bad_private_key is derived from,
1386        // the augmentation will result in zero, causing the function to retry.
1387        let result = gen_plaintext_and_aug_with_retry(&recrypt_mock, &bad_private_key.into());
1388        assert_that!(
1389            &result.unwrap_err(),
1390            is_variant!(IronOxideErr::GroupPrivateKeyRotationError)
1391        );
1392    }
1393
1394    #[test]
1395    fn init_and_rotation_user_and_groups() -> Result<()> {
1396        use crate::{
1397            InitAndRotationCheck, IronOxide, check_groups_and_collect_rotation,
1398            internal::{
1399                group_api::tests::create_group_meta_result, user_api::tests::create_user_result,
1400            },
1401        };
1402        let recrypt = recrypt::api::Recrypt::new();
1403        let (_, pub_key) = recrypt.generate_key_pair()?;
1404        let time = OffsetDateTime::now_utc();
1405        let create_gmr = |id: GroupId, needs_rotation: Option<bool>| {
1406            create_group_meta_result(
1407                id,
1408                None,
1409                pub_key.into(),
1410                true,
1411                true,
1412                time,
1413                time,
1414                needs_rotation,
1415            )
1416        };
1417        let de_json = r#"{"deviceId":314,"accountId":"account_id","segmentId":22,"signingPrivateKey":"AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQGKiOPddAnxlf1S2y08ul1yymcJvx2UEhvzdIgBtA9vXA==","devicePrivateKey":"bzb0Rlg0u7gx9wDuk1ppRI77OH/0ferXleenJ3Ag6Jg="}"#;
1418        let de: DeviceContext = serde_json::from_str(de_json).unwrap();
1419        let user_id = UserId::try_from("account_id")?;
1420        let user = create_user_result(user_id.clone(), 22, pub_key.into(), true);
1421        let io = IronOxide::create(&user, &de, &Default::default());
1422
1423        let good_group_id = GroupId::try_from("group")?;
1424        let gmr_vec = vec![
1425            create_gmr(good_group_id.clone(), Some(true)),
1426            create_gmr(GroupId::try_from("notthisone")?, Some(false)),
1427            create_gmr(GroupId::try_from("northisone")?, None),
1428        ];
1429        let init = check_groups_and_collect_rotation(&gmr_vec, true, user_id.clone(), io);
1430        let rotation = match init {
1431            InitAndRotationCheck::NoRotationNeeded(_) => panic!("user and group need rotation"),
1432            InitAndRotationCheck::RotationNeeded(_, rotation) => rotation,
1433        };
1434        assert_eq!(
1435            rotation.group_rotation_needed(),
1436            Some(&vec1![good_group_id])
1437        );
1438        assert_eq!(rotation.user_rotation_needed(), Some(&user_id));
1439        Ok(())
1440    }
1441
1442    #[tokio::test]
1443    async fn run_maybe_timed_sdk_op_no_timeout() -> Result<()> {
1444        async fn get_42() -> u8 {
1445            tokio::time::sleep(Duration::from_millis(100)).await;
1446            42
1447        }
1448        let forty_two = get_42();
1449        let result =
1450            add_optional_timeout(forty_two, None, SdkOperation::DocumentRevokeAccess).await?;
1451        assert_eq!(result, 42);
1452
1453        let forty_two = get_42();
1454        let result = add_optional_timeout(
1455            forty_two,
1456            Some(Duration::from_secs(1)),
1457            SdkOperation::DocumentRevokeAccess,
1458        )
1459        .await?;
1460        assert_eq!(result, 42);
1461
1462        async fn get_err() -> Result<()> {
1463            tokio::time::sleep(Duration::from_millis(100)).await;
1464            Err(IronOxideErr::MissingTransformBlocks)
1465        }
1466
1467        let err_f = get_err();
1468        let result = add_optional_timeout(err_f, None, SdkOperation::DocumentRevokeAccess).await?;
1469        assert!(result.is_err());
1470        assert_that!(
1471            &result.unwrap_err(),
1472            is_variant!(IronOxideErr::MissingTransformBlocks)
1473        );
1474
1475        let err_f = get_err();
1476        let result = add_optional_timeout(
1477            err_f,
1478            Some(Duration::from_secs(1)),
1479            SdkOperation::DocumentRevokeAccess,
1480        )
1481        .await?;
1482        assert!(result.is_err());
1483        assert_that!(
1484            &result.unwrap_err(),
1485            is_variant!(IronOxideErr::MissingTransformBlocks)
1486        );
1487
1488        Ok(())
1489    }
1490
1491    #[tokio::test]
1492    async fn run_maybe_timed_sdk_op_with_timeout() -> Result<()> {
1493        async fn get_42() -> u8 {
1494            // allow other futures to run, like the timer
1495            // without this the future will run to completion, regardless of the timer
1496            tokio::time::sleep(Duration::from_millis(100)).await;
1497            42
1498        }
1499
1500        let forty_two = get_42();
1501        let result = add_optional_timeout(
1502            forty_two,
1503            Some(Duration::from_nanos(1)),
1504            SdkOperation::DocumentRevokeAccess,
1505        )
1506        .await;
1507        assert!(result.is_err());
1508        assert_that!(
1509            &result.unwrap_err(),
1510            is_variant!(IronOxideErr::OperationTimedOut)
1511        );
1512
1513        async fn get_err() -> Result<u8> {
1514            tokio::time::sleep(Duration::from_millis(100)).await;
1515            Err(IronOxideErr::MissingTransformBlocks)
1516        }
1517
1518        let err_f = get_err();
1519        let result = add_optional_timeout(
1520            err_f,
1521            Some(Duration::from_millis(1)),
1522            SdkOperation::DocumentRevokeAccess,
1523        )
1524        .await;
1525        assert!(result.is_err());
1526        assert_that!(
1527            &result.unwrap_err(),
1528            is_variant!(IronOxideErr::OperationTimedOut)
1529        );
1530        Ok(())
1531    }
1532    #[test]
1533    fn signing_key_sign_then_verify() {
1534        let recrypt = Recrypt::new();
1535        let signing_keypair = recrypt.generate_ed25519_key_pair();
1536        let device_keypair = DeviceSigningKeyPair::from(signing_keypair);
1537
1538        let message = b"test payload";
1539        let signature = device_keypair.sign(message);
1540        assert!(
1541            device_keypair
1542                .verify(&message.as_slice(), &signature)
1543                .unwrap()
1544        );
1545    }
1546    #[test]
1547    fn signing_key_verify_wrong_message_fails() {
1548        let recrypt = Recrypt::new();
1549        let signing_keypair = recrypt.generate_ed25519_key_pair();
1550        let device_keypair = DeviceSigningKeyPair::from(signing_keypair);
1551
1552        let signature = device_keypair.sign(b"original");
1553        assert!(
1554            !device_keypair
1555                .verify(&b"tampered".as_slice(), &signature)
1556                .unwrap()
1557        );
1558    }
1559    #[test]
1560    fn signing_key_verify_bad_signature_length_fails() {
1561        let recrypt = Recrypt::new();
1562        let signing_keypair = recrypt.generate_ed25519_key_pair();
1563        let device_keypair = DeviceSigningKeyPair::from(signing_keypair);
1564
1565        let result = device_keypair.verify(&b"message".as_slice(), &[0u8; 32]);
1566        assert!(result.is_err());
1567    }
1568    #[test]
1569    fn empty_public_key_cache_roundtrip() -> Result<()> {
1570        let recr = recrypt::api::Recrypt::new();
1571        let (_, re_pubk) = recr.generate_key_pair()?;
1572        let cache = PublicKeyCache::new(&re_pubk.into());
1573        let bytes = cache.serialize().unwrap();
1574        let deserialized = PublicKeyCache::deserialize(&bytes).unwrap();
1575        assert_eq!(deserialized.user_keys().len(), 0);
1576        assert_eq!(deserialized.group_keys().len(), 0);
1577        Ok(())
1578    }
1579    #[test]
1580    fn populated_public_key_cache_roundtrip() {
1581        let recrypt = Recrypt::new();
1582        let (_, pub1) = recrypt.generate_key_pair().unwrap();
1583        let (_, pub2) = recrypt.generate_key_pair().unwrap();
1584
1585        let cache = PublicKeyCache::new(&pub1.into());
1586        cache
1587            .user_keys()
1588            .pin()
1589            .insert(UserId::unsafe_from_string("user1".into()), pub1.into());
1590        cache
1591            .group_keys()
1592            .pin()
1593            .insert(GroupId::unsafe_from_string("group1".into()), pub2.into());
1594
1595        let bytes = cache.serialize().unwrap();
1596        let deserialized = PublicKeyCache::deserialize(&bytes).unwrap();
1597
1598        let user_id = UserId::unsafe_from_string("user1".into());
1599        let group_id = GroupId::unsafe_from_string("group1".into());
1600        assert!(deserialized.user_keys().pin().get(&user_id).is_some());
1601        assert!(deserialized.group_keys().pin().get(&group_id).is_some());
1602    }
1603    #[test]
1604    fn public_key_cache_deserialize_garbage_bytes_fails() {
1605        let result = PublicKeyCache::deserialize(b"deadbeef");
1606        assert!(result.is_err());
1607    }
1608    pub(crate) fn create_test_device_context() -> DeviceContext {
1609        let priv_key: recrypt::api::PrivateKey = recrypt::api::PrivateKey::new_from_slice(
1610            BASE64_STANDARD
1611                .decode("bzb0Rlg0u7gx9wDuk1ppRI77OH/0ferXleenJ3Ag6Jg=")
1612                .unwrap()
1613                .as_slice(),
1614        )
1615        .unwrap();
1616        let dev_keys = recrypt::api::SigningKeypair::from_byte_slice(&[
1617            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1618            1, 1, 1, 138, 136, 227, 221, 116, 9, 241, 149, 253, 82, 219, 45, 60, 186, 93, 114, 202,
1619            103, 9, 191, 29, 148, 18, 27, 243, 116, 136, 1, 180, 15, 111, 92,
1620        ])
1621        .unwrap();
1622        DeviceContext::new(
1623            "account_id".try_into().unwrap(),
1624            22,
1625            priv_key.into(),
1626            DeviceSigningKeyPair::from(dev_keys),
1627        )
1628    }
1629
1630    pub(crate) fn create_test_sdk() -> Result<crate::IronOxide> {
1631        use crate::{IronOxide, internal::user_api::tests::create_user_result};
1632        let recrypt = Recrypt::new();
1633        let device = create_test_device_context();
1634        let user_id = UserId::try_from("account_id")?;
1635        let (_, pubk) = recrypt.generate_key_pair()?;
1636        let user = create_user_result(user_id.clone(), 22, pubk.into(), true);
1637        let io = IronOxide::create(&user, &device, &Default::default());
1638        Ok(io)
1639    }
1640
1641    mod signed {
1642        use super::*;
1643        #[test]
1644        fn signed_cache_roundtrip() -> Result<()> {
1645            let io = create_test_sdk()?;
1646            let recrypt = Recrypt::new();
1647            let device = super::create_test_device_context();
1648            let (_, pubk) = recrypt.generate_key_pair()?;
1649
1650            let user_id = UserId::unsafe_from_string("user1".into());
1651            io.public_key_cache
1652                .user_keys()
1653                .pin()
1654                .insert(user_id.clone(), pubk.into());
1655
1656            let signed = io.export_public_key_cache()?;
1657            let result = PublicKeyCache::deserialize_signed_public_key_cache(&device, &signed)?;
1658            // Cache contains 2 entries: the current user (account_id) and the inserted user (user1)
1659            assert!(result.user_keys().len() == 2);
1660            let user_keys = result.user_keys().pin();
1661            let deser_pubk = user_keys
1662                .get(&user_id)
1663                .expect("expected inserted user to exist");
1664            assert_eq!(pubk, deser_pubk.0);
1665            Ok(())
1666        }
1667        #[test]
1668        fn signed_cache_tampered_payload_fails() -> Result<()> {
1669            let io = create_test_sdk()?;
1670            let mut signed = io.export_public_key_cache()?;
1671            // flip a byte in the cache portion
1672            let last = signed.len() - 1;
1673            signed[last] ^= 0xFF;
1674
1675            let result = PublicKeyCache::deserialize_signed_public_key_cache(io.device(), &signed);
1676            assert!(result.is_err());
1677            Ok(())
1678        }
1679        #[test]
1680        fn signed_cache_tampered_signature_fails() -> Result<()> {
1681            let io = create_test_sdk()?;
1682            let mut signed = io.export_public_key_cache()?;
1683            // flip a byte in the signature portion
1684            signed[0] ^= 0xFF;
1685
1686            let result = PublicKeyCache::deserialize_signed_public_key_cache(io.device(), &signed);
1687            assert!(result.is_err());
1688            Ok(())
1689        }
1690        #[test]
1691        fn signed_cache_wrong_device_fails() -> Result<()> {
1692            let io = create_test_sdk()?;
1693            let signed = io.export_public_key_cache()?;
1694            let priv_key: recrypt::api::PrivateKey = recrypt::api::PrivateKey::new_from_slice(
1695                BASE64_STANDARD
1696                    .decode("bzb0Rlg0u7gx9wHuk1ppRI77OH/0ferXleenJ3Ag6Jg=")
1697                    .unwrap()
1698                    .as_slice(),
1699            )
1700            .unwrap();
1701            let dev_keys = recrypt::api::SigningKeypair::from_byte_slice(&[
1702                170, 222, 254, 96, 86, 46, 15, 233, 203, 170, 231, 41, 118, 13, 34, 45, 185, 234,
1703                6, 174, 28, 76, 100, 181, 86, 227, 113, 24, 4, 72, 162, 110, 16, 178, 40, 148, 87,
1704                243, 110, 163, 178, 75, 158, 100, 181, 167, 187, 6, 174, 69, 7, 78, 176, 97, 96,
1705                106, 28, 101, 179, 30, 150, 195, 24, 28,
1706            ])
1707            .unwrap();
1708            let wrong_device = DeviceContext::new(
1709                "account_id_2".try_into().unwrap(),
1710                23,
1711                priv_key.into(),
1712                DeviceSigningKeyPair::from(dev_keys),
1713            );
1714            let result =
1715                PublicKeyCache::deserialize_signed_public_key_cache(&wrong_device, &signed);
1716            assert!(result.is_err());
1717            Ok(())
1718        }
1719        #[test]
1720        fn signed_cache_too_short_fails() {
1721            let device = create_test_device_context();
1722            let result = PublicKeyCache::deserialize_signed_public_key_cache(&device, &[0u8; 32]);
1723            assert!(matches!(result, Err(IronOxideErr::WrongSizeError(_, _))));
1724        }
1725        #[test]
1726        fn signed_cache_exactly_64_bytes_no_payload_fails() {
1727            let device = create_test_device_context();
1728            let result = PublicKeyCache::deserialize_signed_public_key_cache(&device, &[0u8; 64]);
1729            // signature over empty payload won't match, or deserialization of empty bytes fails
1730            assert!(result.is_err());
1731        }
1732        #[test]
1733        // Build an IronOxide with a populated cache, export, then verify+deserialize
1734        fn export_then_deserialize_signed_roundtrip() -> Result<()> {
1735            let device = create_test_device_context();
1736            let recr = Recrypt::new();
1737            let (_, pubk) = recr.generate_key_pair().unwrap();
1738
1739            let io = create_test_sdk()?;
1740            io.public_key_cache
1741                .user_keys()
1742                .pin()
1743                .insert(UserId::unsafe_from_string("user1".into()), pubk.into());
1744
1745            let exported = io.export_public_key_cache().unwrap();
1746            let reimported =
1747                PublicKeyCache::deserialize_signed_public_key_cache(&device, &exported);
1748            assert!(reimported.is_ok());
1749            Ok(())
1750        }
1751    }
1752    #[tokio::test]
1753    async fn cache_lookup_empty_input_returns_empty() {
1754        let cache: HashMap<UserId, PublicKey> = HashMap::new();
1755        let (not_found, found) = get_keys_with_cache(&[], &cache, |_| async {
1756            panic!("fetch should not be called")
1757        })
1758        .await
1759        .unwrap();
1760        assert!(not_found.is_empty());
1761        assert!(found.is_empty());
1762    }
1763    #[tokio::test]
1764    async fn cache_lookup_all_cached_skips_fetch() {
1765        let recr = Recrypt::new();
1766        let (_, pubk) = recr.generate_key_pair().unwrap();
1767        let uid = UserId::unsafe_from_string("user1".into());
1768
1769        let cache: HashMap<UserId, PublicKey> = HashMap::new();
1770        cache.pin().insert(uid.clone(), pubk.into());
1771
1772        let (not_found, found) = get_keys_with_cache(&[uid.clone()], &cache, |_| async {
1773            panic!("fetch should not be called when fully cached")
1774        })
1775        .await
1776        .unwrap();
1777
1778        assert!(not_found.is_empty());
1779        assert_eq!(found.len(), 1);
1780        assert_eq!(found[0].id, uid);
1781    }
1782    #[tokio::test]
1783    async fn cache_lookup_partial_cache_only_fetches_misses() {
1784        let recr = Recrypt::new();
1785        let (_, pub1) = recr.generate_key_pair().unwrap();
1786        let (_, pub2) = recr.generate_key_pair().unwrap();
1787        let cached_user = UserId::unsafe_from_string("cached".into());
1788        let uncached_user = UserId::unsafe_from_string("uncached".into());
1789        let io_pub2: PublicKey = pub2.into();
1790
1791        let cache: HashMap<UserId, PublicKey> = HashMap::new();
1792        cache.pin().insert(cached_user.clone(), pub1.into());
1793
1794        let pub2_clone = io_pub2.clone();
1795        let uncached_clone = uncached_user.clone();
1796        let (not_found, found) = get_keys_with_cache(
1797            &[cached_user.clone(), uncached_user.clone()],
1798            &cache,
1799            move |ids| async move {
1800                assert_eq!(ids.len(), 1, "should only try to fetch uncached ids");
1801                assert_eq!(ids[0], uncached_clone);
1802                let mut map = std::collections::HashMap::new();
1803                map.insert(uncached_clone, pub2_clone);
1804                Ok(map)
1805            },
1806        )
1807        .await
1808        .unwrap();
1809
1810        assert!(not_found.is_empty());
1811        assert_eq!(found.len(), 2);
1812    }
1813    #[tokio::test]
1814    async fn cache_lookup_populates_cache_from_fetch() {
1815        let recr = Recrypt::new();
1816        let (_, pubk) = recr.generate_key_pair().unwrap();
1817        let uid = UserId::unsafe_from_string("user1".into());
1818        let io_pub: PublicKey = pubk.into();
1819
1820        let cache: HashMap<UserId, PublicKey> = HashMap::new();
1821        assert!(cache.pin().get(&uid).is_none());
1822
1823        let pub_clone = io_pub.clone();
1824        let uid_clone = uid.clone();
1825        get_keys_with_cache(&[uid.clone()], &cache, move |_| async move {
1826            let mut map = std::collections::HashMap::new();
1827            map.insert(uid_clone, pub_clone);
1828            Ok(map)
1829        })
1830        .await
1831        .unwrap();
1832
1833        // cache should now contain the fetched key
1834        assert!(cache.pin().get(&uid).is_some());
1835    }
1836    #[tokio::test]
1837    async fn cache_lookup_fetch_returns_not_found() {
1838        let uid = UserId::unsafe_from_string("nonexistent".into());
1839        let cache: HashMap<UserId, PublicKey> = HashMap::new();
1840
1841        let (not_found, found) = get_keys_with_cache(&[uid.clone()], &cache, |_| async {
1842            Ok(std::collections::HashMap::new())
1843        })
1844        .await
1845        .unwrap();
1846
1847        assert_eq!(not_found.len(), 1);
1848        assert_eq!(not_found[0], uid);
1849        assert!(found.is_empty());
1850    }
1851    #[tokio::test]
1852    async fn cache_lookup_fetch_error_propagates() {
1853        let uid = UserId::unsafe_from_string("user1".into());
1854        let cache: HashMap<UserId, PublicKey> = HashMap::new();
1855
1856        let result = get_keys_with_cache(&[uid], &cache, |_| async {
1857            Err(IronOxideErr::InitializeError(
1858                "simulated fetch failure".into(),
1859            ))
1860        })
1861        .await;
1862
1863        assert!(result.is_err());
1864    }
1865
1866    mod papaya_serde {
1867        use super::*;
1868
1869        #[test]
1870        fn multi_user_multi_group_roundtrip() {
1871            let recrypt = Recrypt::new();
1872            let (_, creator_pubk) = recrypt.generate_key_pair().unwrap();
1873            let cache = PublicKeyCache::new(&creator_pubk.into());
1874
1875            // insert multiple users
1876            for i in 0..5 {
1877                let (_, pubk) = recrypt.generate_key_pair().unwrap();
1878                cache
1879                    .user_keys()
1880                    .pin()
1881                    .insert(UserId::unsafe_from_string(format!("user_{i}")), pubk.into());
1882            }
1883            // insert multiple groups
1884            for i in 0..3 {
1885                let (_, pubk) = recrypt.generate_key_pair().unwrap();
1886                cache.group_keys().pin().insert(
1887                    GroupId::unsafe_from_string(format!("group_{i}")),
1888                    pubk.into(),
1889                );
1890            }
1891
1892            let bytes = cache.serialize().unwrap();
1893            let deserialized = PublicKeyCache::deserialize(&bytes).unwrap();
1894
1895            assert_eq!(deserialized.user_keys().len(), 5);
1896            assert_eq!(deserialized.group_keys().len(), 3);
1897
1898            // verify specific entries survived
1899            for i in 0..5 {
1900                let uid = UserId::unsafe_from_string(format!("user_{i}"));
1901                let orig = cache.user_keys().pin().get(&uid).unwrap().clone();
1902                let deser = deserialized.user_keys().pin().get(&uid).unwrap().clone();
1903                assert_eq!(orig, deser);
1904            }
1905            for i in 0..3 {
1906                let gid = GroupId::unsafe_from_string(format!("group_{i}"));
1907                let orig = cache.group_keys().pin().get(&gid).unwrap().clone();
1908                let deser = deserialized.group_keys().pin().get(&gid).unwrap().clone();
1909                assert_eq!(orig, deser);
1910            }
1911        }
1912
1913        #[test]
1914        fn same_key_different_ids_roundtrip() {
1915            let recrypt = Recrypt::new();
1916            let (_, shared_pubk) = recrypt.generate_key_pair().unwrap();
1917            let shared_pk: PublicKey = shared_pubk.into();
1918
1919            let cache = PublicKeyCache::new(&shared_pk);
1920            cache.user_keys().pin().insert(
1921                UserId::unsafe_from_string("alice".into()),
1922                shared_pk.clone(),
1923            );
1924            cache
1925                .user_keys()
1926                .pin()
1927                .insert(UserId::unsafe_from_string("bob".into()), shared_pk.clone());
1928
1929            let bytes = cache.serialize().unwrap();
1930            let deserialized = PublicKeyCache::deserialize(&bytes).unwrap();
1931
1932            assert_eq!(deserialized.user_keys().len(), 2);
1933            let alice = deserialized
1934                .user_keys()
1935                .pin()
1936                .get(&UserId::unsafe_from_string("alice".into()))
1937                .unwrap()
1938                .clone();
1939            let bob = deserialized
1940                .user_keys()
1941                .pin()
1942                .get(&UserId::unsafe_from_string("bob".into()))
1943                .unwrap()
1944                .clone();
1945            assert_eq!(alice, bob);
1946            assert_eq!(alice, shared_pk);
1947        }
1948
1949        #[test]
1950        fn truncated_bytes_fail_deserialization() {
1951            let recrypt = Recrypt::new();
1952            let (_, pubk) = recrypt.generate_key_pair().unwrap();
1953
1954            let cache = PublicKeyCache::new(&pubk.into());
1955            cache
1956                .user_keys()
1957                .pin()
1958                .insert(UserId::unsafe_from_string("user1".into()), pubk.into());
1959
1960            let bytes = cache.serialize().unwrap();
1961            // truncate to half the bytes
1962            let truncated = &bytes[..bytes.len() / 2];
1963            let result = PublicKeyCache::deserialize(truncated);
1964            assert!(result.is_err());
1965        }
1966    }
1967}