use arc_swap::ArcSwap;
use candid::Principal;
use ic_agent::{
identity::{Delegation, SignedDelegation},
{Signature, agent::EnvelopeContent},
};
use std::{
sync::Arc,
time::{Duration, SystemTime, UNIX_EPOCH},
};
pub use ic_agent::identity::{AnonymousIdentity, BasicIdentity, DelegatedIdentity, Identity};
use crate::rand_bytes;
pub struct AtomicIdentity {
inner: ArcSwap<Box<dyn Identity>>,
}
impl Default for AtomicIdentity {
fn default() -> Self {
Self::new(Box::new(AnonymousIdentity))
}
}
impl AtomicIdentity {
pub fn new(identity: Box<dyn Identity>) -> Self {
Self {
inner: ArcSwap::from(Arc::new(identity)),
}
}
pub fn get(&self) -> Arc<dyn Identity> {
self.inner.load().clone()
}
pub fn set(&self, identity: Box<dyn Identity>) {
self.inner.store(Arc::new(identity));
}
pub fn is_authenticated(&self) -> bool {
match self.sender() {
Err(_) => false,
Ok(principal) => {
if principal == Principal::anonymous() {
return false;
}
match get_expiration(self) {
None => true,
Some(expiration) => {
let now = unix_timestamp()
.saturating_sub(Duration::from_secs(60))
.as_nanos() as u64;
expiration > now
}
}
}
}
}
}
impl From<Box<dyn Identity>> for AtomicIdentity {
fn from(identity: Box<dyn Identity>) -> Self {
Self::new(identity)
}
}
impl Identity for AtomicIdentity {
fn sender(&self) -> Result<Principal, String> {
self.inner.load().sender()
}
fn public_key(&self) -> Option<Vec<u8>> {
self.inner.load().public_key()
}
fn sign(&self, content: &EnvelopeContent) -> Result<Signature, String> {
self.inner.load().sign(content)
}
fn sign_delegation(&self, content: &Delegation) -> Result<Signature, String> {
self.inner.load().sign_delegation(content)
}
fn sign_arbitrary(&self, content: &[u8]) -> Result<Signature, String> {
self.inner.load().sign_arbitrary(content)
}
fn delegation_chain(&self) -> Vec<SignedDelegation> {
self.inner.load().delegation_chain()
}
}
pub fn get_expiration(identity: &impl Identity) -> Option<u64> {
let chain = identity.delegation_chain();
if chain.is_empty() {
return None;
}
let mut expiration = u64::MAX;
for delegation in identity.delegation_chain() {
if delegation.delegation.expiration < expiration {
expiration = delegation.delegation.expiration;
}
}
Some(expiration)
}
pub fn signed_delegation_from(src: ic_auth_types::SignedDelegation) -> SignedDelegation {
SignedDelegation {
delegation: Delegation {
pubkey: src.delegation.pubkey.0,
expiration: src.delegation.expiration,
targets: src.delegation.targets,
},
signature: src.signature.0,
}
}
pub fn new_basic_identity() -> BasicIdentity {
let secret: [u8; 32] = rand_bytes();
BasicIdentity::from_raw_key(&secret)
}
pub fn delegated_basic_identity(identity: &BasicIdentity, expires_in_ms: u64) -> DelegatedIdentity {
let expiration = unix_timestamp().saturating_add(Duration::from_millis(expires_in_ms));
let session = new_basic_identity();
let delegation = Delegation {
pubkey: session.public_key().unwrap(),
expiration: expiration.as_nanos() as u64,
targets: None,
};
let signature = identity.sign_delegation(&delegation).unwrap();
DelegatedIdentity::new_unchecked(
identity.public_key().unwrap(),
Box::new(session),
vec![SignedDelegation {
delegation,
signature: signature.signature.unwrap(),
}],
)
}
#[inline]
pub fn unix_timestamp() -> Duration {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("system time before Unix epoch")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_atomic_identity_default() {
let identity = AtomicIdentity::default();
assert_eq!(identity.sender().unwrap(), Principal::anonymous());
assert!(!identity.is_authenticated());
}
#[test]
fn test_atomic_identity_new_and_get() {
let basic = new_basic_identity();
let principal = basic.sender().unwrap();
let public_key = basic.public_key().unwrap();
let atomic = AtomicIdentity::new(Box::new(basic));
assert_eq!(atomic.sender().unwrap(), principal);
assert_eq!(atomic.public_key().unwrap(), public_key);
assert!(atomic.is_authenticated());
}
#[test]
fn test_atomic_identity_set() {
let atomic = AtomicIdentity::default();
assert_eq!(atomic.sender().unwrap(), Principal::anonymous());
let basic = new_basic_identity();
let principal = basic.sender().unwrap();
atomic.set(Box::new(basic));
assert_eq!(atomic.sender().unwrap(), principal);
}
#[test]
fn test_atomic_identity_from() {
let basic = new_basic_identity();
let principal = basic.sender().unwrap();
let basic: Box<dyn Identity> = Box::new(basic);
let atomic: AtomicIdentity = basic.into();
assert_eq!(atomic.sender().unwrap(), principal);
}
#[test]
fn test_atomic_identity_is_authenticated() {
let anonymous = AtomicIdentity::default();
assert!(!anonymous.is_authenticated());
let basic = new_basic_identity();
let atomic = AtomicIdentity::new(Box::new(basic));
assert!(atomic.is_authenticated());
let basic = new_basic_identity();
let expired = unix_timestamp().saturating_sub(Duration::from_secs(120));
let session = new_basic_identity();
let delegation = Delegation {
pubkey: session.public_key().unwrap(),
expiration: expired.as_nanos() as u64,
targets: None,
};
let signature = basic.sign_delegation(&delegation).unwrap();
let delegated = DelegatedIdentity::new_unchecked(
basic.public_key().unwrap(),
Box::new(session),
vec![SignedDelegation {
delegation,
signature: signature.signature.unwrap(),
}],
);
let atomic = AtomicIdentity::new(Box::new(delegated));
assert!(!atomic.is_authenticated());
let basic = new_basic_identity();
let not_expired = unix_timestamp().saturating_add(Duration::from_secs(3600));
let session = new_basic_identity();
let delegation = Delegation {
pubkey: session.public_key().unwrap(),
expiration: not_expired.as_nanos() as u64,
targets: None,
};
let signature = basic.sign_delegation(&delegation).unwrap();
let delegated = DelegatedIdentity::new_unchecked(
basic.public_key().unwrap(),
Box::new(session),
vec![SignedDelegation {
delegation,
signature: signature.signature.unwrap(),
}],
);
let atomic = AtomicIdentity::new(Box::new(delegated));
assert!(atomic.is_authenticated());
}
#[test]
fn test_get_expiration() {
let basic = new_basic_identity();
assert_eq!(get_expiration(&basic), None);
let basic = new_basic_identity();
let expiration = unix_timestamp()
.saturating_add(Duration::from_secs(3600))
.as_millis() as u64;
let delegated = delegated_basic_identity(&basic, 3600 * 1000);
assert_eq!(get_expiration(&delegated).unwrap() / 1000000, expiration);
let basic = new_basic_identity();
let expiration1 = unix_timestamp()
.saturating_add(Duration::from_secs(3600))
.as_nanos() as u64;
let expiration2 = unix_timestamp()
.saturating_add(Duration::from_secs(1800))
.as_nanos() as u64;
let session1 = new_basic_identity();
let delegation1 = Delegation {
pubkey: session1.public_key().unwrap(),
expiration: expiration1,
targets: None,
};
let signature1 = basic.sign_delegation(&delegation1).unwrap();
let session2 = new_basic_identity();
let delegation2 = Delegation {
pubkey: session2.public_key().unwrap(),
expiration: expiration2,
targets: None,
};
let signature2 = basic.sign_delegation(&delegation2).unwrap();
let delegated = DelegatedIdentity::new_unchecked(
basic.public_key().unwrap(),
Box::new(session1),
vec![
SignedDelegation {
delegation: delegation1,
signature: signature1.signature.unwrap(),
},
SignedDelegation {
delegation: delegation2,
signature: signature2.signature.unwrap(),
},
],
);
assert_eq!(get_expiration(&delegated), Some(expiration2)); }
#[test]
fn test_delegated_basic_identity() {
let basic = new_basic_identity();
let expires_in_ms = 3600 * 1000;
let delegated = delegated_basic_identity(&basic, expires_in_ms);
assert_eq!(delegated.delegation_chain().len(), 1);
let expiration = get_expiration(&delegated).unwrap();
let now = unix_timestamp().as_nanos() as u64;
assert!(expiration > now);
}
}