libp2p_noise/
protocol.rs

1// Copyright 2019 Parity Technologies (UK) Ltd.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a
4// copy of this software and associated documentation files (the "Software"),
5// to deal in the Software without restriction, including without limitation
6// the rights to use, copy, modify, merge, publish, distribute, sublicense,
7// and/or sell copies of the Software, and to permit persons to whom the
8// Software is furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in
11// all copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
14// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19// DEALINGS IN THE SOFTWARE.
20
21//! Components of a Noise protocol.
22
23use crate::Error;
24use libp2p_identity as identity;
25use once_cell::sync::Lazy;
26use rand::{Rng as _, SeedableRng};
27use snow::params::NoiseParams;
28use x25519_dalek::{x25519, X25519_BASEPOINT_BYTES};
29use zeroize::Zeroize;
30
31/// Prefix of static key signatures for domain separation.
32pub(crate) const STATIC_KEY_DOMAIN: &str = "noise-libp2p-static-key:";
33
34pub(crate) static PARAMS_XX: Lazy<NoiseParams> = Lazy::new(|| {
35    "Noise_XX_25519_ChaChaPoly_SHA256"
36        .parse()
37        .expect("Invalid protocol name")
38});
39
40pub(crate) fn noise_params_into_builder<'b>(
41    params: NoiseParams,
42    prologue: &'b [u8],
43    private_key: &'b SecretKey,
44    remote_public_key: Option<&'b PublicKey>,
45) -> snow::Builder<'b> {
46    let mut builder = snow::Builder::with_resolver(params, Box::new(Resolver))
47        .prologue(prologue.as_ref())
48        .local_private_key(private_key.as_ref());
49
50    if let Some(remote_public_key) = remote_public_key {
51        builder = builder.remote_public_key(remote_public_key.as_ref());
52    }
53
54    builder
55}
56
57/// DH keypair.
58#[derive(Clone)]
59pub(crate) struct Keypair {
60    secret: SecretKey,
61    public: PublicKey,
62}
63
64/// A DH keypair that is authentic w.r.t. a [`identity::PublicKey`].
65#[derive(Clone)]
66pub(crate) struct AuthenticKeypair {
67    pub(crate) keypair: Keypair,
68    pub(crate) identity: KeypairIdentity,
69}
70
71/// The associated public identity of a DH keypair.
72#[derive(Clone)]
73pub(crate) struct KeypairIdentity {
74    /// The public identity key.
75    pub(crate) public: identity::PublicKey,
76    /// The signature over the public DH key.
77    pub(crate) signature: Vec<u8>,
78}
79
80impl Keypair {
81    /// The secret key of the DH keypair.
82    pub(crate) fn secret(&self) -> &SecretKey {
83        &self.secret
84    }
85
86    /// Turn this DH keypair into a [`AuthenticKeypair`], i.e. a DH keypair that
87    /// is authentic w.r.t. the given identity keypair, by signing the DH public key.
88    pub(crate) fn into_authentic(
89        self,
90        id_keys: &identity::Keypair,
91    ) -> Result<AuthenticKeypair, Error> {
92        let sig = id_keys.sign(&[STATIC_KEY_DOMAIN.as_bytes(), self.public.as_ref()].concat())?;
93
94        let identity = KeypairIdentity {
95            public: id_keys.public(),
96            signature: sig,
97        };
98
99        Ok(AuthenticKeypair {
100            keypair: self,
101            identity,
102        })
103    }
104
105    /// An "empty" keypair as a starting state for DH computations in `snow`,
106    /// which get manipulated through the `snow::types::Dh` interface.
107    pub(crate) fn empty() -> Self {
108        Keypair {
109            secret: SecretKey([0u8; 32]),
110            public: PublicKey([0u8; 32]),
111        }
112    }
113
114    /// Create a new X25519 keypair.
115    pub(crate) fn new() -> Keypair {
116        let mut sk_bytes = [0u8; 32];
117        rand::thread_rng().fill(&mut sk_bytes);
118        let sk = SecretKey(sk_bytes); // Copy
119        sk_bytes.zeroize();
120        Self::from(sk)
121    }
122}
123
124/// DH secret key.
125#[derive(Clone, Default)]
126pub(crate) struct SecretKey([u8; 32]);
127
128impl Drop for SecretKey {
129    fn drop(&mut self) {
130        self.0.zeroize()
131    }
132}
133
134impl AsRef<[u8]> for SecretKey {
135    fn as_ref(&self) -> &[u8] {
136        self.0.as_ref()
137    }
138}
139
140/// DH public key.
141#[derive(Clone, PartialEq, Default)]
142pub(crate) struct PublicKey([u8; 32]);
143
144impl PublicKey {
145    pub(crate) fn from_slice(slice: &[u8]) -> Result<Self, Error> {
146        if slice.len() != 32 {
147            return Err(Error::InvalidLength);
148        }
149
150        let mut key = [0u8; 32];
151        key.copy_from_slice(slice);
152        Ok(PublicKey(key))
153    }
154}
155
156impl AsRef<[u8]> for PublicKey {
157    fn as_ref(&self) -> &[u8] {
158        self.0.as_ref()
159    }
160}
161
162/// Custom `snow::CryptoResolver` which delegates to either the
163/// `RingResolver` on native or the `DefaultResolver` on wasm
164/// for hash functions and symmetric ciphers, while using x25519-dalek
165/// for Curve25519 DH.
166struct Resolver;
167
168impl snow::resolvers::CryptoResolver for Resolver {
169    fn resolve_rng(&self) -> Option<Box<dyn snow::types::Random>> {
170        Some(Box::new(Rng(rand::rngs::StdRng::from_entropy())))
171    }
172
173    fn resolve_dh(&self, choice: &snow::params::DHChoice) -> Option<Box<dyn snow::types::Dh>> {
174        if let snow::params::DHChoice::Curve25519 = choice {
175            Some(Box::new(Keypair::empty()))
176        } else {
177            None
178        }
179    }
180
181    fn resolve_hash(
182        &self,
183        choice: &snow::params::HashChoice,
184    ) -> Option<Box<dyn snow::types::Hash>> {
185        #[cfg(target_arch = "wasm32")]
186        {
187            snow::resolvers::DefaultResolver.resolve_hash(choice)
188        }
189        #[cfg(not(target_arch = "wasm32"))]
190        {
191            snow::resolvers::RingResolver.resolve_hash(choice)
192        }
193    }
194
195    fn resolve_cipher(
196        &self,
197        choice: &snow::params::CipherChoice,
198    ) -> Option<Box<dyn snow::types::Cipher>> {
199        #[cfg(target_arch = "wasm32")]
200        {
201            snow::resolvers::DefaultResolver.resolve_cipher(choice)
202        }
203        #[cfg(not(target_arch = "wasm32"))]
204        {
205            snow::resolvers::RingResolver.resolve_cipher(choice)
206        }
207    }
208}
209
210/// Wrapper around a CSPRNG to implement `snow::Random` trait for.
211struct Rng(rand::rngs::StdRng);
212
213impl rand::RngCore for Rng {
214    fn next_u32(&mut self) -> u32 {
215        self.0.next_u32()
216    }
217
218    fn next_u64(&mut self) -> u64 {
219        self.0.next_u64()
220    }
221
222    fn fill_bytes(&mut self, dest: &mut [u8]) {
223        self.0.fill_bytes(dest)
224    }
225
226    fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> {
227        self.0.try_fill_bytes(dest)
228    }
229}
230
231impl rand::CryptoRng for Rng {}
232
233impl snow::types::Random for Rng {}
234
235impl Default for Keypair {
236    fn default() -> Self {
237        Self::new()
238    }
239}
240
241/// Promote a X25519 secret key into a keypair.
242impl From<SecretKey> for Keypair {
243    fn from(secret: SecretKey) -> Keypair {
244        let public = PublicKey(x25519(secret.0, X25519_BASEPOINT_BYTES));
245        Keypair { secret, public }
246    }
247}
248
249#[doc(hidden)]
250impl snow::types::Dh for Keypair {
251    fn name(&self) -> &'static str {
252        "25519"
253    }
254    fn pub_len(&self) -> usize {
255        32
256    }
257    fn priv_len(&self) -> usize {
258        32
259    }
260    fn pubkey(&self) -> &[u8] {
261        self.public.as_ref()
262    }
263    fn privkey(&self) -> &[u8] {
264        self.secret.as_ref()
265    }
266
267    fn set(&mut self, sk: &[u8]) {
268        let mut secret = [0u8; 32];
269        secret.copy_from_slice(sk);
270        self.secret = SecretKey(secret); // Copy
271        self.public = PublicKey(x25519(secret, X25519_BASEPOINT_BYTES));
272        secret.zeroize();
273    }
274
275    fn generate(&mut self, rng: &mut dyn snow::types::Random) {
276        let mut secret = [0u8; 32];
277        rng.fill_bytes(&mut secret);
278        self.secret = SecretKey(secret); // Copy
279        self.public = PublicKey(x25519(secret, X25519_BASEPOINT_BYTES));
280        secret.zeroize();
281    }
282
283    fn dh(&self, pk: &[u8], shared_secret: &mut [u8]) -> Result<(), snow::Error> {
284        let mut p = [0; 32];
285        p.copy_from_slice(&pk[..32]);
286        let ss = x25519(self.secret.0, p);
287        shared_secret[..32].copy_from_slice(&ss[..]);
288        Ok(())
289    }
290}
291
292#[cfg(test)]
293mod tests {
294    use super::*;
295    use crate::protocol::PARAMS_XX;
296    use once_cell::sync::Lazy;
297
298    #[test]
299    fn handshake_hashes_disagree_if_prologue_differs() {
300        let alice = xx_builder(b"alice prologue").build_initiator().unwrap();
301        let bob = xx_builder(b"bob prologue").build_responder().unwrap();
302
303        let alice_handshake_hash = alice.get_handshake_hash();
304        let bob_handshake_hash = bob.get_handshake_hash();
305
306        assert_ne!(alice_handshake_hash, bob_handshake_hash)
307    }
308
309    #[test]
310    fn handshake_hashes_agree_if_prologue_is_the_same() {
311        let alice = xx_builder(b"shared knowledge").build_initiator().unwrap();
312        let bob = xx_builder(b"shared knowledge").build_responder().unwrap();
313
314        let alice_handshake_hash = alice.get_handshake_hash();
315        let bob_handshake_hash = bob.get_handshake_hash();
316
317        assert_eq!(alice_handshake_hash, bob_handshake_hash)
318    }
319
320    fn xx_builder(prologue: &'static [u8]) -> snow::Builder<'static> {
321        noise_params_into_builder(PARAMS_XX.clone(), prologue, TEST_KEY.secret(), None)
322    }
323
324    // Hack to work around borrow-checker.
325    static TEST_KEY: Lazy<Keypair> = Lazy::new(Keypair::new);
326}