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)]
106pub struct VerifiedHolder {
107 participant: Vec<u8>,
108 live: String,
109}
110
111impl VerifiedHolder {
112 #[must_use]
114 pub fn participant(&self) -> &[u8] {
115 &self.participant
116 }
117
118 #[must_use]
120 pub fn live(&self) -> &str {
121 &self.live
122 }
123
124 #[must_use]
127 pub fn is_named(&self, identity: &str) -> bool {
128 identity.as_bytes() == self.participant.as_slice()
129 }
130}
131
132impl std::fmt::Debug for VerifiedHolder {
133 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
134 formatter
135 .debug_struct("VerifiedHolder")
136 .field("participant", &ByteLength(self.participant.len()))
137 .field("live", &ByteLength(self.live.len()))
138 .finish()
139 }
140}
141
142pub(crate) struct VerifiedPass {
145 pub(crate) principal: PassPrincipal,
146 pub(crate) holder: VerifiedHolder,
147}
148
149#[derive(Clone, Copy, Debug, PartialEq, Eq)]
150pub enum PassCheckError {
151 Malformed,
152 ScopeTooLarge,
155 Signature,
156 Expired,
157}
158impl PassCheckError {
159 #[must_use]
160 pub const fn check_name(self) -> &'static str {
161 match self {
162 Self::Malformed => "malformed",
163 Self::ScopeTooLarge => "scope_too_large",
164 Self::Signature => "signature",
165 Self::Expired => "expired",
166 }
167 }
168}
169
170#[derive(Clone, Debug)]
171pub(crate) struct PassVerifier {
172 key: VerifyingKey,
173 skew: u64,
174 require_verified_worker: bool,
177}
178impl PassVerifier {
179 pub(crate) fn from_auth(auth: &crate::config::AuthConfig) -> Result<Option<Self>, ServerError> {
186 auth.pass
187 .as_ref()
188 .map(|pass| {
189 Self::from_config(pass).map(|verifier| Self {
190 require_verified_worker: auth.require_verified_worker,
191 ..verifier
192 })
193 })
194 .transpose()
195 }
196
197 #[cfg(test)]
200 pub(crate) const fn requiring_verified_worker(self) -> Self {
201 Self {
202 require_verified_worker: true,
203 ..self
204 }
205 }
206
207 pub(crate) const fn requires_verified_worker(&self) -> bool {
210 self.require_verified_worker
211 }
212
213 pub(crate) fn from_config(config: &PassConfig) -> Result<Self, ServerError> {
219 let key = hex::decode(&config.registry_verifying_key).map_err(|error| {
220 ServerError::ConfigValidation {
221 message: format!(
222 "auth.pass.registry_verifying_key: expected 64 hexadecimal digits: {error}"
223 ),
224 }
225 })?;
226 Self::new(&key, config.maximum_clock_skew_seconds).map_err(|_| {
227 ServerError::ConfigValidation {
228 message: "auth.pass.registry_verifying_key: expected a valid 32-byte Ed25519 verifying key".to_owned(),
229 }
230 })
231 }
232
233 pub(crate) fn new(key: &[u8], skew: u64) -> Result<Self, PassCheckError> {
238 let key: [u8; 32] = key.try_into().map_err(|_| PassCheckError::Malformed)?;
239 let key = VerifyingKey::from_bytes(&key).map_err(|_| PassCheckError::Malformed)?;
240 Ok(Self {
241 key,
242 skew,
243 require_verified_worker: false,
244 })
245 }
246
247 pub(crate) fn verify(&self, bytes: &[u8]) -> Result<VerifiedPass, PassCheckError> {
252 let principal = WirePassV1::verify(bytes, &self.key, self.skew, SystemTime::now())?;
253 let holder = VerifiedHolder {
254 participant: principal.participant.clone(),
255 live: principal.live.clone(),
256 };
257 Ok(VerifiedPass { principal, holder })
258 }
259}
260
261impl WirePassV1 {
262 pub fn canonical_unsigned_bytes(&self) -> Result<Vec<u8>, PassCheckError> {
267 if self.issued_at > self.expires_at || self.conversations.windows(2).any(|p| p[0] >= p[1]) {
268 return Err(PassCheckError::Malformed);
269 }
270 let mut out = vec![PASS_VERSION_V1];
271 put_bytes(&mut out, &self.participant)?;
272 out.extend_from_slice(&self.public_key);
273 put_len(&mut out, self.conversations.len())?;
274 for id in &self.conversations {
275 out.extend_from_slice(&id.to_be_bytes());
276 }
277 put_bytes(&mut out, self.live.as_bytes())?;
278 out.push(u8::from(self.may_enroll));
279 out.extend_from_slice(&self.issued_at.to_be_bytes());
280 out.extend_from_slice(&self.expires_at.to_be_bytes());
281 Ok(out)
282 }
283
284 pub fn canonical_bytes(&self) -> Result<Vec<u8>, PassCheckError> {
289 let mut out = self.canonical_unsigned_bytes()?;
290 out.extend_from_slice(&self.signature);
291 Ok(out)
292 }
293
294 pub fn parse(bytes: &[u8]) -> Result<Self, PassCheckError> {
299 let unsigned_len = bytes
300 .len()
301 .checked_sub(SIGNATURE_LEN)
302 .ok_or(PassCheckError::Malformed)?;
303 let (unsigned, sig) = bytes.split_at(unsigned_len);
304 let mut c = Cursor(unsigned);
305 if c.u8()? != PASS_VERSION_V1 {
306 return Err(PassCheckError::Malformed);
307 }
308 let participant = c.bytes()?.to_vec();
309 let public_key = c.array()?;
310 let count = c.len()?;
311 if count > MAX_PASS_CONVERSATIONS {
314 return Err(PassCheckError::ScopeTooLarge);
315 }
316 let mut conversations = Vec::with_capacity(count);
317 for _ in 0..count {
318 conversations.push(c.u64()?);
319 }
320 let live = std::str::from_utf8(c.bytes()?)
321 .map_err(|_| PassCheckError::Malformed)?
322 .to_owned();
323 let may_enroll = match c.u8()? {
324 0 => false,
325 1 => true,
326 _ => return Err(PassCheckError::Malformed),
327 };
328 let issued_at = c.u64()?;
329 let expires_at = c.u64()?;
330 if !c.0.is_empty()
331 || issued_at > expires_at
332 || conversations.windows(2).any(|p| p[0] >= p[1])
333 {
334 return Err(PassCheckError::Malformed);
335 }
336 Ok(Self {
337 participant,
338 public_key,
339 conversations,
340 live,
341 may_enroll,
342 issued_at,
343 expires_at,
344 signature: sig.try_into().map_err(|_| PassCheckError::Malformed)?,
345 })
346 }
347
348 pub fn verify(
353 bytes: &[u8],
354 key: &VerifyingKey,
355 skew: u64,
356 now: SystemTime,
357 ) -> Result<PassPrincipal, PassCheckError> {
358 let pass = Self::parse(bytes)?;
359 key.verify_strict(
360 &pass.canonical_unsigned_bytes()?,
361 &Signature::from_bytes(&pass.signature),
362 )
363 .map_err(|_| PassCheckError::Signature)?;
364 let now = now
365 .duration_since(UNIX_EPOCH)
366 .map_err(|_| PassCheckError::Expired)?
367 .as_secs();
368 if now < pass.issued_at.saturating_sub(skew) || now > pass.expires_at.saturating_add(skew) {
369 return Err(PassCheckError::Expired);
370 }
371 Ok(PassPrincipal {
372 participant: pass.participant,
373 public_key: pass.public_key,
374 conversations: pass.conversations.into_iter().collect(),
375 live: pass.live,
376 may_enroll: pass.may_enroll,
377 })
378 }
379}
380
381fn put_len(out: &mut Vec<u8>, len: usize) -> Result<(), PassCheckError> {
382 out.extend_from_slice(
383 &u32::try_from(len)
384 .map_err(|_| PassCheckError::Malformed)?
385 .to_be_bytes(),
386 );
387 Ok(())
388}
389fn put_bytes(out: &mut Vec<u8>, bytes: &[u8]) -> Result<(), PassCheckError> {
390 put_len(out, bytes.len())?;
391 out.extend_from_slice(bytes);
392 Ok(())
393}
394struct Cursor<'a>(&'a [u8]);
395impl<'a> Cursor<'a> {
396 const fn take(&mut self, n: usize) -> Result<&'a [u8], PassCheckError> {
397 if self.0.len() < n {
398 return Err(PassCheckError::Malformed);
399 }
400 let (a, b) = self.0.split_at(n);
401 self.0 = b;
402 Ok(a)
403 }
404 fn u8(&mut self) -> Result<u8, PassCheckError> {
405 Ok(self.take(1)?[0])
406 }
407 fn len(&mut self) -> Result<usize, PassCheckError> {
408 let a: [u8; 4] = self
409 .take(4)?
410 .try_into()
411 .map_err(|_| PassCheckError::Malformed)?;
412 usize::try_from(u32::from_be_bytes(a)).map_err(|_| PassCheckError::Malformed)
413 }
414 fn u64(&mut self) -> Result<u64, PassCheckError> {
415 Ok(u64::from_be_bytes(
416 self.take(8)?
417 .try_into()
418 .map_err(|_| PassCheckError::Malformed)?,
419 ))
420 }
421 fn bytes(&mut self) -> Result<&'a [u8], PassCheckError> {
422 let n = self.len()?;
423 self.take(n)
424 }
425 fn array<const N: usize>(&mut self) -> Result<[u8; N], PassCheckError> {
426 self.take(N)?
427 .try_into()
428 .map_err(|_| PassCheckError::Malformed)
429 }
430}
431
432#[cfg(test)]
433mod debug_tests;
434
435#[cfg(test)]
436mod tests {
437 use super::*;
438
439 fn pass_declaring(count: u32) -> Vec<u8> {
442 let mut bytes = vec![PASS_VERSION_V1];
443 bytes.extend_from_slice(&0_u32.to_be_bytes());
444 bytes.extend_from_slice(&[0_u8; 32]);
445 bytes.extend_from_slice(&count.to_be_bytes());
446 bytes.extend_from_slice(&[0_u8; SIGNATURE_LEN]);
447 bytes
448 }
449
450 #[test]
451 fn a_pass_declaring_u32_max_conversations_is_refused_by_name_before_allocation() {
452 assert_eq!(
453 WirePassV1::parse(&pass_declaring(u32::MAX)),
454 Err(PassCheckError::ScopeTooLarge)
455 );
456 assert_eq!(
457 PassCheckError::ScopeTooLarge.check_name(),
458 "scope_too_large"
459 );
460 }
461
462 #[test]
463 fn a_pass_declaring_one_past_the_bound_is_refused_and_the_bound_itself_is_not()
464 -> Result<(), Box<dyn std::error::Error>> {
465 let at_bound = u32::try_from(MAX_PASS_CONVERSATIONS)?;
466 assert_eq!(
467 WirePassV1::parse(&pass_declaring(at_bound + 1)),
468 Err(PassCheckError::ScopeTooLarge)
469 );
470 assert_eq!(
473 WirePassV1::parse(&pass_declaring(at_bound)),
474 Err(PassCheckError::Malformed)
475 );
476 Ok(())
477 }
478
479 #[test]
480 fn shared_version_one_vector_is_byte_exact_and_verifies()
481 -> Result<(), Box<dyn std::error::Error>> {
482 let vector: serde_json::Value =
483 serde_json::from_str(include_str!("../test-vectors/wire-pass-v1.json"))?;
484 let unsigned = hex::decode(
485 vector["unsigned_pass_hex"]
486 .as_str()
487 .ok_or("unsigned bytes")?,
488 )?;
489 let signature: [u8; 64] =
490 hex::decode(vector["signature_hex"].as_str().ok_or("signature")?)?
491 .try_into()
492 .map_err(|_| "signature length")?;
493 let bytes = hex::decode(vector["pass_hex"].as_str().ok_or("pass bytes")?)?;
494 let registry_key: [u8; 32] = hex::decode(
495 vector["registry_verifying_key_hex"]
496 .as_str()
497 .ok_or("verifying key")?,
498 )?
499 .try_into()
500 .map_err(|_| "key length")?;
501 let pass = WirePassV1::parse(&bytes).map_err(|error| format!("parse failed: {error:?}"))?;
502 assert_eq!(
503 pass.canonical_unsigned_bytes()
504 .map_err(|error| format!("encode failed: {error:?}"))?,
505 unsigned
506 );
507 assert_eq!(pass.signature, signature);
508 let key = VerifyingKey::from_bytes(®istry_key)?;
509 let principal = WirePassV1::verify(
510 &bytes,
511 &key,
512 0,
513 UNIX_EPOCH + std::time::Duration::from_secs(pass.issued_at),
514 )
515 .map_err(|error| format!("verify failed: {error:?}"))?;
516 assert_eq!(principal.participant, b"participant-42");
517 assert_eq!(
518 principal.conversations.into_iter().collect::<Vec<_>>(),
519 vec![7, 42, 9001]
520 );
521 assert_eq!(principal.live, "workspace/acme/");
522 assert!(principal.may_enroll);
523 Ok(())
524 }
525}