1use crate::{
2 dto::{
3 auth::{
4 AttestationKeySet, DelegatedToken, DelegatedTokenIssueRequest,
5 DelegatedTokenMintRequest, DelegationProof, DelegationProofIssueRequest,
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, SignDelegatedTokenInput, SignDelegationProofInput,
17 VerifyDelegatedTokenRuntimeInput,
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";
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(request: DelegatedTokenIssueRequest) -> Result<DelegatedToken, Error> {
71 DelegatedTokenOps::sign_token(SignDelegatedTokenInput {
72 proof: request.proof,
73 subject: request.subject,
74 audience: request.aud,
75 scopes: request.scopes,
76 ttl_secs: request.ttl_secs,
77 nonce: request.nonce,
78 })
79 .await
80 .map_err(Self::map_delegation_error)
81 }
82
83 pub async fn mint_token(request: DelegatedTokenMintRequest) -> Result<DelegatedToken, Error> {
85 let proof = Self::request_delegation(DelegationProofIssueRequest {
86 shard_pid: IcOps::canister_self(),
87 scopes: request.scopes.clone(),
88 aud: request.aud.clone(),
89 cert_ttl_secs: request.cert_ttl_secs,
90 })
91 .await?;
92
93 Self::issue_token(DelegatedTokenIssueRequest {
94 proof,
95 subject: request.subject,
96 aud: request.aud,
97 scopes: request.scopes,
98 ttl_secs: request.token_ttl_secs,
99 nonce: request.nonce,
100 })
101 .await
102 }
103
104 pub fn verify_token(
106 token: &DelegatedToken,
107 max_cert_ttl_secs: u64,
108 max_token_ttl_secs: u64,
109 required_scopes: &[String],
110 now_secs: u64,
111 ) -> Result<(), Error> {
112 DelegatedTokenOps::verify_token(VerifyDelegatedTokenRuntimeInput {
113 token,
114 max_cert_ttl_secs,
115 max_token_ttl_secs,
116 required_scopes,
117 now_secs,
118 })
119 .map(|_| ())
120 .map_err(Self::map_delegation_error)
121 }
122
123 pub async fn request_delegation(
125 request: DelegationProofIssueRequest,
126 ) -> Result<DelegationProof, Error> {
127 Self::request_delegation_remote(request).await
128 }
129
130 pub async fn issue_delegation_proof(
132 request: DelegationProofIssueRequest,
133 ) -> Result<DelegationProof, Error> {
134 EnvOps::require_root().map_err(Error::from)?;
135 let max_cert_ttl_secs = Self::delegated_auth_max_ttl_secs()?;
136 let max_token_ttl_secs = request.cert_ttl_secs.min(max_cert_ttl_secs);
137 DelegatedTokenOps::sign_delegation_proof(SignDelegationProofInput {
138 audience: request.aud,
139 scopes: request.scopes,
140 shard_pid: request.shard_pid,
141 cert_ttl_secs: request.cert_ttl_secs,
142 max_token_ttl_secs,
143 max_cert_ttl_secs,
144 issued_at: IcOps::now_secs(),
145 })
146 .await
147 .map_err(Self::map_delegation_error)
148 }
149
150 pub async fn request_role_attestation(
152 request: RoleAttestationRequest,
153 ) -> Result<SignedRoleAttestation, Error> {
154 let request = metadata::with_root_attestation_request_metadata(request);
155 let response = Self::request_role_attestation_remote(request).await?;
156
157 match response {
158 RootCapabilityResponse::RoleAttestationIssued(response) => Ok(response),
159 _ => Err(Error::internal(
160 "invalid root response type for role attestation request",
161 )),
162 }
163 }
164
165 pub async fn attestation_key_set() -> Result<AttestationKeySet, Error> {
167 DelegatedTokenOps::attestation_key_set()
168 .await
169 .map_err(Self::map_delegation_error)
170 }
171
172 pub async fn prewarm_root_key_material() -> Result<(), Error> {
174 EnvOps::require_root().map_err(Error::from)?;
175 DelegatedTokenOps::prewarm_root_key_material()
176 .await
177 .map_err(|err| {
178 log!(Topic::Auth, Warn, "root auth key prewarm failed: {err}");
179 Self::map_delegation_error(err)
180 })
181 }
182
183 pub fn replace_attestation_key_set(key_set: AttestationKeySet) {
185 DelegatedTokenOps::replace_attestation_key_set(key_set);
186 }
187
188 pub async fn verify_role_attestation(
190 attestation: &SignedRoleAttestation,
191 min_accepted_epoch: u64,
192 ) -> Result<(), Error> {
193 let configured_min_accepted_epoch = ConfigOps::role_attestation_config()
194 .map_err(Error::from)?
195 .min_accepted_epoch_by_role
196 .get(attestation.payload.role.as_str())
197 .copied();
198 let min_accepted_epoch = verify_flow::resolve_min_accepted_epoch(
199 min_accepted_epoch,
200 configured_min_accepted_epoch,
201 );
202
203 let caller = IcOps::msg_caller();
204 let self_pid = IcOps::canister_self();
205 let now_secs = IcOps::now_secs();
206 let verifier_subnet = Some(EnvOps::subnet_pid().map_err(Error::from)?);
207 let root_pid = EnvOps::root_pid().map_err(Error::from)?;
208
209 let verify = || {
210 DelegatedTokenOps::verify_role_attestation_cached(
211 attestation,
212 caller,
213 self_pid,
214 verifier_subnet,
215 now_secs,
216 min_accepted_epoch,
217 )
218 .map(|_| ())
219 };
220 let refresh = || async {
221 let key_set: AttestationKeySet =
222 RpcOps::call_rpc_result(root_pid, protocol::CANIC_ATTESTATION_KEY_SET, ()).await?;
223 DelegatedTokenOps::replace_attestation_key_set(key_set);
224 Ok(())
225 };
226
227 match verify_flow::verify_role_attestation_with_single_refresh(verify, refresh).await {
228 Ok(()) => Ok(()),
229 Err(verify_flow::RoleAttestationVerifyFlowError::Initial(err)) => {
230 verify_flow::record_attestation_verifier_rejection(&err);
231 verify_flow::log_attestation_verifier_rejection(
232 &err,
233 attestation,
234 caller,
235 self_pid,
236 "cached",
237 );
238 Err(Self::map_delegation_error(err.into()))
239 }
240 Err(verify_flow::RoleAttestationVerifyFlowError::Refresh { trigger, source }) => {
241 verify_flow::record_attestation_verifier_rejection(&trigger);
242 verify_flow::log_attestation_verifier_rejection(
243 &trigger,
244 attestation,
245 caller,
246 self_pid,
247 "cache_miss_refresh",
248 );
249 record_attestation_refresh_failed();
250 log!(
251 Topic::Auth,
252 Warn,
253 "role attestation refresh failed local={} caller={} key_id={} error={}",
254 self_pid,
255 caller,
256 attestation.key_id,
257 source
258 );
259 Err(Self::map_delegation_error(source))
260 }
261 Err(verify_flow::RoleAttestationVerifyFlowError::PostRefresh(err)) => {
262 verify_flow::record_attestation_verifier_rejection(&err);
263 verify_flow::log_attestation_verifier_rejection(
264 &err,
265 attestation,
266 caller,
267 self_pid,
268 "post_refresh",
269 );
270 Err(Self::map_delegation_error(err.into()))
271 }
272 }
273 }
274
275 fn delegated_auth_max_ttl_secs() -> Result<u64, Error> {
277 let cfg = ConfigOps::delegated_tokens_config().map_err(Error::from)?;
278 if !cfg.enabled {
279 return Err(Error::forbidden(Self::DELEGATED_TOKENS_DISABLED));
280 }
281
282 Ok(cfg
283 .max_ttl_secs
284 .unwrap_or(Self::MAX_DELEGATED_SESSION_TTL_SECS))
285 }
286}
287
288impl DelegationApi {
289 async fn request_delegation_remote(
291 request: DelegationProofIssueRequest,
292 ) -> Result<DelegationProof, Error> {
293 let root_pid = EnvOps::root_pid().map_err(Error::from)?;
294 RpcOps::call_rpc_result(root_pid, protocol::CANIC_REQUEST_DELEGATION, request)
295 .await
296 .map_err(Self::map_delegation_error)
297 }
298
299 pub async fn request_role_attestation_root(
301 request: RoleAttestationRequest,
302 ) -> Result<SignedRoleAttestation, Error> {
303 let request = metadata::with_root_attestation_request_metadata(request);
304 let response = RootResponseWorkflow::response(RootRequest::issue_role_attestation(request))
305 .await
306 .map_err(Self::map_delegation_error)?;
307
308 match response {
309 RootCapabilityResponse::RoleAttestationIssued(response) => Ok(response),
310 _ => Err(Error::internal(
311 "invalid root response type for role attestation request",
312 )),
313 }
314 }
315
316 async fn request_role_attestation_remote(
318 request: RoleAttestationRequest,
319 ) -> Result<RootCapabilityResponse, Error> {
320 let root_pid = EnvOps::root_pid().map_err(Error::from)?;
321 RpcOps::call_rpc_result(root_pid, protocol::CANIC_REQUEST_ROLE_ATTESTATION, request)
322 .await
323 .map_err(Self::map_delegation_error)
324 }
325}