Skip to main content

ic_auth_wire/
model.rs

1//! Bounded transport projections. Conversion is structural, never verification.
2use ic_auth_protocol_types as domain;
3use ic_auth_protocol_types::limits::{
4    MAX_CLAIMS_BYTES, MAX_GRANTS, MAX_LABEL_BYTES, MAX_SCOPES_PER_GRANT,
5};
6use ic_wire::{Bytes, Error, List, Text};
7
8trait Field<D>: Sized {
9    fn from_domain(value: D) -> Result<Self, Error>;
10    fn into_domain(self) -> Result<D, Error>;
11}
12macro_rules! scalar {
13    ($($ty:ty),*) => {$(impl Field<$ty> for $ty {
14        fn from_domain(value: $ty) -> Result<Self, Error> { Ok(value) }
15        fn into_domain(self) -> Result<$ty, Error> { Ok(self) }
16    })*};
17}
18scalar!(u16, u64, [u8; 16], [u8; 32], domain::Principal);
19impl<const N: usize> Field<Vec<u8>> for Bytes<N> {
20    fn from_domain(value: Vec<u8>) -> Result<Self, Error> {
21        Self::new(value)
22    }
23    fn into_domain(self) -> Result<Vec<u8>, Error> {
24        Ok(self.into_vec())
25    }
26}
27impl<const N: usize> Field<String> for Text<N> {
28    fn from_domain(value: String) -> Result<Self, Error> {
29        Self::new(value)
30    }
31    fn into_domain(self) -> Result<String, Error> {
32        Ok(self.into_string())
33    }
34}
35impl<D, W: Field<D>, const N: usize> Field<Vec<D>> for List<W, N> {
36    fn from_domain(value: Vec<D>) -> Result<Self, Error> {
37        if value.len() > N {
38            return Err(Error::Limit);
39        }
40        Self::new(
41            value
42                .into_iter()
43                .map(W::from_domain)
44                .collect::<Result<_, _>>()?,
45        )
46    }
47    fn into_domain(self) -> Result<Vec<D>, Error> {
48        self.into_vec().into_iter().map(W::into_domain).collect()
49    }
50}
51impl Field<domain::CanonicalId> for [u8; 32] {
52    fn from_domain(value: domain::CanonicalId) -> Result<Self, Error> {
53        Ok(*value.as_bytes())
54    }
55    fn into_domain(self) -> Result<domain::CanonicalId, Error> {
56        Ok(domain::CanonicalId::from_bytes(self))
57    }
58}
59impl Field<domain::AuthRole> for Text<MAX_LABEL_BYTES> {
60    fn from_domain(value: domain::AuthRole) -> Result<Self, Error> {
61        Self::new(value.into_string())
62    }
63    fn into_domain(self) -> Result<domain::AuthRole, Error> {
64        domain::AuthRole::try_from(self.into_string()).map_err(|_| Error::Malformed)
65    }
66}
67// One declaration owns each Serde transport field and both typed
68// projections. Rust checks the domain field names/types; no intermediate codec.
69macro_rules! record {
70    ($name:ident { $($field:ident: $ty:ty),* $(,)? }) => {
71        #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
72#[cfg_attr(feature = "typescript", derive(ic_wire::Wire))]
73        pub struct $name { $(pub $field: $ty),* }
74        impl TryFrom<domain::$name> for $name {
75            type Error = Error;
76            fn try_from(value: domain::$name) -> Result<Self, Error> {
77                Ok(Self { $($field: Field::from_domain(value.$field)?),* })
78            }
79        }
80        impl TryFrom<$name> for domain::$name {
81            type Error = Error;
82            fn try_from(value: $name) -> Result<Self, Error> {
83                Ok(Self { $($field: Field::into_domain(value.$field)?),* })
84            }
85        }
86        impl Field<domain::$name> for $name {
87            fn from_domain(value: domain::$name) -> Result<Self, Error> { value.try_into() }
88            fn into_domain(self) -> Result<domain::$name, Error> { self.try_into() }
89        }
90    };
91}
92macro_rules! unit_enum {
93    ($name:ident { $($variant:ident),* $(,)? }) => {
94        #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
95#[cfg_attr(feature = "typescript", derive(ic_wire::Wire))]
96        pub enum $name { $($variant),* }
97        impl Field<domain::$name> for $name {
98            fn from_domain(value: domain::$name) -> Result<Self, Error> {
99                Ok(value.into())
100            }
101            fn into_domain(self) -> Result<domain::$name, Error> {
102                Ok(match self { $(Self::$variant => domain::$name::$variant),* })
103            }
104        }
105        impl From<domain::$name> for $name {
106            fn from(value: domain::$name) -> Self {
107                match value { $(domain::$name::$variant => Self::$variant),* }
108            }
109        }
110    };
111}
112macro_rules! tuple_enum {
113    ($name:ident { $($variant:ident($ty:ty)),* $(,)? }) => {
114        #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
115#[cfg_attr(feature = "typescript", derive(ic_wire::Wire))]
116        pub enum $name { $($variant($ty)),* }
117        impl Field<domain::$name> for $name {
118            fn from_domain(value: domain::$name) -> Result<Self, Error> {
119                Ok(match value { $(domain::$name::$variant(v) => Self::$variant(Field::from_domain(v)?)),* })
120            }
121            fn into_domain(self) -> Result<domain::$name, Error> {
122                Ok(match self { $(Self::$variant(v) => domain::$name::$variant(Field::into_domain(v)?)),* })
123            }
124        }
125    };
126}
127record!(AudienceId {
128    canonical_network_id: [u8; 32],
129    fleet_id: [u8; 32]
130});
131record!(DelegatedRoleGrant { target: Text<MAX_LABEL_BYTES>, scopes: List<Text<MAX_LABEL_BYTES>, MAX_SCOPES_PER_GRANT> });
132tuple_enum!(DelegationAudience {
133    Fleet(AudienceId)
134});
135unit_enum!(ChainKeyAlgorithm { EcdsaSecp256k1 });
136record!(ChainKeyKeyId { name: Text<128> });
137unit_enum!(IssuerProofAlgorithm {
138    IcCanisterSignatureV1
139});
140#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
141#[cfg_attr(feature = "typescript", derive(ic_wire::Wire))]
142pub enum IssuerProofBinding {
143    IcCanisterSignatureV1([u8; 32]),
144}
145impl Field<domain::IssuerProofBinding> for IssuerProofBinding {
146    fn from_domain(value: domain::IssuerProofBinding) -> Result<Self, Error> {
147        let domain::IssuerProofBinding::IcCanisterSignatureV1 { seed_hash } = value;
148        Ok(Self::IcCanisterSignatureV1(seed_hash))
149    }
150    fn into_domain(self) -> Result<domain::IssuerProofBinding, Error> {
151        let Self::IcCanisterSignatureV1(seed_hash) = self;
152        Ok(domain::IssuerProofBinding::IcCanisterSignatureV1 { seed_hash })
153    }
154}
155record!(IcCanisterSignatureProofV1 { signature_cbor: Bytes<131072>, public_key_der: Bytes<256> });
156record!(ChainKeyBatchHeaderV1 {
157    schema_version: u16,
158    root_canister_id: domain::Principal,
159    batch_id: [u8; 32],
160    proof_epoch: u64,
161    registry_epoch: u64,
162    registry_hash: [u8; 32],
163    tree_root: [u8; 32],
164    not_before_ns: u64,
165    expires_at_ns: u64,
166    algorithm: ChainKeyAlgorithm,
167    key_id: ChainKeyKeyId,
168    derivation_path_hash: [u8; 32],
169    key_version: u64,
170});
171record!(ChainKeyDelegationCertV1 {
172    root_canister_id: domain::Principal, issuer_canister_id: domain::Principal, proof_epoch: u64,
173    issuer_proof_algorithm: IssuerProofAlgorithm, issuer_proof_binding_hash: [u8; 32],
174    issuer_proof_binding: IssuerProofBinding, max_token_ttl_ns: u64,
175    audience: DelegationAudience, grants: List<DelegatedRoleGrant, MAX_GRANTS>,
176    not_before_ns: u64, expires_at_ns: u64, registry_epoch: u64, registry_hash: [u8; 32],
177});
178record!(ChainKeyRootSignatureV1 {
179    algorithm: ChainKeyAlgorithm, key_id: ChainKeyKeyId,
180    derivation_path: List<Bytes<256>, 32>, public_key: Bytes<65>, signature: Bytes<64>,
181});
182tuple_enum!(ChainKeyBatchWitnessStepV1 {
183    LeftSibling([u8; 32]),
184    RightSibling([u8; 32])
185});
186record!(ChainKeyBatchWitnessV1 { steps: List<ChainKeyBatchWitnessStepV1, 64> });
187record!(IcChainKeyBatchSignatureProofV1 {
188    header: ChainKeyBatchHeaderV1,
189    delegation_cert: ChainKeyDelegationCertV1,
190    issuer_witness: ChainKeyBatchWitnessV1,
191    signature: ChainKeyRootSignatureV1,
192});
193tuple_enum!(RootProof {
194    IcChainKeyBatchSignatureV1(IcChainKeyBatchSignatureProofV1)
195});
196record!(DelegationCert {
197    root_pid: domain::Principal, issuer_pid: domain::Principal, issuer_proof_alg: IssuerProofAlgorithm,
198    issuer_proof_binding_hash: [u8; 32], issuer_proof_binding: IssuerProofBinding,
199    issued_at_ns: u64, not_before_ns: u64, expires_at_ns: u64,
200    max_token_ttl_ns: u64, aud: DelegationAudience, grants: List<DelegatedRoleGrant, MAX_GRANTS>,
201});
202record!(DelegationProof {
203    cert: DelegationCert,
204    root_proof: RootProof
205});
206#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
207#[cfg_attr(feature = "typescript", derive(ic_wire::Wire))]
208pub struct CertifiedProof {
209    pub delegation: DelegationProof,
210    pub issuer_signature: IcCanisterSignatureProofV1,
211}
212#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
213#[cfg_attr(feature = "typescript", derive(ic_wire::Wire))]
214pub enum PortableProof {
215    Certified(Box<CertifiedProof>),
216    Ed25519(Bytes<64>),
217}
218impl Field<domain::PortableProof> for PortableProof {
219    fn from_domain(value: domain::PortableProof) -> Result<Self, Error> {
220        Ok(match value {
221            domain::PortableProof::Certified {
222                delegation,
223                issuer_signature,
224            } => Self::Certified(Box::new(CertifiedProof {
225                delegation: (*delegation).try_into()?,
226                issuer_signature: issuer_signature.try_into()?,
227            })),
228            domain::PortableProof::Ed25519 { signature } => Self::Ed25519(Bytes::new(signature)?),
229        })
230    }
231    fn into_domain(self) -> Result<domain::PortableProof, Error> {
232        Ok(match self {
233            Self::Certified(value) => domain::PortableProof::Certified {
234                delegation: Box::new(value.delegation.try_into()?),
235                issuer_signature: value.issuer_signature.try_into()?,
236            },
237            Self::Ed25519(signature) => domain::PortableProof::Ed25519 {
238                signature: signature.into_vec(),
239            },
240        })
241    }
242}
243record!(PortableCredential { claims: Bytes<MAX_CLAIMS_BYTES>, proof: PortableProof });
244// Admission diagnostics are independent of proof validity and retry policy.
245unit_enum!(AdmissionRefusal {
246    NotYetValid,
247    Expired,
248    InvalidCredential,
249    AuthorityUnavailable,
250    BeforeReset,
251    ScopeRejected,
252    ReplayConflict,
253    Revoked,
254    Capacity,
255    Throttled,
256    Unavailable
257});
258unit_enum!(LogoutUnavailable {
259    Capacity,
260    Storage,
261    Clock,
262    ResetPending
263});
264#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
265#[cfg_attr(feature = "typescript", derive(ic_wire::Wire))]
266pub enum LogoutResult {
267    Revoked(u64),
268    InvalidCaller,
269    Unavailable(LogoutUnavailable),
270}
271impl From<domain::LogoutResult> for LogoutResult {
272    fn from(value: domain::LogoutResult) -> Self {
273        match value {
274            domain::LogoutResult::Revoked { deny_until_ns } => Self::Revoked(deny_until_ns),
275            domain::LogoutResult::InvalidCaller => Self::InvalidCaller,
276            domain::LogoutResult::Unavailable(reason) => Self::Unavailable(reason.into()),
277        }
278    }
279}