relay_auth/
lib.rs

1//! Authentication and crypto for Relay.
2//!
3//! This library contains the [`PublicKey`] and [`SecretKey`] types, which can be used to validate
4//! and sign traffic between Relays in authenticated endpoints. Additionally, Relays identify via a
5//! [`RelayId`], which is included in the request signature and headers.
6//!
7//! Relay uses Ed25519 at the moment. This is considered an implementation detail and is subject to
8//! change at any time. Do not rely on a specific signing mechanism.
9//!
10//! # Generating Credentials
11//!
12//! Use the [`generate_relay_id`] and [`generate_key_pair`] function to generate credentials:
13//!
14//! ```
15//! let relay_id = relay_auth::generate_relay_id();
16//! let (private_key, public_key) = relay_auth::generate_key_pair();
17//! ```
18
19#![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::str::FromStr;
27
28use chrono::{DateTime, Duration, Utc};
29use data_encoding::BASE64URL_NOPAD;
30use ed25519_dalek::{Signer, Verifier};
31use hmac::{Hmac, Mac};
32use rand::rngs::OsRng;
33use rand::{thread_rng, RngCore};
34use relay_common::time::UnixTimestamp;
35use serde::de::DeserializeOwned;
36use serde::{Deserialize, Serialize};
37use sha2::Sha512;
38use uuid::Uuid;
39
40include!(concat!(env!("OUT_DIR"), "/constants.gen.rs"));
41
42/// The latest Relay version known to this Relay. This is the current version.
43const LATEST_VERSION: RelayVersion = RelayVersion::new(VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH);
44
45/// The oldest downstream Relay version still supported by this Relay.
46const OLDEST_VERSION: RelayVersion = RelayVersion::new(0, 0, 0); // support all
47
48/// Alias for Relay IDs (UUIDs).
49pub type RelayId = Uuid;
50
51/// The version of a Relay.
52#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
53pub struct RelayVersion {
54    major: u8,
55    minor: u8,
56    patch: u8,
57}
58
59impl RelayVersion {
60    /// Returns the current Relay version.
61    pub fn current() -> Self {
62        LATEST_VERSION
63    }
64
65    /// Returns the oldest compatible Relay version.
66    ///
67    /// Relays older than this cannot authenticate with this Relay. It is possible for newer Relays
68    /// to authenticate.
69    pub fn oldest() -> Self {
70        OLDEST_VERSION
71    }
72
73    /// Creates a new version with the given components.
74    pub const fn new(major: u8, minor: u8, patch: u8) -> Self {
75        Self {
76            major,
77            minor,
78            patch,
79        }
80    }
81
82    /// Returns `true` if this version is still supported.
83    pub fn supported(self) -> bool {
84        self >= Self::oldest()
85    }
86
87    /// Returns `true` if this version is older than the current version.
88    pub fn outdated(self) -> bool {
89        self < Self::current()
90    }
91}
92
93impl fmt::Display for RelayVersion {
94    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95        write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
96    }
97}
98
99/// Raised if Relay cannot parse the provided version.
100#[derive(Clone, Copy, Debug, Default, thiserror::Error)]
101#[error("invalid relay version string")]
102pub struct ParseRelayVersionError;
103
104impl FromStr for RelayVersion {
105    type Err = ParseRelayVersionError;
106
107    fn from_str(s: &str) -> Result<Self, Self::Err> {
108        let mut iter = s
109            .split(&['.', '-'][..])
110            .map(|s| s.parse().map_err(|_| ParseRelayVersionError));
111
112        let major = iter.next().ok_or(ParseRelayVersionError)??;
113        let minor = iter.next().ok_or(ParseRelayVersionError)??;
114        let patch = iter.next().ok_or(ParseRelayVersionError)??;
115
116        Ok(Self::new(major, minor, patch))
117    }
118}
119
120relay_common::impl_str_serde!(RelayVersion, "a version string");
121
122/// Raised if a key could not be parsed.
123#[derive(Debug, Eq, Hash, PartialEq, thiserror::Error)]
124pub enum KeyParseError {
125    /// Invalid key encoding.
126    #[error("bad key encoding")]
127    BadEncoding,
128    /// Invalid key data.
129    #[error("bad key data")]
130    BadKey,
131}
132
133/// Raised to indicate failure on unpacking.
134#[derive(Debug, thiserror::Error)]
135pub enum UnpackError {
136    /// Raised if the signature is invalid.
137    #[error("invalid signature on data")]
138    BadSignature,
139    /// Invalid key encoding.
140    #[error("bad key encoding")]
141    BadEncoding,
142    /// Raised if deserializing of data failed.
143    #[error("could not deserialize payload")]
144    BadPayload(#[source] serde_json::Error),
145    /// Raised on unpacking if the data is too old.
146    #[error("signature is too old")]
147    SignatureExpired,
148}
149
150/// A wrapper around packed data that adds a timestamp.
151///
152/// This is internally automatically used when data is signed.
153#[derive(Serialize, Deserialize, Debug)]
154pub struct SignatureHeader {
155    /// The timestamp of when the data was packed and signed.
156    #[serde(rename = "t", skip_serializing_if = "Option::is_none")]
157    pub timestamp: Option<DateTime<Utc>>,
158}
159
160impl SignatureHeader {
161    /// Checks if the signature expired.
162    pub fn expired(&self, max_age: Duration) -> bool {
163        if let Some(ts) = self.timestamp {
164            ts < (Utc::now() - max_age)
165        } else {
166            false
167        }
168    }
169}
170
171impl Default for SignatureHeader {
172    fn default() -> SignatureHeader {
173        SignatureHeader {
174            timestamp: Some(Utc::now()),
175        }
176    }
177}
178
179/// Represents the secret key of an Relay.
180///
181/// Secret keys are based on ed25519 but this should be considered an
182/// implementation detail for now.  We only ever represent public keys
183/// on the wire as opaque ascii encoded strings of arbitrary format or length.
184#[derive(Clone)]
185pub struct SecretKey {
186    inner: ed25519_dalek::SigningKey,
187}
188
189/// Represents the final registration.
190#[derive(Serialize, Deserialize, Debug)]
191pub struct Registration {
192    relay_id: RelayId,
193}
194
195impl SecretKey {
196    /// Signs some data with the secret key and returns the signature.
197    ///
198    /// This is will sign with the default header.
199    pub fn sign(&self, data: &[u8]) -> String {
200        self.sign_with_header(data, &SignatureHeader::default())
201    }
202
203    /// Signs some data with the secret key and a specific header and
204    /// then returns the signature.
205    ///
206    /// The default behavior is to attach the timestamp in the header to the
207    /// signature so that old signatures on verification can be rejected.
208    pub fn sign_with_header(&self, data: &[u8], header: &SignatureHeader) -> String {
209        let mut header =
210            serde_json::to_vec(&header).expect("attempted to pack non json safe header");
211        let header_encoded = BASE64URL_NOPAD.encode(&header);
212        header.push(b'\x00');
213        header.extend_from_slice(data);
214        let sig = self.inner.sign(&header);
215        let mut sig_encoded = BASE64URL_NOPAD.encode(&sig.to_bytes());
216        sig_encoded.push('.');
217        sig_encoded.push_str(&header_encoded);
218        sig_encoded
219    }
220
221    /// Packs some serializable data into JSON and signs it with the default header.
222    pub fn pack<S: Serialize>(&self, data: S) -> (Vec<u8>, String) {
223        self.pack_with_header(data, &SignatureHeader::default())
224    }
225
226    /// Packs some serializable data into JSON and signs it with the specified header.
227    pub fn pack_with_header<S: Serialize>(
228        &self,
229        data: S,
230        header: &SignatureHeader,
231    ) -> (Vec<u8>, String) {
232        // this can only fail if we deal with badly formed data.  In that case we
233        // consider that a panic.  Should not happen.
234        let json = serde_json::to_vec(&data).expect("attempted to pack non json safe data");
235        let sig = self.sign_with_header(&json, header);
236        (json, sig)
237    }
238}
239
240impl PartialEq for SecretKey {
241    fn eq(&self, other: &SecretKey) -> bool {
242        self.inner.to_keypair_bytes() == other.inner.to_keypair_bytes()
243    }
244}
245
246impl Eq for SecretKey {}
247
248impl FromStr for SecretKey {
249    type Err = KeyParseError;
250
251    fn from_str(s: &str) -> Result<SecretKey, KeyParseError> {
252        let bytes = match BASE64URL_NOPAD.decode(s.as_bytes()) {
253            Ok(bytes) => bytes,
254            _ => return Err(KeyParseError::BadEncoding),
255        };
256
257        let inner = if let Ok(keypair) = bytes.as_slice().try_into() {
258            ed25519_dalek::SigningKey::from_keypair_bytes(&keypair)
259                .map_err(|_| KeyParseError::BadKey)?
260        } else if let Ok(secret_key) = bytes.try_into() {
261            ed25519_dalek::SigningKey::from_bytes(&secret_key)
262        } else {
263            return Err(KeyParseError::BadKey);
264        };
265
266        Ok(SecretKey { inner })
267    }
268}
269
270impl fmt::Display for SecretKey {
271    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
272        if f.alternate() {
273            write!(
274                f,
275                "{}",
276                BASE64URL_NOPAD.encode(&self.inner.to_keypair_bytes())
277            )
278        } else {
279            write!(f, "{}", BASE64URL_NOPAD.encode(&self.inner.to_bytes()))
280        }
281    }
282}
283
284impl fmt::Debug for SecretKey {
285    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
286        write!(f, "SecretKey(\"{self}\")")
287    }
288}
289
290relay_common::impl_str_serde!(SecretKey, "a secret key");
291
292/// Represents the public key of a Relay.
293///
294/// Public keys are based on ed25519 but this should be considered an
295/// implementation detail for now.  We only ever represent public keys
296/// on the wire as opaque ascii encoded strings of arbitrary format or length.
297#[derive(Clone, Eq, PartialEq)]
298pub struct PublicKey {
299    inner: ed25519_dalek::VerifyingKey,
300}
301
302impl PublicKey {
303    /// Verifies the signature and returns the embedded signature
304    /// header.
305    pub fn verify_meta(&self, data: &[u8], sig: &str) -> Option<SignatureHeader> {
306        let mut iter = sig.splitn(2, '.');
307        let sig_bytes = match iter.next() {
308            Some(sig_encoded) => BASE64URL_NOPAD.decode(sig_encoded.as_bytes()).ok()?,
309            None => return None,
310        };
311        let sig = ed25519_dalek::Signature::from_slice(&sig_bytes).ok()?;
312
313        let header = match iter.next() {
314            Some(header_encoded) => BASE64URL_NOPAD.decode(header_encoded.as_bytes()).ok()?,
315            None => return None,
316        };
317        let mut to_verify = header.clone();
318        to_verify.push(b'\x00');
319        to_verify.extend_from_slice(data);
320        if self.inner.verify(&to_verify, &sig).is_ok() {
321            serde_json::from_slice(&header).ok()
322        } else {
323            None
324        }
325    }
326
327    /// Verifies a signature but discards the header.
328    pub fn verify(&self, data: &[u8], sig: &str) -> bool {
329        self.verify_meta(data, sig).is_some()
330    }
331
332    /// Verifies a signature and checks the timestamp.
333    pub fn verify_timestamp(&self, data: &[u8], sig: &str, max_age: Option<Duration>) -> bool {
334        self.verify_meta(data, sig)
335            .map(|header| max_age.is_none() || !header.expired(max_age.unwrap()))
336            .unwrap_or(false)
337    }
338
339    /// Unpacks signed data and returns it with header.
340    pub fn unpack_meta<D: DeserializeOwned>(
341        &self,
342        data: &[u8],
343        signature: &str,
344    ) -> Result<(SignatureHeader, D), UnpackError> {
345        if let Some(header) = self.verify_meta(data, signature) {
346            serde_json::from_slice(data)
347                .map(|data| (header, data))
348                .map_err(UnpackError::BadPayload)
349        } else {
350            Err(UnpackError::BadSignature)
351        }
352    }
353
354    /// Unpacks the data and verifies that it's not too old, then
355    /// throws away the wrapper.
356    ///
357    /// If no `max_age` is set, the embedded timestamp does not get validated.
358    pub fn unpack<D: DeserializeOwned>(
359        &self,
360        data: &[u8],
361        signature: &str,
362        max_age: Option<Duration>,
363    ) -> Result<D, UnpackError> {
364        let (header, data) = self.unpack_meta(data, signature)?;
365        if max_age.is_none() || !header.expired(max_age.unwrap()) {
366            Ok(data)
367        } else {
368            Err(UnpackError::SignatureExpired)
369        }
370    }
371}
372
373impl FromStr for PublicKey {
374    type Err = KeyParseError;
375
376    fn from_str(s: &str) -> Result<PublicKey, KeyParseError> {
377        let Ok(bytes) = BASE64URL_NOPAD.decode(s.as_bytes()) else {
378            return Err(KeyParseError::BadEncoding);
379        };
380
381        let inner = match bytes.try_into() {
382            Ok(bytes) => ed25519_dalek::VerifyingKey::from_bytes(&bytes)
383                .map_err(|_| KeyParseError::BadKey)?,
384            Err(_) => return Err(KeyParseError::BadKey),
385        };
386
387        Ok(PublicKey { inner })
388    }
389}
390
391impl fmt::Display for PublicKey {
392    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
393        write!(f, "{}", BASE64URL_NOPAD.encode(&self.inner.to_bytes()))
394    }
395}
396
397impl fmt::Debug for PublicKey {
398    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
399        write!(f, "PublicKey(\"{self}\")")
400    }
401}
402
403relay_common::impl_str_serde!(PublicKey, "a public key");
404
405/// Generates an Relay ID.
406pub fn generate_relay_id() -> RelayId {
407    Uuid::new_v4()
408}
409
410/// Generates a secret + public key pair.
411pub fn generate_key_pair() -> (SecretKey, PublicKey) {
412    let mut csprng = OsRng;
413    let kp = ed25519_dalek::SigningKey::generate(&mut csprng);
414    let pk = kp.verifying_key();
415    (SecretKey { inner: kp }, PublicKey { inner: pk })
416}
417
418/// An encoded and signed `RegisterState`.
419///
420/// This signature can be used by the upstream server to ensure that the downstream client did not
421/// tamper with the token without keeping state between requests. For more information, see
422/// `RegisterState`.
423///
424/// The format and contents of `SignedRegisterState` are intentionally opaque. Downstream clients
425/// do not need to interpret it, and the upstream can change its contents at any time. Parsing and
426/// validation is only performed on the upstream.
427///
428/// In the current implementation, the serialized state has the format `{state}:{signature}`, where
429/// each component is:
430///  - `state`: A URL-safe base64 encoding of the JSON serialized `RegisterState`.
431///  - `signature`: A URL-safe base64 encoding of the SHA512 HMAC of the encoded state.
432///
433/// To create a signed state, use `RegisterChallenge::sign`. To validate the signature and read
434/// the state, use `SignedRegisterChallenge::unpack`. In both cases, a secret for signing has to be
435/// supplied.
436#[derive(Clone, Debug, Deserialize, Serialize)]
437pub struct SignedRegisterState(String);
438
439impl SignedRegisterState {
440    /// Creates an Hmac instance for signing the `RegisterState`.
441    fn mac(secret: &[u8]) -> Hmac<Sha512> {
442        Hmac::new_from_slice(secret).expect("HMAC takes variable keys")
443    }
444
445    /// Signs the given `RegisterState` and serializes it into a single string.
446    fn sign(state: RegisterState, secret: &[u8]) -> Self {
447        let json = serde_json::to_string(&state).expect("relay register state serializes to JSON");
448        let token = BASE64URL_NOPAD.encode(json.as_bytes());
449
450        let mut mac = Self::mac(secret);
451        mac.update(token.as_bytes());
452        let signature = BASE64URL_NOPAD.encode(&mac.finalize().into_bytes());
453
454        Self(format!("{token}:{signature}"))
455    }
456
457    /// Splits the signed state into the encoded state and encoded signature.
458    fn split(&self) -> (&str, &str) {
459        let mut split = self.as_str().splitn(2, ':');
460        (split.next().unwrap_or(""), split.next().unwrap_or(""))
461    }
462
463    /// Returns the string representation of the token.
464    pub fn as_str(&self) -> &str {
465        self.0.as_str()
466    }
467
468    /// Unpacks the encoded state and validates the signature.
469    ///
470    /// If `max_age` is specified, then the timestamp in the state is validated against the current
471    /// time stamp. If the stored timestamp is too old, `UnpackError::SignatureExpired` is returned.
472    pub fn unpack(
473        &self,
474        secret: &[u8],
475        max_age: Option<Duration>,
476    ) -> Result<RegisterState, UnpackError> {
477        let (token, signature) = self.split();
478        let code = BASE64URL_NOPAD
479            .decode(signature.as_bytes())
480            .map_err(|_| UnpackError::BadEncoding)?;
481
482        let mut mac = Self::mac(secret);
483        mac.update(token.as_bytes());
484        mac.verify_slice(&code)
485            .map_err(|_| UnpackError::BadSignature)?;
486
487        let json = BASE64URL_NOPAD
488            .decode(token.as_bytes())
489            .map_err(|_| UnpackError::BadEncoding)?;
490        let state =
491            serde_json::from_slice::<RegisterState>(&json).map_err(UnpackError::BadPayload)?;
492
493        if let Some(max_age) = max_age {
494            let secs = state.timestamp().as_secs() as i64;
495            if secs + max_age.num_seconds() < Utc::now().timestamp() {
496                return Err(UnpackError::SignatureExpired);
497            }
498        }
499
500        Ok(state)
501    }
502}
503
504impl fmt::Display for SignedRegisterState {
505    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
506        self.as_str().fmt(f)
507    }
508}
509
510/// A state structure containing relevant information from `RegisterRequest`.
511///
512/// This structure is used to carry over information between the downstream register request and
513/// register response. In addition to identifying information, it contains a random bit to avoid
514/// replay attacks.
515#[derive(Clone, Deserialize, Serialize)]
516pub struct RegisterState {
517    timestamp: UnixTimestamp,
518    relay_id: RelayId,
519    public_key: PublicKey,
520    rand: String,
521}
522
523impl RegisterState {
524    /// Returns the timestamp at which the challenge was created.
525    pub fn timestamp(&self) -> UnixTimestamp {
526        self.timestamp
527    }
528
529    /// Returns the identifier of the requesting downstream Relay.
530    pub fn relay_id(&self) -> RelayId {
531        self.relay_id
532    }
533
534    /// Returns the public key of the requesting downstream Relay.
535    pub fn public_key(&self) -> &PublicKey {
536        &self.public_key
537    }
538}
539
540/// Generates a new random token for the register state.
541fn nonce() -> String {
542    let mut rng = thread_rng();
543    let mut bytes = vec![0u8; 64];
544    rng.fill_bytes(&mut bytes);
545    BASE64URL_NOPAD.encode(&bytes)
546}
547
548/// Represents a request for registration with the upstream.
549///
550/// This is created if the Relay signs in for the first time.  The server needs
551/// to respond to this request with a unique token that is then used to sign
552/// the response.
553#[derive(Serialize, Deserialize, Debug)]
554pub struct RegisterRequest {
555    relay_id: RelayId,
556    public_key: PublicKey,
557    #[serde(default)]
558    version: RelayVersion,
559}
560
561impl RegisterRequest {
562    /// Creates a new request to register an Relay upstream.
563    pub fn new(relay_id: &RelayId, public_key: &PublicKey) -> RegisterRequest {
564        RegisterRequest {
565            relay_id: *relay_id,
566            public_key: public_key.clone(),
567            version: RelayVersion::current(),
568        }
569    }
570
571    /// Unpacks a signed register request for bootstrapping.
572    ///
573    /// This unpacks the embedded public key first, then verifies if the
574    /// self signature was made by that public key.  If all is well then
575    /// the data is returned.
576    pub fn bootstrap_unpack(
577        data: &[u8],
578        signature: &str,
579        max_age: Option<Duration>,
580    ) -> Result<RegisterRequest, UnpackError> {
581        let req: RegisterRequest = serde_json::from_slice(data).map_err(UnpackError::BadPayload)?;
582        let pk = req.public_key();
583        pk.unpack(data, signature, max_age)
584    }
585
586    /// Returns the Relay ID of the registering Relay.
587    pub fn relay_id(&self) -> RelayId {
588        self.relay_id
589    }
590
591    /// Returns the new public key of registering Relay.
592    pub fn public_key(&self) -> &PublicKey {
593        &self.public_key
594    }
595
596    /// Creates a register challenge for this request.
597    pub fn into_challenge(self, secret: &[u8]) -> RegisterChallenge {
598        let state = RegisterState {
599            timestamp: UnixTimestamp::now(),
600            relay_id: self.relay_id,
601            public_key: self.public_key,
602            rand: nonce(),
603        };
604
605        RegisterChallenge {
606            relay_id: self.relay_id,
607            token: SignedRegisterState::sign(state, secret),
608        }
609    }
610}
611
612/// Represents the response the server is supposed to send to a register request.
613#[derive(Serialize, Deserialize, Debug)]
614pub struct RegisterChallenge {
615    relay_id: RelayId,
616    token: SignedRegisterState,
617}
618
619impl RegisterChallenge {
620    /// Returns the Relay ID of the registering Relay.
621    pub fn relay_id(&self) -> &RelayId {
622        &self.relay_id
623    }
624
625    /// Returns the token that needs signing.
626    pub fn token(&self) -> &str {
627        self.token.as_str()
628    }
629
630    /// Creates a register response.
631    pub fn into_response(self) -> RegisterResponse {
632        RegisterResponse {
633            relay_id: self.relay_id,
634            token: self.token,
635            version: RelayVersion::current(),
636        }
637    }
638}
639
640/// Represents a response to a register challenge.
641///
642/// The response contains the same data as the register challenge. By signing this payload
643/// successfully, this Relay authenticates with the upstream.
644#[derive(Serialize, Deserialize, Debug)]
645pub struct RegisterResponse {
646    relay_id: RelayId,
647    token: SignedRegisterState,
648    #[serde(default)]
649    version: RelayVersion,
650}
651
652impl RegisterResponse {
653    /// Unpacks the register response and validates signatures.
654    pub fn unpack(
655        data: &[u8],
656        signature: &str,
657        secret: &[u8],
658        max_age: Option<Duration>,
659    ) -> Result<(Self, RegisterState), UnpackError> {
660        let response: Self = serde_json::from_slice(data).map_err(UnpackError::BadPayload)?;
661        let state = response.token.unpack(secret, max_age)?;
662
663        if let Some(header) = state.public_key().verify_meta(data, signature) {
664            if max_age.is_some_and(|m| header.expired(m)) {
665                return Err(UnpackError::SignatureExpired);
666            }
667        } else {
668            return Err(UnpackError::BadSignature);
669        }
670
671        Ok((response, state))
672    }
673
674    /// Returns the Relay ID of the registering Relay.
675    pub fn relay_id(&self) -> RelayId {
676        self.relay_id
677    }
678
679    /// Returns the token that needs signing.
680    pub fn token(&self) -> &str {
681        self.token.as_str()
682    }
683
684    /// Returns the version of the registering Relay.
685    pub fn version(&self) -> RelayVersion {
686        self.version
687    }
688}
689
690#[cfg(test)]
691mod tests {
692    use super::*;
693
694    #[test]
695    fn test_keys() {
696        let sk: SecretKey =
697        "OvXFVm1tIUi8xDTuyHX1SSqdMc8nCt2qU9IUaH5p7oUk5pHZsdnfXNiMWiMLtSE86J3N9Peo5CBP1YQHDUkApQ"
698            .parse()
699            .unwrap();
700        let pk: PublicKey = "JOaR2bHZ31zYjFojC7UhPOidzfT3qOQgT9WEBw1JAKU"
701            .parse()
702            .unwrap();
703
704        assert_eq!(
705            sk.to_string(),
706            "OvXFVm1tIUi8xDTuyHX1SSqdMc8nCt2qU9IUaH5p7oU"
707        );
708        assert_eq!(
709        format!("{sk:#}"),
710        "OvXFVm1tIUi8xDTuyHX1SSqdMc8nCt2qU9IUaH5p7oUk5pHZsdnfXNiMWiMLtSE86J3N9Peo5CBP1YQHDUkApQ"
711    );
712        assert_eq!(
713            pk.to_string(),
714            "JOaR2bHZ31zYjFojC7UhPOidzfT3qOQgT9WEBw1JAKU"
715        );
716
717        assert_eq!(
718            "bad data".parse::<SecretKey>(),
719            Err(KeyParseError::BadEncoding)
720        );
721        assert_eq!("OvXF".parse::<SecretKey>(), Err(KeyParseError::BadKey));
722
723        assert_eq!(
724            "bad data".parse::<PublicKey>(),
725            Err(KeyParseError::BadEncoding)
726        );
727        assert_eq!("OvXF".parse::<PublicKey>(), Err(KeyParseError::BadKey));
728    }
729
730    #[test]
731    fn test_serializing() {
732        let sk: SecretKey =
733        "OvXFVm1tIUi8xDTuyHX1SSqdMc8nCt2qU9IUaH5p7oUk5pHZsdnfXNiMWiMLtSE86J3N9Peo5CBP1YQHDUkApQ"
734            .parse()
735            .unwrap();
736        let pk: PublicKey = "JOaR2bHZ31zYjFojC7UhPOidzfT3qOQgT9WEBw1JAKU"
737            .parse()
738            .unwrap();
739
740        let sk_json = serde_json::to_string(&sk).unwrap();
741        assert_eq!(sk_json, "\"OvXFVm1tIUi8xDTuyHX1SSqdMc8nCt2qU9IUaH5p7oU\"");
742
743        let pk_json = serde_json::to_string(&pk).unwrap();
744        assert_eq!(pk_json, "\"JOaR2bHZ31zYjFojC7UhPOidzfT3qOQgT9WEBw1JAKU\"");
745
746        assert_eq!(serde_json::from_str::<SecretKey>(&sk_json).unwrap(), sk);
747        assert_eq!(serde_json::from_str::<PublicKey>(&pk_json).unwrap(), pk);
748    }
749
750    #[test]
751    fn test_signatures() {
752        let (sk, pk) = generate_key_pair();
753        let data = b"Hello World!";
754
755        let sig = sk.sign(data);
756        assert!(pk.verify(data, &sig));
757
758        let bad_sig =
759        "jgubwSf2wb2wuiRpgt2H9_bdDSMr88hXLp5zVuhbr65EGkSxOfT5ILIWr623twLgLd0bDgHg6xzOaUCX7XvUCw";
760        assert!(!pk.verify(data, bad_sig));
761    }
762
763    #[test]
764    fn test_registration() {
765        let max_age = Duration::minutes(15);
766
767        // initial setup
768        let relay_id = generate_relay_id();
769        let (sk, pk) = generate_key_pair();
770
771        // create a register request
772        let request = RegisterRequest::new(&relay_id, &pk);
773
774        // sign it
775        let (request_bytes, request_sig) = sk.pack(request);
776
777        // attempt to get the data through bootstrap unpacking.
778        let request =
779            RegisterRequest::bootstrap_unpack(&request_bytes, &request_sig, Some(max_age)).unwrap();
780        assert_eq!(request.relay_id(), relay_id);
781        assert_eq!(request.public_key(), &pk);
782
783        let upstream_secret = b"secret";
784
785        // create a challenge
786        let challenge = request.into_challenge(upstream_secret);
787        let challenge_token = challenge.token().to_owned();
788        assert_eq!(challenge.relay_id(), &relay_id);
789        assert!(challenge.token().len() > 40);
790
791        // check the challenge contains the expected info
792        let state = SignedRegisterState(challenge_token.clone());
793        let register_state = state.unpack(upstream_secret, None).unwrap();
794        assert_eq!(register_state.public_key, pk);
795        assert_eq!(register_state.relay_id, relay_id);
796
797        // create a response from the challenge
798        let response = challenge.into_response();
799
800        // sign and unsign it
801        let (response_bytes, response_sig) = sk.pack(response);
802        let (response, _) = RegisterResponse::unpack(
803            &response_bytes,
804            &response_sig,
805            upstream_secret,
806            Some(max_age),
807        )
808        .unwrap();
809
810        assert_eq!(response.relay_id(), relay_id);
811        assert_eq!(response.token(), challenge_token);
812        assert_eq!(response.version, LATEST_VERSION);
813    }
814
815    /// This is a pseudo-test to easily generate the strings used by test_auth.py
816    /// You can copy the output to the top of the test_auth.py when there are changes in the
817    /// exchanged authentication structures.
818    /// It follows test_registration but instead of asserting it prints the strings
819    #[test]
820    #[allow(clippy::print_stdout, reason = "helper test to generate output")]
821    fn test_generate_strings_for_test_auth_py() {
822        let max_age = Duration::minutes(15);
823        println!("Generating test data for test_auth.py...");
824
825        // initial setup
826        let relay_id = generate_relay_id();
827        println!("RELAY_ID = b\"{relay_id}\"");
828        let (sk, pk) = generate_key_pair();
829        println!("RELAY_KEY = b\"{pk}\"");
830
831        // create a register request
832        let request = RegisterRequest::new(&relay_id, &pk);
833        println!("REQUEST = b'{}'", serde_json::to_string(&request).unwrap());
834
835        // sign it
836        let (request_bytes, request_sig) = sk.pack(&request);
837        println!("REQUEST_SIG = \"{request_sig}\"");
838
839        // attempt to get the data through bootstrap unpacking.
840        let request =
841            RegisterRequest::bootstrap_unpack(&request_bytes, &request_sig, Some(max_age)).unwrap();
842
843        let upstream_secret = b"secret";
844
845        // create a challenge
846        let challenge = request.into_challenge(upstream_secret);
847        let challenge_token = challenge.token().to_owned();
848        println!("TOKEN = \"{challenge_token}\"");
849
850        // create a response from the challenge
851        let response = challenge.into_response();
852        let serialized_response = serde_json::to_string(&response).unwrap();
853        let (_, response_sig) = sk.pack(&response);
854
855        println!("RESPONSE = b'{serialized_response}'");
856        println!("RESPONSE_SIG = \"{response_sig}\"");
857
858        println!("RELAY_VERSION = \"{LATEST_VERSION}\"");
859    }
860
861    /// Test we can still deserialize an old response that does not contain the version
862    #[test]
863    fn test_deserialize_old_response() {
864        let serialized_challenge = "{\"relay_id\":\"6b7d15b8-cee2-4354-9fee-dae7ef43e434\",\"token\":\"eyJ0aW1lc3RhbXAiOjE1OTg5Njc0MzQsInJlbGF5X2lkIjoiNmI3ZDE1YjgtY2VlMi00MzU0LTlmZWUtZGFlN2VmNDNlNDM0IiwicHVibGljX2tleSI6ImtNcEdieWRIWlN2b2h6ZU1sZ2hjV3dIZDhNa3JlS0d6bF9uY2RrWlNPTWciLCJyYW5kIjoiLUViNG9Hal80dUZYOUNRRzFBVmdqTjRmdGxaNU9DSFlNOFl2d1podmlyVXhUY0tFSWYtQzhHaldsZmgwQTNlMzYxWE01dVh0RHhvN00tbWhZeXpWUWcifQ:KJUDXlwvibKNQmex-_Cu1U0FArlmoDkyqP7bYIDGrLXudfjGfCjH-UjNsUHWVDnbM28YdQ-R2MBSyF51aRLQcw\"}";
865        let result: RegisterResponse = serde_json::from_str(serialized_challenge).unwrap();
866        assert_eq!(
867            result.relay_id,
868            Uuid::parse_str("6b7d15b8-cee2-4354-9fee-dae7ef43e434").unwrap()
869        )
870    }
871
872    #[test]
873    fn test_relay_version_current() {
874        assert_eq!(
875            env!("CARGO_PKG_VERSION"),
876            RelayVersion::current().to_string()
877        );
878    }
879
880    #[test]
881    fn test_relay_version_oldest() {
882        // Regression test against unintentional changes.
883        assert_eq!("0.0.0", RelayVersion::oldest().to_string());
884    }
885
886    #[test]
887    fn test_relay_version_parse() {
888        assert_eq!(
889            RelayVersion::new(20, 7, 0),
890            "20.7.0-beta.0".parse().unwrap()
891        );
892    }
893
894    #[test]
895    fn test_relay_version_oldest_supported() {
896        assert!(RelayVersion::oldest().supported());
897    }
898
899    #[test]
900    fn test_relay_version_any_supported() {
901        // Every version must be supported at the moment.
902        // This test can be changed when dropping support for older versions.
903        assert!(RelayVersion::default().supported());
904    }
905
906    #[test]
907    fn test_relay_version_from_str() {
908        assert_eq!(RelayVersion::new(20, 7, 0), "20.7.0".parse().unwrap());
909    }
910}