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

            
6
pub mod ed25519;
7
pub mod keymanip;
8
pub 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`].
15
pub mod curve25519 {
16
    use educe::Educe;
17

            
18
    use crate::util::rng::RngCompat;
19

            
20
    /// A keypair containing a [`StaticSecret`] and its corresponding public key.
21
    #[allow(clippy::exhaustive_structs)]
22
    #[derive(Clone, Educe)]
23
    #[educe(Debug)]
24
    pub struct StaticKeypair {
25
        /// The secret part of the key.
26
        #[educe(Debug(ignore))]
27
        pub secret: StaticSecret,
28
        /// The public part of this key.
29
        pub public: PublicKey,
30
    }
31

            
32
    /// A curve25519 secret key that can only be used once,
33
    /// and that can never be inspected.
34
    ///
35
    /// See [`x25519_dalek::EphemeralSecret`] for more information.
36
    pub struct EphemeralSecret(x25519_dalek::EphemeralSecret);
37

            
38
    /// A curve25519 secret key that can be used more than once,
39
    /// and whose value can be inspected.
40
    ///
41
    /// See [`x25519_dalek::StaticSecret`] for more information.
42
    //
43
    // TODO: We may want eventually want to expose ReusableSecret instead of
44
    // StaticSecret, for use in places where we need to use a single secret
45
    // twice in one handshake, but we do not need that secret to be persistent.
46
    //
47
    // The trouble here is that if we use ReusableSecret in these cases, we
48
    // cannot easily construct it for testing purposes.  We could in theory
49
    // kludge something together using a fake Rng, but that might be more
50
    // trouble than we want to go looking for.
51
    #[derive(Clone)]
52
    pub struct StaticSecret(x25519_dalek::StaticSecret);
53

            
54
    /// A curve15519 public key.
55
    ///
56
    /// See [`x25519_dalek::PublicKey`] for more information.
57
    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
58
    pub struct PublicKey(x25519_dalek::PublicKey);
59

            
60
    /// A shared secret negotiated using curve25519.
61
    ///
62
    /// See [`x25519_dalek::SharedSecret`] for more information
63
    pub struct SharedSecret(x25519_dalek::SharedSecret);
64

            
65
    impl<'a> From<&'a EphemeralSecret> for PublicKey {
66
7030
        fn from(secret: &'a EphemeralSecret) -> Self {
67
7030
            Self((&secret.0).into())
68
7030
        }
69
    }
70

            
71
    impl<'a> From<&'a StaticSecret> for PublicKey {
72
10146
        fn from(secret: &'a StaticSecret) -> Self {
73
10146
            Self((&secret.0).into())
74
10146
        }
75
    }
76

            
77
    impl From<[u8; 32]> for StaticSecret {
78
4146
        fn from(value: [u8; 32]) -> Self {
79
4146
            Self(value.into())
80
4146
        }
81
    }
82
    impl From<[u8; 32]> for PublicKey {
83
568098
        fn from(value: [u8; 32]) -> Self {
84
568098
            Self(value.into())
85
568098
        }
86
    }
87

            
88
    impl EphemeralSecret {
89
        /// Return a new random ephemeral secret key.
90
192
        pub fn random_from_rng<R: rand_core::RngCore + rand_core::CryptoRng>(csprng: R) -> Self {
91
192
            Self(x25519_dalek::EphemeralSecret::random_from_rng(
92
192
                RngCompat::new(csprng),
93
192
            ))
94
192
        }
95
        /// Negotiate a shared secret using this secret key and a public key.
96
814
        pub fn diffie_hellman(self, their_public: &PublicKey) -> SharedSecret {
97
814
            SharedSecret(self.0.diffie_hellman(&their_public.0))
98
814
        }
99
    }
100
    impl StaticSecret {
101
        /// Return a new random static secret key.
102
1246
        pub fn random_from_rng<R: rand_core::RngCore + rand_core::CryptoRng>(csprng: R) -> Self {
103
1246
            Self(x25519_dalek::StaticSecret::random_from_rng(RngCompat::new(
104
1246
                csprng,
105
1246
            )))
106
1246
        }
107
        /// Negotiate a shared secret using this secret key and a public key.
108
9398
        pub fn diffie_hellman(&self, their_public: &PublicKey) -> SharedSecret {
109
9398
            SharedSecret(self.0.diffie_hellman(&their_public.0))
110
9398
        }
111
        /// Return the bytes that represent this key.
112
2302
        pub fn to_bytes(&self) -> [u8; 32] {
113
2302
            self.0.to_bytes()
114
2302
        }
115
    }
116
    impl SharedSecret {
117
        /// Return the shared secret as an array of bytes.
118
12580
        pub fn as_bytes(&self) -> &[u8; 32] {
119
12580
            self.0.as_bytes()
120
12580
        }
121
        /// Return true if both keys contributed to this shared secret.
122
        ///
123
        /// See [`x25519_dalek::SharedSecret::was_contributory`] for more information.
124
8584
        pub fn was_contributory(&self) -> bool {
125
8584
            self.0.was_contributory()
126
8584
        }
127
    }
128
    impl PublicKey {
129
        /// Return this public key as a reference to an array of bytes.
130
116410
        pub fn as_bytes(&self) -> &[u8; 32] {
131
116410
            self.0.as_bytes()
132
116410
        }
133
        /// Return this public key as an array of bytes.
134
18130
        pub fn to_bytes(&self) -> [u8; 32] {
135
18130
            self.0.to_bytes()
136
18130
        }
137
    }
138
}
139

            
140
/// A type for a validatable signature.
141
///
142
/// It necessarily includes the signature, the public key, and (a hash
143
/// of?) the document being checked.
144
///
145
/// Having this trait enables us to write code for checking a large number
146
/// of validatable signatures in a way that permits batch signatures for
147
/// Ed25519.
148
///
149
/// To be used with [`validate_all_sigs`].
150
pub trait ValidatableSignature {
151
    /// Check whether this signature is a correct signature for the document.
152
    fn is_valid(&self) -> bool;
153

            
154
    /// Return this value as a validatable Ed25519 signature, if it is one.
155
3160
    fn as_ed25519(&self) -> Option<&ed25519::ValidatableEd25519Signature> {
156
3160
        None
157
3160
    }
158
}
159

            
160
/// Check whether all of the signatures in this Vec are valid.
161
///
162
/// Return `true` if every signature is valid; return `false` if even
163
/// one is invalid.
164
///
165
/// This function should typically give the same result as just
166
/// calling `v.iter().all(ValidatableSignature::is_valid))`, while taking
167
/// advantage of batch verification to whatever extent possible.
168
///
169
/// (See [`ed25519::validate_batch`] for caveats.)
170
3724
pub fn validate_all_sigs(v: &[Box<dyn ValidatableSignature>]) -> bool {
171
3724
    // First we break out the ed25519 signatures (if any) so we can do
172
3724
    // a batch-verification on them.
173
3724
    let mut ed_sigs = Vec::new();
174
3724
    let mut non_ed_sigs = Vec::new();
175
14112
    for sig in v.iter() {
176
14112
        match sig.as_ed25519() {
177
9457
            Some(ed_sig) => ed_sigs.push(ed_sig),
178
4655
            None => non_ed_sigs.push(sig),
179
        }
180
    }
181

            
182
    // Find out if the ed25519 batch is valid.
183
3724
    let ed_batch_is_valid = crate::pk::ed25519::validate_batch(&ed_sigs[..]);
184
3724

            
185
3724
    // if so, verify the rest.
186
6736
    ed_batch_is_valid && non_ed_sigs.iter().all(|b| b.is_valid())
187
3724
}
188

            
189
#[cfg(test)]
190
mod test {
191
    // @@ begin test lint list maintained by maint/add_warning @@
192
    #![allow(clippy::bool_assert_comparison)]
193
    #![allow(clippy::clone_on_copy)]
194
    #![allow(clippy::dbg_macro)]
195
    #![allow(clippy::mixed_attributes_style)]
196
    #![allow(clippy::print_stderr)]
197
    #![allow(clippy::print_stdout)]
198
    #![allow(clippy::single_char_pattern)]
199
    #![allow(clippy::unwrap_used)]
200
    #![allow(clippy::unchecked_duration_subtraction)]
201
    #![allow(clippy::useless_vec)]
202
    #![allow(clippy::needless_pass_by_value)]
203
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
204
    #[test]
205
    fn validatable_ed_sig() {
206
        use super::ed25519::{PublicKey, Signature, ValidatableEd25519Signature};
207
        use super::ValidatableSignature;
208
        use hex_literal::hex;
209
        let pk = PublicKey::from_bytes(&hex!(
210
            "fc51cd8e6218a1a38da47ed00230f058
211
             0816ed13ba3303ac5deb911548908025"
212
        ))
213
        .unwrap();
214
        let sig: Signature = hex!(
215
            "6291d657deec24024827e69c3abe01a3
216
             0ce548a284743a445e3680d7db5ac3ac
217
             18ff9b538d16f290ae67f760984dc659
218
             4a7c15e9716ed28dc027beceea1ec40a"
219
        )
220
        .into();
221

            
222
        let valid = ValidatableEd25519Signature::new(pk, sig, &hex!("af82"));
223
        let invalid = ValidatableEd25519Signature::new(pk, sig, &hex!("af83"));
224

            
225
        assert!(valid.is_valid());
226
        assert!(!invalid.is_valid());
227
    }
228
}