1use core::ops::{BitOr, BitOrAssign};
4
5#[cfg(feature = "client")]
6use blindplane_core::{Recipient, RecipientKeypair, recipient_key_id};
7#[cfg(feature = "client")]
8use blindplane_crypto::StaticSecret;
9#[cfg(feature = "client")]
10use blindplane_crypto::aead::Suite;
11#[cfg(feature = "client")]
12use blindplane_crypto::hpke;
13#[cfg(feature = "client")]
14use blindplane_crypto::util::Secret;
15
16use crate::AccessError;
17use crate::codec::{
18 AccessValidationPolicy, Cursor, push_bytes, push_header, push_string, validate_identifier,
19};
20use crate::principal::issuer_key_id;
21#[cfg(feature = "client")]
22use crate::principal::{AccessIssuer, Principal, PrincipalKeypair, TrustedIssuer};
23#[cfg(feature = "client")]
24use crate::revocation::VerifiedRevocation;
25#[cfg(feature = "client")]
26use crate::signed::sign;
27use crate::signed::verify;
28
29const GRANT_TAG: u8 = 4;
30const GRANT_SIGNATURE_DOMAIN: &[u8] = b"blindplane/access/grant/v1";
31#[cfg(feature = "client")]
32const GRANT_HPKE_INFO: &[u8] = b"blindplane/access/grant-hpke/v1";
33#[cfg(feature = "client")]
34const ROLE_BODY_DOMAIN: &[u8] = b"blindplane/access/role-key/v1";
35const KNOWN_PERMISSION_BITS: u8 = 0b0000_1111;
36
37#[derive(Clone, Copy, Debug, Eq, PartialEq)]
39pub struct Permissions(u8);
40
41impl Permissions {
42 pub const NONE: Self = Self(0);
44 pub const READ: Self = Self(1 << 0);
46 pub const WRITE: Self = Self(1 << 1);
48 pub const OBSERVE: Self = Self(1 << 2);
50 pub const ADMINISTER: Self = Self(1 << 3);
52
53 pub const fn contains(self, requested: Self) -> bool {
55 self.0 & requested.0 == requested.0
56 }
57
58 const fn bits(self) -> u8 {
59 self.0
60 }
61
62 fn from_bits(bits: u8) -> Result<Self, AccessError> {
63 if bits == 0 || bits & !KNOWN_PERMISSION_BITS != 0 {
64 Err(AccessError::InvalidPermissions)
65 } else {
66 Ok(Self(bits))
67 }
68 }
69}
70
71impl BitOr for Permissions {
72 type Output = Self;
73
74 fn bitor(self, rhs: Self) -> Self::Output {
75 Self(self.0 | rhs.0)
76 }
77}
78
79impl BitOrAssign for Permissions {
80 fn bitor_assign(&mut self, rhs: Self) {
81 self.0 |= rhs.0;
82 }
83}
84
85#[derive(Clone, Debug, Eq, PartialEq)]
87pub struct GrantSpec {
88 pub grant_id: String,
90 pub scope: String,
92 pub permissions: Permissions,
94 pub authorization_epoch: u64,
96 pub not_before: u64,
98 pub not_after: u64,
100}
101
102#[cfg(feature = "client")]
104pub struct RoleKeypair {
105 tenant_id: String,
106 scope: String,
107 role_id: String,
108 key_epoch: u64,
109 secret: StaticSecret,
110}
111
112#[cfg(feature = "client")]
113impl core::fmt::Debug for RoleKeypair {
114 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
115 f.debug_struct("RoleKeypair")
116 .field("tenant_id", &self.tenant_id)
117 .field("scope", &self.scope)
118 .field("role_id", &self.role_id)
119 .field("key_epoch", &self.key_epoch)
120 .finish_non_exhaustive()
121 }
122}
123
124#[cfg(feature = "client")]
125impl PartialEq for RoleKeypair {
126 fn eq(&self, other: &Self) -> bool {
127 self.tenant_id == other.tenant_id
128 && self.scope == other.scope
129 && self.role_id == other.role_id
130 && self.key_epoch == other.key_epoch
131 && self.secret.to_bytes() == other.secret.to_bytes()
132 }
133}
134
135#[cfg(feature = "client")]
136impl Eq for RoleKeypair {}
137
138#[cfg(feature = "client")]
139impl RoleKeypair {
140 pub fn generate(
142 tenant_id: impl Into<String>,
143 scope: impl Into<String>,
144 role_id: impl Into<String>,
145 key_epoch: u64,
146 ) -> Result<Self, AccessError> {
147 let secret = StaticSecret::generate().map_err(|_| AccessError::CryptographicFailure)?;
148 Self::assemble(
149 tenant_id.into(),
150 scope.into(),
151 role_id.into(),
152 key_epoch,
153 secret,
154 )
155 }
156
157 pub fn from_secret_bytes(
159 tenant_id: impl Into<String>,
160 scope: impl Into<String>,
161 role_id: impl Into<String>,
162 key_epoch: u64,
163 secret: [u8; 32],
164 ) -> Result<Self, AccessError> {
165 Self::assemble(
166 tenant_id.into(),
167 scope.into(),
168 role_id.into(),
169 key_epoch,
170 StaticSecret::from_bytes(secret),
171 )
172 }
173
174 fn assemble(
175 tenant_id: String,
176 scope: String,
177 role_id: String,
178 key_epoch: u64,
179 secret: StaticSecret,
180 ) -> Result<Self, AccessError> {
181 let limit = AccessValidationPolicy::default().max_identifier_bytes;
182 validate_identifier(&tenant_id, limit)?;
183 validate_identifier(&scope, limit)?;
184 validate_identifier(&role_id, limit)?;
185 if key_epoch == 0 {
186 return Err(AccessError::InvalidEpoch);
187 }
188 Ok(Self {
189 tenant_id,
190 scope,
191 role_id,
192 key_epoch,
193 secret,
194 })
195 }
196
197 pub fn role_id(&self) -> &str {
199 &self.role_id
200 }
201
202 pub const fn key_epoch(&self) -> u64 {
204 self.key_epoch
205 }
206
207 pub const fn public_key(&self) -> [u8; 32] {
209 self.secret.public_key()
210 }
211
212 pub(crate) fn tenant_id(&self) -> &str {
213 &self.tenant_id
214 }
215
216 pub(crate) fn scope(&self) -> &str {
217 &self.scope
218 }
219
220 pub(crate) fn secret_bytes(&self) -> Secret<32> {
221 Secret::new(self.secret.to_bytes())
222 }
223
224 pub(crate) fn recipient(&self) -> Result<Recipient, AccessError> {
225 let public_key = self.public_key();
226 Recipient::from_verified_key(
227 self.role_id.clone(),
228 self.key_epoch,
229 public_key,
230 recipient_key_id(&public_key),
231 )
232 .map_err(|_| AccessError::InvalidKeyIdentity)
233 }
234
235 pub(crate) fn recipient_keypair(&self) -> Result<RecipientKeypair, AccessError> {
236 RecipientKeypair::from_secret_bytes(
237 self.role_id.clone(),
238 self.key_epoch,
239 self.secret.to_bytes(),
240 )
241 .map_err(|_| AccessError::InvalidKeyIdentity)
242 }
243}
244
245#[derive(Clone, Debug, Eq, PartialEq)]
247pub struct AccessGrant {
248 grant_id: String,
249 tenant_id: String,
250 subject_id: String,
251 subject_key_id: [u8; 32],
252 scope: String,
253 permissions: Permissions,
254 authorization_epoch: u64,
255 role_key_epoch: u64,
256 not_before: u64,
257 not_after: u64,
258 encapsulated_key: Vec<u8>,
259 wrapped_role_key: Vec<u8>,
260 issuer_id: String,
261 issuer_key_id: [u8; 32],
262 issuer_public_key: [u8; 32],
263 signature: [u8; 64],
264}
265
266impl AccessGrant {
267 #[cfg(feature = "client")]
269 pub fn issue(
270 issuer: &AccessIssuer,
271 subject: &Principal,
272 role: &RoleKeypair,
273 spec: GrantSpec,
274 ) -> Result<Self, AccessError> {
275 let mut grant = Self {
276 grant_id: spec.grant_id,
277 tenant_id: subject.tenant_id().to_owned(),
278 subject_id: subject.principal_id().to_owned(),
279 subject_key_id: subject.key_id(),
280 scope: spec.scope,
281 permissions: spec.permissions,
282 authorization_epoch: spec.authorization_epoch,
283 role_key_epoch: role.key_epoch,
284 not_before: spec.not_before,
285 not_after: spec.not_after,
286 encapsulated_key: Vec::new(),
287 wrapped_role_key: Vec::new(),
288 issuer_id: issuer.issuer_id().to_owned(),
289 issuer_key_id: issuer.key_id(),
290 issuer_public_key: issuer.public_key(),
291 signature: [0; 64],
292 };
293 grant.validate_metadata(&AccessValidationPolicy::default())?;
294 if grant.tenant_id != role.tenant_id() || grant.scope != role.scope() {
295 return Err(AccessError::SubjectMismatch);
296 }
297 let role_body = encode_role_body(role);
298 let (encapsulated_key, wrapped_role_key) = hpke::seal(
299 Suite::ChaCha20Poly1305,
300 &subject.wrapping_public_key(),
301 GRANT_HPKE_INFO,
302 &grant.hpke_aad(),
303 &role_body,
304 )
305 .map_err(|_| AccessError::CryptographicFailure)?;
306 grant.encapsulated_key = encapsulated_key;
307 grant.wrapped_role_key = wrapped_role_key;
308 grant.validate_structure(&AccessValidationPolicy::default())?;
309 grant.signature = sign(
310 GRANT_SIGNATURE_DOMAIN,
311 issuer.signing_key(),
312 &grant.unsigned_bytes(),
313 );
314 Ok(grant)
315 }
316
317 pub fn grant_id(&self) -> &str {
319 &self.grant_id
320 }
321
322 pub fn tenant_id(&self) -> &str {
324 &self.tenant_id
325 }
326
327 pub fn subject_id(&self) -> &str {
329 &self.subject_id
330 }
331
332 pub fn scope(&self) -> &str {
334 &self.scope
335 }
336
337 pub const fn permissions(&self) -> Permissions {
339 self.permissions
340 }
341
342 pub const fn authorization_epoch(&self) -> u64 {
344 self.authorization_epoch
345 }
346
347 pub const fn role_key_epoch(&self) -> u64 {
349 self.role_key_epoch
350 }
351
352 pub fn encode(&self) -> Vec<u8> {
354 let mut out = self.unsigned_bytes();
355 out.extend_from_slice(&self.signature);
356 out
357 }
358
359 pub fn decode(bytes: &[u8], limits: &AccessValidationPolicy) -> Result<Self, AccessError> {
361 let mut cursor = Cursor::new(bytes);
362 cursor.take_header(GRANT_TAG)?;
363 let grant = Self {
364 grant_id: cursor.take_string(limits.max_identifier_bytes)?,
365 tenant_id: cursor.take_string(limits.max_identifier_bytes)?,
366 subject_id: cursor.take_string(limits.max_identifier_bytes)?,
367 subject_key_id: cursor.take_array32()?,
368 scope: cursor.take_string(limits.max_identifier_bytes)?,
369 permissions: Permissions::from_bits(cursor.take_u8()?)?,
370 authorization_epoch: cursor.take_u64()?,
371 role_key_epoch: cursor.take_u64()?,
372 not_before: cursor.take_u64()?,
373 not_after: cursor.take_u64()?,
374 issuer_id: cursor.take_string(limits.max_identifier_bytes)?,
375 issuer_key_id: cursor.take_array32()?,
376 issuer_public_key: cursor.take_array32()?,
377 encapsulated_key: cursor.take_bytes(32)?.to_vec(),
378 wrapped_role_key: cursor.take_bytes(limits.max_wrapped_grant_bytes)?.to_vec(),
379 signature: cursor.take_array64()?,
380 };
381 if !cursor.is_empty() {
382 return Err(AccessError::TrailingBytes);
383 }
384 grant.validate_structure(limits)?;
385 grant.verify_signature()?;
386 if grant.encode() != bytes {
387 return Err(AccessError::NonCanonicalEncoding);
388 }
389 Ok(grant)
390 }
391
392 #[cfg(feature = "client")]
394 pub fn open_role(
395 &self,
396 subject: &PrincipalKeypair,
397 trusted: &TrustedIssuer,
398 now: u64,
399 revocation: &VerifiedRevocation<'_>,
400 ) -> Result<RoleKeypair, AccessError> {
401 self.verify_signature()?;
402 if self.issuer_id != trusted.issuer_id()
403 || self.issuer_key_id != trusted.key_id()
404 || self.issuer_public_key != trusted.public_key()
405 {
406 return Err(AccessError::UntrustedIssuer);
407 }
408 let principal = subject.principal();
409 if self.tenant_id != principal.tenant_id()
410 || self.subject_id != principal.principal_id()
411 || self.subject_key_id != principal.key_id()
412 {
413 return Err(AccessError::SubjectMismatch);
414 }
415 if now < self.not_before {
416 return Err(AccessError::NotYetValid);
417 }
418 if now > self.not_after {
419 return Err(AccessError::Expired);
420 }
421 revocation.accepts(
422 trusted,
423 &self.tenant_id,
424 &self.subject_id,
425 &self.scope,
426 self.authorization_epoch,
427 self.role_key_epoch,
428 )?;
429 let plaintext = hpke::open(
430 Suite::ChaCha20Poly1305,
431 subject.wrapping_secret(),
432 &self.encapsulated_key,
433 GRANT_HPKE_INFO,
434 &self.hpke_aad(),
435 &self.wrapped_role_key,
436 )
437 .map_err(|_| AccessError::CryptographicFailure)?;
438 let role = decode_role_body(&plaintext, &AccessValidationPolicy::default())?;
439 if role.tenant_id != self.tenant_id
440 || role.scope != self.scope
441 || role.key_epoch != self.role_key_epoch
442 {
443 return Err(AccessError::CryptographicFailure);
444 }
445 Ok(role)
446 }
447
448 fn validate_metadata(&self, limits: &AccessValidationPolicy) -> Result<(), AccessError> {
449 validate_identifier(&self.grant_id, limits.max_identifier_bytes)?;
450 validate_identifier(&self.tenant_id, limits.max_identifier_bytes)?;
451 validate_identifier(&self.subject_id, limits.max_identifier_bytes)?;
452 validate_identifier(&self.scope, limits.max_identifier_bytes)?;
453 validate_identifier(&self.issuer_id, limits.max_identifier_bytes)?;
454 Permissions::from_bits(self.permissions.bits())?;
455 if self.authorization_epoch == 0
456 || self.role_key_epoch == 0
457 || self.not_before > self.not_after
458 {
459 return Err(AccessError::InvalidEpoch);
460 }
461 if self.issuer_key_id != issuer_key_id(&self.issuer_public_key) {
462 return Err(AccessError::InvalidKeyIdentity);
463 }
464 Ok(())
465 }
466
467 fn validate_structure(&self, limits: &AccessValidationPolicy) -> Result<(), AccessError> {
468 self.validate_metadata(limits)?;
469 if self.encapsulated_key.len() != 32
470 || self.wrapped_role_key.len() <= 16
471 || self.wrapped_role_key.len() > limits.max_wrapped_grant_bytes
472 {
473 return Err(AccessError::LengthLimit(self.wrapped_role_key.len()));
474 }
475 Ok(())
476 }
477
478 fn verify_signature(&self) -> Result<(), AccessError> {
479 verify(
480 GRANT_SIGNATURE_DOMAIN,
481 &self.issuer_public_key,
482 &self.unsigned_bytes(),
483 &self.signature,
484 )
485 }
486
487 fn hpke_aad(&self) -> Vec<u8> {
488 let mut out = Vec::with_capacity(256);
489 push_string(&mut out, &self.grant_id);
490 push_string(&mut out, &self.tenant_id);
491 push_string(&mut out, &self.subject_id);
492 out.extend_from_slice(&self.subject_key_id);
493 push_string(&mut out, &self.scope);
494 out.push(self.permissions.bits());
495 out.extend_from_slice(&self.authorization_epoch.to_be_bytes());
496 out.extend_from_slice(&self.role_key_epoch.to_be_bytes());
497 out.extend_from_slice(&self.not_before.to_be_bytes());
498 out.extend_from_slice(&self.not_after.to_be_bytes());
499 push_string(&mut out, &self.issuer_id);
500 out.extend_from_slice(&self.issuer_key_id);
501 out.extend_from_slice(&self.issuer_public_key);
502 out
503 }
504
505 fn unsigned_bytes(&self) -> Vec<u8> {
506 let mut out = Vec::with_capacity(384 + self.wrapped_role_key.len());
507 push_header(&mut out, GRANT_TAG);
508 out.extend_from_slice(&self.hpke_aad());
509 push_bytes(&mut out, &self.encapsulated_key);
510 push_bytes(&mut out, &self.wrapped_role_key);
511 out
512 }
513}
514
515#[cfg(feature = "client")]
516fn encode_role_body(role: &RoleKeypair) -> Vec<u8> {
517 let secret = role.secret_bytes();
518 let mut out = Vec::with_capacity(160);
519 push_bytes(&mut out, ROLE_BODY_DOMAIN);
520 push_string(&mut out, &role.tenant_id);
521 push_string(&mut out, &role.scope);
522 push_string(&mut out, &role.role_id);
523 out.extend_from_slice(&role.key_epoch.to_be_bytes());
524 out.extend_from_slice(secret.as_bytes());
525 out
526}
527
528#[cfg(feature = "client")]
529fn decode_role_body(
530 bytes: &[u8],
531 limits: &AccessValidationPolicy,
532) -> Result<RoleKeypair, AccessError> {
533 let mut cursor = Cursor::new(bytes);
534 if cursor.take_bytes(ROLE_BODY_DOMAIN.len())? != ROLE_BODY_DOMAIN {
535 return Err(AccessError::CryptographicFailure);
536 }
537 let tenant_id = cursor.take_string(limits.max_identifier_bytes)?;
538 let scope = cursor.take_string(limits.max_identifier_bytes)?;
539 let role_id = cursor.take_string(limits.max_identifier_bytes)?;
540 let key_epoch = cursor.take_u64()?;
541 let secret = cursor.take_array32()?;
542 if !cursor.is_empty() {
543 return Err(AccessError::CryptographicFailure);
544 }
545 RoleKeypair::from_secret_bytes(tenant_id, scope, role_id, key_epoch, secret)
546}