1#[cfg(feature = "client")]
4use blindplane_crypto::util::Secret;
5use blindplane_crypto::{PreparedVerifier, Sha256};
6#[cfg(feature = "client")]
7use blindplane_crypto::{SigningKey, StaticSecret};
8
9use crate::AccessError;
10use crate::codec::{AccessValidationPolicy, Cursor, push_header, push_string, validate_identifier};
11use crate::signed::key_id;
12
13const PRINCIPAL_TAG: u8 = 1;
14const PRINCIPAL_KEY_ID_DOMAIN: &[u8] = b"blindplane/access/principal-key-id/v1";
15const ISSUER_KEY_ID_DOMAIN: &[u8] = b"blindplane/access/issuer-key-id/v1";
16
17pub(crate) fn issuer_key_id(public_key: &[u8; 32]) -> [u8; 32] {
18 key_id(ISSUER_KEY_ID_DOMAIN, public_key)
19}
20
21#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
23pub enum PrincipalKind {
24 User,
26 Administrator,
28 Device,
30 Service,
32}
33
34impl PrincipalKind {
35 const fn code(self) -> u8 {
36 match self {
37 Self::User => 1,
38 Self::Administrator => 2,
39 Self::Device => 3,
40 Self::Service => 4,
41 }
42 }
43
44 fn from_code(code: u8) -> Result<Self, AccessError> {
45 match code {
46 1 => Ok(Self::User),
47 2 => Ok(Self::Administrator),
48 3 => Ok(Self::Device),
49 4 => Ok(Self::Service),
50 _ => Err(AccessError::WrongObjectType),
51 }
52 }
53}
54
55#[derive(Clone, Debug, Eq, PartialEq)]
57pub struct Principal {
58 tenant_id: String,
59 principal_id: String,
60 kind: PrincipalKind,
61 key_epoch: u64,
62 key_id: [u8; 32],
63 wrapping_public_key: [u8; 32],
64 signing_public_key: [u8; 32],
65}
66
67impl Principal {
68 pub fn tenant_id(&self) -> &str {
70 &self.tenant_id
71 }
72
73 pub fn principal_id(&self) -> &str {
75 &self.principal_id
76 }
77
78 pub const fn kind(&self) -> PrincipalKind {
80 self.kind
81 }
82
83 pub const fn key_epoch(&self) -> u64 {
85 self.key_epoch
86 }
87
88 pub const fn key_id(&self) -> [u8; 32] {
90 self.key_id
91 }
92
93 pub const fn wrapping_public_key(&self) -> [u8; 32] {
95 self.wrapping_public_key
96 }
97
98 pub const fn signing_public_key(&self) -> [u8; 32] {
100 self.signing_public_key
101 }
102
103 pub fn encode(&self) -> Vec<u8> {
105 let mut out = Vec::with_capacity(160 + self.tenant_id.len() + self.principal_id.len());
106 push_header(&mut out, PRINCIPAL_TAG);
107 push_string(&mut out, &self.tenant_id);
108 push_string(&mut out, &self.principal_id);
109 out.push(self.kind.code());
110 out.extend_from_slice(&self.key_epoch.to_be_bytes());
111 out.extend_from_slice(&self.key_id);
112 out.extend_from_slice(&self.wrapping_public_key);
113 out.extend_from_slice(&self.signing_public_key);
114 out
115 }
116
117 pub fn decode(bytes: &[u8], policy: &AccessValidationPolicy) -> Result<Self, AccessError> {
119 let mut cursor = Cursor::new(bytes);
120 cursor.take_header(PRINCIPAL_TAG)?;
121 let principal = Self {
122 tenant_id: cursor.take_string(policy.max_identifier_bytes)?,
123 principal_id: cursor.take_string(policy.max_identifier_bytes)?,
124 kind: PrincipalKind::from_code(cursor.take_u8()?)?,
125 key_epoch: cursor.take_u64()?,
126 key_id: cursor.take_array32()?,
127 wrapping_public_key: cursor.take_array32()?,
128 signing_public_key: cursor.take_array32()?,
129 };
130 if !cursor.is_empty() {
131 return Err(AccessError::TrailingBytes);
132 }
133 principal.validate(policy)?;
134 if principal.encode() != bytes {
135 return Err(AccessError::NonCanonicalEncoding);
136 }
137 Ok(principal)
138 }
139
140 fn validate(&self, policy: &AccessValidationPolicy) -> Result<(), AccessError> {
141 validate_identifier(&self.tenant_id, policy.max_identifier_bytes)?;
142 validate_identifier(&self.principal_id, policy.max_identifier_bytes)?;
143 if self.key_epoch == 0 {
144 return Err(AccessError::InvalidEpoch);
145 }
146 let expected = key_id(PRINCIPAL_KEY_ID_DOMAIN, &self.wrapping_public_key);
147 if expected != self.key_id
148 || PreparedVerifier::new(&self.signing_public_key).is_err()
149 || !usable_x25519_public_key(&self.wrapping_public_key)
150 {
151 return Err(AccessError::InvalidKeyIdentity);
152 }
153 Ok(())
154 }
155}
156
157fn usable_x25519_public_key(public_key: &[u8; 32]) -> bool {
158 let probe = blindplane_crypto::StaticSecret::from_bytes(Sha256::digest(
159 b"blindplane/access/public-key-validation/v1",
160 ));
161 probe.diffie_hellman(public_key).is_some()
162}
163
164#[derive(Clone, Debug, Eq, PartialEq)]
166pub struct TrustedIssuer {
167 issuer_id: String,
168 key_id: [u8; 32],
169 public_key: [u8; 32],
170}
171
172impl TrustedIssuer {
173 pub fn issuer_id(&self) -> &str {
175 &self.issuer_id
176 }
177
178 pub const fn key_id(&self) -> [u8; 32] {
180 self.key_id
181 }
182
183 pub const fn public_key(&self) -> [u8; 32] {
185 self.public_key
186 }
187}
188
189#[cfg(feature = "client")]
191pub struct PrincipalKeypair {
192 principal: Principal,
193 wrapping_secret: StaticSecret,
194 #[allow(
195 dead_code,
196 reason = "consumed by the event implementation in a later task"
197 )]
198 signing_seed: Secret<32>,
199}
200
201#[cfg(feature = "client")]
202impl core::fmt::Debug for PrincipalKeypair {
203 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
204 f.debug_struct("PrincipalKeypair")
205 .field("principal", &self.principal)
206 .field("secrets", &"redacted")
207 .finish_non_exhaustive()
208 }
209}
210
211#[cfg(feature = "client")]
212impl PrincipalKeypair {
213 pub fn generate(
215 tenant_id: impl Into<String>,
216 principal_id: impl Into<String>,
217 kind: PrincipalKind,
218 key_epoch: u64,
219 ) -> Result<Self, AccessError> {
220 let wrapping = StaticSecret::generate().map_err(|_| AccessError::InvalidKeyIdentity)?;
221 let signing = SigningKey::generate().map_err(|_| AccessError::InvalidKeyIdentity)?;
222 Self::assemble(
223 tenant_id.into(),
224 principal_id.into(),
225 kind,
226 key_epoch,
227 wrapping,
228 signing.to_seed(),
229 )
230 }
231
232 pub fn from_secret_bytes(
234 tenant_id: impl Into<String>,
235 principal_id: impl Into<String>,
236 kind: PrincipalKind,
237 key_epoch: u64,
238 wrapping_secret: [u8; 32],
239 signing_seed: [u8; 32],
240 ) -> Result<Self, AccessError> {
241 Self::assemble(
242 tenant_id.into(),
243 principal_id.into(),
244 kind,
245 key_epoch,
246 StaticSecret::from_bytes(wrapping_secret),
247 signing_seed,
248 )
249 }
250
251 fn assemble(
252 tenant_id: String,
253 principal_id: String,
254 kind: PrincipalKind,
255 key_epoch: u64,
256 wrapping_secret: StaticSecret,
257 signing_seed: [u8; 32],
258 ) -> Result<Self, AccessError> {
259 let signing = SigningKey::from_seed(&signing_seed);
260 let wrapping_public_key = wrapping_secret.public_key();
261 let principal = Principal {
262 tenant_id,
263 principal_id,
264 kind,
265 key_epoch,
266 key_id: key_id(PRINCIPAL_KEY_ID_DOMAIN, &wrapping_public_key),
267 wrapping_public_key,
268 signing_public_key: signing.verifying_key(),
269 };
270 principal.validate(&AccessValidationPolicy::default())?;
271 Ok(Self {
272 principal,
273 wrapping_secret,
274 signing_seed: Secret::new(signing_seed),
275 })
276 }
277
278 pub const fn principal(&self) -> &Principal {
280 &self.principal
281 }
282
283 pub(crate) const fn wrapping_secret(&self) -> &StaticSecret {
284 &self.wrapping_secret
285 }
286
287 #[allow(
288 dead_code,
289 reason = "consumed by the event implementation in a later task"
290 )]
291 pub(crate) fn signing_seed(&self) -> [u8; 32] {
292 self.signing_seed.expose()
293 }
294}
295
296#[cfg(feature = "client")]
298pub struct AccessIssuer {
299 issuer_id: String,
300 key_id: [u8; 32],
301 signing_key: SigningKey,
302}
303
304#[cfg(feature = "client")]
305impl core::fmt::Debug for AccessIssuer {
306 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
307 f.debug_struct("AccessIssuer")
308 .field("issuer_id", &self.issuer_id)
309 .field("key_id", &self.key_id)
310 .field("signing_key", &"redacted")
311 .finish()
312 }
313}
314
315#[cfg(feature = "client")]
316impl AccessIssuer {
317 pub fn generate(issuer_id: impl Into<String>) -> Result<Self, AccessError> {
319 let signing_key = SigningKey::generate().map_err(|_| AccessError::InvalidKeyIdentity)?;
320 Self::assemble(issuer_id.into(), signing_key)
321 }
322
323 pub fn from_seed(issuer_id: impl Into<String>, seed: [u8; 32]) -> Result<Self, AccessError> {
325 Self::assemble(issuer_id.into(), SigningKey::from_seed(&seed))
326 }
327
328 fn assemble(issuer_id: String, signing_key: SigningKey) -> Result<Self, AccessError> {
329 validate_identifier(
330 &issuer_id,
331 AccessValidationPolicy::default().max_identifier_bytes,
332 )?;
333 let public_key = signing_key.verifying_key();
334 Ok(Self {
335 issuer_id,
336 key_id: issuer_key_id(&public_key),
337 signing_key,
338 })
339 }
340
341 pub fn trusted(&self) -> TrustedIssuer {
343 TrustedIssuer {
344 issuer_id: self.issuer_id.clone(),
345 key_id: self.key_id,
346 public_key: self.public_key(),
347 }
348 }
349
350 pub const fn public_key(&self) -> [u8; 32] {
352 self.signing_key.verifying_key()
353 }
354
355 pub(crate) fn issuer_id(&self) -> &str {
356 &self.issuer_id
357 }
358
359 pub(crate) const fn key_id(&self) -> [u8; 32] {
360 self.key_id
361 }
362
363 pub(crate) const fn signing_key(&self) -> &SigningKey {
364 &self.signing_key
365 }
366}