Compare commits
4 commits
666196c103
...
8dfc811bf7
Author | SHA1 | Date | |
---|---|---|---|
8dfc811bf7 | |||
132463a433 | |||
e1dceb4386 | |||
7b9d2a976e |
7 changed files with 110 additions and 12 deletions
|
@ -1,7 +1,7 @@
|
||||||
use async_trait::async_trait;
|
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,
|
||||||
},
|
},
|
||||||
dns::DnsClient,
|
dns::DnsClient,
|
||||||
Client, LibError,
|
Client, LibError,
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
//! Utils for self-signed server certificate verifying
|
||||||
|
//! using a file with known hosts
|
||||||
|
|
||||||
use std::{borrow::Cow, os::fd::AsFd, path::Path, sync::Mutex};
|
use std::{borrow::Cow, os::fd::AsFd, path::Path, sync::Mutex};
|
||||||
|
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
|
@ -5,6 +8,10 @@ use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufWriter};
|
||||||
|
|
||||||
use crate::certs::{fingerprint::CertFingerprint, SelfsignedCert};
|
use crate::certs::{fingerprint::CertFingerprint, SelfsignedCert};
|
||||||
|
|
||||||
|
/// Structure holding a known_hosts file descriptor
|
||||||
|
/// and an in-memory host-to-fingerprint hashmap,
|
||||||
|
/// providing a handy API to parse such files,
|
||||||
|
/// to get or store a cert fingerprint
|
||||||
pub struct KnownHostsFile {
|
pub struct KnownHostsFile {
|
||||||
fd: Mutex<std::os::fd::OwnedFd>,
|
fd: Mutex<std::os::fd::OwnedFd>,
|
||||||
map: DashMap<String, SelfsignedCert>,
|
map: DashMap<String, SelfsignedCert>,
|
||||||
|
@ -89,6 +96,7 @@ impl KnownHostsFile {
|
||||||
Ok(KnownHostsFile { fd, map })
|
Ok(KnownHostsFile { fd, map })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a known certificate fingerprint from the in-memory hashmap
|
||||||
pub fn get_known_cert(
|
pub fn get_known_cert(
|
||||||
&self,
|
&self,
|
||||||
host: &str,
|
host: &str,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
//! Everything related to TLS certs verification
|
//! Everything related to TLS certs verification
|
||||||
|
|
||||||
pub mod fingerprint;
|
pub mod fingerprint;
|
||||||
|
pub mod resolver;
|
||||||
pub mod verifier;
|
pub mod verifier;
|
||||||
|
|
||||||
#[cfg(feature = "file")]
|
#[cfg(feature = "file")]
|
||||||
|
@ -9,12 +10,18 @@ pub mod file;
|
||||||
#[cfg(feature = "hickory")]
|
#[cfg(feature = "hickory")]
|
||||||
pub mod dane;
|
pub mod dane;
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
use tokio_rustls::rustls::{self, crypto::CryptoProvider};
|
||||||
|
|
||||||
pub use tokio_rustls::rustls::pki_types::{CertificateDer, ServerName, UnixTime};
|
pub use tokio_rustls::rustls::pki_types::{CertificateDer, ServerName, UnixTime};
|
||||||
|
|
||||||
|
use resolver::InternalCertResolver;
|
||||||
|
|
||||||
/// Trait for implementing self-signed cert verifiers,
|
/// Trait for implementing self-signed cert verifiers,
|
||||||
/// probably via known_hosts with TOFU policy or DANE verification.
|
/// probably via known_hosts with TOFU policy or DANE verification.
|
||||||
/// 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(
|
async fn verify(
|
||||||
|
@ -33,3 +40,23 @@ pub struct SelfsignedCert {
|
||||||
pub fingerprint: fingerprint::CertFingerprint,
|
pub fingerprint: fingerprint::CertFingerprint,
|
||||||
pub expires: u64,
|
pub expires: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Trait for implementing authentication cert resolvers,
|
||||||
|
/// choosing the right TLS client cert for the given host and path.
|
||||||
|
/// It is recommended to use helpers from [`file::accr`]
|
||||||
|
#[async_trait]
|
||||||
|
pub trait AuthCertResolver: Send + Sync {
|
||||||
|
async fn load(&self, host: &str, port: u16, path: &str) -> Option<Arc<InternalCertResolver>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_crypto_provider() -> Arc<CryptoProvider> {
|
||||||
|
if let Some(provider) = CryptoProvider::get_default() {
|
||||||
|
provider.clone()
|
||||||
|
} else {
|
||||||
|
let provider = rustls::crypto::ring::default_provider();
|
||||||
|
// unwrap: checked above that default not set
|
||||||
|
provider.install_default().unwrap();
|
||||||
|
// unwrap: default has been just installed
|
||||||
|
CryptoProvider::get_default().unwrap().clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
40
src/certs/resolver.rs
Normal file
40
src/certs/resolver.rs
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use tokio_rustls::rustls::{client::ResolvesClientCert, sign};
|
||||||
|
use webpki::types::{CertificateDer, PrivateKeyDer};
|
||||||
|
|
||||||
|
use crate::LibError;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct InternalCertResolver(Arc<sign::CertifiedKey>);
|
||||||
|
|
||||||
|
impl InternalCertResolver {
|
||||||
|
pub fn new(
|
||||||
|
chain: Vec<CertificateDer<'static>>,
|
||||||
|
key: PrivateKeyDer<'static>,
|
||||||
|
) -> Result<Self, LibError> {
|
||||||
|
let provider = crate::certs::get_crypto_provider();
|
||||||
|
|
||||||
|
let private_key = provider.key_provider.load_private_key(key)?;
|
||||||
|
|
||||||
|
Ok(InternalCertResolver(Arc::new(sign::CertifiedKey::new(
|
||||||
|
chain,
|
||||||
|
private_key,
|
||||||
|
))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResolvesClientCert for InternalCertResolver {
|
||||||
|
fn resolve(
|
||||||
|
&self,
|
||||||
|
_root_hint_subjects: &[&[u8]],
|
||||||
|
_sigschemes: &[tokio_rustls::rustls::SignatureScheme],
|
||||||
|
) -> Option<Arc<sign::CertifiedKey>> {
|
||||||
|
Some(self.0.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn has_certs(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,10 +10,7 @@ use crate::{
|
||||||
#[cfg(feature = "hickory")]
|
#[cfg(feature = "hickory")]
|
||||||
use crate::dns::DnsClient;
|
use crate::dns::DnsClient;
|
||||||
|
|
||||||
use tokio_rustls::{
|
use tokio_rustls::rustls::{self, SupportedProtocolVersion};
|
||||||
rustls::{self, SupportedProtocolVersion},
|
|
||||||
TlsConnector,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Builder for creating configured [`Client`] instance
|
/// Builder for creating configured [`Client`] instance
|
||||||
pub struct ClientBuilder {
|
pub struct ClientBuilder {
|
||||||
|
@ -58,7 +55,8 @@ impl ClientBuilder {
|
||||||
let tls_config = tls_config.with_no_client_auth();
|
let tls_config = tls_config.with_no_client_auth();
|
||||||
|
|
||||||
Client {
|
Client {
|
||||||
connector: TlsConnector::from(Arc::new(tls_config)),
|
tls_config: Arc::new(tls_config),
|
||||||
|
cs_resolver: None,
|
||||||
ss_verifier: self.ss_verifier,
|
ss_verifier: self.ss_verifier,
|
||||||
#[cfg(feature = "hickory")]
|
#[cfg(feature = "hickory")]
|
||||||
dns: self.dns,
|
dns: self.dns,
|
||||||
|
|
|
@ -16,7 +16,7 @@ use hickory_client::rr::IntoName;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
certs::{SelfsignedCertVerifier, ServerName},
|
certs::{AuthCertResolver, SelfsignedCertVerifier, ServerName},
|
||||||
error::*,
|
error::*,
|
||||||
into_url::IntoUrl,
|
into_url::IntoUrl,
|
||||||
status::*,
|
status::*,
|
||||||
|
@ -34,7 +34,8 @@ use tokio_rustls::{client::TlsStream, rustls, TlsConnector};
|
||||||
pub type ThisResponse = Response<BufReader<TlsStream<TcpStream>>>;
|
pub type ThisResponse = Response<BufReader<TlsStream<TcpStream>>>;
|
||||||
|
|
||||||
pub struct Client {
|
pub struct Client {
|
||||||
pub(crate) connector: TlsConnector,
|
pub(crate) tls_config: Arc<rustls::ClientConfig>,
|
||||||
|
pub(crate) cs_resolver: Option<Arc<dyn AuthCertResolver>>,
|
||||||
pub(crate) ss_verifier: Option<Arc<dyn SelfsignedCertVerifier>>,
|
pub(crate) ss_verifier: Option<Arc<dyn SelfsignedCertVerifier>>,
|
||||||
#[cfg(feature = "hickory")]
|
#[cfg(feature = "hickory")]
|
||||||
pub(crate) dns: Option<DnsClient>,
|
pub(crate) dns: Option<DnsClient>,
|
||||||
|
@ -139,10 +140,34 @@ impl Client {
|
||||||
.map_err(|_| InvalidUrl::ConvertError)?
|
.map_err(|_| InvalidUrl::ConvertError)?
|
||||||
.to_owned();
|
.to_owned();
|
||||||
|
|
||||||
|
let connector = {
|
||||||
|
let cert = if let Some(cs_chooser) = &self.cs_resolver {
|
||||||
|
cs_chooser.load(host, port, "/").await // TODO path
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let config = if let Some(cert) = cert {
|
||||||
|
// clone the underlying rustls::ClientConfig (not just copy Arc pointer)
|
||||||
|
let mut new_config = (*self.tls_config).clone();
|
||||||
|
// `cert` is actually an instance of `certs::resolver::InternalCertResolver`
|
||||||
|
new_config.client_auth_cert_resolver = cert;
|
||||||
|
|
||||||
|
Arc::new(new_config)
|
||||||
|
} else {
|
||||||
|
// Arc of config without client cert is stored in Client
|
||||||
|
// to avoid additional heap allocations on each request
|
||||||
|
// (new Arc-alloc only if auth cert is present, see the line above)
|
||||||
|
self.tls_config.clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
TlsConnector::from(config)
|
||||||
|
};
|
||||||
|
|
||||||
// TCP connection
|
// TCP connection
|
||||||
let stream = self.try_connect(host, port).await?;
|
let stream = self.try_connect(host, port).await?;
|
||||||
// TLS connection via tokio-rustls
|
// TLS connection via tokio-rustls
|
||||||
let stream = self.connector.connect(domain, stream).await?;
|
let stream = connector.connect(domain, stream).await?;
|
||||||
|
|
||||||
// certificate verification
|
// certificate verification
|
||||||
if let Some(ssv) = &self.ss_verifier {
|
if let Some(ssv) = &self.ss_verifier {
|
||||||
|
|
|
@ -27,7 +27,7 @@ fn check_parser() {
|
||||||
assert_eq!(status.second_digit(), 0u8);
|
assert_eq!(status.second_digit(), 0u8);
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_eq!(resp.is_ok(), true);
|
assert!(resp.is_ok());
|
||||||
|
|
||||||
assert_eq!(resp.message(), "text/gemini");
|
assert_eq!(resp.message(), "text/gemini");
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ fn check_real_site() {
|
||||||
.block_on(client.request("gemini://geminiprotocol.net/docs"))
|
.block_on(client.request("gemini://geminiprotocol.net/docs"))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(resp.is_ok(), true); // check if redirection is processed correctly
|
assert!(resp.is_ok()); // check if redirection is processed correctly
|
||||||
|
|
||||||
{
|
{
|
||||||
let mime = resp.mime().unwrap();
|
let mime = resp.mime().unwrap();
|
||||||
|
|
Loading…
Add table
Reference in a new issue