aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJesse Luehrs <doy@tozt.net>2020-04-06 07:20:11 -0400
committerJesse Luehrs <doy@tozt.net>2020-04-06 07:20:11 -0400
commit1c40a0f9ff0ced652ee8b74f2333000ca47a0692 (patch)
treea0be3e77060d7f267596d70e4ce14b4604f9bb25
parentbc18bca5c67b4a678a31198877e39d57d97b1e0c (diff)
downloadrbw-1c40a0f9ff0ced652ee8b74f2333000ca47a0692.tar.gz
rbw-1c40a0f9ff0ced652ee8b74f2333000ca47a0692.zip
a bit more cleanup
-rw-r--r--src/actions.rs37
-rw-r--r--src/bin/agent.rs20
-rw-r--r--src/error.rs22
-rw-r--r--src/pinentry.rs55
4 files changed, 87 insertions, 47 deletions
diff --git a/src/actions.rs b/src/actions.rs
index d7f3103..0998abc 100644
--- a/src/actions.rs
+++ b/src/actions.rs
@@ -1,20 +1,21 @@
-// TODO api needs to be async
+use crate::prelude::*;
-pub async fn login(email: &str, password: &str) -> (String, u32, String) {
+pub async fn login(
+ email: &str,
+ password: &str,
+) -> Result<(String, u32, String)> {
let client =
crate::api::Client::new_self_hosted("https://bitwarden.tozt.net");
- let iterations = client.prelogin(&email).await.unwrap();
+ let iterations = client.prelogin(&email).await?;
let identity =
- crate::identity::Identity::new(&email, &password, iterations)
- .unwrap();
+ crate::identity::Identity::new(&email, &password, iterations)?;
let (access_token, _refresh_token, protected_key) = client
.login(&identity.email, &identity.master_password_hash)
- .await
- .unwrap();
+ .await?;
- (access_token, iterations, protected_key)
+ Ok((access_token, iterations, protected_key))
}
pub async fn unlock(
@@ -22,25 +23,25 @@ pub async fn unlock(
password: &str,
iterations: u32,
protected_key: String,
-) -> (Vec<u8>, Vec<u8>) {
+) -> Result<(Vec<u8>, Vec<u8>)> {
let identity =
- crate::identity::Identity::new(&email, &password, iterations)
- .unwrap();
+ crate::identity::Identity::new(&email, &password, iterations)?;
let protected_key =
- crate::cipherstring::CipherString::new(&protected_key).unwrap();
- let master_key = protected_key
- .decrypt(&identity.enc_key, &identity.mac_key)
- .unwrap();
+ crate::cipherstring::CipherString::new(&protected_key)?;
+ let master_key =
+ protected_key.decrypt(&identity.enc_key, &identity.mac_key)?;
let enc_key = &master_key[0..32];
let mac_key = &master_key[32..64];
- (enc_key.to_vec(), mac_key.to_vec())
+ Ok((enc_key.to_vec(), mac_key.to_vec()))
}
-pub async fn sync(access_token: &str) -> (String, Vec<crate::api::Cipher>) {
+pub async fn sync(
+ access_token: &str,
+) -> Result<(String, Vec<crate::api::Cipher>)> {
let client =
crate::api::Client::new_self_hosted("https://bitwarden.tozt.net");
- client.sync(access_token).await.unwrap()
+ client.sync(access_token).await
}
diff --git a/src/bin/agent.rs b/src/bin/agent.rs
index 5a97448..dedfe68 100644
--- a/src/bin/agent.rs
+++ b/src/bin/agent.rs
@@ -62,14 +62,17 @@ async fn login(
) {
let mut state = state.write().await;
let email = "bitwarden@tozt.net"; // XXX read from config
- let password = rbw::pinentry::pinentry("prompt", "desc", tty).await;
+ let password = rbw::pinentry::pinentry("prompt", "desc", tty)
+ .await
+ .unwrap();
let (access_token, iterations, protected_key) =
- rbw::actions::login(email, &password).await;
+ rbw::actions::login(email, &password).await.unwrap();
state.access_token = Some(access_token);
state.iterations = Some(iterations);
let (enc_key, mac_key) =
rbw::actions::unlock(email, &password, iterations, protected_key)
- .await;
+ .await
+ .unwrap();
state.priv_key = Some((enc_key, mac_key));
}
@@ -86,14 +89,17 @@ async fn unlock(
) {
let mut state = state.write().await;
let email = "bitwarden@tozt.net"; // XXX read from config
- let password = rbw::pinentry::pinentry("prompt", "desc", tty).await;
+ let password = rbw::pinentry::pinentry("prompt", "desc", tty)
+ .await
+ .unwrap();
let (enc_key, mac_key) = rbw::actions::unlock(
email,
&password,
state.iterations.unwrap(),
state.protected_key.as_ref().unwrap().to_string(),
)
- .await;
+ .await
+ .unwrap();
state.priv_key = Some((enc_key, mac_key));
}
@@ -101,7 +107,9 @@ async fn sync(state: std::sync::Arc<tokio::sync::RwLock<State>>) {
ensure_login(state.clone()).await;
let mut state = state.write().await;
let (protected_key, ciphers) =
- rbw::actions::sync(state.access_token.as_ref().unwrap()).await;
+ rbw::actions::sync(state.access_token.as_ref().unwrap())
+ .await
+ .unwrap();
state.protected_key = Some(protected_key);
println!("{}", serde_json::to_string(&ciphers).unwrap());
state.ciphers = ciphers;
diff --git a/src/error.rs b/src/error.rs
index 8ebbfcd..8947fd0 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -9,6 +9,19 @@ pub enum Error {
#[snafu(display("failed to decrypt: {}", source))]
Decrypt { source: block_modes::BlockModeError },
+ #[snafu(display("failed to parse pinentry output ({:?})", out,))]
+ FailedToParsePinentry { out: Vec<u8> },
+
+ #[snafu(display(
+ "failed to parse pinentry output ({:?}): {}",
+ out,
+ source
+ ))]
+ FailedToParsePinentryUtf8 {
+ out: Vec<u8>,
+ source: std::string::FromUtf8Error,
+ },
+
// no Error impl
// #[snafu(display("failed to expand with hkdf: {}", source))]
// HkdfExpand { source: hkdf::InvalidLength },
@@ -36,8 +49,17 @@ pub enum Error {
#[snafu(display("invalid mac key"))]
InvalidMacKey,
+ #[snafu(display("error waiting for pinentry to exit: {}", source))]
+ ProcessWaitOutput { source: tokio::io::Error },
+
#[snafu(display("error making api request: {}", source))]
Reqwest { source: reqwest::Error },
+
+ #[snafu(display("error spawning pinentry: {}", source))]
+ Spawn { source: tokio::io::Error },
+
+ #[snafu(display("error writing to pinentry stdin: {}", source))]
+ WriteStdin { source: tokio::io::Error },
}
pub type Result<T> = std::result::Result<T, Error>;
diff --git a/src/pinentry.rs b/src/pinentry.rs
index 610f52d..8a25759 100644
--- a/src/pinentry.rs
+++ b/src/pinentry.rs
@@ -1,7 +1,12 @@
-use tokio::io::{AsyncBufReadExt as _, AsyncWriteExt as _};
+use crate::prelude::*;
-// TODO result
-pub async fn pinentry(prompt: &str, desc: &str, tty: Option<&str>) -> String {
+use tokio::io::AsyncWriteExt as _;
+
+pub async fn pinentry(
+ prompt: &str,
+ desc: &str,
+ tty: Option<&str>,
+) -> Result<String> {
let mut opts = tokio::process::Command::new("pinentry");
let opts = opts
.stdin(std::process::Stdio::piped())
@@ -11,39 +16,43 @@ pub async fn pinentry(prompt: &str, desc: &str, tty: Option<&str>) -> String {
} else {
opts
};
- let mut child = opts.spawn().unwrap();
+ let mut child = opts.spawn().context(crate::error::Spawn)?;
{
let stdin = child.stdin.as_mut().unwrap();
- let mut stdout =
- tokio::io::BufReader::new(child.stdout.as_mut().unwrap());
- let mut buf = String::new();
-
- stdin.write_all(b"SETTITLE rbw\n").await.unwrap();
- stdout.read_line(&mut buf).await.unwrap();
stdin
+ .write_all(b"SETTITLE rbw\n")
+ .await
+ .context(crate::error::WriteStdin)?;
+ stdin
.write_all(format!("SETPROMPT {}\n", prompt).as_bytes())
.await
- .unwrap();
- stdout.read_line(&mut buf).await.unwrap();
-
+ .context(crate::error::WriteStdin)?;
stdin
.write_all(format!("SETDESC {}\n", desc).as_bytes())
.await
- .unwrap();
- stdout.read_line(&mut buf).await.unwrap();
-
- stdin.write_all(b"GETPIN\n").await.unwrap();
+ .context(crate::error::WriteStdin)?;
+ stdin
+ .write_all(b"GETPIN\n")
+ .await
+ .context(crate::error::WriteStdin)?;
}
- let res =
- String::from_utf8(child.wait_with_output().await.unwrap().stdout)
- .unwrap();
- for line in res.lines() {
+
+ let out = child
+ .wait_with_output()
+ .await
+ .context(crate::error::ProcessWaitOutput)?
+ .stdout;
+ let out_str = String::from_utf8(out.clone()).context(
+ crate::error::FailedToParsePinentryUtf8 { out: out.clone() },
+ )?;
+ for line in out_str.lines() {
if line.starts_with("OK") {
continue;
} else if line.starts_with("D ") {
- return line[2..line.len()].to_string();
+ return Ok(line[2..line.len()].to_string());
}
}
- panic!("failed to parse pinentry output: {:?}", res)
+
+ Err(Error::FailedToParsePinentry { out })
}