1use crate::{
2 dto::{
3 auth::{
4 AttestationKeySet, DelegatedTokenIssueRequestV2, DelegatedTokenMintRequestV2,
5 DelegatedTokenV2, DelegationProofIssueRequestV2, DelegationProofV2,
6 RoleAttestationRequest, SignedRoleAttestation,
7 },
8 error::Error,
9 rpc::{Request as RootRequest, Response as RootCapabilityResponse},
10 },
11 error::InternalErrorClass,
12 log,
13 log::Topic,
14 ops::{
15 auth::{
16 DelegatedTokenOps, SignDelegatedTokenV2Input, SignDelegationProofV2Input,
17 VerifyDelegatedTokenV2RuntimeInput,
18 },
19 config::ConfigOps,
20 ic::IcOps,
21 rpc::RpcOps,
22 runtime::env::EnvOps,
23 runtime::metrics::auth::record_attestation_refresh_failed,
24 },
25 protocol,
26 workflow::rpc::request::handler::RootResponseWorkflow,
27};
28
29mod metadata;
34mod session;
35mod verify_flow;
36
37pub struct DelegationApi;
44
45impl DelegationApi {
46 const DELEGATED_TOKENS_DISABLED: &str =
47 "delegated token auth disabled; set auth.delegated_tokens.enabled=true in canic.toml";
48 const MAX_DELEGATED_SESSION_TTL_SECS: u64 = 24 * 60 * 60;
49 const SESSION_BOOTSTRAP_TOKEN_FINGERPRINT_DOMAIN: &[u8] =
50 b"canic-session-bootstrap-token-fingerprint:v2";
51
52 fn map_delegation_error(err: crate::InternalError) -> Error {
54 match err.class() {
55 InternalErrorClass::Infra | InternalErrorClass::Ops | InternalErrorClass::Workflow => {
56 Error::internal(err.to_string())
57 }
58 _ => Error::from(err),
59 }
60 }
61
62 pub async fn local_shard_public_key_sec1() -> Result<Vec<u8>, Error> {
64 DelegatedTokenOps::local_shard_public_key_sec1(IcOps::canister_self())
65 .await
66 .map_err(Self::map_delegation_error)
67 }
68
69 pub async fn issue_token(
71 request: DelegatedTokenIssueRequestV2,
72 ) -> Result<DelegatedTokenV2, Error> {
73 DelegatedTokenOps::sign_token_v2(SignDelegatedTokenV2Input {
74 proof: request.proof,
75 subject: request.subject,
76 audience: request.aud,
77 scopes: request.scopes,
78 ttl_secs: request.ttl_secs,
79 nonce: request.nonce,
80 })
81 .await
82 .map_err(Self::map_delegation_error)
83 }
84
85 pub async fn mint_token(
87 request: DelegatedTokenMintRequestV2,
88 ) -> Result<DelegatedTokenV2, Error> {
89 let proof = Self::request_delegation(DelegationProofIssueRequestV2 {
90 shard_pid: IcOps::canister_self(),
91 scopes: request.scopes.clone(),
92 aud: request.aud.clone(),
93 cert_ttl_secs: request.cert_ttl_secs,
94 root_key_cert: request.root_key_cert,
95 })
96 .await?;
97
98 Self::issue_token(DelegatedTokenIssueRequestV2 {
99 proof,
100 subject: request.subject,
101 aud: request.aud,
102 scopes: request.scopes,
103 ttl_secs: request.token_ttl_secs,
104 nonce: request.nonce,
105 })
106 .await
107 }
108
109 pub async fn mint_token_v2(
111 request: DelegatedTokenMintRequestV2,
112 ) -> Result<DelegatedTokenV2, Error> {
113 Self::mint_token(request).await
114 }
115
116 pub fn verify_token(
118 token: &DelegatedTokenV2,
119 max_cert_ttl_secs: u64,
120 max_token_ttl_secs: u64,
121 required_scopes: &[String],
122 now_secs: u64,
123 ) -> Result<(), Error> {
124 DelegatedTokenOps::verify_token_v2(VerifyDelegatedTokenV2RuntimeInput {
125 token,
126 max_cert_ttl_secs,
127 max_token_ttl_secs,
128 required_scopes,
129 now_secs,
130 })
131 .map(|_| ())
132 .map_err(Self::map_delegation_error)
133 }
134
135 pub async fn request_delegation(
137 request: DelegationProofIssueRequestV2,
138 ) -> Result<DelegationProofV2, Error> {
139 Self::request_delegation_remote(request).await
140 }
141
142 pub async fn request_delegation_v2(
144 request: DelegationProofIssueRequestV2,
145 ) -> Result<DelegationProofV2, Error> {
146 Self::request_delegation(request).await
147 }
148
149 pub async fn issue_delegation_proof(
151 request: DelegationProofIssueRequestV2,
152 ) -> Result<DelegationProofV2, Error> {
153 EnvOps::require_root().map_err(Error::from)?;
154 let max_cert_ttl_secs = Self::delegated_auth_max_ttl_secs()?;
155 let max_token_ttl_secs = request.cert_ttl_secs.min(max_cert_ttl_secs);
156 DelegatedTokenOps::sign_delegation_proof_v2(SignDelegationProofV2Input {
157 audience: request.aud,
158 scopes: request.scopes,
159 shard_pid: request.shard_pid,
160 cert_ttl_secs: request.cert_ttl_secs,
161 max_token_ttl_secs,
162 max_cert_ttl_secs,
163 issued_at: IcOps::now_secs(),
164 root_key_cert: request.root_key_cert,
165 })
166 .await
167 .map_err(Self::map_delegation_error)
168 }
169
170 pub async fn issue_delegation_proof_v2(
172 request: DelegationProofIssueRequestV2,
173 ) -> Result<DelegationProofV2, Error> {
174 Self::issue_delegation_proof(request).await
175 }
176
177 pub async fn request_role_attestation(
179 request: RoleAttestationRequest,
180 ) -> Result<SignedRoleAttestation, Error> {
181 let request = metadata::with_root_attestation_request_metadata(request);
182 let response = Self::request_role_attestation_remote(request).await?;
183
184 match response {
185 RootCapabilityResponse::RoleAttestationIssued(response) => Ok(response),
186 _ => Err(Error::internal(
187 "invalid root response type for role attestation request",
188 )),
189 }
190 }
191
192 pub async fn attestation_key_set() -> Result<AttestationKeySet, Error> {
194 DelegatedTokenOps::attestation_key_set()
195 .await
196 .map_err(Self::map_delegation_error)
197 }
198
199 pub async fn prewarm_root_key_material() -> Result<(), Error> {
201 EnvOps::require_root().map_err(Error::from)?;
202 DelegatedTokenOps::prewarm_root_key_material()
203 .await
204 .map_err(|err| {
205 log!(Topic::Auth, Warn, "root auth key prewarm failed: {err}");
206 Self::map_delegation_error(err)
207 })
208 }
209
210 pub fn replace_attestation_key_set(key_set: AttestationKeySet) {
212 DelegatedTokenOps::replace_attestation_key_set(key_set);
213 }
214
215 pub async fn verify_role_attestation(
217 attestation: &SignedRoleAttestation,
218 min_accepted_epoch: u64,
219 ) -> Result<(), Error> {
220 let configured_min_accepted_epoch = ConfigOps::role_attestation_config()
221 .map_err(Error::from)?
222 .min_accepted_epoch_by_role
223 .get(attestation.payload.role.as_str())
224 .copied();
225 let min_accepted_epoch = verify_flow::resolve_min_accepted_epoch(
226 min_accepted_epoch,
227 configured_min_accepted_epoch,
228 );
229
230 let caller = IcOps::msg_caller();
231 let self_pid = IcOps::canister_self();
232 let now_secs = IcOps::now_secs();
233 let verifier_subnet = Some(EnvOps::subnet_pid().map_err(Error::from)?);
234 let root_pid = EnvOps::root_pid().map_err(Error::from)?;
235
236 let verify = || {
237 DelegatedTokenOps::verify_role_attestation_cached(
238 attestation,
239 caller,
240 self_pid,
241 verifier_subnet,
242 now_secs,
243 min_accepted_epoch,
244 )
245 .map(|_| ())
246 };
247 let refresh = || async {
248 let key_set: AttestationKeySet =
249 RpcOps::call_rpc_result(root_pid, protocol::CANIC_ATTESTATION_KEY_SET, ()).await?;
250 DelegatedTokenOps::replace_attestation_key_set(key_set);
251 Ok(())
252 };
253
254 match verify_flow::verify_role_attestation_with_single_refresh(verify, refresh).await {
255 Ok(()) => Ok(()),
256 Err(verify_flow::RoleAttestationVerifyFlowError::Initial(err)) => {
257 verify_flow::record_attestation_verifier_rejection(&err);
258 verify_flow::log_attestation_verifier_rejection(
259 &err,
260 attestation,
261 caller,
262 self_pid,
263 "cached",
264 );
265 Err(Self::map_delegation_error(err.into()))
266 }
267 Err(verify_flow::RoleAttestationVerifyFlowError::Refresh { trigger, source }) => {
268 verify_flow::record_attestation_verifier_rejection(&trigger);
269 verify_flow::log_attestation_verifier_rejection(
270 &trigger,
271 attestation,
272 caller,
273 self_pid,
274 "cache_miss_refresh",
275 );
276 record_attestation_refresh_failed();
277 log!(
278 Topic::Auth,
279 Warn,
280 "role attestation refresh failed local={} caller={} key_id={} error={}",
281 self_pid,
282 caller,
283 attestation.key_id,
284 source
285 );
286 Err(Self::map_delegation_error(source))
287 }
288 Err(verify_flow::RoleAttestationVerifyFlowError::PostRefresh(err)) => {
289 verify_flow::record_attestation_verifier_rejection(&err);
290 verify_flow::log_attestation_verifier_rejection(
291 &err,
292 attestation,
293 caller,
294 self_pid,
295 "post_refresh",
296 );
297 Err(Self::map_delegation_error(err.into()))
298 }
299 }
300 }
301
302 fn delegated_auth_max_ttl_secs() -> Result<u64, Error> {
304 let cfg = ConfigOps::delegated_tokens_config().map_err(Error::from)?;
305 if !cfg.enabled {
306 return Err(Error::forbidden(Self::DELEGATED_TOKENS_DISABLED));
307 }
308
309 Ok(cfg
310 .max_ttl_secs
311 .unwrap_or(Self::MAX_DELEGATED_SESSION_TTL_SECS))
312 }
313}
314
315impl DelegationApi {
316 async fn request_delegation_remote(
318 request: DelegationProofIssueRequestV2,
319 ) -> Result<DelegationProofV2, Error> {
320 let root_pid = EnvOps::root_pid().map_err(Error::from)?;
321 RpcOps::call_rpc_result(root_pid, protocol::CANIC_REQUEST_DELEGATION_V2, request)
322 .await
323 .map_err(Self::map_delegation_error)
324 }
325
326 pub async fn request_role_attestation_root(
328 request: RoleAttestationRequest,
329 ) -> Result<SignedRoleAttestation, Error> {
330 let request = metadata::with_root_attestation_request_metadata(request);
331 let response = RootResponseWorkflow::response(RootRequest::issue_role_attestation(request))
332 .await
333 .map_err(Self::map_delegation_error)?;
334
335 match response {
336 RootCapabilityResponse::RoleAttestationIssued(response) => Ok(response),
337 _ => Err(Error::internal(
338 "invalid root response type for role attestation request",
339 )),
340 }
341 }
342
343 async fn request_role_attestation_remote(
345 request: RoleAttestationRequest,
346 ) -> Result<RootCapabilityResponse, Error> {
347 let root_pid = EnvOps::root_pid().map_err(Error::from)?;
348 RpcOps::call_rpc_result(root_pid, protocol::CANIC_REQUEST_ROLE_ATTESTATION, request)
349 .await
350 .map_err(Self::map_delegation_error)
351 }
352}