Skip to main content

canic_core/api/auth/
mod.rs

1//! Module: api::auth
2//!
3//! Responsibility: expose auth endpoint helpers and auth boundary adapters.
4//! Does not own: stable auth records, proof verification internals, or runtime policy.
5//! Boundary: endpoint layer maps public DTOs into ops/workflow auth calls.
6
7use crate::{
8    cdk::types::Principal,
9    dto::{
10        auth::{
11            ActiveDelegationProofStatusResponse, DelegatedToken, DelegatedTokenGetRequest,
12            DelegatedTokenPrepareRequest, DelegatedTokenPrepareResponse,
13            InstallActiveDelegationProofRequest, InstallActiveDelegationProofResponse,
14            RoleAttestationGetRequest, RoleAttestationPrepareResponse, RoleAttestationRequest,
15            RootDelegationProofBatchProof, RootIssuerPolicyResponse, RootIssuerPolicyUpsertRequest,
16            RootIssuerRenewalStatusRequest, RootIssuerRenewalStatusResponse,
17            RootIssuerRenewalTemplateResponse, RootIssuerRenewalTemplateUpsertRequest,
18            SignedRoleAttestation,
19        },
20        error::Error,
21    },
22    error::InternalErrorClass,
23    ops::{
24        auth::{AuthOps, VerifyDelegatedTokenRuntimeInput},
25        config::ConfigOps,
26        ic::IcOps,
27        runtime::env::EnvOps,
28    },
29    workflow::runtime::auth::RuntimeAuthWorkflow,
30};
31
32// Internal auth pipeline:
33// - `session` owns delegated-session ingress and replay/session state handling.
34mod session;
35
36///
37/// AuthApi
38///
39/// Owns delegated-token helpers and root-signed role-attestation helpers.
40/// Owned by the API layer and called by generated endpoint wrappers.
41///
42
43pub struct AuthApi;
44
45impl AuthApi {
46    const DELEGATED_TOKENS_DISABLED: &str =
47        "delegated token auth disabled; set auth.delegated_tokens.enabled=true in canic.toml";
48    const DELEGATED_TOKEN_ISSUER_DISABLED: &str = "delegated token issuer disabled for this canister; set subnets.<subnet>.canisters.<role>.auth.delegated_token_issuer=true in canic.toml";
49    const MAX_DELEGATED_SESSION_TTL_SECS: u64 = 24 * 60 * 60;
50    const SESSION_BOOTSTRAP_TOKEN_FINGERPRINT_DOMAIN: &[u8] =
51        b"canic-session-bootstrap-token-fingerprint";
52
53    // Map internal auth failures onto public endpoint errors.
54    fn map_auth_error(err: crate::InternalError) -> Error {
55        match err.class() {
56            InternalErrorClass::Infra | InternalErrorClass::Ops | InternalErrorClass::Workflow => {
57                Error::internal(err.to_string())
58            }
59            _ => Error::from(err),
60        }
61    }
62
63    fn require_delegated_token_issuer_enabled() -> Result<(), Error> {
64        let delegated_tokens_cfg =
65            ConfigOps::delegated_tokens_config().map_err(Self::map_auth_error)?;
66        if !delegated_tokens_cfg.enabled {
67            return Err(Error::invalid(Self::DELEGATED_TOKENS_DISABLED));
68        }
69
70        let canister_cfg = ConfigOps::current_canister().map_err(Self::map_auth_error)?;
71        if !canister_cfg.auth.delegated_token_issuer {
72            return Err(Error::forbidden(Self::DELEGATED_TOKEN_ISSUER_DISABLED));
73        }
74
75        Ok(())
76    }
77
78    // Verify delegated-token material and return the token subject.
79    //
80    // This is intentionally private: endpoint authorization must also bind the
81    // verified subject to the caller before dispatch.
82    fn verify_token_material(
83        token: &DelegatedToken,
84        max_cert_ttl_ns: u64,
85        max_token_ttl_ns: u64,
86        required_scopes: &[String],
87        now_ns: u64,
88    ) -> Result<Principal, Error> {
89        AuthOps::verify_token(VerifyDelegatedTokenRuntimeInput {
90            token,
91            caller: IcOps::msg_caller(),
92            max_cert_ttl_ns,
93            max_token_ttl_ns,
94            required_scopes,
95            now_ns,
96        })
97        .map(|verified| verified.subject)
98        .map_err(Self::map_auth_error)
99    }
100
101    /// Prepare a delegated token from the issuer-local active delegation proof.
102    pub async fn prepare_delegated_token(
103        request: DelegatedTokenPrepareRequest,
104    ) -> Result<DelegatedTokenPrepareResponse, Error> {
105        Self::require_delegated_token_issuer_enabled()?;
106        RuntimeAuthWorkflow::prepare_delegated_token(request)
107            .await
108            .map_err(Self::map_auth_error)
109    }
110
111    /// Retrieve a prepared delegated token with its issuer canister-signature proof.
112    pub fn get_delegated_token(request: DelegatedTokenGetRequest) -> Result<DelegatedToken, Error> {
113        Self::require_delegated_token_issuer_enabled()?;
114
115        AuthOps::get_delegated_token_issuer_proof(request.claims_hash, IcOps::msg_caller())
116            .map_err(Self::map_auth_error)
117    }
118
119    /// Install validated root-certified delegation material for issuer-local token issuance.
120    pub fn install_active_delegation_proof(
121        request: InstallActiveDelegationProofRequest,
122    ) -> Result<InstallActiveDelegationProofResponse, Error> {
123        Self::require_delegated_token_issuer_enabled()?;
124
125        let active_proof =
126            AuthOps::install_active_delegation_proof(request.proof, IcOps::msg_caller())
127                .map_err(Self::map_auth_error)?;
128
129        Ok(InstallActiveDelegationProofResponse { active_proof })
130    }
131
132    /// Report non-secret issuer-local active proof lifecycle status for operators.
133    pub fn active_delegation_proof_status() -> Result<ActiveDelegationProofStatusResponse, Error> {
134        Self::require_delegated_token_issuer_enabled()?;
135        Ok(AuthOps::active_delegation_proof_status(IcOps::now_nanos()))
136    }
137
138    /// Upsert root issuer policy from the local root controller path.
139    pub fn upsert_root_issuer_policy_root(
140        request: RootIssuerPolicyUpsertRequest,
141    ) -> Result<RootIssuerPolicyResponse, Error> {
142        EnvOps::require_root().map_err(Error::from)?;
143        AuthOps::upsert_root_issuer_policy(request, IcOps::now_nanos())
144            .map_err(Self::map_auth_error)
145    }
146
147    /// Upsert root-managed renewal template from the local root controller path.
148    pub fn upsert_root_issuer_renewal_template_root(
149        request: RootIssuerRenewalTemplateUpsertRequest,
150    ) -> Result<RootIssuerRenewalTemplateResponse, Error> {
151        EnvOps::require_root().map_err(Error::from)?;
152        let response = AuthOps::upsert_root_issuer_renewal_template(request, IcOps::now_nanos())
153            .map_err(Self::map_auth_error)?;
154        if response.template.enabled {
155            RuntimeAuthWorkflow::start_root_delegation_renewal_timer_soon_if_configured()
156                .map_err(Self::map_auth_error)?;
157        }
158        Ok(response)
159    }
160
161    /// Report root-managed renewal template/state for one issuer.
162    pub fn root_issuer_renewal_status_root(
163        request: RootIssuerRenewalStatusRequest,
164    ) -> Result<RootIssuerRenewalStatusResponse, Error> {
165        EnvOps::require_root().map_err(Error::from)?;
166        Ok(AuthOps::root_issuer_renewal_status(request))
167    }
168
169    /// Return or create a chain-key root delegation proof for the registered issuer caller.
170    pub async fn get_or_create_chain_key_delegation_proof_root()
171    -> Result<RootDelegationProofBatchProof, Error> {
172        EnvOps::require_root().map_err(Error::from)?;
173        RuntimeAuthWorkflow::get_or_create_chain_key_delegation_proof_for_issuer_root(
174            IcOps::msg_caller(),
175        )
176        .await
177        .map_err(Self::map_auth_error)
178    }
179
180    /// Prepare a root-certified role attestation from the local root update path.
181    pub fn prepare_role_attestation_root(
182        request: RoleAttestationRequest,
183    ) -> Result<RoleAttestationPrepareResponse, Error> {
184        RuntimeAuthWorkflow::prepare_role_attestation_root(request).map_err(Self::map_auth_error)
185    }
186
187    /// Retrieve a prepared role attestation with its root canister-signature proof.
188    pub fn get_role_attestation_root(
189        request: RoleAttestationGetRequest,
190    ) -> Result<SignedRoleAttestation, Error> {
191        EnvOps::require_root().map_err(Error::from)?;
192        AuthOps::get_role_attestation(IcOps::msg_caller(), request.payload_hash)
193            .map_err(Self::map_auth_error)
194    }
195
196    /// Verify a role attestation locally from its embedded root proof.
197    pub async fn verify_role_attestation(
198        attestation: &SignedRoleAttestation,
199        min_accepted_epoch: u64,
200    ) -> Result<(), Error> {
201        crate::workflow::runtime::auth::RuntimeAuthWorkflow::verify_role_attestation(
202            attestation,
203            min_accepted_epoch,
204        )
205        .await
206        .map_err(Self::map_auth_error)
207    }
208
209    // Resolve the delegated-token TTL ceiling for endpoint auth/session callers.
210    fn delegated_token_max_ttl_ns() -> Result<u64, Error> {
211        let cfg = ConfigOps::delegated_tokens_config().map_err(Error::from)?;
212        if !cfg.enabled {
213            return Err(Error::forbidden(Self::DELEGATED_TOKENS_DISABLED));
214        }
215
216        let max_ttl_secs = cfg
217            .max_ttl_secs
218            .unwrap_or(Self::MAX_DELEGATED_SESSION_TTL_SECS);
219        max_ttl_secs.checked_mul(1_000_000_000).ok_or_else(|| {
220            Error::invalid("auth.delegated_tokens.max_ttl_secs overflows nanoseconds")
221        })
222    }
223}