#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
mod io;
mod protocol;
use std::{collections::HashSet, fmt::Write, pin::Pin};
use futures::prelude::*;
pub use io::Output;
use libp2p_core::{
upgrade::{InboundConnectionUpgrade, OutboundConnectionUpgrade},
UpgradeInfo,
};
use libp2p_identity as identity;
use libp2p_identity::PeerId;
use multiaddr::Protocol;
use multihash::Multihash;
use snow::params::NoiseParams;
use crate::{
handshake::State,
io::handshake,
protocol::{noise_params_into_builder, AuthenticKeypair, Keypair, PARAMS_XX},
};
#[derive(Clone)]
pub struct Config {
dh_keys: AuthenticKeypair,
params: NoiseParams,
webtransport_certhashes: Option<HashSet<Multihash<64>>>,
prologue: Vec<u8>,
}
impl Config {
pub fn new(identity: &identity::Keypair) -> Result<Self, Error> {
let noise_keys = Keypair::new().into_authentic(identity)?;
Ok(Self {
dh_keys: noise_keys,
params: PARAMS_XX.clone(),
webtransport_certhashes: None,
prologue: vec![],
})
}
pub fn with_prologue(mut self, prologue: Vec<u8>) -> Self {
self.prologue = prologue;
self
}
pub fn with_webtransport_certhashes(mut self, certhashes: HashSet<Multihash<64>>) -> Self {
self.webtransport_certhashes = Some(certhashes).filter(|h| !h.is_empty());
self
}
fn into_responder<S: AsyncRead + AsyncWrite>(self, socket: S) -> Result<State<S>, Error> {
let session = noise_params_into_builder(
self.params,
&self.prologue,
self.dh_keys.keypair.secret(),
None,
)
.build_responder()?;
let state = State::new(
socket,
session,
self.dh_keys.identity,
None,
self.webtransport_certhashes,
);
Ok(state)
}
fn into_initiator<S: AsyncRead + AsyncWrite>(self, socket: S) -> Result<State<S>, Error> {
let session = noise_params_into_builder(
self.params,
&self.prologue,
self.dh_keys.keypair.secret(),
None,
)
.build_initiator()?;
let state = State::new(
socket,
session,
self.dh_keys.identity,
None,
self.webtransport_certhashes,
);
Ok(state)
}
}
impl UpgradeInfo for Config {
type Info = &'static str;
type InfoIter = std::iter::Once<Self::Info>;
fn protocol_info(&self) -> Self::InfoIter {
std::iter::once("/noise")
}
}
impl<T> InboundConnectionUpgrade<T> for Config
where
T: AsyncRead + AsyncWrite + Unpin + Send + 'static,
{
type Output = (PeerId, Output<T>);
type Error = Error;
type Future = Pin<Box<dyn Future<Output = Result<Self::Output, Self::Error>> + Send>>;
fn upgrade_inbound(self, socket: T, _: Self::Info) -> Self::Future {
async move {
let mut state = self.into_responder(socket)?;
handshake::recv_empty(&mut state).await?;
handshake::send_identity(&mut state).await?;
handshake::recv_identity(&mut state).await?;
let (pk, io) = state.finish()?;
Ok((pk.to_peer_id(), io))
}
.boxed()
}
}
impl<T> OutboundConnectionUpgrade<T> for Config
where
T: AsyncRead + AsyncWrite + Unpin + Send + 'static,
{
type Output = (PeerId, Output<T>);
type Error = Error;
type Future = Pin<Box<dyn Future<Output = Result<Self::Output, Self::Error>> + Send>>;
fn upgrade_outbound(self, socket: T, _: Self::Info) -> Self::Future {
async move {
let mut state = self.into_initiator(socket)?;
handshake::send_empty(&mut state).await?;
handshake::recv_identity(&mut state).await?;
handshake::send_identity(&mut state).await?;
let (pk, io) = state.finish()?;
Ok((pk.to_peer_id(), io))
}
.boxed()
}
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
Noise(#[from] snow::Error),
#[error("Invalid public key")]
InvalidKey(#[from] libp2p_identity::DecodingError),
#[error("Only keys of length 32 bytes are supported")]
InvalidLength,
#[error("Remote authenticated with an unexpected public key")]
UnexpectedKey,
#[error("The signature of the remote identity's public key does not verify")]
BadSignature,
#[error("Authentication failed")]
AuthenticationFailed,
#[error("failed to decode protobuf ")]
InvalidPayload(#[from] DecodeError),
#[error(transparent)]
#[allow(clippy::enum_variant_names)]
SigningError(#[from] libp2p_identity::SigningError),
#[error("Expected WebTransport certhashes ({}) are not a subset of received ones ({})", certhashes_to_string(.0), certhashes_to_string(.1))]
UnknownWebTransportCerthashes(HashSet<Multihash<64>>, HashSet<Multihash<64>>),
}
#[derive(Debug, thiserror::Error)]
#[error(transparent)]
pub struct DecodeError(quick_protobuf::Error);
fn certhashes_to_string(certhashes: &HashSet<Multihash<64>>) -> String {
let mut s = String::new();
for hash in certhashes {
write!(&mut s, "{}", Protocol::Certhash(*hash)).unwrap();
}
s
}