Skip to main content

ic_auth/portable/
verified.rs

1#[cfg(any(
2    feature = "portable-signature-verification",
3    feature = "certified-proof-verification"
4))]
5use super::PortableClaimsContext;
6use crate::session_request::{SessionRequest, SessionRequestError, SessionTtlPolicy};
7use ic_auth_protocol_types::{AuthRole, PortableClaims, Principal};
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    browser_caller: Principal,
25    verified_at_ns: u64,
26    authority_expires_at_ns: u64,
27    enrollment_expires_at_ns: u64,
28}
29
30impl VerifiedPortableClaims {
31    #[cfg(any(
32        feature = "portable-signature-verification",
33        feature = "certified-proof-verification"
34    ))]
35    pub(crate) fn new(
36        claims: PortableClaims,
37        fingerprint: [u8; 32],
38        browser_caller: Principal,
39        context: &PortableClaimsContext<'_>,
40    ) -> Self {
41        Self {
42            claims,
43            fingerprint,
44            browser_caller,
45            verified_at_ns: context.authority.now_ns,
46            authority_expires_at_ns: context.authority.authority_expires_at_ns,
47            enrollment_expires_at_ns: context.authority.enrollment_expires_at_ns,
48        }
49    }
50
51    /// Exclusive lease deadline for admission at this verification snapshot.
52    /// Proof expiry limits new admission, not the resulting lease. The signed
53    /// login deadline and protected key/enrollment deadlines cap the lease.
54    /// Persist this result once; refreshing enrollment never extends stored leases.
55    /// The role comes from protected receiver configuration, never the request.
56    /// Requested scopes must narrow that role's grant; other roles cannot supply
57    /// missing scopes. This does not check replay, revocation or current authority
58    /// after verification, and does not create or authorize a session.
59    pub fn session_expires_at_ns(
60        &self,
61        request: &SessionRequest,
62        role: &AuthRole,
63        policy: SessionTtlPolicy,
64    ) -> Result<u64, SessionRequestError> {
65        let requested = request.evaluate(&self.claims.grants, role, self.verified_at_ns, policy)?;
66        Ok(self.clamp_session_expiry(requested))
67    }
68
69    // Only the engine supplies a request already evaluated for this same
70    // immutable claim set and protected snapshot, before expensive verification.
71    pub(crate) fn clamp_session_expiry(&self, requested: u64) -> u64 {
72        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(crate) fn browser_caller(&self) -> Principal {
88        self.browser_caller
89    }
90
91    pub fn verified_at_ns(&self) -> u64 {
92        self.verified_at_ns
93    }
94}