1#![warn(missing_docs)]
20#![doc(
21 html_logo_url = "https://raw.githubusercontent.com/getsentry/relay/master/artwork/relay-icon.png",
22 html_favicon_url = "https://raw.githubusercontent.com/getsentry/relay/master/artwork/relay-icon.png"
23)]
24
25use std::fmt;
26use std::fmt::Display;
27use std::str::FromStr;
28
29use chrono::{DateTime, Duration, Utc};
30use data_encoding::BASE64URL_NOPAD;
31use ed25519_dalek::{Digest, DigestSigner, DigestVerifier, Signer, Verifier};
32use hmac::{Hmac, Mac};
33use rand::rngs::OsRng;
34use rand::{RngCore as _, TryRngCore as _};
35use relay_common::time::UnixTimestamp;
36use serde::de::DeserializeOwned;
37use serde::{Deserialize, Serialize};
38use sha2::Sha512;
39use uuid::Uuid;
40
41include!(concat!(env!("OUT_DIR"), "/constants.gen.rs"));
42
43const LATEST_VERSION: RelayVersion = RelayVersion::new(VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH);
45
46const OLDEST_VERSION: RelayVersion = RelayVersion::new(0, 0, 0); pub type RelayId = Uuid;
51
52#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
54pub struct RelayVersion {
55 major: u8,
56 minor: u8,
57 patch: u8,
58}
59
60impl RelayVersion {
61 pub fn current() -> Self {
63 LATEST_VERSION
64 }
65
66 pub fn oldest() -> Self {
71 OLDEST_VERSION
72 }
73
74 pub const fn new(major: u8, minor: u8, patch: u8) -> Self {
76 Self {
77 major,
78 minor,
79 patch,
80 }
81 }
82
83 pub fn supported(self) -> bool {
85 self >= Self::oldest()
86 }
87
88 pub fn outdated(self) -> bool {
90 self < Self::current()
91 }
92}
93
94impl fmt::Display for RelayVersion {
95 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96 write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
97 }
98}
99
100#[derive(Clone, Copy, Debug, Default, thiserror::Error)]
102#[error("invalid relay version string")]
103pub struct ParseRelayVersionError;
104
105impl FromStr for RelayVersion {
106 type Err = ParseRelayVersionError;
107
108 fn from_str(s: &str) -> Result<Self, Self::Err> {
109 let mut iter = s
110 .split(&['.', '-'][..])
111 .map(|s| s.parse().map_err(|_| ParseRelayVersionError));
112
113 let major = iter.next().ok_or(ParseRelayVersionError)??;
114 let minor = iter.next().ok_or(ParseRelayVersionError)??;
115 let patch = iter.next().ok_or(ParseRelayVersionError)??;
116
117 Ok(Self::new(major, minor, patch))
118 }
119}
120
121relay_common::impl_str_serde!(RelayVersion, "a version string");
122
123#[derive(Debug, Eq, Hash, PartialEq, thiserror::Error)]
125pub enum KeyParseError {
126 #[error("bad key encoding")]
128 BadEncoding,
129 #[error("bad key data")]
131 BadKey,
132}
133
134#[derive(Debug, thiserror::Error)]
136pub enum UnpackError {
137 #[error("invalid signature on data")]
139 BadSignature,
140 #[error("bad key encoding")]
142 BadEncoding,
143 #[error("could not deserialize payload")]
145 BadPayload(#[source] serde_json::Error),
146 #[error("signature is too old")]
148 SignatureExpired,
149}
150
151#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
153pub enum SignatureAlgorithm {
154 #[serde(rename = "v0")]
156 Regular,
157 #[serde(rename = "v1")]
159 Prehashed,
160}
161
162#[derive(Serialize, Deserialize, Debug)]
166pub struct SignatureHeader {
167 #[serde(rename = "t", skip_serializing_if = "Option::is_none")]
169 pub timestamp: Option<DateTime<Utc>>,
170
171 #[serde(rename = "a", skip_serializing_if = "Option::is_none")]
176 pub signature_algorithm: Option<SignatureAlgorithm>,
177}
178
179impl SignatureHeader {
180 pub fn expired(&self, max_age: Duration) -> bool {
182 if let Some(ts) = self.timestamp {
183 ts < (Utc::now() - max_age)
184 } else {
185 false
186 }
187 }
188}
189
190impl Default for SignatureHeader {
191 fn default() -> SignatureHeader {
192 SignatureHeader {
193 timestamp: Some(Utc::now()),
194 signature_algorithm: None,
195 }
196 }
197}
198
199#[derive(Clone)]
205pub struct SecretKey {
206 inner: ed25519_dalek::SigningKey,
207}
208
209#[derive(Serialize, Deserialize, Debug)]
211pub struct Registration {
212 relay_id: RelayId,
213}
214
215fn create_digest(header: &[u8], data: &[u8]) -> Sha512 {
217 let mut digest = Sha512::default();
218 digest.update(header);
219 digest.update(b"\x00");
220 digest.update(data);
221 digest
222}
223
224impl SecretKey {
225 pub fn sign(&self, data: &[u8]) -> Signature {
229 self.sign_with_header(data, &SignatureHeader::default())
230 }
231
232 pub fn sign_with_header(&self, data: &[u8], sig_header: &SignatureHeader) -> Signature {
238 let mut header =
239 serde_json::to_vec(&sig_header).expect("attempted to pack non json safe header");
240 let header_encoded = BASE64URL_NOPAD.encode(&header);
241 let sig = match sig_header
242 .signature_algorithm
243 .unwrap_or(SignatureAlgorithm::Regular)
244 {
245 SignatureAlgorithm::Regular => {
246 header.push(b'\x00');
247 header.extend_from_slice(data);
248 self.inner.sign(&header)
249 }
250 SignatureAlgorithm::Prehashed => {
251 let digest = create_digest(&header, data);
252 self.inner.sign_digest(digest)
253 }
254 };
255
256 let mut sig_encoded = BASE64URL_NOPAD.encode(&sig.to_bytes());
257 sig_encoded.push('.');
258 sig_encoded.push_str(&header_encoded);
259 Signature(sig_encoded)
260 }
261
262 pub fn pack<S: Serialize>(&self, data: S) -> (Vec<u8>, Signature) {
264 self.pack_with_header(data, &SignatureHeader::default())
265 }
266
267 pub fn pack_with_header<S: Serialize>(
269 &self,
270 data: S,
271 header: &SignatureHeader,
272 ) -> (Vec<u8>, Signature) {
273 let json = serde_json::to_vec(&data).expect("attempted to pack non json safe data");
276 let sig = self.sign_with_header(&json, header);
277 (json, sig)
278 }
279}
280
281impl PartialEq for SecretKey {
282 fn eq(&self, other: &SecretKey) -> bool {
283 self.inner.to_keypair_bytes() == other.inner.to_keypair_bytes()
284 }
285}
286
287impl Eq for SecretKey {}
288
289impl FromStr for SecretKey {
290 type Err = KeyParseError;
291
292 fn from_str(s: &str) -> Result<SecretKey, KeyParseError> {
293 let bytes = match BASE64URL_NOPAD.decode(s.as_bytes()) {
294 Ok(bytes) => bytes,
295 _ => return Err(KeyParseError::BadEncoding),
296 };
297
298 let inner = if let Ok(keypair) = bytes.as_slice().try_into() {
299 ed25519_dalek::SigningKey::from_keypair_bytes(&keypair)
300 .map_err(|_| KeyParseError::BadKey)?
301 } else if let Ok(secret_key) = bytes.try_into() {
302 ed25519_dalek::SigningKey::from_bytes(&secret_key)
303 } else {
304 return Err(KeyParseError::BadKey);
305 };
306
307 Ok(SecretKey { inner })
308 }
309}
310
311impl fmt::Display for SecretKey {
312 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
313 if f.alternate() {
314 write!(
315 f,
316 "{}",
317 BASE64URL_NOPAD.encode(&self.inner.to_keypair_bytes())
318 )
319 } else {
320 write!(f, "{}", BASE64URL_NOPAD.encode(&self.inner.to_bytes()))
321 }
322 }
323}
324
325impl fmt::Debug for SecretKey {
326 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
327 write!(f, "SecretKey(\"{self}\")")
328 }
329}
330
331relay_common::impl_str_serde!(SecretKey, "a secret key");
332
333#[derive(Clone, Eq, PartialEq)]
339pub struct PublicKey {
340 inner: ed25519_dalek::VerifyingKey,
341}
342
343impl PublicKey {
344 pub fn verify_meta(&self, data: &[u8], sig: SignatureRef<'_>) -> Option<SignatureHeader> {
347 let mut iter = sig.0.splitn(2, '.');
348 let sig_bytes = match iter.next() {
349 Some(sig_encoded) => BASE64URL_NOPAD.decode(sig_encoded.as_bytes()).ok()?,
350 None => return None,
351 };
352 let sig = ed25519_dalek::Signature::from_slice(&sig_bytes).ok()?;
353
354 let header = match iter.next() {
355 Some(header_encoded) => BASE64URL_NOPAD.decode(header_encoded.as_bytes()).ok()?,
356 None => return None,
357 };
358 let parsed: SignatureHeader = serde_json::from_slice(&header).ok()?;
359
360 let verification_result = match parsed
361 .signature_algorithm
362 .unwrap_or(SignatureAlgorithm::Regular)
363 {
364 SignatureAlgorithm::Regular => {
365 let mut to_verify = header.clone();
366 to_verify.push(b'\x00');
367 to_verify.extend_from_slice(data);
368 self.inner.verify(&to_verify, &sig)
369 }
370 SignatureAlgorithm::Prehashed => {
371 let digest = create_digest(&header, data);
372 self.inner.verify_digest(digest, &sig)
373 }
374 };
375 if verification_result.is_ok() {
376 Some(parsed)
377 } else {
378 None
379 }
380 }
381
382 pub fn verify(&self, data: &[u8], sig: SignatureRef<'_>) -> bool {
384 self.verify_meta(data, sig).is_some()
385 }
386
387 pub fn verify_timestamp(
389 &self,
390 data: &[u8],
391 sig: SignatureRef<'_>,
392 max_age: Option<Duration>,
393 ) -> bool {
394 self.verify_meta(data, sig)
395 .map(|header| max_age.is_none() || !header.expired(max_age.unwrap()))
396 .unwrap_or(false)
397 }
398
399 pub fn unpack_meta<D: DeserializeOwned>(
401 &self,
402 data: &[u8],
403 signature: SignatureRef<'_>,
404 ) -> Result<(SignatureHeader, D), UnpackError> {
405 if let Some(header) = self.verify_meta(data, signature) {
406 serde_json::from_slice(data)
407 .map(|data| (header, data))
408 .map_err(UnpackError::BadPayload)
409 } else {
410 Err(UnpackError::BadSignature)
411 }
412 }
413
414 pub fn unpack<D: DeserializeOwned>(
419 &self,
420 data: &[u8],
421 signature: SignatureRef<'_>,
422 max_age: Option<Duration>,
423 ) -> Result<D, UnpackError> {
424 let (header, data) = self.unpack_meta(data, signature)?;
425 if max_age.is_none() || !header.expired(max_age.unwrap()) {
426 Ok(data)
427 } else {
428 Err(UnpackError::SignatureExpired)
429 }
430 }
431}
432
433impl FromStr for PublicKey {
434 type Err = KeyParseError;
435
436 fn from_str(s: &str) -> Result<PublicKey, KeyParseError> {
437 let Ok(bytes) = BASE64URL_NOPAD.decode(s.as_bytes()) else {
438 return Err(KeyParseError::BadEncoding);
439 };
440
441 let inner = match bytes.try_into() {
442 Ok(bytes) => ed25519_dalek::VerifyingKey::from_bytes(&bytes)
443 .map_err(|_| KeyParseError::BadKey)?,
444 Err(_) => return Err(KeyParseError::BadKey),
445 };
446
447 Ok(PublicKey { inner })
448 }
449}
450
451impl fmt::Display for PublicKey {
452 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
453 write!(f, "{}", BASE64URL_NOPAD.encode(&self.inner.to_bytes()))
454 }
455}
456
457impl fmt::Debug for PublicKey {
458 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
459 write!(f, "PublicKey(\"{self}\")")
460 }
461}
462
463relay_common::impl_str_serde!(PublicKey, "a public key");
464
465pub fn generate_relay_id() -> RelayId {
467 Uuid::new_v4()
468}
469
470pub fn generate_key_pair() -> (SecretKey, PublicKey) {
472 let mut csprng = OsRng;
473 let mut secret = [0; 32];
474 csprng
475 .try_fill_bytes(&mut secret)
476 .expect("os rng should be available");
477 let kp = ed25519_dalek::SigningKey::from_bytes(&secret);
478 let pk = kp.verifying_key();
479 (SecretKey { inner: kp }, PublicKey { inner: pk })
480}
481
482#[derive(Clone, Debug, Deserialize, Serialize)]
501pub struct SignedRegisterState(String);
502
503impl SignedRegisterState {
504 fn mac(secret: &[u8]) -> Hmac<Sha512> {
506 Hmac::new_from_slice(secret).expect("HMAC takes variable keys")
507 }
508
509 fn sign(state: RegisterState, secret: &[u8]) -> Self {
511 let json = serde_json::to_string(&state).expect("relay register state serializes to JSON");
512 let token = BASE64URL_NOPAD.encode(json.as_bytes());
513
514 let mut mac = Self::mac(secret);
515 mac.update(token.as_bytes());
516 let signature = BASE64URL_NOPAD.encode(&mac.finalize().into_bytes());
517
518 Self(format!("{token}:{signature}"))
519 }
520
521 fn split(&self) -> (&str, &str) {
523 let mut split = self.as_str().splitn(2, ':');
524 (split.next().unwrap_or(""), split.next().unwrap_or(""))
525 }
526
527 pub fn as_str(&self) -> &str {
529 self.0.as_str()
530 }
531
532 pub fn unpack(
537 &self,
538 secret: &[u8],
539 max_age: Option<Duration>,
540 ) -> Result<RegisterState, UnpackError> {
541 let (token, signature) = self.split();
542 let code = BASE64URL_NOPAD
543 .decode(signature.as_bytes())
544 .map_err(|_| UnpackError::BadEncoding)?;
545
546 let mut mac = Self::mac(secret);
547 mac.update(token.as_bytes());
548 mac.verify_slice(&code)
549 .map_err(|_| UnpackError::BadSignature)?;
550
551 let json = BASE64URL_NOPAD
552 .decode(token.as_bytes())
553 .map_err(|_| UnpackError::BadEncoding)?;
554 let state =
555 serde_json::from_slice::<RegisterState>(&json).map_err(UnpackError::BadPayload)?;
556
557 if let Some(max_age) = max_age {
558 let secs = state.timestamp().as_secs() as i64;
559 if secs + max_age.num_seconds() < Utc::now().timestamp() {
560 return Err(UnpackError::SignatureExpired);
561 }
562 }
563
564 Ok(state)
565 }
566}
567
568impl fmt::Display for SignedRegisterState {
569 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
570 self.as_str().fmt(f)
571 }
572}
573
574#[derive(Clone, Deserialize, Serialize)]
580pub struct RegisterState {
581 timestamp: UnixTimestamp,
582 relay_id: RelayId,
583 public_key: PublicKey,
584 rand: String,
585}
586
587impl RegisterState {
588 pub fn timestamp(&self) -> UnixTimestamp {
590 self.timestamp
591 }
592
593 pub fn relay_id(&self) -> RelayId {
595 self.relay_id
596 }
597
598 pub fn public_key(&self) -> &PublicKey {
600 &self.public_key
601 }
602}
603
604fn nonce() -> String {
606 let mut rng = rand::rng();
607 let mut bytes = vec![0u8; 64];
608 rng.fill_bytes(&mut bytes);
609 BASE64URL_NOPAD.encode(&bytes)
610}
611
612#[derive(Serialize, Deserialize, Debug)]
618pub struct RegisterRequest {
619 relay_id: RelayId,
620 public_key: PublicKey,
621 #[serde(default)]
622 version: RelayVersion,
623}
624
625impl RegisterRequest {
626 pub fn new(relay_id: &RelayId, public_key: &PublicKey) -> RegisterRequest {
628 RegisterRequest {
629 relay_id: *relay_id,
630 public_key: public_key.clone(),
631 version: RelayVersion::current(),
632 }
633 }
634
635 pub fn bootstrap_unpack(
641 data: &[u8],
642 signature: SignatureRef<'_>,
643 max_age: Option<Duration>,
644 ) -> Result<RegisterRequest, UnpackError> {
645 let req: RegisterRequest = serde_json::from_slice(data).map_err(UnpackError::BadPayload)?;
646 let pk = req.public_key();
647 pk.unpack(data, signature, max_age)
648 }
649
650 pub fn relay_id(&self) -> RelayId {
652 self.relay_id
653 }
654
655 pub fn public_key(&self) -> &PublicKey {
657 &self.public_key
658 }
659
660 pub fn into_challenge(self, secret: &[u8]) -> RegisterChallenge {
662 let state = RegisterState {
663 timestamp: UnixTimestamp::now(),
664 relay_id: self.relay_id,
665 public_key: self.public_key,
666 rand: nonce(),
667 };
668
669 RegisterChallenge {
670 relay_id: self.relay_id,
671 token: SignedRegisterState::sign(state, secret),
672 }
673 }
674}
675
676#[derive(Serialize, Deserialize, Debug)]
678pub struct RegisterChallenge {
679 relay_id: RelayId,
680 token: SignedRegisterState,
681}
682
683impl RegisterChallenge {
684 pub fn relay_id(&self) -> &RelayId {
686 &self.relay_id
687 }
688
689 pub fn token(&self) -> &str {
691 self.token.as_str()
692 }
693
694 pub fn into_response(self) -> RegisterResponse {
696 RegisterResponse {
697 relay_id: self.relay_id,
698 token: self.token,
699 version: RelayVersion::current(),
700 }
701 }
702}
703
704#[derive(Serialize, Deserialize, Debug)]
709pub struct RegisterResponse {
710 relay_id: RelayId,
711 token: SignedRegisterState,
712 #[serde(default)]
713 version: RelayVersion,
714}
715
716impl RegisterResponse {
717 pub fn unpack(
719 data: &[u8],
720 signature: SignatureRef<'_>,
721 secret: &[u8],
722 max_age: Option<Duration>,
723 ) -> Result<(Self, RegisterState), UnpackError> {
724 let response: Self = serde_json::from_slice(data).map_err(UnpackError::BadPayload)?;
725 let state = response.token.unpack(secret, max_age)?;
726
727 if let Some(header) = state.public_key().verify_meta(data, signature) {
728 if max_age.is_some_and(|m| header.expired(m)) {
729 return Err(UnpackError::SignatureExpired);
730 }
731 } else {
732 return Err(UnpackError::BadSignature);
733 }
734
735 Ok((response, state))
736 }
737
738 pub fn relay_id(&self) -> RelayId {
740 self.relay_id
741 }
742
743 pub fn token(&self) -> &str {
745 self.token.as_str()
746 }
747
748 pub fn version(&self) -> RelayVersion {
750 self.version
751 }
752}
753
754#[derive(Debug, Clone, PartialEq)]
756pub struct Signature(pub String);
757
758impl Display for Signature {
759 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
760 write!(f, "{}", self.0)
761 }
762}
763
764impl Signature {
765 pub fn verify_any(
771 &self,
772 public_key: &[PublicKey],
773 start_time: DateTime<Utc>,
774 max_age: Duration,
775 ) -> bool {
776 public_key
777 .iter()
778 .any(|p| self.verify(p, start_time, max_age))
779 }
780
781 pub fn verify(
787 &self,
788 public_key: &PublicKey,
789 start_time: DateTime<Utc>,
790 max_age: Duration,
791 ) -> bool {
792 let Some(header) = public_key.verify_meta(&[], self.as_signature_ref()) else {
793 return false;
794 };
795 let Some(timestamp) = header.timestamp else {
796 return false;
797 };
798 let elapsed = start_time - timestamp;
799 elapsed >= Duration::zero() && elapsed <= max_age
800 }
801
802 pub fn verify_bytes(&self, data: &[u8], public_key: &PublicKey) -> bool {
807 public_key
808 .verify_meta(data, self.as_signature_ref())
809 .is_some()
810 }
811
812 pub fn as_signature_ref(&self) -> SignatureRef<'_> {
817 SignatureRef(self.0.as_str())
818 }
819}
820
821pub struct SignatureRef<'a>(pub &'a str);
827
828#[cfg(test)]
829mod tests {
830 use super::*;
831
832 #[test]
833 fn test_keys() {
834 let sk: SecretKey =
835 "OvXFVm1tIUi8xDTuyHX1SSqdMc8nCt2qU9IUaH5p7oUk5pHZsdnfXNiMWiMLtSE86J3N9Peo5CBP1YQHDUkApQ"
836 .parse()
837 .unwrap();
838 let pk: PublicKey = "JOaR2bHZ31zYjFojC7UhPOidzfT3qOQgT9WEBw1JAKU"
839 .parse()
840 .unwrap();
841
842 assert_eq!(
843 sk.to_string(),
844 "OvXFVm1tIUi8xDTuyHX1SSqdMc8nCt2qU9IUaH5p7oU"
845 );
846 assert_eq!(
847 format!("{sk:#}"),
848 "OvXFVm1tIUi8xDTuyHX1SSqdMc8nCt2qU9IUaH5p7oUk5pHZsdnfXNiMWiMLtSE86J3N9Peo5CBP1YQHDUkApQ"
849 );
850 assert_eq!(
851 pk.to_string(),
852 "JOaR2bHZ31zYjFojC7UhPOidzfT3qOQgT9WEBw1JAKU"
853 );
854
855 assert_eq!(
856 "bad data".parse::<SecretKey>(),
857 Err(KeyParseError::BadEncoding)
858 );
859 assert_eq!("OvXF".parse::<SecretKey>(), Err(KeyParseError::BadKey));
860
861 assert_eq!(
862 "bad data".parse::<PublicKey>(),
863 Err(KeyParseError::BadEncoding)
864 );
865 assert_eq!("OvXF".parse::<PublicKey>(), Err(KeyParseError::BadKey));
866 }
867
868 #[test]
869 fn test_serializing() {
870 let sk: SecretKey =
871 "OvXFVm1tIUi8xDTuyHX1SSqdMc8nCt2qU9IUaH5p7oUk5pHZsdnfXNiMWiMLtSE86J3N9Peo5CBP1YQHDUkApQ"
872 .parse()
873 .unwrap();
874 let pk: PublicKey = "JOaR2bHZ31zYjFojC7UhPOidzfT3qOQgT9WEBw1JAKU"
875 .parse()
876 .unwrap();
877
878 let sk_json = serde_json::to_string(&sk).unwrap();
879 assert_eq!(sk_json, "\"OvXFVm1tIUi8xDTuyHX1SSqdMc8nCt2qU9IUaH5p7oU\"");
880
881 let pk_json = serde_json::to_string(&pk).unwrap();
882 assert_eq!(pk_json, "\"JOaR2bHZ31zYjFojC7UhPOidzfT3qOQgT9WEBw1JAKU\"");
883
884 assert_eq!(serde_json::from_str::<SecretKey>(&sk_json).unwrap(), sk);
885 assert_eq!(serde_json::from_str::<PublicKey>(&pk_json).unwrap(), pk);
886 }
887
888 #[test]
889 fn test_signatures() {
890 let (sk, pk) = generate_key_pair();
891 let data = b"Hello World!";
892
893 let sig = sk.sign(data);
894 assert!(pk.verify(data, sig.as_signature_ref()));
895
896 let bad_sig = "jgubwSf2wb2wuiRpgt2H9_bdDSMr88hXLp5zVuhbr65EGkSxOfT5ILIWr623twLgLd0bDgHg6xzOaUCX7XvUCw";
897 assert!(!pk.verify(data, SignatureRef(bad_sig)));
898 }
899
900 #[test]
901 fn test_registration() {
902 let max_age = Duration::minutes(15);
903
904 let relay_id = generate_relay_id();
906 let (sk, pk) = generate_key_pair();
907
908 let request = RegisterRequest::new(&relay_id, &pk);
910
911 let (request_bytes, request_sig) = sk.pack(request);
913
914 let request = RegisterRequest::bootstrap_unpack(
916 &request_bytes,
917 request_sig.as_signature_ref(),
918 Some(max_age),
919 )
920 .unwrap();
921 assert_eq!(request.relay_id(), relay_id);
922 assert_eq!(request.public_key(), &pk);
923
924 let upstream_secret = b"secret";
925
926 let challenge = request.into_challenge(upstream_secret);
928 let challenge_token = challenge.token().to_owned();
929 assert_eq!(challenge.relay_id(), &relay_id);
930 assert!(challenge.token().len() > 40);
931
932 let state = SignedRegisterState(challenge_token.clone());
934 let register_state = state.unpack(upstream_secret, None).unwrap();
935 assert_eq!(register_state.public_key, pk);
936 assert_eq!(register_state.relay_id, relay_id);
937
938 let response = challenge.into_response();
940
941 let (response_bytes, response_sig) = sk.pack(response);
943 let (response, _) = RegisterResponse::unpack(
944 &response_bytes,
945 response_sig.as_signature_ref(),
946 upstream_secret,
947 Some(max_age),
948 )
949 .unwrap();
950
951 assert_eq!(response.relay_id(), relay_id);
952 assert_eq!(response.token(), challenge_token);
953 assert_eq!(response.version, LATEST_VERSION);
954 }
955
956 #[test]
961 #[allow(clippy::print_stdout, reason = "helper test to generate output")]
962 fn test_generate_strings_for_test_auth_py() {
963 let max_age = Duration::minutes(15);
964 println!("Generating test data for test_auth.py...");
965
966 let relay_id = generate_relay_id();
968 println!("RELAY_ID = b\"{relay_id}\"");
969 let (sk, pk) = generate_key_pair();
970 println!("RELAY_KEY = b\"{pk}\"");
971
972 let request = RegisterRequest::new(&relay_id, &pk);
974 println!("REQUEST = b'{}'", serde_json::to_string(&request).unwrap());
975
976 let (request_bytes, request_sig) = sk.pack(&request);
978 println!("REQUEST_SIG = \"{request_sig}\"");
979
980 let request = RegisterRequest::bootstrap_unpack(
982 &request_bytes,
983 request_sig.as_signature_ref(),
984 Some(max_age),
985 )
986 .unwrap();
987
988 let upstream_secret = b"secret";
989
990 let challenge = request.into_challenge(upstream_secret);
992 let challenge_token = challenge.token().to_owned();
993 println!("TOKEN = \"{challenge_token}\"");
994
995 let response = challenge.into_response();
997 let serialized_response = serde_json::to_string(&response).unwrap();
998 let (_, response_sig) = sk.pack(&response);
999
1000 println!("RESPONSE = b'{serialized_response}'");
1001 println!("RESPONSE_SIG = \"{response_sig}\"");
1002
1003 println!("RELAY_VERSION = \"{LATEST_VERSION}\"");
1004 }
1005
1006 #[test]
1008 fn test_deserialize_old_response() {
1009 let serialized_challenge = "{\"relay_id\":\"6b7d15b8-cee2-4354-9fee-dae7ef43e434\",\"token\":\"eyJ0aW1lc3RhbXAiOjE1OTg5Njc0MzQsInJlbGF5X2lkIjoiNmI3ZDE1YjgtY2VlMi00MzU0LTlmZWUtZGFlN2VmNDNlNDM0IiwicHVibGljX2tleSI6ImtNcEdieWRIWlN2b2h6ZU1sZ2hjV3dIZDhNa3JlS0d6bF9uY2RrWlNPTWciLCJyYW5kIjoiLUViNG9Hal80dUZYOUNRRzFBVmdqTjRmdGxaNU9DSFlNOFl2d1podmlyVXhUY0tFSWYtQzhHaldsZmgwQTNlMzYxWE01dVh0RHhvN00tbWhZeXpWUWcifQ:KJUDXlwvibKNQmex-_Cu1U0FArlmoDkyqP7bYIDGrLXudfjGfCjH-UjNsUHWVDnbM28YdQ-R2MBSyF51aRLQcw\"}";
1010 let result: RegisterResponse = serde_json::from_str(serialized_challenge).unwrap();
1011 assert_eq!(
1012 result.relay_id,
1013 Uuid::parse_str("6b7d15b8-cee2-4354-9fee-dae7ef43e434").unwrap()
1014 )
1015 }
1016
1017 #[test]
1018 fn test_relay_version_current() {
1019 assert_eq!(
1020 env!("CARGO_PKG_VERSION"),
1021 RelayVersion::current().to_string()
1022 );
1023 }
1024
1025 #[test]
1026 fn test_relay_version_oldest() {
1027 assert_eq!("0.0.0", RelayVersion::oldest().to_string());
1029 }
1030
1031 #[test]
1032 fn test_relay_version_parse() {
1033 assert_eq!(
1034 RelayVersion::new(20, 7, 0),
1035 "20.7.0-beta.0".parse().unwrap()
1036 );
1037 }
1038
1039 #[test]
1040 fn test_relay_version_oldest_supported() {
1041 assert!(RelayVersion::oldest().supported());
1042 }
1043
1044 #[test]
1045 fn test_relay_version_any_supported() {
1046 assert!(RelayVersion::default().supported());
1049 }
1050
1051 #[test]
1052 fn test_relay_version_from_str() {
1053 assert_eq!(RelayVersion::new(20, 7, 0), "20.7.0".parse().unwrap());
1054 }
1055
1056 #[test]
1057 fn test_verify_any() {
1058 let pair1 = generate_key_pair();
1059 let pair2 = generate_key_pair();
1060 let pair3 = generate_key_pair();
1061
1062 let signature = pair3.0.sign(&[]);
1063 assert!(signature.verify_any(
1064 &[pair1.1, pair2.1, pair3.1],
1065 Utc::now(),
1066 Duration::seconds(10)
1067 ));
1068 }
1069
1070 #[test]
1071 fn test_verify_max_age() {
1072 let pair = generate_key_pair();
1073 let signature = pair.0.sign(&[]);
1074 let start_time = Utc::now();
1075 assert!(signature.verify(&pair.1, start_time, Duration::seconds(10)));
1077 assert!(!signature.verify(
1079 &pair.1,
1080 start_time - Duration::seconds(1),
1081 Duration::milliseconds(500)
1082 ))
1083 }
1084
1085 #[test]
1086 fn test_verify_any_max_age() {
1087 let start_time = Utc::now();
1088 let pair1 = generate_key_pair();
1089 let pair2 = generate_key_pair();
1090 let pair3 = generate_key_pair();
1091
1092 let header = SignatureHeader {
1093 timestamp: Some(start_time),
1094 signature_algorithm: Some(SignatureAlgorithm::Regular),
1095 };
1096 let signature = pair3.0.sign_with_header(&[], &header);
1097
1098 let public_keys = &[pair1.1, pair2.1, pair3.1];
1099
1100 assert!(signature.verify_any(
1102 public_keys,
1103 start_time + Duration::seconds(1),
1104 Duration::seconds(2)
1105 ));
1106 assert!(!signature.verify_any(
1108 public_keys,
1109 start_time + Duration::seconds(3),
1110 Duration::seconds(2)
1111 ))
1112 }
1113
1114 #[test]
1115 fn test_regular_algorithm() {
1116 let (secret, public) = generate_key_pair();
1117 let signature = secret.sign(&[]);
1118 assert!(signature.verify(&public, Utc::now(), Duration::seconds(10)));
1119 }
1120
1121 #[test]
1122 fn test_prehashed_algorithm() {
1123 let (secret, public) = generate_key_pair();
1124 let header = SignatureHeader {
1125 timestamp: Some(Utc::now()),
1126 signature_algorithm: Some(SignatureAlgorithm::Prehashed),
1127 };
1128 let signature = secret.sign_with_header(&[], &header);
1129 assert!(signature.verify(&public, Utc::now(), Duration::seconds(10)));
1130 }
1131
1132 #[test]
1133 fn test_legacy_signature_can_be_verified() {
1134 #[derive(Serialize)]
1137 struct TestHeader {
1138 #[serde(rename = "t")]
1139 timestamp: Option<DateTime<Utc>>,
1140 }
1141 let header = serde_json::to_string(&TestHeader {
1142 timestamp: Some(Utc::now()),
1143 })
1144 .unwrap();
1145
1146 let data: &[u8] = &[];
1147 let (secret, public) = generate_key_pair();
1148 let mut to_sign = header.clone().into_bytes();
1149 to_sign.push(b'\x00');
1150 to_sign.extend_from_slice(data);
1151 let sig = secret.inner.sign(to_sign.as_slice());
1152 let mut sig_encoded = BASE64URL_NOPAD.encode(sig.to_bytes().as_slice());
1153 sig_encoded.push('.');
1154 sig_encoded.push_str(BASE64URL_NOPAD.encode(header.as_bytes()).as_str());
1155
1156 assert!(public.verify(data, SignatureRef(sig_encoded.as_str())));
1157 }
1158}