libp2p_noise/io/
handshake.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 handshake I/O.
22
23mod proto {
24    #![allow(unreachable_pub)]
25    include!("../generated/mod.rs");
26    pub use self::payload::proto::NoiseExtensions;
27    pub use self::payload::proto::NoiseHandshakePayload;
28}
29
30use crate::io::{framed::NoiseFramed, Output};
31use crate::protocol::{KeypairIdentity, STATIC_KEY_DOMAIN};
32use crate::{DecodeError, Error};
33use bytes::Bytes;
34use futures::prelude::*;
35use libp2p_identity as identity;
36use multihash::Multihash;
37use quick_protobuf::{BytesReader, MessageRead, MessageWrite, Writer};
38use std::collections::HashSet;
39use std::io;
40
41//////////////////////////////////////////////////////////////////////////////
42// Internal
43
44/// Handshake state.
45pub(crate) struct State<T> {
46    /// The underlying I/O resource.
47    io: NoiseFramed<T, snow::HandshakeState>,
48    /// The associated public identity of the local node's static DH keypair,
49    /// which can be sent to the remote as part of an authenticated handshake.
50    identity: KeypairIdentity,
51    /// The received signature over the remote's static DH public key, if any.
52    dh_remote_pubkey_sig: Option<Vec<u8>>,
53    /// The known or received public identity key of the remote, if any.
54    id_remote_pubkey: Option<identity::PublicKey>,
55    /// The WebTransport certhashes of the responder, if any.
56    responder_webtransport_certhashes: Option<HashSet<Multihash<64>>>,
57    /// The received extensions of the remote, if any.
58    remote_extensions: Option<Extensions>,
59}
60
61/// Extensions
62struct Extensions {
63    webtransport_certhashes: HashSet<Multihash<64>>,
64}
65
66impl<T> State<T> {
67    /// Initializes the state for a new Noise handshake, using the given local
68    /// identity keypair and local DH static public key. The handshake messages
69    /// will be sent and received on the given I/O resource and using the
70    /// provided session for cryptographic operations according to the chosen
71    /// Noise handshake pattern.
72
73    pub(crate) fn new(
74        io: T,
75        session: snow::HandshakeState,
76        identity: KeypairIdentity,
77        expected_remote_key: Option<identity::PublicKey>,
78        responder_webtransport_certhashes: Option<HashSet<Multihash<64>>>,
79    ) -> Self {
80        Self {
81            identity,
82            io: NoiseFramed::new(io, session),
83            dh_remote_pubkey_sig: None,
84            id_remote_pubkey: expected_remote_key,
85            responder_webtransport_certhashes,
86            remote_extensions: None,
87        }
88    }
89}
90
91impl<T> State<T> {
92    /// Finish a handshake, yielding the established remote identity and the
93    /// [`Output`] for communicating on the encrypted channel.
94    pub(crate) fn finish(self) -> Result<(identity::PublicKey, Output<T>), Error> {
95        let is_initiator = self.io.is_initiator();
96        let (pubkey, io) = self.io.into_transport()?;
97
98        let id_pk = self
99            .id_remote_pubkey
100            .ok_or_else(|| Error::AuthenticationFailed)?;
101
102        let is_valid_signature = self.dh_remote_pubkey_sig.as_ref().map_or(false, |s| {
103            id_pk.verify(&[STATIC_KEY_DOMAIN.as_bytes(), pubkey.as_ref()].concat(), s)
104        });
105
106        if !is_valid_signature {
107            return Err(Error::BadSignature);
108        }
109
110        // Check WebTransport certhashes that responder reported back to us.
111        if is_initiator {
112            // We check only if we care (i.e. Config::with_webtransport_certhashes was used).
113            if let Some(expected_certhashes) = self.responder_webtransport_certhashes {
114                let ext = self.remote_extensions.ok_or_else(|| {
115                    Error::UnknownWebTransportCerthashes(
116                        expected_certhashes.to_owned(),
117                        HashSet::new(),
118                    )
119                })?;
120
121                let received_certhashes = ext.webtransport_certhashes;
122
123                // Expected WebTransport certhashes must be a strict subset
124                // of the reported ones.
125                if !expected_certhashes.is_subset(&received_certhashes) {
126                    return Err(Error::UnknownWebTransportCerthashes(
127                        expected_certhashes,
128                        received_certhashes,
129                    ));
130                }
131            }
132        }
133
134        Ok((id_pk, io))
135    }
136}
137
138impl From<proto::NoiseExtensions> for Extensions {
139    fn from(value: proto::NoiseExtensions) -> Self {
140        Extensions {
141            webtransport_certhashes: value
142                .webtransport_certhashes
143                .into_iter()
144                .filter_map(|bytes| Multihash::read(&bytes[..]).ok())
145                .collect(),
146        }
147    }
148}
149
150//////////////////////////////////////////////////////////////////////////////
151// Handshake Message Futures
152
153/// A future for receiving a Noise handshake message.
154async fn recv<T>(state: &mut State<T>) -> Result<Bytes, Error>
155where
156    T: AsyncRead + Unpin,
157{
158    match state.io.next().await {
159        None => Err(io::Error::new(io::ErrorKind::UnexpectedEof, "eof").into()),
160        Some(Err(e)) => Err(e.into()),
161        Some(Ok(m)) => Ok(m),
162    }
163}
164
165/// A future for receiving a Noise handshake message with an empty payload.
166pub(crate) async fn recv_empty<T>(state: &mut State<T>) -> Result<(), Error>
167where
168    T: AsyncRead + Unpin,
169{
170    let msg = recv(state).await?;
171    if !msg.is_empty() {
172        return Err(
173            io::Error::new(io::ErrorKind::InvalidData, "Unexpected handshake payload.").into(),
174        );
175    }
176    Ok(())
177}
178
179/// A future for sending a Noise handshake message with an empty payload.
180pub(crate) async fn send_empty<T>(state: &mut State<T>) -> Result<(), Error>
181where
182    T: AsyncWrite + Unpin,
183{
184    state.io.send(&Vec::new()).await?;
185    Ok(())
186}
187
188/// A future for receiving a Noise handshake message with a payload identifying the remote.
189pub(crate) async fn recv_identity<T>(state: &mut State<T>) -> Result<(), Error>
190where
191    T: AsyncRead + Unpin,
192{
193    let msg = recv(state).await?;
194    let mut reader = BytesReader::from_bytes(&msg[..]);
195    let pb =
196        proto::NoiseHandshakePayload::from_reader(&mut reader, &msg[..]).map_err(DecodeError)?;
197
198    state.id_remote_pubkey = Some(identity::PublicKey::try_decode_protobuf(&pb.identity_key)?);
199
200    if !pb.identity_sig.is_empty() {
201        state.dh_remote_pubkey_sig = Some(pb.identity_sig);
202    }
203
204    if let Some(extensions) = pb.extensions {
205        state.remote_extensions = Some(extensions.into());
206    }
207
208    Ok(())
209}
210
211/// Send a Noise handshake message with a payload identifying the local node to the remote.
212pub(crate) async fn send_identity<T>(state: &mut State<T>) -> Result<(), Error>
213where
214    T: AsyncWrite + Unpin,
215{
216    let mut pb = proto::NoiseHandshakePayload {
217        identity_key: state.identity.public.encode_protobuf(),
218        ..Default::default()
219    };
220
221    pb.identity_sig = state.identity.signature.clone();
222
223    // If this is the responder then send WebTransport certhashes to initiator, if any.
224    if state.io.is_responder() {
225        if let Some(ref certhashes) = state.responder_webtransport_certhashes {
226            let ext = pb
227                .extensions
228                .get_or_insert_with(proto::NoiseExtensions::default);
229
230            ext.webtransport_certhashes = certhashes.iter().map(|hash| hash.to_bytes()).collect();
231        }
232    }
233
234    let mut msg = Vec::with_capacity(pb.get_size());
235
236    let mut writer = Writer::new(&mut msg);
237    pb.write_message(&mut writer).expect("Encoding to succeed");
238    state.io.send(&msg).await?;
239
240    Ok(())
241}