Compare commits
No commits in common. "8dfc811bf75890b313d9fda3499a76a19644333b" and "666196c103403289a6f2af9abd5f1d4d49c93887" have entirely different histories.
8dfc811bf7
...
666196c103
7 changed files with 12 additions and 110 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,6 +1,3 @@
|
||||||
//! 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;
|
||||||
|
@ -8,10 +5,6 @@ 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>,
|
||||||
|
@ -96,7 +89,6 @@ 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,7 +1,6 @@
|
||||||
//! 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")]
|
||||||
|
@ -10,18 +9,12 @@ 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(
|
||||||
|
@ -40,23 +33,3 @@ 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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,40 +0,0 @@
|
||||||
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,7 +10,10 @@ use crate::{
|
||||||
#[cfg(feature = "hickory")]
|
#[cfg(feature = "hickory")]
|
||||||
use crate::dns::DnsClient;
|
use crate::dns::DnsClient;
|
||||||
|
|
||||||
use tokio_rustls::rustls::{self, SupportedProtocolVersion};
|
use tokio_rustls::{
|
||||||
|
rustls::{self, SupportedProtocolVersion},
|
||||||
|
TlsConnector,
|
||||||
|
};
|
||||||
|
|
||||||
/// Builder for creating configured [`Client`] instance
|
/// Builder for creating configured [`Client`] instance
|
||||||
pub struct ClientBuilder {
|
pub struct ClientBuilder {
|
||||||
|
@ -55,8 +58,7 @@ impl ClientBuilder {
|
||||||
let tls_config = tls_config.with_no_client_auth();
|
let tls_config = tls_config.with_no_client_auth();
|
||||||
|
|
||||||
Client {
|
Client {
|
||||||
tls_config: Arc::new(tls_config),
|
connector: TlsConnector::from(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::{AuthCertResolver, SelfsignedCertVerifier, ServerName},
|
certs::{SelfsignedCertVerifier, ServerName},
|
||||||
error::*,
|
error::*,
|
||||||
into_url::IntoUrl,
|
into_url::IntoUrl,
|
||||||
status::*,
|
status::*,
|
||||||
|
@ -34,8 +34,7 @@ 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) tls_config: Arc<rustls::ClientConfig>,
|
pub(crate) connector: TlsConnector,
|
||||||
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>,
|
||||||
|
@ -140,34 +139,10 @@ 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 = connector.connect(domain, stream).await?;
|
let stream = self.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!(resp.is_ok());
|
assert_eq!(resp.is_ok(), true);
|
||||||
|
|
||||||
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!(resp.is_ok()); // check if redirection is processed correctly
|
assert_eq!(resp.is_ok(), true); // check if redirection is processed correctly
|
||||||
|
|
||||||
{
|
{
|
||||||
let mime = resp.mime().unwrap();
|
let mime = resp.mime().unwrap();
|
||||||
|
|
Loading…
Add table
Reference in a new issue