use std::fs;
use std::io;
use std::net::TcpStream;
use std::path::Path;
use std::sync::Arc;
use rustls::pki_types::pem::PemObject;
use rustls::pki_types::{CertificateDer, PrivateKeyDer};
use rustls::{ServerConfig, ServerConnection, StreamOwned};
use super::error::NetbatError;
pub(crate) type TlsStream = StreamOwned<ServerConnection, TcpStream>;
#[derive(Clone)]
pub struct TlsServerConfig {
server_config: Arc<ServerConfig>,
}
impl std::fmt::Debug for TlsServerConfig {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TlsServerConfig").finish_non_exhaustive()
}
}
impl TlsServerConfig {
pub fn from_pem(cert_chain_pem: &[u8], private_key_pem: &[u8]) -> Result<Self, NetbatError> {
let certs = parse_cert_chain(cert_chain_pem)?;
let key = parse_private_key(private_key_pem)?;
let server_config =
ServerConfig::builder_with_provider(Arc::new(rustls::crypto::ring::default_provider()))
.with_safe_default_protocol_versions()
.map_err(|_| invalid_tls_data())?
.with_no_client_auth()
.with_single_cert(certs, key)
.map_err(|_| invalid_tls_data())?;
Ok(Self {
server_config: Arc::new(server_config),
})
}
pub fn from_pem_files(
cert_chain_path: impl AsRef<Path>,
private_key_path: impl AsRef<Path>,
) -> Result<Self, NetbatError> {
let cert_chain_pem = fs::read(cert_chain_path).map_err(NetbatError::from)?;
let private_key_pem = fs::read(private_key_path).map_err(NetbatError::from)?;
Self::from_pem(&cert_chain_pem, &private_key_pem)
}
pub(crate) fn handshake(&self, mut stream: TcpStream) -> Result<TlsStream, NetbatError> {
let mut conn = ServerConnection::new(Arc::clone(&self.server_config))
.map_err(|_| invalid_tls_data())?;
conn.complete_io(&mut stream).map_err(NetbatError::from)?;
Ok(StreamOwned::new(conn, stream))
}
}
fn parse_cert_chain(pem: &[u8]) -> Result<Vec<CertificateDer<'static>>, NetbatError> {
let certs = CertificateDer::pem_slice_iter(pem)
.collect::<Result<Vec<_>, _>>()
.map_err(|_| invalid_tls_data())?;
if certs.is_empty() {
return Err(invalid_tls_data());
}
Ok(certs)
}
fn parse_private_key(pem: &[u8]) -> Result<PrivateKeyDer<'static>, NetbatError> {
PrivateKeyDer::from_pem_slice(pem).map_err(|_| invalid_tls_data())
}
fn invalid_tls_data() -> NetbatError {
NetbatError::Io {
kind: io::ErrorKind::InvalidData,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn from_pem_rejects_empty_cert_chain() {
let err = TlsServerConfig::from_pem(b"not a pem", b"not a key")
.expect_err("empty cert chain is rejected");
assert_eq!(
err,
NetbatError::Io {
kind: io::ErrorKind::InvalidData
}
);
}
}