From 290b0a9a511e9dfae91ebc0ffee12203c5a6cda5 Mon Sep 17 00:00:00 2001 From: Dave Tucker Date: Wed, 24 Nov 2021 15:35:24 +0000 Subject: [PATCH] Add YubiKey Auth Support This commit adds support for YubiKey auth. A pinentry prompt asks the user to touch the Yubikey, which causes it to produce input (like a keyboard). That can then be sent to the Bitwarden API where it can ID the YubiKey and validate the request. Fixes: #7 Signed-off-by: Dave Tucker --- src/bin/rbw-agent/actions.rs | 95 +++++++++++++++++++++++++++++++++++- 1 file changed, 93 insertions(+), 2 deletions(-) diff --git a/src/bin/rbw-agent/actions.rs b/src/bin/rbw-agent/actions.rs index 1cc71c3..2eb0bc4 100644 --- a/src/bin/rbw-agent/actions.rs +++ b/src/bin/rbw-agent/actions.rs @@ -172,8 +172,36 @@ pub async fn login( ) .await?; break; - } else { - return Err(anyhow::anyhow!("TODO")); + } else if providers.contains( + &rbw::api::TwoFactorProviderType::Yubikey, + ){ + let ( + access_token, + refresh_token, + iterations, + protected_key, + ) = yubikey_auth( + tty, + &email, + password.clone(), + ) + .await?; + login_success( + sock, + state, + access_token, + refresh_token, + iterations, + protected_key, + password, + db, + email, + ) + .await?; + break; + } + else { + return Err(anyhow::anyhow!("no supported 2fa method found in {:?}", providers)); } } Err(rbw::error::Error::IncorrectPassword { message }) => { @@ -277,6 +305,69 @@ async fn two_factor( unreachable!() } +async fn yubikey_auth( + tty: Option<&str>, + email: &str, + password: rbw::locked::Password, +) -> anyhow::Result<(String, String, u32, String)> { + let mut err_msg = None; + for i in 1_u8..=3 { + let err = if i > 1 { + // this unwrap is safe because we only ever continue the loop if + // we have set err_msg + Some(format!("{} (attempt {}/3)", err_msg.unwrap(), i)) + } else { + None + }; + let code = rbw::pinentry::getpin( + &config_pinentry().await?, + "Authenticator App", + "Touch your Security Key", + err.as_deref(), + tty, + true, + ) + .await + .context("failed to read code from pinentry")?; + let code = std::str::from_utf8(code.password()) + .context("code was not valid utf8")?; + match rbw::actions::login( + email, + password.clone(), + Some(code), + Some(rbw::api::TwoFactorProviderType::Yubikey), + ) + .await + { + Ok((access_token, refresh_token, iterations, protected_key)) => { + return Ok(( + access_token, + refresh_token, + iterations, + protected_key, + )) + } + Err(rbw::error::Error::IncorrectPassword { message }) => { + if i == 3 { + return Err(rbw::error::Error::IncorrectPassword { + message, + }) + .context("failed to log in to bitwarden instance"); + } else { + err_msg = Some(message); + continue; + } + } + Err(e) => { + return Err(e) + .context("failed to log in to bitwarden instance") + } + } + } + + unreachable!() +} + async fn login_success( sock: &mut crate::sock::Sock, state: std::sync::Arc>,