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