From 56e9b720bade5dcdc6505952c1bf81bdeca26bcd Mon Sep 17 00:00:00 2001 From: Jesse Luehrs Date: Sat, 25 Jul 2020 03:53:02 -0400 Subject: also display the totp code --- src/bin/rbw/commands.rs | 53 +++++++++++++++++++++++++++++++++++++++++++++++++ src/bin/rbw/main.rs | 14 +++++++++++++ 2 files changed, 67 insertions(+) (limited to 'src') diff --git a/src/bin/rbw/commands.rs b/src/bin/rbw/commands.rs index 3180cc9..e5858b6 100644 --- a/src/bin/rbw/commands.rs +++ b/src/bin/rbw/commands.rs @@ -545,6 +545,40 @@ pub fn get( Ok(()) } +pub fn code( + name: &str, + user: Option<&str>, + folder: Option<&str>, +) -> anyhow::Result<()> { + unlock()?; + + let db = load_db()?; + + let desc = format!( + "{}{}", + user.map(|s| format!("{}@", s)) + .unwrap_or_else(|| "".to_string()), + name + ); + + let (_, decrypted) = find_entry(&db, name, user, folder) + .with_context(|| format!("couldn't find entry for '{}'", desc))?; + + if let DecryptedData::Login { totp, .. } = decrypted.data { + if let Some(totp) = totp { + println!("{}", generate_totp(&totp)?) + } else { + return Err(anyhow::anyhow!( + "entry does not contain a totp secret" + )); + } + } else { + return Err(anyhow::anyhow!("not a login entry")); + } + + Ok(()) +} + pub fn add( name: &str, username: Option<&str>, @@ -1392,6 +1426,25 @@ fn remove_db() -> anyhow::Result<()> { } } +fn generate_totp(secret: &str) -> anyhow::Result { + Ok(format!( + "{}", + oath::totp_raw_now( + &base32::decode( + base32::Alphabet::RFC4648 { padding: false }, + secret + ) + .ok_or_else(|| anyhow::anyhow!( + "totp secret was not valid base32" + ))?, + 6, + 0, + 30, + &oath::HashType::SHA1, + ) + )) +} + #[cfg(test)] mod test { use super::*; diff --git a/src/bin/rbw/main.rs b/src/bin/rbw/main.rs index 258fd04..f1903e7 100644 --- a/src/bin/rbw/main.rs +++ b/src/bin/rbw/main.rs @@ -56,6 +56,16 @@ enum Opt { full: bool, }, + #[structopt(about = "Display the authenticator code for a given entry")] + Code { + #[structopt(help = "Name or UUID of the entry to display")] + name: String, + #[structopt(help = "Username of the entry to display")] + user: Option, + #[structopt(long, help = "Folder name to search in")] + folder: Option, + }, + #[structopt( about = "Add a new password to the database", long_about = "Add a new password to the database\n\n\ @@ -200,6 +210,7 @@ impl Opt { Self::Sync => "sync".to_string(), Self::List { .. } => "list".to_string(), Self::Get { .. } => "get".to_string(), + Self::Code { .. } => "code".to_string(), Self::Add { .. } => "add".to_string(), Self::Generate { .. } => "generate".to_string(), Self::Edit { .. } => "edit".to_string(), @@ -272,6 +283,9 @@ fn main(opt: Opt) { folder, full, } => commands::get(&name, user.as_deref(), folder.as_deref(), *full), + Opt::Code { name, user, folder } => { + commands::code(&name, user.as_deref(), folder.as_deref()) + } Opt::Add { name, user, -- cgit v1.2.3-54-g00ecf