tor_interface/
tor_crypto.rs

1// standard
2use std::convert::TryInto;
3use std::str;
4
5// extern crates
6use curve25519_dalek::Scalar;
7use data_encoding::{BASE32_NOPAD, BASE64};
8use data_encoding_macro::new_encoding;
9#[cfg(any(feature = "legacy-tor-provider", feature = "arti-tor-provider"))]
10use rand::distr::Alphanumeric;
11#[cfg(any(feature = "legacy-tor-provider", feature = "arti-tor-provider"))]
12use rand::Rng;
13use sha3::{Digest, Sha3_256};
14use static_assertions::const_assert_eq;
15use tor_llcrypto::pk::keymanip::*;
16use tor_llcrypto::*;
17
18/// Represents various errors that can occur in the tor_crypto module.
19#[derive(thiserror::Error, Debug)]
20pub enum Error {
21    /// A error encountered converting a String to a tor_crypto type
22    #[error("{0}")]
23    ParseError(String),
24
25    /// An error encountered converting between tor_crypto types
26    #[error("{0}")]
27    ConversionError(String),
28
29    /// An error encountered converting from a raw byte representation
30    #[error("invalid key")]
31    KeyInvalid,
32}
33
34/// The number of bytes in an ed25519 secret key
35/// cbindgen:ignore
36pub const ED25519_PRIVATE_KEY_SIZE: usize = 64;
37/// The number of bytes in an ed25519 public key
38/// cbindgen:ignore
39pub const ED25519_PUBLIC_KEY_SIZE: usize = 32;
40/// The number of bytes in an ed25519 signature
41/// cbindgen:ignore
42pub const ED25519_SIGNATURE_SIZE: usize = 64;
43/// The number of bytes needed to store onion service id as an ASCII c-string (not including null-terminator)
44pub const V3_ONION_SERVICE_ID_STRING_LENGTH: usize = 56;
45/// The number of bytes needed to store onion service id as an ASCII c-string (including null-terminator)
46pub const V3_ONION_SERVICE_ID_STRING_SIZE: usize = 57;
47const_assert_eq!(
48    V3_ONION_SERVICE_ID_STRING_SIZE,
49    V3_ONION_SERVICE_ID_STRING_LENGTH + 1
50);
51/// The number of bytes needed to store base64 encoded ed25519 private key as an ASCII c-string (not including null-terminator)
52pub const ED25519_PRIVATE_KEYBLOB_BASE64_LENGTH: usize = 88;
53/// key klob header string
54const ED25519_PRIVATE_KEY_KEYBLOB_HEADER: &str = "ED25519-V3:";
55/// The number of bytes needed to store the keyblob header
56pub const ED25519_PRIVATE_KEY_KEYBLOB_HEADER_LENGTH: usize = 11;
57const_assert_eq!(
58    ED25519_PRIVATE_KEY_KEYBLOB_HEADER_LENGTH,
59    ED25519_PRIVATE_KEY_KEYBLOB_HEADER.len()
60);
61/// The number of bytes needed to store ed25519 private keyblob as an ASCII c-string (not including a null terminator)
62pub const ED25519_PRIVATE_KEY_KEYBLOB_LENGTH: usize = 99;
63const_assert_eq!(
64    ED25519_PRIVATE_KEY_KEYBLOB_LENGTH,
65    ED25519_PRIVATE_KEY_KEYBLOB_HEADER_LENGTH + ED25519_PRIVATE_KEYBLOB_BASE64_LENGTH
66);
67/// The number of bytes needed to store ed25519 private keyblob as an ASCII c-string (including a null terminator)
68pub const ED25519_PRIVATE_KEY_KEYBLOB_SIZE: usize = 100;
69const_assert_eq!(
70    ED25519_PRIVATE_KEY_KEYBLOB_SIZE,
71    ED25519_PRIVATE_KEY_KEYBLOB_LENGTH + 1
72);
73// number of bytes in an onion service id after base32 decode
74const V3_ONION_SERVICE_ID_RAW_SIZE: usize = 35;
75// byte index of the start of the public key checksum
76const V3_ONION_SERVICE_ID_CHECKSUM_OFFSET: usize = 32;
77// byte index of the v3 onion service version
78const V3_ONION_SERVICE_ID_VERSION_OFFSET: usize = 34;
79/// The number of bytes in a v3 service id's truncated checksum
80const TRUNCATED_CHECKSUM_SIZE: usize = 2;
81/// The number of bytes in an x25519 private key
82/// cbindgen:ignore
83pub const X25519_PRIVATE_KEY_SIZE: usize = 32;
84/// The number of bytes in an x25519 publickey
85/// cbindgen:ignore
86pub const X25519_PUBLIC_KEY_SIZE: usize = 32;
87/// The number of bytes needed to store base64 encoded x25519 private key as an ASCII c-string (not including null-terminator)
88pub const X25519_PRIVATE_KEY_BASE64_LENGTH: usize = 44;
89/// The number of bytes needed to store base64 encoded x25519 private key as an ASCII c-string (including a null terminator)
90pub const X25519_PRIVATE_KEY_BASE64_SIZE: usize = 45;
91const_assert_eq!(
92    X25519_PRIVATE_KEY_BASE64_SIZE,
93    X25519_PRIVATE_KEY_BASE64_LENGTH + 1
94);
95/// The number of bytes needed to store base32 encoded x25519 public key as an ASCII c-string (not including null-terminator)
96pub const X25519_PUBLIC_KEY_BASE32_LENGTH: usize = 52;
97/// The number of bytes needed to store base32 encoded x25519 public key as an ASCII c-string (including a null terminator)
98pub const X25519_PUBLIC_KEY_BASE32_SIZE: usize = 53;
99const_assert_eq!(
100    X25519_PUBLIC_KEY_BASE32_SIZE,
101    X25519_PUBLIC_KEY_BASE32_LENGTH + 1
102);
103
104const ONION_BASE32: data_encoding::Encoding = new_encoding! {
105    symbols: "abcdefghijklmnopqrstuvwxyz234567",
106    padding: '=',
107};
108
109// Free functions
110
111// securely generate password using CautionsRng
112#[cfg(any(feature = "legacy-tor-provider", feature = "arti-tor-provider"))]
113pub(crate) fn generate_password(length: usize) -> String {
114    let password: String = std::iter::repeat(())
115        .map(|()| tor_llcrypto::rng::CautiousRng.sample(Alphanumeric))
116        .map(char::from)
117        .take(length)
118        .collect();
119
120    password
121}
122
123// Struct deinitions
124
125/// An ed25519 private key.
126///
127/// This key type is used with [`crate::tor_provider::TorProvider`] trait for hosting onion-services and can be convertd to an [`Ed25519PublicKey`]. It can also be used to sign messages and create an [`Ed25519Signature`].
128pub struct Ed25519PrivateKey {
129    expanded_keypair: pk::ed25519::ExpandedKeypair,
130}
131
132/// An ed25519 public key.
133///
134/// This key type is derived from [`Ed25519PrivateKey`] and can be converted to a [`V3OnionServiceId`]. It can also be used to verify a [`Ed25519Signature`].
135#[derive(Clone)]
136pub struct Ed25519PublicKey {
137    public_key: pk::ed25519::PublicKey,
138}
139
140/// An ed25519 cryptographic signature
141#[derive(Clone)]
142pub struct Ed25519Signature {
143    signature: pk::ed25519::Signature,
144}
145
146/// An x25519 private key
147#[derive(Clone)]
148pub struct X25519PrivateKey {
149    secret_key: pk::curve25519::StaticSecret,
150}
151
152/// An x25519 public key
153#[derive(Clone, PartialEq, Eq)]
154pub struct X25519PublicKey {
155    public_key: pk::curve25519::PublicKey,
156}
157
158/// A v3 onion-service id
159#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
160pub struct V3OnionServiceId {
161    data: [u8; V3_ONION_SERVICE_ID_STRING_LENGTH],
162}
163
164/// An enum representing a single bit
165#[derive(Clone, Copy)]
166pub enum SignBit {
167    Zero,
168    One,
169}
170
171impl From<SignBit> for u8 {
172    fn from(signbit: SignBit) -> Self {
173        match signbit {
174            SignBit::Zero => 0u8,
175            SignBit::One => 1u8,
176        }
177    }
178}
179
180impl From<SignBit> for bool {
181    fn from(signbit: SignBit) -> Self {
182        match signbit {
183            SignBit::Zero => false,
184            SignBit::One => true,
185        }
186    }
187}
188
189impl From<bool> for SignBit {
190    fn from(signbit: bool) -> Self {
191        if signbit {
192            SignBit::One
193        } else {
194            SignBit::Zero
195        }
196    }
197}
198
199// which validation method to use when constructing an ed25519 expanded key from
200// a byte array
201enum FromRawValidationMethod {
202    // expanded ed25519 keys coming from legacy c-tor daemon; the scalar portion
203    // is clamped, but not reduced
204    #[cfg(feature = "legacy-tor-provider")]
205    LegacyCTor,
206    // expanded ed25519 keys coming from ed25519-dalek crate; the scalar portion
207    // has been clamped AND reduced
208    Ed25519Dalek,
209}
210
211/// A wrapper around `tor_llcrypto::pk::ed25519::ExpandedKeypair`.
212impl Ed25519PrivateKey {
213    /// Securely generate a new `Ed25519PrivateKey`.
214    pub fn generate() -> Ed25519PrivateKey {
215        let csprng = &mut tor_llcrypto::rng::CautiousRng;
216        let keypair = pk::ed25519::Keypair::generate(csprng);
217
218        Ed25519PrivateKey {
219            expanded_keypair: pk::ed25519::ExpandedKeypair::from(&keypair),
220        }
221    }
222
223    fn from_raw_impl(
224        raw: &[u8; ED25519_PRIVATE_KEY_SIZE],
225        method: FromRawValidationMethod,
226    ) -> Result<Ed25519PrivateKey, Error> {
227        // see: https://gitlab.torproject.org/tpo/core/arti/-/issues/1343
228        match method {
229            #[cfg(feature = "legacy-tor-provider")]
230            FromRawValidationMethod::LegacyCTor => {
231                // Verify the scalar portion of the expanded key has been clamped
232                // see: https://gitlab.torproject.org/tpo/core/arti/-/issues/1021
233                if !(raw[0] == raw[0] & 248 && raw[31] == (raw[31] & 63) | 64) {
234                    return Err(Error::KeyInvalid);
235                }
236            }
237            FromRawValidationMethod::Ed25519Dalek => {
238                // Verify the scalar is non-zero and it has been reduced
239                let scalar: [u8; 32] = raw[..32].try_into().unwrap();
240                if scalar.iter().all(|&x| x == 0x00u8) {
241                    return Err(Error::KeyInvalid);
242                }
243                let reduced_scalar = Scalar::from_bytes_mod_order(scalar).to_bytes();
244                if scalar != reduced_scalar {
245                    return Err(Error::KeyInvalid);
246                }
247            }
248        }
249
250        if let Some(expanded_keypair) = pk::ed25519::ExpandedKeypair::from_secret_key_bytes(*raw) {
251            Ok(Ed25519PrivateKey { expanded_keypair })
252        } else {
253            Err(Error::KeyInvalid)
254        }
255    }
256
257    /// Attempt to create an `Ed25519PrivateKey` from an array of bytes. Not all byte buffers of the required size can create a valid `Ed25519PrivateKey`. Only buffers derived from [`Ed25519PrivateKey::to_bytes()`] are required to convert correctly.
258    ///
259    /// To securely generate a valid `Ed25519PrivateKey`, use [`Ed25519PrivateKey::generate()`].
260    pub fn from_raw(raw: &[u8; ED25519_PRIVATE_KEY_SIZE]) -> Result<Ed25519PrivateKey, Error> {
261        Self::from_raw_impl(raw, FromRawValidationMethod::Ed25519Dalek)
262    }
263
264    fn from_key_blob_impl(
265        key_blob: &str,
266        method: FromRawValidationMethod,
267    ) -> Result<Ed25519PrivateKey, Error> {
268        if key_blob.len() != ED25519_PRIVATE_KEY_KEYBLOB_LENGTH {
269            return Err(Error::ParseError(format!(
270                "expects string of length '{}'; received string with length '{}'",
271                ED25519_PRIVATE_KEY_KEYBLOB_LENGTH,
272                key_blob.len()
273            )));
274        }
275
276        if !key_blob.starts_with(ED25519_PRIVATE_KEY_KEYBLOB_HEADER) {
277            return Err(Error::ParseError(format!(
278                "expects string that begins with '{}'; received '{}'",
279                &ED25519_PRIVATE_KEY_KEYBLOB_HEADER, &key_blob
280            )));
281        }
282
283        let base64_key: &str = &key_blob[ED25519_PRIVATE_KEY_KEYBLOB_HEADER.len()..];
284        let private_key_data = match BASE64.decode(base64_key.as_bytes()) {
285            Ok(private_key_data) => private_key_data,
286            Err(_) => {
287                return Err(Error::ParseError(format!(
288                    "could not parse '{}' as base64",
289                    base64_key
290                )))
291            }
292        };
293        let private_key_data_len = private_key_data.len();
294        let private_key_data_raw: [u8; ED25519_PRIVATE_KEY_SIZE] = match private_key_data.try_into()
295        {
296            Ok(private_key_data) => private_key_data,
297            Err(_) => {
298                return Err(Error::ParseError(format!(
299                    "expects decoded private key length '{}'; actual '{}'",
300                    ED25519_PRIVATE_KEY_SIZE, private_key_data_len
301                )))
302            }
303        };
304
305        Ed25519PrivateKey::from_raw_impl(&private_key_data_raw, method)
306    }
307
308    #[cfg(feature = "legacy-tor-provider")]
309    pub(crate) fn from_key_blob_legacy(key_blob: &str) -> Result<Ed25519PrivateKey, Error> {
310        Self::from_key_blob_impl(key_blob, FromRawValidationMethod::LegacyCTor)
311    }
312
313    /// Create an `Ed25519PrivateKey` from a [`String`] in the legacy c-tor daemon key blob format used in the `ADD_ONION` control-port command. From the c-tor control [specification](https://spec.torproject.org/control-spec/commands.html#add_onion):
314    /// > For a "ED25519-V3" key is the Base64 encoding of the concatenation of the 32-byte ed25519 secret scalar in little-endian and the 32-byte ed25519 PRF secret.
315    ///
316    /// Only key blob strings derived from [`Ed25519PrivateKey::to_key_blob()`] are required to convert correctly.
317    pub fn from_key_blob(key_blob: &str) -> Result<Ed25519PrivateKey, Error> {
318        Self::from_key_blob_impl(key_blob, FromRawValidationMethod::Ed25519Dalek)
319    }
320
321    /// Construct an `Ed25519PrivateKEy` from an [`X25519PrivateKey`].
322    pub fn from_private_x25519(
323        x25519_private: &X25519PrivateKey,
324    ) -> Result<(Ed25519PrivateKey, SignBit), Error> {
325        if let Some((result, signbit)) =
326            convert_curve25519_to_ed25519_private(&x25519_private.secret_key)
327        {
328            Ok((
329                Ed25519PrivateKey {
330                    expanded_keypair: result,
331                },
332                match signbit {
333                    0u8 => SignBit::Zero,
334                    1u8 => SignBit::One,
335                    invalid_signbit => {
336                        return Err(Error::ConversionError(format!(
337                            "convert_curve25519_to_ed25519_private() returned invalid signbit: {}",
338                            invalid_signbit
339                        )))
340                    }
341                },
342            ))
343        } else {
344            Err(Error::ConversionError(
345                "could not convert x25519 private key to ed25519 private key".to_string(),
346            ))
347        }
348    }
349
350    /// Write `Ed25519PrivateKey` to a c-tor key blob formatted [`String`].
351    pub fn to_key_blob(&self) -> String {
352        let mut key_blob = ED25519_PRIVATE_KEY_KEYBLOB_HEADER.to_string();
353        key_blob.push_str(&BASE64.encode(&self.expanded_keypair.to_secret_key_bytes()));
354
355        key_blob
356    }
357
358    /// Sign the provided message and return an [`Ed25519Signature`].
359    /// ## ⚠ Warning ⚠
360    ///Only ever sign messages the private key owner controls the contents of!
361    pub fn sign_message(&self, message: &[u8]) -> Ed25519Signature {
362        let signature = self.expanded_keypair.sign(message);
363        Ed25519Signature { signature }
364    }
365
366    /// Convert this private key to an array of bytes.
367    pub fn to_bytes(&self) -> [u8; ED25519_PRIVATE_KEY_SIZE] {
368        self.expanded_keypair.to_secret_key_bytes()
369    }
370
371    #[cfg(feature = "arti-client-tor-provider")]
372    pub(crate) fn inner(&self) -> &pk::ed25519::ExpandedKeypair {
373        &self.expanded_keypair
374    }
375}
376
377impl PartialEq for Ed25519PrivateKey {
378    fn eq(&self, other: &Self) -> bool {
379        self.to_bytes().eq(&other.to_bytes())
380    }
381}
382
383impl Clone for Ed25519PrivateKey {
384    fn clone(&self) -> Ed25519PrivateKey {
385        match Ed25519PrivateKey::from_raw(&self.to_bytes()) {
386            Ok(ed25519_private_key) => ed25519_private_key,
387            Err(_) => unreachable!(),
388        }
389    }
390}
391
392impl std::fmt::Debug for Ed25519PrivateKey {
393    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
394        write!(f, "--- ed25519 private key ---")
395    }
396}
397
398/// A wrapper around `tor_llcrypto::pk::ed25519::PublicKey`
399impl Ed25519PublicKey {
400    /// Construct an `Ed25519PublicKey` from an array of bytes. Not all byte buffers of the required size can create a valid `Ed25519PublicKey`. Only buffers derived from [`Ed25519PublicKey::as_bytes()`] are required to convert correctly.
401    pub fn from_raw(raw: &[u8; ED25519_PUBLIC_KEY_SIZE]) -> Result<Ed25519PublicKey, Error> {
402        Ok(Ed25519PublicKey {
403            public_key: match pk::ed25519::PublicKey::from_bytes(raw) {
404                Ok(public_key) => public_key,
405                Err(_) => return Err(Error::KeyInvalid),
406            },
407        })
408    }
409
410    /// Construct an `Ed25519PublicKey` from a [`V3OnionServiceId`].
411    pub fn from_service_id(service_id: &V3OnionServiceId) -> Result<Ed25519PublicKey, Error> {
412        // decode base32 encoded service id
413        let mut decoded_service_id = [0u8; V3_ONION_SERVICE_ID_RAW_SIZE];
414        let decoded_byte_count =
415            match ONION_BASE32.decode_mut(service_id.as_bytes(), &mut decoded_service_id) {
416                Ok(decoded_byte_count) => decoded_byte_count,
417                Err(_) => {
418                    return Err(Error::ConversionError(format!(
419                        "failed to decode '{}' as V3OnionServiceId",
420                        service_id
421                    )))
422                }
423            };
424        if decoded_byte_count != V3_ONION_SERVICE_ID_RAW_SIZE {
425            return Err(Error::ConversionError(format!(
426                "decoded byte count is '{}', expected '{}'",
427                decoded_byte_count, V3_ONION_SERVICE_ID_RAW_SIZE
428            )));
429        }
430
431        Ed25519PublicKey::from_raw(
432            decoded_service_id[0..ED25519_PUBLIC_KEY_SIZE]
433                .try_into()
434                .unwrap(),
435        )
436    }
437
438    /// Construct an `Ed25519PublicKey` from an [`Ed25519PrivateKey`].
439    pub fn from_private_key(private_key: &Ed25519PrivateKey) -> Ed25519PublicKey {
440        Ed25519PublicKey {
441            public_key: *private_key.expanded_keypair.public(),
442        }
443    }
444
445    fn from_public_x25519(
446        public_x25519: &X25519PublicKey,
447        signbit: SignBit,
448    ) -> Result<Ed25519PublicKey, Error> {
449        match convert_curve25519_to_ed25519_public(&public_x25519.public_key, signbit.into()) {
450            Some(public_key) => Ok(Ed25519PublicKey { public_key }),
451            None => Err(Error::ConversionError(
452                "failed to create ed25519 public key from x25519 public key and signbit"
453                    .to_string(),
454            )),
455        }
456    }
457
458    /// View this public key as an array of bytes
459    pub fn as_bytes(&self) -> &[u8; ED25519_PUBLIC_KEY_SIZE] {
460        self.public_key.as_bytes()
461    }
462}
463
464impl PartialEq for Ed25519PublicKey {
465    fn eq(&self, other: &Self) -> bool {
466        self.public_key.eq(&other.public_key)
467    }
468}
469
470impl std::fmt::Debug for Ed25519PublicKey {
471    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
472        self.public_key.fmt(f)
473    }
474}
475
476
477/// A wrapper around `tor_llcrypto::pk::ed25519::Signature`
478impl Ed25519Signature {
479    /// Construct an `Ed25519Signature` from an array of bytes.
480    pub fn from_raw(raw: &[u8; ED25519_SIGNATURE_SIZE]) -> Result<Ed25519Signature, Error> {
481        // todo: message cannot fail so should not return a Result<>
482        Ok(Ed25519Signature {
483            signature: pk::ed25519::Signature::from_bytes(raw),
484        })
485    }
486
487    /// Verify this `Ed25519Signature` for the given message and [`Ed25519PublicKey`].
488    pub fn verify(&self, message: &[u8], public_key: &Ed25519PublicKey) -> bool {
489        public_key
490            .public_key
491            .verify(message, &self.signature)
492            .is_ok()
493    }
494
495    /// Verify this `Ed25519Signature` for the given message, [`X25519PublicKey`], and [`SignBit`]. This signature must have been created by first converting an [`X25519PrivateKey`] to a [`Ed25519PrivateKey`] and [`SignBit`], and then signing the message using this [`Ed25519PrivateKey`]. This method verifies the signature using the [`Ed25519PublicKey`] derived from the provided  [`X25519PublicKey`] and [`SignBit`].
496    pub fn verify_x25519(
497        &self,
498        message: &[u8],
499        public_key: &X25519PublicKey,
500        signbit: SignBit,
501    ) -> bool {
502        if let Ok(public_key) = Ed25519PublicKey::from_public_x25519(public_key, signbit) {
503            return self.verify(message, &public_key);
504        }
505        false
506    }
507
508    /// Convert this signature to an array of bytes
509    pub fn to_bytes(&self) -> [u8; ED25519_SIGNATURE_SIZE] {
510        self.signature.to_bytes()
511    }
512}
513
514impl PartialEq for Ed25519Signature {
515    fn eq(&self, other: &Self) -> bool {
516        self.signature.eq(&other.signature)
517    }
518}
519
520impl std::fmt::Debug for Ed25519Signature {
521    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
522        self.signature.fmt(f)
523    }
524}
525
526/// A wrapper around `tor_llcrypto::pk::curve25519::StaticSecret`
527impl X25519PrivateKey {
528    /// Securely generate a new `X25519PrivateKey`
529    pub fn generate() -> X25519PrivateKey {
530        let csprng = &mut tor_llcrypto::rng::CautiousRng;
531        X25519PrivateKey {
532            secret_key: pk::curve25519::StaticSecret::random_from_rng(csprng),
533        }
534    }
535
536    /// Attempt to create an `X25519PrivateKey` from an array of bytes. Not all byte buffers of the required size can create a valid `X25519PrivateKey`. Only buffers derived from [`X25519PrivateKey::to_bytes()`] are required to convert correctly.
537    ///
538    /// To securely generate a valid `X25519PrivateKey`, use [`X25519PrivateKey::generate()`].
539    pub fn from_raw(raw: &[u8; X25519_PRIVATE_KEY_SIZE]) -> Result<X25519PrivateKey, Error> {
540        // see: https://docs.rs/x25519-dalek/2.0.0-pre.1/src/x25519_dalek/x25519.rs.html#197
541        if raw[0] == raw[0] & 240 && raw[31] == (raw[31] & 127) | 64 {
542            Ok(X25519PrivateKey {
543                secret_key: pk::curve25519::StaticSecret::from(*raw),
544            })
545        } else {
546            Err(Error::KeyInvalid)
547        }
548    }
549
550    /// Create an `X25519PrivateKey` from a [`String`] in the legacy c-tor daemon key blob format used in the `ONION_CLIENT_AUTH_ADD` control-port command. From the c-tor control [specification](https://spec.torproject.org/control-spec/commands.html#onion_client_auth_add):
551    /// > ```text
552    /// > PrivateKeyBlob = base64 encoding of x25519 key
553    /// > ```
554    ///
555    /// Only key blob strings derived from [`X25519PrivateKey::to_base64()`] are required to convert correctly.
556    pub fn from_base64(base64: &str) -> Result<X25519PrivateKey, Error> {
557        // todo: see if this should be from/to key blob like with ed25519 rather than base64
558        if base64.len() != X25519_PRIVATE_KEY_BASE64_LENGTH {
559            return Err(Error::ParseError(format!(
560                "expects string of length '{}'; received string with length '{}'",
561                X25519_PRIVATE_KEY_BASE64_LENGTH,
562                base64.len()
563            )));
564        }
565
566        let private_key_data = match BASE64.decode(base64.as_bytes()) {
567            Ok(private_key_data) => private_key_data,
568            Err(_) => {
569                return Err(Error::ParseError(format!(
570                    "could not parse '{}' as base64",
571                    base64
572                )))
573            }
574        };
575        let private_key_data_len = private_key_data.len();
576        let private_key_data_raw: [u8; X25519_PRIVATE_KEY_SIZE] = match private_key_data.try_into()
577        {
578            Ok(private_key_data) => private_key_data,
579            Err(_) => {
580                return Err(Error::ParseError(format!(
581                    "expects decoded private key length '{}'; actual '{}'",
582                    X25519_PRIVATE_KEY_SIZE, private_key_data_len
583                )))
584            }
585        };
586
587        X25519PrivateKey::from_raw(&private_key_data_raw)
588    }
589
590    /// Sign the provided message and return an [`Ed25519Signature`] and [`SignBit`].
591    ///
592    /// This method first converts this `X25519PrivateKey` to an [`Ed25519PrivateKey`] and [`SignBit`]. Then, the message is signed using the derived [`Ed25519PrivateKey`]. To verify the signature, both the [`X25519PublicKey`] and this calculated [`SignBit`] are required.
593    ///
594    /// ## ⚠ Warning ⚠
595    ///Only ever sign messages the private key owner controls the contents of!
596    pub fn sign_message(&self, message: &[u8]) -> Result<(Ed25519Signature, SignBit), Error> {
597        let (ed25519_private, signbit) = Ed25519PrivateKey::from_private_x25519(self)?;
598        Ok((ed25519_private.sign_message(message), signbit))
599    }
600
601    /// Write `X25519PrivateKey` to a base64 encocded [`String`].
602    pub fn to_base64(&self) -> String {
603        BASE64.encode(&self.secret_key.to_bytes())
604    }
605
606    /// Convert this private key to an array of bytes.
607    pub fn to_bytes(&self) -> [u8; X25519_PRIVATE_KEY_SIZE] {
608        self.secret_key.to_bytes()
609    }
610
611    #[cfg(feature = "arti-client-tor-provider")]
612    pub(crate) fn inner(&self) -> &pk::curve25519::StaticSecret {
613        &self.secret_key
614    }
615}
616
617impl PartialEq for X25519PrivateKey {
618    fn eq(&self, other: &Self) -> bool {
619        self.secret_key.to_bytes() == other.secret_key.to_bytes()
620    }
621}
622
623impl std::fmt::Debug for X25519PrivateKey {
624    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
625        write!(f, "--- x25519 private key ---")
626    }
627}
628
629/// A wrapper around `tor_llcrypto::pk::curve25519::PublicKey`
630impl X25519PublicKey {
631    /// Construct an `X25519PublicKey` from an [`X25519PrivateKey`].
632    pub fn from_private_key(private_key: &X25519PrivateKey) -> X25519PublicKey {
633        X25519PublicKey {
634            public_key: pk::curve25519::PublicKey::from(&private_key.secret_key),
635        }
636    }
637
638    /// Construct an `X25519PublicKey` from an array of bytes.
639    pub fn from_raw(raw: &[u8; X25519_PUBLIC_KEY_SIZE]) -> X25519PublicKey {
640        X25519PublicKey {
641            public_key: pk::curve25519::PublicKey::from(*raw),
642        }
643    }
644
645    /// Create an `X25519PublicKey` from a [`String`] in the legacy c-tor daemon key base32 format used in the `ADD_ONION` control-port command. From the c-tor control [specification](https://spec.torproject.org/control-spec/commands.html#add_onion):
646    /// > ```text
647    /// > V3Key = The client's base32-encoded x25519 public key, using only the key
648    /// >         part of rend-spec-v3.txt section G.1.2 (v3 only).
649    /// > ```
650    ///
651    /// Only key base32 strings derived from [`X25519PublicKey::to_base32()`] are required to convert correctly.
652    pub fn from_base32(base32: &str) -> Result<X25519PublicKey, Error> {
653        if base32.len() != X25519_PUBLIC_KEY_BASE32_LENGTH {
654            return Err(Error::ParseError(format!(
655                "expects string of length '{}'; received '{}' with length '{}'",
656                X25519_PUBLIC_KEY_BASE32_LENGTH,
657                base32,
658                base32.len()
659            )));
660        }
661
662        let public_key_data = match BASE32_NOPAD.decode(base32.as_bytes()) {
663            Ok(public_key_data) => public_key_data,
664            Err(_) => {
665                return Err(Error::ParseError(format!(
666                    "failed to decode '{}' as X25519PublicKey",
667                    base32
668                )))
669            }
670        };
671        let public_key_data_len = public_key_data.len();
672        let public_key_data_raw: [u8; X25519_PUBLIC_KEY_SIZE] = match public_key_data.try_into() {
673            Ok(public_key_data) => public_key_data,
674            Err(_) => {
675                return Err(Error::ParseError(format!(
676                    "expects decoded public key length '{}'; actual '{}'",
677                    X25519_PUBLIC_KEY_SIZE, public_key_data_len
678                )))
679            }
680        };
681
682        Ok(X25519PublicKey::from_raw(&public_key_data_raw))
683    }
684
685    /// Write `X25519PublicKey` to a base32 encocded [`String`].
686    pub fn to_base32(&self) -> String {
687        BASE32_NOPAD.encode(self.public_key.as_bytes())
688    }
689
690    /// View this public key as an array of bytes
691    pub fn as_bytes(&self) -> &[u8; X25519_PUBLIC_KEY_SIZE] {
692        self.public_key.as_bytes()
693    }
694
695    #[cfg(feature = "arti-client-tor-provider")]
696    pub(crate) fn inner(&self) -> &pk::curve25519::PublicKey {
697        &self.public_key
698    }
699
700}
701
702impl std::fmt::Debug for X25519PublicKey {
703    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
704        write!(f, "{}", self.to_base32())
705    }
706}
707
708/// Strongly-typed representation of a v3 onion-service id
709impl V3OnionServiceId {
710    // see https://github.com/torproject/torspec/blob/main/rend-spec-v3.txt#L2143
711    fn calc_truncated_checksum(
712        public_key: &[u8; ED25519_PUBLIC_KEY_SIZE],
713    ) -> [u8; TRUNCATED_CHECKSUM_SIZE] {
714        let mut hasher = Sha3_256::new();
715
716        // calculate checksum
717        hasher.update(b".onion checksum");
718        hasher.update(public_key);
719        hasher.update([0x03u8]);
720        let hash_bytes = hasher.finalize();
721
722        [hash_bytes[0], hash_bytes[1]]
723    }
724
725    /// Create a `V3OnionServiceId` from a [`String`] in the version 3 onion service digest format. From the tor address [specification](https://spec.torproject.org/address-spec.html#onion):
726    /// > ```text
727    /// > onion_address = base32(PUBKEY | CHECKSUM | VERSION)
728    /// > CHECKSUM = H(".onion checksum" | PUBKEY | VERSION)[:2]
729    /// >
730    /// > where:
731    /// > - PUBKEY is the 32 bytes ed25519 master pubkey of the onion service.
732    /// > - VERSION is a one byte version field (default value '\x03')
733    /// > - ".onion checksum" is a constant string
734    /// > - H is SHA3-256
735    /// > - CHECKSUM is truncated to two bytes before inserting it in onion_address
736    /// > ```
737    pub fn from_string(service_id: &str) -> Result<V3OnionServiceId, Error> {
738        if !V3OnionServiceId::is_valid(service_id) {
739            return Err(Error::ParseError(format!(
740                "'{}' is not a valid v3 onion service id",
741                service_id
742            )));
743        }
744        Ok(V3OnionServiceId {
745            data: service_id.as_bytes().try_into().unwrap(),
746        })
747    }
748
749    /// Create a `V3OnionServiceId` from an [`Ed25519PublicKey`].
750    pub fn from_public_key(public_key: &Ed25519PublicKey) -> V3OnionServiceId {
751        let mut raw_service_id = [0u8; V3_ONION_SERVICE_ID_RAW_SIZE];
752
753        raw_service_id[..ED25519_PUBLIC_KEY_SIZE].copy_from_slice(&public_key.as_bytes()[..]);
754        let truncated_checksum = Self::calc_truncated_checksum(public_key.as_bytes());
755        raw_service_id[V3_ONION_SERVICE_ID_CHECKSUM_OFFSET] = truncated_checksum[0];
756        raw_service_id[V3_ONION_SERVICE_ID_CHECKSUM_OFFSET + 1] = truncated_checksum[1];
757        raw_service_id[V3_ONION_SERVICE_ID_VERSION_OFFSET] = 0x03u8;
758
759        let mut service_id = [0u8; V3_ONION_SERVICE_ID_STRING_LENGTH];
760        // panics on wrong buffer size, but given our constant buffer sizes should be fine
761        ONION_BASE32.encode_mut(&raw_service_id, &mut service_id);
762
763        V3OnionServiceId { data: service_id }
764    }
765
766    /// Create a `V3OnionServiceId` from an [`Ed25519PrivateKey`].
767    pub fn from_private_key(private_key: &Ed25519PrivateKey) -> V3OnionServiceId {
768        Self::from_public_key(&Ed25519PublicKey::from_private_key(private_key))
769    }
770
771    /// Determine if the provided string is a valid representation of a `V3OnionServiceId`
772    pub fn is_valid(service_id: &str) -> bool {
773        if service_id.len() != V3_ONION_SERVICE_ID_STRING_LENGTH {
774            return false;
775        }
776
777        let mut decoded_service_id = [0u8; V3_ONION_SERVICE_ID_RAW_SIZE];
778        match ONION_BASE32.decode_mut(service_id.as_bytes(), &mut decoded_service_id) {
779            Ok(decoded_byte_count) => {
780                // ensure right size
781                if decoded_byte_count != V3_ONION_SERVICE_ID_RAW_SIZE {
782                    return false;
783                }
784                // ensure correct version
785                if decoded_service_id[V3_ONION_SERVICE_ID_VERSION_OFFSET] != 0x03 {
786                    return false;
787                }
788                // copy public key into own buffer
789                let mut public_key = [0u8; ED25519_PUBLIC_KEY_SIZE];
790                public_key[..].copy_from_slice(&decoded_service_id[..ED25519_PUBLIC_KEY_SIZE]);
791                // ensure checksum is correct
792                let truncated_checksum = Self::calc_truncated_checksum(&public_key);
793                if truncated_checksum[0] != decoded_service_id[V3_ONION_SERVICE_ID_CHECKSUM_OFFSET]
794                    || truncated_checksum[1]
795                        != decoded_service_id[V3_ONION_SERVICE_ID_CHECKSUM_OFFSET + 1]
796                {
797                    return false;
798                }
799                true
800            }
801            Err(_) => false,
802        }
803    }
804
805    /// View this service id as an array of bytes
806    pub fn as_bytes(&self) -> &[u8; V3_ONION_SERVICE_ID_STRING_LENGTH] {
807        &self.data
808    }
809}
810
811impl std::fmt::Display for V3OnionServiceId {
812    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
813        unsafe { write!(f, "{}", str::from_utf8_unchecked(&self.data)) }
814    }
815}
816
817impl std::fmt::Debug for V3OnionServiceId {
818    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
819        unsafe { write!(f, "{}", str::from_utf8_unchecked(&self.data)) }
820    }
821}