1use ic_auth_protocol_types as domain;
3use ic_wire::{Bytes, Error, List, Text};
4
5trait Field<D>: Sized {
6 fn from_domain(value: D) -> Result<Self, Error>;
7 fn into_domain(self) -> Result<D, Error>;
8}
9macro_rules! scalar {
10 ($($ty:ty),*) => {$(impl Field<$ty> for $ty {
11 fn from_domain(value: $ty) -> Result<Self, Error> { Ok(value) }
12 fn into_domain(self) -> Result<$ty, Error> { Ok(self) }
13 })*};
14}
15scalar!(u16, u64, [u8; 16], [u8; 32]);
16impl<const N: usize> Field<Vec<u8>> for Bytes<N> {
17 fn from_domain(value: Vec<u8>) -> Result<Self, Error> {
18 Self::new(value)
19 }
20 fn into_domain(self) -> Result<Vec<u8>, Error> {
21 Ok(self.into_vec())
22 }
23}
24impl<const N: usize> Field<String> for Text<N> {
25 fn from_domain(value: String) -> Result<Self, Error> {
26 Self::new(value)
27 }
28 fn into_domain(self) -> Result<String, Error> {
29 Ok(self.into_string())
30 }
31}
32impl<D, W: Field<D>, const N: usize> Field<Vec<D>> for List<W, N> {
33 fn from_domain(value: Vec<D>) -> Result<Self, Error> {
34 if value.len() > N {
35 return Err(Error::Limit);
36 }
37 Self::new(
38 value
39 .into_iter()
40 .map(W::from_domain)
41 .collect::<Result<_, _>>()?,
42 )
43 }
44 fn into_domain(self) -> Result<Vec<D>, Error> {
45 self.into_vec().into_iter().map(W::into_domain).collect()
46 }
47}
48impl Field<domain::Principal> for Bytes<29> {
49 fn from_domain(value: domain::Principal) -> Result<Self, Error> {
50 Self::new(value.as_slice().to_vec())
51 }
52 fn into_domain(self) -> Result<domain::Principal, Error> {
53 domain::Principal::try_from_slice(self.as_slice()).map_err(|_| Error::Malformed)
54 }
55}
56impl Field<domain::CanonicalId> for [u8; 32] {
57 fn from_domain(value: domain::CanonicalId) -> Result<Self, Error> {
58 Ok(*value.as_bytes())
59 }
60 fn into_domain(self) -> Result<domain::CanonicalId, Error> {
61 Ok(domain::CanonicalId::from_bytes(self))
62 }
63}
64impl Field<domain::AuthRole> for Text<64> {
65 fn from_domain(value: domain::AuthRole) -> Result<Self, Error> {
66 Self::new(value.as_str().to_owned())
67 }
68 fn into_domain(self) -> Result<domain::AuthRole, Error> {
69 self.as_str().parse().map_err(|_| Error::Malformed)
70 }
71}
72macro_rules! record {
75 ($name:ident { $($field:ident: $ty:ty),* $(,)? }) => {
76 #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
77#[cfg_attr(feature = "typescript", derive(ic_wire::Wire))]
78 pub struct $name { $(pub $field: $ty),* }
79 impl TryFrom<domain::$name> for $name {
80 type Error = Error;
81 fn try_from(value: domain::$name) -> Result<Self, Error> {
82 Ok(Self { $($field: Field::from_domain(value.$field)?),* })
83 }
84 }
85 impl TryFrom<$name> for domain::$name {
86 type Error = Error;
87 fn try_from(value: $name) -> Result<Self, Error> {
88 Ok(Self { $($field: Field::into_domain(value.$field)?),* })
89 }
90 }
91 impl Field<domain::$name> for $name {
92 fn from_domain(value: domain::$name) -> Result<Self, Error> { value.try_into() }
93 fn into_domain(self) -> Result<domain::$name, Error> { self.try_into() }
94 }
95 };
96}
97macro_rules! unit_enum {
98 ($name:ident { $($variant:ident),* $(,)? }) => {
99 #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
100#[cfg_attr(feature = "typescript", derive(ic_wire::Wire))]
101 pub enum $name { $($variant),* }
102 impl Field<domain::$name> for $name {
103 fn from_domain(value: domain::$name) -> Result<Self, Error> {
104 Ok(match value { $(domain::$name::$variant => Self::$variant),* })
105 }
106 fn into_domain(self) -> Result<domain::$name, Error> {
107 Ok(match self { $(Self::$variant => domain::$name::$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<64>, scopes: List<Text<64>, 32> });
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, root_canister_id: Bytes<29>, batch_id: [u8; 32],
158 proof_epoch: u64, registry_epoch: u64, registry_hash: [u8; 32],
159 tree_root: [u8; 32], not_before_ns: u64, expires_at_ns: u64,
160 algorithm: ChainKeyAlgorithm, key_id: ChainKeyKeyId,
161 derivation_path_hash: [u8; 32], key_version: u64,
162});
163record!(ChainKeyDelegationCertV1 {
164 root_canister_id: Bytes<29>, issuer_canister_id: Bytes<29>, proof_epoch: u64,
165 issuer_proof_algorithm: IssuerProofAlgorithm, issuer_proof_binding_hash: [u8; 32],
166 issuer_proof_binding: IssuerProofBinding, max_token_ttl_ns: u64,
167 audience: DelegationAudience, grants: List<DelegatedRoleGrant, 16>,
168 not_before_ns: u64, expires_at_ns: u64, registry_epoch: u64, registry_hash: [u8; 32],
169});
170record!(ChainKeyRootSignatureV1 {
171 algorithm: ChainKeyAlgorithm, key_id: ChainKeyKeyId,
172 derivation_path: List<Bytes<256>, 32>, public_key: Bytes<65>, signature: Bytes<64>,
173});
174tuple_enum!(ChainKeyBatchWitnessStepV1 {
175 LeftSibling([u8; 32]),
176 RightSibling([u8; 32])
177});
178record!(ChainKeyBatchWitnessV1 { steps: List<ChainKeyBatchWitnessStepV1, 64> });
179record!(IcChainKeyBatchSignatureProofV1 {
180 header: ChainKeyBatchHeaderV1,
181 delegation_cert: ChainKeyDelegationCertV1,
182 issuer_witness: ChainKeyBatchWitnessV1,
183 signature: ChainKeyRootSignatureV1,
184});
185tuple_enum!(RootProof {
186 IcChainKeyBatchSignatureV1(IcChainKeyBatchSignatureProofV1)
187});
188record!(DelegationCert {
189 root_pid: Bytes<29>, issuer_pid: Bytes<29>, issuer_proof_alg: IssuerProofAlgorithm,
190 issuer_proof_binding_hash: [u8; 32], issuer_proof_binding: IssuerProofBinding,
191 issued_at_ns: u64, not_before_ns: u64, expires_at_ns: u64,
192 max_token_ttl_ns: u64, aud: DelegationAudience, grants: List<DelegatedRoleGrant, 16>,
193});
194record!(DelegationProof {
195 cert: DelegationCert,
196 root_proof: RootProof
197});
198#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
199#[cfg_attr(feature = "typescript", derive(ic_wire::Wire))]
200pub struct CertifiedProof {
201 pub delegation: DelegationProof,
202 pub issuer_signature: IcCanisterSignatureProofV1,
203}
204#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
205#[cfg_attr(feature = "typescript", derive(ic_wire::Wire))]
206#[allow(
207 clippy::large_enum_variant,
208 reason = "Keep the bounded transport projection and explicit domain conversion"
209)]
210pub enum PortableProof {
211 Certified(CertifiedProof),
212 Ed25519(Bytes<64>),
213}
214impl Field<domain::PortableProof> for PortableProof {
215 fn from_domain(value: domain::PortableProof) -> Result<Self, Error> {
216 Ok(match value {
217 domain::PortableProof::Certified {
218 delegation,
219 issuer_signature,
220 } => Self::Certified(CertifiedProof {
221 delegation: (*delegation).try_into()?,
222 issuer_signature: issuer_signature.try_into()?,
223 }),
224 domain::PortableProof::Ed25519 { signature } => Self::Ed25519(Bytes::new(signature)?),
225 })
226 }
227 fn into_domain(self) -> Result<domain::PortableProof, Error> {
228 Ok(match self {
229 Self::Certified(value) => domain::PortableProof::Certified {
230 delegation: Box::new(value.delegation.try_into()?),
231 issuer_signature: value.issuer_signature.try_into()?,
232 },
233 Self::Ed25519(signature) => domain::PortableProof::Ed25519 {
234 signature: signature.into_vec(),
235 },
236 })
237 }
238}
239record!(PortableCredential { claims: Bytes<65536>, proof: PortableProof });
240unit_enum!(AdmissionRefusal {
242 NotYetValid,
243 Expired,
244 InvalidCredential,
245 AuthorityUnavailable,
246 BeforeReset,
247 ScopeRejected,
248 ReplayConflict,
249 Revoked,
250 Capacity,
251 Throttled,
252 Unavailable
253});
254impl From<domain::AdmissionRefusal> for AdmissionRefusal {
255 fn from(value: domain::AdmissionRefusal) -> Self {
256 use domain::AdmissionRefusal as R;
257 match value {
258 R::NotYetValid => Self::NotYetValid,
259 R::Expired => Self::Expired,
260 R::InvalidCredential => Self::InvalidCredential,
261 R::AuthorityUnavailable => Self::AuthorityUnavailable,
262 R::BeforeReset => Self::BeforeReset,
263 R::ScopeRejected => Self::ScopeRejected,
264 R::ReplayConflict => Self::ReplayConflict,
265 R::Revoked => Self::Revoked,
266 R::Capacity => Self::Capacity,
267 R::Throttled => Self::Throttled,
268 R::Unavailable => Self::Unavailable,
269 }
270 }
271}
272unit_enum!(LogoutUnavailable {
273 Capacity,
274 Storage,
275 Clock,
276 ResetPending
277});
278#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
279#[cfg_attr(feature = "typescript", derive(ic_wire::Wire))]
280pub enum LogoutResult {
281 Revoked(u64),
282 InvalidCaller,
283 Unavailable(LogoutUnavailable),
284}
285impl From<domain::LogoutResult> for LogoutResult {
286 fn from(value: domain::LogoutResult) -> Self {
287 match value {
288 domain::LogoutResult::Revoked { deny_until_ns } => Self::Revoked(deny_until_ns),
289 domain::LogoutResult::InvalidCaller => Self::InvalidCaller,
290 domain::LogoutResult::Unavailable(reason) => Self::Unavailable(match reason {
291 domain::LogoutUnavailable::Capacity => LogoutUnavailable::Capacity,
292 domain::LogoutUnavailable::Storage => LogoutUnavailable::Storage,
293 domain::LogoutUnavailable::Clock => LogoutUnavailable::Clock,
294 domain::LogoutUnavailable::ResetPending => LogoutUnavailable::ResetPending,
295 }),
296 }
297 }
298}