diff --git a/Cargo.lock b/Cargo.lock index 6bd2778..78e1a13 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1198,6 +1198,7 @@ dependencies = [ "serde", "serde_json", "thiserror", + "tracing", ] [[package]] @@ -1209,6 +1210,7 @@ dependencies = [ "cbc", "data-encoding", "dotenv", + "hex", "hmac 0.12.1", "itertools", "json", diff --git a/cli/src/main.rs b/cli/src/main.rs index dd205fe..e5e7c36 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -39,6 +39,7 @@ enum Config { #[structopt(short, long, help = "output type", default_value)] output: Output, }, + DumpSeeds, } pub fn setup_error_handlers() -> Result<()> { @@ -135,6 +136,22 @@ async fn work() -> Result<()> { } output.print(output_data)?; } + Config::DumpSeeds => { + let client = get_saved_client()?; + let mut services: Vec = match mambembe_keyring::get() { + Ok(services) => services, + Err(MambembeKeyringError::NoPasswordFound) => { + let services = client.list_authenticator_tokens().await?; + mambembe_keyring::set(&services).unwrap(); + services + } + Err(err) => return Err(err.into()), + }; + for service in services.iter_mut() { + client.initialize_authenticator_token(service)?; + println!("Servie: {} Seed: {}", service.name, &service.dump_seed()?); + } + } } // client.check_current_device().await?; diff --git a/keyring/Cargo.toml b/keyring/Cargo.toml index d288f67..0b86a8c 100644 --- a/keyring/Cargo.toml +++ b/keyring/Cargo.toml @@ -13,6 +13,7 @@ mambembe-lib = { path = "../lib" } serde = "1.0.193" serde_json = "1.0.108" thiserror = "1.0.51" +tracing = "0.1.26" [features] with-keyring = ["keyring"] diff --git a/keyring/src/lib.rs b/keyring/src/lib.rs index d018675..56207fe 100644 --- a/keyring/src/lib.rs +++ b/keyring/src/lib.rs @@ -10,6 +10,7 @@ use mambembe_lib::{models::AuthenticatorToken, AuthyClient}; use serde::{de::DeserializeOwned, Serialize}; use serde_json::{from_str, to_string_pretty}; use thiserror::Error; +use tracing::instrument; #[cfg(feature = "without-keyring")] use crate::local::{Keyring, KeyringError}; @@ -37,17 +38,20 @@ pub trait Data { } impl Data for AuthyClient { + #[instrument] fn get_keyring() -> &'static Keyring { &DEVICES } } impl Data for Vec { + #[instrument] fn get_keyring() -> &'static Keyring { &TOKENS } } +#[instrument] pub fn get() -> Result where T: DeserializeOwned + Data, @@ -60,6 +64,7 @@ where Ok(from_str(&data)?) } +#[instrument(skip(data))] pub fn set(data: &T) -> Result<()> where T: Serialize + Data, diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 701ef1b..fffc6ae 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -10,6 +10,7 @@ aes = "0.8.3" async-trait = "0.1.75" cbc = { version = "0.1.2", features = ["block-padding", "alloc"] } data-encoding = "2.5.0" +hex = "0.4.3" hmac = "0.12.1" itertools = "0.12.0" json = "0.12.4" diff --git a/lib/src/client.rs b/lib/src/client.rs index c635746..2dd76a9 100644 --- a/lib/src/client.rs +++ b/lib/src/client.rs @@ -1,4 +1,4 @@ -use std::fs; +use std::{fmt::Write, fs}; use async_trait::async_trait; use rand::{thread_rng, Rng}; @@ -49,11 +49,11 @@ impl TimeSync { TimeSync::Future { last_time_checked: _, time_offset, - } => (time + time_offset), + } => time + time_offset, TimeSync::Past { last_time_checked: _, time_offset, - } => (time - time_offset), + } => time - time_offset, } } } @@ -108,10 +108,10 @@ impl AuthyClient { pub fn with_url(url: &str, device_name: &str, backup_password: &str) -> Result { let mut signature = [0u8; 32]; thread_rng().fill(&mut signature); - let signature = signature - .iter() - .map(|n| format!("{:x}", n)) - .collect::(); + let signature = signature.iter().fold(String::new(), |mut output, n| { + let _ = write!(output, "{:x}", n); + output + }); Ok(Self { url: url.parse()?, @@ -236,7 +236,7 @@ impl AuthyClientApi for AuthyClient { #[instrument] async fn complete_registration(&mut self, pin: &str) -> Result<()> { // I'm assuming this is used for idempotency so this should suffice - let uuid = format!("{:x}", md5::compute(&pin.as_bytes())); + let uuid = format!("{:x}", md5::compute(pin.as_bytes())); let payload = AuthyCompleteRegistrationRequest { api_key: API_KEY.to_string(), locale: DEFAULT_LOCALE.to_string(), diff --git a/lib/src/models.rs b/lib/src/models.rs index 2054ae7..8554451 100644 --- a/lib/src/models.rs +++ b/lib/src/models.rs @@ -1,10 +1,14 @@ -use data_encoding::HEXLOWER; +use data_encoding::{BASE32, HEXLOWER}; use serde::{Deserialize, Serialize}; use sha2::Digest; use crate::{ - client::TimeSync, crypto::decrypt_data, error::Result, password::derive_key, - tokens::calculate_future_tokens, MambembeError, + client::TimeSync, + crypto::decrypt_data, + error::Result, + password::derive_key, + tokens::{calculate_future_tokens, decode_seed}, + MambembeError, }; pub type Pin = String; @@ -36,7 +40,7 @@ pub enum CheckRegistrationStatus { impl Device { pub(crate) fn hash_secret(&self) -> String { - format!("{:x}", sha2::Sha256::digest(&self.secret_seed.as_bytes())) + format!("{:x}", sha2::Sha256::digest(self.secret_seed.as_bytes())) } } @@ -89,6 +93,12 @@ impl AuthenticatorToken { Ok(data.to_ascii_uppercase()) } + + pub fn dump_seed(&self) -> Result { + self.decrypt_seed() + .map(decode_seed) + .map(|seed| BASE32.encode(&seed)) // Re-encode again because most of the clients expect this + } } #[cfg(test)] diff --git a/lib/src/tokens.rs b/lib/src/tokens.rs index 9901757..56c94a0 100644 --- a/lib/src/tokens.rs +++ b/lib/src/tokens.rs @@ -30,12 +30,22 @@ pub(crate) fn calculate_token( digits: usize, time_sync: Option<&TimeSync>, ) -> InternalResult { - let seed = BASE32_NOPAD.decode(seed)?; + let seed = decode_seed(seed); + // let seed = HEXLOWER.encode(&seed); let time = get_time(time_sync); let s = build_slauth_context(&seed, digits, time / OTHERS_DEFAULT_PERIOD); Ok(s.gen()) } +pub(crate) fn decode_seed(seed: T) -> Vec +where + T: AsRef<[u8]>, +{ + BASE32_NOPAD + .decode(seed.as_ref()) + .unwrap_or_else(|_| seed.as_ref().to_vec()) +} + #[tracing::instrument] pub(crate) fn build_slauth_context(seed: &[u8], digits: usize, padded_time: u64) -> HOTPContext { HOTPBuilder::new() diff --git a/stub_server/src/lib.rs b/stub_server/src/lib.rs index 8a4d586..8d3b7b0 100644 --- a/stub_server/src/lib.rs +++ b/stub_server/src/lib.rs @@ -36,7 +36,7 @@ impl WiremockRunner { check_process( &compose() - .args(&["up", "-d"]) + .args(["up", "-d"]) .output() .await .wrap_err("error to bring wiremock up")?, @@ -44,7 +44,7 @@ impl WiremockRunner { let host = check_process( &compose() - .args(&["port", "wiremock", "8080"]) + .args(["port", "wiremock", "8080"]) .output() .await .wrap_err("failed to get wiremock port")?,