Compare commits
4 commits
dc126d2e0f
...
4314df372b
Author | SHA1 | Date | |
---|---|---|---|
4314df372b | |||
9806eb6a02 | |||
2feb039c11 | |||
e77b785fa0 |
8 changed files with 110 additions and 166 deletions
10
Cargo.lock
generated
10
Cargo.lock
generated
|
@ -890,7 +890,6 @@ dependencies = [
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-rustls 0.26.0",
|
"tokio-rustls 0.26.0",
|
||||||
"url",
|
"url",
|
||||||
"webpki-roots",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1112,15 +1111,6 @@ dependencies = [
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "webpki-roots"
|
|
||||||
version = "0.26.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd"
|
|
||||||
dependencies = [
|
|
||||||
"rustls-pki-types",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
|
|
10
Cargo.toml
10
Cargo.toml
|
@ -21,7 +21,6 @@ url = "2.5.2"
|
||||||
|
|
||||||
tokio = { version = "1.39.2", features = ["io-util", "net"] }
|
tokio = { version = "1.39.2", features = ["io-util", "net"] }
|
||||||
tokio-rustls = { version = "0.26.0", default-features = false, features = ["ring"] }
|
tokio-rustls = { version = "0.26.0", default-features = false, features = ["ring"] }
|
||||||
webpki-roots = { version = "0.26.3", optional = true }
|
|
||||||
|
|
||||||
dashmap = { version = "6.0.1", optional = true }
|
dashmap = { version = "6.0.1", optional = true }
|
||||||
hickory-client = { version = "0.24.1", optional = true }
|
hickory-client = { version = "0.24.1", optional = true }
|
||||||
|
@ -30,9 +29,9 @@ hickory-client = { version = "0.24.1", optional = true }
|
||||||
tokio = { version = "1.39.2", features = ["macros", "rt-multi-thread"] }
|
tokio = { version = "1.39.2", features = ["macros", "rt-multi-thread"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
webpki = ["dep:webpki-roots"]
|
|
||||||
file-sscv = ["dep:dashmap", "tokio/fs"]
|
file-sscv = ["dep:dashmap", "tokio/fs"]
|
||||||
|
|
||||||
|
dane = ["hickory"]
|
||||||
hickory = ["dep:hickory-client"]
|
hickory = ["dep:hickory-client"]
|
||||||
hickory-dot = ["hickory", "hickory-client/dns-over-rustls"]
|
hickory-dot = ["hickory", "hickory-client/dns-over-rustls"]
|
||||||
hickory-doh = ["hickory", "hickory-client/dns-over-https-rustls"]
|
hickory-doh = ["hickory", "hickory-client/dns-over-https-rustls"]
|
||||||
|
@ -46,9 +45,4 @@ path = "examples/simple.rs"
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "main"
|
name = "main"
|
||||||
path = "examples/main.rs"
|
path = "examples/main.rs"
|
||||||
required-features = ["file-sscv"]
|
required-features = ["file-sscv", "hickory"]
|
||||||
|
|
||||||
[[example]]
|
|
||||||
name = "dane"
|
|
||||||
path = "examples/dane.rs"
|
|
||||||
required-features = ["hickory"]
|
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
use tokio_gemini::dns::DnsClient;
|
|
||||||
|
|
||||||
#[tokio::main]
|
|
||||||
async fn main() {
|
|
||||||
let mut client = DnsClient::init(("1.1.1.1", 53)).await.unwrap();
|
|
||||||
println!(
|
|
||||||
"{}",
|
|
||||||
client
|
|
||||||
.query_tlsa("torproject.org", 443)
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.next()
|
|
||||||
.unwrap()
|
|
||||||
.hex()
|
|
||||||
);
|
|
||||||
}
|
|
111
examples/main.rs
111
examples/main.rs
|
@ -1,38 +1,30 @@
|
||||||
use tokio_gemini::certs::{file_sscv::FileBasedCertVerifier, insecure::AllowAllCertVerifier};
|
use tokio_gemini::{
|
||||||
|
certs::{file_sscv::FileBasedCertVerifier, insecure::AllowAllCertVerifier},
|
||||||
|
dns::DnsClient,
|
||||||
|
Client, LibError,
|
||||||
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// cargo add tokio_gemini -F file-sscv
|
// cargo add tokio_gemini -F file-sscv,hickory
|
||||||
// cargo add tokio -F macros,rt-multi-thread,io-util,fs
|
// cargo add tokio -F macros,rt-multi-thread,io-util,fs
|
||||||
//
|
//
|
||||||
|
|
||||||
|
const USAGE: &str = "-k\t\tinsecure mode (trust all certs)
|
||||||
|
-d <DNS server addr>\tuse custom DNS for resolving & DANE
|
||||||
|
-h\t\tshow help";
|
||||||
|
|
||||||
|
struct Config {
|
||||||
|
insecure: bool,
|
||||||
|
dns: Option<String>,
|
||||||
|
url: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<(), tokio_gemini::LibError> {
|
async fn main() -> Result<(), LibError> {
|
||||||
let mut args = std::env::args();
|
let config = parse_args();
|
||||||
let mut insecure = false;
|
let client = build_client(&config).await?;
|
||||||
let mut url = "gemini://geminiprotocol.net/".to_owned();
|
|
||||||
_ = args.next(); // skip exe path
|
|
||||||
if let Some(arg) = args.next() {
|
|
||||||
if arg == "-k" {
|
|
||||||
insecure = true;
|
|
||||||
if let Some(arg) = args.next() {
|
|
||||||
url = arg;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
url = arg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let client = if insecure {
|
let mut resp = client.request(&config.url).await?;
|
||||||
tokio_gemini::Client::builder()
|
|
||||||
.with_custom_verifier(AllowAllCertVerifier::yes_i_know_what_i_am_doing())
|
|
||||||
.build()
|
|
||||||
} else {
|
|
||||||
tokio_gemini::Client::builder()
|
|
||||||
.with_selfsigned_cert_verifier(FileBasedCertVerifier::init("known_hosts").await?)
|
|
||||||
.build()
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut resp = client.request(&url).await?;
|
|
||||||
|
|
||||||
{
|
{
|
||||||
let status_code = resp.status().status_code();
|
let status_code = resp.status().status_code();
|
||||||
|
@ -57,3 +49,66 @@ async fn main() -> Result<(), tokio_gemini::LibError> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_args() -> Config {
|
||||||
|
let mut config = Config {
|
||||||
|
insecure: false,
|
||||||
|
dns: None,
|
||||||
|
url: "gemini://geminiprotocol.net/".to_owned(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut expected_dns = false;
|
||||||
|
|
||||||
|
for arg in std::env::args().skip(1) {
|
||||||
|
match arg.as_str() {
|
||||||
|
dns if expected_dns => {
|
||||||
|
config.dns = Some(dns.to_owned());
|
||||||
|
expected_dns = false;
|
||||||
|
}
|
||||||
|
"-k" => config.insecure = true,
|
||||||
|
"-d" => expected_dns = true,
|
||||||
|
"-h" => {
|
||||||
|
println!("{}", USAGE);
|
||||||
|
std::process::exit(0);
|
||||||
|
}
|
||||||
|
url => {
|
||||||
|
println!("URL: {}", url);
|
||||||
|
config.url = url.to_owned();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if expected_dns {
|
||||||
|
println!("{}", USAGE);
|
||||||
|
std::process::exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
config
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn build_client(config: &Config) -> Result<Client, LibError> {
|
||||||
|
let dns = if let Some(addr) = &config.dns {
|
||||||
|
Some(DnsClient::init(addr).await?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let client = tokio_gemini::Client::builder();
|
||||||
|
|
||||||
|
let client = if config.insecure {
|
||||||
|
client.with_custom_verifier(AllowAllCertVerifier::yes_i_know_what_i_am_doing())
|
||||||
|
} else {
|
||||||
|
client.with_selfsigned_cert_verifier(
|
||||||
|
FileBasedCertVerifier::init("known_hosts", dns.clone()).await?,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let client = if let Some(dns) = dns {
|
||||||
|
client.with_dns_client(dns)
|
||||||
|
} else {
|
||||||
|
client
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(client.build())
|
||||||
|
}
|
||||||
|
|
|
@ -15,15 +15,21 @@ use crate::{
|
||||||
LibError,
|
LibError,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "hickory")]
|
||||||
|
use crate::dns::DnsClient;
|
||||||
|
|
||||||
pub struct FileBasedCertVerifier {
|
pub struct FileBasedCertVerifier {
|
||||||
fd: Mutex<std::os::fd::OwnedFd>,
|
fd: Mutex<std::os::fd::OwnedFd>,
|
||||||
map: DashMap<String, SelfsignedCert>,
|
map: DashMap<String, SelfsignedCert>,
|
||||||
#[cfg(feature = "hickory")]
|
#[cfg(feature = "hickory")]
|
||||||
dns: Option<crate::dns::DnsClient>,
|
dns: Option<DnsClient>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileBasedCertVerifier {
|
impl FileBasedCertVerifier {
|
||||||
pub async fn init(path: impl AsRef<Path>) -> Result<Self, LibError> {
|
pub async fn init(
|
||||||
|
path: impl AsRef<Path>,
|
||||||
|
#[cfg(feature = "hickory")] dns: Option<DnsClient>,
|
||||||
|
) -> Result<Self, LibError> {
|
||||||
let map = DashMap::new();
|
let map = DashMap::new();
|
||||||
|
|
||||||
if tokio::fs::try_exists(&path).await? {
|
if tokio::fs::try_exists(&path).await? {
|
||||||
|
@ -97,7 +103,7 @@ impl FileBasedCertVerifier {
|
||||||
fd,
|
fd,
|
||||||
map,
|
map,
|
||||||
#[cfg(feature = "hickory")]
|
#[cfg(feature = "hickory")]
|
||||||
dns: None,
|
dns,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,8 +15,6 @@ use tokio_rustls::rustls::{
|
||||||
|
|
||||||
pub struct CustomCertVerifier {
|
pub struct CustomCertVerifier {
|
||||||
pub(crate) provider: Arc<rustls::crypto::CryptoProvider>,
|
pub(crate) provider: Arc<rustls::crypto::CryptoProvider>,
|
||||||
pub(crate) webpki_verifier: Option<Arc<rustls::client::WebPkiServerVerifier>>,
|
|
||||||
pub(crate) ss_allowed: bool,
|
|
||||||
pub(crate) ss_verifier: Box<dyn SelfsignedCertVerifier>,
|
pub(crate) ss_verifier: Box<dyn SelfsignedCertVerifier>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,52 +27,20 @@ impl ServerCertVerifier for CustomCertVerifier {
|
||||||
_ocsp_response: &[u8],
|
_ocsp_response: &[u8],
|
||||||
now: UnixTime,
|
now: UnixTime,
|
||||||
) -> Result<ServerCertVerified, rustls::Error> {
|
) -> Result<ServerCertVerified, rustls::Error> {
|
||||||
// if webpki CA certs enabled
|
// TODO: certificate validation (domain, expiry, etc.)
|
||||||
#[cfg(feature = "webpki")]
|
|
||||||
if let Some(wv) = &self.webpki_verifier {
|
|
||||||
match wv.verify_server_cert(
|
|
||||||
end_entity,
|
|
||||||
_intermediates,
|
|
||||||
server_name,
|
|
||||||
_ocsp_response,
|
|
||||||
now,
|
|
||||||
) {
|
|
||||||
Ok(verified) => {
|
|
||||||
return Ok(verified);
|
|
||||||
}
|
|
||||||
Err(
|
|
||||||
e @ rustls::Error::InvalidCertificate(rustls::CertificateError::UnknownIssuer),
|
|
||||||
) => {
|
|
||||||
if !self.ss_allowed {
|
|
||||||
return Err(e);
|
|
||||||
}
|
|
||||||
// go ahead, verify as self-signed
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
// any other error, probably related to invalid cert
|
|
||||||
return Err(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: certificate validation when webpki_verifier is not used
|
|
||||||
|
|
||||||
// if self-signed certs enabled
|
|
||||||
if self.ss_allowed {
|
|
||||||
// TODO: check if expired or provide handy API to check it
|
|
||||||
// (probably with rustls-webpki's webpki::Cert)
|
|
||||||
if self
|
if self
|
||||||
.ss_verifier
|
.ss_verifier
|
||||||
.verify(end_entity, server_name.to_str().as_ref(), now)?
|
.verify(end_entity, server_name.to_str().as_ref(), now)?
|
||||||
{
|
{
|
||||||
return Ok(ServerCertVerified::assertion());
|
Ok(ServerCertVerified::assertion())
|
||||||
|
} else {
|
||||||
|
Err(rustls::Error::InvalidCertificate(
|
||||||
|
rustls::CertificateError::ApplicationVerificationFailure,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// both disabled (shouldn't happen)
|
|
||||||
Err(rustls::Error::UnsupportedNameType) // not sure if chosen correct enum item
|
|
||||||
}
|
|
||||||
|
|
||||||
fn verify_tls12_signature(
|
fn verify_tls12_signature(
|
||||||
&self,
|
&self,
|
||||||
message: &[u8],
|
message: &[u8],
|
||||||
|
@ -112,10 +78,6 @@ impl ServerCertVerifier for CustomCertVerifier {
|
||||||
|
|
||||||
impl std::fmt::Debug for CustomCertVerifier {
|
impl std::fmt::Debug for CustomCertVerifier {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(
|
write!(f, "CustomCertVerifier {{ provider: {:?} }}", self.provider)
|
||||||
f,
|
|
||||||
"CustomCertVerifier {{ provider: {:?}, webpki_verifier: {:?} }}",
|
|
||||||
self.provider, self.webpki_verifier
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,9 +12,6 @@ use crate::dns::DnsClient;
|
||||||
|
|
||||||
use tokio_rustls::rustls::{self, client::danger::ServerCertVerifier, SupportedProtocolVersion};
|
use tokio_rustls::rustls::{self, client::danger::ServerCertVerifier, SupportedProtocolVersion};
|
||||||
|
|
||||||
#[cfg(feature = "webpki")]
|
|
||||||
use tokio_rustls::rustls::{client::WebPkiServerVerifier, pki_types::TrustAnchor};
|
|
||||||
|
|
||||||
/// Builder for creating configured [`Client`] instance
|
/// Builder for creating configured [`Client`] instance
|
||||||
pub struct ClientBuilder {
|
pub struct ClientBuilder {
|
||||||
root_certs: rustls::RootCertStore,
|
root_certs: rustls::RootCertStore,
|
||||||
|
@ -63,33 +60,10 @@ impl ClientBuilder {
|
||||||
let tls_config = if let Some(cv) = self.custom_verifier {
|
let tls_config = if let Some(cv) = self.custom_verifier {
|
||||||
tls_config.dangerous().with_custom_certificate_verifier(cv)
|
tls_config.dangerous().with_custom_certificate_verifier(cv)
|
||||||
} else if let Some(ssv) = self.ss_verifier {
|
} else if let Some(ssv) = self.ss_verifier {
|
||||||
let webpki_verifier = {
|
|
||||||
#[cfg(feature = "webpki")]
|
|
||||||
if !self.root_certs.is_empty() {
|
|
||||||
Some(
|
|
||||||
WebPkiServerVerifier::builder_with_provider(
|
|
||||||
Arc::new(self.root_certs),
|
|
||||||
provider.clone(),
|
|
||||||
)
|
|
||||||
.build()
|
|
||||||
// panics only if roots are empty (that is checked above)
|
|
||||||
// or CRLs couldn't be parsed (we didn't provide any)
|
|
||||||
.unwrap(),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "webpki"))]
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
tls_config
|
tls_config
|
||||||
.dangerous()
|
.dangerous()
|
||||||
.with_custom_certificate_verifier(Arc::new(CustomCertVerifier {
|
.with_custom_certificate_verifier(Arc::new(CustomCertVerifier {
|
||||||
provider: provider.clone(),
|
provider: provider.clone(),
|
||||||
webpki_verifier,
|
|
||||||
ss_allowed: true,
|
|
||||||
ss_verifier: ssv,
|
ss_verifier: ssv,
|
||||||
}))
|
}))
|
||||||
} else {
|
} else {
|
||||||
|
@ -117,27 +91,6 @@ impl ClientBuilder {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Include webpki trust anchors.
|
|
||||||
/// Not recommended (useless) as most Gemini capsules use self-signed
|
|
||||||
/// TLS certs and properly configured TOFU policy is enough.
|
|
||||||
#[cfg(feature = "webpki")]
|
|
||||||
pub fn with_webpki_roots(mut self) -> Self {
|
|
||||||
self.root_certs
|
|
||||||
.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Include custom trust anchors.
|
|
||||||
/// Not recommended (useless), see note for [`ClientBuilder::with_webpki_roots`].
|
|
||||||
#[cfg(feature = "webpki")]
|
|
||||||
pub fn with_custom_roots(
|
|
||||||
mut self,
|
|
||||||
iter: impl IntoIterator<Item = TrustAnchor<'static>>,
|
|
||||||
) -> Self {
|
|
||||||
self.root_certs.extend(iter);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Include a self-signed cert verifier.
|
/// Include a self-signed cert verifier.
|
||||||
/// If you only need a known_hosts file, consider using
|
/// If you only need a known_hosts file, consider using
|
||||||
/// [`crate::certs::file_sscv::FileBasedCertVerifier`],
|
/// [`crate::certs::file_sscv::FileBasedCertVerifier`],
|
||||||
|
|
|
@ -7,16 +7,14 @@ pub use response::Response;
|
||||||
|
|
||||||
#[cfg(feature = "hickory")]
|
#[cfg(feature = "hickory")]
|
||||||
use crate::dns::DnsClient;
|
use crate::dns::DnsClient;
|
||||||
|
|
||||||
#[cfg(feature = "hickory")]
|
#[cfg(feature = "hickory")]
|
||||||
use hickory_client::rr::IntoName;
|
use hickory_client::rr::IntoName;
|
||||||
|
#[cfg(feature = "hickory")]
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
use crate::{error::*, status::*};
|
use crate::{error::*, status::*};
|
||||||
use builder::ClientBuilder;
|
use builder::ClientBuilder;
|
||||||
|
|
||||||
#[cfg(feature = "hickory")]
|
|
||||||
use std::net::SocketAddr;
|
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use tokio::{
|
use tokio::{
|
||||||
|
@ -49,6 +47,8 @@ impl From<rustls::ClientConfig> for Client {
|
||||||
|
|
||||||
#[cfg(feature = "hickory")]
|
#[cfg(feature = "hickory")]
|
||||||
impl From<(rustls::ClientConfig, DnsClient)> for Client {
|
impl From<(rustls::ClientConfig, DnsClient)> for Client {
|
||||||
|
/// Create a Client from a Rustls config and
|
||||||
|
/// a DnsClient instance as a custom resolver.
|
||||||
#[inline]
|
#[inline]
|
||||||
fn from(value: (rustls::ClientConfig, DnsClient)) -> Self {
|
fn from(value: (rustls::ClientConfig, DnsClient)) -> Self {
|
||||||
Client {
|
Client {
|
||||||
|
|
Loading…
Add table
Reference in a new issue