tor_hscrypto/
pk.rs

1//! Key type wrappers of various kinds used in onion services.
2//!
3//! (We define wrappers here as a safety net against confusing one kind of
4//! key for another: without a system like this, it can get pretty hard making
5//! sure that each key is used only in the right way.)
6
7use std::fmt::{self, Debug, Display};
8use std::str::FromStr;
9
10use digest::Digest;
11use itertools::{chain, Itertools};
12use safelog::DisplayRedacted;
13use thiserror::Error;
14use tor_basic_utils::{impl_debug_hex, StrExt as _};
15use tor_key_forge::ToEncodableKey;
16use tor_llcrypto::d::Sha3_256;
17use tor_llcrypto::pk::ed25519::{Ed25519PublicKey, Ed25519SigningKey};
18use tor_llcrypto::pk::{curve25519, ed25519, keymanip};
19use tor_llcrypto::util::ct::CtByteArray;
20
21use crate::macros::{define_bytes, define_pk_keypair};
22use crate::time::TimePeriod;
23
24#[allow(deprecated)]
25pub use hs_client_intro_auth::{HsClientIntroAuthKey, HsClientIntroAuthKeypair};
26
27define_bytes! {
28/// The identity of a v3 onion service. (`KP_hs_id`)
29///
30/// This is the decoded and validated ed25519 public key that is encoded as a
31/// `${base32}.onion` address.  When expanded, it is a public key whose
32/// corresponding secret key is controlled by the onion service.
33///
34/// `HsId`'s `Display` and `FromStr` representation is the domain name
35/// `"${base32}.onion"`.  (Without any subdomains.)
36///
37/// Note: This is a separate type from [`HsIdKey`] because it is about 6x
38/// smaller.
39#[derive(Copy, Clone, Eq, PartialEq, Hash)]
40pub struct HsId([u8; 32]);
41}
42
43define_pk_keypair! {
44/// The identity of a v3 onion service, expanded into a public key. (`KP_hs_id`)
45///
46/// This is the decoded and validated ed25519 public key that is encoded as
47/// a `${base32}.onion` address.
48///
49/// This key is not used to sign or validate anything on its own; instead, it is
50/// used to derive a [`HsBlindIdKey`].
51///
52/// Note: This is a separate type from [`HsId`] because it is about 6x
53/// larger.  It is an expanded form, used for doing actual cryptography.
54//
55// NOTE: This is called the "master" key in rend-spec-v3, but we're deprecating
56// that vocabulary generally.
57pub struct HsIdKey(ed25519::PublicKey) /
58    ///
59    /// This is stored as an expanded secret key, for compatibility with the C
60    /// tor implementation, and in order to support custom-generated addresses.
61    ///
62    /// (About custom generated addresses: When making a vanity onion address,
63    /// it is inefficient to search for a compact secret key `s` and compute
64    /// `SHA512(s)=(a,r)` and `A=aB` until you find an `s` that produces an `A`
65    /// that you like.  Instead, most folks use the algorithm of
66    /// rend-spec-v3.txt appendix C, wherein you search for a good `a` directly
67    /// by repeatedly adding `8B` to A until you find an `A` you like.  The only
68    /// major drawback is that once you have found a good `a`, you can't get an
69    /// `s` for it, since you presumably can't find SHA512 preimages.  And that
70    /// is why we store the private key in (a,r) form.)
71    HsIdKeypair(ed25519::ExpandedKeypair);
72}
73
74impl HsIdKey {
75    /// Return a representation of this key as an [`HsId`].
76    ///
77    /// ([`HsId`] is much smaller, and easier to store.)
78    pub fn id(&self) -> HsId {
79        HsId(self.0.to_bytes().into())
80    }
81}
82impl TryFrom<HsId> for HsIdKey {
83    type Error = signature::Error;
84
85    fn try_from(value: HsId) -> Result<Self, Self::Error> {
86        ed25519::PublicKey::from_bytes(value.0.as_ref()).map(HsIdKey)
87    }
88}
89impl From<HsIdKey> for HsId {
90    fn from(value: HsIdKey) -> Self {
91        value.id()
92    }
93}
94
95impl From<&HsIdKeypair> for HsIdKey {
96    fn from(value: &HsIdKeypair) -> Self {
97        Self(*value.0.public())
98    }
99}
100
101impl From<HsIdKeypair> for HsIdKey {
102    fn from(value: HsIdKeypair) -> Self {
103        Self(*value.0.public())
104    }
105}
106
107/// VERSION from rend-spec-v3 s.6 \[ONIONADDRESS]
108const HSID_ONION_VERSION: u8 = 0x03;
109
110/// The fixed string `.onion`
111pub const HSID_ONION_SUFFIX: &str = ".onion";
112
113impl safelog::DisplayRedacted for HsId {
114    fn fmt_unredacted(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
115        // rend-spec-v3 s.6 [ONIONADDRESS]
116        let checksum = self.onion_checksum();
117        let binary = chain!(self.0.as_ref(), &checksum, &[HSID_ONION_VERSION],)
118            .cloned()
119            .collect_vec();
120        let mut b32 = data_encoding::BASE32_NOPAD.encode(&binary);
121        b32.make_ascii_lowercase();
122        write!(f, "{}{}", b32, HSID_ONION_SUFFIX)
123    }
124
125    // We here display some of the end.  We don't want to display the
126    // *start* because vanity domains, which would perhaps suffer from
127    // reduced deniability.
128    fn fmt_redacted(&self, f: &mut fmt::Formatter) -> fmt::Result {
129        let unredacted = self.display_unredacted().to_string();
130        /// Length of the base32 data part of the address
131        const DATA: usize = 56;
132        assert_eq!(unredacted.len(), DATA + HSID_ONION_SUFFIX.len());
133
134        // We show this part of the domain:
135        //     e     n     l     5     s     i     d     .onion
136        //   KKKKK KKKKK KCCCC CCCCC CCCCC CCVVV VVVVV
137        //                           ^^^^^^^^^^^^^^^^^ ^^^^^^^^^
138        // This contains 3 characters of base32, which is 15 bits.
139        // 8 of those bits are the version, which is currently always 0x03.
140        // So we are showing 7 bits derived from the site key.
141
142        write!(f, "???{}", &unredacted[DATA - 3..])
143    }
144}
145
146impl safelog::DebugRedacted for HsId {
147    fn fmt_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148        write!(f, "HsId({})", self.display_redacted())
149    }
150
151    fn fmt_unredacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
152        write!(f, "HsId({})", self.display_unredacted())
153    }
154}
155
156safelog::derive_redacted_debug!(HsId);
157
158impl FromStr for HsId {
159    type Err = HsIdParseError;
160    fn from_str(s: &str) -> Result<Self, HsIdParseError> {
161        use HsIdParseError as PE;
162
163        let s = s
164            .strip_suffix_ignore_ascii_case(HSID_ONION_SUFFIX)
165            .ok_or(PE::NotOnionDomain)?;
166
167        if s.contains('.') {
168            return Err(PE::HsIdContainsSubdomain);
169        }
170
171        // We must convert to uppercase because RFC4648 says so and that's what Rust
172        // ecosystem libraries for base32 expect.  All this allocation and copying is
173        // still probably less work than the SHA3 for the checksum.
174        // However, we are going to use this function to *detect* and filter .onion
175        // addresses, so it should have a fast path to reject them.
176        let mut s = s.to_owned();
177        s.make_ascii_uppercase();
178
179        // Ideally we'd have code here that would provide a clear error message if
180        // we encounter an address with the wrong version.  But that is very complicated
181        // because the encoding format does not make that at all convenient.
182        // So instead our errors tell you what aspect of the parsing went wrong.
183        let binary = data_encoding::BASE32_NOPAD.decode(s.as_bytes())?;
184        let mut binary = tor_bytes::Reader::from_slice(&binary);
185
186        let pubkey: [u8; 32] = binary.extract()?;
187        let checksum: [u8; 2] = binary.extract()?;
188        let version: u8 = binary.extract()?;
189        let tentative = HsId(pubkey.into());
190
191        // Check version before checksum; maybe a future version does checksum differently
192        if version != HSID_ONION_VERSION {
193            return Err(PE::UnsupportedVersion(version));
194        }
195        if checksum != tentative.onion_checksum() {
196            return Err(PE::WrongChecksum);
197        }
198        Ok(tentative)
199    }
200}
201
202/// Error that can occur parsing an `HsId` from a v3 `.onion` domain name
203#[derive(Error, Clone, Debug)]
204#[non_exhaustive]
205pub enum HsIdParseError {
206    /// Supplied domain name string does not end in `.onion`
207    #[error("Domain name does not end in .onion")]
208    NotOnionDomain,
209
210    /// Base32 decoding failed
211    ///
212    /// `position` is indeed the (byte) position in the input string
213    #[error("Invalid base32 in .onion address")]
214    InvalidBase32(#[from] data_encoding::DecodeError),
215
216    /// Encoded binary data is invalid
217    #[error("Invalid encoded binary data in .onion address")]
218    InvalidData(#[from] tor_bytes::Error),
219
220    /// Unsupported `.onion` address version
221    #[error("Unsupported .onion address version, v{0}")]
222    UnsupportedVersion(u8),
223
224    /// Checksum failed
225    #[error("Checksum failed, .onion address corrupted")]
226    WrongChecksum,
227
228    /// If you try to parse a domain with subdomains as an `HsId`
229    #[error("`.onion` address with subdomain passed where not expected")]
230    HsIdContainsSubdomain,
231}
232
233impl HsId {
234    /// Calculates CHECKSUM rend-spec-v3 s.6 \[ONIONADDRESS]
235    fn onion_checksum(&self) -> [u8; 2] {
236        let mut h = Sha3_256::new();
237        h.update(b".onion checksum");
238        h.update(self.0.as_ref());
239        h.update([HSID_ONION_VERSION]);
240        h.finalize()[..2]
241            .try_into()
242            .expect("slice of fixed size wasn't that size")
243    }
244}
245
246impl HsIdKey {
247    /// Derive the blinded key and subcredential for this identity during `cur_period`.
248    pub fn compute_blinded_key(
249        &self,
250        cur_period: TimePeriod,
251    ) -> Result<(HsBlindIdKey, crate::Subcredential), keymanip::BlindingError> {
252        // TODO: someday we might want to support this kinds of a shared secret
253        // in our protocol. (C tor does not.)  If we did, it would be an
254        // additional piece of information about an onion service that you would
255        // need to know in order to connect to it.
256        //
257        // This is the "optional secret s" mentioned in the key-blinding
258        // appendix to rend-spec.txt.
259        let secret = b"";
260        let h = self.blinding_factor(secret, cur_period);
261
262        let blinded_key = keymanip::blind_pubkey(&self.0, h)?.into();
263        // rend-spec-v3 section 2.1
264        let subcredential = self.compute_subcredential(&blinded_key, cur_period);
265
266        Ok((blinded_key, subcredential))
267    }
268
269    /// Given a time period and a blinded public key, compute the subcredential.
270    pub fn compute_subcredential(
271        &self,
272        blinded_key: &HsBlindIdKey,
273        cur_period: TimePeriod,
274    ) -> crate::Subcredential {
275        // rend-spec-v3 section 2.1
276        let subcredential_bytes: [u8; 32] = {
277            // N_hs_subcred = H("subcredential" | N_hs_cred | blinded-public-key).
278            // where
279            //    N_hs_cred = H("credential" | public-identity-key)
280            let n_hs_cred: [u8; 32] = {
281                let mut h = Sha3_256::new();
282                h.update(b"credential");
283                h.update(self.0.as_bytes());
284                h.finalize().into()
285            };
286            let mut h = Sha3_256::new();
287            h.update(b"subcredential");
288            h.update(n_hs_cred);
289            h.update(blinded_key.as_bytes());
290            h.finalize().into()
291        };
292
293        subcredential_bytes.into()
294    }
295
296    /// Compute the 32-byte "blinding factor" used to compute blinded public
297    /// (and secret) keys.
298    ///
299    /// Returns the value `h = H(...)`, from rend-spec-v3 A.2., before clamping.
300    fn blinding_factor(&self, secret: &[u8], cur_period: TimePeriod) -> [u8; 32] {
301        // rend-spec-v3 appendix A.2
302        // We generate our key blinding factor as
303        //    h = H(BLIND_STRING | A | s | B | N)
304        // Where:
305        //    H is SHA3-256.
306        //    A is this public key.
307        //    BLIND_STRING = "Derive temporary signing key" | INT_1(0)
308        //    s is an optional secret (not implemented here.)
309        //    B is the ed25519 basepoint.
310        //    N = "key-blind" || INT_8(period_num) || INT_8(period_length).
311
312        /// String used as part of input to blinding hash.
313        const BLIND_STRING: &[u8] = b"Derive temporary signing key\0";
314        /// String representation of our Ed25519 basepoint.
315        const ED25519_BASEPOINT: &[u8] =
316            b"(15112221349535400772501151409588531511454012693041857206046113283949847762202, \
317               46316835694926478169428394003475163141307993866256225615783033603165251855960)";
318
319        let mut h = Sha3_256::new();
320        h.update(BLIND_STRING);
321        h.update(self.0.as_bytes());
322        h.update(secret);
323        h.update(ED25519_BASEPOINT);
324        h.update(b"key-blind");
325        h.update(cur_period.interval_num.to_be_bytes());
326        h.update((u64::from(cur_period.length.as_minutes())).to_be_bytes());
327
328        h.finalize().into()
329    }
330}
331
332impl HsIdKeypair {
333    /// Derive the blinded key and subcredential for this identity during `cur_period`.
334    pub fn compute_blinded_key(
335        &self,
336        cur_period: TimePeriod,
337    ) -> Result<(HsBlindIdKey, HsBlindIdKeypair, crate::Subcredential), keymanip::BlindingError>
338    {
339        // TODO: as discussed above in `HsId::compute_blinded_key`, we might
340        // someday want to implement nonempty values for this secret, if we
341        // decide it would be good for something.
342        let secret = b"";
343
344        let public_key = HsIdKey(*self.0.public());
345
346        // Note: This implementation is somewhat inefficient, as it recomputes
347        // the PublicKey, and computes our blinding factor twice.  But we
348        // only do this on an onion service once per time period: the
349        // performance does not matter.
350        let (blinded_public_key, subcredential) = public_key.compute_blinded_key(cur_period)?;
351
352        let h = public_key.blinding_factor(secret, cur_period);
353
354        let blinded_keypair = keymanip::blind_keypair(&self.0, h)?;
355
356        Ok((blinded_public_key, blinded_keypair.into(), subcredential))
357    }
358}
359
360define_pk_keypair! {
361/// The "blinded" identity of a v3 onion service. (`KP_hs_blind_id`)
362///
363/// This key is derived via a one-way transformation from an
364/// `HsIdKey` and the current time period.
365///
366/// It is used for two purposes: first, to compute an index into the HSDir
367/// ring, and second, to sign a `DescSigningKey`.
368///
369/// Note: This is a separate type from [`HsBlindId`] because it is about 6x
370/// larger.  It is an expanded form, used for doing actual cryptography.
371pub struct HsBlindIdKey(ed25519::PublicKey) / HsBlindIdKeypair(ed25519::ExpandedKeypair);
372}
373
374impl From<HsBlindIdKeypair> for HsBlindIdKey {
375    fn from(kp: HsBlindIdKeypair) -> HsBlindIdKey {
376        HsBlindIdKey(kp.0.into())
377    }
378}
379
380define_bytes! {
381/// A blinded onion service identity, represented in a compact format. (`KP_hs_blind_id`)
382///
383/// Note: This is a separate type from [`HsBlindIdKey`] because it is about
384/// 6x smaller.
385#[derive(Copy, Clone, Eq, PartialEq, Hash)]
386pub struct HsBlindId([u8; 32]);
387}
388impl_debug_hex! { HsBlindId .0 }
389
390impl HsBlindIdKey {
391    /// Return a representation of this key as a [`HsBlindId`].
392    ///
393    /// ([`HsBlindId`] is much smaller, and easier to store.)
394    pub fn id(&self) -> HsBlindId {
395        HsBlindId(self.0.to_bytes().into())
396    }
397}
398impl TryFrom<HsBlindId> for HsBlindIdKey {
399    type Error = signature::Error;
400
401    fn try_from(value: HsBlindId) -> Result<Self, Self::Error> {
402        ed25519::PublicKey::from_bytes(value.0.as_ref()).map(HsBlindIdKey)
403    }
404}
405
406impl From<&HsBlindIdKeypair> for HsBlindIdKey {
407    fn from(value: &HsBlindIdKeypair) -> Self {
408        HsBlindIdKey(*value.0.public())
409    }
410}
411
412impl From<HsBlindIdKey> for HsBlindId {
413    fn from(value: HsBlindIdKey) -> Self {
414        value.id()
415    }
416}
417impl From<ed25519::Ed25519Identity> for HsBlindId {
418    fn from(value: ed25519::Ed25519Identity) -> Self {
419        Self(CtByteArray::from(<[u8; 32]>::from(value)))
420    }
421}
422
423impl Ed25519SigningKey for HsBlindIdKeypair {
424    fn sign(&self, message: &[u8]) -> ed25519::Signature {
425        self.0.sign(message)
426    }
427}
428
429impl Ed25519PublicKey for HsBlindIdKeypair {
430    fn public_key(&self) -> ed25519::PublicKey {
431        *self.0.public()
432    }
433}
434
435define_pk_keypair! {
436/// A key used to sign onion service descriptors. (`KP_desc_sign`)
437///
438/// It is authenticated with a [`HsBlindIdKey`] to prove that it belongs to
439/// the right onion service, and is used in turn to sign the descriptor that
440/// tells clients what they need to know about contacting an onion service.
441///
442/// Onion services create a new `DescSigningKey` every time the
443/// `HsBlindIdKey` rotates, to prevent descriptors made in one time period
444/// from being linkable to those made in another.
445///
446/// Note: we use a separate signing key here, rather than using the
447/// `HsBlindIdKey` directly, so that the [`HsBlindIdKeypair`]
448/// can be kept offline.
449pub struct HsDescSigningKey(ed25519::PublicKey) / HsDescSigningKeypair(ed25519::Keypair);
450}
451
452define_pk_keypair! {
453/// A key used to identify and authenticate an onion service at a single
454/// introduction point. (`KP_hs_ipt_sid`)
455///
456/// This key is included in the onion service's descriptor; a different one is
457/// used at each introduction point.  Introduction points don't know the
458/// relation of this key to the onion service: they only recognize the same key
459/// when they see it again.
460pub struct HsIntroPtSessionIdKey(ed25519::PublicKey) / HsIntroPtSessionIdKeypair(ed25519::Keypair);
461}
462
463define_pk_keypair! {
464/// A key used in the HsNtor handshake between the client and the onion service.
465/// (`KP_hss_ntor`)
466///
467/// The onion service chooses a different one of these to use with each
468/// introduction point, though it does not need to tell the introduction points
469/// about these keys.
470pub struct HsSvcNtorKey(curve25519::PublicKey) / HsSvcNtorSecretKey(curve25519::StaticSecret);
471curve25519_pair as HsSvcNtorKeypair;
472}
473
474mod hs_client_intro_auth {
475    #![allow(deprecated)]
476    //! Key type wrappers for the deprecated `HsClientIntroKey`/`HsClientIntroKeypair` types.
477
478    use tor_llcrypto::pk::ed25519;
479
480    use crate::macros::define_pk_keypair;
481
482    define_pk_keypair! {
483    /// First type of client authorization key, used for the introduction protocol.
484    /// (`KP_hsc_intro_auth`)
485    ///
486    /// This is used to sign a nonce included in an extension in the encrypted
487    /// portion of an introduce cell.
488    #[deprecated(note = "This key type is not used in the protocol implemented today.")]
489    pub struct HsClientIntroAuthKey(ed25519::PublicKey) /
490    #[deprecated(note = "This key type is not used in the protocol implemented today.")]
491    HsClientIntroAuthKeypair(ed25519::Keypair);
492    }
493}
494
495define_pk_keypair! {
496/// Client service discovery key, used for onion descriptor
497/// decryption. (`KP_hsc_desc_enc`)
498///
499/// Any client who knows the secret key corresponding to this key can decrypt
500/// the inner layer of the onion service descriptor.
501///
502/// The [`Display`] and [`FromStr`] representation of keys of this type is
503/// `descriptor:x25519:<base32-encoded-x25519-public-key>`.
504/// Note: the base32 encoding of the key is unpadded and case-insensitive,
505/// for compatibility with the format accepted by C Tor.
506/// See also `CLIENT AUTHORIZATION` in `tor(1)`.
507///
508/// # Example
509///
510/// ```rust
511/// # use tor_hscrypto::pk::HsClientDescEncKey;
512/// # use std::str::FromStr;
513/// // A client service discovery key for connecting
514/// // to a service running in restricted discovery mode,
515/// // with an uppercase base32 encoding for the key material.
516/// const CLIENT_KEY1: &str = "descriptor:x25519:ZPRRMIV6DV6SJFL7SFBSVLJ5VUNPGCDFEVZ7M23LTLVTCCXJQBKA";
517/// // An identical key using lowercase base32 encoding for the key material.
518/// const CLIENT_KEY2: &str = "descriptor:x25519:zprrmiv6dv6sjfl7sfbsvlj5vunpgcdfevz7m23ltlvtccxjqbka";
519///
520/// // Both key encodings parse successfully
521/// let key1 = HsClientDescEncKey::from_str(CLIENT_KEY1).unwrap();
522/// let key2 = HsClientDescEncKey::from_str(CLIENT_KEY2).unwrap();
523/// // The keys are identical
524/// assert_eq!(key1, key2);
525/// ```
526pub struct HsClientDescEncKey(curve25519::PublicKey) / HsClientDescEncSecretKey(curve25519::StaticSecret);
527curve25519_pair as HsClientDescEncKeypair;
528}
529
530impl PartialEq for HsClientDescEncKey {
531    fn eq(&self, other: &Self) -> bool {
532        self.0 == other.0
533    }
534}
535
536impl Eq for HsClientDescEncKey {}
537
538impl Display for HsClientDescEncKey {
539    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
540        let x25519_pk = data_encoding::BASE32_NOPAD.encode(&self.0.to_bytes());
541        write!(f, "descriptor:x25519:{}", x25519_pk)
542    }
543}
544
545impl FromStr for HsClientDescEncKey {
546    type Err = HsClientDescEncKeyParseError;
547
548    fn from_str(key: &str) -> Result<Self, HsClientDescEncKeyParseError> {
549        let (auth_type, key_type, encoded_key) = key
550            .split(':')
551            .collect_tuple()
552            .ok_or(HsClientDescEncKeyParseError::InvalidFormat)?;
553
554        if auth_type != "descriptor" {
555            return Err(HsClientDescEncKeyParseError::InvalidAuthType(
556                auth_type.into(),
557            ));
558        }
559
560        if key_type != "x25519" {
561            return Err(HsClientDescEncKeyParseError::InvalidKeyType(
562                key_type.into(),
563            ));
564        }
565
566        // Note: Tor's base32 decoder is case-insensitive, so we can't assume the input
567        // is all uppercase.
568        //
569        // TODO: consider using `data_encoding_macro::new_encoding` to create a new Encoding
570        // with an alphabet that includes lowercase letters instead of to_uppercase()ing the string.
571        let encoded_key = encoded_key.to_uppercase();
572        let x25519_pk = data_encoding::BASE32_NOPAD.decode(encoded_key.as_bytes())?;
573        let x25519_pk: [u8; 32] = x25519_pk
574            .try_into()
575            .map_err(|_| HsClientDescEncKeyParseError::InvalidKeyMaterial)?;
576
577        Ok(Self(curve25519::PublicKey::from(x25519_pk)))
578    }
579}
580
581/// Error that can occur parsing an `HsClientDescEncKey` from C Tor format.
582#[derive(Error, Clone, Debug, PartialEq)]
583#[non_exhaustive]
584pub enum HsClientDescEncKeyParseError {
585    /// The auth type is not "descriptor".
586    #[error("Invalid auth type {0}")]
587    InvalidAuthType(String),
588
589    /// The key type is not "x25519".
590    #[error("Invalid key type {0}")]
591    InvalidKeyType(String),
592
593    /// The key is not in the `<auth-type>:x25519:<base32-encoded-public-key>` format.
594    #[error("Invalid key format")]
595    InvalidFormat,
596
597    /// The encoded key material is invalid.
598    #[error("Invalid key material")]
599    InvalidKeyMaterial,
600
601    /// Base32 decoding failed.
602    #[error("Invalid base32 in client key")]
603    InvalidBase32(#[from] data_encoding::DecodeError),
604}
605
606define_pk_keypair! {
607/// Server key, used for diffie hellman during onion descriptor decryption.
608/// (`KP_hss_desc_enc`)
609///
610/// This key is created for a single descriptor, and then thrown away.
611pub struct HsSvcDescEncKey(curve25519::PublicKey) / HsSvcDescEncSecretKey(curve25519::StaticSecret);
612}
613
614impl From<&HsClientDescEncSecretKey> for HsClientDescEncKey {
615    fn from(ks: &HsClientDescEncSecretKey) -> Self {
616        Self(curve25519::PublicKey::from(&ks.0))
617    }
618}
619
620impl From<&HsClientDescEncKeypair> for HsClientDescEncKey {
621    fn from(ks: &HsClientDescEncKeypair) -> Self {
622        Self(**ks.public())
623    }
624}
625
626/// An ephemeral x25519 keypair, generated by an onion service
627/// and used to for onion service encryption.
628#[allow(clippy::exhaustive_structs)]
629#[derive(Debug)]
630pub struct HsSvcDescEncKeypair {
631    /// The public part of the key.
632    pub public: HsSvcDescEncKey,
633    /// The secret part of the key.
634    pub secret: HsSvcDescEncSecretKey,
635}
636
637// TODO: let the define_ed25519_keypair/define_curve25519_keypair macros
638// auto-generate these impls.
639//
640// For some of the keys here, this currently cannot be done
641// because the macro doesn't support generating expanded ed25519 keys.
642
643impl ToEncodableKey for HsClientDescEncKeypair {
644    type Key = curve25519::StaticKeypair;
645    type KeyPair = HsClientDescEncKeypair;
646
647    fn to_encodable_key(self) -> Self::Key {
648        self.into()
649    }
650
651    fn from_encodable_key(key: Self::Key) -> Self {
652        HsClientDescEncKeypair::new(key.public.into(), key.secret.into())
653    }
654}
655
656impl ToEncodableKey for HsBlindIdKeypair {
657    type Key = ed25519::ExpandedKeypair;
658    type KeyPair = HsBlindIdKeypair;
659
660    fn to_encodable_key(self) -> Self::Key {
661        self.into()
662    }
663
664    fn from_encodable_key(key: Self::Key) -> Self {
665        HsBlindIdKeypair::from(key)
666    }
667}
668
669impl ToEncodableKey for HsBlindIdKey {
670    type Key = ed25519::PublicKey;
671    type KeyPair = HsBlindIdKeypair;
672
673    fn to_encodable_key(self) -> Self::Key {
674        self.into()
675    }
676
677    fn from_encodable_key(key: Self::Key) -> Self {
678        HsBlindIdKey::from(key)
679    }
680}
681
682impl ToEncodableKey for HsIdKeypair {
683    type Key = ed25519::ExpandedKeypair;
684    type KeyPair = HsIdKeypair;
685
686    fn to_encodable_key(self) -> Self::Key {
687        self.into()
688    }
689
690    fn from_encodable_key(key: Self::Key) -> Self {
691        HsIdKeypair::from(key)
692    }
693}
694
695impl ToEncodableKey for HsIdKey {
696    type Key = ed25519::PublicKey;
697    type KeyPair = HsIdKeypair;
698
699    fn to_encodable_key(self) -> Self::Key {
700        self.into()
701    }
702
703    fn from_encodable_key(key: Self::Key) -> Self {
704        HsIdKey::from(key)
705    }
706}
707
708impl ToEncodableKey for HsDescSigningKeypair {
709    type Key = ed25519::Keypair;
710    type KeyPair = HsDescSigningKeypair;
711
712    fn to_encodable_key(self) -> Self::Key {
713        self.into()
714    }
715
716    fn from_encodable_key(key: Self::Key) -> Self {
717        HsDescSigningKeypair::from(key)
718    }
719}
720
721impl ToEncodableKey for HsIntroPtSessionIdKeypair {
722    type Key = ed25519::Keypair;
723    type KeyPair = HsIntroPtSessionIdKeypair;
724
725    fn to_encodable_key(self) -> Self::Key {
726        self.into()
727    }
728
729    fn from_encodable_key(key: Self::Key) -> Self {
730        key.into()
731    }
732}
733
734impl ToEncodableKey for HsSvcNtorKeypair {
735    type Key = curve25519::StaticKeypair;
736    type KeyPair = HsSvcNtorKeypair;
737
738    fn to_encodable_key(self) -> Self::Key {
739        self.into()
740    }
741
742    fn from_encodable_key(key: Self::Key) -> Self {
743        key.into()
744    }
745}
746
747#[cfg(test)]
748mod test {
749    // @@ begin test lint list maintained by maint/add_warning @@
750    #![allow(clippy::bool_assert_comparison)]
751    #![allow(clippy::clone_on_copy)]
752    #![allow(clippy::dbg_macro)]
753    #![allow(clippy::mixed_attributes_style)]
754    #![allow(clippy::print_stderr)]
755    #![allow(clippy::print_stdout)]
756    #![allow(clippy::single_char_pattern)]
757    #![allow(clippy::unwrap_used)]
758    #![allow(clippy::unchecked_duration_subtraction)]
759    #![allow(clippy::useless_vec)]
760    #![allow(clippy::needless_pass_by_value)]
761    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
762
763    use hex_literal::hex;
764    use itertools::izip;
765    use std::time::{Duration, SystemTime};
766    use tor_basic_utils::test_rng::testing_rng;
767
768    use super::*;
769
770    #[test]
771    fn hsid_strings() {
772        use HsIdParseError as PE;
773
774        // From C Tor src/test/test_hs_common.c test_build_address
775        let hex = "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a";
776        let b32 = "25njqamcweflpvkl73j4szahhihoc4xt3ktcgjnpaingr5yhkenl5sid";
777
778        let hsid: [u8; 32] = hex::decode(hex).unwrap().try_into().unwrap();
779        let hsid = HsId::from(hsid);
780        let onion = format!("{}.onion", b32);
781
782        assert_eq!(onion.parse::<HsId>().unwrap(), hsid);
783        assert_eq!(hsid.display_unredacted().to_string(), onion);
784
785        let weird_case: String = izip!(onion.chars(), [false, true].iter().cloned().cycle(),)
786            .map(|(c, swap)| if swap { c.to_ascii_uppercase() } else { c })
787            .collect();
788        dbg!(&weird_case);
789        assert_eq!(weird_case.parse::<HsId>().unwrap(), hsid);
790
791        macro_rules! chk_err { { $s:expr, $($pat:tt)* } => {
792            let e = $s.parse::<HsId>();
793            assert!(matches!(e, Err($($pat)*)), "{:?}", &e);
794        } }
795        let edited = |i, c| {
796            let mut s = b32.to_owned().into_bytes();
797            s[i] = c;
798            format!("{}.onion", String::from_utf8(s).unwrap())
799        };
800
801        chk_err!("wrong", PE::NotOnionDomain);
802        chk_err!("@.onion", PE::InvalidBase32(..));
803        chk_err!("aaaaaaaa.onion", PE::InvalidData(..));
804        chk_err!(edited(55, b'E'), PE::UnsupportedVersion(4));
805        chk_err!(edited(53, b'X'), PE::WrongChecksum);
806        chk_err!(&format!("www.{}", &onion), PE::HsIdContainsSubdomain);
807
808        safelog::with_safe_logging_suppressed(|| {
809            assert_eq!(format!("{:?}", &hsid), format!("HsId({})", onion));
810        });
811
812        assert_eq!(format!("{}", hsid.display_redacted()), "???sid.onion");
813    }
814
815    #[test]
816    fn key_blinding_blackbox() {
817        let mut rng = testing_rng();
818        let offset = Duration::new(12 * 60 * 60, 0);
819        let when = TimePeriod::new(Duration::from_secs(3600), SystemTime::now(), offset).unwrap();
820        let keypair = ed25519::Keypair::generate(&mut rng);
821        let id_pub = HsIdKey::from(keypair.verifying_key());
822        let id_keypair = HsIdKeypair::from(ed25519::ExpandedKeypair::from(&keypair));
823
824        let (blinded_pub, subcred1) = id_pub.compute_blinded_key(when).unwrap();
825        let (blinded_pub2, blinded_keypair, subcred2) =
826            id_keypair.compute_blinded_key(when).unwrap();
827
828        assert_eq!(subcred1.as_ref(), subcred2.as_ref());
829        assert_eq!(blinded_pub.0.to_bytes(), blinded_pub2.0.to_bytes());
830        assert_eq!(blinded_pub.id(), blinded_pub2.id());
831
832        let message = b"Here is a terribly important string to authenticate.";
833        let other_message = b"Hey, that is not what I signed!";
834        let sign = blinded_keypair.sign(message);
835
836        assert!(blinded_pub.as_ref().verify(message, &sign).is_ok());
837        assert!(blinded_pub.as_ref().verify(other_message, &sign).is_err());
838    }
839
840    #[test]
841    fn key_blinding_testvec() {
842        // Test vectors generated with C tor.
843        let id = HsId::from(hex!(
844            "833990B085C1A688C1D4C8B1F6B56AFAF5A2ECA674449E1D704F83765CCB7BC6"
845        ));
846        let id_pubkey = HsIdKey::try_from(id).unwrap();
847        let id_seckey = HsIdKeypair::from(
848            ed25519::ExpandedKeypair::from_secret_key_bytes(hex!(
849                "D8C7FF0E31295B66540D789AF3E3DF992038A9592EEA01D8B7CBA06D6E66D159
850                 4D6167696320576F7264733A20737065697373636F62616C742062697669756D"
851            ))
852            .unwrap(),
853        );
854        let time_period = TimePeriod::new(
855            humantime::parse_duration("1 day").unwrap(),
856            humantime::parse_rfc3339("1973-05-20T01:50:33Z").unwrap(),
857            humantime::parse_duration("12 hours").unwrap(),
858        )
859        .unwrap();
860        assert_eq!(time_period.interval_num, 1234);
861
862        let h = id_pubkey.blinding_factor(b"", time_period);
863        assert_eq!(
864            h,
865            hex!("379E50DB31FEE6775ABD0AF6FB7C371E060308F4F847DB09FE4CFE13AF602287")
866        );
867
868        let (blinded_pub1, subcred1) = id_pubkey.compute_blinded_key(time_period).unwrap();
869        assert_eq!(
870            blinded_pub1.0.to_bytes(),
871            hex!("3A50BF210E8F9EE955AE0014F7A6917FB65EBF098A86305ABB508D1A7291B6D5")
872        );
873        assert_eq!(
874            subcred1.as_ref(),
875            &hex!("635D55907816E8D76398A675A50B1C2F3E36B42A5CA77BA3A0441285161AE07D")
876        );
877
878        let (blinded_pub2, blinded_sec, subcred2) =
879            id_seckey.compute_blinded_key(time_period).unwrap();
880        assert_eq!(blinded_pub1.0.to_bytes(), blinded_pub2.0.to_bytes());
881        assert_eq!(subcred1.as_ref(), subcred2.as_ref());
882        assert_eq!(
883            blinded_sec.0.to_secret_key_bytes(),
884            hex!(
885                "A958DC83AC885F6814C67035DE817A2C604D5D2F715282079448F789B656350B
886                 4540FE1F80AA3F7E91306B7BF7A8E367293352B14A29FDCC8C19F3558075524B"
887            )
888        );
889    }
890
891    #[test]
892    fn parse_client_desc_enc_key() {
893        use HsClientDescEncKeyParseError::*;
894
895        /// Valid base32-encoded x25519 public key.
896        const VALID_KEY_BASE32: &str = "dz4q5xqlb4ldnbs72iarrml4ephk3du4i7o2cgiva5lwr6wkquja";
897
898        // Some keys that are in the wrong format
899        const WRONG_FORMAT: &[&str] = &["a:b:c:d:e", "descriptor:", "descriptor:x25519", ""];
900
901        for key in WRONG_FORMAT {
902            let err = HsClientDescEncKey::from_str(key).unwrap_err();
903
904            assert_eq!(err, InvalidFormat);
905        }
906
907        let err =
908            HsClientDescEncKey::from_str(&format!("foo:descriptor:x25519:{VALID_KEY_BASE32}"))
909                .unwrap_err();
910
911        assert_eq!(err, InvalidFormat);
912
913        // A key with an invalid auth type
914        let err = HsClientDescEncKey::from_str("bar:x25519:aa==").unwrap_err();
915        assert_eq!(err, InvalidAuthType("bar".into()));
916
917        // A key with an invalid key type
918        let err = HsClientDescEncKey::from_str("descriptor:not-x25519:aa==").unwrap_err();
919        assert_eq!(err, InvalidKeyType("not-x25519".into()));
920
921        // A key with an invalid base32 part
922        let err = HsClientDescEncKey::from_str("descriptor:x25519:aa==").unwrap_err();
923        assert!(matches!(err, InvalidBase32(_)));
924
925        // A valid client desc enc key
926        let _key =
927            HsClientDescEncKey::from_str(&format!("descriptor:x25519:{VALID_KEY_BASE32}")).unwrap();
928
929        // Roundtrip
930        let desc_enc_key = HsClientDescEncKey::from(curve25519::PublicKey::from(
931            &curve25519::StaticSecret::random_from_rng(testing_rng()),
932        ));
933
934        assert_eq!(
935            desc_enc_key,
936            HsClientDescEncKey::from_str(&desc_enc_key.to_string()).unwrap()
937        );
938    }
939}