M src/bin/poptea-cli.rs => src/bin/poptea-cli.rs +4 -2
@@ 1,10 1,12 @@
use io::Write;
use poptea::GeminiClient;
-use std::io;
+use std::{io, sync::Arc};
fn main() {
let url = std::env::args().nth(1).expect("please provide gemini url");
- let client = poptea::TlsClient::new();
+ let fs = poptea::FileSystem::new("~/.poptea".into());
+
+ let client = poptea::TlsClient::new(Arc::new(fs));
let res = client.get(&url).expect("failed to make a request");
io::stdout()
M src/infra/client.rs => src/infra/client.rs +23 -12
@@ 4,19 4,21 @@ use url::Url;
use io::{Read, Write};
use std::str::FromStr;
use std::sync::Arc;
-use std::{io, io::BufRead};
+use std::{io, io::BufRead, convert::Into};
use x509_parser::prelude::*;
-use crate::{GemResponse, GemStatus, GeminiClient, PopResult};
+use crate::{GemResponse, GemStatus, GeminiClient, PopResult, TrustStore, VerifyStatus};
-fn fingerprint(cert: &rustls::Certificate) -> std::result::Result<String, String> {
+fn fingerprint(cert: &rustls::Certificate) -> PopResult<(String, String)> {
let (_, pk) = X509Certificate::from_der(cert.as_ref()).unwrap();
let res = pk.public_key().subject_public_key.as_ref();
- Ok(format!("{:?}", res))
+ Ok((pk.subject().to_string(), format!("{:?}", res)))
}
-struct TofuVerification {}
+struct TofuVerification {
+ store: Arc<dyn TrustStore>,
+}
impl rustls::client::ServerCertVerifier for TofuVerification {
fn verify_server_cert(
@@ 31,17 33,24 @@ impl rustls::client::ServerCertVerifier for TofuVerification {
let path = "cert.der";
let mut file = File::create(path).unwrap();
file.write_all(cert.as_ref()).unwrap();
- let fingerprint = fingerprint(cert).unwrap();
-
- Ok(rustls::client::ServerCertVerified::assertion())
+ let (addr, fingerprint) = fingerprint(cert).unwrap();
+ match self.store.verify(&addr, fingerprint) {
+ Ok(VerifyStatus::Trusted) => Ok(rustls::client::ServerCertVerified::assertion()),
+ Ok(VerifyStatus::Untrusted) => {
+ Err(rustls::Error::InvalidCertificateData("untrusted".into()))
+ }
+ Err(_) => Err(rustls::Error::General("storage error".into())),
+ }
}
}
-pub struct TlsClient {}
+pub struct TlsClient {
+ store: Arc<dyn TrustStore>,
+}
impl TlsClient {
- pub fn new() -> Self {
- Self {}
+ pub fn new(store: Arc<dyn TrustStore>) -> Self {
+ Self { store }
}
pub fn get_plain(&self, url: &str) -> PopResult<Vec<u8>> {
@@ 61,7 70,9 @@ impl TlsClient {
.with_no_client_auth();
config
.dangerous()
- .set_certificate_verifier(Arc::new(TofuVerification {}));
+ .set_certificate_verifier(Arc::new(TofuVerification {
+ store: self.store.clone(),
+ }));
let arc = std::sync::Arc::new(config);
let mut sess = rustls::ClientConnection::new(arc, host.try_into().unwrap()).unwrap();
A src/infra/fs.rs => src/infra/fs.rs +16 -0
@@ 0,0 1,16 @@
+use crate::{PopResult, TrustStore, VerifyStatus};
+
+pub struct FileSystem {}
+
+impl FileSystem {
+ pub fn new(pop_dir: String) -> Self {
+ Self {}
+ }
+}
+
+impl TrustStore for FileSystem {
+ fn verify(&self, addr: &str, fingerprint: String) -> PopResult<VerifyStatus> {
+ println!("{} {}", addr, fingerprint);
+ Ok(VerifyStatus::Untrusted)
+ }
+}
M src/infra/mod.rs => src/infra/mod.rs +2 -0
@@ 1,3 1,5 @@
mod client;
+mod fs;
pub use client::TlsClient;
+pub use fs::FileSystem;
M src/lib.rs => src/lib.rs +13 -2
@@ 1,7 1,8 @@
-mod infra;
-pub use infra::TlsClient;
use std::str::FromStr;
+mod infra;
+pub use infra::{TlsClient, FileSystem};
+
#[derive(Debug)]
pub enum GemStatus {
Input,
@@ 58,3 59,13 @@ pub enum PopError {
}
pub type PopResult<T> = Result<T, PopError>;
+
+pub trait TrustStore: Send + Sync {
+ fn verify(&self, addr: &str, fingerprint: String) -> PopResult<VerifyStatus>;
+}
+
+pub enum VerifyStatus {
+ Trusted,
+ Untrusted,
+}
+