canic-core 0.65.4

Canic — a canister orchestration and management toolkit for the Internet Computer
Documentation
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 {
    /// Prepare a canonical delegation proof certificate and certify its canister-signature path.
    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)
    }

    /// Finish an already-prepared root delegation proof from query-only certificate material.
    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)
    }

    /// Resolve the local shard public key, fetching and caching it on demand.
    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()
}