Skip to main content

ic_auth/portable/
verified.rs

1#[cfg(any(
2    feature = "portable-signature-verification",
3    feature = "token-verification"
4))]
5use super::PortableClaimsContext;
6use crate::session_request::{SessionRequest, SessionRequestError};
7use ic_auth_protocol_types::{AuthRole, PortableClaims};
8
9/// Claims authenticated by one complete portable proof verifier at a host clock.
10/// Read-only and not deserializable; decoding or editing claims cannot forge this
11/// result. It is not a session, enrollment approval or resource authorization.
12/// Consume it under the same host transaction/authority snapshot as verification;
13/// matching time alone does not establish that authority is still current.
14///
15/// ```compile_fail
16/// use ic_auth::portable::VerifiedPortableClaims;
17/// use ic_auth_protocol_types::PortableClaims;
18/// fn forge(claims: PortableClaims) -> VerifiedPortableClaims { claims.into() }
19/// ```
20#[derive(Clone, Debug, Eq, PartialEq)]
21pub struct VerifiedPortableClaims {
22    claims: PortableClaims,
23    fingerprint: [u8; 32],
24    verified_at_ns: u64,
25    authority_expires_at_ns: u64,
26    enrollment_expires_at_ns: u64,
27}
28
29impl VerifiedPortableClaims {
30    #[cfg(any(
31        feature = "portable-signature-verification",
32        feature = "token-verification"
33    ))]
34    pub(crate) fn new(
35        claims: PortableClaims,
36        fingerprint: [u8; 32],
37        context: &PortableClaimsContext<'_>,
38    ) -> Self {
39        Self {
40            claims,
41            fingerprint,
42            verified_at_ns: context.now_ns,
43            authority_expires_at_ns: context.authority_expires_at_ns,
44            enrollment_expires_at_ns: context.enrollment_expires_at_ns,
45        }
46    }
47
48    /// Exclusive lease deadline for admission at this verification snapshot.
49    /// Proof expiry limits new admission, not the resulting lease. The signed
50    /// login deadline and protected key/enrollment deadlines cap the lease.
51    /// Persist this result once; refreshing enrollment never extends stored leases.
52    /// The role comes from protected receiver configuration, never the request.
53    /// Requested scopes must narrow that role's grant; other roles cannot supply
54    /// missing scopes. This does not check replay, revocation or current authority
55    /// after verification, and does not create or authorize a session.
56    pub fn session_expires_at_ns(
57        &self,
58        request: &SessionRequest,
59        role: &AuthRole,
60        default_ttl_secs: u64,
61        max_ttl_secs: u64,
62    ) -> Result<u64, SessionRequestError> {
63        let grant = self
64            .claims
65            .grants
66            .iter()
67            .find(|grant| &grant.target == role)
68            .ok_or(SessionRequestError::ScopeRejected)?;
69        request.check_granted_scopes(&grant.scopes)?;
70        let requested =
71            request.expires_at_ns(self.verified_at_ns, default_ttl_secs, max_ttl_secs)?;
72        Ok(requested
73            .min(self.claims.login_deadline_ns)
74            .min(self.authority_expires_at_ns)
75            .min(self.enrollment_expires_at_ns))
76    }
77
78    pub fn claims(&self) -> &PortableClaims {
79        &self.claims
80    }
81
82    /// Canonical claims digest, independent of alternate proof/signature bytes.
83    pub fn fingerprint(&self) -> [u8; 32] {
84        self.fingerprint
85    }
86
87    pub fn verified_at_ns(&self) -> u64 {
88        self.verified_at_ns
89    }
90}