use std::pin::Pin;
use openssl::{nid::Nid, ssl::Ssl};
use tokio::{
io::{AsyncRead, AsyncWrite, AsyncWriteExt},
net::{TcpStream, ToSocketAddrs},
};
use tokio_openssl::SslStream;
use tracing::instrument;
use uuid::Uuid;
use zerocopy::IntoBytes;
use crate::{
error::ConnectionError as Error,
protocol::{ProtocolAck, ProtocolHeader, Role},
};
pub struct NestlsBuilder {
bridge_ssl: Ssl,
role: Role,
}
pub struct Nestls {
inner: SslStream<SslStream<TcpStream>>,
session_id: Uuid,
}
impl Nestls {
pub fn builder(bridge_ssl: Ssl, role: Role) -> NestlsBuilder {
NestlsBuilder::new(bridge_ssl, role)
}
pub fn session_id(&self) -> Uuid {
self.session_id
}
pub fn peer_common_name(&self) -> Option<String> {
self.inner
.ssl()
.peer_certificate()
.and_then(|cert| {
cert.subject_name()
.entries_by_nid(Nid::COMMONNAME)
.next()
.and_then(|entry| entry.data().as_utf8().ok())
})
.map(|common_name| common_name.to_string())
}
}
impl AsyncRead for Nestls {
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
buf: &mut tokio::io::ReadBuf<'_>,
) -> std::task::Poll<std::io::Result<()>> {
Pin::new(&mut self.as_mut().inner).poll_read(cx, buf)
}
}
impl AsyncWrite for Nestls {
fn poll_write(
mut self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
buf: &[u8],
) -> std::task::Poll<Result<usize, std::io::Error>> {
Pin::new(&mut self.as_mut().inner).poll_write(cx, buf)
}
fn poll_flush(
mut self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), std::io::Error>> {
Pin::new(&mut self.as_mut().inner).poll_flush(cx)
}
fn poll_shutdown(
mut self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), std::io::Error>> {
Pin::new(&mut self.as_mut().inner).poll_shutdown(cx)
}
}
impl NestlsBuilder {
fn new(bridge_ssl: Ssl, role: Role) -> Self {
Self { bridge_ssl, role }
}
#[instrument(err, skip(bridge_addr, bridge_ssl), level = "debug")]
async fn connect_to_bridge<A: ToSocketAddrs + std::fmt::Debug>(
bridge_addr: A,
bridge_ssl: Ssl,
role: Role,
) -> Result<(SslStream<TcpStream>, Uuid), Error> {
let outer_stream = TcpStream::connect(&bridge_addr).await?;
tracing::debug!(?bridge_addr, "TCP connection to the bridge established");
let mut outer_stream = tokio_openssl::SslStream::new(bridge_ssl, outer_stream)?;
Pin::new(&mut outer_stream).connect().await?;
let username = outer_stream
.ssl()
.certificate()
.and_then(|cert| {
cert.subject_name()
.entries_by_nid(Nid::COMMONNAME)
.next()
.and_then(|entry| entry.data().as_utf8().ok())
})
.map(|common_name| common_name.to_string());
tracing::debug!(?username, "TLS session with the bridge established");
let protocol_header: ProtocolHeader = role.into();
let protocol_header = protocol_header.as_bytes();
outer_stream.write_all(protocol_header).await?;
tracing::debug!(
bytes_sent = protocol_header.len(),
"Protocol header sent to the bridge"
);
let session_id = ProtocolAck::check(&mut outer_stream).await?;
tracing::info!(
?bridge_addr,
?session_id,
"Connection to the bridge established."
);
Ok((outer_stream, session_id))
}
#[instrument(err, skip(self, server_ssl))]
pub async fn connect<S: ToSocketAddrs + std::fmt::Debug>(
self,
bridge_addr: S,
server_ssl: Ssl,
) -> Result<Nestls, Error> {
let (outer_stream, session_id) =
Self::connect_to_bridge(bridge_addr, self.bridge_ssl, self.role).await?;
let mut inner = tokio_openssl::SslStream::new(server_ssl, outer_stream)?;
Pin::new(&mut inner).connect().await?;
tracing::debug!(?session_id, "Inner TLS session with the server established");
Ok(Nestls { inner, session_id })
}
#[instrument(err, skip(self, ssl), level = tracing::Level::DEBUG)]
pub async fn accept<S: ToSocketAddrs + std::fmt::Debug>(
self,
bridge_addr: S,
ssl: Ssl,
) -> Result<Nestls, Error> {
let (outer_stream, session_id) =
Self::connect_to_bridge(bridge_addr, self.bridge_ssl, self.role).await?;
let mut inner = tokio_openssl::SslStream::new(ssl, outer_stream)?;
Pin::new(&mut inner).accept().await?;
tracing::debug!("Accepted new inner TLS connection from a client");
Ok(Nestls { inner, session_id })
}
}