otp: Fix compatibility with 2FAS

See https://2fas.com/check-token/
This commit is contained in:
Bilal Elmoussaoui 2023-04-09 01:49:21 +02:00 committed by Bilal Elmoussaoui
parent 598cdf9d6b
commit cdee19ccb8
4 changed files with 30 additions and 70 deletions

25
Cargo.lock generated
View file

@ -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",

View file

@ -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"]}

View file

@ -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 == '=')

View file

@ -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<Vec<u8>> {
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::<Vec<u8>>();
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<u32> {
/// (HOTP) given an RFC4648 base32 encoded secret, and an integer counter.
pub(crate) fn hotp(secret: &str, counter: u64, algorithm: Algorithm, digits: u32) -> Result<u32> {
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<String> {
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()