Skip to main content

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