canic-core 0.29.7

Canic — a canister orchestration and management toolkit for the Internet Computer
Documentation
use super::{
    AuthOps, SignDelegatedTokenInput, VerifyDelegatedTokenRuntimeInput,
    delegated::mint::{
        MintDelegatedTokenError, MintDelegatedTokenInput, finish_delegated_token,
        prepare_delegated_token,
    },
    delegated::{
        canonical::{derivation_path_hash, key_name_hash, public_key_hash},
        policy::DelegatedAuthTtlPolicy,
        verify::{
            VerifiedDelegatedToken, VerifyDelegatedTokenError, VerifyDelegatedTokenInput,
            verify_delegated_token,
        },
    },
    keys,
};
use crate::{
    InternalError,
    dto::auth::{DelegatedToken, RootPublicKey, RootTrustAnchor, ShardKeyBinding},
    ops::{
        auth::{AuthScopeError, AuthSignatureError, AuthValidationError},
        config::ConfigOps,
        ic::{IcOps, ecdsa::EcdsaOps},
        runtime::env::EnvOps,
        storage::state::subnet::SubnetStateOps,
    },
};

impl AuthOps {
    /// Sign a self-contained delegated token with local shard threshold ECDSA material.
    pub async fn sign_token(
        input: SignDelegatedTokenInput,
    ) -> Result<DelegatedToken, InternalError> {
        let local = IcOps::canister_self();
        if input.proof.cert.shard_pid != local {
            return Err(AuthScopeError::ShardPidMismatch {
                expected: local,
                found: input.proof.cert.shard_pid,
            }
            .into());
        }

        let prepared = prepare_delegated_token(MintDelegatedTokenInput {
            proof: &input.proof,
            subject: input.subject,
            audience: input.audience,
            scopes: input.scopes,
            ttl_secs: input.ttl_secs,
            nonce: input.nonce,
            now_secs: IcOps::now_secs(),
        })
        .map_err(map_mint_delegated_token_error)?;

        let key_name = keys::delegated_tokens_key_name()?;
        let shard_sig = EcdsaOps::sign_bytes(
            &key_name,
            keys::shard_derivation_path(local),
            prepared.claims_hash,
        )
        .await?;

        Ok(finish_delegated_token(prepared, shard_sig))
    }

    /// Verify a self-contained delegated token without local proof lookup.
    pub fn verify_token(
        input: VerifyDelegatedTokenRuntimeInput<'_>,
    ) -> Result<VerifiedDelegatedToken, InternalError> {
        let cfg = ConfigOps::delegated_tokens_config()?;
        if !cfg.enabled {
            return Err(AuthValidationError::DelegatedTokenAuthDisabled.into());
        }

        Self::verify_shard_key_binding(input.token)?;
        let root_trust = Self::root_trust_anchor(input.token, input.now_secs)?;
        let local_role = EnvOps::canister_role()?;

        verify_delegated_token(
            VerifyDelegatedTokenInput {
                token: input.token,
                root_trust: &root_trust,
                local_principal: IcOps::canister_self(),
                local_role: Some(&local_role),
                ttl_policy: DelegatedAuthTtlPolicy {
                    max_cert_ttl_secs: input.max_cert_ttl_secs,
                    max_token_ttl_secs: input.max_token_ttl_secs,
                },
                required_scopes: input.required_scopes,
                now_secs: input.now_secs,
            },
            |public_key, hash, sig, _alg| {
                EcdsaOps::verify_signature(public_key, hash, sig).map_err(|err| err.to_string())
            },
        )
        .map_err(map_verify_delegated_token_error)
    }

    fn root_trust_anchor(
        token: &DelegatedToken,
        now_secs: u64,
    ) -> Result<RootTrustAnchor, InternalError> {
        let cert = &token.proof.cert;
        let key_name = keys::delegated_tokens_key_name()?;
        if cert.root_key_id != key_name {
            return Err(AuthValidationError::Auth(format!(
                "delegated auth root key id mismatch (expected {key_name}, found {})",
                cert.root_key_id
            ))
            .into());
        }

        let root_public_key = SubnetStateOps::delegated_root_public_key(&key_name)
            .ok_or(AuthSignatureError::RootPublicKeyUnavailable)?;
        let key_hash = public_key_hash(&root_public_key);

        Ok(RootTrustAnchor {
            root_pid: EnvOps::root_pid()?,
            root_key: RootPublicKey {
                root_pid: cert.root_pid,
                key_id: cert.root_key_id.clone(),
                alg: cert.alg,
                public_key_sec1: root_public_key,
                key_hash,
                not_before: cert.issued_at.min(now_secs),
                not_after: None,
            },
        })
    }

    fn verify_shard_key_binding(token: &DelegatedToken) -> Result<(), InternalError> {
        let cert = &token.proof.cert;
        let key_name = keys::delegated_tokens_key_name()?;
        let expected_derivation_path_hash =
            derivation_path_hash(&keys::shard_derivation_path(cert.shard_pid));
        match cert.shard_key_binding {
            ShardKeyBinding::IcThresholdEcdsa {
                key_name_hash: observed_key_name_hash,
                derivation_path_hash: observed_derivation_path_hash,
            } => {
                if observed_key_name_hash != key_name_hash(&key_name)
                    || observed_derivation_path_hash != expected_derivation_path_hash
                {
                    return Err(AuthValidationError::Auth(
                        "delegated auth shard key binding mismatch".to_string(),
                    )
                    .into());
                }
            }
        }
        Ok(())
    }
}

fn map_mint_delegated_token_error(err: MintDelegatedTokenError) -> InternalError {
    AuthValidationError::Auth(err.to_string()).into()
}

fn map_verify_delegated_token_error(err: VerifyDelegatedTokenError) -> InternalError {
    AuthValidationError::Auth(err.to_string()).into()
}