1use ed25519_dalek::{Signature, VerifyingKey};
4use std::collections::BTreeSet;
5use std::time::{SystemTime, UNIX_EPOCH};
6
7use crate::ServerError;
8use crate::config::PassConfig;
9
10pub const PASS_VERSION_V1: u8 = 1;
11const SIGNATURE_LEN: usize = 64;
12
13pub(crate) const MAX_PASS_CONVERSATIONS: usize =
24 crate::server::connection::pre_admission::PRE_ADMISSION_FRAME_BOUND / 8;
25const _: () = assert!(MAX_PASS_CONVERSATIONS == 8192);
26
27#[derive(Clone, PartialEq, Eq)]
28pub struct PassPrincipal {
29 pub participant: Vec<u8>,
30 pub public_key: [u8; 32],
31 pub conversations: BTreeSet<u64>,
32 pub live: String,
33 pub may_enroll: bool,
34}
35
36#[derive(Clone, PartialEq, Eq)]
37pub struct WirePassV1 {
38 pub participant: Vec<u8>,
39 pub public_key: [u8; 32],
40 pub conversations: Vec<u64>,
41 pub live: String,
42 pub may_enroll: bool,
43 pub issued_at: u64,
44 pub expires_at: u64,
45 pub signature: [u8; SIGNATURE_LEN],
46}
47
48pub(crate) struct ByteLength(pub(crate) usize);
51
52impl std::fmt::Debug for ByteLength {
53 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54 write!(formatter, "{} bytes", self.0)
55 }
56}
57
58impl std::fmt::Debug for PassPrincipal {
59 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60 formatter
61 .debug_struct("PassPrincipal")
62 .field("participant", &ByteLength(self.participant.len()))
63 .field("public_key", &ByteLength(self.public_key.len()))
64 .field("conversations", &self.conversations)
65 .field("live", &ByteLength(self.live.len()))
66 .field("may_enroll", &self.may_enroll)
67 .finish()
68 }
69}
70
71impl std::fmt::Debug for WirePassV1 {
72 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73 formatter
74 .debug_struct("WirePassV1")
75 .field("participant", &ByteLength(self.participant.len()))
76 .field("public_key", &ByteLength(self.public_key.len()))
77 .field("conversations", &self.conversations)
78 .field("live", &ByteLength(self.live.len()))
79 .field("may_enroll", &self.may_enroll)
80 .field("issued_at", &self.issued_at)
81 .field("expires_at", &self.expires_at)
82 .field("signature", &ByteLength(self.signature.len()))
83 .finish()
84 }
85}
86
87#[derive(Clone, PartialEq, Eq)]
107pub struct VerifiedHolder {
108 participant: Vec<u8>,
109 live: String,
110 source: HolderSource,
111}
112
113#[derive(Clone, Debug, PartialEq, Eq)]
116enum HolderSource {
117 RegistryPass,
119 Lys { kind: Option<String> },
124}
125
126impl VerifiedHolder {
127 pub(crate) fn from_lys_session(session: &dyn crate::auth_lys::session::LysSession) -> Self {
131 Self {
132 participant: session.holder().as_bytes().to_vec(),
133 live: String::new(),
134 source: HolderSource::Lys {
135 kind: session.holder_kind().map(str::to_owned),
136 },
137 }
138 }
139
140 #[must_use]
143 pub fn kind(&self) -> Option<&str> {
144 match &self.source {
145 HolderSource::RegistryPass => None,
146 HolderSource::Lys { kind } => kind.as_deref(),
147 }
148 }
149
150 #[must_use]
155 pub fn allows_channel(&self, channel: &str) -> bool {
156 match self.source {
157 HolderSource::RegistryPass => {
158 !self.live.is_empty() && channel.starts_with(self.live.as_str())
159 }
160 HolderSource::Lys { .. } => true,
161 }
162 }
163
164 #[must_use]
166 pub fn participant(&self) -> &[u8] {
167 &self.participant
168 }
169
170 #[must_use]
172 pub fn live(&self) -> &str {
173 &self.live
174 }
175
176 #[must_use]
179 pub fn is_named(&self, identity: &str) -> bool {
180 identity.as_bytes() == self.participant.as_slice()
181 }
182}
183
184impl std::fmt::Debug for VerifiedHolder {
185 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
186 formatter
187 .debug_struct("VerifiedHolder")
188 .field("participant", &ByteLength(self.participant.len()))
189 .field("live", &ByteLength(self.live.len()))
190 .field("source", &self.source)
191 .finish()
192 }
193}
194
195pub(crate) struct VerifiedPass {
198 pub(crate) principal: PassPrincipal,
199 pub(crate) holder: VerifiedHolder,
200}
201
202#[derive(Clone, Copy, Debug, PartialEq, Eq)]
203pub enum PassCheckError {
204 Malformed,
205 ScopeTooLarge,
208 Signature,
209 Expired,
210}
211impl PassCheckError {
212 #[must_use]
213 pub const fn check_name(self) -> &'static str {
214 match self {
215 Self::Malformed => "malformed",
216 Self::ScopeTooLarge => "scope_too_large",
217 Self::Signature => "signature",
218 Self::Expired => "expired",
219 }
220 }
221}
222
223#[derive(Clone, Debug)]
224pub(crate) struct PassVerifier {
225 key: VerifyingKey,
226 skew: u64,
227 require_verified_worker: bool,
230}
231impl PassVerifier {
232 pub(crate) fn from_auth(auth: &crate::config::AuthConfig) -> Result<Option<Self>, ServerError> {
239 auth.pass
240 .as_ref()
241 .map(|pass| {
242 Self::from_config(pass).map(|verifier| Self {
243 require_verified_worker: auth.require_verified_worker,
244 ..verifier
245 })
246 })
247 .transpose()
248 }
249
250 #[cfg(test)]
253 pub(crate) const fn requiring_verified_worker(self) -> Self {
254 Self {
255 require_verified_worker: true,
256 ..self
257 }
258 }
259
260 pub(crate) const fn requires_verified_worker(&self) -> bool {
263 self.require_verified_worker
264 }
265
266 pub(crate) fn from_config(config: &PassConfig) -> Result<Self, ServerError> {
272 let key = hex::decode(&config.registry_verifying_key).map_err(|error| {
273 ServerError::ConfigValidation {
274 message: format!(
275 "auth.pass.registry_verifying_key: expected 64 hexadecimal digits: {error}"
276 ),
277 }
278 })?;
279 Self::new(&key, config.maximum_clock_skew_seconds).map_err(|_| {
280 ServerError::ConfigValidation {
281 message: "auth.pass.registry_verifying_key: expected a valid 32-byte Ed25519 verifying key".to_owned(),
282 }
283 })
284 }
285
286 pub(crate) fn new(key: &[u8], skew: u64) -> Result<Self, PassCheckError> {
291 let key: [u8; 32] = key.try_into().map_err(|_| PassCheckError::Malformed)?;
292 let key = VerifyingKey::from_bytes(&key).map_err(|_| PassCheckError::Malformed)?;
293 Ok(Self {
294 key,
295 skew,
296 require_verified_worker: false,
297 })
298 }
299
300 pub(crate) fn verify(&self, bytes: &[u8]) -> Result<VerifiedPass, PassCheckError> {
305 let principal = WirePassV1::verify(bytes, &self.key, self.skew, SystemTime::now())?;
306 let holder = VerifiedHolder {
307 participant: principal.participant.clone(),
308 live: principal.live.clone(),
309 source: HolderSource::RegistryPass,
310 };
311 Ok(VerifiedPass { principal, holder })
312 }
313}
314
315impl WirePassV1 {
316 pub fn canonical_unsigned_bytes(&self) -> Result<Vec<u8>, PassCheckError> {
321 if self.issued_at > self.expires_at || self.conversations.windows(2).any(|p| p[0] >= p[1]) {
322 return Err(PassCheckError::Malformed);
323 }
324 let mut out = vec![PASS_VERSION_V1];
325 put_bytes(&mut out, &self.participant)?;
326 out.extend_from_slice(&self.public_key);
327 put_len(&mut out, self.conversations.len())?;
328 for id in &self.conversations {
329 out.extend_from_slice(&id.to_be_bytes());
330 }
331 put_bytes(&mut out, self.live.as_bytes())?;
332 out.push(u8::from(self.may_enroll));
333 out.extend_from_slice(&self.issued_at.to_be_bytes());
334 out.extend_from_slice(&self.expires_at.to_be_bytes());
335 Ok(out)
336 }
337
338 pub fn canonical_bytes(&self) -> Result<Vec<u8>, PassCheckError> {
343 let mut out = self.canonical_unsigned_bytes()?;
344 out.extend_from_slice(&self.signature);
345 Ok(out)
346 }
347
348 pub fn parse(bytes: &[u8]) -> Result<Self, PassCheckError> {
353 let unsigned_len = bytes
354 .len()
355 .checked_sub(SIGNATURE_LEN)
356 .ok_or(PassCheckError::Malformed)?;
357 let (unsigned, sig) = bytes.split_at(unsigned_len);
358 let mut c = Cursor(unsigned);
359 if c.u8()? != PASS_VERSION_V1 {
360 return Err(PassCheckError::Malformed);
361 }
362 let participant = c.bytes()?.to_vec();
363 let public_key = c.array()?;
364 let count = c.len()?;
365 if count > MAX_PASS_CONVERSATIONS {
368 return Err(PassCheckError::ScopeTooLarge);
369 }
370 let mut conversations = Vec::with_capacity(count);
371 for _ in 0..count {
372 conversations.push(c.u64()?);
373 }
374 let live = std::str::from_utf8(c.bytes()?)
375 .map_err(|_| PassCheckError::Malformed)?
376 .to_owned();
377 let may_enroll = match c.u8()? {
378 0 => false,
379 1 => true,
380 _ => return Err(PassCheckError::Malformed),
381 };
382 let issued_at = c.u64()?;
383 let expires_at = c.u64()?;
384 if !c.0.is_empty()
385 || issued_at > expires_at
386 || conversations.windows(2).any(|p| p[0] >= p[1])
387 {
388 return Err(PassCheckError::Malformed);
389 }
390 Ok(Self {
391 participant,
392 public_key,
393 conversations,
394 live,
395 may_enroll,
396 issued_at,
397 expires_at,
398 signature: sig.try_into().map_err(|_| PassCheckError::Malformed)?,
399 })
400 }
401
402 pub fn verify(
407 bytes: &[u8],
408 key: &VerifyingKey,
409 skew: u64,
410 now: SystemTime,
411 ) -> Result<PassPrincipal, PassCheckError> {
412 let pass = Self::parse(bytes)?;
413 key.verify_strict(
414 &pass.canonical_unsigned_bytes()?,
415 &Signature::from_bytes(&pass.signature),
416 )
417 .map_err(|_| PassCheckError::Signature)?;
418 let now = now
419 .duration_since(UNIX_EPOCH)
420 .map_err(|_| PassCheckError::Expired)?
421 .as_secs();
422 if now < pass.issued_at.saturating_sub(skew) || now > pass.expires_at.saturating_add(skew) {
423 return Err(PassCheckError::Expired);
424 }
425 Ok(PassPrincipal {
426 participant: pass.participant,
427 public_key: pass.public_key,
428 conversations: pass.conversations.into_iter().collect(),
429 live: pass.live,
430 may_enroll: pass.may_enroll,
431 })
432 }
433}
434
435fn put_len(out: &mut Vec<u8>, len: usize) -> Result<(), PassCheckError> {
436 out.extend_from_slice(
437 &u32::try_from(len)
438 .map_err(|_| PassCheckError::Malformed)?
439 .to_be_bytes(),
440 );
441 Ok(())
442}
443fn put_bytes(out: &mut Vec<u8>, bytes: &[u8]) -> Result<(), PassCheckError> {
444 put_len(out, bytes.len())?;
445 out.extend_from_slice(bytes);
446 Ok(())
447}
448struct Cursor<'a>(&'a [u8]);
449impl<'a> Cursor<'a> {
450 const fn take(&mut self, n: usize) -> Result<&'a [u8], PassCheckError> {
451 if self.0.len() < n {
452 return Err(PassCheckError::Malformed);
453 }
454 let (a, b) = self.0.split_at(n);
455 self.0 = b;
456 Ok(a)
457 }
458 fn u8(&mut self) -> Result<u8, PassCheckError> {
459 Ok(self.take(1)?[0])
460 }
461 fn len(&mut self) -> Result<usize, PassCheckError> {
462 let a: [u8; 4] = self
463 .take(4)?
464 .try_into()
465 .map_err(|_| PassCheckError::Malformed)?;
466 usize::try_from(u32::from_be_bytes(a)).map_err(|_| PassCheckError::Malformed)
467 }
468 fn u64(&mut self) -> Result<u64, PassCheckError> {
469 Ok(u64::from_be_bytes(
470 self.take(8)?
471 .try_into()
472 .map_err(|_| PassCheckError::Malformed)?,
473 ))
474 }
475 fn bytes(&mut self) -> Result<&'a [u8], PassCheckError> {
476 let n = self.len()?;
477 self.take(n)
478 }
479 fn array<const N: usize>(&mut self) -> Result<[u8; N], PassCheckError> {
480 self.take(N)?
481 .try_into()
482 .map_err(|_| PassCheckError::Malformed)
483 }
484}
485
486#[cfg(test)]
487mod debug_tests;
488
489#[cfg(test)]
490mod tests {
491 use super::*;
492
493 fn pass_declaring(count: u32) -> Vec<u8> {
496 let mut bytes = vec![PASS_VERSION_V1];
497 bytes.extend_from_slice(&0_u32.to_be_bytes());
498 bytes.extend_from_slice(&[0_u8; 32]);
499 bytes.extend_from_slice(&count.to_be_bytes());
500 bytes.extend_from_slice(&[0_u8; SIGNATURE_LEN]);
501 bytes
502 }
503
504 #[test]
505 fn a_pass_declaring_u32_max_conversations_is_refused_by_name_before_allocation() {
506 assert_eq!(
507 WirePassV1::parse(&pass_declaring(u32::MAX)),
508 Err(PassCheckError::ScopeTooLarge)
509 );
510 assert_eq!(
511 PassCheckError::ScopeTooLarge.check_name(),
512 "scope_too_large"
513 );
514 }
515
516 #[test]
517 fn a_pass_declaring_one_past_the_bound_is_refused_and_the_bound_itself_is_not()
518 -> Result<(), Box<dyn std::error::Error>> {
519 let at_bound = u32::try_from(MAX_PASS_CONVERSATIONS)?;
520 assert_eq!(
521 WirePassV1::parse(&pass_declaring(at_bound + 1)),
522 Err(PassCheckError::ScopeTooLarge)
523 );
524 assert_eq!(
527 WirePassV1::parse(&pass_declaring(at_bound)),
528 Err(PassCheckError::Malformed)
529 );
530 Ok(())
531 }
532
533 #[test]
534 fn shared_version_one_vector_is_byte_exact_and_verifies()
535 -> Result<(), Box<dyn std::error::Error>> {
536 let vector: serde_json::Value =
537 serde_json::from_str(include_str!("../test-vectors/wire-pass-v1.json"))?;
538 let unsigned = hex::decode(
539 vector["unsigned_pass_hex"]
540 .as_str()
541 .ok_or("unsigned bytes")?,
542 )?;
543 let signature: [u8; 64] =
544 hex::decode(vector["signature_hex"].as_str().ok_or("signature")?)?
545 .try_into()
546 .map_err(|_| "signature length")?;
547 let bytes = hex::decode(vector["pass_hex"].as_str().ok_or("pass bytes")?)?;
548 let registry_key: [u8; 32] = hex::decode(
549 vector["registry_verifying_key_hex"]
550 .as_str()
551 .ok_or("verifying key")?,
552 )?
553 .try_into()
554 .map_err(|_| "key length")?;
555 let pass = WirePassV1::parse(&bytes).map_err(|error| format!("parse failed: {error:?}"))?;
556 assert_eq!(
557 pass.canonical_unsigned_bytes()
558 .map_err(|error| format!("encode failed: {error:?}"))?,
559 unsigned
560 );
561 assert_eq!(pass.signature, signature);
562 let key = VerifyingKey::from_bytes(®istry_key)?;
563 let principal = WirePassV1::verify(
564 &bytes,
565 &key,
566 0,
567 UNIX_EPOCH + std::time::Duration::from_secs(pass.issued_at),
568 )
569 .map_err(|error| format!("verify failed: {error:?}"))?;
570 assert_eq!(principal.participant, b"participant-42");
571 assert_eq!(
572 principal.conversations.into_iter().collect::<Vec<_>>(),
573 vec![7, 42, 9001]
574 );
575 assert_eq!(principal.live, "workspace/acme/");
576 assert!(principal.may_enroll);
577 Ok(())
578 }
579}