diff --git a/Cargo.lock b/Cargo.lock index d70f297..1035a07 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -81,6 +81,18 @@ version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afddf7f520a80dbf76e6f50a35bca42a2331ef227a28b3b6dc5c2e2338d114b1" +[[package]] +name = "arrayref" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" + +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + [[package]] name = "ashpd" version = "0.1.0" @@ -265,11 +277,12 @@ dependencies = [ "pretty_env_logger", "qrcode", "quick-xml", + "rand 0.8.2", "ring", + "rust-argon2", "secret-service", "serde", "serde_json", - "sha2", "surf", "unicase", "url", @@ -306,6 +319,17 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +[[package]] +name = "blake2b_simd" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + [[package]] name = "block" version = "0.1.6" @@ -473,6 +497,12 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28b9d6de7f49e22cf97ad17fc4036ece69300032f45f78f30b4a4482cdc3f4a6" +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + [[package]] name = "cookie" version = "0.14.3" @@ -2407,6 +2437,18 @@ dependencies = [ "winapi", ] +[[package]] +name = "rust-argon2" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb" +dependencies = [ + "base64 0.13.0", + "blake2b_simd", + "constant_time_eq", + "crossbeam-utils", +] + [[package]] name = "rustc_version" version = "0.2.3" diff --git a/Cargo.toml b/Cargo.toml index 8eaf4f4..1c975ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,11 +25,12 @@ percent-encoding = "2.1" pretty_env_logger = "0.4" qrcode = {version = "0.12", features = ["image"]} quick-xml = "0.20" +rand = "0.8" ring = "0.16" +rust-argon2 = "0.8" secret-service = "2.0" serde = "1.0" serde_json = "1.0" -sha2 = "0.9" surf = "2.1" unicase = "2.6" url = "2.2" diff --git a/src/models/keyring.rs b/src/models/keyring.rs index b0abe13..0273263 100644 --- a/src/models/keyring.rs +++ b/src/models/keyring.rs @@ -1,6 +1,5 @@ use crate::config; use secret_service::{Collection, EncryptionType, Error, SecretService}; -use sha2::{Digest, Sha512}; use std::collections::HashMap; pub struct Keyring; @@ -20,6 +19,31 @@ fn password_attributes() -> HashMap<&'static str, &'static str> { attributes } +fn random_salt() -> [u8; 64] { + use rand::RngCore; + + let mut salt = [0u8; 64]; + rand::thread_rng().fill_bytes(&mut salt); + salt +} + +fn encode_argon2(secret: &str) -> anyhow::Result { + use argon2::Config; + + let password = secret.as_bytes(); + let salt = &random_salt(); + let config = Config::default(); + let hash = argon2::hash_encoded(password, salt, &config)?; + + Ok(hash) +} + +/// Verifies that the hash generated by `password` corresponds +/// to `hash`. +fn verify_argon2(hash: &str, password: &str) -> anyhow::Result { + Ok(argon2::verify_encoded(hash, password.as_bytes())?) +} + impl Keyring { pub fn get_default_collection<'a>(ss: &'a SecretService<'a>) -> Result, Error> { let collection = match ss.get_default_collection() { @@ -38,14 +62,13 @@ impl Keyring { Ok(()) } - pub fn store(label: &str, token: &str) -> Result { - let token = token.as_bytes(); + pub fn store(label: &str, token: &str) -> anyhow::Result { let ss = SecretService::new(EncryptionType::Dh)?; let col = Self::get_default_collection(&ss)?; - let token_id = hex::encode(Sha512::digest(token)); + let token_id = encode_argon2(token)?; let attributes = token_attributes(&token_id); - let base64_token = hex::encode(token); + let base64_token = hex::encode(token.as_bytes()); col.create_item(label, attributes, base64_token.as_bytes(), true, "plain")?; Ok(token_id) } @@ -85,15 +108,17 @@ impl Keyring { } } - pub fn set_password(password: &str) -> Result<(), Error> { + /// Stores password using the Argon2 algorithm with a random 128bit salt. + pub fn set_password(password: &str) -> anyhow::Result<()> { let ss = SecretService::new(EncryptionType::Dh)?; let col = Self::get_default_collection(&ss)?; + let encoded_password = encode_argon2(password)?; let attributes = password_attributes(); col.create_item( "Authenticator password", attributes, - password.as_bytes(), + encoded_password.as_bytes(), true, "plain", )?; @@ -113,14 +138,18 @@ impl Keyring { } } - pub fn is_current_password(password: &str) -> Result { + pub fn is_current_password(password: &str) -> anyhow::Result { let ss = SecretService::new(EncryptionType::Dh)?; let col = Self::get_default_collection(&ss)?; let attributes = password_attributes(); let items = col.search_items(attributes)?; Ok(match items.get(0) { - Some(i) => i.get_secret()? == password.as_bytes(), + Some(i) => { + let secret = &i.get_secret()?; + let stored_pass = std::str::from_utf8(secret)?; + verify_argon2(stored_pass, password)? + } None => false, }) }