diff options
-rw-r--r-- | Cargo.lock | 35 | ||||
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | README.md | 5 | ||||
-rw-r--r-- | src/api.rs | 7 | ||||
-rw-r--r-- | src/bin/rbw-agent/actions.rs | 31 | ||||
-rw-r--r-- | src/bin/rbw/commands.rs | 32 | ||||
-rw-r--r-- | src/edit.rs | 10 | ||||
-rw-r--r-- | src/error.rs | 5 |
8 files changed, 91 insertions, 35 deletions
@@ -133,6 +133,17 @@ dependencies = [ ] [[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + +[[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -683,6 +694,15 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" @@ -828,7 +848,7 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.2", "libc", "windows-sys 0.48.0", ] @@ -845,7 +865,7 @@ version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.2", "io-lifetimes", "rustix", "windows-sys 0.48.0", @@ -1075,7 +1095,7 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.2", "libc", ] @@ -1296,6 +1316,7 @@ dependencies = [ "argon2", "arrayvec", "async-trait", + "atty", "base32", "base64", "block-padding", @@ -1564,9 +1585,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.101.1" +version = "0.101.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15f36a6828982f422756984e47912a7a51dcbc2a197aa791158f8ca61cd8204e" +checksum = "7d93931baf2d282fff8d3a532bbfd7653f734643161b87e3e01e59a04439bf0d" dependencies = [ "ring", "untrusted", @@ -2324,9 +2345,9 @@ dependencies = [ [[package]] name = "webpki" -version = "0.22.0" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +checksum = "f0e74f82d49d545ad128049b7e88f6576df2da6b02e9ce565c6f533be576957e" dependencies = [ "ring", "untrusted", @@ -18,6 +18,7 @@ anyhow = "1.0.72" argon2 = "0.5.1" arrayvec = "0.7.4" async-trait = "0.1.71" +atty="0.2.*" base32 = "0.4.0" base64 = "0.21.2" block-padding = "0.3.3" @@ -23,8 +23,8 @@ and merge pull requests implementing those features. ### Arch Linux -`rbw` is available in the [community -repository](https://archlinux.org/packages/community/x86_64/rbw/). +`rbw` is available in the [extra +repository](https://archlinux.org/packages/extra/x86_64/rbw/). Alternatively, you can install [`rbw-git`](https://aur.archlinux.org/packages/rbw-git/) from the AUR, which will always build from the latest master commit. @@ -109,3 +109,4 @@ the instructions [here](https://bitwarden.com/help/article/personal-api-key/). * [rofi-rbw](https://github.com/fdw/rofi-rbw): A rofi frontend for Bitwarden * [bw-ssh](https://framagit.org/Glandos/bw-ssh/): Manage SSH key passphrases in Bitwarden +* [rbw-menu](https://github.com/rbuchberger/rbw-menu): Tiny menu picker for rbw @@ -1041,7 +1041,12 @@ impl Client { history: &[crate::db::HistoryEntry], ) -> Result<()> { let mut req = CiphersPutReq { - ty: 1, + ty: match data { + crate::db::EntryData::Login {..} => 1, + crate::db::EntryData::SecureNote {..} => 2, + crate::db::EntryData::Card {..} => 3, + crate::db::EntryData::Identity {..} => 4, + }, folder_id: folder_uuid.map(std::string::ToString::to_string), organization_id: org_id.map(std::string::ToString::to_string), name: name.to_string(), diff --git a/src/bin/rbw-agent/actions.rs b/src/bin/rbw-agent/actions.rs index 58432b1..ff39510 100644 --- a/src/bin/rbw-agent/actions.rs +++ b/src/bin/rbw-agent/actions.rs @@ -10,8 +10,7 @@ pub async fn register( let url_str = config_base_url().await?; let url = reqwest::Url::parse(&url_str) .context("failed to parse base url")?; - let Some(host) = url.host_str() - else { + let Some(host) = url.host_str() else { return Err(anyhow::anyhow!( "couldn't find host in rbw base url {}", url_str @@ -88,8 +87,7 @@ pub async fn login( let url_str = config_base_url().await?; let url = reqwest::Url::parse(&url_str) .context("failed to parse base url")?; - let Some(host) = url.host_str() - else { + let Some(host) = url.host_str() else { return Err(anyhow::anyhow!( "couldn't find host in rbw base url {}", url_str @@ -330,8 +328,7 @@ async fn login_success( sync(None, state.clone()).await?; let db = load_db().await?; - let Some(protected_private_key) = db.protected_private_key - else { + let Some(protected_private_key) = db.protected_private_key else { return Err(anyhow::anyhow!( "failed to find protected private key in db" )); @@ -369,15 +366,11 @@ pub async fn unlock( if state.lock().await.needs_unlock() { let db = load_db().await?; - let Some(kdf) = db.kdf - else { - return Err(anyhow::anyhow!( - "failed to find kdf type in db" - )); + let Some(kdf) = db.kdf else { + return Err(anyhow::anyhow!("failed to find kdf type in db")); }; - let Some(iterations) = db.iterations - else { + let Some(iterations) = db.iterations else { return Err(anyhow::anyhow!( "failed to find number of iterations in db" )); @@ -386,14 +379,12 @@ pub async fn unlock( let memory = db.memory; let parallelism = db.parallelism; - let Some(protected_key) = db.protected_key - else { + let Some(protected_key) = db.protected_key else { return Err(anyhow::anyhow!( "failed to find protected key in db" )); }; - let Some(protected_private_key) = db.protected_private_key - else { + let Some(protected_private_key) = db.protected_private_key else { return Err(anyhow::anyhow!( "failed to find protected private key in db" )); @@ -543,8 +534,7 @@ pub async fn decrypt( org_id: Option<&str>, ) -> anyhow::Result<()> { let state = state.lock().await; - let Some(keys) = state.key(org_id) - else { + let Some(keys) = state.key(org_id) else { return Err(anyhow::anyhow!( "failed to find decryption keys in in-memory state" )); @@ -570,8 +560,7 @@ pub async fn encrypt( org_id: Option<&str>, ) -> anyhow::Result<()> { let state = state.lock().await; - let Some(keys) = state.key(org_id) - else { + let Some(keys) = state.key(org_id) else { return Err(anyhow::anyhow!( "failed to find encryption keys in in-memory state" )); diff --git a/src/bin/rbw/commands.rs b/src/bin/rbw/commands.rs index 4ea3529..aa7f5b9 100644 --- a/src/bin/rbw/commands.rs +++ b/src/bin/rbw/commands.rs @@ -671,12 +671,17 @@ impl std::convert::TryFrom<&String> for ListField { } } -const HELP: &str = r#" +const HELP_PW: &str = r#" # The first line of this file will be the password, and the remainder of the # file (after any blank lines after the password) will be stored as a note. # Lines with leading # will be ignored. "#; +const HELP_NOTES: &str = r#" +# The content of this file will be stored as a note. +# Lines with leading # will be ignored. +"#; + pub fn config_show() -> anyhow::Result<()> { let config = rbw::config::Config::load()?; serde_json::to_writer_pretty(std::io::stdout(), &config) @@ -939,7 +944,7 @@ pub fn add( .map(|username| crate::actions::encrypt(username, None)) .transpose()?; - let contents = rbw::edit::edit("", HELP)?; + let contents = rbw::edit::edit("", HELP_PW)?; let (password, notes) = parse_editor(&contents); let password = password @@ -1140,7 +1145,7 @@ pub fn edit( contents.push_str(&format!("\n{notes}\n")); } - let contents = rbw::edit::edit(&contents, HELP)?; + let contents = rbw::edit::edit(&contents, HELP_NOTES)?; let (password, notes) = parse_editor(&contents); let password = password @@ -1188,6 +1193,27 @@ pub fn edit( }; (data, notes, history) } + DecryptedData::SecureNote {} => + { + let data = rbw::db::EntryData::SecureNote {}; + + let editor_content = match decrypted.notes { + Some(notes) => format!("{notes}\n"), + None => format!("\n"), + }; + let contents = rbw::edit::edit(&editor_content, HELP_NOTES)?; + + // prepend blank line to be parsed as pw by `parse_editor` + let (_, notes) = parse_editor(&format!("\n{contents}\n")); + + let notes = notes + .map(|notes| { + crate::actions::encrypt(¬es, entry.org_id.as_deref()) + }) + .transpose()?; + + (data, notes, entry.history) + } _ => { return Err(anyhow::anyhow!( "modifications are only supported for login entries" diff --git a/src/edit.rs b/src/edit.rs index aa8c7b1..cb59366 100644 --- a/src/edit.rs +++ b/src/edit.rs @@ -3,6 +3,14 @@ use crate::prelude::*; use std::io::{Read as _, Write as _}; pub fn edit(contents: &str, help: &str) -> Result<String> { + if ! atty::is(atty::Stream::Stdin) { + // directly read from piped content + return match std::io::read_to_string(std::io::stdin()) { + Err(e) => Err(Error::FailedToReadFromStdin{ err: e }), + Ok(res) => Ok(res), + }; + } + let mut var = "VISUAL"; let editor = std::env::var_os(var).unwrap_or_else(|| { var = "EDITOR"; @@ -30,7 +38,7 @@ pub fn edit(contents: &str, help: &str) -> Result<String> { let editor = std::path::Path::new(&editor); let mut editor_args = vec![]; - #[allow(clippy::single_match)] // more to come + #[allow(clippy::single_match_else)] // more to come match editor.file_name() { Some(editor) => match editor.to_str() { Some("vim" | "nvim") => { diff --git a/src/error.rs b/src/error.rs index 31edaf3..1ffbcdd 100644 --- a/src/error.rs +++ b/src/error.rs @@ -21,6 +21,11 @@ pub enum Error { #[error("failed to parse pinentry output ({out:?})")] FailedToParsePinentry { out: String }, + #[error("failed to read from stdin: {err}")] + FailedToReadFromStdin { + err: std::io::Error, + }, + #[error( "failed to run editor {}: {err}", .editor.to_string_lossy(), |