use crate::{
crypto::aes::{self, EncryptedMasterKey},
internal::{rest::IronCoreRequest, *},
};
use chrono::{DateTime, Utc};
use futures::prelude::*;
use itertools::{Either, Itertools};
use recrypt::prelude::*;
use std::{collections::HashMap, result::Result};
mod requests;
#[derive(PartialEq, Debug, Serialize, Deserialize, Clone, Eq, Hash)]
pub struct UserId(pub(crate) String);
impl UserId {
pub fn id(&self) -> &String {
&self.0
}
}
impl TryFrom<String> for UserId {
type Error = IronOxideErr;
fn try_from(user_id: String) -> Result<Self, Self::Error> {
user_id.as_str().try_into()
}
}
impl TryFrom<&str> for UserId {
type Error = IronOxideErr;
fn try_from(user_id: &str) -> Result<Self, Self::Error> {
validate_id(user_id, "user_id").map(UserId)
}
}
#[derive(Clone, Serialize, Deserialize, PartialEq, Debug)]
pub struct DeviceId(pub(crate) u64);
impl DeviceId {
pub fn id(&self) -> &u64 {
&self.0
}
}
impl TryFrom<u64> for DeviceId {
type Error = IronOxideErr;
fn try_from(device_id: u64) -> Result<Self, Self::Error> {
if device_id < 1 || device_id > (std::i64::MAX as u64) {
Err(IronOxideErr::ValidationError(
"device_id".to_string(),
format!("'{}' must be a number greater than 0", device_id),
))
} else {
Ok(DeviceId(device_id))
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct DeviceName(pub(crate) String);
impl DeviceName {
pub fn name(&self) -> &String {
&self.0
}
}
impl TryFrom<&str> for DeviceName {
type Error = IronOxideErr;
fn try_from(name: &str) -> Result<Self, Self::Error> {
validate_name(name, "device_name").map(DeviceName)
}
}
#[derive(Debug)]
pub struct UserCreateKeyPair {
user_encrypted_master_key: EncryptedMasterKey,
user_public_key: PublicKey,
}
impl UserCreateKeyPair {
pub fn user_encrypted_master_key(&self) -> &EncryptedMasterKey {
&self.user_encrypted_master_key
}
pub fn user_encrypted_master_key_bytes(&self) -> [u8; 92] {
self.user_encrypted_master_key.bytes()
}
pub fn user_public_key(&self) -> &PublicKey {
&self.user_public_key
}
}
pub struct DeviceAdd {
user_public_key: PublicKey,
transform_key: TransformKey,
device_keys: KeyPair,
signing_keys: DeviceSigningKeyPair,
signature: SchnorrSignature,
signature_ts: DateTime<Utc>,
}
#[derive(Debug)]
pub struct UserVerifyResult {
account_id: UserId,
segment_id: usize,
user_public_key: PublicKey,
}
impl UserVerifyResult {
pub fn user_public_key(&self) -> &PublicKey {
&self.user_public_key
}
pub fn account_id(&self) -> &UserId {
&self.account_id
}
pub fn segment_id(&self) -> usize {
self.segment_id
}
}
#[derive(Debug)]
pub struct UserDeviceListResult {
result: Vec<UserDevice>,
}
impl UserDeviceListResult {
fn new(result: Vec<UserDevice>) -> UserDeviceListResult {
UserDeviceListResult { result }
}
pub fn result(&self) -> &Vec<UserDevice> {
&self.result
}
}
#[derive(Clone, PartialEq, Debug)]
pub struct UserDevice {
id: DeviceId,
name: Option<DeviceName>,
created: DateTime<Utc>,
last_updated: DateTime<Utc>,
is_current_device: bool,
}
impl UserDevice {
pub fn id(&self) -> &DeviceId {
&self.id
}
pub fn name(&self) -> Option<&DeviceName> {
self.name.as_ref()
}
pub fn created(&self) -> &DateTime<Utc> {
&self.created
}
pub fn last_updated(&self) -> &DateTime<Utc> {
&self.last_updated
}
pub fn is_current_device(&self) -> bool {
self.is_current_device
}
}
pub fn user_verify(
jwt: Jwt,
request: IronCoreRequest,
) -> impl Future<Item = Option<UserVerifyResult>, Error = IronOxideErr> {
requests::user_verify::user_verify(&jwt, &request)
.and_then(|e| e.map(|resp| resp.try_into()).transpose())
}
pub fn user_create<CR: rand::CryptoRng + rand::RngCore>(
recrypt: &mut Recrypt<Sha256, Ed25519, RandomBytes<CR>>,
jwt: Jwt,
passphrase: Password,
request: IronCoreRequest,
) -> impl Future<Item = UserCreateKeyPair, Error = IronOxideErr> {
recrypt
.generate_key_pair()
.map_err(IronOxideErr::from)
.and_then(|(recrypt_priv, recrypt_pub)| {
Ok(aes::encrypt_user_master_key(
&mut rand::thread_rng(),
passphrase.0.as_str(),
recrypt_priv.bytes(),
)
.map(|encrypted_private_key| (encrypted_private_key, recrypt_pub))?)
})
.into_future()
.and_then(move |(encrypted_priv_key, recrypt_pub)| {
requests::user_create::user_create(
&jwt,
recrypt_pub.into(),
encrypted_priv_key.into(),
request,
)
})
.and_then(|resp| resp.try_into())
}
pub fn generate_device_key<'a, CR: rand::CryptoRng + rand::RngCore>(
recrypt: &'a mut Recrypt<Sha256, Ed25519, RandomBytes<CR>>,
jwt: &'a Jwt,
password: Password,
device_name: Option<DeviceName>,
signing_ts: &'a DateTime<Utc>,
request: IronCoreRequest,
) -> impl Future<Item = DeviceContext, Error = IronOxideErr> + 'a {
requests::user_verify::user_verify(&jwt, &request)
.and_then(|maybe_user| {
maybe_user.ok_or(IronOxideErr::UserDoesNotExist(
"Device cannot be added to a user that doesn't exist".to_string(),
))
})
.and_then(
move |requests::user_verify::UserVerifyResponse {
user_private_key,
user_master_public_key,
id: account_id,
segment_id,
..
}| {
Ok((
{
let user_public_key: RecryptPublicKey =
PublicKey::try_from(user_master_public_key)?.into();
let user_private_key =
EncryptedMasterKey::new_from_slice(&user_private_key.0)?;
let user_private_key =
aes::decrypt_user_master_key(&password.0, &user_private_key)?;
let user_keypair: KeyPair =
KeyPair::new(user_public_key, RecryptPrivateKey::new(user_private_key));
let device_add =
generate_device_add(recrypt, &jwt, &user_keypair, &signing_ts)?;
device_add
},
account_id.try_into()?,
segment_id,
))
},
)
.and_then(move |(device_add, account_id, segment_id)| {
requests::device_add::user_device_add(&jwt, &device_add, &device_name, &request)
.map(move |_| {
DeviceContext::new(
account_id,
segment_id,
device_add.device_keys.private_key,
device_add.signing_keys,
)
})
})
}
pub fn device_list(
auth: &RequestAuth,
) -> impl Future<Item = UserDeviceListResult, Error = IronOxideErr> {
requests::device_list::device_list(auth).map(|resp| {
let mut vec: Vec<UserDevice> = resp.result.into_iter().map(UserDevice::from).collect();
vec.sort_by(|a, b| a.id.0.cmp(&b.id.0));
UserDeviceListResult::new(vec)
})
}
pub fn device_delete(
auth: &RequestAuth,
device_id: Option<&DeviceId>,
) -> impl Future<Item = DeviceId, Error = IronOxideErr> {
match device_id {
Some(device_id) => requests::device_delete::device_delete(auth, device_id),
None => requests::device_delete::device_delete_current(auth),
}
.map(|resp| resp.id)
}
pub fn user_key_list<'a>(
auth: &RequestAuth,
user_ids: &'a Vec<UserId>,
) -> impl Future<Item = HashMap<UserId, PublicKey>, Error = IronOxideErr> + 'a {
requests::user_key_list::user_key_list_request(auth, user_ids).map(
move |requests::user_key_list::UserKeyListResponse { result }| {
result
.into_iter()
.fold(HashMap::with_capacity(user_ids.len()), |mut acc, user| {
let maybe_pub_key = PublicKey::try_from(user.user_master_public_key.clone());
maybe_pub_key.into_iter().for_each(|pub_key| {
acc.insert(UserId(user.id.clone()), pub_key);
});
acc
})
},
)
}
pub(crate) fn get_user_keys<'a>(
auth: &'a RequestAuth,
users: &'a Vec<UserId>,
) -> Box<Future<Item = (Vec<UserId>, Vec<WithKey<UserId>>), Error = IronOxideErr> + 'a> {
if users.len() == 0 {
return Box::new(futures::future::ok((vec![], vec![])));
}
let cloned_users = users.clone();
let fetch_users = user_api::user_key_list(auth, &users);
Box::new(fetch_users.map(|ids_with_keys| {
cloned_users.into_iter().partition_map(|user_id| {
let maybe_public_key = ids_with_keys.get(&user_id).cloned();
match maybe_public_key {
Some(pk) => Either::Right(WithKey::new(user_id, pk)),
None => Either::Left(user_id),
}
})
}))
}
fn generate_device_add<CR: rand::CryptoRng + rand::RngCore>(
recrypt: &mut Recrypt<Sha256, Ed25519, RandomBytes<CR>>,
jwt: &Jwt,
user_master_keypair: &KeyPair,
signing_ts: &DateTime<Utc>,
) -> Result<DeviceAdd, IronOxideErr> {
let signing_keypair = recrypt.generate_ed25519_key_pair();
let (recrypt_priv_key, recrypt_pub_key) = recrypt.generate_key_pair()?;
let device_keypair = KeyPair::new(recrypt_pub_key, recrypt_priv_key);
let trans_key: TransformKey = recrypt
.generate_transform_key(
&user_master_keypair.private_key().recrypt_key(),
&device_keypair.public_key().into(),
&signing_keypair,
)?
.into();
let sig = gen_device_add_signature(recrypt, jwt, &user_master_keypair, &trans_key, signing_ts);
Ok(DeviceAdd {
user_public_key: user_master_keypair.public_key().clone(),
transform_key: trans_key.into(),
device_keys: device_keypair.into(),
signing_keys: signing_keypair.into(),
signature: sig,
signature_ts: signing_ts.to_owned(),
})
}
fn gen_device_add_signature<CR: rand::CryptoRng + rand::RngCore>(
recrypt: &mut Recrypt<Sha256, Ed25519, RandomBytes<CR>>,
jwt: &Jwt,
user_master_keypair: &KeyPair,
transform_key: &TransformKey,
signing_ts: &DateTime<Utc>,
) -> SchnorrSignature {
struct SignedMessage<'a> {
timestamp: &'a DateTime<Utc>,
transform_key: &'a TransformKey,
jwt: &'a Jwt,
user_public_key: &'a PublicKey,
};
impl<'a> recrypt::api::Hashable for SignedMessage<'a> {
fn to_bytes(&self) -> Vec<u8> {
let mut vec: Vec<u8> = vec![];
vec.extend_from_slice(&self.timestamp.timestamp_millis().to_string().as_bytes());
vec.extend_from_slice(&self.transform_key.to_bytes());
vec.extend_from_slice(&self.jwt.to_utf8());
vec.extend_from_slice(&self.user_public_key.as_bytes());
vec
}
}
let msg = SignedMessage {
timestamp: signing_ts,
transform_key,
jwt,
user_public_key: &user_master_keypair.public_key(),
};
recrypt
.schnorr_sign(
&user_master_keypair.private_key().recrypt_key(),
&user_master_keypair.public_key().into(),
&msg,
)
.into()
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn user_id_validate_good() {
let user_id1 = "a_fo_real_good_group_id$";
let user_id2 = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789";
assert_eq!(
UserId(user_id1.to_string()),
UserId::try_from(user_id1).unwrap()
);
assert_eq!(
UserId(user_id2.to_string()),
UserId::try_from(user_id2).unwrap()
)
}
#[test]
fn user_id_rejects_invalid() {
let user_id1 = UserId::try_from("not a good ID!");
let user_id2 = UserId::try_from("!!");
let user_id3 = UserId::try_from("01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567891");
assert_that!(
&user_id1.unwrap_err(),
is_variant!(IronOxideErr::ValidationError)
);
assert_that!(
&user_id2.unwrap_err(),
is_variant!(IronOxideErr::ValidationError)
);
assert_that!(
&user_id3.unwrap_err(),
is_variant!(IronOxideErr::ValidationError)
);
}
#[test]
fn user_id_rejects_empty() {
let user_id = UserId::try_from("");
assert_that!(&user_id, is_variant!(Err));
assert_that!(
&user_id.unwrap_err(),
is_variant!(IronOxideErr::ValidationError)
);
let user_id = UserId::try_from("\n \t ");
assert_that!(&user_id, is_variant!(Err));
assert_that!(
&user_id.unwrap_err(),
is_variant!(IronOxideErr::ValidationError)
);
}
}