Compare commits

...

5 commits

7 changed files with 53 additions and 31 deletions

View file

@ -1,3 +1,4 @@
use async_trait::async_trait;
use tokio_gemini::{ use tokio_gemini::{
certs::{ certs::{
dane, file_sscv::KnownHostsFile, fingerprint::CertFingerprint, SelfsignedCertVerifier, dane, file_sscv::KnownHostsFile, fingerprint::CertFingerprint, SelfsignedCertVerifier,
@ -12,7 +13,7 @@ use tokio_gemini::{
// //
const USAGE: &str = "-k\t\tinsecure mode (trust all certs) const USAGE: &str = "-k\t\tinsecure mode (trust all certs)
-d <DNS server addr>\tuse custom DNS for resolving & DANE -d <DNS server>\tuse custom DNS for resolving & DANE
-h\t\tshow help"; -h\t\tshow help";
#[tokio::main] #[tokio::main]
@ -120,10 +121,11 @@ struct CertVerifier {
dns: Option<DnsClient>, dns: Option<DnsClient>,
} }
#[async_trait]
impl SelfsignedCertVerifier for CertVerifier { impl SelfsignedCertVerifier for CertVerifier {
async fn verify<'c>( async fn verify(
&self, &self,
cert: &'c tokio_gemini::certs::CertificateDer<'c>, cert: &tokio_gemini::certs::CertificateDer<'_>,
host: &str, host: &str,
port: u16, port: u16,
) -> Result<bool, tokio_gemini::LibError> { ) -> Result<bool, tokio_gemini::LibError> {

View file

@ -1,3 +1,4 @@
use async_trait::async_trait;
use tokio_gemini::{ use tokio_gemini::{
certs::{fingerprint::CertFingerprint, SelfsignedCertVerifier}, certs::{fingerprint::CertFingerprint, SelfsignedCertVerifier},
Client, LibError, Client, LibError,
@ -32,10 +33,11 @@ async fn main() -> Result<(), LibError> {
struct CertVerifier; struct CertVerifier;
#[async_trait]
impl SelfsignedCertVerifier for CertVerifier { impl SelfsignedCertVerifier for CertVerifier {
async fn verify<'c>( async fn verify(
&self, &self,
cert: &'c tokio_gemini::certs::CertificateDer<'c>, cert: &tokio_gemini::certs::CertificateDer<'_>,
host: &str, host: &str,
port: u16, port: u16,
) -> Result<bool, tokio_gemini::LibError> { ) -> Result<bool, tokio_gemini::LibError> {

View file

@ -4,9 +4,9 @@ use crate::{
LibError, LibError,
}; };
pub async fn dane<'d>( pub async fn dane(
dns: &DnsClient, dns: &DnsClient,
cert: &CertificateDer<'d>, cert: &CertificateDer<'_>,
host: &str, host: &str,
port: u16, port: u16,
) -> Result<CertFingerprint, LibError> { ) -> Result<CertFingerprint, LibError> {

View file

@ -17,9 +17,9 @@ pub use tokio_rustls::rustls::pki_types::{CertificateDer, ServerName, UnixTime};
/// It is recommended to use helpers from file_sscv. /// It is recommended to use helpers from file_sscv.
#[async_trait] #[async_trait]
pub trait SelfsignedCertVerifier: Send + Sync { pub trait SelfsignedCertVerifier: Send + Sync {
async fn verify<'c>( async fn verify(
&self, &self,
cert: &'c CertificateDer<'c>, cert: &CertificateDer<'_>,
host: &str, host: &str,
port: u16, port: u16,
// now: UnixTime, // now: UnixTime,

View file

@ -25,7 +25,7 @@ use tokio::{
io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt}, io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt},
net::TcpStream, net::TcpStream,
}; };
use tokio_rustls::TlsConnector; use tokio_rustls::{rustls, TlsConnector};
use url::Url; use url::Url;
pub struct Client { pub struct Client {
@ -101,6 +101,20 @@ impl Client {
let stream = self.try_connect(host, port).await?; let stream = self.try_connect(host, port).await?;
let mut stream = self.connector.connect(domain, stream).await?; let mut stream = self.connector.connect(domain, stream).await?;
if let Some(ssv) = &self.ss_verifier {
let cert = stream
.get_ref()
.1 // rustls::ClientConnection
.peer_certificates()
.unwrap() // i think handshake already completed if we awaited on connector.connect?
.first()
.ok_or(rustls::Error::NoCertificatesPresented)?;
if !ssv.verify(cert, host, port).await? {
return Err(rustls::CertificateError::ApplicationVerificationFailure.into());
}
}
// Write URL, then CRLF // Write URL, then CRLF
stream.write_all(url_str.as_bytes()).await?; stream.write_all(url_str.as_bytes()).await?;
stream.write_all(b"\r\n").await?; stream.write_all(b"\r\n").await?;

View file

@ -89,7 +89,9 @@ impl DnsClient {
Ok(answers.into_iter().filter_map(|rec| { Ok(answers.into_iter().filter_map(|rec| {
if let Some(RData::TLSA(tlsa)) = rec.data() { if let Some(RData::TLSA(tlsa)) = rec.data() {
if tlsa.cert_usage() == CertUsage::DomainIssued if tlsa.cert_usage() == CertUsage::DomainIssued
&& tlsa.selector() == Selector::Spki // maybe implement extracting public key later,
// but for now only accept TLSA records with full certs hashed
&& tlsa.selector() == Selector::Full
{ {
match tlsa.matching() { match tlsa.matching() {
Matching::Sha256 => CertFingerprint::try_from_sha256(tlsa.cert_data()) Matching::Sha256 => CertFingerprint::try_from_sha256(tlsa.cert_data())

View file

@ -1,11 +1,11 @@
//! Library error structures and enums //! Library error structures and enums
use tokio_rustls::rustls;
#[cfg(feature = "hickory")] #[cfg(feature = "hickory")]
use hickory_client::{ use hickory_client::{
error::ClientError as HickoryClientError, proto::error::ProtoError as HickoryProtoError, error::ClientError as HickoryClientError, proto::error::ProtoError as HickoryProtoError,
}; };
#[cfg(feature = "hickory")]
use tokio::runtime::TryCurrentError;
/// Main error structure, also a wrapper for everything else /// Main error structure, also a wrapper for everything else
#[derive(Debug)] #[derive(Debug)]
@ -15,9 +15,12 @@ pub enum LibError {
IoError(std::io::Error), IoError(std::io::Error),
/// URL parse or check error /// URL parse or check error
InvalidUrlError(InvalidUrl), InvalidUrlError(InvalidUrl),
/// DNS provided no suitable records /// DNS server has provided no suitable records
/// (e.&nbsp;g. domain does not exist) /// (e.&nbsp;g. domain does not exist)
HostLookupError, HostLookupError,
/// TLS library error related to certificate/signature
/// verification failure or connection failure
RustlsError(rustls::Error),
/// Response status code is out of [10; 69] range /// Response status code is out of [10; 69] range
StatusOutOfRange(u8), StatusOutOfRange(u8),
/// Response metadata or content cannot be parsed /// Response metadata or content cannot be parsed
@ -25,16 +28,9 @@ pub enum LibError {
DataNotUtf8(std::string::FromUtf8Error), DataNotUtf8(std::string::FromUtf8Error),
/// Provided string is not a valid MIME type /// Provided string is not a valid MIME type
InvalidMime(mime::FromStrError), InvalidMime(mime::FromStrError),
/// Hickory Client error /// Hickory DNS client error
#[cfg(feature = "hickory")] #[cfg(feature = "hickory")]
DnsClientError(HickoryClientError), DnsClientError(HickoryClientError),
/// Hickory Proto error
#[cfg(feature = "hickory")]
DnsProtoError(HickoryProtoError),
/// Could not get Tokio runtime handle
/// inside Rustls cert verifier
#[cfg(feature = "hickory")]
NoTokioRuntime(TryCurrentError),
} }
impl From<std::io::Error> for LibError { impl From<std::io::Error> for LibError {
@ -58,6 +54,20 @@ impl From<InvalidUrl> for LibError {
} }
} }
impl From<rustls::Error> for LibError {
#[inline]
fn from(err: rustls::Error) -> Self {
Self::RustlsError(err)
}
}
impl From<rustls::CertificateError> for LibError {
#[inline]
fn from(err: rustls::CertificateError) -> Self {
Self::RustlsError(err.into())
}
}
impl LibError { impl LibError {
#[inline] #[inline]
pub fn status_out_of_range(num: u8) -> Self { pub fn status_out_of_range(num: u8) -> Self {
@ -91,15 +101,7 @@ impl From<HickoryClientError> for LibError {
impl From<HickoryProtoError> for LibError { impl From<HickoryProtoError> for LibError {
#[inline] #[inline]
fn from(err: HickoryProtoError) -> Self { fn from(err: HickoryProtoError) -> Self {
Self::DnsProtoError(err) Self::DnsClientError(err.into())
}
}
#[cfg(feature = "hickory")]
impl From<TryCurrentError> for LibError {
#[inline]
fn from(err: TryCurrentError) -> Self {
Self::NoTokioRuntime(err)
} }
} }