diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/actions.rs | 14 | ||||
-rw-r--r-- | src/api.rs | 10 | ||||
-rw-r--r-- | src/bin/rbw-agent/actions.rs | 44 | ||||
-rw-r--r-- | src/db.rs | 4 | ||||
-rw-r--r-- | src/error.rs | 7 | ||||
-rw-r--r-- | src/identity.rs | 48 |
6 files changed, 110 insertions, 17 deletions
diff --git a/src/actions.rs b/src/actions.rs index dc78f7e..2c57405 100644 --- a/src/actions.rs +++ b/src/actions.rs @@ -18,11 +18,12 @@ pub async fn login( password: crate::locked::Password, two_factor_token: Option<&str>, two_factor_provider: Option<crate::api::TwoFactorProviderType>, -) -> Result<(String, String, u32, String)> { +) -> Result<(String, String, u32, u32, Option<u32>, Option<u32>, String)> { let (client, config) = api_client_async().await?; - let iterations = client.prelogin(email).await?; + let (kdf, iterations, memory, parallelism) = client.prelogin(email).await?; + let identity = - crate::identity::Identity::new(email, &password, iterations)?; + crate::identity::Identity::new(email, &password, kdf, iterations, memory, parallelism)?; let (access_token, refresh_token, protected_key) = client .login( email, @@ -33,13 +34,16 @@ pub async fn login( ) .await?; - Ok((access_token, refresh_token, iterations, protected_key)) + Ok((access_token, refresh_token, kdf, iterations, memory, parallelism, protected_key)) } pub fn unlock<S: std::hash::BuildHasher>( email: &str, password: &crate::locked::Password, + kdf: u32, iterations: u32, + memory: Option<u32>, + parallelism: Option<u32>, protected_key: &str, protected_private_key: &str, protected_org_keys: &std::collections::HashMap<String, String, S>, @@ -48,7 +52,7 @@ pub fn unlock<S: std::hash::BuildHasher>( std::collections::HashMap<String, crate::locked::Keys>, )> { let identity = - crate::identity::Identity::new(email, password, iterations)?; + crate::identity::Identity::new(email, password, kdf, iterations, memory, parallelism)?; let protected_key = crate::cipherstring::CipherString::new(protected_key)?; @@ -169,8 +169,14 @@ struct PreloginReq { #[derive(serde::Deserialize, Debug)] struct PreloginRes { + #[serde(rename = "Kdf", alias = "kdf")] + kdf: u32, #[serde(rename = "KdfIterations", alias = "kdfIterations")] kdf_iterations: u32, + #[serde(rename = "KdfMemory", alias = "kdfMemory")] + kdf_memory: Option<u32>, + #[serde(rename = "KdfParallelism", alias = "kdfParallelism")] + kdf_parallelism: Option<u32>, } #[derive(serde::Serialize, Debug)] @@ -628,7 +634,7 @@ impl Client { } } - pub async fn prelogin(&self, email: &str) -> Result<u32> { + pub async fn prelogin(&self, email: &str) -> Result<(u32, u32, Option<u32>, Option<u32>)> { let prelogin = PreloginReq { email: email.to_string(), }; @@ -640,7 +646,7 @@ impl Client { .await .map_err(|source| Error::Reqwest { source })?; let prelogin_res: PreloginRes = res.json_with_path().await?; - Ok(prelogin_res.kdf_iterations) + Ok((prelogin_res.kdf, prelogin_res.kdf_iterations, prelogin_res.kdf_memory, prelogin_res.kdf_parallelism)) } pub async fn register( diff --git a/src/bin/rbw-agent/actions.rs b/src/bin/rbw-agent/actions.rs index 88236ba..4b3267f 100644 --- a/src/bin/rbw-agent/actions.rs +++ b/src/bin/rbw-agent/actions.rs @@ -123,7 +123,10 @@ pub async fn login( Ok(( access_token, refresh_token, + kdf, iterations, + memory, + parallelism, protected_key, )) => { login_success( @@ -131,7 +134,10 @@ pub async fn login( state, access_token, refresh_token, + kdf, iterations, + memory, + parallelism, protected_key, password, db, @@ -151,7 +157,10 @@ pub async fn login( let ( access_token, refresh_token, + kdf, iterations, + memory, + parallelism, protected_key, ) = two_factor( tty, @@ -165,7 +174,10 @@ pub async fn login( state, access_token, refresh_token, + kdf, iterations, + memory, + parallelism, protected_key, password, db, @@ -205,7 +217,7 @@ async fn two_factor( email: &str, password: rbw::locked::Password, provider: rbw::api::TwoFactorProviderType, -) -> anyhow::Result<(String, String, u32, String)> { +) -> anyhow::Result<(String, String, u32, u32, Option<u32>, Option<u32>, String)> { let mut err_msg = None; for i in 1_u8..=3 { let err = if i > 1 { @@ -235,11 +247,14 @@ async fn two_factor( ) .await { - Ok((access_token, refresh_token, iterations, protected_key)) => { + Ok((access_token, refresh_token, kdf, iterations, memory, parallelism, protected_key)) => { return Ok(( access_token, refresh_token, + kdf, iterations, + memory, + parallelism, protected_key, )) } @@ -280,7 +295,10 @@ async fn login_success( state: std::sync::Arc<tokio::sync::RwLock<crate::agent::State>>, access_token: String, refresh_token: String, + kdf: u32, iterations: u32, + memory: Option<u32>, + parallelism: Option<u32>, protected_key: String, password: rbw::locked::Password, mut db: rbw::db::Db, @@ -288,7 +306,10 @@ async fn login_success( ) -> anyhow::Result<()> { db.access_token = Some(access_token.to_string()); db.refresh_token = Some(refresh_token.to_string()); + db.kdf = Some(kdf); db.iterations = Some(iterations); + db.memory = memory; + db.parallelism = parallelism; db.protected_key = Some(protected_key.to_string()); save_db(&db).await?; @@ -305,7 +326,10 @@ async fn login_success( let res = rbw::actions::unlock( &email, &password, + kdf, iterations, + memory, + parallelism, &protected_key, &protected_private_key, &db.protected_org_keys, @@ -331,12 +355,23 @@ pub async fn unlock( if state.read().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(iterations) = db.iterations else { return Err(anyhow::anyhow!( - "failed to find number of iterations in db" + "failed to find iterations in db" )); }; + + let memory= db.memory; + let parallelism = db.parallelism; + let Some(protected_key) = db.protected_key else { return Err(anyhow::anyhow!( @@ -377,7 +412,10 @@ pub async fn unlock( match rbw::actions::unlock( &email, &password, + kdf, iterations, + memory, + parallelism, &protected_key, &protected_private_key, &db.protected_org_keys, @@ -164,7 +164,10 @@ pub struct Db { pub access_token: Option<String>, pub refresh_token: Option<String>, + pub kdf: Option<u32>, pub iterations: Option<u32>, + pub memory: Option<u32>, + pub parallelism: Option<u32>, pub protected_key: Option<String>, pub protected_private_key: Option<String>, pub protected_org_keys: std::collections::HashMap<String, String>, @@ -293,6 +296,7 @@ impl Db { self.access_token.is_none() || self.refresh_token.is_none() || self.iterations.is_none() + || self.kdf.is_none() || self.protected_key.is_none() } } diff --git a/src/error.rs b/src/error.rs index 6d49e09..9b7261f 100644 --- a/src/error.rs +++ b/src/error.rs @@ -142,6 +142,9 @@ pub enum Error { #[error("failed to run pbkdf2")] Pbkdf2, + #[error("failed to run argon2")] + Argon2, + #[error("pinentry cancelled")] PinentryCancelled, @@ -224,6 +227,10 @@ pub enum Error { #[error("error writing to pinentry stdin")] WriteStdin { source: tokio::io::Error }, + + + #[error("invalid kdf type: {ty}")] + InvalidKdfType { ty: String }, } pub type Result<T> = std::result::Result<T, Error>; diff --git a/src/identity.rs b/src/identity.rs index 8a5dc61..d63dc9b 100644 --- a/src/identity.rs +++ b/src/identity.rs @@ -1,4 +1,7 @@ use crate::prelude::*; +use sha2::Digest; +extern crate argon2; +use argon2::{Config, ThreadMode, Variant, Version}; pub struct Identity { pub email: String, @@ -10,7 +13,10 @@ impl Identity { pub fn new( email: &str, password: &crate::locked::Password, + kdf: u32, iterations: u32, + memory: Option<u32>, + parallelism: Option<u32>, ) -> Result<Self> { let iterations = std::num::NonZeroU32::new(iterations) .ok_or(Error::Pbkdf2ZeroIterations)?; @@ -19,14 +25,42 @@ impl Identity { keys.extend(std::iter::repeat(0).take(64)); let enc_key = &mut keys.data_mut()[0..32]; - pbkdf2::pbkdf2::<hmac::Hmac<sha2::Sha256>>( - password.password(), - email.as_bytes(), - iterations.get(), - enc_key, - ) - .map_err(|_| Error::Pbkdf2)?; + match kdf { + 0 => { + pbkdf2::pbkdf2::<hmac::Hmac<sha2::Sha256>>( + password.password(), + email.as_bytes(), + iterations.get(), + enc_key, + ) + .map_err(|_| Error::Pbkdf2)?; + } + + 1 => { + let mut hasher = sha2::Sha256::new(); + hasher.update(email.as_bytes()); + let salt = hasher.finalize(); + + let config = Config { + variant: Variant::Argon2id, + version: Version::Version13, + mem_cost: memory.unwrap() * 1024, + time_cost: iterations.get(), + lanes: parallelism.unwrap(), + thread_mode: ThreadMode::Parallel, + secret: &[], + ad: &[], + hash_length: 32 + }; + let hash = argon2::hash_raw(password.password(), &salt[..], &config).map_err(|_| Error::Argon2)?; + enc_key.copy_from_slice(&hash); + } + _ => { + // todo throw error or switch to enum? + } + }; + let mut hash = crate::locked::Vec::new(); hash.extend(std::iter::repeat(0).take(32)); pbkdf2::pbkdf2::<hmac::Hmac<sha2::Sha256>>( |