1use 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]);
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::Principal> for Bytes<29> {
52 fn from_domain(value: domain::Principal) -> Result<Self, Error> {
53 Self::new(value.as_slice().to_vec())
54 }
55 fn into_domain(self) -> Result<domain::Principal, Error> {
56 domain::Principal::try_from_slice(self.as_slice()).map_err(|_| Error::Malformed)
57 }
58}
59impl Field<domain::CanonicalId> for [u8; 32] {
60 fn from_domain(value: domain::CanonicalId) -> Result<Self, Error> {
61 Ok(*value.as_bytes())
62 }
63 fn into_domain(self) -> Result<domain::CanonicalId, Error> {
64 Ok(domain::CanonicalId::from_bytes(self))
65 }
66}
67impl Field<domain::AuthRole> for Text<MAX_LABEL_BYTES> {
68 fn from_domain(value: domain::AuthRole) -> Result<Self, Error> {
69 Self::new(value.as_str().to_owned())
70 }
71 fn into_domain(self) -> Result<domain::AuthRole, Error> {
72 self.as_str().parse().map_err(|_| Error::Malformed)
73 }
74}
75macro_rules! record {
78 ($name:ident { $($field:ident: $ty:ty),* $(,)? }) => {
79 #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
80#[cfg_attr(feature = "typescript", derive(ic_wire::Wire))]
81 pub struct $name { $(pub $field: $ty),* }
82 impl TryFrom<domain::$name> for $name {
83 type Error = Error;
84 fn try_from(value: domain::$name) -> Result<Self, Error> {
85 Ok(Self { $($field: Field::from_domain(value.$field)?),* })
86 }
87 }
88 impl TryFrom<$name> for domain::$name {
89 type Error = Error;
90 fn try_from(value: $name) -> Result<Self, Error> {
91 Ok(Self { $($field: Field::into_domain(value.$field)?),* })
92 }
93 }
94 impl Field<domain::$name> for $name {
95 fn from_domain(value: domain::$name) -> Result<Self, Error> { value.try_into() }
96 fn into_domain(self) -> Result<domain::$name, Error> { self.try_into() }
97 }
98 };
99}
100macro_rules! unit_enum {
101 ($name:ident { $($variant:ident),* $(,)? }) => {
102 #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
103#[cfg_attr(feature = "typescript", derive(ic_wire::Wire))]
104 pub enum $name { $($variant),* }
105 impl Field<domain::$name> for $name {
106 fn from_domain(value: domain::$name) -> Result<Self, Error> {
107 Ok(value.into())
108 }
109 fn into_domain(self) -> Result<domain::$name, Error> {
110 Ok(match self { $(Self::$variant => domain::$name::$variant),* })
111 }
112 }
113 impl From<domain::$name> for $name {
114 fn from(value: domain::$name) -> Self {
115 match value { $(domain::$name::$variant => Self::$variant),* }
116 }
117 }
118 };
119}
120macro_rules! tuple_enum {
121 ($name:ident { $($variant:ident($ty:ty)),* $(,)? }) => {
122 #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
123#[cfg_attr(feature = "typescript", derive(ic_wire::Wire))]
124 pub enum $name { $($variant($ty)),* }
125 impl Field<domain::$name> for $name {
126 fn from_domain(value: domain::$name) -> Result<Self, Error> {
127 Ok(match value { $(domain::$name::$variant(v) => Self::$variant(Field::from_domain(v)?)),* })
128 }
129 fn into_domain(self) -> Result<domain::$name, Error> {
130 Ok(match self { $(Self::$variant(v) => domain::$name::$variant(Field::into_domain(v)?)),* })
131 }
132 }
133 };
134}
135record!(AudienceId {
136 canonical_network_id: [u8; 32],
137 fleet_id: [u8; 32]
138});
139record!(DelegatedRoleGrant { target: Text<MAX_LABEL_BYTES>, scopes: List<Text<MAX_LABEL_BYTES>, MAX_SCOPES_PER_GRANT> });
140tuple_enum!(DelegationAudience {
141 Fleet(AudienceId)
142});
143unit_enum!(ChainKeyAlgorithm { EcdsaSecp256k1 });
144record!(ChainKeyKeyId { name: Text<128> });
145unit_enum!(IssuerProofAlgorithm {
146 IcCanisterSignatureV1
147});
148#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
149#[cfg_attr(feature = "typescript", derive(ic_wire::Wire))]
150pub enum IssuerProofBinding {
151 IcCanisterSignatureV1([u8; 32]),
152}
153impl Field<domain::IssuerProofBinding> for IssuerProofBinding {
154 fn from_domain(value: domain::IssuerProofBinding) -> Result<Self, Error> {
155 let domain::IssuerProofBinding::IcCanisterSignatureV1 { seed_hash } = value;
156 Ok(Self::IcCanisterSignatureV1(seed_hash))
157 }
158 fn into_domain(self) -> Result<domain::IssuerProofBinding, Error> {
159 let Self::IcCanisterSignatureV1(seed_hash) = self;
160 Ok(domain::IssuerProofBinding::IcCanisterSignatureV1 { seed_hash })
161 }
162}
163record!(IcCanisterSignatureProofV1 { signature_cbor: Bytes<131072>, public_key_der: Bytes<256> });
164record!(ChainKeyBatchHeaderV1 {
165 schema_version: u16, root_canister_id: Bytes<29>, batch_id: [u8; 32],
166 proof_epoch: u64, registry_epoch: u64, registry_hash: [u8; 32],
167 tree_root: [u8; 32], not_before_ns: u64, expires_at_ns: u64,
168 algorithm: ChainKeyAlgorithm, key_id: ChainKeyKeyId,
169 derivation_path_hash: [u8; 32], key_version: u64,
170});
171record!(ChainKeyDelegationCertV1 {
172 root_canister_id: Bytes<29>, issuer_canister_id: Bytes<29>, 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: Bytes<29>, issuer_pid: Bytes<29>, 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))]
214#[allow(
215 clippy::large_enum_variant,
216 reason = "Keep the bounded transport projection and explicit domain conversion"
217)]
218pub enum PortableProof {
219 Certified(CertifiedProof),
220 Ed25519(Bytes<64>),
221}
222impl Field<domain::PortableProof> for PortableProof {
223 fn from_domain(value: domain::PortableProof) -> Result<Self, Error> {
224 Ok(match value {
225 domain::PortableProof::Certified {
226 delegation,
227 issuer_signature,
228 } => Self::Certified(CertifiedProof {
229 delegation: (*delegation).try_into()?,
230 issuer_signature: issuer_signature.try_into()?,
231 }),
232 domain::PortableProof::Ed25519 { signature } => Self::Ed25519(Bytes::new(signature)?),
233 })
234 }
235 fn into_domain(self) -> Result<domain::PortableProof, Error> {
236 Ok(match self {
237 Self::Certified(value) => domain::PortableProof::Certified {
238 delegation: Box::new(value.delegation.try_into()?),
239 issuer_signature: value.issuer_signature.try_into()?,
240 },
241 Self::Ed25519(signature) => domain::PortableProof::Ed25519 {
242 signature: signature.into_vec(),
243 },
244 })
245 }
246}
247record!(PortableCredential { claims: Bytes<MAX_CLAIMS_BYTES>, proof: PortableProof });
248unit_enum!(AdmissionRefusal {
250 NotYetValid,
251 Expired,
252 InvalidCredential,
253 AuthorityUnavailable,
254 BeforeReset,
255 ScopeRejected,
256 ReplayConflict,
257 Revoked,
258 Capacity,
259 Throttled,
260 Unavailable
261});
262unit_enum!(LogoutUnavailable {
263 Capacity,
264 Storage,
265 Clock,
266 ResetPending
267});
268#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
269#[cfg_attr(feature = "typescript", derive(ic_wire::Wire))]
270pub enum LogoutResult {
271 Revoked(u64),
272 InvalidCaller,
273 Unavailable(LogoutUnavailable),
274}
275impl From<domain::LogoutResult> for LogoutResult {
276 fn from(value: domain::LogoutResult) -> Self {
277 match value {
278 domain::LogoutResult::Revoked { deny_until_ns } => Self::Revoked(deny_until_ns),
279 domain::LogoutResult::InvalidCaller => Self::InvalidCaller,
280 domain::LogoutResult::Unavailable(reason) => Self::Unavailable(reason.into()),
281 }
282 }
283}