1use 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#[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 #[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 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 NotGroupAdmin(id: GroupId) {
182 display("You are not an administrator of group '{}'", id.id())
183 }
184 PolicyDoesNotExist {
186 display("No policy is defined. Please visit https://admin.ironcorelabs.com/policy to set a policy")
187 }
188 ProtobufSerdeError(err: ProtobufError) {
190 source(err)
191 }
192 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
227impl 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 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
267pub 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
288pub 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 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 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#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
348#[serde(rename_all = "camelCase")]
349pub struct RequestAuth {
350 account_id: UserId,
352 segment_id: usize,
354 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#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
398#[serde(rename_all = "camelCase")]
399pub struct DeviceContext {
400 #[serde(flatten)]
401 auth: RequestAuth,
402 device_private_key: PrivateKey,
404}
405impl DeviceContext {
406 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 pub fn account_id(&self) -> &UserId {
432 &self.auth.account_id
433 }
434 pub fn segment_id(&self) -> usize {
436 self.auth.segment_id
437 }
438 pub fn signing_private_key(&self) -> &DeviceSigningKeyPair {
440 &self.auth.signing_private_key
441 }
442 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#[derive(Serialize, Deserialize, Debug)]
454pub(crate) struct PublicKeyCache {
455 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
470fn 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 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 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 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 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
561pub(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 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 if uncached.is_empty() {
587 return Ok((vec![], cached));
588 }
589
590 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.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#[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#[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#[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 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#[derive(Clone, Debug, Eq, Hash, PartialEq)]
709pub struct PrivateKey(RecryptPrivateKey);
710impl PrivateKey {
711 const BYTES_SIZE: usize = RecryptPrivateKey::ENCODED_SIZE_BYTES;
712 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 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 let augmented_key = self.0.augment_minus(&augmenting_key.clone().into());
735 Ok(augmented_key.into())
736 }
737 }
738 fn augment_user(&self, augmenting_key: &AugmentationFactor) -> Result<PrivateKey> {
740 self.augment(augmenting_key, IronOxideErr::UserPrivateKeyRotationError)
741 }
742 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#[derive(Clone, Debug)]
794pub(crate) struct AugmentationFactor(PrivateKey);
795impl AugmentationFactor {
796 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#[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 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#[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
901pub(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
927fn 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 aug_private_key().or_else(|_| aug_private_key())
940}
941
942fn 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 aug_private_key().or_else(|_| aug_private_key())
959}
960
961pub 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 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 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 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(); 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]); recrypt_mock.random_private_key.return_values(vec![
1315 RecryptPrivateKey::new([0u8; 32]), good_re_private_key.clone(), ]);
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]), RecryptPrivateKey::new([100u8; 32]), RecryptPrivateKey::new([42u8; 32]), ]);
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 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 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 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 recrypt_mock.gen_plaintext.return_values(vec![
1380 bad_plaintext.clone(),
1381 bad_plaintext,
1382 good_plaintext,
1383 ]);
1384
1385 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 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 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 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 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 assert!(result.is_err());
1731 }
1732 #[test]
1733 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 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 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 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 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 let truncated = &bytes[..bytes.len() / 2];
1963 let result = PublicKeyCache::deserialize(truncated);
1964 assert!(result.is_err());
1965 }
1966 }
1967}