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::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
43/// The latest Relay version known to this Relay. This is the current version.
44const LATEST_VERSION: RelayVersion = RelayVersion::new(VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH);
45
46/// The oldest downstream Relay version still supported by this Relay.
47const OLDEST_VERSION: RelayVersion = RelayVersion::new(0, 0, 0); // support all
48
49/// Alias for Relay IDs (UUIDs).
50pub type RelayId = Uuid;
51
52/// The version of a Relay.
53#[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    /// Returns the current Relay version.
62    pub fn current() -> Self {
63        LATEST_VERSION
64    }
65
66    /// Returns the oldest compatible Relay version.
67    ///
68    /// Relays older than this cannot authenticate with this Relay. It is possible for newer Relays
69    /// to authenticate.
70    pub fn oldest() -> Self {
71        OLDEST_VERSION
72    }
73
74    /// Creates a new version with the given components.
75    pub const fn new(major: u8, minor: u8, patch: u8) -> Self {
76        Self {
77            major,
78            minor,
79            patch,
80        }
81    }
82
83    /// Returns `true` if this version is still supported.
84    pub fn supported(self) -> bool {
85        self >= Self::oldest()
86    }
87
88    /// Returns `true` if this version is older than the current version.
89    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/// Raised if Relay cannot parse the provided version.
101#[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/// Raised if a key could not be parsed.
124#[derive(Debug, Eq, Hash, PartialEq, thiserror::Error)]
125pub enum KeyParseError {
126    /// Invalid key encoding.
127    #[error("bad key encoding")]
128    BadEncoding,
129    /// Invalid key data.
130    #[error("bad key data")]
131    BadKey,
132}
133
134/// Raised to indicate failure on unpacking.
135#[derive(Debug, thiserror::Error)]
136pub enum UnpackError {
137    /// Raised if the signature is invalid.
138    #[error("invalid signature on data")]
139    BadSignature,
140    /// Invalid key encoding.
141    #[error("bad key encoding")]
142    BadEncoding,
143    /// Raised if deserializing of data failed.
144    #[error("could not deserialize payload")]
145    BadPayload(#[source] serde_json::Error),
146    /// Raised on unpacking if the data is too old.
147    #[error("signature is too old")]
148    SignatureExpired,
149}
150
151/// Used to tell which algorithm was used for signature creation.
152#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
153pub enum SignatureAlgorithm {
154    /// Regular signature creation which clones the data internally.
155    #[serde(rename = "v0")]
156    Regular,
157    /// Pre-hashed signature which allows incremental hashing.
158    #[serde(rename = "v1")]
159    Prehashed,
160}
161
162/// A wrapper around packed data that adds a timestamp.
163///
164/// This is internally automatically used when data is signed.
165#[derive(Serialize, Deserialize, Debug)]
166pub struct SignatureHeader {
167    /// The timestamp of when the data was packed and signed.
168    #[serde(rename = "t", skip_serializing_if = "Option::is_none")]
169    pub timestamp: Option<DateTime<Utc>>,
170
171    /// Represents how this signature was created and how it needs to be verified.
172    ///
173    /// Defaults to [`SignatureAlgorithm::Regular`] because that was used before the introduction
174    /// of this field.
175    #[serde(rename = "a", skip_serializing_if = "Option::is_none")]
176    pub signature_algorithm: Option<SignatureAlgorithm>,
177}
178
179impl SignatureHeader {
180    /// Checks if the signature expired.
181    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/// Represents the secret key of an Relay.
200///
201/// Secret keys are based on ed25519 but this should be considered an
202/// implementation detail for now.  We only ever represent public keys
203/// on the wire as opaque ascii encoded strings of arbitrary format or length.
204#[derive(Clone)]
205pub struct SecretKey {
206    inner: ed25519_dalek::SigningKey,
207}
208
209/// Represents the final registration.
210#[derive(Serialize, Deserialize, Debug)]
211pub struct Registration {
212    relay_id: RelayId,
213}
214
215/// Creates a digest for signature verification/signing.
216fn 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    /// Signs some data with the secret key and returns the signature.
226    ///
227    /// This is will sign with the default header.
228    pub fn sign(&self, data: &[u8]) -> Signature {
229        self.sign_with_header(data, &SignatureHeader::default())
230    }
231
232    /// Signs some data with the secret key and a specific header and
233    /// then returns the signature.
234    ///
235    /// The default behavior is to attach the timestamp in the header to the
236    /// signature so that old signatures on verification can be rejected.
237    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    /// Packs some serializable data into JSON and signs it with the default header.
263    pub fn pack<S: Serialize>(&self, data: S) -> (Vec<u8>, Signature) {
264        self.pack_with_header(data, &SignatureHeader::default())
265    }
266
267    /// Packs some serializable data into JSON and signs it with the specified header.
268    pub fn pack_with_header<S: Serialize>(
269        &self,
270        data: S,
271        header: &SignatureHeader,
272    ) -> (Vec<u8>, Signature) {
273        // this can only fail if we deal with badly formed data.  In that case we
274        // consider that a panic.  Should not happen.
275        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/// Represents the public key of a Relay.
334///
335/// Public keys are based on ed25519 but this should be considered an
336/// implementation detail for now.  We only ever represent public keys
337/// on the wire as opaque ascii encoded strings of arbitrary format or length.
338#[derive(Clone, Eq, PartialEq)]
339pub struct PublicKey {
340    inner: ed25519_dalek::VerifyingKey,
341}
342
343impl PublicKey {
344    /// Verifies the signature and returns the embedded signature
345    /// header.
346    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    /// Verifies a signature but discards the header.
383    pub fn verify(&self, data: &[u8], sig: SignatureRef<'_>) -> bool {
384        self.verify_meta(data, sig).is_some()
385    }
386
387    /// Verifies a signature and checks the timestamp.
388    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    /// Unpacks signed data and returns it with header.
400    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    /// Unpacks the data and verifies that it's not too old, then
415    /// throws away the wrapper.
416    ///
417    /// If no `max_age` is set, the embedded timestamp does not get validated.
418    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
465/// Generates an Relay ID.
466pub fn generate_relay_id() -> RelayId {
467    Uuid::new_v4()
468}
469
470/// Generates a secret + public key pair.
471pub 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/// An encoded and signed `RegisterState`.
483///
484/// This signature can be used by the upstream server to ensure that the downstream client did not
485/// tamper with the token without keeping state between requests. For more information, see
486/// `RegisterState`.
487///
488/// The format and contents of `SignedRegisterState` are intentionally opaque. Downstream clients
489/// do not need to interpret it, and the upstream can change its contents at any time. Parsing and
490/// validation is only performed on the upstream.
491///
492/// In the current implementation, the serialized state has the format `{state}:{signature}`, where
493/// each component is:
494///  - `state`: A URL-safe base64 encoding of the JSON serialized `RegisterState`.
495///  - `signature`: A URL-safe base64 encoding of the SHA512 HMAC of the encoded state.
496///
497/// To create a signed state, use `RegisterChallenge::sign`. To validate the signature and read
498/// the state, use `SignedRegisterChallenge::unpack`. In both cases, a secret for signing has to be
499/// supplied.
500#[derive(Clone, Debug, Deserialize, Serialize)]
501pub struct SignedRegisterState(String);
502
503impl SignedRegisterState {
504    /// Creates an Hmac instance for signing the `RegisterState`.
505    fn mac(secret: &[u8]) -> Hmac<Sha512> {
506        Hmac::new_from_slice(secret).expect("HMAC takes variable keys")
507    }
508
509    /// Signs the given `RegisterState` and serializes it into a single string.
510    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    /// Splits the signed state into the encoded state and encoded signature.
522    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    /// Returns the string representation of the token.
528    pub fn as_str(&self) -> &str {
529        self.0.as_str()
530    }
531
532    /// Unpacks the encoded state and validates the signature.
533    ///
534    /// If `max_age` is specified, then the timestamp in the state is validated against the current
535    /// time stamp. If the stored timestamp is too old, `UnpackError::SignatureExpired` is returned.
536    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/// A state structure containing relevant information from `RegisterRequest`.
575///
576/// This structure is used to carry over information between the downstream register request and
577/// register response. In addition to identifying information, it contains a random bit to avoid
578/// replay attacks.
579#[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    /// Returns the timestamp at which the challenge was created.
589    pub fn timestamp(&self) -> UnixTimestamp {
590        self.timestamp
591    }
592
593    /// Returns the identifier of the requesting downstream Relay.
594    pub fn relay_id(&self) -> RelayId {
595        self.relay_id
596    }
597
598    /// Returns the public key of the requesting downstream Relay.
599    pub fn public_key(&self) -> &PublicKey {
600        &self.public_key
601    }
602}
603
604/// Generates a new random token for the register state.
605fn 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/// Represents a request for registration with the upstream.
613///
614/// This is created if the Relay signs in for the first time.  The server needs
615/// to respond to this request with a unique token that is then used to sign
616/// the response.
617#[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    /// Creates a new request to register an Relay upstream.
627    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    /// Unpacks a signed register request for bootstrapping.
636    ///
637    /// This unpacks the embedded public key first, then verifies if the
638    /// self signature was made by that public key.  If all is well then
639    /// the data is returned.
640    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    /// Returns the Relay ID of the registering Relay.
651    pub fn relay_id(&self) -> RelayId {
652        self.relay_id
653    }
654
655    /// Returns the new public key of registering Relay.
656    pub fn public_key(&self) -> &PublicKey {
657        &self.public_key
658    }
659
660    /// Creates a register challenge for this request.
661    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/// Represents the response the server is supposed to send to a register request.
677#[derive(Serialize, Deserialize, Debug)]
678pub struct RegisterChallenge {
679    relay_id: RelayId,
680    token: SignedRegisterState,
681}
682
683impl RegisterChallenge {
684    /// Returns the Relay ID of the registering Relay.
685    pub fn relay_id(&self) -> &RelayId {
686        &self.relay_id
687    }
688
689    /// Returns the token that needs signing.
690    pub fn token(&self) -> &str {
691        self.token.as_str()
692    }
693
694    /// Creates a register response.
695    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/// Represents a response to a register challenge.
705///
706/// The response contains the same data as the register challenge. By signing this payload
707/// successfully, this Relay authenticates with the upstream.
708#[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    /// Unpacks the register response and validates signatures.
718    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    /// Returns the Relay ID of the registering Relay.
739    pub fn relay_id(&self) -> RelayId {
740        self.relay_id
741    }
742
743    /// Returns the token that needs signing.
744    pub fn token(&self) -> &str {
745        self.token.as_str()
746    }
747
748    /// Returns the version of the registering Relay.
749    pub fn version(&self) -> RelayVersion {
750        self.version
751    }
752}
753
754/// A wrapper around a String that represents a signature.
755#[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    /// Verifies the signature against any of the provided public keys.
766    ///
767    /// Returns `true` if the signature is valid with one of the given
768    /// public keys and satisfies the timestamp constraints defined by `start_time`
769    /// and `max_age`.
770    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    /// Verifies the signature using the specified public key.
782    ///
783    /// The signature is considered valid if it can be verified using the given
784    /// public key and its embedded timestamp falls within the valid time range,
785    /// starting from `start_time` and not exceeding `max_age`.
786    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    /// Verifies the signature against the given data and public key.
803    ///
804    /// Returns `true` if the signature is valid for the provided `data`
805    /// when verified with the given public key.
806    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    /// Returns a borrowed view of the signature as a `SignatureRef`.
813    ///
814    /// This method provides a lightweight reference wrapper over the internal
815    /// signature data.
816    pub fn as_signature_ref(&self) -> SignatureRef<'_> {
817        SignatureRef(self.0.as_str())
818    }
819}
820
821/// A borrowed reference to a signature string used for validation.
822///
823/// `SignatureRef` provides a view into the signature data as a string slice,
824/// allowing verification to work with borrowed data without unnecessary allocations.
825/// This type is typically obtained by borrowing from an owned [`Signature`].
826pub 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        // initial setup
905        let relay_id = generate_relay_id();
906        let (sk, pk) = generate_key_pair();
907
908        // create a register request
909        let request = RegisterRequest::new(&relay_id, &pk);
910
911        // sign it
912        let (request_bytes, request_sig) = sk.pack(request);
913
914        // attempt to get the data through bootstrap unpacking.
915        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        // create a challenge
927        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        // check the challenge contains the expected info
933        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        // create a response from the challenge
939        let response = challenge.into_response();
940
941        // sign and unsign it
942        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    /// This is a pseudo-test to easily generate the strings used by test_auth.py
957    /// You can copy the output to the top of the test_auth.py when there are changes in the
958    /// exchanged authentication structures.
959    /// It follows test_registration but instead of asserting it prints the strings
960    #[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        // initial setup
967        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        // create a register request
973        let request = RegisterRequest::new(&relay_id, &pk);
974        println!("REQUEST = b'{}'", serde_json::to_string(&request).unwrap());
975
976        // sign it
977        let (request_bytes, request_sig) = sk.pack(&request);
978        println!("REQUEST_SIG = \"{request_sig}\"");
979
980        // attempt to get the data through bootstrap unpacking.
981        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        // create a challenge
991        let challenge = request.into_challenge(upstream_secret);
992        let challenge_token = challenge.token().to_owned();
993        println!("TOKEN = \"{challenge_token}\"");
994
995        // create a response from the challenge
996        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 we can still deserialize an old response that does not contain the version
1007    #[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        // Regression test against unintentional changes.
1028        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        // Every version must be supported at the moment.
1047        // This test can be changed when dropping support for older versions.
1048        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        // The signature is valid in general
1076        assert!(signature.verify(&pair.1, start_time, Duration::seconds(10)));
1077        // Signature is no longer valid because too much time elapsed
1078        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        // Signature still valid after 1 second
1101        assert!(signature.verify_any(
1102            public_keys,
1103            start_time + Duration::seconds(1),
1104            Duration::seconds(2)
1105        ));
1106        // Signature is no longer valid because too much time elapsed
1107        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        // TestHeader struct is used to mimic old version that do not have
1135        // the `signature_variant` fields.
1136        #[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}