1#![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#[derive(Clone)]
85pub struct Config {
86 dh_keys: AuthenticKeypair,
87 params: NoiseParams,
88 webtransport_certhashes: Option<HashSet<Multihash<64>>>,
89
90 prologue: Vec<u8>,
97}
98
99impl Config {
100 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 pub fn with_prologue(mut self, prologue: Vec<u8>) -> Self {
114 self.prologue = prologue;
115 self
116 }
117
118 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#[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}