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 tokio_gemini::{
|
||||
certs::{
|
||||
dane, file_sscv::KnownHostsFile, fingerprint::CertFingerprint, SelfsignedCertVerifier,
|
||||
dane, file::sscv::KnownHostsFile, fingerprint::CertFingerprint, SelfsignedCertVerifier,
|
||||
},
|
||||
dns::DnsClient,
|
||||
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 dashmap::DashMap;
|
||||
|
@ -5,6 +8,10 @@ use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufWriter};
|
|||
|
||||
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 {
|
||||
fd: Mutex<std::os::fd::OwnedFd>,
|
||||
map: DashMap<String, SelfsignedCert>,
|
||||
|
@ -89,6 +96,7 @@ impl KnownHostsFile {
|
|||
Ok(KnownHostsFile { fd, map })
|
||||
}
|
||||
|
||||
/// Get a known certificate fingerprint from the in-memory hashmap
|
||||
pub fn get_known_cert(
|
||||
&self,
|
||||
host: &str,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
//! Everything related to TLS certs verification
|
||||
|
||||
pub mod fingerprint;
|
||||
pub mod resolver;
|
||||
pub mod verifier;
|
||||
|
||||
#[cfg(feature = "file")]
|
||||
|
@ -9,12 +10,18 @@ pub mod file;
|
|||
#[cfg(feature = "hickory")]
|
||||
pub mod dane;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use tokio_rustls::rustls::{self, crypto::CryptoProvider};
|
||||
|
||||
pub use tokio_rustls::rustls::pki_types::{CertificateDer, ServerName, UnixTime};
|
||||
|
||||
use resolver::InternalCertResolver;
|
||||
|
||||
/// Trait for implementing self-signed cert verifiers,
|
||||
/// 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]
|
||||
pub trait SelfsignedCertVerifier: Send + Sync {
|
||||
async fn verify(
|
||||
|
@ -33,3 +40,23 @@ pub struct SelfsignedCert {
|
|||
pub fingerprint: fingerprint::CertFingerprint,
|
||||
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")]
|
||||
use crate::dns::DnsClient;
|
||||
|
||||
use tokio_rustls::{
|
||||
rustls::{self, SupportedProtocolVersion},
|
||||
TlsConnector,
|
||||
};
|
||||
use tokio_rustls::rustls::{self, SupportedProtocolVersion};
|
||||
|
||||
/// Builder for creating configured [`Client`] instance
|
||||
pub struct ClientBuilder {
|
||||
|
@ -58,7 +55,8 @@ impl ClientBuilder {
|
|||
let tls_config = tls_config.with_no_client_auth();
|
||||
|
||||
Client {
|
||||
connector: TlsConnector::from(Arc::new(tls_config)),
|
||||
tls_config: Arc::new(tls_config),
|
||||
cs_resolver: None,
|
||||
ss_verifier: self.ss_verifier,
|
||||
#[cfg(feature = "hickory")]
|
||||
dns: self.dns,
|
||||
|
|
|
@ -16,7 +16,7 @@ use hickory_client::rr::IntoName;
|
|||
use std::net::SocketAddr;
|
||||
|
||||
use crate::{
|
||||
certs::{SelfsignedCertVerifier, ServerName},
|
||||
certs::{AuthCertResolver, SelfsignedCertVerifier, ServerName},
|
||||
error::*,
|
||||
into_url::IntoUrl,
|
||||
status::*,
|
||||
|
@ -34,7 +34,8 @@ use tokio_rustls::{client::TlsStream, rustls, TlsConnector};
|
|||
pub type ThisResponse = Response<BufReader<TlsStream<TcpStream>>>;
|
||||
|
||||
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>>,
|
||||
#[cfg(feature = "hickory")]
|
||||
pub(crate) dns: Option<DnsClient>,
|
||||
|
@ -139,10 +140,34 @@ impl Client {
|
|||
.map_err(|_| InvalidUrl::ConvertError)?
|
||||
.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
|
||||
let stream = self.try_connect(host, port).await?;
|
||||
// TLS connection via tokio-rustls
|
||||
let stream = self.connector.connect(domain, stream).await?;
|
||||
let stream = connector.connect(domain, stream).await?;
|
||||
|
||||
// certificate verification
|
||||
if let Some(ssv) = &self.ss_verifier {
|
||||
|
|
|
@ -27,7 +27,7 @@ fn check_parser() {
|
|||
assert_eq!(status.second_digit(), 0u8);
|
||||
}
|
||||
|
||||
assert_eq!(resp.is_ok(), true);
|
||||
assert!(resp.is_ok());
|
||||
|
||||
assert_eq!(resp.message(), "text/gemini");
|
||||
|
||||
|
@ -54,7 +54,7 @@ fn check_real_site() {
|
|||
.block_on(client.request("gemini://geminiprotocol.net/docs"))
|
||||
.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();
|
||||
|
|
Loading…
Add table
Reference in a new issue