From 9d9d5dac7a2e342a35482756ef7c92d045f8f835 Mon Sep 17 00:00:00 2001 From: Jesse Luehrs Date: Sat, 18 Feb 2023 20:40:08 -0500 Subject: refactor client cert handling --- src/actions.rs | 2 +- src/api.rs | 61 ++++++++++++++++++++++++++++++------------------- src/bin/rbw/commands.rs | 3 ++- src/config.rs | 6 ++--- src/error.rs | 12 ++++++++++ 5 files changed, 56 insertions(+), 28 deletions(-) diff --git a/src/actions.rs b/src/actions.rs index a7829a4..ca5e448 100644 --- a/src/actions.rs +++ b/src/actions.rs @@ -5,7 +5,7 @@ fn api_client() -> Result<(crate::api::Client, crate::config::Config)> { let client = crate::api::Client::new( &config.base_url(), &config.identity_url(), - &config.client_cert_path(), + config.client_cert_path(), ); Ok((client, config)) } diff --git a/src/api.rs b/src/api.rs index 44d0fe0..b6cb4d4 100644 --- a/src/api.rs +++ b/src/api.rs @@ -8,8 +8,7 @@ use crate::json::{ DeserializeJsonWithPath as _, DeserializeJsonWithPathAsync as _, }; -use std::fs::File; -use std::io::Read; +use tokio::io::AsyncReadExt as _; #[derive( serde_repr::Serialize_repr, @@ -554,7 +553,7 @@ struct FoldersPostReq { pub struct Client { base_url: String, identity_url: String, - client_cert_path: String, + client_cert_path: Option, } impl Client { @@ -562,29 +561,45 @@ impl Client { pub fn new( base_url: &str, identity_url: &str, - client_cert_path: &str, + client_cert_path: Option<&std::path::Path>, ) -> Self { Self { base_url: base_url.to_string(), identity_url: identity_url.to_string(), - client_cert_path: client_cert_path.to_string(), + client_cert_path: client_cert_path + .map(std::path::Path::to_path_buf), } } - fn reqwest_client(&self) -> reqwest::Client { - if self.client_cert_path.is_empty() { - reqwest::Client::new() - } else { + async fn reqwest_client(&self) -> Result { + if let Some(client_cert_path) = self.client_cert_path.as_ref() { let mut buf = Vec::new(); - let mut f = - File::open(&self.client_cert_path).expect("cert not found"); - f.read_to_end(&mut buf).expect("cert read failed"); - let pem = - reqwest::Identity::from_pem(&buf).expect("invalid cert"); - reqwest::Client::builder() - .identity(pem) - .build() - .expect("wtv") + let mut f = tokio::fs::File::open(client_cert_path) + .await + .map_err(|e| Error::LoadClientCert { + source: e, + file: client_cert_path.clone(), + })?; + f.read_to_end(&mut buf).await.map_err(|e| { + Error::LoadClientCert { + source: e, + file: client_cert_path.clone(), + } + })?; + let pem = reqwest::Identity::from_pem(&buf).map_err(|e| { + Error::LoadClientCertReqwest { + source: e, + file: client_cert_path.clone(), + } + })?; + Ok(reqwest::Client::builder().identity(pem).build().map_err( + |e| Error::LoadClientCertReqwest { + source: e, + file: client_cert_path.clone(), + }, + )?) + } else { + Ok(reqwest::Client::new()) } } @@ -592,7 +607,7 @@ impl Client { let prelogin = PreloginReq { email: email.to_string(), }; - let client = self.reqwest_client(); + let client = self.reqwest_client().await?; let res = client .post(&self.api_url("/accounts/prelogin")) .json(&prelogin) @@ -627,7 +642,7 @@ impl Client { two_factor_token: None, two_factor_provider: None, }; - let client = self.reqwest_client(); + let client = self.reqwest_client().await?; let res = client .post(&self.identity_url("/connect/token")) .form(&connect_req) @@ -668,7 +683,7 @@ impl Client { #[allow(clippy::as_conversions)] two_factor_provider: two_factor_provider.map(|ty| ty as u32), }; - let client = self.reqwest_client(); + let client = self.reqwest_client().await?; let res = client .post(&self.identity_url("/connect/token")) .form(&connect_req) @@ -702,7 +717,7 @@ impl Client { std::collections::HashMap, Vec, )> { - let client = self.reqwest_client(); + let client = self.reqwest_client().await?; let res = client .get(&self.api_url("/sync")) .header("Authorization", format!("Bearer {access_token}")) @@ -1098,7 +1113,7 @@ impl Client { client_id: "desktop".to_string(), refresh_token: refresh_token.to_string(), }; - let client = self.reqwest_client(); + let client = self.reqwest_client().await?; let res = client .post(&self.identity_url("/connect/token")) .form(&connect_req) diff --git a/src/bin/rbw/commands.rs b/src/bin/rbw/commands.rs index dda35e8..a2516d6 100644 --- a/src/bin/rbw/commands.rs +++ b/src/bin/rbw/commands.rs @@ -617,7 +617,8 @@ pub fn config_set(key: &str, value: &str) -> anyhow::Result<()> { "base_url" => config.base_url = Some(value.to_string()), "identity_url" => config.identity_url = Some(value.to_string()), "client_cert_path" => { - config.client_cert_path = Some(value.to_string()); + config.client_cert_path = + Some(std::path::PathBuf::from(value.to_string())); } "lock_timeout" => { let timeout = value diff --git a/src/config.rs b/src/config.rs index 8209ddb..91aa449 100644 --- a/src/config.rs +++ b/src/config.rs @@ -12,7 +12,7 @@ pub struct Config { pub lock_timeout: u64, #[serde(default = "default_pinentry")] pub pinentry: String, - pub client_cert_path: Option, + pub client_cert_path: Option, // backcompat, no longer generated in new configs #[serde(skip_serializing)] pub device_id: Option, @@ -151,8 +151,8 @@ impl Config { } #[must_use] - pub fn client_cert_path(&self) -> String { - self.client_cert_path.clone().unwrap_or_default() + pub fn client_cert_path(&self) -> Option<&std::path::Path> { + self.client_cert_path.as_deref() } #[must_use] diff --git a/src/error.rs b/src/error.rs index 41d0d4a..d165a92 100644 --- a/src/error.rs +++ b/src/error.rs @@ -118,6 +118,18 @@ pub enum Error { file: std::path::PathBuf, }, + #[error("failed to load client cert from {}", .file.display())] + LoadClientCert { + source: tokio::io::Error, + file: std::path::PathBuf, + }, + + #[error("failed to load client cert from {}", .file.display())] + LoadClientCertReqwest { + source: reqwest::Error, + file: std::path::PathBuf, + }, + #[error("invalid padding")] Padding, -- cgit v1.2.3-54-g00ecf