1use crate::{
2 cdk::types::Principal,
3 dto::{
4 auth::{
5 AttestationKeySet, DelegatedToken, DelegatedTokenClaims, DelegationCert,
6 DelegationProof, DelegationProvisionResponse, DelegationProvisionTargetKind,
7 DelegationRequest, RoleAttestationRequest, SignedRoleAttestation,
8 },
9 error::Error,
10 rpc::{Request as RootRequest, Response as RootCapabilityResponse},
11 },
12 error::InternalErrorClass,
13 log,
14 log::Topic,
15 ops::{
16 auth::DelegatedTokenOps,
17 config::ConfigOps,
18 ic::IcOps,
19 rpc::RpcOps,
20 runtime::env::EnvOps,
21 runtime::metrics::auth::{
22 record_attestation_refresh_failed, record_signer_mint_without_proof,
23 },
24 storage::auth::DelegationStateOps,
25 },
26 protocol,
27 workflow::rpc::request::handler::RootResponseWorkflow,
28};
29
30mod metadata;
31mod verify_flow;
32
33pub struct DelegationApi;
40
41impl DelegationApi {
42 const DELEGATED_TOKENS_DISABLED: &str =
43 "delegated token auth disabled; set auth.delegated_tokens.enabled=true in canic.toml";
44
45 fn map_delegation_error(err: crate::InternalError) -> Error {
46 match err.class() {
47 InternalErrorClass::Infra | InternalErrorClass::Ops | InternalErrorClass::Workflow => {
48 Error::internal(err.to_string())
49 }
50 _ => Error::from(err),
51 }
52 }
53
54 pub fn verify_delegation_proof(
59 proof: &DelegationProof,
60 authority_pid: Principal,
61 ) -> Result<(), Error> {
62 DelegatedTokenOps::verify_delegation_proof(proof, authority_pid)
63 .map_err(Self::map_delegation_error)
64 }
65
66 pub async fn sign_token(
67 claims: DelegatedTokenClaims,
68 proof: DelegationProof,
69 ) -> Result<DelegatedToken, Error> {
70 DelegatedTokenOps::sign_token(claims, proof)
71 .await
72 .map_err(Self::map_delegation_error)
73 }
74
75 pub fn verify_token(
80 token: &DelegatedToken,
81 authority_pid: Principal,
82 now_secs: u64,
83 ) -> Result<(), Error> {
84 DelegatedTokenOps::verify_token(token, authority_pid, now_secs, IcOps::canister_self())
85 .map(|_| ())
86 .map_err(Self::map_delegation_error)
87 }
88
89 pub fn verify_token_verified(
94 token: &DelegatedToken,
95 authority_pid: Principal,
96 now_secs: u64,
97 ) -> Result<(DelegatedTokenClaims, DelegationCert), Error> {
98 DelegatedTokenOps::verify_token(token, authority_pid, now_secs, IcOps::canister_self())
99 .map(|verified| (verified.claims, verified.cert))
100 .map_err(Self::map_delegation_error)
101 }
102
103 pub async fn request_delegation(
107 request: DelegationRequest,
108 ) -> Result<DelegationProvisionResponse, Error> {
109 let request = metadata::with_root_request_metadata(request);
110 let response = RootResponseWorkflow::response(RootRequest::issue_delegation(request))
111 .await
112 .map_err(Self::map_delegation_error)?;
113
114 match response {
115 RootCapabilityResponse::DelegationIssued(response) => Ok(response),
116 _ => Err(Error::internal(
117 "invalid root response type for delegation request",
118 )),
119 }
120 }
121
122 pub async fn request_role_attestation(
123 request: RoleAttestationRequest,
124 ) -> Result<SignedRoleAttestation, Error> {
125 let request = metadata::with_root_attestation_request_metadata(request);
126 let response = RootResponseWorkflow::response(RootRequest::issue_role_attestation(request))
127 .await
128 .map_err(Self::map_delegation_error)?;
129
130 match response {
131 RootCapabilityResponse::RoleAttestationIssued(response) => Ok(response),
132 _ => Err(Error::internal(
133 "invalid root response type for role attestation request",
134 )),
135 }
136 }
137
138 pub async fn attestation_key_set() -> Result<AttestationKeySet, Error> {
139 DelegatedTokenOps::attestation_key_set()
140 .await
141 .map_err(Self::map_delegation_error)
142 }
143
144 pub fn replace_attestation_key_set(key_set: AttestationKeySet) {
145 DelegatedTokenOps::replace_attestation_key_set(key_set);
146 }
147
148 pub async fn verify_role_attestation(
149 attestation: &SignedRoleAttestation,
150 min_accepted_epoch: u64,
151 ) -> Result<(), Error> {
152 let configured_min_accepted_epoch = ConfigOps::role_attestation_config()
153 .map_err(Error::from)?
154 .min_accepted_epoch_by_role
155 .get(attestation.payload.role.as_str())
156 .copied();
157 let min_accepted_epoch = verify_flow::resolve_min_accepted_epoch(
158 min_accepted_epoch,
159 configured_min_accepted_epoch,
160 );
161
162 let caller = IcOps::msg_caller();
163 let self_pid = IcOps::canister_self();
164 let now_secs = IcOps::now_secs();
165 let verifier_subnet = Some(EnvOps::subnet_pid().map_err(Error::from)?);
166 let root_pid = EnvOps::root_pid().map_err(Error::from)?;
167
168 let verify = || {
169 DelegatedTokenOps::verify_role_attestation_cached(
170 attestation,
171 caller,
172 self_pid,
173 verifier_subnet,
174 now_secs,
175 min_accepted_epoch,
176 )
177 .map(|_| ())
178 };
179 let refresh = || async {
180 let key_set: AttestationKeySet =
181 RpcOps::call_rpc_result(root_pid, protocol::CANIC_ATTESTATION_KEY_SET, ()).await?;
182 DelegatedTokenOps::replace_attestation_key_set(key_set);
183 Ok(())
184 };
185
186 match verify_flow::verify_role_attestation_with_single_refresh(verify, refresh).await {
187 Ok(()) => Ok(()),
188 Err(verify_flow::RoleAttestationVerifyFlowError::Initial(err)) => {
189 verify_flow::record_attestation_verifier_rejection(&err);
190 verify_flow::log_attestation_verifier_rejection(
191 &err,
192 attestation,
193 caller,
194 self_pid,
195 "cached",
196 );
197 Err(Self::map_delegation_error(err.into()))
198 }
199 Err(verify_flow::RoleAttestationVerifyFlowError::Refresh { trigger, source }) => {
200 verify_flow::record_attestation_verifier_rejection(&trigger);
201 verify_flow::log_attestation_verifier_rejection(
202 &trigger,
203 attestation,
204 caller,
205 self_pid,
206 "cache_miss_refresh",
207 );
208 record_attestation_refresh_failed();
209 log!(
210 Topic::Auth,
211 Warn,
212 "role attestation refresh failed local={} caller={} key_id={} error={}",
213 self_pid,
214 caller,
215 attestation.key_id,
216 source
217 );
218 Err(Self::map_delegation_error(source))
219 }
220 Err(verify_flow::RoleAttestationVerifyFlowError::PostRefresh(err)) => {
221 verify_flow::record_attestation_verifier_rejection(&err);
222 verify_flow::log_attestation_verifier_rejection(
223 &err,
224 attestation,
225 caller,
226 self_pid,
227 "post_refresh",
228 );
229 Err(Self::map_delegation_error(err.into()))
230 }
231 }
232 }
233
234 pub async fn store_proof(
235 proof: DelegationProof,
236 kind: DelegationProvisionTargetKind,
237 ) -> Result<(), Error> {
238 let cfg = ConfigOps::delegated_tokens_config().map_err(Error::from)?;
239 if !cfg.enabled {
240 return Err(Error::forbidden(Self::DELEGATED_TOKENS_DISABLED));
241 }
242
243 let root_pid = EnvOps::root_pid().map_err(Error::from)?;
244 let caller = IcOps::msg_caller();
245 if caller != root_pid {
246 return Err(Error::forbidden(
247 "delegation proof store requires root caller",
248 ));
249 }
250
251 DelegatedTokenOps::cache_public_keys_for_cert(&proof.cert)
252 .await
253 .map_err(Self::map_delegation_error)?;
254 if let Err(err) = DelegatedTokenOps::verify_delegation_proof(&proof, root_pid) {
255 let local = IcOps::canister_self();
256 log!(
257 Topic::Auth,
258 Warn,
259 "delegation proof rejected kind={:?} local={} shard={} issued_at={} expires_at={} error={}",
260 kind,
261 local,
262 proof.cert.shard_pid,
263 proof.cert.issued_at,
264 proof.cert.expires_at,
265 err
266 );
267 return Err(Self::map_delegation_error(err));
268 }
269
270 DelegationStateOps::set_proof_from_dto(proof);
271 let local = IcOps::canister_self();
272 let stored = DelegationStateOps::proof_dto()
273 .ok_or_else(|| Error::invariant("delegation proof missing after store"))?;
274 log!(
275 Topic::Auth,
276 Info,
277 "delegation proof stored kind={:?} local={} shard={} issued_at={} expires_at={}",
278 kind,
279 local,
280 stored.cert.shard_pid,
281 stored.cert.issued_at,
282 stored.cert.expires_at
283 );
284
285 Ok(())
286 }
287
288 pub fn require_proof() -> Result<DelegationProof, Error> {
289 let cfg = ConfigOps::delegated_tokens_config().map_err(Error::from)?;
290 if !cfg.enabled {
291 return Err(Error::forbidden(Self::DELEGATED_TOKENS_DISABLED));
292 }
293
294 DelegationStateOps::proof_dto().ok_or_else(|| {
295 record_signer_mint_without_proof();
296 Error::not_found("delegation proof not set")
297 })
298 }
299}
300
301#[cfg(test)]
302mod tests;