Skip to main content

tor_llcrypto/
pk.rs

1//! Public-key cryptography for Tor.
2//!
3//! In old places, Tor uses RSA; newer Tor public-key cryptography is
4//! based on curve25519 and ed25519.
5
6pub mod ed25519;
7pub mod keymanip;
8pub mod rsa;
9
10/// Re-exporting Curve25519 implementations.
11///
12/// *TODO*: Eventually we should probably recommend using this code via some
13/// key-agreement trait, but for now we are just re-using the APIs from
14/// [`x25519_dalek`].
15pub mod curve25519 {
16    use derive_deftly::Deftly;
17    use educe::Educe;
18    use subtle::ConstantTimeEq;
19
20    use crate::util::ct::derive_deftly_template_PartialEqFromCtEq;
21    use crate::util::rng::RngCompat;
22
23    /// A keypair containing a [`StaticSecret`] and its corresponding public key.
24    #[allow(clippy::exhaustive_structs)]
25    #[derive(Clone, Educe)]
26    #[educe(Debug)]
27    pub struct StaticKeypair {
28        /// The secret part of the key.
29        #[educe(Debug(ignore))]
30        pub secret: StaticSecret,
31        /// The public part of this key.
32        pub public: PublicKey,
33    }
34
35    /// A curve25519 secret key that can only be used once,
36    /// and that can never be inspected.
37    ///
38    /// See [`x25519_dalek::EphemeralSecret`] for more information.
39    pub struct EphemeralSecret(x25519_dalek::EphemeralSecret);
40
41    /// A curve25519 secret key that can be used more than once,
42    /// and whose value can be inspected.
43    ///
44    /// See [`x25519_dalek::StaticSecret`] for more information.
45    //
46    // TODO: We may want eventually want to expose ReusableSecret instead of
47    // StaticSecret, for use in places where we need to use a single secret
48    // twice in one handshake, but we do not need that secret to be persistent.
49    //
50    // The trouble here is that if we use ReusableSecret in these cases, we
51    // cannot easily construct it for testing purposes.  We could in theory
52    // kludge something together using a fake Rng, but that might be more
53    // trouble than we want to go looking for.
54    #[derive(Clone)]
55    pub struct StaticSecret(x25519_dalek::StaticSecret);
56
57    impl ConstantTimeEq for StaticSecret {
58        fn ct_eq(&self, other: &Self) -> subtle::Choice {
59            let Self { 0: self_secret } = self;
60            let Self { 0: other_secret } = other;
61
62            self_secret.as_bytes().ct_eq(other_secret.as_bytes())
63        }
64    }
65
66    /// A curve15519 public key.
67    ///
68    /// See [`x25519_dalek::PublicKey`] for more information.
69    #[derive(Clone, Copy, Debug, Eq, Hash, Deftly)]
70    // Sadly not Ord because x25519_dalek::PublicKey isn't
71    #[allow(clippy::derived_hash_with_manual_eq)]
72    #[derive_deftly(PartialEqFromCtEq)]
73    pub struct PublicKey(x25519_dalek::PublicKey);
74
75    impl ConstantTimeEq for PublicKey {
76        fn ct_eq(&self, other: &Self) -> subtle::Choice {
77            let Self { 0: self_secret } = self;
78            let Self { 0: other_secret } = other;
79
80            self_secret.as_bytes().ct_eq(other_secret.as_bytes())
81        }
82    }
83
84    /// A shared secret negotiated using curve25519.
85    ///
86    /// See [`x25519_dalek::SharedSecret`] for more information
87    pub struct SharedSecret(x25519_dalek::SharedSecret);
88
89    impl<'a> From<&'a EphemeralSecret> for PublicKey {
90        fn from(secret: &'a EphemeralSecret) -> Self {
91            Self((&secret.0).into())
92        }
93    }
94
95    impl<'a> From<&'a StaticSecret> for PublicKey {
96        fn from(secret: &'a StaticSecret) -> Self {
97            Self((&secret.0).into())
98        }
99    }
100
101    impl From<[u8; 32]> for StaticSecret {
102        fn from(value: [u8; 32]) -> Self {
103            Self(value.into())
104        }
105    }
106    impl From<[u8; 32]> for PublicKey {
107        fn from(value: [u8; 32]) -> Self {
108            Self(value.into())
109        }
110    }
111
112    impl EphemeralSecret {
113        /// Return a new random ephemeral secret key.
114        pub fn random_from_rng<R: rand_core::Rng + rand_core::CryptoRng>(csprng: R) -> Self {
115            Self(x25519_dalek::EphemeralSecret::random_from_rng(
116                RngCompat::new(csprng),
117            ))
118        }
119        /// Negotiate a shared secret using this secret key and a public key.
120        pub fn diffie_hellman(self, their_public: &PublicKey) -> SharedSecret {
121            SharedSecret(self.0.diffie_hellman(&their_public.0))
122        }
123    }
124    impl StaticSecret {
125        /// Return a new random static secret key.
126        pub fn random_from_rng<R: rand_core::Rng + rand_core::CryptoRng>(csprng: R) -> Self {
127            Self(x25519_dalek::StaticSecret::random_from_rng(RngCompat::new(
128                csprng,
129            )))
130        }
131        /// Negotiate a shared secret using this secret key and a public key.
132        pub fn diffie_hellman(&self, their_public: &PublicKey) -> SharedSecret {
133            SharedSecret(self.0.diffie_hellman(&their_public.0))
134        }
135        /// Return the bytes that represent this key.
136        pub fn to_bytes(&self) -> [u8; 32] {
137            self.0.to_bytes()
138        }
139        /// Return a reference to the bytes that represent this key.
140        pub fn as_bytes(&self) -> &[u8; 32] {
141            self.0.as_bytes()
142        }
143    }
144    impl SharedSecret {
145        /// Return the shared secret as an array of bytes.
146        pub fn as_bytes(&self) -> &[u8; 32] {
147            self.0.as_bytes()
148        }
149        /// Return true if both keys contributed to this shared secret.
150        ///
151        /// See [`x25519_dalek::SharedSecret::was_contributory`] for more information.
152        pub fn was_contributory(&self) -> bool {
153            self.0.was_contributory()
154        }
155    }
156    impl PublicKey {
157        /// Return this public key as a reference to an array of bytes.
158        pub fn as_bytes(&self) -> &[u8; 32] {
159            self.0.as_bytes()
160        }
161        /// Return this public key as an array of bytes.
162        pub fn to_bytes(&self) -> [u8; 32] {
163            self.0.to_bytes()
164        }
165    }
166}
167
168/// A type for a validatable signature.
169///
170/// It necessarily includes the signature, the public key, and (a hash
171/// of?) the document being checked.
172///
173/// Having this trait enables us to write code for checking a large number
174/// of validatable signatures in a way that permits batch signatures for
175/// Ed25519.
176///
177/// To be used with [`validate_all_sigs`].
178pub trait ValidatableSignature {
179    /// Check whether this signature is a correct signature for the document.
180    fn is_valid(&self) -> bool;
181
182    /// Return this value as a validatable Ed25519 signature, if it is one.
183    fn as_ed25519(&self) -> Option<&ed25519::ValidatableEd25519Signature> {
184        None
185    }
186}
187
188/// Check whether all of the signatures in this Vec are valid.
189///
190/// Return `true` if every signature is valid; return `false` if even
191/// one is invalid.
192///
193/// This function should typically give the same result as just
194/// calling `v.iter().all(ValidatableSignature::is_valid))`, while taking
195/// advantage of batch verification to whatever extent possible.
196///
197/// (See [`ed25519::validate_batch`] for caveats.)
198pub fn validate_all_sigs(v: &[Box<dyn ValidatableSignature>]) -> bool {
199    // First we break out the ed25519 signatures (if any) so we can do
200    // a batch-verification on them.
201    let mut ed_sigs = Vec::new();
202    let mut non_ed_sigs = Vec::new();
203    for sig in v.iter() {
204        match sig.as_ed25519() {
205            Some(ed_sig) => ed_sigs.push(ed_sig),
206            None => non_ed_sigs.push(sig),
207        }
208    }
209
210    // Find out if the ed25519 batch is valid.
211    let ed_batch_is_valid = crate::pk::ed25519::validate_batch(&ed_sigs[..]);
212
213    // if so, verify the rest.
214    ed_batch_is_valid && non_ed_sigs.iter().all(|b| b.is_valid())
215}
216
217#[cfg(test)]
218mod test {
219    // @@ begin test lint list maintained by maint/add_warning @@
220    #![allow(clippy::bool_assert_comparison)]
221    #![allow(clippy::clone_on_copy)]
222    #![allow(clippy::dbg_macro)]
223    #![allow(clippy::mixed_attributes_style)]
224    #![allow(clippy::print_stderr)]
225    #![allow(clippy::print_stdout)]
226    #![allow(clippy::single_char_pattern)]
227    #![allow(clippy::unwrap_used)]
228    #![allow(clippy::unchecked_time_subtraction)]
229    #![allow(clippy::useless_vec)]
230    #![allow(clippy::needless_pass_by_value)]
231    #![allow(clippy::string_slice)] // See arti#2571
232    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
233    #[test]
234    fn validatable_ed_sig() {
235        use super::ValidatableSignature;
236        use super::ed25519::{PublicKey, Signature, ValidatableEd25519Signature};
237        use hex_literal::hex;
238        let pk = PublicKey::from_bytes(&hex!(
239            "fc51cd8e6218a1a38da47ed00230f058
240             0816ed13ba3303ac5deb911548908025"
241        ))
242        .unwrap();
243        let sig: Signature = hex!(
244            "6291d657deec24024827e69c3abe01a3
245             0ce548a284743a445e3680d7db5ac3ac
246             18ff9b538d16f290ae67f760984dc659
247             4a7c15e9716ed28dc027beceea1ec40a"
248        )
249        .into();
250
251        let valid = ValidatableEd25519Signature::new(pk, sig, &hex!("af82"));
252        let invalid = ValidatableEd25519Signature::new(pk, sig, &hex!("af83"));
253
254        assert!(valid.is_valid());
255        assert!(!invalid.is_valid());
256    }
257}