Skip to main content

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::pkcs8::{DecodePrivateKey as _, DecodePublicKey as _};
32use ed25519_dalek::{Digest, DigestSigner, DigestVerifier, Signer, Verifier};
33use hmac::{Hmac, Mac};
34use rand::rngs::OsRng;
35use rand::{RngCore as _, TryRngCore as _};
36use relay_common::time::UnixTimestamp;
37use serde::de::DeserializeOwned;
38use serde::{Deserialize, Serialize};
39use sha2::Sha512;
40use uuid::Uuid;
41
42include!(concat!(env!("OUT_DIR"), "/constants.gen.rs"));
43
44/// The latest Relay version known to this Relay. This is the current version.
45const LATEST_VERSION: RelayVersion = RelayVersion::new(VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH);
46
47/// The oldest downstream Relay version still supported by this Relay.
48const OLDEST_VERSION: RelayVersion = RelayVersion::new(0, 0, 0); // support all
49
50/// Alias for Relay IDs (UUIDs).
51pub type RelayId = Uuid;
52
53/// The version of a Relay.
54#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
55pub struct RelayVersion {
56    major: u8,
57    minor: u8,
58    patch: u8,
59}
60
61impl RelayVersion {
62    /// Returns the current Relay version.
63    pub fn current() -> Self {
64        LATEST_VERSION
65    }
66
67    /// Returns the oldest compatible Relay version.
68    ///
69    /// Relays older than this cannot authenticate with this Relay. It is possible for newer Relays
70    /// to authenticate.
71    pub fn oldest() -> Self {
72        OLDEST_VERSION
73    }
74
75    /// Creates a new version with the given components.
76    pub const fn new(major: u8, minor: u8, patch: u8) -> Self {
77        Self {
78            major,
79            minor,
80            patch,
81        }
82    }
83
84    /// Returns `true` if this version is still supported.
85    pub fn supported(self) -> bool {
86        self >= Self::oldest()
87    }
88
89    /// Returns `true` if this version is older than the current version.
90    pub fn outdated(self) -> bool {
91        self < Self::current()
92    }
93}
94
95impl fmt::Display for RelayVersion {
96    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97        write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
98    }
99}
100
101/// Raised if Relay cannot parse the provided version.
102#[derive(Clone, Copy, Debug, Default, thiserror::Error)]
103#[error("invalid relay version string")]
104pub struct ParseRelayVersionError;
105
106impl FromStr for RelayVersion {
107    type Err = ParseRelayVersionError;
108
109    fn from_str(s: &str) -> Result<Self, Self::Err> {
110        let mut iter = s
111            .split(&['.', '-'][..])
112            .map(|s| s.parse().map_err(|_| ParseRelayVersionError));
113
114        let major = iter.next().ok_or(ParseRelayVersionError)??;
115        let minor = iter.next().ok_or(ParseRelayVersionError)??;
116        let patch = iter.next().ok_or(ParseRelayVersionError)??;
117
118        Ok(Self::new(major, minor, patch))
119    }
120}
121
122relay_common::impl_str_serde!(RelayVersion, "a version string");
123
124/// Raised if a key could not be parsed.
125#[derive(Debug, Eq, Hash, PartialEq, thiserror::Error)]
126pub enum KeyParseError {
127    /// Invalid key encoding.
128    #[error("bad key encoding")]
129    BadEncoding,
130    /// Invalid key data.
131    #[error("bad key data")]
132    BadKey,
133}
134
135/// Raised to indicate errors when verifying a signature.
136#[derive(Debug, thiserror::Error, PartialEq, Eq)]
137pub enum SignatureError {
138    /// Raised if the signature is structurally invalid.
139    #[error("invalid signature")]
140    Invalid,
141    /// Raised if the signature is structurally valid but cannot be verified.
142    #[error("signature cannot be verified")]
143    Unverifiable,
144    /// Raised if the signature timestamp cannot be verified.
145    #[error("signature is too old")]
146    Expired,
147}
148
149/// Raised to indicate failure on unpacking.
150#[derive(Debug, thiserror::Error)]
151pub enum UnpackError {
152    /// Raised if the signature is invalid.
153    #[error("invalid signature on data")]
154    BadSignature,
155    /// Invalid key encoding.
156    #[error("bad key encoding")]
157    BadEncoding,
158    /// Raised if deserializing of data failed.
159    #[error("could not deserialize payload")]
160    BadPayload(#[source] serde_json::Error),
161    /// Raised on unpacking if the data is too old.
162    #[error("signature is too old")]
163    SignatureExpired,
164}
165
166impl From<SignatureError> for UnpackError {
167    fn from(value: SignatureError) -> Self {
168        match value {
169            SignatureError::Invalid | SignatureError::Unverifiable => Self::BadSignature,
170            SignatureError::Expired => Self::SignatureExpired,
171        }
172    }
173}
174
175/// Used to tell which algorithm was used for signature creation.
176#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
177pub enum SignatureAlgorithm {
178    /// Regular signature creation which clones the data internally.
179    #[serde(rename = "v0")]
180    Regular,
181    /// Pre-hashed signature which allows incremental hashing.
182    #[serde(rename = "v1")]
183    Prehashed,
184}
185
186/// A wrapper around packed data that adds a timestamp.
187///
188/// This is internally automatically used when data is signed.
189#[derive(Serialize, Deserialize, Debug)]
190pub struct SignatureHeader {
191    /// The timestamp of when the data was packed and signed.
192    #[serde(rename = "t")]
193    pub timestamp: DateTime<Utc>,
194
195    /// Represents how this signature was created and how it needs to be verified.
196    ///
197    /// Defaults to [`SignatureAlgorithm::Regular`] because that was used before the introduction
198    /// of this field.
199    #[serde(rename = "a", skip_serializing_if = "Option::is_none")]
200    pub signature_algorithm: Option<SignatureAlgorithm>,
201}
202
203impl Default for SignatureHeader {
204    fn default() -> SignatureHeader {
205        SignatureHeader {
206            timestamp: Utc::now(),
207            signature_algorithm: None,
208        }
209    }
210}
211
212/// A [`SignatureHeader`] which has been verified.
213#[derive(Debug)]
214pub struct VerifiedSignatureHeader {
215    timestamp: DateTime<Utc>,
216    signature_algorithm: SignatureAlgorithm,
217}
218
219impl VerifiedSignatureHeader {
220    /// Returns the [`SignatureHeader::timestamp`] of the verified header.
221    pub fn timestamp(&self) -> DateTime<Utc> {
222        self.timestamp
223    }
224
225    /// Returns the [`SignatureHeader::signature_algorithm`] of the verified header.
226    pub fn signature_algorithm(&self) -> SignatureAlgorithm {
227        self.signature_algorithm
228    }
229}
230
231/// Represents the secret key of an Relay.
232///
233/// Secret keys are based on ed25519 but this should be considered an
234/// implementation detail for now.  We only ever represent public keys
235/// on the wire as opaque ascii encoded strings of arbitrary format or length.
236#[derive(Clone)]
237pub struct SecretKey {
238    inner: ed25519_dalek::SigningKey,
239}
240
241/// Represents the final registration.
242#[derive(Serialize, Deserialize, Debug)]
243pub struct Registration {
244    relay_id: RelayId,
245}
246
247/// Creates a digest for signature verification/signing.
248fn create_digest(header: &[u8], data: &[u8]) -> Sha512 {
249    let mut digest = Sha512::default();
250    digest.update(header);
251    digest.update(b"\x00");
252    digest.update(data);
253    digest
254}
255
256impl SecretKey {
257    /// Signs some data with the secret key and returns the signature.
258    ///
259    /// This is will sign with the default header.
260    pub fn sign(&self, data: &[u8]) -> Signature {
261        self.sign_with_header(data, &SignatureHeader::default())
262    }
263
264    /// Signs some data with the secret key and a specific header and
265    /// then returns the signature.
266    ///
267    /// The default behavior is to attach the timestamp in the header to the
268    /// signature so that old signatures on verification can be rejected.
269    pub fn sign_with_header(&self, data: &[u8], sig_header: &SignatureHeader) -> Signature {
270        let mut header =
271            serde_json::to_vec(&sig_header).expect("attempted to pack non json safe header");
272        let header_encoded = BASE64URL_NOPAD.encode(&header);
273        let sig = match sig_header
274            .signature_algorithm
275            .unwrap_or(SignatureAlgorithm::Regular)
276        {
277            SignatureAlgorithm::Regular => {
278                header.push(b'\x00');
279                header.extend_from_slice(data);
280                self.inner.sign(&header)
281            }
282            SignatureAlgorithm::Prehashed => {
283                let digest = create_digest(&header, data);
284                self.inner.sign_digest(digest)
285            }
286        };
287
288        let mut sig_encoded = BASE64URL_NOPAD.encode(&sig.to_bytes());
289        sig_encoded.push('.');
290        sig_encoded.push_str(&header_encoded);
291        Signature(sig_encoded)
292    }
293
294    /// Packs some serializable data into JSON and signs it with the default header.
295    pub fn pack<S: Serialize>(&self, data: S) -> (Vec<u8>, Signature) {
296        self.pack_with_header(data, &SignatureHeader::default())
297    }
298
299    /// Packs some serializable data into JSON and signs it with the specified header.
300    pub fn pack_with_header<S: Serialize>(
301        &self,
302        data: S,
303        header: &SignatureHeader,
304    ) -> (Vec<u8>, Signature) {
305        // this can only fail if we deal with badly formed data.  In that case we
306        // consider that a panic.  Should not happen.
307        let json = serde_json::to_vec(&data).expect("attempted to pack non json safe data");
308        let sig = self.sign_with_header(&json, header);
309        (json, sig)
310    }
311}
312
313impl PartialEq for SecretKey {
314    fn eq(&self, other: &SecretKey) -> bool {
315        self.inner.to_keypair_bytes() == other.inner.to_keypair_bytes()
316    }
317}
318
319impl Eq for SecretKey {}
320
321impl FromStr for SecretKey {
322    type Err = KeyParseError;
323
324    fn from_str(s: &str) -> Result<SecretKey, KeyParseError> {
325        if let Ok(inner) = ed25519_dalek::SigningKey::from_pkcs8_pem(s) {
326            return Ok(Self { inner });
327        }
328
329        let bytes = match BASE64URL_NOPAD.decode(s.as_bytes()) {
330            Ok(bytes) => bytes,
331            _ => return Err(KeyParseError::BadEncoding),
332        };
333
334        let inner = if let Ok(keypair) = bytes.as_slice().try_into() {
335            ed25519_dalek::SigningKey::from_keypair_bytes(&keypair)
336                .map_err(|_| KeyParseError::BadKey)?
337        } else if let Ok(secret_key) = bytes.try_into() {
338            ed25519_dalek::SigningKey::from_bytes(&secret_key)
339        } else {
340            return Err(KeyParseError::BadKey);
341        };
342
343        Ok(SecretKey { inner })
344    }
345}
346
347impl fmt::Display for SecretKey {
348    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
349        if f.alternate() {
350            write!(
351                f,
352                "{}",
353                BASE64URL_NOPAD.encode(&self.inner.to_keypair_bytes())
354            )
355        } else {
356            write!(f, "{}", BASE64URL_NOPAD.encode(&self.inner.to_bytes()))
357        }
358    }
359}
360
361impl fmt::Debug for SecretKey {
362    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
363        write!(f, "SecretKey(\"{self}\")")
364    }
365}
366
367relay_common::impl_str_serde!(SecretKey, "a secret key");
368
369/// Represents the public key of a Relay.
370///
371/// Public keys are based on ed25519 but this should be considered an
372/// implementation detail for now.  We only ever represent public keys
373/// on the wire as opaque ascii encoded strings of arbitrary format or length.
374#[derive(Clone, Eq, PartialEq)]
375pub struct PublicKey {
376    inner: ed25519_dalek::VerifyingKey,
377}
378
379impl PublicKey {
380    /// Verifies the signature and returns the embedded signature header.
381    ///
382    /// Returns [`SignatureError`] when the signature cannot be verified.
383    pub fn verify(
384        &self,
385        data: &[u8],
386        sig: SignatureRef<'_>,
387        start_time: DateTime<Utc>,
388        max_age: Duration,
389    ) -> Result<VerifiedSignatureHeader, SignatureError> {
390        let mut iter = sig.0.splitn(2, '.');
391        let sig_bytes = {
392            let sig_encoded = iter.next().ok_or(SignatureError::Invalid)?;
393            BASE64URL_NOPAD
394                .decode(sig_encoded.as_bytes())
395                .map_err(|_| SignatureError::Invalid)?
396        };
397        let sig = ed25519_dalek::Signature::from_slice(&sig_bytes)
398            .map_err(|_| SignatureError::Invalid)?;
399
400        let header = {
401            let header_encoded = iter.next().ok_or(SignatureError::Invalid)?;
402            BASE64URL_NOPAD
403                .decode(header_encoded.as_bytes())
404                .map_err(|_| SignatureError::Invalid)?
405        };
406        let parsed: SignatureHeader =
407            serde_json::from_slice(&header).map_err(|_| SignatureError::Invalid)?;
408
409        let signature_algorithm = parsed
410            .signature_algorithm
411            // Default to the regular algorithm for backwards compatibility.
412            .unwrap_or(SignatureAlgorithm::Regular);
413
414        let verification_result = match signature_algorithm {
415            SignatureAlgorithm::Regular => {
416                let mut to_verify = header.clone();
417                to_verify.push(b'\x00');
418                to_verify.extend_from_slice(data);
419                self.inner.verify(&to_verify, &sig)
420            }
421            SignatureAlgorithm::Prehashed => {
422                let digest = create_digest(&header, data);
423                self.inner.verify_digest(digest, &sig)
424            }
425        };
426
427        let Ok(()) = verification_result else {
428            return Err(SignatureError::Unverifiable);
429        };
430
431        if !is_valid_time(parsed.timestamp, start_time, max_age) {
432            return Err(SignatureError::Expired);
433        }
434
435        Ok(VerifiedSignatureHeader {
436            timestamp: parsed.timestamp,
437            signature_algorithm,
438        })
439    }
440
441    /// Unpacks signed data and returns it with header.
442    pub fn unpack<D: DeserializeOwned>(
443        &self,
444        data: &[u8],
445        signature: SignatureRef<'_>,
446        start_time: DateTime<Utc>,
447        max_age_diff: Duration,
448    ) -> Result<D, UnpackError> {
449        let _verified = self.verify(data, signature, start_time, max_age_diff)?;
450        serde_json::from_slice(data).map_err(UnpackError::BadPayload)
451    }
452}
453
454impl FromStr for PublicKey {
455    type Err = KeyParseError;
456
457    fn from_str(s: &str) -> Result<PublicKey, KeyParseError> {
458        if let Ok(inner) = ed25519_dalek::VerifyingKey::from_public_key_pem(s) {
459            return Ok(Self { inner });
460        }
461
462        let Ok(bytes) = BASE64URL_NOPAD.decode(s.as_bytes()) else {
463            return Err(KeyParseError::BadEncoding);
464        };
465
466        let inner = match bytes.try_into() {
467            Ok(bytes) => ed25519_dalek::VerifyingKey::from_bytes(&bytes)
468                .map_err(|_| KeyParseError::BadKey)?,
469            Err(_) => return Err(KeyParseError::BadKey),
470        };
471
472        Ok(PublicKey { inner })
473    }
474}
475
476impl fmt::Display for PublicKey {
477    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
478        write!(f, "{}", BASE64URL_NOPAD.encode(&self.inner.to_bytes()))
479    }
480}
481
482impl fmt::Debug for PublicKey {
483    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
484        write!(f, "PublicKey(\"{self}\")")
485    }
486}
487
488relay_common::impl_str_serde!(PublicKey, "a public key");
489
490/// Generates an Relay ID.
491pub fn generate_relay_id() -> RelayId {
492    Uuid::new_v4()
493}
494
495/// Generates a secret + public key pair.
496pub fn generate_key_pair() -> (SecretKey, PublicKey) {
497    let mut csprng = OsRng;
498    let mut secret = [0; 32];
499    csprng
500        .try_fill_bytes(&mut secret)
501        .expect("os rng should be available");
502    let kp = ed25519_dalek::SigningKey::from_bytes(&secret);
503    let pk = kp.verifying_key();
504    (SecretKey { inner: kp }, PublicKey { inner: pk })
505}
506
507/// An encoded and signed `RegisterState`.
508///
509/// This signature can be used by the upstream server to ensure that the downstream client did not
510/// tamper with the token without keeping state between requests. For more information, see
511/// `RegisterState`.
512///
513/// The format and contents of `SignedRegisterState` are intentionally opaque. Downstream clients
514/// do not need to interpret it, and the upstream can change its contents at any time. Parsing and
515/// validation is only performed on the upstream.
516///
517/// In the current implementation, the serialized state has the format `{state}:{signature}`, where
518/// each component is:
519///  - `state`: A URL-safe base64 encoding of the JSON serialized `RegisterState`.
520///  - `signature`: A URL-safe base64 encoding of the SHA512 HMAC of the encoded state.
521///
522/// To create a signed state, use `RegisterChallenge::sign`. To validate the signature and read
523/// the state, use `SignedRegisterChallenge::unpack`. In both cases, a secret for signing has to be
524/// supplied.
525#[derive(Clone, Debug, Deserialize, Serialize)]
526pub struct SignedRegisterState(String);
527
528impl SignedRegisterState {
529    /// Creates an Hmac instance for signing the `RegisterState`.
530    fn mac(secret: &[u8]) -> Hmac<Sha512> {
531        Hmac::new_from_slice(secret).expect("HMAC takes variable keys")
532    }
533
534    /// Signs the given `RegisterState` and serializes it into a single string.
535    fn sign(state: RegisterState, secret: &[u8]) -> Self {
536        let json = serde_json::to_string(&state).expect("relay register state serializes to JSON");
537        let token = BASE64URL_NOPAD.encode(json.as_bytes());
538
539        let mut mac = Self::mac(secret);
540        mac.update(token.as_bytes());
541        let signature = BASE64URL_NOPAD.encode(&mac.finalize().into_bytes());
542
543        Self(format!("{token}:{signature}"))
544    }
545
546    /// Splits the signed state into the encoded state and encoded signature.
547    fn split(&self) -> (&str, &str) {
548        let mut split = self.as_str().splitn(2, ':');
549        (split.next().unwrap_or(""), split.next().unwrap_or(""))
550    }
551
552    /// Returns the string representation of the token.
553    pub fn as_str(&self) -> &str {
554        self.0.as_str()
555    }
556
557    /// Unpacks the encoded state and validates the signature.
558    ///
559    /// The timestamp in the state is validated against the current timestamp.
560    /// If the stored timestamp is older than `max_age`, [`UnpackError::SignatureExpired`] is returned.
561    pub fn unpack(
562        &self,
563        secret: &[u8],
564        start_time: DateTime<Utc>,
565        max_age: Duration,
566    ) -> Result<RegisterState, UnpackError> {
567        let (token, signature) = self.split();
568        let code = BASE64URL_NOPAD
569            .decode(signature.as_bytes())
570            .map_err(|_| UnpackError::BadEncoding)?;
571
572        let mut mac = Self::mac(secret);
573        mac.update(token.as_bytes());
574        mac.verify_slice(&code)
575            .map_err(|_| UnpackError::BadSignature)?;
576
577        let json = BASE64URL_NOPAD
578            .decode(token.as_bytes())
579            .map_err(|_| UnpackError::BadEncoding)?;
580        let state =
581            serde_json::from_slice::<RegisterState>(&json).map_err(UnpackError::BadPayload)?;
582
583        let ts = state
584            .timestamp
585            .as_datetime()
586            .ok_or(UnpackError::SignatureExpired)?;
587        if !is_valid_time(ts, start_time, max_age) {
588            return Err(UnpackError::SignatureExpired);
589        }
590
591        Ok(state)
592    }
593}
594
595impl fmt::Display for SignedRegisterState {
596    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
597        self.as_str().fmt(f)
598    }
599}
600
601/// A state structure containing relevant information from `RegisterRequest`.
602///
603/// This structure is used to carry over information between the downstream register request and
604/// register response. In addition to identifying information, it contains a random bit to avoid
605/// replay attacks.
606#[derive(Clone, Deserialize, Serialize)]
607pub struct RegisterState {
608    timestamp: UnixTimestamp,
609    relay_id: RelayId,
610    public_key: PublicKey,
611    rand: String,
612}
613
614impl RegisterState {
615    /// Returns the timestamp at which the challenge was created.
616    pub fn timestamp(&self) -> UnixTimestamp {
617        self.timestamp
618    }
619
620    /// Returns the identifier of the requesting downstream Relay.
621    pub fn relay_id(&self) -> RelayId {
622        self.relay_id
623    }
624
625    /// Returns the public key of the requesting downstream Relay.
626    pub fn public_key(&self) -> &PublicKey {
627        &self.public_key
628    }
629}
630
631/// Generates a new random token for the register state.
632fn nonce() -> String {
633    let mut rng = rand::rng();
634    let mut bytes = vec![0u8; 64];
635    rng.fill_bytes(&mut bytes);
636    BASE64URL_NOPAD.encode(&bytes)
637}
638
639/// Represents a request for registration with the upstream.
640///
641/// This is created if the Relay signs in for the first time.  The server needs
642/// to respond to this request with a unique token that is then used to sign
643/// the response.
644#[derive(Serialize, Deserialize, Debug)]
645pub struct RegisterRequest {
646    relay_id: RelayId,
647    public_key: PublicKey,
648    #[serde(default)]
649    version: RelayVersion,
650}
651
652impl RegisterRequest {
653    /// Creates a new request to register an Relay upstream.
654    pub fn new(relay_id: &RelayId, public_key: &PublicKey) -> RegisterRequest {
655        RegisterRequest {
656            relay_id: *relay_id,
657            public_key: public_key.clone(),
658            version: RelayVersion::current(),
659        }
660    }
661
662    /// Unpacks a signed register request for bootstrapping.
663    ///
664    /// This unpacks the embedded public key first, then verifies if the
665    /// self signature was made by that public key.  If all is well then
666    /// the data is returned.
667    pub fn bootstrap_unpack(
668        data: &[u8],
669        signature: SignatureRef<'_>,
670        start_time: DateTime<Utc>,
671        max_age: Duration,
672    ) -> Result<RegisterRequest, UnpackError> {
673        let req: RegisterRequest = serde_json::from_slice(data).map_err(UnpackError::BadPayload)?;
674        let pk = req.public_key();
675        pk.unpack(data, signature, start_time, max_age)
676    }
677
678    /// Returns the Relay ID of the registering Relay.
679    pub fn relay_id(&self) -> RelayId {
680        self.relay_id
681    }
682
683    /// Returns the new public key of registering Relay.
684    pub fn public_key(&self) -> &PublicKey {
685        &self.public_key
686    }
687
688    /// Creates a register challenge for this request.
689    pub fn into_challenge(self, secret: &[u8]) -> RegisterChallenge {
690        let state = RegisterState {
691            timestamp: UnixTimestamp::now(),
692            relay_id: self.relay_id,
693            public_key: self.public_key,
694            rand: nonce(),
695        };
696
697        RegisterChallenge {
698            relay_id: self.relay_id,
699            token: SignedRegisterState::sign(state, secret),
700        }
701    }
702}
703
704/// Represents the response the server is supposed to send to a register request.
705#[derive(Serialize, Deserialize, Debug)]
706pub struct RegisterChallenge {
707    relay_id: RelayId,
708    token: SignedRegisterState,
709}
710
711impl RegisterChallenge {
712    /// Returns the Relay ID of the registering Relay.
713    pub fn relay_id(&self) -> &RelayId {
714        &self.relay_id
715    }
716
717    /// Returns the token that needs signing.
718    pub fn token(&self) -> &str {
719        self.token.as_str()
720    }
721
722    /// Creates a register response.
723    pub fn into_response(self) -> RegisterResponse {
724        RegisterResponse {
725            relay_id: self.relay_id,
726            token: self.token,
727            version: RelayVersion::current(),
728        }
729    }
730}
731
732/// Represents a response to a register challenge.
733///
734/// The response contains the same data as the register challenge. By signing this payload
735/// successfully, this Relay authenticates with the upstream.
736#[derive(Serialize, Deserialize, Debug)]
737pub struct RegisterResponse {
738    relay_id: RelayId,
739    token: SignedRegisterState,
740    #[serde(default)]
741    version: RelayVersion,
742}
743
744impl RegisterResponse {
745    /// Unpacks the register response and validates signatures.
746    pub fn unpack(
747        data: &[u8],
748        signature: SignatureRef<'_>,
749        secret: &[u8],
750        start_time: DateTime<Utc>,
751        max_age: Duration,
752    ) -> Result<(Self, RegisterState), UnpackError> {
753        let response: Self = serde_json::from_slice(data).map_err(UnpackError::BadPayload)?;
754        let state = response.token.unpack(secret, start_time, max_age)?;
755
756        let _verified = state
757            .public_key()
758            .verify(data, signature, start_time, max_age)?;
759
760        Ok((response, state))
761    }
762
763    /// Returns the Relay ID of the registering Relay.
764    pub fn relay_id(&self) -> RelayId {
765        self.relay_id
766    }
767
768    /// Returns the token that needs signing.
769    pub fn token(&self) -> &str {
770        self.token.as_str()
771    }
772
773    /// Returns the version of the registering Relay.
774    pub fn version(&self) -> RelayVersion {
775        self.version
776    }
777}
778
779/// A wrapper around a String that represents a signature.
780#[derive(Debug, Clone, PartialEq)]
781pub struct Signature(pub String);
782
783impl Display for Signature {
784    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
785        write!(f, "{}", self.0)
786    }
787}
788
789impl Signature {
790    /// Verifies the signature against any of the provided public keys.
791    ///
792    /// Returns `true` if the signature is valid with one of the given
793    /// public keys and satisfies the timestamp constraints defined by `start_time`
794    /// and `max_age`.
795    pub fn verify_any<'a>(
796        &self,
797        public_key: &'a [PublicKey],
798        start_time: DateTime<Utc>,
799        max_age: Duration,
800    ) -> Option<(&'a PublicKey, VerifiedSignatureHeader)> {
801        public_key.iter().find_map(|p| {
802            let verified = self.verify(&[], p, start_time, max_age).ok()?;
803            Some((p, verified))
804        })
805    }
806
807    /// Verifies the signature using the specified public key.
808    ///
809    /// The signature is considered valid if it can be verified using the given
810    /// public key and its embedded timestamp falls within the valid time range,
811    /// starting from `start_time` and not exceeding `max_age`.
812    pub fn verify(
813        &self,
814        data: &[u8],
815        public_key: &PublicKey,
816        start_time: DateTime<Utc>,
817        max_age_diff: Duration,
818    ) -> Result<VerifiedSignatureHeader, SignatureError> {
819        public_key.verify(data, self.as_signature_ref(), start_time, max_age_diff)
820    }
821
822    /// Returns a borrowed view of the signature as a `SignatureRef`.
823    ///
824    /// This method provides a lightweight reference wrapper over the internal
825    /// signature data.
826    pub fn as_signature_ref(&self) -> SignatureRef<'_> {
827        SignatureRef(self.0.as_str())
828    }
829}
830
831/// A borrowed reference to a signature string used for validation.
832///
833/// `SignatureRef` provides a view into the signature data as a string slice,
834/// allowing verification to work with borrowed data without unnecessary allocations.
835/// This type is typically obtained by borrowing from an owned [`Signature`].
836pub struct SignatureRef<'a>(pub &'a str);
837
838/// Verifies a timestamp `ts` is not in the future and not expired.
839fn is_valid_time(ts: DateTime<Utc>, start_time: DateTime<Utc>, max_age: Duration) -> bool {
840    let diff = start_time - ts;
841    diff >= Duration::zero() && diff <= max_age
842}
843
844#[cfg(test)]
845mod tests {
846    use super::*;
847
848    #[test]
849    fn test_keys() {
850        let sk: SecretKey =
851        "OvXFVm1tIUi8xDTuyHX1SSqdMc8nCt2qU9IUaH5p7oUk5pHZsdnfXNiMWiMLtSE86J3N9Peo5CBP1YQHDUkApQ"
852            .parse()
853            .unwrap();
854        let pk: PublicKey = "JOaR2bHZ31zYjFojC7UhPOidzfT3qOQgT9WEBw1JAKU"
855            .parse()
856            .unwrap();
857
858        assert_eq!(
859            sk.to_string(),
860            "OvXFVm1tIUi8xDTuyHX1SSqdMc8nCt2qU9IUaH5p7oU"
861        );
862        assert_eq!(
863            format!("{sk:#}"),
864            "OvXFVm1tIUi8xDTuyHX1SSqdMc8nCt2qU9IUaH5p7oUk5pHZsdnfXNiMWiMLtSE86J3N9Peo5CBP1YQHDUkApQ"
865        );
866        assert_eq!(
867            pk.to_string(),
868            "JOaR2bHZ31zYjFojC7UhPOidzfT3qOQgT9WEBw1JAKU"
869        );
870
871        assert_eq!(
872            "bad data".parse::<SecretKey>(),
873            Err(KeyParseError::BadEncoding)
874        );
875        assert_eq!("OvXF".parse::<SecretKey>(), Err(KeyParseError::BadKey));
876
877        assert_eq!(
878            "bad data".parse::<PublicKey>(),
879            Err(KeyParseError::BadEncoding)
880        );
881        assert_eq!("OvXF".parse::<PublicKey>(), Err(KeyParseError::BadKey));
882    }
883
884    #[test]
885    fn test_serializing() {
886        let sk: SecretKey =
887        "OvXFVm1tIUi8xDTuyHX1SSqdMc8nCt2qU9IUaH5p7oUk5pHZsdnfXNiMWiMLtSE86J3N9Peo5CBP1YQHDUkApQ"
888            .parse()
889            .unwrap();
890        let pk: PublicKey = "JOaR2bHZ31zYjFojC7UhPOidzfT3qOQgT9WEBw1JAKU"
891            .parse()
892            .unwrap();
893
894        let sk_json = serde_json::to_string(&sk).unwrap();
895        assert_eq!(sk_json, "\"OvXFVm1tIUi8xDTuyHX1SSqdMc8nCt2qU9IUaH5p7oU\"");
896
897        let pk_json = serde_json::to_string(&pk).unwrap();
898        assert_eq!(pk_json, "\"JOaR2bHZ31zYjFojC7UhPOidzfT3qOQgT9WEBw1JAKU\"");
899
900        assert_eq!(serde_json::from_str::<SecretKey>(&sk_json).unwrap(), sk);
901        assert_eq!(serde_json::from_str::<PublicKey>(&pk_json).unwrap(), pk);
902    }
903
904    #[test]
905    fn test_signatures() {
906        let (sk, pk) = generate_key_pair();
907        let data = b"Hello World!";
908
909        let sig = sk.sign(data);
910        let _verified = pk.verify(
911            data,
912            sig.as_signature_ref(),
913            Utc::now(),
914            Duration::seconds(1),
915        );
916
917        let bad_sig = "jgubwSf2wb2wuiRpgt2H9_bdDSMr88hXLp5zVuhbr65EGkSxOfT5ILIWr623twLgLd0bDgHg6xzOaUCX7XvUCw";
918        assert_eq!(
919            pk.verify(data, SignatureRef(bad_sig), Utc::now(), Duration::MAX)
920                .unwrap_err(),
921            SignatureError::Invalid
922        );
923    }
924
925    #[test]
926    fn test_registration() {
927        let max_age = Duration::minutes(15);
928
929        // initial setup
930        let relay_id = generate_relay_id();
931        let (sk, pk) = generate_key_pair();
932
933        // create a register request
934        let request = RegisterRequest::new(&relay_id, &pk);
935
936        // sign it
937        let (request_bytes, request_sig) = sk.pack(request);
938
939        // attempt to get the data through bootstrap unpacking.
940        let request = RegisterRequest::bootstrap_unpack(
941            &request_bytes,
942            request_sig.as_signature_ref(),
943            Utc::now(),
944            max_age,
945        )
946        .unwrap();
947        assert_eq!(request.relay_id(), relay_id);
948        assert_eq!(request.public_key(), &pk);
949
950        let upstream_secret = b"secret";
951
952        // create a challenge
953        let challenge = request.into_challenge(upstream_secret);
954        let challenge_token = challenge.token().to_owned();
955        assert_eq!(challenge.relay_id(), &relay_id);
956        assert!(challenge.token().len() > 40);
957
958        // check the challenge contains the expected info
959        let state = SignedRegisterState(challenge_token.clone());
960        let register_state = state.unpack(upstream_secret, Utc::now(), max_age).unwrap();
961        assert_eq!(register_state.public_key, pk);
962        assert_eq!(register_state.relay_id, relay_id);
963
964        // create a response from the challenge
965        let response = challenge.into_response();
966
967        // sign and unsign it
968        let (response_bytes, response_sig) = sk.pack(response);
969        let (response, _) = RegisterResponse::unpack(
970            &response_bytes,
971            response_sig.as_signature_ref(),
972            upstream_secret,
973            Utc::now(),
974            max_age,
975        )
976        .unwrap();
977
978        assert_eq!(response.relay_id(), relay_id);
979        assert_eq!(response.token(), challenge_token);
980        assert_eq!(response.version, LATEST_VERSION);
981    }
982
983    /// This is a pseudo-test to easily generate the strings used by test_auth.py
984    /// You can copy the output to the top of the test_auth.py when there are changes in the
985    /// exchanged authentication structures.
986    /// It follows test_registration but instead of asserting it prints the strings
987    #[test]
988    #[allow(clippy::print_stdout, reason = "helper test to generate output")]
989    fn test_generate_strings_for_test_auth_py() {
990        let max_age = Duration::minutes(15);
991        println!("Generating test data for test_auth.py...");
992
993        // initial setup
994        let relay_id = generate_relay_id();
995        println!("RELAY_ID = b\"{relay_id}\"");
996        let (sk, pk) = generate_key_pair();
997        println!("RELAY_KEY = b\"{pk}\"");
998
999        // create a register request
1000        let request = RegisterRequest::new(&relay_id, &pk);
1001        println!("REQUEST = b'{}'", serde_json::to_string(&request).unwrap());
1002
1003        // sign it
1004        let (request_bytes, request_sig) = sk.pack(&request);
1005        println!("REQUEST_SIG = \"{request_sig}\"");
1006
1007        // attempt to get the data through bootstrap unpacking.
1008        let request = RegisterRequest::bootstrap_unpack(
1009            &request_bytes,
1010            request_sig.as_signature_ref(),
1011            Utc::now(),
1012            max_age,
1013        )
1014        .unwrap();
1015
1016        let upstream_secret = b"secret";
1017
1018        // create a challenge
1019        let challenge = request.into_challenge(upstream_secret);
1020        let challenge_token = challenge.token().to_owned();
1021        println!("TOKEN = \"{challenge_token}\"");
1022
1023        // create a response from the challenge
1024        let response = challenge.into_response();
1025        let serialized_response = serde_json::to_string(&response).unwrap();
1026        let (_, response_sig) = sk.pack(&response);
1027
1028        println!("RESPONSE = b'{serialized_response}'");
1029        println!("RESPONSE_SIG = \"{response_sig}\"");
1030
1031        println!("RELAY_VERSION = \"{LATEST_VERSION}\"");
1032    }
1033
1034    /// Test we can still deserialize an old response that does not contain the version
1035    #[test]
1036    fn test_deserialize_old_response() {
1037        let serialized_challenge = "{\"relay_id\":\"6b7d15b8-cee2-4354-9fee-dae7ef43e434\",\"token\":\"eyJ0aW1lc3RhbXAiOjE1OTg5Njc0MzQsInJlbGF5X2lkIjoiNmI3ZDE1YjgtY2VlMi00MzU0LTlmZWUtZGFlN2VmNDNlNDM0IiwicHVibGljX2tleSI6ImtNcEdieWRIWlN2b2h6ZU1sZ2hjV3dIZDhNa3JlS0d6bF9uY2RrWlNPTWciLCJyYW5kIjoiLUViNG9Hal80dUZYOUNRRzFBVmdqTjRmdGxaNU9DSFlNOFl2d1podmlyVXhUY0tFSWYtQzhHaldsZmgwQTNlMzYxWE01dVh0RHhvN00tbWhZeXpWUWcifQ:KJUDXlwvibKNQmex-_Cu1U0FArlmoDkyqP7bYIDGrLXudfjGfCjH-UjNsUHWVDnbM28YdQ-R2MBSyF51aRLQcw\"}";
1038        let result: RegisterResponse = serde_json::from_str(serialized_challenge).unwrap();
1039        assert_eq!(
1040            result.relay_id,
1041            Uuid::parse_str("6b7d15b8-cee2-4354-9fee-dae7ef43e434").unwrap()
1042        )
1043    }
1044
1045    #[test]
1046    fn test_relay_version_current() {
1047        assert_eq!(
1048            env!("CARGO_PKG_VERSION"),
1049            RelayVersion::current().to_string()
1050        );
1051    }
1052
1053    #[test]
1054    fn test_relay_version_oldest() {
1055        // Regression test against unintentional changes.
1056        assert_eq!("0.0.0", RelayVersion::oldest().to_string());
1057    }
1058
1059    #[test]
1060    fn test_relay_version_parse() {
1061        assert_eq!(
1062            RelayVersion::new(20, 7, 0),
1063            "20.7.0-beta.0".parse().unwrap()
1064        );
1065    }
1066
1067    #[test]
1068    fn test_relay_version_oldest_supported() {
1069        assert!(RelayVersion::oldest().supported());
1070    }
1071
1072    #[test]
1073    fn test_relay_version_any_supported() {
1074        // Every version must be supported at the moment.
1075        // This test can be changed when dropping support for older versions.
1076        assert!(RelayVersion::default().supported());
1077    }
1078
1079    #[test]
1080    fn test_relay_version_from_str() {
1081        assert_eq!(RelayVersion::new(20, 7, 0), "20.7.0".parse().unwrap());
1082    }
1083
1084    #[test]
1085    fn test_verify_any() {
1086        let (_, p1) = generate_key_pair();
1087        let (_, p2) = generate_key_pair();
1088        let (s3, p3) = generate_key_pair();
1089
1090        let keys = [p1, p2, p3];
1091        let signature = s3.sign(&[]);
1092
1093        let verification = signature
1094            .verify_any(&keys, Utc::now(), Duration::seconds(10))
1095            .unwrap();
1096        assert_eq!(verification.0, &keys[2]);
1097    }
1098
1099    #[test]
1100    fn test_verify_max_age() {
1101        let pair = generate_key_pair();
1102        let signature = pair.0.sign(&[]);
1103        let start_time = Utc::now();
1104
1105        // The signature is valid in general
1106        let _verified = signature
1107            .verify(&[], &pair.1, start_time, Duration::seconds(10))
1108            .unwrap();
1109
1110        // Signature is no longer valid because too far in the future.
1111        let err = signature
1112            .verify(
1113                &[],
1114                &pair.1,
1115                start_time - Duration::seconds(1),
1116                Duration::milliseconds(500),
1117            )
1118            .unwrap_err();
1119        assert_eq!(err, SignatureError::Expired);
1120
1121        // Signature is no longer valid because too much time elapsed
1122        let err = signature
1123            .verify(
1124                &[],
1125                &pair.1,
1126                start_time + Duration::seconds(1),
1127                Duration::milliseconds(500),
1128            )
1129            .unwrap_err();
1130        assert_eq!(err, SignatureError::Expired);
1131    }
1132
1133    #[test]
1134    fn test_verify_any_max_age() {
1135        let start_time = Utc::now();
1136        let pair1 = generate_key_pair();
1137        let pair2 = generate_key_pair();
1138        let pair3 = generate_key_pair();
1139
1140        let header = SignatureHeader {
1141            timestamp: start_time,
1142            signature_algorithm: Some(SignatureAlgorithm::Regular),
1143        };
1144        let signature = pair3.0.sign_with_header(&[], &header);
1145
1146        let public_keys = &[pair1.1, pair2.1, pair3.1];
1147
1148        // Signature still valid after 1 second
1149        let v = signature
1150            .verify_any(
1151                public_keys,
1152                start_time + Duration::seconds(1),
1153                Duration::seconds(2),
1154            )
1155            .unwrap();
1156        assert_eq!(v.0, &public_keys[2]);
1157        // Signature is no longer valid because too much time elapsed
1158        assert!(
1159            signature
1160                .verify_any(
1161                    public_keys,
1162                    start_time + Duration::seconds(3),
1163                    Duration::seconds(2)
1164                )
1165                .is_none()
1166        );
1167        // Signature is valid (and verification doesn't panic) with `Duration::MAX`.
1168        let v = signature
1169            .verify_any(public_keys, start_time, Duration::MAX)
1170            .unwrap();
1171        assert_eq!(v.0, &public_keys[2]);
1172    }
1173
1174    #[test]
1175    fn test_regular_algorithm() {
1176        let (secret, public) = generate_key_pair();
1177        let signature = secret.sign(&[]);
1178        let _verified = signature
1179            .verify(&[], &public, Utc::now(), Duration::seconds(10))
1180            .unwrap();
1181    }
1182
1183    #[test]
1184    fn test_prehashed_algorithm() {
1185        let (secret, public) = generate_key_pair();
1186        let header = SignatureHeader {
1187            timestamp: Utc::now(),
1188            signature_algorithm: Some(SignatureAlgorithm::Prehashed),
1189        };
1190        let signature = secret.sign_with_header(&[], &header);
1191        let _verified = signature
1192            .verify(&[], &public, Utc::now(), Duration::seconds(10))
1193            .unwrap();
1194    }
1195
1196    #[test]
1197    fn test_legacy_signature_can_be_verified() {
1198        // TestHeader struct is used to mimic old version that do not have
1199        // the `signature_variant` fields.
1200        #[derive(Serialize)]
1201        struct TestHeader {
1202            #[serde(rename = "t")]
1203            timestamp: Option<DateTime<Utc>>,
1204        }
1205        let header = serde_json::to_string(&TestHeader {
1206            timestamp: Some(Utc::now()),
1207        })
1208        .unwrap();
1209
1210        let data: &[u8] = &[];
1211        let (secret, public) = generate_key_pair();
1212        let mut to_sign = header.clone().into_bytes();
1213        to_sign.push(b'\x00');
1214        to_sign.extend_from_slice(data);
1215        let sig = secret.inner.sign(to_sign.as_slice());
1216        let mut sig_encoded = BASE64URL_NOPAD.encode(sig.to_bytes().as_slice());
1217        sig_encoded.push('.');
1218        sig_encoded.push_str(BASE64URL_NOPAD.encode(header.as_bytes()).as_str());
1219
1220        let _verified = public
1221            .verify(
1222                data,
1223                SignatureRef(sig_encoded.as_str()),
1224                Utc::now(),
1225                Duration::seconds(3),
1226            )
1227            .unwrap();
1228    }
1229
1230    #[test]
1231    fn test_parse_private_pem() {
1232        let s = r#"-----BEGIN PRIVATE KEY-----
1233MC4CAQAwBQYDK2VwBCIEIPBFGz4q5QW27KNimPqb3dr9/pO4o6XR7QIKE1rxGAIK
1234-----END PRIVATE KEY-----"#;
1235        let key: SecretKey = s.parse().unwrap();
1236        assert_eq!(
1237            key.to_string(),
1238            "8EUbPirlBbbso2KY-pvd2v3-k7ijpdHtAgoTWvEYAgo"
1239        );
1240    }
1241
1242    #[test]
1243    fn test_parse_public_pem() {
1244        let s = r#"-----BEGIN PUBLIC KEY-----
1245MCowBQYDK2VwAyEATQCO/kpf2pyVjQyTuzr2qhi8IBxmBm2apZrUjJALYeA=
1246-----END PUBLIC KEY-----"#;
1247        let key: PublicKey = s.parse().unwrap();
1248        assert_eq!(
1249            key.to_string(),
1250            "TQCO_kpf2pyVjQyTuzr2qhi8IBxmBm2apZrUjJALYeA"
1251        );
1252    }
1253}