mirror of
https://github.com/darkrenaissance/darkfi.git
synced 2026-04-28 03:00:18 -04:00
net: Implement QUIC transport based on quinn
Also deduplicate some TLS code.
This commit is contained in:
491
Cargo.lock
generated
491
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -67,6 +67,7 @@ tor-hscrypto = {version = "0.37.0", optional = true}
|
||||
tor-hsservice = {version = "0.37.0", optional = true}
|
||||
tor-proto = {version = "0.37.0", optional = true}
|
||||
tor-cell = {version = "0.37.0", optional = true}
|
||||
quinn = {git="https://github.com/parazyd/quinn", branch="no-tokio", default-features = false, features = ["rustls-ring", "runtime-smol"], optional = true}
|
||||
|
||||
# TLS cert utilities
|
||||
ed25519-compact = {version = "2.2.0", optional = true}
|
||||
@@ -228,6 +229,7 @@ net-defaults = [
|
||||
"p2p-i2p",
|
||||
"p2p-socks5",
|
||||
"p2p-unix",
|
||||
"p2p-quic",
|
||||
]
|
||||
|
||||
p2p-unix = []
|
||||
@@ -238,6 +240,10 @@ p2p-i2p = [
|
||||
"p2p-socks5"
|
||||
]
|
||||
|
||||
p2p-quic = [
|
||||
"quinn"
|
||||
]
|
||||
|
||||
net = ["net-defaults"]
|
||||
|
||||
rpc = [
|
||||
|
||||
@@ -48,6 +48,10 @@ pub(crate) mod nym;
|
||||
#[cfg(feature = "p2p-unix")]
|
||||
pub(crate) mod unix;
|
||||
|
||||
/// QUIC transport
|
||||
#[cfg(feature = "p2p-quic")]
|
||||
pub(crate) mod quic;
|
||||
|
||||
/// Dialer variants
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum DialerVariant {
|
||||
@@ -84,6 +88,9 @@ pub enum DialerVariant {
|
||||
/// SOCKS5 proxy with TLS
|
||||
#[cfg(feature = "p2p-socks5")]
|
||||
Socks5Tls(socks5::Socks5Dialer),
|
||||
|
||||
/// QUIC (with built-in TLS)
|
||||
Quic(quic::QuicDialer),
|
||||
}
|
||||
|
||||
/// Listener variants
|
||||
@@ -99,9 +106,13 @@ pub enum ListenerVariant {
|
||||
/// Tor
|
||||
Tor(tor::TorListener),
|
||||
|
||||
/// Unix socket
|
||||
#[cfg(feature = "p2p-unix")]
|
||||
/// Unix socket
|
||||
Unix(unix::UnixListener),
|
||||
|
||||
#[cfg(feature = "p2p-quic")]
|
||||
/// QUIC (with built-in TLS)
|
||||
Quic(quic::QuicListener),
|
||||
}
|
||||
|
||||
/// A dialer that is able to transparently operate over arbitrary transports.
|
||||
@@ -243,6 +254,15 @@ impl Dialer {
|
||||
Ok(Self { endpoint, variant })
|
||||
}
|
||||
|
||||
#[cfg(feature = "p2p-quic")]
|
||||
"quic" => {
|
||||
// Build a QUIC dialer (TLS is built-in)
|
||||
enforce_hostport!(endpoint);
|
||||
let variant = quic::QuicDialer::new().await?;
|
||||
let variant = DialerVariant::Quic(variant);
|
||||
Ok(Self { endpoint, variant })
|
||||
}
|
||||
|
||||
x => {
|
||||
error!("[P2P] Requested unsupported transport: {x}");
|
||||
Err(io::Error::from_raw_os_error(libc::ENETUNREACH))
|
||||
@@ -323,6 +343,13 @@ impl Dialer {
|
||||
let stream = tlsupgrade.upgrade_dialer_tls(stream).await?;
|
||||
Ok(Box::new(stream))
|
||||
}
|
||||
|
||||
#[cfg(feature = "p2p-quic")]
|
||||
DialerVariant::Quic(dialer) => {
|
||||
let sockaddr = self.endpoint.socket_addrs(|| None)?;
|
||||
let stream = dialer.do_dial(sockaddr[0], timeout).await?;
|
||||
Ok(Box::new(stream))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -378,6 +405,14 @@ impl Listener {
|
||||
Ok(Self { endpoint, variant })
|
||||
}
|
||||
|
||||
#[cfg(feature = "p2p-quic")]
|
||||
"quic" => {
|
||||
enforce_hostport!(endpoint);
|
||||
let variant = quic::QuicListener::new().await?;
|
||||
let variant = ListenerVariant::Quic(variant);
|
||||
Ok(Self { endpoint, variant })
|
||||
}
|
||||
|
||||
x => {
|
||||
error!("[P2P] Requested unsupported transport: {x}");
|
||||
Err(io::Error::from_raw_os_error(libc::ENETUNREACH))
|
||||
@@ -419,6 +454,13 @@ impl Listener {
|
||||
let l = listener.do_listen(&path).await?;
|
||||
Ok(Box::new(l))
|
||||
}
|
||||
|
||||
#[cfg(feature = "p2p-quic")]
|
||||
ListenerVariant::Quic(listener) => {
|
||||
let sockaddr = self.endpoint.socket_addrs(|| None)?;
|
||||
let l = listener.do_listen(sockaddr[0]).await?;
|
||||
Ok(Box::new(l))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -443,8 +485,24 @@ impl Listener {
|
||||
|
||||
endpoint
|
||||
}
|
||||
|
||||
#[cfg(feature = "p2p-tor")]
|
||||
ListenerVariant::Tor(listener) => listener.endpoint.get().unwrap().clone(),
|
||||
|
||||
#[cfg(feature = "p2p-quic")]
|
||||
ListenerVariant::Quic(listener) => {
|
||||
let mut endpoint = self.endpoint.clone();
|
||||
let port = self.endpoint.port().unwrap();
|
||||
|
||||
if port == 0 {
|
||||
if let Some(actual_port) = listener.port.get() {
|
||||
endpoint.set_port(Some(*actual_port)).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
endpoint
|
||||
}
|
||||
|
||||
#[allow(unreachable_patterns)]
|
||||
_ => self.endpoint.clone(),
|
||||
}
|
||||
@@ -467,6 +525,9 @@ impl PtStream for futures_rustls::TlsStream<arti_client::DataStream> {}
|
||||
#[cfg(feature = "p2p-unix")]
|
||||
impl PtStream for smol::net::unix::UnixStream {}
|
||||
|
||||
#[cfg(feature = "p2p-quic")]
|
||||
impl PtStream for quic::QuicStream {}
|
||||
|
||||
/// Wrapper trait for async listeners
|
||||
#[async_trait]
|
||||
pub trait PtListener: Send + Unpin {
|
||||
|
||||
286
src/net/transport/quic.rs
Normal file
286
src/net/transport/quic.rs
Normal file
@@ -0,0 +1,286 @@
|
||||
/* This file is part of DarkFi (https://dark.fi)
|
||||
*
|
||||
* Copyright (C) 2020-2026 Dyne.org foundation
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use std::{
|
||||
io,
|
||||
net::SocketAddr,
|
||||
pin::Pin,
|
||||
sync::Arc,
|
||||
task::{Context, Poll},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use futures::{
|
||||
future::{select, Either},
|
||||
pin_mut,
|
||||
};
|
||||
use futures_rustls::rustls::{self, version::TLS13};
|
||||
use quinn::{
|
||||
crypto::rustls::{QuicClientConfig, QuicServerConfig},
|
||||
ClientConfig, Endpoint, RecvStream, SendStream, ServerConfig, TransportConfig, VarInt,
|
||||
};
|
||||
use smol::{
|
||||
io::{AsyncRead, AsyncWrite},
|
||||
lock::OnceCell,
|
||||
Timer,
|
||||
};
|
||||
use tracing::debug;
|
||||
use url::Url;
|
||||
|
||||
use super::{
|
||||
tls::{
|
||||
generate_certificate, ClientCertificateVerifier, ServerCertificateVerifier, TLS_DNS_NAME,
|
||||
},
|
||||
PtListener, PtStream,
|
||||
};
|
||||
|
||||
/// Create QUIC client configuration with our TLS config
|
||||
fn create_client_config() -> io::Result<ClientConfig> {
|
||||
let (certificate, secret_key) = generate_certificate()?;
|
||||
|
||||
let server_cert_verifier = Arc::new(ServerCertificateVerifier {});
|
||||
|
||||
let tls_config = rustls::ClientConfig::builder_with_protocol_versions(&[&TLS13])
|
||||
.dangerous()
|
||||
.with_custom_certificate_verifier(server_cert_verifier)
|
||||
.with_client_auth_cert(vec![certificate], secret_key)
|
||||
.map_err(|e| io::Error::other(format!("Failed to create QUIC client TLS config: {e}")))?;
|
||||
|
||||
let quic_config: QuicClientConfig = tls_config
|
||||
.try_into()
|
||||
.map_err(|e| io::Error::other(format!("Failed to create QUIC client config: {e}")))?;
|
||||
|
||||
let mut config = ClientConfig::new(Arc::new(quic_config));
|
||||
|
||||
// Configure transport parameters
|
||||
let mut transport = TransportConfig::default();
|
||||
transport.keep_alive_interval(Some(Duration::from_secs(15)));
|
||||
transport.max_idle_timeout(Some(VarInt::from_u32(30_000).into()));
|
||||
config.transport_config(Arc::new(transport));
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
/// Create QUIC server configuration with our TLS config
|
||||
fn create_server_config() -> io::Result<ServerConfig> {
|
||||
let (certificate, secret_key) = generate_certificate()?;
|
||||
|
||||
let client_cert_verifier = Arc::new(ClientCertificateVerifier {});
|
||||
|
||||
let tls_config = rustls::ServerConfig::builder_with_protocol_versions(&[&TLS13])
|
||||
.with_client_cert_verifier(client_cert_verifier)
|
||||
.with_single_cert(vec![certificate], secret_key)
|
||||
.map_err(|e| io::Error::other(format!("Failed to create QUIC server TLS config: {e}")))?;
|
||||
|
||||
let quic_config: QuicServerConfig = tls_config
|
||||
.try_into()
|
||||
.map_err(|e| io::Error::other(format!("Failed to create QUIC server config: {e}")))?;
|
||||
|
||||
let mut config = ServerConfig::with_crypto(Arc::new(quic_config));
|
||||
|
||||
// Configure transport parameters
|
||||
let mut transport = TransportConfig::default();
|
||||
transport.keep_alive_interval(Some(Duration::from_secs(15)));
|
||||
transport.max_idle_timeout(Some(VarInt::from_u32(30_000).into()));
|
||||
config.transport_config(Arc::new(transport));
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
/// Wrapper around quinn's bidirectional stream to implement PtStream
|
||||
pub struct QuicStream {
|
||||
send: SendStream,
|
||||
recv: RecvStream,
|
||||
}
|
||||
|
||||
impl QuicStream {
|
||||
fn new(send: SendStream, recv: RecvStream) -> Self {
|
||||
Self { send, recv }
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncRead for QuicStream {
|
||||
fn poll_read(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut [u8],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
Pin::new(&mut self.recv)
|
||||
.poll_read(cx, buf)
|
||||
.map_err(|e| io::Error::other(format!("QUIC read error: {e}")))
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncWrite for QuicStream {
|
||||
fn poll_write(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
Pin::new(&mut self.send)
|
||||
.poll_write(cx, buf)
|
||||
.map_err(|e| io::Error::other(format!("QUIC write error: {e}")))
|
||||
}
|
||||
|
||||
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||
Pin::new(&mut self.send)
|
||||
.poll_flush(cx)
|
||||
.map_err(|e| io::Error::other(format!("QUIC flush error: {e}")))
|
||||
}
|
||||
|
||||
fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||
Pin::new(&mut self.send)
|
||||
.poll_close(cx)
|
||||
.map_err(|e| io::Error::other(format!("QUIC close error: {e}")))
|
||||
}
|
||||
}
|
||||
|
||||
/// QUIC Dialer implementation
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct QuicDialer {
|
||||
endpoint: Endpoint,
|
||||
}
|
||||
|
||||
impl QuicDialer {
|
||||
/// Instantiate a new [`QuicDialer`] object
|
||||
pub(crate) async fn new() -> io::Result<Self> {
|
||||
let client_config = create_client_config()?;
|
||||
|
||||
// Bind to any available port for outgoing connections
|
||||
let endpoint = Endpoint::client("0.0.0.0:0".parse().unwrap())
|
||||
.map_err(|e| io::Error::other(format!("Failed to create QUIC endpoint: {e}")))?;
|
||||
|
||||
endpoint.set_default_client_config(client_config);
|
||||
|
||||
Ok(Self { endpoint })
|
||||
}
|
||||
|
||||
/// Internal dial function
|
||||
pub(crate) async fn do_dial(
|
||||
&self,
|
||||
socket_addr: SocketAddr,
|
||||
timeout: Option<Duration>,
|
||||
) -> io::Result<QuicStream> {
|
||||
debug!(target: "net::quic::do_dial", "Dialing {socket_addr} with QUIC...");
|
||||
|
||||
let connect = async {
|
||||
// Connect to the remote endpoint
|
||||
let connection = self
|
||||
.endpoint
|
||||
.connect(socket_addr, TLS_DNS_NAME)
|
||||
.map_err(|e| io::Error::other(format!("QUIC connect error: {e}")))?
|
||||
.await
|
||||
.map_err(|e| io::Error::other(format!("QUIC connection error: {e}")))?;
|
||||
|
||||
// Open a bidirectional stream
|
||||
let (send, recv) = connection
|
||||
.open_bi()
|
||||
.await
|
||||
.map_err(|e| io::Error::other(format!("QUIC stream error: {e}")))?;
|
||||
|
||||
Ok(QuicStream::new(send, recv))
|
||||
};
|
||||
|
||||
match timeout {
|
||||
Some(t) => {
|
||||
let timer = Timer::after(t);
|
||||
pin_mut!(timer);
|
||||
pin_mut!(connect);
|
||||
|
||||
match select(connect, timer).await {
|
||||
Either::Left((Ok(stream), _)) => Ok(stream),
|
||||
Either::Left((Err(e), _)) => Err(e),
|
||||
Either::Right((_, _)) => Err(io::ErrorKind::TimedOut.into()),
|
||||
}
|
||||
}
|
||||
None => connect.await,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// QUIC Listener implementation
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct QuicListener {
|
||||
/// When the user puts a port of 0, the OS will assign a random port.
|
||||
/// We get it from the listener so we know what the true endpoint is.
|
||||
pub port: Arc<OnceCell<u16>>,
|
||||
}
|
||||
|
||||
impl QuicListener {
|
||||
/// Instantiate a new [`QuicListener`]
|
||||
pub async fn new() -> io::Result<Self> {
|
||||
Ok(Self { port: Arc::new(OnceCell::new()) })
|
||||
}
|
||||
|
||||
/// Internal listen function
|
||||
pub(crate) async fn do_listen(
|
||||
&self,
|
||||
socket_addr: SocketAddr,
|
||||
) -> io::Result<QuicListenerIntern> {
|
||||
let server_config = create_server_config()?;
|
||||
|
||||
let endpoint = Endpoint::server(server_config, socket_addr)
|
||||
.map_err(|e| io::Error::other(format!("Failed to create QUIC server endpoint: {e}")))?;
|
||||
|
||||
let local_port = endpoint.local_addr()?.port();
|
||||
|
||||
debug!(
|
||||
target: "net::quic::do_listen",
|
||||
"Listening on QUIC endpoint: {}",
|
||||
endpoint.local_addr()?,
|
||||
);
|
||||
|
||||
self.port.set(local_port).await.expect("fatal port already set for QuicListener");
|
||||
|
||||
Ok(QuicListenerIntern { endpoint })
|
||||
}
|
||||
}
|
||||
|
||||
/// Internal QUIC Listener implementation, used with `PtListener`
|
||||
pub struct QuicListenerIntern {
|
||||
endpoint: Endpoint,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl PtListener for QuicListenerIntern {
|
||||
async fn next(&self) -> io::Result<(Box<dyn PtStream>, Url)> {
|
||||
// Wait for an incoming connection
|
||||
let incoming =
|
||||
self.endpoint.accept().await.ok_or_else(|| {
|
||||
io::Error::new(io::ErrorKind::ConnectionAborted, "Endpoint closed")
|
||||
})?;
|
||||
|
||||
let peer_addr = incoming.remote_address();
|
||||
|
||||
let connection =
|
||||
incoming.await.map_err(|e| io::Error::other(format!("QUIC accept error: {e}")))?;
|
||||
|
||||
// Accept a bidirectional stream from the client
|
||||
let (send, recv) = connection
|
||||
.accept_bi()
|
||||
.await
|
||||
.map_err(|e| io::Error::other(format!("QUIC stream accept error: {e}")))?;
|
||||
|
||||
let url = Url::parse(&format!("quic://{peer_addr}")).map_err(|e| {
|
||||
io::Error::new(io::ErrorKind::InvalidData, format!("Invalid peer address: {e}"))
|
||||
})?;
|
||||
|
||||
Ok((Box::new(QuicStream::new(send, recv)), url))
|
||||
}
|
||||
}
|
||||
@@ -36,10 +36,13 @@ use x509_parser::{
|
||||
prelude::{GeneralName, ParsedExtension, X509Certificate},
|
||||
};
|
||||
|
||||
/// The DNS name used for certificate validation across all transports
|
||||
pub(crate) const TLS_DNS_NAME: &str = "dark.fi";
|
||||
|
||||
/// Validate certificate DNSName.
|
||||
fn validate_dnsname(cert: &X509Certificate) -> std::result::Result<(), rustls::Error> {
|
||||
#[rustfmt::skip]
|
||||
let oid = x509_parser::oid_registry::asn1_rs::oid!(2.5.29.17);
|
||||
let oid = x509_parser::oid_registry::asn1_rs::oid!(2.5.29.17);
|
||||
let Ok(Some(extension)) = cert.get_extension_unique(&oid) else {
|
||||
return Err(rustls::CertificateError::BadEncoding.into())
|
||||
};
|
||||
@@ -59,15 +62,52 @@ fn validate_dnsname(cert: &X509Certificate) -> std::result::Result<(), rustls::E
|
||||
_ => return Err(rustls::CertificateError::BadEncoding.into()),
|
||||
};
|
||||
|
||||
if dns_name != "dark.fi" {
|
||||
if dns_name != TLS_DNS_NAME {
|
||||
return Err(rustls::CertificateError::BadEncoding.into())
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn verify_ed25519_signature(
|
||||
message: &[u8],
|
||||
cert: &CertificateDer,
|
||||
dss: &DigitallySignedStruct,
|
||||
) -> std::result::Result<HandshakeSignatureValid, rustls::Error> {
|
||||
if dss.scheme != SignatureScheme::ED25519 {
|
||||
return Err(rustls::CertificateError::BadSignature.into())
|
||||
}
|
||||
|
||||
// Read the DER-encoded certificate into a buffer
|
||||
let buf: Vec<u8> = cert.iter().copied().collect();
|
||||
|
||||
// Parse the cert and extract the public key
|
||||
let Ok((_, cert)) = parse_x509_certificate(&buf) else {
|
||||
error!(target: "net::tls::verify_ed25519_signature", "[net::tls] Failed parsing TLS certificate");
|
||||
return Err(rustls::CertificateError::BadEncoding.into())
|
||||
};
|
||||
|
||||
let Ok(public_key) = ed25519_compact::PublicKey::from_der(cert.public_key().raw) else {
|
||||
error!(target: "net::tls::verify_ed25519_signature", "[net::tls] Failed parsing public key");
|
||||
return Err(rustls::CertificateError::BadEncoding.into())
|
||||
};
|
||||
|
||||
let Ok(signature) = ed25519_compact::Signature::from_slice(dss.signature()) else {
|
||||
error!(target: "net::tls::verify_ed25519_signature", "[net::tls] Failed verifying signature");
|
||||
return Err(rustls::CertificateError::BadSignature.into())
|
||||
};
|
||||
|
||||
if let Err(e) = public_key.verify(message, &signature) {
|
||||
error!(target: "net::tls::verify_ed25519_signature", "[net::tls] Failed verifying signature: {e}");
|
||||
return Err(rustls::CertificateError::BadSignature.into())
|
||||
}
|
||||
|
||||
Ok(HandshakeSignatureValid::assertion())
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ServerCertificateVerifier;
|
||||
pub(crate) struct ServerCertificateVerifier;
|
||||
|
||||
impl ServerCertVerifier for ServerCertificateVerifier {
|
||||
fn verify_server_cert(
|
||||
&self,
|
||||
@@ -78,10 +118,7 @@ impl ServerCertVerifier for ServerCertificateVerifier {
|
||||
_now: UnixTime,
|
||||
) -> std::result::Result<ServerCertVerified, rustls::Error> {
|
||||
// Read the DER-encoded certificate into a buffer
|
||||
let mut buf = Vec::with_capacity(end_entity.len());
|
||||
for byte in end_entity.iter() {
|
||||
buf.push(*byte);
|
||||
}
|
||||
let buf: Vec<u8> = end_entity.iter().copied().collect();
|
||||
|
||||
// Parse the certificate
|
||||
let Ok((_, cert)) = parse_x509_certificate(&buf) else {
|
||||
@@ -110,40 +147,7 @@ impl ServerCertVerifier for ServerCertificateVerifier {
|
||||
cert: &CertificateDer,
|
||||
dss: &DigitallySignedStruct,
|
||||
) -> std::result::Result<HandshakeSignatureValid, rustls::Error> {
|
||||
// Verify we're using the correct signature scheme
|
||||
if dss.scheme != SignatureScheme::ED25519 {
|
||||
return Err(rustls::CertificateError::BadSignature.into())
|
||||
}
|
||||
|
||||
// Read the DER-encoded certificate into a buffer
|
||||
let mut buf = Vec::with_capacity(cert.len());
|
||||
for byte in cert.iter() {
|
||||
buf.push(*byte);
|
||||
}
|
||||
|
||||
// Parse the certificate and extract the public key
|
||||
let Ok((_, cert)) = parse_x509_certificate(&buf) else {
|
||||
error!(target: "net::tls::verify_tls13_signature", "[net::tls] Failed parsing server TLS certificate");
|
||||
return Err(rustls::CertificateError::BadEncoding.into())
|
||||
};
|
||||
|
||||
let Ok(public_key) = ed25519_compact::PublicKey::from_der(cert.public_key().raw) else {
|
||||
error!(target: "net::tls::verify_tls13_signature", "[net::tls] Failed parsing server public key");
|
||||
return Err(rustls::CertificateError::BadEncoding.into())
|
||||
};
|
||||
|
||||
// Verify the signature
|
||||
let Ok(signature) = ed25519_compact::Signature::from_slice(dss.signature()) else {
|
||||
error!(target: "net::tls::verify_tls13_signature", "[net::tls] Failed verifying server signature");
|
||||
return Err(rustls::CertificateError::BadSignature.into())
|
||||
};
|
||||
|
||||
if let Err(e) = public_key.verify(message, &signature) {
|
||||
error!(target: "net::tls::verify_tls13_signature", "[net::tls] Failed verifying server signature: {e}");
|
||||
return Err(rustls::CertificateError::BadSignature.into())
|
||||
}
|
||||
|
||||
Ok(HandshakeSignatureValid::assertion())
|
||||
verify_ed25519_signature(message, cert, dss)
|
||||
}
|
||||
|
||||
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
|
||||
@@ -152,7 +156,8 @@ impl ServerCertVerifier for ServerCertificateVerifier {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ClientCertificateVerifier;
|
||||
pub(crate) struct ClientCertificateVerifier;
|
||||
|
||||
impl ClientCertVerifier for ClientCertificateVerifier {
|
||||
fn offer_client_auth(&self) -> bool {
|
||||
true
|
||||
@@ -173,13 +178,10 @@ impl ClientCertVerifier for ClientCertificateVerifier {
|
||||
_now: UnixTime,
|
||||
) -> std::result::Result<ClientCertVerified, rustls::Error> {
|
||||
// Read the DER-encoded certificate into a buffer
|
||||
let mut cert = Vec::with_capacity(end_entity.len());
|
||||
for byte in end_entity.iter() {
|
||||
cert.push(*byte);
|
||||
}
|
||||
let buf: Vec<u8> = end_entity.iter().copied().collect();
|
||||
|
||||
// Parse the certificate
|
||||
let Ok((_, cert)) = parse_x509_certificate(&cert) else {
|
||||
let Ok((_, cert)) = parse_x509_certificate(&buf) else {
|
||||
error!(target: "net::tls::verify_server_cert", "[net::tls] Failed parsing server TLS certificate");
|
||||
return Err(rustls::CertificateError::BadEncoding.into())
|
||||
};
|
||||
@@ -205,40 +207,7 @@ impl ClientCertVerifier for ClientCertificateVerifier {
|
||||
cert: &CertificateDer,
|
||||
dss: &DigitallySignedStruct,
|
||||
) -> std::result::Result<HandshakeSignatureValid, rustls::Error> {
|
||||
// Verify we're using the correct signature scheme
|
||||
if dss.scheme != SignatureScheme::ED25519 {
|
||||
return Err(rustls::CertificateError::BadSignature.into())
|
||||
}
|
||||
|
||||
// Read the DER-encoded certificate into a buffer
|
||||
let mut buf = Vec::with_capacity(cert.len());
|
||||
for byte in cert.iter() {
|
||||
buf.push(*byte);
|
||||
}
|
||||
|
||||
// Parse the certificate and extract the public key
|
||||
let Ok((_, cert)) = parse_x509_certificate(&buf) else {
|
||||
error!(target: "net::tls::verify_tls13_signature", "[net::tls] Failed parsing server TLS certificate");
|
||||
return Err(rustls::CertificateError::BadEncoding.into())
|
||||
};
|
||||
|
||||
let Ok(public_key) = ed25519_compact::PublicKey::from_der(cert.public_key().raw) else {
|
||||
error!(target: "net::tls::verify_tls13_signature", "[net::tls] Failed parsing server public key");
|
||||
return Err(rustls::CertificateError::BadEncoding.into())
|
||||
};
|
||||
|
||||
// Verify the signature
|
||||
let Ok(signature) = ed25519_compact::Signature::from_slice(dss.signature()) else {
|
||||
error!(target: "net::tls::verify_tls13_signature", "[net::tls] Failed verifying server signature");
|
||||
return Err(rustls::CertificateError::BadSignature.into())
|
||||
};
|
||||
|
||||
if let Err(e) = public_key.verify(message, &signature) {
|
||||
error!(target: "net::tls::verify_tls13_signature", "[net::tls] Failed verifying server signature: {e}");
|
||||
return Err(rustls::CertificateError::BadSignature.into())
|
||||
}
|
||||
|
||||
Ok(HandshakeSignatureValid::assertion())
|
||||
verify_ed25519_signature(message, cert, dss)
|
||||
}
|
||||
|
||||
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
|
||||
@@ -246,6 +215,39 @@ impl ClientCertVerifier for ClientCertificateVerifier {
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a self-signed Ed25519 certificate for TLS.
|
||||
/// Returns the certificate and private key in DER format.
|
||||
pub(crate) fn generate_certificate() -> io::Result<(CertificateDer<'static>, PrivateKeyDer<'static>)>
|
||||
{
|
||||
let Ok(keypair) = rcgen::KeyPair::generate_for(&rcgen::PKCS_ED25519) else {
|
||||
return Err(io::Error::other("Failed to generate TLS keypair"))
|
||||
};
|
||||
|
||||
let Ok(mut cert_params) = rcgen::CertificateParams::new(&[]) else {
|
||||
return Err(io::Error::other("Failed to generate TLS params"))
|
||||
};
|
||||
|
||||
cert_params.subject_alt_names =
|
||||
vec![rcgen::SanType::DnsName(Ia5String::try_from(TLS_DNS_NAME).unwrap())];
|
||||
cert_params.extended_key_usages = vec![
|
||||
rcgen::ExtendedKeyUsagePurpose::ClientAuth,
|
||||
rcgen::ExtendedKeyUsagePurpose::ServerAuth,
|
||||
];
|
||||
|
||||
let Ok(certificate) = cert_params.self_signed(&keypair) else {
|
||||
return Err(io::Error::other("Failed to sign TLS certificate"))
|
||||
};
|
||||
|
||||
let certificate = certificate.der().clone();
|
||||
let keypair_der = keypair.serialize_der();
|
||||
|
||||
let Ok(secret_key_der) = PrivateKeyDer::try_from(keypair_der) else {
|
||||
return Err(io::Error::other("Failed to deserialize DER TLS secret"))
|
||||
};
|
||||
|
||||
Ok((certificate, secret_key_der))
|
||||
}
|
||||
|
||||
pub struct TlsUpgrade {
|
||||
/// TLS server configuration
|
||||
server_config: Arc<ServerConfig>,
|
||||
@@ -256,30 +258,7 @@ pub struct TlsUpgrade {
|
||||
impl TlsUpgrade {
|
||||
pub async fn new() -> io::Result<Self> {
|
||||
// On each instantiation, generate a new keypair and certificate
|
||||
let Ok(keypair) = rcgen::KeyPair::generate_for(&rcgen::PKCS_ED25519) else {
|
||||
return Err(io::Error::other("Failed to generate TLS keypair"))
|
||||
};
|
||||
|
||||
let Ok(mut cert_params) = rcgen::CertificateParams::new(&[]) else {
|
||||
return Err(io::Error::other("Failed to generate TLS params"))
|
||||
};
|
||||
|
||||
cert_params.subject_alt_names =
|
||||
vec![rcgen::SanType::DnsName(Ia5String::try_from("dark.fi").unwrap())];
|
||||
cert_params.extended_key_usages = vec![
|
||||
rcgen::ExtendedKeyUsagePurpose::ClientAuth,
|
||||
rcgen::ExtendedKeyUsagePurpose::ServerAuth,
|
||||
];
|
||||
|
||||
let Ok(certificate) = cert_params.self_signed(&keypair) else {
|
||||
return Err(io::Error::other("Failed to sign TLS certificate"))
|
||||
};
|
||||
let certificate = certificate.der();
|
||||
|
||||
let keypair_der = keypair.serialize_der();
|
||||
let Ok(secret_key_der) = PrivateKeyDer::try_from(keypair_der) else {
|
||||
return Err(io::Error::other("Failed to deserialize DER TLS secret"))
|
||||
};
|
||||
let (certificate, secret_key_der) = generate_certificate()?;
|
||||
|
||||
// Server-side config
|
||||
let client_cert_verifier = Arc::new(ClientCertificateVerifier {});
|
||||
@@ -307,7 +286,7 @@ impl TlsUpgrade {
|
||||
where
|
||||
IO: super::PtStream,
|
||||
{
|
||||
let server_name = ServerName::try_from("dark.fi").unwrap();
|
||||
let server_name = ServerName::try_from(TLS_DNS_NAME).unwrap();
|
||||
let connector = TlsConnector::from(self.client_config);
|
||||
let stream = connector.connect(server_name, stream).await?;
|
||||
Ok(TlsStream::Client(stream))
|
||||
|
||||
@@ -88,6 +88,38 @@ fn tcp_tls_transport() {
|
||||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn quic_transport() {
|
||||
let executor = LocalExecutor::new();
|
||||
|
||||
smol::block_on(executor.run(async {
|
||||
let listener = std::net::UdpSocket::bind("127.0.0.1:0").unwrap();
|
||||
let port = listener.local_addr().unwrap().port();
|
||||
drop(listener);
|
||||
let url = Url::parse(&format!("quic://127.0.0.1:{port}")).unwrap();
|
||||
|
||||
let listener = Listener::new(url.clone(), None).await.unwrap().listen().await.unwrap();
|
||||
|
||||
executor
|
||||
.spawn(async move {
|
||||
let (stream, _) = listener.next().await.unwrap();
|
||||
let (mut reader, mut writer) = smol::io::split(stream);
|
||||
io::copy(&mut reader, &mut writer).await.unwrap();
|
||||
})
|
||||
.detach();
|
||||
|
||||
let payload = "ohai quic";
|
||||
|
||||
let dialer = Dialer::new(url, None, None).await.unwrap();
|
||||
let mut client = dialer.dial(None).await.unwrap();
|
||||
payload.encode_async(&mut client).await.unwrap();
|
||||
|
||||
let buf: String = AsyncDecodable::decode_async(&mut client).await.unwrap();
|
||||
|
||||
assert_eq!(buf, payload);
|
||||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unix_transport() {
|
||||
let executor = LocalExecutor::new();
|
||||
|
||||
Reference in New Issue
Block a user