Compare commits
5 commits
e042a139bf
...
ef307f4983
Author | SHA1 | Date | |
---|---|---|---|
ef307f4983 | |||
04ebac7d6c | |||
220a4a3316 | |||
060aa4a1f7 | |||
49c46aeaf5 |
7 changed files with 53 additions and 31 deletions
|
@ -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> {
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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?;
|
||||||
|
|
|
@ -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())
|
||||||
|
|
42
src/error.rs
42
src/error.rs
|
@ -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. g. domain does not exist)
|
/// (e. 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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue