mirror of
https://gitlab.gnome.org/World/Authenticator.git
synced 2025-03-04 08:44:40 +01:00
Fix OTP generation
This commit is contained in:
parent
2630089c47
commit
59c748f61f
5 changed files with 85 additions and 29 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -247,7 +247,7 @@ dependencies = [
|
|||
"anyhow",
|
||||
"ashpd",
|
||||
"async-std",
|
||||
"byteorder",
|
||||
"data-encoding",
|
||||
"diesel",
|
||||
"diesel_migrations",
|
||||
"futures",
|
||||
|
|
|
@ -6,6 +6,7 @@ version = "0.1.0"
|
|||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
ashpd = { git = "https://github.com/bilelmoussaoui/ashpd", features=["feature_gtk4"] }
|
||||
data-encoding = "2.1"
|
||||
diesel = {version = "1.4", features = ["sqlite", "r2d2"]}
|
||||
diesel_migrations = {version = "1.4", features = ["sqlite"]}
|
||||
futures = "0.3"
|
||||
|
@ -24,7 +25,6 @@ once_cell = "1.5"
|
|||
unicase = "2.6"
|
||||
sha2 = "0.9"
|
||||
hex = "0.4"
|
||||
byteorder = "1.3"
|
||||
ring = "0.16"
|
||||
serde = "1.0"
|
||||
serde_json = "1.0"
|
||||
|
|
|
@ -2,10 +2,10 @@ use super::{
|
|||
provider::{DiProvider, Provider},
|
||||
OTPMethod, OTPUri,
|
||||
};
|
||||
use crate::models::otp;
|
||||
use crate::widgets::QRCodeData;
|
||||
use crate::{helpers::Keyring, models::database, schema::accounts};
|
||||
use anyhow::Result;
|
||||
use byteorder::{BigEndian, ReadBytesExt};
|
||||
use core::cmp::Ordering;
|
||||
use diesel::{BelongingToDsl, ExpressionMethods, QueryDsl, RunQueryDsl};
|
||||
use glib::{clone, Cast, ObjectExt, StaticType, ToValue};
|
||||
|
@ -282,17 +282,18 @@ impl Account {
|
|||
OTPMethod::Steam => 1,
|
||||
};
|
||||
|
||||
let otp = generate_otp(
|
||||
if let Ok(otp) = otp::generate_hotp(
|
||||
&self.token(),
|
||||
counter,
|
||||
provider.algorithm().into(),
|
||||
provider.digits() as u32,
|
||||
);
|
||||
self.set_property(
|
||||
"otp",
|
||||
&format!("{:0width$}", otp, width = provider.digits() as usize),
|
||||
)
|
||||
.unwrap();
|
||||
) {
|
||||
self.set_property(
|
||||
"otp",
|
||||
&format!("{:0width$}", otp, width = provider.digits() as usize),
|
||||
)
|
||||
.unwrap();
|
||||
};
|
||||
}
|
||||
|
||||
fn increment_counter(&self) -> Result<()> {
|
||||
|
@ -384,22 +385,3 @@ impl Account {
|
|||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn generate_otp(
|
||||
token: &str,
|
||||
counter: u64,
|
||||
algorithm: hmac::Algorithm,
|
||||
digits: u32,
|
||||
) -> u32 {
|
||||
// Modified version of an implementation from otpauth crate
|
||||
let key = hmac::Key::new(algorithm, token.as_bytes());
|
||||
let wtr = counter.to_be_bytes().to_vec();
|
||||
let result = hmac::sign(&key, &wtr);
|
||||
let digest = result.as_ref();
|
||||
let ob = digest[19];
|
||||
let pos = (ob & 15) as usize;
|
||||
let mut rdr = std::io::Cursor::new(digest[pos..pos + 4].to_vec());
|
||||
let base = rdr.read_u32::<BigEndian>().unwrap() & 0x7fff_ffff;
|
||||
|
||||
base % 10_u32.pow(digits)
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ mod accounts;
|
|||
mod algorithm;
|
||||
pub mod database;
|
||||
mod favicon;
|
||||
pub mod otp;
|
||||
mod otp_uri;
|
||||
mod provider;
|
||||
mod provider_sorter;
|
||||
|
|
73
src/models/otp.rs
Normal file
73
src/models/otp.rs
Normal file
|
@ -0,0 +1,73 @@
|
|||
use anyhow::Result;
|
||||
use data_encoding::{DecodeError, BASE32_NOPAD};
|
||||
use ring::hmac;
|
||||
use std::convert::TryInto;
|
||||
use std::time::{SystemTime, SystemTimeError};
|
||||
|
||||
/// Code graciously taken from the rust-top crate.
|
||||
/// https://github.com/TimDumol/rust-otp/blob/master/src/lib.rs
|
||||
|
||||
/// Decodes a secret (given as an RFC4648 base32-encoded ASCII string)
|
||||
/// into a byte string
|
||||
fn decode_secret(secret: &str) -> Result<Vec<u8>> {
|
||||
let res = BASE32_NOPAD.decode(secret.as_bytes())?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Calculates the HMAC digest for the given secret and counter.
|
||||
fn calc_digest(decoded_secret: &[u8], counter: u64, algorithm: hmac::Algorithm) -> hmac::Tag {
|
||||
let key = hmac::Key::new(algorithm, decoded_secret);
|
||||
hmac::sign(&key, &counter.to_be_bytes())
|
||||
}
|
||||
|
||||
/// Encodes the HMAC digest into a n-digit integer.
|
||||
fn encode_digest(digest: &[u8], digits: u32) -> Result<u32> {
|
||||
let offset = match digest.last() {
|
||||
Some(x) => *x & 0xf,
|
||||
None => anyhow::bail!("Invalid digest"),
|
||||
} as usize;
|
||||
let code_bytes: [u8; 4] = match digest[offset..offset + 4].try_into() {
|
||||
Ok(x) => x,
|
||||
Err(_) => anyhow::bail!("Invalid digest"),
|
||||
};
|
||||
let code = u32::from_be_bytes(code_bytes);
|
||||
Ok((code & 0x7fffffff) % 10_u32.pow(digits))
|
||||
}
|
||||
|
||||
/// Performs the [HMAC-based One-time Password Algorithm](http://en.wikipedia.org/wiki/HMAC-based_One-time_Password_Algorithm)
|
||||
/// (HOTP) given an RFC4648 base32 encoded secret, and an integer counter.
|
||||
pub(crate) fn generate_hotp(
|
||||
secret: &str,
|
||||
counter: u64,
|
||||
algorithm: hmac::Algorithm,
|
||||
digits: u32,
|
||||
) -> Result<u32> {
|
||||
let decoded = decode_secret(secret)?;
|
||||
encode_digest(
|
||||
calc_digest(decoded.as_slice(), counter, algorithm).as_ref(),
|
||||
digits,
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::make_hotp;
|
||||
|
||||
#[test]
|
||||
fn hotp() {
|
||||
let algorithm = hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY;
|
||||
let digits: u32 = 6;
|
||||
assert_eq!(
|
||||
make_hotp("BASE32SECRET3232", 0, algorithm, digits).unwrap(),
|
||||
260182
|
||||
);
|
||||
assert_eq!(
|
||||
make_hotp("BASE32SECRET3232", 1, algorithm, digits).unwrap(),
|
||||
55283
|
||||
);
|
||||
assert_eq!(
|
||||
make_hotp("BASE32SECRET3232", 1401, algorithm, digits).unwrap(),
|
||||
316439
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue