use anyhow::{Result, anyhow};
use quinn::{ClientConfig, Connection, Endpoint, ServerConfig};
use rustls::pki_types::{CertificateDer, PrivateKeyDer};
use std::net::SocketAddr;
use std::sync::Arc;
use tracing::{debug, error, info};
#[derive(Debug, Clone)]
pub struct QuicConfig {
pub cert_der: Vec<u8>,
pub key_der: Vec<u8>,
pub skip_verify: bool,
}
pub struct QuicClient {
endpoint: Endpoint,
}
impl QuicClient {
pub fn new(bind: SocketAddr, config: &QuicConfig) -> Result<Self> {
let client_crypto = if config.skip_verify {
rustls::ClientConfig::builder()
.dangerous()
.with_custom_certificate_verifier(Arc::new(SkipServerVerification))
.with_no_client_auth()
} else {
let mut root_store = rustls::RootCertStore::empty();
tracing::debug!("QUIC: Using empty root store - add webpki-roots for production");
rustls::ClientConfig::builder()
.with_root_certificates(root_store)
.with_no_client_auth()
};
let client_config = ClientConfig::new(Arc::new(
quinn::crypto::rustls::QuicClientConfig::try_from(client_crypto)?,
));
let mut endpoint = Endpoint::client(bind)?;
endpoint.set_default_client_config(client_config);
Ok(Self { endpoint })
}
pub async fn connect(
&self,
server_addr: SocketAddr,
server_name: &str,
) -> Result<QuicConnection> {
let connection = self.endpoint.connect(server_addr, server_name)?.await?;
info!("QUIC connected to {}", server_addr);
Ok(QuicConnection { connection })
}
}
pub struct QuicServer {
endpoint: Endpoint,
}
impl QuicServer {
pub fn new(bind: SocketAddr, config: &QuicConfig) -> Result<Self> {
let cert = CertificateDer::from(config.cert_der.clone());
let key = PrivateKeyDer::Pkcs8(config.key_der.clone().into());
let server_crypto = rustls::ServerConfig::builder()
.with_no_client_auth()
.with_single_cert(vec![cert], key)?;
let server_config = ServerConfig::with_crypto(Arc::new(
quinn::crypto::rustls::QuicServerConfig::try_from(server_crypto)?,
));
let endpoint = Endpoint::server(server_config, bind)?;
info!("QUIC server listening on {}", bind);
Ok(Self { endpoint })
}
pub async fn accept(&self) -> Option<QuicConnection> {
match self.endpoint.accept().await {
Some(incoming) => match incoming.await {
Ok(connection) => Some(QuicConnection { connection }),
Err(e) => {
error!("QUIC accept error: {}", e);
None
}
},
None => None,
}
}
}
pub struct QuicConnection {
connection: Connection,
}
impl QuicConnection {
pub async fn send(&self, data: &[u8]) -> Result<()> {
let mut stream = self.connection.open_uni().await?;
stream.write_all(data).await?;
stream.finish()?;
Ok(())
}
pub async fn recv(&self) -> Result<Vec<u8>> {
let mut recv = self.connection.accept_uni().await?;
let data = recv.read_to_end(1024 * 1024).await?; Ok(data)
}
pub async fn open_bi(&self) -> Result<(quinn::SendStream, quinn::RecvStream)> {
Ok(self.connection.open_bi().await?)
}
pub fn close(&self) {
self.connection.close(0u32.into(), b"done");
}
}
#[derive(Debug)]
struct SkipServerVerification;
impl rustls::client::danger::ServerCertVerifier for SkipServerVerification {
fn verify_server_cert(
&self,
_end_entity: &CertificateDer<'_>,
_intermediates: &[CertificateDer<'_>],
_server_name: &rustls::pki_types::ServerName<'_>,
_ocsp_response: &[u8],
_now: rustls::pki_types::UnixTime,
) -> Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
Ok(rustls::client::danger::ServerCertVerified::assertion())
}
fn verify_tls12_signature(
&self,
_message: &[u8],
_cert: &CertificateDer<'_>,
_dss: &rustls::DigitallySignedStruct,
) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
}
fn verify_tls13_signature(
&self,
_message: &[u8],
_cert: &CertificateDer<'_>,
_dss: &rustls::DigitallySignedStruct,
) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
}
fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
vec![
rustls::SignatureScheme::ECDSA_NISTP256_SHA256,
rustls::SignatureScheme::ECDSA_NISTP384_SHA384,
rustls::SignatureScheme::RSA_PSS_SHA256,
rustls::SignatureScheme::RSA_PSS_SHA384,
rustls::SignatureScheme::RSA_PSS_SHA512,
]
}
}