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 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 fn from(secret: &'a EphemeralSecret) -> Self {
67 Self((&secret.0).into())
68 }
69 }
70
71 impl<'a> From<&'a StaticSecret> for PublicKey {
72 fn from(secret: &'a StaticSecret) -> Self {
73 Self((&secret.0).into())
74 }
75 }
76
77 impl From<[u8; 32]> for StaticSecret {
78 fn from(value: [u8; 32]) -> Self {
79 Self(value.into())
80 }
81 }
82 impl From<[u8; 32]> for PublicKey {
83 fn from(value: [u8; 32]) -> Self {
84 Self(value.into())
85 }
86 }
87
88 impl EphemeralSecret {
89 /// Return a new random ephemeral secret key.
90 pub fn random_from_rng<R: rand_core::RngCore + rand_core::CryptoRng>(csprng: R) -> Self {
91 Self(x25519_dalek::EphemeralSecret::random_from_rng(
92 RngCompat::new(csprng),
93 ))
94 }
95 /// Negotiate a shared secret using this secret key and a public key.
96 pub fn diffie_hellman(self, their_public: &PublicKey) -> SharedSecret {
97 SharedSecret(self.0.diffie_hellman(&their_public.0))
98 }
99 }
100 impl StaticSecret {
101 /// Return a new random static secret key.
102 pub fn random_from_rng<R: rand_core::RngCore + rand_core::CryptoRng>(csprng: R) -> Self {
103 Self(x25519_dalek::StaticSecret::random_from_rng(RngCompat::new(
104 csprng,
105 )))
106 }
107 /// Negotiate a shared secret using this secret key and a public key.
108 pub fn diffie_hellman(&self, their_public: &PublicKey) -> SharedSecret {
109 SharedSecret(self.0.diffie_hellman(&their_public.0))
110 }
111 /// Return the bytes that represent this key.
112 pub fn to_bytes(&self) -> [u8; 32] {
113 self.0.to_bytes()
114 }
115 }
116 impl SharedSecret {
117 /// Return the shared secret as an array of bytes.
118 pub fn as_bytes(&self) -> &[u8; 32] {
119 self.0.as_bytes()
120 }
121 /// Return true if both keys contributed to this shared secret.
122 ///
123 /// See [`x25519_dalek::SharedSecret::was_contributory`] for more information.
124 pub fn was_contributory(&self) -> bool {
125 self.0.was_contributory()
126 }
127 }
128 impl PublicKey {
129 /// Return this public key as a reference to an array of bytes.
130 pub fn as_bytes(&self) -> &[u8; 32] {
131 self.0.as_bytes()
132 }
133 /// Return this public key as an array of bytes.
134 pub fn to_bytes(&self) -> [u8; 32] {
135 self.0.to_bytes()
136 }
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`].
150pub 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 fn as_ed25519(&self) -> Option<&ed25519::ValidatableEd25519Signature> {
156 None
157 }
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.)
170pub fn validate_all_sigs(v: &[Box<dyn ValidatableSignature>]) -> bool {
171 // First we break out the ed25519 signatures (if any) so we can do
172 // a batch-verification on them.
173 let mut ed_sigs = Vec::new();
174 let mut non_ed_sigs = Vec::new();
175 for sig in v.iter() {
176 match sig.as_ed25519() {
177 Some(ed_sig) => ed_sigs.push(ed_sig),
178 None => non_ed_sigs.push(sig),
179 }
180 }
181
182 // Find out if the ed25519 batch is valid.
183 let ed_batch_is_valid = crate::pk::ed25519::validate_batch(&ed_sigs[..]);
184
185 // if so, verify the rest.
186 ed_batch_is_valid && non_ed_sigs.iter().all(|b| b.is_valid())
187}
188
189#[cfg(test)]
190mod 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}