update main example: proper ss cert check, changes in lib API
This commit is contained in:
parent
49a50aecd9
commit
5533ec0d2b
4 changed files with 229 additions and 62 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1 +1,2 @@
|
||||||
/target
|
/target
|
||||||
|
known_hosts
|
||||||
|
|
77
Cargo.lock
generated
77
Cargo.lock
generated
|
@ -17,6 +17,12 @@ version = "1.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "backtrace"
|
name = "backtrace"
|
||||||
version = "0.3.73"
|
version = "0.3.73"
|
||||||
|
@ -38,6 +44,12 @@ version = "1.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
|
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "2.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "block-buffer"
|
name = "block-buffer"
|
||||||
version = "0.10.4"
|
version = "0.10.4"
|
||||||
|
@ -74,6 +86,12 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-utils"
|
||||||
|
version = "0.8.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crypto-common"
|
name = "crypto-common"
|
||||||
version = "0.1.6"
|
version = "0.1.6"
|
||||||
|
@ -84,6 +102,20 @@ dependencies = [
|
||||||
"typenum",
|
"typenum",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dashmap"
|
||||||
|
version = "6.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "804c8821570c3f8b70230c2ba75ffa5c0f9a4189b9a432b6656c536712acae28"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"crossbeam-utils",
|
||||||
|
"hashbrown",
|
||||||
|
"lock_api",
|
||||||
|
"once_cell",
|
||||||
|
"parking_lot_core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "digest"
|
name = "digest"
|
||||||
version = "0.10.7"
|
version = "0.10.7"
|
||||||
|
@ -174,6 +206,16 @@ version = "0.2.155"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
|
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lock_api"
|
||||||
|
version = "0.4.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"scopeguard",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.7.4"
|
version = "2.7.4"
|
||||||
|
@ -243,6 +285,19 @@ version = "1.19.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parking_lot_core"
|
||||||
|
version = "0.9.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"redox_syscall",
|
||||||
|
"smallvec",
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
version = "2.3.1"
|
version = "2.3.1"
|
||||||
|
@ -282,6 +337,15 @@ dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_syscall"
|
||||||
|
version = "0.5.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ring"
|
name = "ring"
|
||||||
version = "0.17.8"
|
version = "0.17.8"
|
||||||
|
@ -334,6 +398,12 @@ dependencies = [
|
||||||
"untrusted",
|
"untrusted",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scopeguard"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sha2"
|
name = "sha2"
|
||||||
version = "0.10.8"
|
version = "0.10.8"
|
||||||
|
@ -345,6 +415,12 @@ dependencies = [
|
||||||
"digest",
|
"digest",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smallvec"
|
||||||
|
version = "1.13.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "socket2"
|
name = "socket2"
|
||||||
version = "0.5.7"
|
version = "0.5.7"
|
||||||
|
@ -415,6 +491,7 @@ version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64ct",
|
"base64ct",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
"dashmap",
|
||||||
"mime",
|
"mime",
|
||||||
"num_enum",
|
"num_enum",
|
||||||
"sha2",
|
"sha2",
|
||||||
|
|
|
@ -25,4 +25,5 @@ name = "main"
|
||||||
path = "examples/main.rs"
|
path = "examples/main.rs"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
dashmap = "6.0.1"
|
||||||
tokio = { version = "1.39.2", features = ["macros", "rt-multi-thread", "io-util", "fs"] }
|
tokio = { version = "1.39.2", features = ["macros", "rt-multi-thread", "io-util", "fs"] }
|
||||||
|
|
212
examples/main.rs
212
examples/main.rs
|
@ -1,7 +1,11 @@
|
||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
use std::{io::Write, os::fd::AsFd, sync::Mutex};
|
||||||
use tokio_rustls::rustls::{
|
|
||||||
client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
|
use dashmap::DashMap;
|
||||||
ClientConfig, SignatureScheme,
|
use tokio::io::AsyncBufReadExt;
|
||||||
|
use tokio_gemini::certs::{
|
||||||
|
fingerprint::{self, generate_fingerprint},
|
||||||
|
insecure::AllowAllCertVerifier,
|
||||||
|
verifier::{SelfsignedCert, SelfsignedCertVerifier},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
|
@ -22,91 +26,175 @@ async fn main() -> Result<(), tokio_gemini::LibError> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let client = if insecure {
|
let client = if insecure {
|
||||||
tokio_gemini::Client::from(get_insecure_config())
|
tokio_gemini::Client::builder()
|
||||||
|
.with_custom_verifier(AllowAllCertVerifier::yes_i_know_what_i_am_doing())
|
||||||
|
.build()
|
||||||
} else {
|
} else {
|
||||||
tokio_gemini::Client::default()
|
tokio_gemini::Client::builder()
|
||||||
|
.with_webpki_roots() // TODO: do we really need them?
|
||||||
|
.with_selfsigned_cert_verifier(CertVerifier::init().await?)
|
||||||
|
.build()
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut resp = client.request(&url).await?;
|
let mut resp = client.request(&url).await?;
|
||||||
|
|
||||||
{
|
{
|
||||||
let status_code = resp.status().status_code();
|
let status_code = resp.status().status_code();
|
||||||
let status_num: u8 = status_code.into();
|
let status_num: u8 = status_code.into();
|
||||||
eprintln!("{} {:?}", status_num, status_code);
|
eprintln!("{} {:?}", status_num, status_code);
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.status().reply_type() == tokio_gemini::ReplyType::Success {
|
if resp.status().reply_type() == tokio_gemini::ReplyType::Success {
|
||||||
let mime = resp.mime()?;
|
let mime = resp.mime()?;
|
||||||
eprintln!("Mime: {}", mime);
|
eprintln!("Mime: {}", mime);
|
||||||
let mut buf = [0u8, 128];
|
|
||||||
let body = resp.body();
|
|
||||||
if mime.type_() == mime::TEXT {
|
if mime.type_() == mime::TEXT {
|
||||||
loop {
|
println!("{}", resp.text().await?);
|
||||||
let n = body.read(&mut buf).await?;
|
|
||||||
if n == 0 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
print!("{}", std::str::from_utf8(&buf[..n])?);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
eprintln!("Downloading into content.bin");
|
eprintln!("Downloading into content.bin");
|
||||||
let mut f = tokio::fs::File::create("content.bin").await?;
|
let mut f = tokio::fs::File::create("content.bin").await?;
|
||||||
loop {
|
tokio::io::copy(&mut resp.stream(), &mut f).await?;
|
||||||
let n = body.read(&mut buf).await?;
|
|
||||||
if n == 0 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
f.write_all(&buf[..n]).await?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
eprintln!("Message: {}", resp.message());
|
eprintln!("Message: {}", resp.message());
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_insecure_config() -> ClientConfig {
|
struct CertVerifier {
|
||||||
ClientConfig::builder()
|
f: Mutex<std::fs::File>,
|
||||||
.dangerous()
|
map: DashMap<String, SelfsignedCert>,
|
||||||
.with_custom_certificate_verifier(std::sync::Arc::new(NoCertVerification {}))
|
|
||||||
.with_no_client_auth()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
impl CertVerifier {
|
||||||
struct NoCertVerification;
|
async fn init() -> Result<Self, tokio_gemini::LibError> {
|
||||||
|
let map = DashMap::new();
|
||||||
|
|
||||||
impl ServerCertVerifier for NoCertVerification {
|
if tokio::fs::try_exists("known_hosts").await? {
|
||||||
fn verify_server_cert(
|
let mut f = tokio::fs::OpenOptions::new()
|
||||||
&self,
|
.read(true)
|
||||||
end_entity: &tokio_rustls::rustls::pki_types::CertificateDer<'_>,
|
.open("known_hosts")
|
||||||
intermediates: &[tokio_rustls::rustls::pki_types::CertificateDer<'_>],
|
.await?;
|
||||||
server_name: &tokio_rustls::rustls::pki_types::ServerName<'_>,
|
|
||||||
ocsp_response: &[u8],
|
|
||||||
now: tokio_rustls::rustls::pki_types::UnixTime,
|
|
||||||
) -> Result<ServerCertVerified, tokio_rustls::rustls::Error> {
|
|
||||||
Ok(ServerCertVerified::assertion())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn verify_tls12_signature(
|
let mut reader = tokio::io::BufReader::new(&mut f);
|
||||||
&self,
|
|
||||||
message: &[u8],
|
|
||||||
cert: &tokio_rustls::rustls::pki_types::CertificateDer<'_>,
|
|
||||||
dss: &tokio_rustls::rustls::DigitallySignedStruct,
|
|
||||||
) -> Result<HandshakeSignatureValid, tokio_rustls::rustls::Error> {
|
|
||||||
Ok(HandshakeSignatureValid::assertion())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn verify_tls13_signature(
|
let mut buf = String::new();
|
||||||
&self,
|
loop {
|
||||||
message: &[u8],
|
buf.clear();
|
||||||
cert: &tokio_rustls::rustls::pki_types::CertificateDer<'_>,
|
let n = reader.read_line(&mut buf).await?;
|
||||||
dss: &tokio_rustls::rustls::DigitallySignedStruct,
|
if n == 0 {
|
||||||
) -> Result<HandshakeSignatureValid, tokio_rustls::rustls::Error> {
|
break;
|
||||||
Ok(HandshakeSignatureValid::assertion())
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn supported_verify_schemes(&self) -> Vec<tokio_rustls::rustls::SignatureScheme> {
|
// Format:
|
||||||
vec![
|
// host <space> expires <space> hash-algo <space> fingerprint
|
||||||
SignatureScheme::ECDSA_NISTP256_SHA256,
|
// Example:
|
||||||
SignatureScheme::ECDSA_NISTP384_SHA384,
|
// dc09.ru 1722930541 sha512 dGVzdHRlc3R0ZXN0Cg
|
||||||
SignatureScheme::ECDSA_NISTP521_SHA512,
|
if let [host, expires, algo, fp] = buf
|
||||||
]
|
.split_whitespace()
|
||||||
|
.take(4)
|
||||||
|
.collect::<Vec<&str>>()
|
||||||
|
.as_slice()
|
||||||
|
{
|
||||||
|
let expires = if let Ok(num) = expires.parse::<u64>() {
|
||||||
|
num
|
||||||
|
} else {
|
||||||
|
eprintln!("Cannot parse expires = {:?} as u64", expires);
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let algo = match algo {
|
||||||
|
&"sha256" => fingerprint::Algorithm::Sha256,
|
||||||
|
&"sha512" => fingerprint::Algorithm::Sha512,
|
||||||
|
&_ => {
|
||||||
|
eprintln!("Unknown hash algorithm {:?}, skipping", algo);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
map.insert(
|
||||||
|
(*host).to_owned(),
|
||||||
|
SelfsignedCert {
|
||||||
|
algo,
|
||||||
|
fingerprint: (*fp).to_owned(),
|
||||||
|
expires,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
eprintln!("Cannot parse line: {:?}", buf);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let f = Mutex::new(
|
||||||
|
std::fs::OpenOptions::new()
|
||||||
|
.append(true)
|
||||||
|
.create(true)
|
||||||
|
.open("known_hosts")?,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(CertVerifier { f, map })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SelfsignedCertVerifier for CertVerifier {
|
||||||
|
fn verify(
|
||||||
|
&self,
|
||||||
|
cert: &tokio_gemini::certs::verifier::CertificateDer,
|
||||||
|
host: &str,
|
||||||
|
_now: tokio_gemini::certs::verifier::UnixTime,
|
||||||
|
) -> Result<bool, tokio_rustls::rustls::Error> {
|
||||||
|
if let Some(known_cert) = self.map.get(host) {
|
||||||
|
// if host is found in known_hosts, compare certs
|
||||||
|
let this_fp = generate_fingerprint(cert, known_cert.algo);
|
||||||
|
if this_fp == known_cert.fingerprint {
|
||||||
|
// current cert hash matches known cert hash
|
||||||
|
eprintln!("Cert for {} matched: {}", &host, &this_fp);
|
||||||
|
Ok(true)
|
||||||
|
} else {
|
||||||
|
// TODO (after implementing `expires`) update cert if known is expired
|
||||||
|
eprintln!(
|
||||||
|
"Error: certs do not match! Possibly MitM attack.\nKnown FP: {}\nGot: {}",
|
||||||
|
&known_cert.fingerprint, &this_fp,
|
||||||
|
);
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// host is unknown, generate hash and add to known_hosts
|
||||||
|
let this_fp = generate_fingerprint(cert, fingerprint::Algorithm::Sha512);
|
||||||
|
eprintln!(
|
||||||
|
"Warning: updating known_hosts with cert {} for {}",
|
||||||
|
&this_fp, &host,
|
||||||
|
);
|
||||||
|
|
||||||
|
(|| {
|
||||||
|
// trick with cloning file descriptor
|
||||||
|
// because we are not allowed to mutate &self
|
||||||
|
let mut f = std::fs::File::from(
|
||||||
|
self.f.lock().unwrap().as_fd().try_clone_to_owned().unwrap(),
|
||||||
|
);
|
||||||
|
f.write_all(host.as_bytes())?;
|
||||||
|
f.write_all(b" 0 sha512 ")?; // TODO after implementing `expires`
|
||||||
|
f.write_all(&this_fp.as_bytes())?;
|
||||||
|
f.write(b"\n")?;
|
||||||
|
Ok::<(), std::io::Error>(())
|
||||||
|
})()
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
eprintln!("Could not add cert to file: {:?}", e);
|
||||||
|
});
|
||||||
|
|
||||||
|
self.map.insert(
|
||||||
|
host.to_owned(),
|
||||||
|
SelfsignedCert {
|
||||||
|
algo: fingerprint::Algorithm::Sha512,
|
||||||
|
fingerprint: this_fp,
|
||||||
|
expires: 0, // TODO after implementing cert parsing in tokio-gemini
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue