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::{api::is_controller as caller_is_controller, types::Principal},
9    dto::{
10        auth::{
11            ActiveDelegationProofStatusResponse, DelegatedToken, DelegatedTokenGetRequest,
12            DelegatedTokenPrepareRequest, DelegatedTokenPrepareResponse,
13            InstallActiveDelegationProofRequest, InstallActiveDelegationProofResponse,
14            RoleAttestationGetRequest, RoleAttestationPrepareResponse, RoleAttestationRequest,
15            RootDelegationProofBatchGetRequest, RootDelegationProofBatchGetResponse,
16            RootDelegationProofBatchInstallRequest, RootDelegationProofBatchInstallResponse,
17            RootDelegationProofBatchPrepareRequest, RootDelegationProofBatchPrepareResponse,
18            RootDelegationProofBatchProof, RootDelegationRenewalProofBatchGetRequest,
19            RootDelegationRenewalProvisionerListResponse, RootDelegationRenewalProvisionerResponse,
20            RootDelegationRenewalProvisionerUpsertRequest, RootDelegationRenewalWorkListResponse,
21            RootIssuerPolicyResponse, RootIssuerPolicyUpsertRequest,
22            RootIssuerRenewalStatusRequest, RootIssuerRenewalStatusResponse,
23            RootIssuerRenewalTemplateResponse, RootIssuerRenewalTemplateUpsertRequest,
24            SignedRoleAttestation,
25        },
26        error::Error,
27    },
28    error::InternalErrorClass,
29    ops::{
30        auth::{AuthOps, VerifyDelegatedTokenRuntimeInput},
31        config::ConfigOps,
32        ic::IcOps,
33        runtime::env::EnvOps,
34    },
35    workflow::runtime::auth::RuntimeAuthWorkflow,
36};
37
38// Internal auth pipeline:
39// - `session` owns delegated-session ingress and replay/session state handling.
40mod session;
41
42///
43/// AuthApi
44///
45/// Owns delegated-token helpers and root-signed role-attestation helpers.
46/// Owned by the API layer and called by generated endpoint wrappers.
47///
48
49pub struct AuthApi;
50
51impl AuthApi {
52    const DELEGATED_TOKENS_DISABLED: &str =
53        "delegated token auth disabled; set auth.delegated_tokens.enabled=true in canic.toml";
54    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";
55    const LEGACY_ROOT_PROOF_PROVISIONING_DISABLED: &str = "bridge-backed canister-signature root proof provisioning is disabled in 0.76 chain_key_batch mode";
56    const MAX_DELEGATED_SESSION_TTL_SECS: u64 = 24 * 60 * 60;
57    const SESSION_BOOTSTRAP_TOKEN_FINGERPRINT_DOMAIN: &[u8] =
58        b"canic-session-bootstrap-token-fingerprint";
59
60    // Map internal auth failures onto public endpoint errors.
61    fn map_auth_error(err: crate::InternalError) -> Error {
62        match err.class() {
63            InternalErrorClass::Infra | InternalErrorClass::Ops | InternalErrorClass::Workflow => {
64                Error::internal(err.to_string())
65            }
66            _ => Error::from(err),
67        }
68    }
69
70    fn require_delegated_token_issuer_enabled() -> Result<(), Error> {
71        let delegated_tokens_cfg =
72            ConfigOps::delegated_tokens_config().map_err(Self::map_auth_error)?;
73        if !delegated_tokens_cfg.enabled {
74            return Err(Error::invalid(Self::DELEGATED_TOKENS_DISABLED));
75        }
76
77        let canister_cfg = ConfigOps::current_canister().map_err(Self::map_auth_error)?;
78        if !canister_cfg.auth.delegated_token_issuer {
79            return Err(Error::forbidden(Self::DELEGATED_TOKEN_ISSUER_DISABLED));
80        }
81
82        Ok(())
83    }
84
85    fn require_delegation_renewal_provisioner_or_controller() -> Result<bool, Error> {
86        let caller = IcOps::msg_caller();
87        if caller_is_controller(&caller) {
88            return Ok(true);
89        }
90        if AuthOps::is_delegation_renewal_provisioner(caller) {
91            return Ok(false);
92        }
93
94        Err(Error::forbidden(
95            "caller is not a controller or enabled delegation renewal provisioner",
96        ))
97    }
98
99    fn require_legacy_root_proof_provisioning_enabled() -> Result<(), Error> {
100        let cfg = ConfigOps::delegated_tokens_config().map_err(Self::map_auth_error)?;
101        if cfg.root_proof_mode.trim() == "chain_key_batch" {
102            return Err(Error::forbidden(
103                Self::LEGACY_ROOT_PROOF_PROVISIONING_DISABLED,
104            ));
105        }
106        Ok(())
107    }
108
109    // Verify delegated-token material and return the token subject.
110    //
111    // This is intentionally private: endpoint authorization must also bind the
112    // verified subject to the caller before dispatch.
113    fn verify_token_material(
114        token: &DelegatedToken,
115        max_cert_ttl_ns: u64,
116        max_token_ttl_ns: u64,
117        required_scopes: &[String],
118        now_ns: u64,
119    ) -> Result<Principal, Error> {
120        AuthOps::verify_token(VerifyDelegatedTokenRuntimeInput {
121            token,
122            caller: IcOps::msg_caller(),
123            max_cert_ttl_ns,
124            max_token_ttl_ns,
125            required_scopes,
126            now_ns,
127        })
128        .map(|verified| verified.subject)
129        .map_err(Self::map_auth_error)
130    }
131
132    /// Prepare a delegated token from the issuer-local active delegation proof.
133    pub async fn prepare_delegated_token(
134        request: DelegatedTokenPrepareRequest,
135    ) -> Result<DelegatedTokenPrepareResponse, Error> {
136        Self::require_delegated_token_issuer_enabled()?;
137        RuntimeAuthWorkflow::prepare_delegated_token(request)
138            .await
139            .map_err(Self::map_auth_error)
140    }
141
142    /// Retrieve a prepared delegated token with its issuer canister-signature proof.
143    pub fn get_delegated_token(request: DelegatedTokenGetRequest) -> Result<DelegatedToken, Error> {
144        Self::require_delegated_token_issuer_enabled()?;
145
146        AuthOps::get_delegated_token_issuer_proof(request.claims_hash, IcOps::msg_caller())
147            .map_err(Self::map_auth_error)
148    }
149
150    /// Install validated root-certified delegation material for issuer-local token issuance.
151    pub fn install_active_delegation_proof(
152        request: InstallActiveDelegationProofRequest,
153    ) -> Result<InstallActiveDelegationProofResponse, Error> {
154        Self::require_delegated_token_issuer_enabled()?;
155
156        let active_proof =
157            AuthOps::install_active_delegation_proof(request.proof, IcOps::msg_caller())
158                .map_err(Self::map_auth_error)?;
159
160        Ok(InstallActiveDelegationProofResponse { active_proof })
161    }
162
163    /// Report non-secret issuer-local active proof lifecycle status for provisioners.
164    pub fn active_delegation_proof_status() -> Result<ActiveDelegationProofStatusResponse, Error> {
165        Self::require_delegated_token_issuer_enabled()?;
166        Ok(AuthOps::active_delegation_proof_status(IcOps::now_nanos()))
167    }
168
169    /// Upsert root issuer policy from the local root controller path.
170    pub fn upsert_root_issuer_policy_root(
171        request: RootIssuerPolicyUpsertRequest,
172    ) -> Result<RootIssuerPolicyResponse, Error> {
173        EnvOps::require_root().map_err(Error::from)?;
174        AuthOps::upsert_root_issuer_policy(request, IcOps::now_nanos())
175            .map_err(Self::map_auth_error)
176    }
177
178    /// Upsert root-managed renewal template from the local root controller path.
179    pub fn upsert_root_issuer_renewal_template_root(
180        request: RootIssuerRenewalTemplateUpsertRequest,
181    ) -> Result<RootIssuerRenewalTemplateResponse, Error> {
182        EnvOps::require_root().map_err(Error::from)?;
183        let response = AuthOps::upsert_root_issuer_renewal_template(request, IcOps::now_nanos())
184            .map_err(Self::map_auth_error)?;
185        if response.template.enabled {
186            RuntimeAuthWorkflow::start_root_delegation_renewal_timer_soon_if_configured()
187                .map_err(Self::map_auth_error)?;
188        }
189        Ok(response)
190    }
191
192    /// Report root-managed renewal template/state for one issuer.
193    pub fn root_issuer_renewal_status_root(
194        request: RootIssuerRenewalStatusRequest,
195    ) -> Result<RootIssuerRenewalStatusResponse, Error> {
196        EnvOps::require_root().map_err(Error::from)?;
197        Ok(AuthOps::root_issuer_renewal_status(request))
198    }
199
200    /// Upsert a constrained root-managed renewal provisioner.
201    pub fn upsert_delegation_renewal_provisioner_root(
202        request: RootDelegationRenewalProvisionerUpsertRequest,
203    ) -> Result<RootDelegationRenewalProvisionerResponse, Error> {
204        EnvOps::require_root().map_err(Error::from)?;
205        Self::require_legacy_root_proof_provisioning_enabled()?;
206        Ok(AuthOps::upsert_delegation_renewal_provisioner(request))
207    }
208
209    /// List constrained root-managed renewal provisioners.
210    pub fn delegation_renewal_provisioners_root()
211    -> Result<RootDelegationRenewalProvisionerListResponse, Error> {
212        EnvOps::require_root().map_err(Error::from)?;
213        Self::require_legacy_root_proof_provisioning_enabled()?;
214        Ok(AuthOps::delegation_renewal_provisioners())
215    }
216
217    /// List root-scheduled renewal batches ready for a constrained provisioner.
218    pub fn delegation_renewal_work_root() -> Result<RootDelegationRenewalWorkListResponse, Error> {
219        EnvOps::require_root().map_err(Error::from)?;
220        Self::require_legacy_root_proof_provisioning_enabled()?;
221        Self::require_delegation_renewal_provisioner_or_controller()?;
222        Ok(AuthOps::delegation_renewal_work(IcOps::now_nanos()))
223    }
224
225    /// Prepare root delegation proof batch metadata from the local root update path.
226    pub fn prepare_delegation_proof_batch_root(
227        request: RootDelegationProofBatchPrepareRequest,
228    ) -> Result<RootDelegationProofBatchPrepareResponse, Error> {
229        EnvOps::require_root().map_err(Error::from)?;
230        Self::require_legacy_root_proof_provisioning_enabled()?;
231        let max_cert_ttl_ns = Self::delegated_token_max_ttl_ns()?;
232        AuthOps::prepare_delegation_proof_batch(request, max_cert_ttl_ns, IcOps::now_nanos())
233            .map_err(Self::map_auth_error)
234    }
235
236    /// Retrieve root delegation proofs from the local direct root query path.
237    pub fn get_delegation_proof_batch_root(
238        request: RootDelegationProofBatchGetRequest,
239    ) -> Result<RootDelegationProofBatchGetResponse, Error> {
240        EnvOps::require_root().map_err(Error::from)?;
241        Self::require_legacy_root_proof_provisioning_enabled()?;
242        AuthOps::get_delegation_proof_batch(request).map_err(Self::map_auth_error)
243    }
244
245    /// Retrieve root-scheduled renewal proofs from the local direct root query path.
246    pub fn get_delegation_renewal_proof_batch_root(
247        request: RootDelegationRenewalProofBatchGetRequest,
248    ) -> Result<RootDelegationProofBatchGetResponse, Error> {
249        EnvOps::require_root().map_err(Error::from)?;
250        Self::require_legacy_root_proof_provisioning_enabled()?;
251        Self::require_delegation_renewal_provisioner_or_controller()?;
252        AuthOps::get_delegation_renewal_proof_batch(request).map_err(Self::map_auth_error)
253    }
254
255    /// Install retrieved root delegation proof batches from the local root update path.
256    pub async fn install_delegation_proof_batch_root(
257        request: RootDelegationProofBatchInstallRequest,
258    ) -> Result<RootDelegationProofBatchInstallResponse, Error> {
259        EnvOps::require_root().map_err(Error::from)?;
260        Self::require_legacy_root_proof_provisioning_enabled()?;
261        let caller_is_controller = Self::require_delegation_renewal_provisioner_or_controller()?;
262        if !caller_is_controller {
263            AuthOps::ensure_delegation_renewal_batch_scheduled(
264                request.batch_id,
265                IcOps::now_nanos(),
266            )
267            .map_err(Self::map_auth_error)?;
268        }
269        RuntimeAuthWorkflow::install_delegation_proof_batch_root(request)
270            .await
271            .map_err(Self::map_auth_error)
272    }
273
274    /// Return or create a chain-key root delegation proof for the registered issuer caller.
275    pub async fn get_or_create_chain_key_delegation_proof_root()
276    -> Result<RootDelegationProofBatchProof, Error> {
277        EnvOps::require_root().map_err(Error::from)?;
278        RuntimeAuthWorkflow::get_or_create_chain_key_delegation_proof_for_issuer_root(
279            IcOps::msg_caller(),
280        )
281        .await
282        .map_err(Self::map_auth_error)
283    }
284
285    /// Prepare a root-certified role attestation from the local root update path.
286    pub fn prepare_role_attestation_root(
287        request: RoleAttestationRequest,
288    ) -> Result<RoleAttestationPrepareResponse, Error> {
289        RuntimeAuthWorkflow::prepare_role_attestation_root(request).map_err(Self::map_auth_error)
290    }
291
292    /// Retrieve a prepared role attestation with its root canister-signature proof.
293    pub fn get_role_attestation_root(
294        request: RoleAttestationGetRequest,
295    ) -> Result<SignedRoleAttestation, Error> {
296        EnvOps::require_root().map_err(Error::from)?;
297        AuthOps::get_role_attestation(IcOps::msg_caller(), request.payload_hash)
298            .map_err(Self::map_auth_error)
299    }
300
301    /// Verify a role attestation locally from its embedded root proof.
302    pub async fn verify_role_attestation(
303        attestation: &SignedRoleAttestation,
304        min_accepted_epoch: u64,
305    ) -> Result<(), Error> {
306        crate::workflow::runtime::auth::RuntimeAuthWorkflow::verify_role_attestation(
307            attestation,
308            min_accepted_epoch,
309        )
310        .await
311        .map_err(Self::map_auth_error)
312    }
313
314    // Resolve the delegated-token TTL ceiling for endpoint auth/session callers.
315    fn delegated_token_max_ttl_ns() -> Result<u64, Error> {
316        let cfg = ConfigOps::delegated_tokens_config().map_err(Error::from)?;
317        if !cfg.enabled {
318            return Err(Error::forbidden(Self::DELEGATED_TOKENS_DISABLED));
319        }
320
321        let max_ttl_secs = cfg
322            .max_ttl_secs
323            .unwrap_or(Self::MAX_DELEGATED_SESSION_TTL_SECS);
324        max_ttl_secs.checked_mul(1_000_000_000).ok_or_else(|| {
325            Error::invalid("auth.delegated_tokens.max_ttl_secs overflows nanoseconds")
326        })
327    }
328}