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            DelegatedToken, DelegatedTokenGetRequest, DelegatedTokenPrepareRequest,
12            DelegatedTokenPrepareResponse, DelegationProof, DelegationProofGetRequest,
13            DelegationProofIssueRequest, DelegationProofPrepareResponse,
14            InstallActiveDelegationProofRequest, InstallActiveDelegationProofResponse,
15            RoleAttestationGetRequest, RoleAttestationPrepareResponse, RoleAttestationRequest,
16            SignedRoleAttestation,
17        },
18        error::Error,
19    },
20    error::InternalErrorClass,
21    ops::{
22        auth::{AuthOps, VerifyDelegatedTokenRuntimeInput},
23        config::ConfigOps,
24        ic::IcOps,
25        runtime::env::EnvOps,
26    },
27    workflow::runtime::auth::RuntimeAuthWorkflow,
28};
29use root_delegation_client::RootDelegationProofClient;
30
31// Internal auth pipeline:
32// - `session` owns delegated-session ingress and replay/session state handling.
33// - `metadata` owns root request metadata construction.
34mod metadata;
35mod root_delegation_client;
36mod session;
37
38///
39/// AuthApi
40///
41/// Owns delegated-token helpers and root-signed role-attestation helpers.
42/// Owned by the API layer and called by generated endpoint wrappers.
43///
44
45pub struct AuthApi;
46
47impl AuthApi {
48    const DELEGATED_TOKENS_DISABLED: &str =
49        "delegated token auth disabled; set auth.delegated_tokens.enabled=true in canic.toml";
50    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";
51    const MAX_DELEGATED_SESSION_TTL_SECS: u64 = 24 * 60 * 60;
52    const SESSION_BOOTSTRAP_TOKEN_FINGERPRINT_DOMAIN: &[u8] =
53        b"canic-session-bootstrap-token-fingerprint";
54
55    // Map internal auth failures onto public endpoint errors.
56    fn map_auth_error(err: crate::InternalError) -> Error {
57        match err.class() {
58            InternalErrorClass::Infra | InternalErrorClass::Ops | InternalErrorClass::Workflow => {
59                Error::internal(err.to_string())
60            }
61            _ => Error::from(err),
62        }
63    }
64
65    fn require_delegated_token_issuer_enabled() -> Result<(), Error> {
66        let delegated_tokens_cfg =
67            ConfigOps::delegated_tokens_config().map_err(Self::map_auth_error)?;
68        if !delegated_tokens_cfg.enabled {
69            return Err(Error::invalid(Self::DELEGATED_TOKENS_DISABLED));
70        }
71
72        let canister_cfg = ConfigOps::current_canister().map_err(Self::map_auth_error)?;
73        if !canister_cfg.auth.delegated_token_issuer {
74            return Err(Error::forbidden(Self::DELEGATED_TOKEN_ISSUER_DISABLED));
75        }
76
77        Ok(())
78    }
79
80    // Verify delegated-token material and return the token subject.
81    //
82    // This is intentionally private: endpoint authorization must also bind the
83    // verified subject to the caller before dispatch.
84    fn verify_token_material(
85        token: &DelegatedToken,
86        max_cert_ttl_ns: u64,
87        max_token_ttl_ns: u64,
88        required_scopes: &[String],
89        now_ns: u64,
90    ) -> Result<Principal, Error> {
91        AuthOps::verify_token(VerifyDelegatedTokenRuntimeInput {
92            token,
93            caller: IcOps::msg_caller(),
94            max_cert_ttl_ns,
95            max_token_ttl_ns,
96            required_scopes,
97            now_ns,
98        })
99        .map(|verified| verified.subject)
100        .map_err(Self::map_auth_error)
101    }
102
103    /// Prepare a delegated token from the issuer-local active delegation proof.
104    pub fn prepare_delegated_token(
105        request: DelegatedTokenPrepareRequest,
106    ) -> Result<DelegatedTokenPrepareResponse, Error> {
107        Self::require_delegated_token_issuer_enabled()?;
108        RuntimeAuthWorkflow::prepare_delegated_token(request).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    /// Prepare a root delegation proof from root over RPC.
133    pub async fn prepare_delegation_proof(
134        request: DelegationProofIssueRequest,
135    ) -> Result<DelegationProofPrepareResponse, Error> {
136        let request = metadata::with_delegation_request_metadata(request);
137        Self::prepare_delegation_proof_remote(request).await
138    }
139
140    /// Prepare a root-certified delegation proof from the local root update path.
141    pub fn prepare_delegation_proof_root(
142        request: DelegationProofIssueRequest,
143    ) -> Result<DelegationProofPrepareResponse, Error> {
144        RuntimeAuthWorkflow::prepare_delegation_proof_root(request).map_err(Self::map_auth_error)
145    }
146
147    /// Retrieve a prepared self-contained delegation proof from the local root query path.
148    pub fn get_delegation_proof_root(
149        request: DelegationProofGetRequest,
150    ) -> Result<DelegationProof, Error> {
151        EnvOps::require_root().map_err(Error::from)?;
152        let caller = IcOps::msg_caller();
153        AuthOps::get_delegation_proof(caller, request.cert_hash).map_err(Self::map_auth_error)
154    }
155
156    /// Prepare a root-certified role attestation from the local root update path.
157    pub fn prepare_role_attestation_root(
158        request: RoleAttestationRequest,
159    ) -> Result<RoleAttestationPrepareResponse, Error> {
160        RuntimeAuthWorkflow::prepare_role_attestation_root(request).map_err(Self::map_auth_error)
161    }
162
163    /// Retrieve a prepared role attestation with its root canister-signature proof.
164    pub fn get_role_attestation_root(
165        request: RoleAttestationGetRequest,
166    ) -> Result<SignedRoleAttestation, Error> {
167        EnvOps::require_root().map_err(Error::from)?;
168        AuthOps::get_role_attestation(IcOps::msg_caller(), request.payload_hash)
169            .map_err(Self::map_auth_error)
170    }
171
172    /// Verify a role attestation locally from its embedded root proof.
173    pub async fn verify_role_attestation(
174        attestation: &SignedRoleAttestation,
175        min_accepted_epoch: u64,
176    ) -> Result<(), Error> {
177        crate::workflow::runtime::auth::RuntimeAuthWorkflow::verify_role_attestation(
178            attestation,
179            min_accepted_epoch,
180        )
181        .await
182        .map_err(Self::map_auth_error)
183    }
184
185    // Resolve the delegated-token TTL ceiling for endpoint auth/session callers.
186    fn delegated_token_max_ttl_ns() -> Result<u64, Error> {
187        let cfg = ConfigOps::delegated_tokens_config().map_err(Error::from)?;
188        if !cfg.enabled {
189            return Err(Error::forbidden(Self::DELEGATED_TOKENS_DISABLED));
190        }
191
192        let max_ttl_secs = cfg
193            .max_ttl_secs
194            .unwrap_or(Self::MAX_DELEGATED_SESSION_TTL_SECS);
195        max_ttl_secs.checked_mul(1_000_000_000).ok_or_else(|| {
196            Error::invalid("auth.delegated_tokens.max_ttl_secs overflows nanoseconds")
197        })
198    }
199}
200
201impl AuthApi {
202    // Route a delegation proof prepare request over RPC to root.
203    async fn prepare_delegation_proof_remote(
204        request: DelegationProofIssueRequest,
205    ) -> Result<DelegationProofPrepareResponse, Error> {
206        let root_pid = EnvOps::root_pid().map_err(Error::from)?;
207        RootDelegationProofClient::new(root_pid)
208            .prepare_delegation_proof(request)
209            .await
210            .map_err(Self::map_auth_error)
211    }
212}