From 9645a4636f6f4b04f4e6aba84e3c77fa0f2f6961 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 15 Mar 2023 16:40:49 +0100 Subject: Implement argon2 kdf --- src/identity.rs | 48 +++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 41 insertions(+), 7 deletions(-) (limited to 'src/identity.rs') 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, + parallelism: Option, ) -> Result { 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::>( - password.password(), - email.as_bytes(), - iterations.get(), - enc_key, - ) - .map_err(|_| Error::Pbkdf2)?; + match kdf { + 0 => { + pbkdf2::pbkdf2::>( + 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::>( -- cgit v1.2.3-54-g00ecf From 22f7344befc026666b48e3153c4dfe4175f052ee Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 15 Mar 2023 22:38:44 +0100 Subject: Switch kdf type to enum --- src/actions.rs | 6 +-- src/api.rs | 95 +++++++++++++++++++++++++++++++++++++++++++- src/bin/rbw-agent/actions.rs | 5 ++- src/db.rs | 4 +- src/identity.rs | 11 ++--- 5 files changed, 105 insertions(+), 16 deletions(-) (limited to 'src/identity.rs') diff --git a/src/actions.rs b/src/actions.rs index 2c57405..7212415 100644 --- a/src/actions.rs +++ b/src/actions.rs @@ -1,4 +1,4 @@ -use crate::prelude::*; +use crate::{prelude::*, api::KdfType}; pub async fn register( email: &str, @@ -18,7 +18,7 @@ pub async fn login( password: crate::locked::Password, two_factor_token: Option<&str>, two_factor_provider: Option, -) -> Result<(String, String, u32, u32, Option, Option, String)> { +) -> Result<(String, String, KdfType, u32, Option, Option, String)> { let (client, config) = api_client_async().await?; let (kdf, iterations, memory, parallelism) = client.prelogin(email).await?; @@ -40,7 +40,7 @@ pub async fn login( pub fn unlock( email: &str, password: &crate::locked::Password, - kdf: u32, + kdf: KdfType, iterations: u32, memory: Option, parallelism: Option, diff --git a/src/api.rs b/src/api.rs index 6c8545c..85a0ce5 100644 --- a/src/api.rs +++ b/src/api.rs @@ -170,7 +170,7 @@ struct PreloginReq { #[derive(serde::Deserialize, Debug)] struct PreloginRes { #[serde(rename = "Kdf", alias = "kdf")] - kdf: u32, + kdf: KdfType, #[serde(rename = "KdfIterations", alias = "kdfIterations")] kdf_iterations: u32, #[serde(rename = "KdfMemory", alias = "kdfMemory")] @@ -634,7 +634,7 @@ impl Client { } } - pub async fn prelogin(&self, email: &str) -> Result<(u32, u32, Option, Option)> { + pub async fn prelogin(&self, email: &str) -> Result<(KdfType, u32, Option, Option)> { let prelogin = PreloginReq { email: email.to_string(), }; @@ -1218,3 +1218,94 @@ fn classify_login_error(error_res: &ConnectErrorRes, code: u16) -> Error { log::warn!("unexpected error received during login: {:?}", error_res); Error::RequestFailed { status: code } } + + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum KdfType { + Pbkdf2 = 0, + Argon2id = 1, +} + +impl<'de> serde::Deserialize<'de> for KdfType { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct KdfTypeVisitor; + impl<'de> serde::de::Visitor<'de> for KdfTypeVisitor { + type Value = KdfType; + + fn expecting( + &self, + formatter: &mut std::fmt::Formatter, + ) -> std::fmt::Result { + formatter.write_str("two factor provider id") + } + + fn visit_str( + self, + value: &str, + ) -> std::result::Result + where + E: serde::de::Error, + { + value.parse().map_err(serde::de::Error::custom) + } + + fn visit_u64( + self, + value: u64, + ) -> std::result::Result + where + E: serde::de::Error, + { + std::convert::TryFrom::try_from(value) + .map_err(serde::de::Error::custom) + } + } + + deserializer.deserialize_any(KdfTypeVisitor) + } +} + +impl std::convert::TryFrom for KdfType { + type Error = Error; + + fn try_from(ty: u64) -> Result { + match ty { + 0 => Ok(Self::Pbkdf2), + 1 => Ok(Self::Argon2id), + _ => Err(Error::InvalidTwoFactorProvider { + ty: format!("{ty}"), + }), + } + } +} + +impl std::str::FromStr for KdfType { + type Err = Error; + + fn from_str(ty: &str) -> Result { + match ty { + "0" => Ok(Self::Pbkdf2), + "1" => Ok(Self::Argon2id), + _ => Err(Error::InvalidTwoFactorProvider { ty: ty.to_string() }), + } + } +} + +impl serde::Serialize for KdfType { + fn serialize( + &self, + serializer: S, + ) -> std::result::Result + where + S: serde::Serializer, + { + let s = match self { + Self::Pbkdf2 => "0", + Self::Argon2id => "1", + }; + serializer.serialize_str(s) + } +} diff --git a/src/bin/rbw-agent/actions.rs b/src/bin/rbw-agent/actions.rs index 4b3267f..56a98e9 100644 --- a/src/bin/rbw-agent/actions.rs +++ b/src/bin/rbw-agent/actions.rs @@ -1,4 +1,5 @@ use anyhow::Context as _; +use rbw::api::KdfType; pub async fn register( sock: &mut crate::sock::Sock, @@ -217,7 +218,7 @@ async fn two_factor( email: &str, password: rbw::locked::Password, provider: rbw::api::TwoFactorProviderType, -) -> anyhow::Result<(String, String, u32, u32, Option, Option, String)> { +) -> anyhow::Result<(String, String, KdfType, u32, Option, Option, String)> { let mut err_msg = None; for i in 1_u8..=3 { let err = if i > 1 { @@ -295,7 +296,7 @@ async fn login_success( state: std::sync::Arc>, access_token: String, refresh_token: String, - kdf: u32, + kdf: KdfType, iterations: u32, memory: Option, parallelism: Option, diff --git a/src/db.rs b/src/db.rs index 8781459..74be28a 100644 --- a/src/db.rs +++ b/src/db.rs @@ -1,4 +1,4 @@ -use crate::prelude::*; +use crate::{prelude::*, api::KdfType}; use std::io::{Read as _, Write as _}; use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _}; @@ -164,7 +164,7 @@ pub struct Db { pub access_token: Option, pub refresh_token: Option, - pub kdf: Option, + pub kdf: Option, pub iterations: Option, pub memory: Option, pub parallelism: Option, diff --git a/src/identity.rs b/src/identity.rs index d63dc9b..3637d75 100644 --- a/src/identity.rs +++ b/src/identity.rs @@ -1,4 +1,4 @@ -use crate::prelude::*; +use crate::{prelude::*, api::KdfType}; use sha2::Digest; extern crate argon2; use argon2::{Config, ThreadMode, Variant, Version}; @@ -13,7 +13,7 @@ impl Identity { pub fn new( email: &str, password: &crate::locked::Password, - kdf: u32, + kdf: KdfType, iterations: u32, memory: Option, parallelism: Option, @@ -27,7 +27,7 @@ impl Identity { let enc_key = &mut keys.data_mut()[0..32]; match kdf { - 0 => { + KdfType::Pbkdf2 => { pbkdf2::pbkdf2::>( password.password(), email.as_bytes(), @@ -37,7 +37,7 @@ impl Identity { .map_err(|_| Error::Pbkdf2)?; } - 1 => { + KdfType::Argon2id => { let mut hasher = sha2::Sha256::new(); hasher.update(email.as_bytes()); let salt = hasher.finalize(); @@ -56,9 +56,6 @@ impl Identity { 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(); -- cgit v1.2.3-54-g00ecf From 7b57f928e5fb345e02f6101d07d5cbbc7e540458 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Sun, 26 Mar 2023 04:20:00 +0200 Subject: Switch argon2 implementation to rustcrypto --- Cargo.lock | 79 ++++++++++++++++++++------------------------------------- Cargo.toml | 2 +- src/identity.rs | 30 ++++++++++------------ 3 files changed, 41 insertions(+), 70 deletions(-) (limited to 'src/identity.rs') diff --git a/Cargo.lock b/Cargo.lock index a6afafc..92a042c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -40,10 +40,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" [[package]] -name = "arrayref" -version = "0.3.6" +name = "argon2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" +checksum = "95c2fcf79ad1932ac6269a738109997a83c227c09b75842ae564dc8ede6a861c" +dependencies = [ + "base64ct", + "blake2", + "password-hash", +] [[package]] name = "arrayvec" @@ -74,12 +79,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa" -[[package]] -name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - [[package]] name = "base64" version = "0.21.0" @@ -99,14 +98,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] -name = "blake2b_simd" -version = "1.0.1" +name = "blake2" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c2f0dc9a68c6317d884f97cc36cf5a3d20ba14ce404227df55e1af708ab04bc" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ - "arrayref", - "arrayvec", - "constant_time_eq 0.2.5", + "digest", ] [[package]] @@ -229,18 +226,6 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913" -[[package]] -name = "constant_time_eq" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" - -[[package]] -name = "constant_time_eq" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13418e745008f7349ec7e449155f419a61b92b58a99cc3616942b926825ec76b" - [[package]] name = "core-foundation" version = "0.9.3" @@ -266,15 +251,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crossbeam-utils" -version = "0.8.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" -dependencies = [ - "cfg-if", -] - [[package]] name = "crypto-common" version = "0.1.6" @@ -900,6 +876,17 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core", + "subtle", +] + [[package]] name = "pbkdf2" version = "0.12.1" @@ -1043,10 +1030,11 @@ version = "1.6.0" dependencies = [ "aes", "anyhow", + "argon2", "arrayvec", "async-trait", "base32", - "base64 0.21.0", + "base64", "block-padding", "cbc", "clap", @@ -1067,7 +1055,6 @@ dependencies = [ "region", "reqwest", "rsa", - "rust-argon2", "serde", "serde_json", "serde_path_to_error", @@ -1140,7 +1127,7 @@ version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21eed90ec8570952d53b772ecf8f206aa1ec9a3d76b2521c56c42973f2d91ee9" dependencies = [ - "base64 0.21.0", + "base64", "bytes", "encoding_rs", "futures-core", @@ -1208,18 +1195,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "rust-argon2" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b50162d19404029c1ceca6f6980fe40d45c8b369f6f44446fa14bb39573b5bb9" -dependencies = [ - "base64 0.13.1", - "blake2b_simd", - "constant_time_eq 0.1.5", - "crossbeam-utils", -] - [[package]] name = "rustix" version = "0.36.9" @@ -1264,7 +1239,7 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" dependencies = [ - "base64 0.21.0", + "base64", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 19d11c5..ae6d641 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ include = ["src/**/*", "bin/**/*", "LICENSE", "README.md", "CHANGELOG.md"] [dependencies] aes = "0.8.2" anyhow = "1.0.69" +argon2 = "0.5.0" arrayvec = "0.7.2" async-trait = "0.1.66" base32 = "0.4.0" @@ -39,7 +40,6 @@ rand = "0.8.5" region = "3.0.0" reqwest = { version = "0.11.14", default-features = false, features = ["blocking", "json", "rustls-tls-native-roots"] } rsa = "0.8.2" -rust-argon2 = "1.0.0" serde = { version = "1.0.154", features = ["derive"] } serde_json = "1.0.94" serde_path_to_error = "0.1.10" diff --git a/src/identity.rs b/src/identity.rs index 3637d75..9bc435f 100644 --- a/src/identity.rs +++ b/src/identity.rs @@ -1,8 +1,6 @@ use crate::{prelude::*, api::KdfType}; use sha2::Digest; -extern crate argon2; -use argon2::{Config, ThreadMode, Variant, Version}; - +use argon2::Argon2; pub struct Identity { pub email: String, pub keys: crate::locked::Keys, @@ -40,21 +38,19 @@ impl Identity { KdfType::Argon2id => { let mut hasher = sha2::Sha256::new(); hasher.update(email.as_bytes()); - let salt = hasher.finalize(); + let mut 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); + let mut output_key_material = [0u8]; + let argon2_config = Argon2::new( + argon2::Algorithm::Argon2id, + argon2::Version::V0x13, + argon2::Params::new(memory.unwrap() * 1024, + iterations.get(), + parallelism.unwrap(), + Some(32)).unwrap()); + argon2::Argon2::hash_password_into(&argon2_config, password.password(), &mut salt, &mut output_key_material) + .map_err(|_| Error::Argon2)?; + enc_key.copy_from_slice(&output_key_material); } }; -- cgit v1.2.3-54-g00ecf