#![recursion_limit = "128"]
#[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate base64_serde;
#[macro_use]
extern crate quick_error;
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate vec1;
#[cfg(test)]
#[macro_use]
extern crate galvanic_assert;
#[cfg(test)]
#[macro_use]
extern crate double;
#[macro_use]
extern crate percent_encoding;
mod crypto;
mod internal;
include!(concat!(env!("OUT_DIR"), "/transform.rs"));
pub mod document;
pub mod group;
pub mod user;
pub mod policy;
pub mod prelude;
use crate::internal::{
group_api::{GroupId, GroupUpdatePrivateKeyResult},
user_api::{UserId, UserResult, UserUpdatePrivateKeyResult},
};
pub use crate::internal::{
DeviceAddResult, DeviceContext, DeviceSigningKeyPair, IronOxideErr, KeyPair, PrivateKey,
PublicKey,
};
use itertools::EitherOrBoth;
use rand::{
rngs::{adapter::ReseedingRng, EntropyRng},
FromEntropy,
};
use rand_chacha::ChaChaCore;
use recrypt::api::{Ed25519, RandomBytes, Recrypt, Sha256};
use std::{convert::TryInto, sync::Mutex};
use tokio::runtime::Runtime;
use vec1::Vec1;
pub type Result<T> = std::result::Result<T, IronOxideErr>;
pub struct IronOxide {
pub(crate) recrypt: Recrypt<Sha256, Ed25519, RandomBytes<recrypt::api::DefaultRng>>,
pub(crate) user_master_pub_key: PublicKey,
pub(crate) device: DeviceContext,
pub(crate) rng: Mutex<ReseedingRng<ChaChaCore, EntropyRng>>,
pub(crate) runtime: tokio::runtime::Runtime,
}
pub enum InitAndRotationCheck {
NoRotationNeeded(IronOxide),
RotationNeeded(IronOxide, PrivateKeyRotationCheckResult),
}
impl InitAndRotationCheck {
pub fn discard_check(self) -> IronOxide {
match self {
InitAndRotationCheck::NoRotationNeeded(io)
| InitAndRotationCheck::RotationNeeded(io, _) => io,
}
}
pub fn new_rotation_needed(
io: IronOxide,
rotations_needed: EitherOrBoth<UserId, Vec1<GroupId>>,
) -> InitAndRotationCheck {
InitAndRotationCheck::RotationNeeded(io, PrivateKeyRotationCheckResult { rotations_needed })
}
}
const BYTES_BEFORE_RESEEDING: u64 = 1 * 1024 * 1024;
pub struct PrivateKeyRotationCheckResult {
pub rotations_needed: EitherOrBoth<UserId, Vec1<GroupId>>,
}
impl PrivateKeyRotationCheckResult {
pub fn user_rotation_needed(&self) -> Option<&UserId> {
match &self.rotations_needed {
EitherOrBoth::Left(u) | EitherOrBoth::Both(u, _) => Some(u),
_ => None,
}
}
pub fn group_rotation_needed(&self) -> Option<&Vec1<GroupId>> {
match &self.rotations_needed {
EitherOrBoth::Right(groups) | EitherOrBoth::Both(_, groups) => Some(groups),
_ => None,
}
}
pub fn rotate_all(
&self,
ironoxide: &IronOxide,
password: &str,
) -> Result<(
Option<UserUpdatePrivateKeyResult>,
Option<Vec<GroupUpdatePrivateKeyResult>>,
)> {
use crate::internal::Password;
use futures::future::OptionFuture;
let valid_password: Password = password.try_into()?;
let user_future = self.user_rotation_needed().map(|_| {
crate::internal::user_api::user_rotate_private_key(
&ironoxide.recrypt,
valid_password,
ironoxide.device().auth(),
)
});
let group_futures = self.group_rotation_needed().map(|groups| {
let group_futures = groups
.into_iter()
.map(|group_id| {
crate::internal::group_api::group_rotate_private_key(
&ironoxide.recrypt,
ironoxide.device().auth(),
&group_id,
ironoxide.device().device_private_key(),
)
})
.collect::<Vec<_>>();
futures::future::join_all(group_futures)
});
let user_opt_future: OptionFuture<_> = user_future.into();
let group_opt_future: OptionFuture<_> = group_futures.into();
let (user_opt_result, group_opt_vec_result) = ironoxide.runtime.enter(|| {
futures::executor::block_on(futures::future::join(user_opt_future, group_opt_future))
});
let group_opt_result_vec = group_opt_vec_result.map(|g| g.into_iter().collect());
Ok((
user_opt_result.transpose()?,
group_opt_result_vec.transpose()?,
))
}
}
pub fn initialize(device_context: &DeviceContext) -> Result<IronOxide> {
let mut rt = Runtime::new().unwrap();
rt.block_on(internal::user_api::user_get_current(&device_context.auth()))
.map(|current_user| IronOxide::create(¤t_user, device_context))
.map_err(|_| IronOxideErr::InitializeError)
}
pub fn initialize_check_rotation(device_context: &DeviceContext) -> Result<InitAndRotationCheck> {
Runtime::new()
.unwrap()
.block_on(initialize_check_rotation_async(device_context))
}
fn check_groups_and_collect_rotation(
groups: &Vec<internal::group_api::GroupMetaResult>,
user_needs_rotation: bool,
account_id: UserId,
ironoxide: IronOxide,
) -> InitAndRotationCheck {
use EitherOrBoth::{Both, Left, Right};
let groups_needing_rotation = groups
.into_iter()
.filter(|meta_result| meta_result.needs_rotation() == Some(true))
.map(|meta_result| meta_result.id().to_owned())
.collect::<Vec<_>>();
let maybe_groups_needing_rotation = Vec1::try_from_vec(groups_needing_rotation).ok();
match (user_needs_rotation, maybe_groups_needing_rotation) {
(false, None) => InitAndRotationCheck::NoRotationNeeded(ironoxide),
(true, None) => InitAndRotationCheck::new_rotation_needed(ironoxide, Left(account_id)),
(false, Some(groups)) => {
InitAndRotationCheck::new_rotation_needed(ironoxide, Right(groups))
}
(true, Some(groups)) => {
InitAndRotationCheck::new_rotation_needed(ironoxide, Both(account_id, groups))
}
}
}
async fn initialize_check_rotation_async(
device_context: &DeviceContext,
) -> Result<InitAndRotationCheck> {
let (curr_user, group_list_result) = futures::try_join!(
internal::user_api::user_get_current(device_context.auth()),
internal::group_api::list(device_context.auth(), None)
)?;
let ironoxide = IronOxide::create(&curr_user, &device_context);
let user_groups = group_list_result.result();
Ok(check_groups_and_collect_rotation(
user_groups,
curr_user.needs_rotation(),
curr_user.account_id().to_owned(),
ironoxide,
))
}
impl IronOxide {
pub fn device(&self) -> &DeviceContext {
&self.device
}
fn create(curr_user: &UserResult, device_context: &DeviceContext) -> IronOxide {
let runtime = tokio::runtime::Builder::new()
.threaded_scheduler()
.enable_all()
.max_threads(250)
.build()
.expect("tokio runtime failed to initialize");
IronOxide {
recrypt: Recrypt::new(),
device: device_context.clone(),
user_master_pub_key: curr_user.user_public_key().to_owned(),
rng: Mutex::new(ReseedingRng::new(
rand_chacha::ChaChaCore::from_entropy(),
BYTES_BEFORE_RESEEDING,
EntropyRng::new(),
)),
runtime,
}
}
}
impl From<IronOxideErr> for String {
fn from(err: IronOxideErr) -> Self {
format!("{}", err)
}
}