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