Skip to content

Commit

Permalink
[sdk] WIP: KeyLoaderFacade and CryptoFacade
Browse files Browse the repository at this point in the history
Co-authored by: [email protected]
  • Loading branch information
paw-hub committed Jun 11, 2024
1 parent df769f9 commit 05a7005
Show file tree
Hide file tree
Showing 7 changed files with 318 additions and 26 deletions.
20 changes: 10 additions & 10 deletions tuta-sdk/rust/src/crypto/aes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ pub enum EnforceMac {


#[derive(Clone, ZeroizeOnDrop)]
pub struct Aes128Key([u8; 16]);
pub struct Aes128Key([u8; AES_128_KEY_SIZE]);

impl Aes128Key {
fn as_bytes(&self) -> &[u8; 16] {
Expand Down Expand Up @@ -65,7 +65,7 @@ impl TryFrom<Vec<u8>> for Aes128Key {
}

#[derive(Clone, ZeroizeOnDrop)]
pub struct Aes256Key([u8; 32]);
pub struct Aes256Key([u8; AES_256_KEY_SIZE]);

impl Aes256Key {
pub fn as_bytes(&self) -> &[u8; 32] {
Expand Down Expand Up @@ -258,8 +258,8 @@ fn arr_cast_size<const SIZE: usize, const ARR_SIZE: usize>(arr: [u8; ARR_SIZE])
}
}

const AES_128_KEY_SIZE: usize = 16;
const AES_256_KEY_SIZE: usize = 32;
pub const AES_128_KEY_SIZE: usize = 16;
pub const AES_256_KEY_SIZE: usize = 32;

/// The size of an AES initialisation vector in bytes
const IV_BYTE_SIZE: usize = 16;
Expand All @@ -277,17 +277,17 @@ fn encrypt_unpadded_vec_mut<C: BlockCipher + BlockEncryptMut>(encryptor: &mut cb
}

/// Keys derived for AES key to enable authentication
struct AesSubKeys<KEY: AesKey> {
struct AesSubKeys<Key: AesKey> {
/// Key used for encrypting data
c_key: KEY,
c_key: Key,
/// Key used for HMAC (authentication)
m_key: KEY,
m_key: Key,
}

type Aes128SubKeys = AesSubKeys<Aes128Key>;
type Aes256SubKeys = AesSubKeys<Aes256Key>;

impl<KEY: AesKey> AesSubKeys<KEY> {
impl<Key: AesKey> AesSubKeys<Key> {
fn compute_mac(&self, iv: &[u8], ciphertext: &[u8]) -> [u8; 32] {
use sha2::Sha256;
use hmac::Mac;
Expand Down Expand Up @@ -367,15 +367,15 @@ impl<'a> CiphertextWithAuthentication<'a> {
}
}

fn compute<KEY: AesKey>(ciphertext: &'a [u8], iv: &'a [u8], subkeys: &AesSubKeys<KEY>) -> CiphertextWithAuthentication<'a> {
fn compute<Key: AesKey>(ciphertext: &'a [u8], iv: &'a [u8], subkeys: &AesSubKeys<Key>) -> CiphertextWithAuthentication<'a> {
CiphertextWithAuthentication {
iv,
ciphertext,
mac: subkeys.compute_mac(iv, ciphertext),
}
}

fn matches<KEY: AesKey>(&self, subkeys: &AesSubKeys<KEY>) -> bool {
fn matches<Key: AesKey>(&self, subkeys: &AesSubKeys<Key>) -> bool {
self.mac == subkeys.compute_mac(self.iv, self.ciphertext)
}

Expand Down
215 changes: 215 additions & 0 deletions tuta-sdk/rust/src/crypto/crypto_facade.rs
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 {}
63 changes: 63 additions & 0 deletions tuta-sdk/rust/src/crypto/key_loader_facade.rs
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 {}
2 changes: 2 additions & 0 deletions tuta-sdk/rust/src/crypto/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ mod ecc;
mod kyber;
mod rsa;
mod tuta_crypt;
mod key_loader_facade;
mod crypto_facade;

#[cfg(test)]
mod compatibility_test_utils;
5 changes: 5 additions & 0 deletions tuta-sdk/rust/src/crypto/rsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ impl RSAPublicKey {

pub struct RSAPrivateKey(rsa::RsaPrivateKey);

pub struct RSAKeyPair {
pub public_key: RSAPublicKey,
pub private_key: RSAPrivateKey
}

impl RSAPrivateKey {
/// Instantiate an RSAPrivateKey from its components.
///
Expand Down
Loading

0 comments on commit 05a7005

Please sign in to comment.