use super::{
AuthOps, PreparedRootDelegationProof, SignDelegationProofInput,
delegated::{
canonical::{derivation_path_hash, key_name_hash},
cert_rules::DelegatedAuthTtlLimits,
issue::{
IssueDelegationProofError, IssueDelegationProofInput, finish_delegation_proof,
prepare_delegation_cert,
},
},
keys,
root_canister_sig::RootPayloadKind,
};
use crate::{
InternalError,
cdk::types::Principal,
dto::auth::{DelegationProof, ShardKeyBinding, ShardSignatureAlgorithm},
ops::{
auth::AuthValidationError,
ic::{IcOps, ecdsa::EcdsaOps},
},
};
use std::{cell::RefCell, collections::BTreeMap};
thread_local! {
static PENDING_DELEGATION_PROOFS: RefCell<BTreeMap<PendingDelegationProofKey, PreparedRootDelegationProof>> =
const { RefCell::new(BTreeMap::new()) };
}
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
struct PendingDelegationProofKey {
cert_hash: [u8; 32],
prepared_by: Vec<u8>,
}
impl PendingDelegationProofKey {
fn new(cert_hash: [u8; 32], prepared_by: Principal) -> Self {
Self {
cert_hash,
prepared_by: prepared_by.as_slice().to_vec(),
}
}
}
impl AuthOps {
pub(crate) async fn prepare_delegation_proof(
input: SignDelegationProofInput,
) -> Result<PreparedRootDelegationProof, InternalError> {
let root_pid = IcOps::canister_self();
let key_name = keys::delegated_tokens_key_name()?;
let shard_derivation_path = keys::shard_derivation_path(input.shard_pid);
let shard_public_key_sec1 = Self::local_shard_public_key_sec1(input.shard_pid).await?;
let prepared = prepare_delegation_cert(IssueDelegationProofInput {
root_pid,
shard_pid: input.shard_pid,
shard_key_id: key_name.clone(),
shard_sig_alg: ShardSignatureAlgorithm::IcThresholdEcdsaSecp256k1,
shard_public_key_sec1,
shard_key_binding: ShardKeyBinding::IcThresholdEcdsaSecp256k1 {
key_name_hash: key_name_hash(&key_name),
derivation_path_hash: derivation_path_hash(&shard_derivation_path),
},
issued_at_ns: input.issued_at_ns,
cert_ttl_ns: input.cert_ttl_ns,
max_token_ttl_ns: input.max_token_ttl_ns,
audience: input.audience,
grants: input.grants,
ttl_limits: DelegatedAuthTtlLimits {
max_cert_ttl_ns: input.max_cert_ttl_ns,
max_token_ttl_ns: input.max_token_ttl_ns,
},
})
.map_err(map_issue_delegation_proof_error)?;
let prepared_root_signature = Self::prepare_root_canister_signature(
RootPayloadKind::DelegationCert,
input.operation_id,
prepared.cert_hash,
input.shard_pid,
input.issued_at_ns,
)?;
let prepared = PreparedRootDelegationProof {
cert: prepared.cert,
cert_hash: prepared.cert_hash,
retrieval_expires_at_ns: prepared_root_signature.retrieval_expires_at_ns,
};
cache_prepared_delegation_proof(input.shard_pid, prepared.clone());
Ok(prepared)
}
pub(crate) fn get_delegation_proof(
caller: Principal,
cert_hash: [u8; 32],
) -> Result<DelegationProof, InternalError> {
let prepared = PENDING_DELEGATION_PROOFS
.with(|pending| {
pending
.borrow()
.get(&PendingDelegationProofKey::new(cert_hash, caller))
.cloned()
})
.ok_or_else(|| {
AuthValidationError::Auth(
"delegation proof was not prepared or has expired".to_string(),
)
})?;
let root_proof = Self::get_root_canister_signature_proof(
RootPayloadKind::DelegationCert,
prepared.cert_hash,
caller,
prepared.cert.root_pid,
IcOps::now_nanos(),
)?;
Ok(finish_delegation_proof(
super::delegated::issue::PreparedDelegationCert {
cert: prepared.cert,
cert_hash: prepared.cert_hash,
},
root_proof,
)
.proof)
}
pub(crate) async fn local_shard_public_key_sec1(
shard_pid: Principal,
) -> Result<Vec<u8>, InternalError> {
let key_name = keys::delegated_tokens_key_name()?;
EcdsaOps::public_key_sec1(&key_name, keys::shard_derivation_path(shard_pid), shard_pid)
.await
}
}
fn cache_prepared_delegation_proof(caller: Principal, prepared: PreparedRootDelegationProof) {
PENDING_DELEGATION_PROOFS.with(|pending| {
pending.borrow_mut().insert(
PendingDelegationProofKey::new(prepared.cert_hash, caller),
prepared,
);
});
}
fn map_issue_delegation_proof_error(err: IssueDelegationProofError) -> InternalError {
AuthValidationError::Auth(err.to_string()).into()
}