src/client/http_client.rs: implement fingerprint cache

This commit is contained in:
Dietmar Maurer 2020-01-25 15:37:34 +01:00
parent d59dbeca1b
commit 5a74756c15
2 changed files with 92 additions and 5 deletions

View File

@ -167,6 +167,7 @@ fn connect(server: &str, userid: &str) -> Result<HttpClient, Error> {
let options = HttpClientOptions::new() let options = HttpClientOptions::new()
.interactive(true) .interactive(true)
.fingerprint_cache(true)
.ticket_cache(true); .ticket_cache(true);
HttpClient::new(server, userid, options) HttpClient::new(server, userid, options)
@ -1472,10 +1473,9 @@ async fn status(param: Value) -> Result<Value, Error> {
// like get, but simply ignore errors and return Null instead // like get, but simply ignore errors and return Null instead
async fn try_get(repo: &BackupRepository, url: &str) -> Value { async fn try_get(repo: &BackupRepository, url: &str) -> Value {
let options = HttpClientOptions::new() let options = HttpClientOptions::new()
.verify_cert(false) // fixme: set verify to true, but howto handle fingerprint ??
.interactive(false) .interactive(false)
.fingerprint_cache(true)
.ticket_cache(true); .ticket_cache(true);
let client = match HttpClient::new(repo.host(), repo.user(), options) { let client = match HttpClient::new(repo.host(), repo.user(), options) {

View File

@ -35,6 +35,7 @@ pub struct HttpClientOptions {
fingerprint: Option<String>, fingerprint: Option<String>,
interactive: bool, interactive: bool,
ticket_cache: bool, ticket_cache: bool,
fingerprint_cache: bool,
verify_cert: bool, verify_cert: bool,
} }
@ -46,6 +47,7 @@ impl HttpClientOptions {
fingerprint: None, fingerprint: None,
interactive: false, interactive: false,
ticket_cache: false, ticket_cache: false,
fingerprint_cache: false,
verify_cert: true, verify_cert: true,
} }
} }
@ -70,6 +72,11 @@ impl HttpClientOptions {
self self
} }
pub fn fingerprint_cache(mut self, fingerprint_cache: bool) -> Self {
self.fingerprint_cache = fingerprint_cache;
self
}
pub fn verify_cert(mut self, verify_cert: bool) -> Self { pub fn verify_cert(mut self, verify_cert: bool) -> Self {
self.verify_cert = verify_cert; self.verify_cert = verify_cert;
self self
@ -106,6 +113,69 @@ pub fn delete_ticket_info(server: &str, username: &str) -> Result<(), Error> {
Ok(()) Ok(())
} }
fn store_fingerprint(server: &str, fingerprint: &str) -> Result<(), Error> {
let base = BaseDirectories::with_prefix("proxmox-backup")?;
// usually ~/.config/proxmox-backup/fingerprints
let path = base.place_config_file("fingerprints")?;
let raw = match std::fs::read_to_string(&path) {
Ok(v) => v,
Err(err) => {
if err.kind() == std::io::ErrorKind::NotFound {
String::new()
} else {
bail!("unable to read fingerprints from {:?} - {}", path, err);
}
}
};
let mut result = String::new();
raw.split('\n').for_each(|line| {
let items: Vec<String> = line.split_whitespace().map(String::from).collect();
if items.len() == 2 {
if &items[0] == server {
// found, add later with new fingerprint
} else {
result.push_str(line);
result.push('\n');
}
}
});
result.push_str(server);
result.push(' ');
result.push_str(fingerprint);
result.push('\n');
replace_file(path, result.as_bytes(), CreateOptions::new())?;
Ok(())
}
fn load_fingerprint(server: &str) -> Option<String> {
let base = BaseDirectories::with_prefix("proxmox-backup").ok()?;
// usually ~/.config/proxmox-backup/fingerprints
let path = base.place_config_file("fingerprints").ok()?;
let raw = std::fs::read_to_string(&path).ok()?;
for line in raw.split('\n') {
let items: Vec<String> = line.split_whitespace().map(String::from).collect();
if items.len() == 2 {
if &items[0] == server {
return Some(items[1].clone());
}
}
}
None
}
fn store_ticket_info(server: &str, username: &str, ticket: &str, token: &str) -> Result<(), Error> { fn store_ticket_info(server: &str, username: &str, ticket: &str, token: &str) -> Result<(), Error> {
let base = BaseDirectories::with_prefix("proxmox-backup")?; let base = BaseDirectories::with_prefix("proxmox-backup")?;
@ -169,11 +239,18 @@ impl HttpClient {
let verified_fingerprint = Arc::new(Mutex::new(None)); let verified_fingerprint = Arc::new(Mutex::new(None));
let mut fingerprint = options.fingerprint.take();
if options.fingerprint_cache && fingerprint.is_none() {
fingerprint = load_fingerprint(server);
}
let client = Self::build_client( let client = Self::build_client(
options.fingerprint.clone(), server.to_string(),
fingerprint,
options.interactive, options.interactive,
verified_fingerprint.clone(), verified_fingerprint.clone(),
options.verify_cert, options.verify_cert,
options.fingerprint_cache,
); );
let password = options.password.take(); let password = options.password.take();
@ -243,9 +320,11 @@ impl HttpClient {
fn verify_callback( fn verify_callback(
valid: bool, ctx: valid: bool, ctx:
&mut X509StoreContextRef, &mut X509StoreContextRef,
server: String,
expected_fingerprint: Option<String>, expected_fingerprint: Option<String>,
interactive: bool, interactive: bool,
verified_fingerprint: Arc<Mutex<Option<String>>>, verified_fingerprint: Arc<Mutex<Option<String>>>,
fingerprint_cache: bool,
) -> bool { ) -> bool {
if valid { return true; } if valid { return true; }
@ -285,7 +364,11 @@ impl HttpClient {
match std::io::stdin().read_exact(&mut buf) { match std::io::stdin().read_exact(&mut buf) {
Ok(()) => { Ok(()) => {
if buf[0] == b'y' || buf[0] == b'Y' { if buf[0] == b'y' || buf[0] == b'Y' {
println!("TRUST {}", fp_string); if fingerprint_cache {
if let Err(err) = store_fingerprint(&server, &fp_string) {
eprintln!("{}", err);
}
}
*verified_fingerprint.lock().unwrap() = Some(fp_string); *verified_fingerprint.lock().unwrap() = Some(fp_string);
return true; return true;
} else if buf[0] == b'n' || buf[0] == b'N' { } else if buf[0] == b'n' || buf[0] == b'N' {
@ -302,17 +385,21 @@ impl HttpClient {
} }
fn build_client( fn build_client(
server: String,
fingerprint: Option<String>, fingerprint: Option<String>,
interactive: bool, interactive: bool,
verified_fingerprint: Arc<Mutex<Option<String>>>, verified_fingerprint: Arc<Mutex<Option<String>>>,
verify_cert: bool, verify_cert: bool,
fingerprint_cache: bool,
) -> Client<HttpsConnector> { ) -> Client<HttpsConnector> {
let mut ssl_connector_builder = SslConnector::builder(SslMethod::tls()).unwrap(); let mut ssl_connector_builder = SslConnector::builder(SslMethod::tls()).unwrap();
if verify_cert { if verify_cert {
ssl_connector_builder.set_verify_callback(openssl::ssl::SslVerifyMode::PEER, move |valid, ctx| { ssl_connector_builder.set_verify_callback(openssl::ssl::SslVerifyMode::PEER, move |valid, ctx| {
Self::verify_callback(valid, ctx, fingerprint.clone(), interactive, verified_fingerprint.clone()) Self::verify_callback(
valid, ctx, server.clone(), fingerprint.clone(), interactive,
verified_fingerprint.clone(), fingerprint_cache)
}); });
} else { } else {
ssl_connector_builder.set_verify(openssl::ssl::SslVerifyMode::NONE); ssl_connector_builder.set_verify(openssl::ssl::SslVerifyMode::NONE);