-
Notifications
You must be signed in to change notification settings - Fork 503
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[sdk] WIP: KeyLoaderFacade and CryptoFacade
Co-authored by: [email protected]
- Loading branch information
Showing
7 changed files
with
318 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,215 @@ | ||
#![allow(dead_code)] // TODO: remove this later | ||
|
||
use std::collections::HashMap; | ||
use std::sync::Arc; | ||
use zeroize::Zeroizing; | ||
use crate::crypto::ecc::EccPublicKey; | ||
use crate::crypto::rsa::RSAEncryptionError; | ||
use crate::crypto::tuta_crypt::{PQError, PQMessage}; | ||
use crate::element_value::{ElementValue, GeneratedId, ParsedEntity}; | ||
use crate::metamodel::TypeModel; | ||
use super::key_loader_facade::{AsymmetricKeyPair, GenericAesKey, KeyLoaderFacade, KeyLoadError}; | ||
|
||
const OWNER_ENC_SESSION_KEY_NAME: &'static str = "_ownerEncSessionKey"; | ||
const OWNER_KEY_VERSION_NAME: &'static str = "_ownerKeyVersion"; | ||
const OWNER_GROUP_NAME: &'static str = "_ownerGroup"; | ||
|
||
macro_rules! get_field { | ||
($entity:expr, $field:expr, $type:tt) => { | ||
match $entity.get($field) { | ||
Some(ElementValue::$type(q)) => Ok(q), | ||
_ => Err(SessionKeyResolutionError { reason: format!("no field `{}` in entity", $field) }) | ||
} | ||
}; | ||
} | ||
|
||
macro_rules! get_nullable_field { | ||
($entity:expr, $field:expr, $type:tt) => { | ||
match $entity.get($field) { | ||
Some(ElementValue::$type(q)) => Ok(Some(q)), | ||
None => Ok(None), | ||
_ => Err(SessionKeyResolutionError { reason: format!("field `{}` is not the expected type", $field) }) | ||
} | ||
}; | ||
} | ||
|
||
#[derive(uniffi::Object)] | ||
pub struct CryptoFacade { | ||
key_loader_facade: Arc<KeyLoaderFacade> | ||
} | ||
|
||
impl CryptoFacade { | ||
pub fn resolve_session_key(&self, entity: &ParsedEntity, model: &TypeModel) -> Result<Option<GenericAesKey>, SessionKeyResolutionError> { | ||
if !model.encrypted { | ||
return Ok(None) | ||
} | ||
|
||
if let Some(ElementValue::Array(bucket_key_data)) = entity.get("bucketKey") { | ||
let ElementValue::Dict(bucket_key_entry) = &bucket_key_data[0] else { | ||
unreachable!("bucketKey was not a dictionary") | ||
}; | ||
|
||
return self.resolve_bucket_key(bucket_key_entry, entity, model).map(|o| Some(o)); | ||
} | ||
|
||
let EntityOwnerKeyData { | ||
owner_enc_session_key, | ||
owner_key_version, | ||
owner_group, | ||
} = EntityOwnerKeyData::extract_owner_key_data(entity)?; | ||
|
||
let group_key = self.key_loader_facade.get_group_key(owner_group, *owner_key_version)?; | ||
Ok(group_key.decrypt_key(owner_enc_session_key).map(|k| Some(k))?) | ||
} | ||
|
||
fn resolve_bucket_key(&self, bucket_key_data: &HashMap<String, ElementValue>, entity: &ParsedEntity, model: &TypeModel) -> Result<GenericAesKey, SessionKeyResolutionError> { | ||
let bucket_key = BucketKey::new(bucket_key_data)?; | ||
let mut auth_status = None; | ||
|
||
let resolved_key = if let (Some(key_group), Some(pub_enc_bucket_key)) = (bucket_key.key_group, bucket_key.pub_enc_bucket_key) { | ||
let keypair = self.key_loader_facade.get_asymmetric_key_pair(key_group, bucket_key.recipient_key_version)?; | ||
match keypair { | ||
AsymmetricKeyPair::PQKeyPair(k) => { | ||
let bucket_key = GenericAesKey::Aes256 { key: PQMessage::deserialize(pub_enc_bucket_key)?.decapsulate(&k)? }; | ||
ResolvedBucketKey { | ||
bucket_key, | ||
sender_identity_key: Some(k.ecc_keys.public_key) | ||
} | ||
}, | ||
AsymmetricKeyPair::RSAKeyPair(k) => { | ||
let bucket_key_bytes = Zeroizing::new(k.private_key.decrypt(pub_enc_bucket_key)?); | ||
let bucket_key = GenericAesKey::from_bytes(bucket_key_bytes.as_slice())?.into(); | ||
ResolvedBucketKey { | ||
bucket_key, | ||
sender_identity_key: None | ||
} | ||
} | ||
} | ||
} else if let Some(group_enc_bucket_key) = bucket_key.group_enc_bucket_key { | ||
let key_group = match bucket_key.key_group { | ||
Some(n) => n, | ||
None => get_field!(entity, OWNER_GROUP_NAME, GeneratedId)? | ||
}; | ||
|
||
auth_status = Some(EncryptionAuthStatus::AESNoAuthentication); | ||
todo!("secure external resolveWithGroupReference") | ||
} else { | ||
return Err(SessionKeyResolutionError { reason: format!("encrypted bucket key not set on instance {}", model.name) }) | ||
}; | ||
|
||
todo!("authenticate, update instance session keys, remove bucket key; this is going to be fun"); | ||
} | ||
} | ||
|
||
pub enum EncryptionAuthStatus { | ||
RSANoAuthentication = 0, | ||
TutacryptAuthenticationSucceeded = 1, | ||
TutacryptAuthenticationFailed = 2, | ||
AESNoAuthentication = 3, | ||
TutacryptSender = 4, | ||
} | ||
|
||
struct ResolvedBucketKey { | ||
bucket_key: GenericAesKey, | ||
sender_identity_key: Option<EccPublicKey> | ||
} | ||
|
||
// FIXME: replace with BucketKey/InstanceSessionKey from model | ||
struct BucketKey<'a> { | ||
group_enc_bucket_key: Option<&'a Vec<u8>>, | ||
protocol_version: i64, | ||
pub_enc_bucket_key: Option<&'a Vec<u8>>, | ||
recipient_key_version: i64, | ||
sender_key_version: i64, | ||
bucket_enc_session_keys: &'a Vec<ElementValue>, | ||
key_group: Option<&'a GeneratedId> | ||
} | ||
struct InstanceSessionKey<'a> { | ||
encryption_auth_status: Option<&'a Vec<u8>>, | ||
instance_id: &'a GeneratedId, | ||
instance_list: &'a GeneratedId, | ||
sym_enc_session_key: &'a Vec<u8>, | ||
sym_key_version: i64, | ||
} | ||
|
||
impl<'a> InstanceSessionKey<'a> { | ||
pub fn new(data: &'a ElementValue) -> Result<InstanceSessionKey<'a>, SessionKeyResolutionError> { | ||
let ElementValue::Dict(k) = data else { | ||
return Err(SessionKeyResolutionError { reason: "bucket key instance session key is not a dict".to_owned() }); | ||
}; | ||
|
||
let encryption_auth_status = get_nullable_field!(k, "encryptionAuthStatus", Bytes)?; | ||
let instance_id = get_field!(k, "instanceId", GeneratedId)?; | ||
let instance_list = get_field!(k, "instanceList", GeneratedId)?; | ||
let sym_enc_session_key = get_field!(k, "symEncSessionKey", Bytes)?; | ||
let sym_key_version = *get_field!(k, "symKeyVersion", Number)?; | ||
|
||
Ok(InstanceSessionKey { | ||
sym_key_version, | ||
sym_enc_session_key, | ||
instance_list, | ||
instance_id, | ||
encryption_auth_status | ||
}) | ||
} | ||
} | ||
|
||
impl<'a> BucketKey<'a> { | ||
pub fn new(data: &'a HashMap<String, ElementValue>) -> Result<BucketKey<'a>, SessionKeyResolutionError> { | ||
let protocol_version = *get_field!(data, "protocolVersion", Number)?; | ||
let recipient_key_version = *get_field!(data, "recipientKeyVersion", Number)?; | ||
let sender_key_version = *get_field!(data, "senderKeyVersion", Number)?; | ||
let bucket_enc_session_keys = get_field!(data, "bucketEncSessionKeys", Array)?; | ||
let key_group = get_nullable_field!(data, "keyGroup", GeneratedId)?; | ||
let pub_enc_bucket_key = get_nullable_field!(data, "pubEncBucketKey", Bytes)?; | ||
let group_enc_bucket_key = get_nullable_field!(data, "groupEncBucketKey", Bytes)?; | ||
|
||
Ok(BucketKey { | ||
protocol_version, | ||
recipient_key_version, | ||
sender_key_version, | ||
bucket_enc_session_keys, | ||
key_group, | ||
group_enc_bucket_key, | ||
pub_enc_bucket_key | ||
}) | ||
} | ||
} | ||
|
||
struct EntityOwnerKeyData<'a> { | ||
owner_enc_session_key: &'a Vec<u8>, | ||
owner_key_version: &'a i64, | ||
owner_group: &'a GeneratedId | ||
} | ||
|
||
impl<'a> EntityOwnerKeyData<'a> { | ||
fn extract_owner_key_data(entity: &'a ParsedEntity) -> Result<EntityOwnerKeyData<'a>, SessionKeyResolutionError> { | ||
let owner_enc_session_key = get_field!(entity, OWNER_ENC_SESSION_KEY_NAME, Bytes)?; | ||
let owner_key_version = get_field!(entity, OWNER_KEY_VERSION_NAME, Number)?; | ||
let owner_group = get_field!(entity, OWNER_GROUP_NAME, GeneratedId)?; | ||
|
||
Ok(EntityOwnerKeyData { | ||
owner_enc_session_key, | ||
owner_key_version, | ||
owner_group | ||
}) | ||
} | ||
} | ||
|
||
#[derive(thiserror::Error, Debug)] | ||
#[error("Session key resolution failure: {reason}")] | ||
pub struct SessionKeyResolutionError { | ||
reason: String | ||
} | ||
|
||
trait SessionKeyResolutionErrorSubtype: ToString {} | ||
|
||
impl<T: SessionKeyResolutionErrorSubtype> From<T> for SessionKeyResolutionError { | ||
fn from(value: T) -> Self { | ||
Self { reason: value.to_string() } | ||
} | ||
} | ||
|
||
impl SessionKeyResolutionErrorSubtype for KeyLoadError {} | ||
impl SessionKeyResolutionErrorSubtype for PQError {} | ||
impl SessionKeyResolutionErrorSubtype for RSAEncryptionError {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
use zeroize::Zeroizing; | ||
use crate::crypto::aes::{aes_128_decrypt_no_padding_fixed_iv, aes_256_decrypt_no_padding, AesDecryptError}; | ||
use crate::element_value::GeneratedId; | ||
|
||
#[derive(uniffi::Object)] | ||
pub struct KeyLoaderFacade { | ||
|
||
} | ||
|
||
impl KeyLoaderFacade { | ||
pub fn get_group_key(&self, group: &GeneratedId, version: u64) -> Result<GenericAesKey, KeyLoadError> { | ||
todo!() | ||
} | ||
pub fn get_asymmetric_key_pair(&self, group: &GeneratedId, version: u64) -> Result<AsymmetricKeyPair, KeyLoadError> { | ||
todo!() | ||
} | ||
} | ||
|
||
pub enum GenericAesKey { | ||
Aes128 { key: super::aes::Aes128Key }, | ||
Aes256 { key: super::aes::Aes256Key }, | ||
} | ||
|
||
impl GenericAesKey { | ||
pub(super) fn decrypt_key(&self, owner_enc_session_key: &Vec<u8>) -> Result<GenericAesKey, KeyLoadError> { | ||
let decrypted = match &self { | ||
Self::Aes128 { key } => aes_128_decrypt_no_padding_fixed_iv(&key, owner_enc_session_key)?, | ||
Self::Aes256 { key } => aes_256_decrypt_no_padding(&key, owner_enc_session_key)?, | ||
}; | ||
|
||
let decrypted = Zeroizing::new(decrypted); | ||
Self::from_bytes(decrypted.as_slice()) | ||
} | ||
|
||
pub(super) fn from_bytes(bytes: &[u8]) -> Result<Self, KeyLoadError> { | ||
match bytes.len() { | ||
super::aes::AES_128_KEY_SIZE => Ok(Self::Aes128 { key: super::aes::Aes128Key::from_bytes(bytes).unwrap() }), | ||
super::aes::AES_256_KEY_SIZE => Ok(Self::Aes256 { key: super::aes::Aes256Key::from_bytes(bytes).unwrap() }), | ||
n => Err(KeyLoadError { reason: format!("key is wrong size {n}") }) | ||
} | ||
} | ||
} | ||
|
||
pub enum AsymmetricKeyPair { | ||
RSAKeyPair(super::rsa::RSAKeyPair), | ||
PQKeyPair(super::tuta_crypt::PQKeyPairs) | ||
} | ||
|
||
#[derive(thiserror::Error, Debug)] | ||
#[error("Failed to load key: {reason}")] | ||
pub struct KeyLoadError { | ||
reason: String | ||
} | ||
|
||
trait KeyLoadErrorSubtype: ToString {} | ||
|
||
impl<T: KeyLoadErrorSubtype> From<T> for KeyLoadError { | ||
fn from(value: T) -> Self { | ||
Self { reason: value.to_string() } | ||
} | ||
} | ||
|
||
impl KeyLoadErrorSubtype for AesDecryptError {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.