From cdee19ccb8f1a494c2c12791cc837fdfe34e1a63 Mon Sep 17 00:00:00 2001 From: Bilal Elmoussaoui Date: Sun, 9 Apr 2023 01:49:21 +0200 Subject: [PATCH] otp: Fix compatibility with 2FAS See https://2fas.com/check-token/ --- Cargo.lock | 25 +++++++------------ Cargo.toml | 1 - src/backup/google.rs | 17 ++----------- src/models/otp.rs | 57 +++++++++++++++----------------------------- 4 files changed, 30 insertions(+), 70 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f0e01d8..3d70a12 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -149,7 +149,6 @@ dependencies = [ "aes-gcm", "anyhow", "ashpd", - "binascii", "data-encoding", "diesel", "diesel_migrations", @@ -202,12 +201,6 @@ version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" -[[package]] -name = "binascii" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72" - [[package]] name = "bindgen" version = "0.64.0" @@ -725,13 +718,13 @@ dependencies = [ [[package]] name = "errno" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d6a0976c999d473fe89ad888d5a284e55366d9dc9038b1ba2aa15128c4afa0" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" dependencies = [ "errno-dragonfly", "libc", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -1025,9 +1018,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" dependencies = [ "cfg-if", "libc", @@ -1419,9 +1412,9 @@ dependencies = [ [[package]] name = "gstreamer-video" -version = "0.20.3" +version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "467cddb6a4135e72fefb6ba21262b1cca5493e9928792e88fe672ec0a37b761c" +checksum = "dce97769effde2d779dc4f7037b37106457b74e53f2a711bddc90b30ffeb7e06" dependencies = [ "bitflags", "cfg-if", @@ -2749,9 +2742,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.37.8" +version = "0.37.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aef160324be24d31a62147fae491c14d2204a3865c7ca8c3b0d7f7bcb3ea635" +checksum = "85597d61f83914ddeba6a47b3b8ffe7365107221c2e557ed94426489fefb5f77" dependencies = [ "bitflags", "errno", diff --git a/Cargo.toml b/Cargo.toml index 5acecf2..04fd285 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,6 @@ adw = {package = "libadwaita", version = "0.3", features = ["v1_2"]} aes-gcm = "0.10" anyhow = "1.0" ashpd = {version = "0.4", default-features = false, features = ["pipewire", "gtk4", "tokio", "tracing"]} -binascii = "0.1" data-encoding = "2.3" diesel = {version = "2.0", features = ["sqlite", "r2d2"]} diesel_migrations = {version = "2.0", features = ["sqlite"]} diff --git a/src/backup/google.rs b/src/backup/google.rs index f4f6d67..4901833 100644 --- a/src/backup/google.rs +++ b/src/backup/google.rs @@ -5,7 +5,7 @@ use prost::{Enumeration, Message}; use url::Url; use super::Restorable; -use crate::models::{Algorithm, Method, OTPUri}; +use crate::models::{otp, Algorithm, Method, OTPUri}; pub struct Google; @@ -92,20 +92,7 @@ impl Restorable for Google { protobuf::migration_payload::OtpType::OTP_TOTP => Method::TOTP, }, secret: { - let secret = &*otp.secret; - - let mut buffer = [0; 128]; - - if binascii::b32encode(secret, &mut buffer).is_err() { - return folded; - } - - let buffer = buffer.to_vec(); - - let string = match String::from_utf8(buffer) { - Ok(string) => string, - Err(_) => return folded, - }; + let string = otp::encode_secret(&*otp.secret); string .trim_end_matches(|c| c == '\0' || c == '=') diff --git a/src/models/otp.rs b/src/models/otp.rs index 2b173e3..0ad15dc 100644 --- a/src/models/otp.rs +++ b/src/models/otp.rs @@ -4,6 +4,7 @@ use std::{ }; use anyhow::{anyhow, Result}; +use data_encoding::BASE32; use ring::hmac; use super::Algorithm; @@ -21,15 +22,15 @@ pub static TOTP_DEFAULT_PERIOD: u32 = 30; /// Decodes a secret (given as an RFC4648 base32-encoded ASCII string) /// into a byte string. It fails if secret is not a valid Base32 string. fn decode_secret(secret: &str) -> Result> { - let secret = secret.trim().replace(' ', "").to_uppercase(); + let secret = secret.trim().replace(' ', "").to_ascii_uppercase(); // The buffer should have a length of secret.len() * 5 / 8. - let size = secret.len(); - let mut output_buffer = std::iter::repeat(0).take(size).collect::>(); - let vec = binascii::b32decode(secret.as_bytes(), &mut output_buffer) - .map_err(|_| anyhow!("Invalid Input"))? - .to_vec(); + BASE32 + .decode(secret.as_bytes()) + .map_err(|_| anyhow!("Invalid Input")) +} - Ok(vec) +pub fn encode_secret(secret: &[u8]) -> String { + BASE32.encode(secret) } /// Validates if `secret` is a valid Base32 String. @@ -61,14 +62,13 @@ fn encode_digest(digest: &[u8]) -> Result { /// (HOTP) given an RFC4648 base32 encoded secret, and an integer counter. pub(crate) fn hotp(secret: &str, counter: u64, algorithm: Algorithm, digits: u32) -> Result { let decoded = decode_secret(secret)?; - let digest = encode_digest(calc_digest(decoded.as_slice(), counter, algorithm).as_ref())?; + let digest = encode_digest(calc_digest(&decoded, counter, algorithm).as_ref())?; Ok(digest % 10_u32.pow(digits)) } pub(crate) fn steam(secret: &str, counter: u64) -> Result { let decoded = decode_secret(secret)?; - let mut full_token = - encode_digest(calc_digest(decoded.as_slice(), counter, Algorithm::SHA1).as_ref())?; + let mut full_token = encode_digest(calc_digest(&decoded, counter, Algorithm::SHA1).as_ref())?; let mut code = String::new(); let total_chars = STEAM_CHARS.len() as u32; @@ -103,30 +103,16 @@ pub(crate) fn time_based_counter(period: u32) -> u64 { #[cfg(test)] mod tests { - use super::{format, hotp, steam, Algorithm, DEFAULT_DIGITS, TOTP_DEFAULT_PERIOD}; + use super::{ + encode_secret, format, hotp, steam, Algorithm, DEFAULT_DIGITS, TOTP_DEFAULT_PERIOD, + }; + #[test] fn test_totp() { - let secret_sha1 = String::from_utf8( - binascii::b32encode(b"12345678901234567890", &mut [0; 64]) - .unwrap() - .to_vec(), - ) - .unwrap(); - let secret_sha256 = String::from_utf8( - binascii::b32encode(b"12345678901234567890123456789012", &mut [0; 64]) - .unwrap() - .to_vec(), - ) - .unwrap(); - let secret_sha512 = String::from_utf8( - binascii::b32encode( - b"1234567890123456789012345678901234567890123456789012345678901234", - &mut [0; 128], - ) - .unwrap() - .to_vec(), - ) - .unwrap(); + let secret_sha1 = encode_secret(b"12345678901234567890"); + let secret_sha256 = encode_secret(b"12345678901234567890123456789012"); + let secret_sha512 = + encode_secret(b"1234567890123456789012345678901234567890123456789012345678901234"); let counter1 = 59 / TOTP_DEFAULT_PERIOD as u64; assert_eq!( @@ -228,12 +214,7 @@ mod tests { hotp("BASE32SECRET3232", 1401, Algorithm::SHA1, DEFAULT_DIGITS).ok(), Some(316439) ); - let secret = String::from_utf8( - binascii::b32encode(b"12345678901234567890", &mut [0; 64]) - .unwrap() - .to_vec(), - ) - .unwrap(); + let secret = encode_secret(b"12345678901234567890"); assert_eq!( Some(755224), hotp(&secret, 0, Algorithm::SHA1, DEFAULT_DIGITS).ok()