libp2p_noise/
lib.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//! [Noise protocol framework][noise] support for libp2p.
22//!
23//! > **Note**: This crate is still experimental and subject to major breaking changes
24//! > both on the API and the wire protocol.
25//!
26//! This crate provides `libp2p_core::InboundUpgrade` and `libp2p_core::OutboundUpgrade`
27//! implementations for various noise handshake patterns (currently `IK`, `IX`, and `XX`)
28//! over a particular choice of Diffie–Hellman key agreement (currently only X25519).
29//!
30//! > **Note**: Only the `XX` handshake pattern is currently guaranteed to provide
31//! > interoperability with other libp2p implementations.
32//!
33//! All upgrades produce as output a pair, consisting of the remote's static public key
34//! and a `NoiseOutput` which represents the established cryptographic session with the
35//! remote, implementing `futures::io::AsyncRead` and `futures::io::AsyncWrite`.
36//!
37//! # Usage
38//!
39//! Example:
40//!
41//! ```
42//! use libp2p_core::{transport::MemoryTransport, upgrade, Transport};
43//! use libp2p_identity as identity;
44//! use libp2p_noise as noise;
45//!
46//! # fn main() {
47//! let id_keys = identity::Keypair::generate_ed25519();
48//! let noise = noise::Config::new(&id_keys).unwrap();
49//! let builder = MemoryTransport::default()
50//!     .upgrade(upgrade::Version::V1)
51//!     .authenticate(noise);
52//! // let transport = builder.multiplex(...);
53//! # }
54//! ```
55//!
56//! [noise]: http://noiseprotocol.org/
57
58#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
59
60mod io;
61mod protocol;
62
63use std::{collections::HashSet, fmt::Write, pin::Pin};
64
65use futures::prelude::*;
66pub use io::Output;
67use libp2p_core::{
68    upgrade::{InboundConnectionUpgrade, OutboundConnectionUpgrade},
69    UpgradeInfo,
70};
71use libp2p_identity as identity;
72use libp2p_identity::PeerId;
73use multiaddr::Protocol;
74use multihash::Multihash;
75use snow::params::NoiseParams;
76
77use crate::{
78    handshake::State,
79    io::handshake,
80    protocol::{noise_params_into_builder, AuthenticKeypair, Keypair, PARAMS_XX},
81};
82
83/// The configuration for the noise handshake.
84#[derive(Clone)]
85pub struct Config {
86    dh_keys: AuthenticKeypair,
87    params: NoiseParams,
88    webtransport_certhashes: Option<HashSet<Multihash<64>>>,
89
90    /// Prologue to use in the noise handshake.
91    ///
92    /// The prologue can contain arbitrary data that will be hashed into the noise handshake.
93    /// For the handshake to succeed, both parties must set the same prologue.
94    ///
95    /// For further information, see <https://noiseprotocol.org/noise.html#prologue>.
96    prologue: Vec<u8>,
97}
98
99impl Config {
100    /// Construct a new configuration for the noise handshake using the XX handshake pattern.
101    pub fn new(identity: &identity::Keypair) -> Result<Self, Error> {
102        let noise_keys = Keypair::new().into_authentic(identity)?;
103
104        Ok(Self {
105            dh_keys: noise_keys,
106            params: PARAMS_XX.clone(),
107            webtransport_certhashes: None,
108            prologue: vec![],
109        })
110    }
111
112    /// Set the noise prologue.
113    pub fn with_prologue(mut self, prologue: Vec<u8>) -> Self {
114        self.prologue = prologue;
115        self
116    }
117
118    /// Set WebTransport certhashes extension.
119    ///
120    /// In case of initiator, these certhashes will be used to validate the ones reported by
121    /// responder.
122    ///
123    /// In case of responder, these certhashes will be reported to initiator.
124    pub fn with_webtransport_certhashes(mut self, certhashes: HashSet<Multihash<64>>) -> Self {
125        self.webtransport_certhashes = Some(certhashes).filter(|h| !h.is_empty());
126        self
127    }
128
129    fn into_responder<S: AsyncRead + AsyncWrite>(self, socket: S) -> Result<State<S>, Error> {
130        let session = noise_params_into_builder(
131            self.params,
132            &self.prologue,
133            self.dh_keys.keypair.secret(),
134            None,
135        )
136        .build_responder()?;
137
138        let state = State::new(
139            socket,
140            session,
141            self.dh_keys.identity,
142            None,
143            self.webtransport_certhashes,
144        );
145
146        Ok(state)
147    }
148
149    fn into_initiator<S: AsyncRead + AsyncWrite>(self, socket: S) -> Result<State<S>, Error> {
150        let session = noise_params_into_builder(
151            self.params,
152            &self.prologue,
153            self.dh_keys.keypair.secret(),
154            None,
155        )
156        .build_initiator()?;
157
158        let state = State::new(
159            socket,
160            session,
161            self.dh_keys.identity,
162            None,
163            self.webtransport_certhashes,
164        );
165
166        Ok(state)
167    }
168}
169
170impl UpgradeInfo for Config {
171    type Info = &'static str;
172    type InfoIter = std::iter::Once<Self::Info>;
173
174    fn protocol_info(&self) -> Self::InfoIter {
175        std::iter::once("/noise")
176    }
177}
178
179impl<T> InboundConnectionUpgrade<T> for Config
180where
181    T: AsyncRead + AsyncWrite + Unpin + Send + 'static,
182{
183    type Output = (PeerId, Output<T>);
184    type Error = Error;
185    type Future = Pin<Box<dyn Future<Output = Result<Self::Output, Self::Error>> + Send>>;
186
187    fn upgrade_inbound(self, socket: T, _: Self::Info) -> Self::Future {
188        async move {
189            let mut state = self.into_responder(socket)?;
190
191            handshake::recv_empty(&mut state).await?;
192            handshake::send_identity(&mut state).await?;
193            handshake::recv_identity(&mut state).await?;
194
195            let (pk, io) = state.finish()?;
196
197            Ok((pk.to_peer_id(), io))
198        }
199        .boxed()
200    }
201}
202
203impl<T> OutboundConnectionUpgrade<T> for Config
204where
205    T: AsyncRead + AsyncWrite + Unpin + Send + 'static,
206{
207    type Output = (PeerId, Output<T>);
208    type Error = Error;
209    type Future = Pin<Box<dyn Future<Output = Result<Self::Output, Self::Error>> + Send>>;
210
211    fn upgrade_outbound(self, socket: T, _: Self::Info) -> Self::Future {
212        async move {
213            let mut state = self.into_initiator(socket)?;
214
215            handshake::send_empty(&mut state).await?;
216            handshake::recv_identity(&mut state).await?;
217            handshake::send_identity(&mut state).await?;
218
219            let (pk, io) = state.finish()?;
220
221            Ok((pk.to_peer_id(), io))
222        }
223        .boxed()
224    }
225}
226
227/// libp2p_noise error type.
228#[derive(Debug, thiserror::Error)]
229#[non_exhaustive]
230pub enum Error {
231    #[error(transparent)]
232    Io(#[from] std::io::Error),
233    #[error(transparent)]
234    Noise(#[from] snow::Error),
235    #[error("Invalid public key")]
236    InvalidKey(#[from] libp2p_identity::DecodingError),
237    #[error("Only keys of length 32 bytes are supported")]
238    InvalidLength,
239    #[error("Remote authenticated with an unexpected public key")]
240    UnexpectedKey,
241    #[error("The signature of the remote identity's public key does not verify")]
242    BadSignature,
243    #[error("Authentication failed")]
244    AuthenticationFailed,
245    #[error("failed to decode protobuf ")]
246    InvalidPayload(#[from] DecodeError),
247    #[error(transparent)]
248    #[allow(clippy::enum_variant_names)]
249    SigningError(#[from] libp2p_identity::SigningError),
250    #[error("Expected WebTransport certhashes ({}) are not a subset of received ones ({})", certhashes_to_string(.0), certhashes_to_string(.1))]
251    UnknownWebTransportCerthashes(HashSet<Multihash<64>>, HashSet<Multihash<64>>),
252}
253
254#[derive(Debug, thiserror::Error)]
255#[error(transparent)]
256pub struct DecodeError(quick_protobuf::Error);
257
258fn certhashes_to_string(certhashes: &HashSet<Multihash<64>>) -> String {
259    let mut s = String::new();
260
261    for hash in certhashes {
262        write!(&mut s, "{}", Protocol::Certhash(*hash)).unwrap();
263    }
264
265    s
266}