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::{
certs::{
dane, file_sscv::KnownHostsFile, fingerprint::CertFingerprint, SelfsignedCertVerifier,
@ -12,7 +13,7 @@ use tokio_gemini::{
//
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";
#[tokio::main]
@ -120,10 +121,11 @@ struct CertVerifier {
dns: Option<DnsClient>,
}
#[async_trait]
impl SelfsignedCertVerifier for CertVerifier {
async fn verify<'c>(
async fn verify(
&self,
cert: &'c tokio_gemini::certs::CertificateDer<'c>,
cert: &tokio_gemini::certs::CertificateDer<'_>,
host: &str,
port: u16,
) -> Result<bool, tokio_gemini::LibError> {

View file

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

View file

@ -4,9 +4,9 @@ use crate::{
LibError,
};
pub async fn dane<'d>(
pub async fn dane(
dns: &DnsClient,
cert: &CertificateDer<'d>,
cert: &CertificateDer<'_>,
host: &str,
port: u16,
) -> 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.
#[async_trait]
pub trait SelfsignedCertVerifier: Send + Sync {
async fn verify<'c>(
async fn verify(
&self,
cert: &'c CertificateDer<'c>,
cert: &CertificateDer<'_>,
host: &str,
port: u16,
// now: UnixTime,

View file

@ -25,7 +25,7 @@ use tokio::{
io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt},
net::TcpStream,
};
use tokio_rustls::TlsConnector;
use tokio_rustls::{rustls, TlsConnector};
use url::Url;
pub struct Client {
@ -101,6 +101,20 @@ impl Client {
let stream = self.try_connect(host, port).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
stream.write_all(url_str.as_bytes()).await?;
stream.write_all(b"\r\n").await?;

View file

@ -89,7 +89,9 @@ impl DnsClient {
Ok(answers.into_iter().filter_map(|rec| {
if let Some(RData::TLSA(tlsa)) = rec.data() {
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() {
Matching::Sha256 => CertFingerprint::try_from_sha256(tlsa.cert_data())

View file

@ -1,11 +1,11 @@
//! Library error structures and enums
use tokio_rustls::rustls;
#[cfg(feature = "hickory")]
use hickory_client::{
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
#[derive(Debug)]
@ -15,9 +15,12 @@ pub enum LibError {
IoError(std::io::Error),
/// URL parse or check error
InvalidUrlError(InvalidUrl),
/// DNS provided no suitable records
/// DNS server has provided no suitable records
/// (e.&nbsp;g. domain does not exist)
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
StatusOutOfRange(u8),
/// Response metadata or content cannot be parsed
@ -25,16 +28,9 @@ pub enum LibError {
DataNotUtf8(std::string::FromUtf8Error),
/// Provided string is not a valid MIME type
InvalidMime(mime::FromStrError),
/// Hickory Client error
/// Hickory DNS client error
#[cfg(feature = "hickory")]
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 {
@ -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 {
#[inline]
pub fn status_out_of_range(num: u8) -> Self {
@ -91,15 +101,7 @@ impl From<HickoryClientError> for LibError {
impl From<HickoryProtoError> for LibError {
#[inline]
fn from(err: HickoryProtoError) -> Self {
Self::DnsProtoError(err)
}
}
#[cfg(feature = "hickory")]
impl From<TryCurrentError> for LibError {
#[inline]
fn from(err: TryCurrentError) -> Self {
Self::NoTokioRuntime(err)
Self::DnsClientError(err.into())
}
}