From 733af58e89045435aba3e251a770c9bfe9b41261 Mon Sep 17 00:00:00 2001 From: Bilal Elmoussaoui Date: Sat, 23 Jan 2021 02:53:53 +0100 Subject: [PATCH] otp: implement steam algorithm fixes #115 --- .../2021-01-23-022153_steam_provider/down.sql | 1 + .../2021-01-23-022153_steam_provider/up.sql | 1 + src/application.rs | 3 +- src/helpers/database.rs | 0 src/helpers/mod.rs | 4 -- src/helpers/qrcode.rs | 50 ------------------- src/main.rs | 1 - src/meson.build | 6 +-- src/models/account.rs | 32 +++++++----- src/{helpers => models}/keyring.rs | 0 src/models/mod.rs | 3 +- src/models/otp.rs | 34 ++++++++++--- src/widgets/preferences/password_page.rs | 2 +- src/widgets/window.rs | 3 +- 14 files changed, 57 insertions(+), 83 deletions(-) create mode 100644 migrations/2021-01-23-022153_steam_provider/down.sql create mode 100644 migrations/2021-01-23-022153_steam_provider/up.sql delete mode 100644 src/helpers/database.rs delete mode 100644 src/helpers/mod.rs delete mode 100644 src/helpers/qrcode.rs rename src/{helpers => models}/keyring.rs (100%) diff --git a/migrations/2021-01-23-022153_steam_provider/down.sql b/migrations/2021-01-23-022153_steam_provider/down.sql new file mode 100644 index 0000000..a8ee39d --- /dev/null +++ b/migrations/2021-01-23-022153_steam_provider/down.sql @@ -0,0 +1 @@ +DELETE FROM "providers" WHERE "name"="Steam"; diff --git a/migrations/2021-01-23-022153_steam_provider/up.sql b/migrations/2021-01-23-022153_steam_provider/up.sql new file mode 100644 index 0000000..a0af850 --- /dev/null +++ b/migrations/2021-01-23-022153_steam_provider/up.sql @@ -0,0 +1 @@ +INSERT INTO "providers" ("name", "website", "help_url", "digits", "method") VALUES ("Steam", "https://steamcommunity.com/", "https://steamcommunity.com/favicon.ico", 5, "steam"); diff --git a/src/application.rs b/src/application.rs index a3089a3..3f5d580 100644 --- a/src/application.rs +++ b/src/application.rs @@ -1,7 +1,6 @@ use crate::{ config, - helpers::Keyring, - models::ProvidersModel, + models::{Keyring, ProvidersModel}, widgets::{PreferencesWindow, ProvidersDialog, Window}, }; use gettextrs::gettext; diff --git a/src/helpers/database.rs b/src/helpers/database.rs deleted file mode 100644 index e69de29..0000000 diff --git a/src/helpers/mod.rs b/src/helpers/mod.rs deleted file mode 100644 index 36f5f3c..0000000 --- a/src/helpers/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -mod keyring; -pub mod qrcode; - -pub use keyring::Keyring; diff --git a/src/helpers/qrcode.rs b/src/helpers/qrcode.rs deleted file mode 100644 index c650afc..0000000 --- a/src/helpers/qrcode.rs +++ /dev/null @@ -1,50 +0,0 @@ -use crate::models::OTPUri; -use anyhow::Result; -use ashpd::{ - desktop::screenshot::{Screenshot, ScreenshotOptions, ScreenshotProxy}, - zbus, RequestProxy, Response, WindowIdentifier, -}; -use gtk::{gio, prelude::*}; -use image::GenericImageView; -use std::str::FromStr; -use zbar_rust::ZBarImageScanner; - -pub(crate) fn scan(screenshot: &gio::File) -> Result { - let (data, _) = screenshot.load_contents(gio::NONE_CANCELLABLE)?; - - let img = image::load_from_memory(&data)?; - - let (width, height) = img.dimensions(); - let img_data: Vec = img.to_luma8().to_vec(); - - let mut scanner = ZBarImageScanner::new(); - - let results = scanner - .scan_y800(&img_data, width, height) - .map_err(|e| anyhow::format_err!(e))?; - - if let Some(ref result) = results.get(0) { - let uri = String::from_utf8(result.data.clone())?; - return Ok(OTPUri::from_str(&uri)?); - } - anyhow::bail!("Invalid QR code") -} - -pub(crate) fn screenshot_area( - window: gtk::Window, - callback: F, -) -> Result<()> { - let connection = zbus::Connection::new_session()?; - let proxy = ScreenshotProxy::new(&connection)?; - let handle = proxy.screenshot( - WindowIdentifier::from(window), - ScreenshotOptions::default().interactive(true).modal(true), - )?; - let request = RequestProxy::new(&connection, &handle)?; - request.on_response(move |response: Response| { - if let Ok(screenshot) = response { - callback(gio::File::new_for_uri(&screenshot.uri)); - } - })?; - Ok(()) -} diff --git a/src/main.rs b/src/main.rs index 33b06e9..560be5c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,7 +9,6 @@ use gettextrs::*; mod application; mod backup; mod config; -mod helpers; mod models; mod schema; mod static_resources; diff --git a/src/meson.build b/src/meson.build index 4bde5ba..4eecc51 100644 --- a/src/meson.build +++ b/src/meson.build @@ -42,17 +42,15 @@ sources = files( 'backup/freeotp.rs', 'backup/legacy.rs', 'backup/mod.rs', - 'helpers/database.rs', - 'helpers/keyring.rs', - 'helpers/mod.rs', - 'helpers/qrcode.rs', 'models/account_sorter.rs', 'models/account.rs', 'models/accounts.rs', 'models/algorithm.rs', 'models/database.rs', 'models/favicon.rs', + 'models/keyring.rs', 'models/mod.rs', + 'models/otp.rs', 'models/otp_uri.rs', 'models/provider_sorter.rs', 'models/provider.rs', diff --git a/src/models/account.rs b/src/models/account.rs index 8c27a31..3e9a383 100644 --- a/src/models/account.rs +++ b/src/models/account.rs @@ -3,8 +3,7 @@ use super::{ OTPMethod, OTPUri, }; use crate::{ - helpers::Keyring, - models::{database, otp}, + models::{database, otp, Keyring}, schema::accounts, widgets::QRCodeData, }; @@ -268,7 +267,7 @@ impl Account { let provider = self.provider(); let counter = match provider.method() { - OTPMethod::TOTP => { + OTPMethod::TOTP | OTPMethod::Steam => { let timestamp = SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() @@ -282,21 +281,30 @@ impl Account { } old_counter as u64 } - OTPMethod::Steam => 1, }; - let label = match otp::hotp( - &self.token(), - counter, - provider.algorithm().into(), - provider.digits() as u32, - ) { - Ok(otp) => otp::format(otp, provider.digits() as usize), + let otp_password: Result = match provider.method() { + OTPMethod::Steam => otp::steam(&self.token()), + _ => { + let token = otp::hotp( + &self.token(), + counter, + provider.algorithm().into(), + provider.digits() as u32, + ); + + token.map(|d| otp::format(d, provider.digits() as usize)) + } + }; + + let label = match otp_password { + Ok(password) => password, Err(err) => { - debug!("Could not generate HOTP {:?}", err); + warn!("Failed to generate the OTP {}", err); "Error".to_string() } }; + self.set_property("otp", &label).unwrap(); } diff --git a/src/helpers/keyring.rs b/src/models/keyring.rs similarity index 100% rename from src/helpers/keyring.rs rename to src/models/keyring.rs diff --git a/src/models/mod.rs b/src/models/mod.rs index 53e387c..f325e31 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -1,11 +1,11 @@ use once_cell::sync::Lazy; - mod account; mod account_sorter; mod accounts; mod algorithm; pub mod database; mod favicon; +mod keyring; pub mod otp; mod otp_uri; mod provider; @@ -21,6 +21,7 @@ pub use self::{ accounts::AccountsModel, algorithm::{Algorithm, OTPMethod}, favicon::{FaviconError, FaviconScrapper}, + keyring::Keyring, otp_uri::OTPUri, provider::Provider, provider_sorter::ProviderSorter, diff --git a/src/models/otp.rs b/src/models/otp.rs index ebe4531..8d1004d 100644 --- a/src/models/otp.rs +++ b/src/models/otp.rs @@ -1,8 +1,13 @@ +use super::Algorithm; use anyhow::Result; use data_encoding::BASE32_NOPAD; use ring::hmac; use std::convert::TryInto; +static STEAM_CHARS: &str = "23456789BCDFGHJKMNPQRTVWXY"; +static STEAM_DEFAULT_COUNTER: u64 = 30; +static STEAM_DEFAULT_DIGITS: u32 = 5; + /// Code graciously taken from the rust-top crate. /// https://github.com/TimDumol/rust-otp/blob/master/src/lib.rs @@ -20,7 +25,7 @@ fn calc_digest(decoded_secret: &[u8], counter: u64, algorithm: hmac::Algorithm) } /// Encodes the HMAC digest into a n-digit integer. -fn encode_digest(digest: &[u8], digits: u32) -> Result { +fn encode_digest(digest: &[u8]) -> Result { let offset = match digest.last() { Some(x) => *x & 0xf, None => anyhow::bail!("Invalid digest"), @@ -30,7 +35,7 @@ fn encode_digest(digest: &[u8], digits: u32) -> Result { Err(_) => anyhow::bail!("Invalid digest"), }; let code = u32::from_be_bytes(code_bytes); - Ok((code & 0x7fffffff) % 10_u32.pow(digits)) + Ok(code & 0x7fffffff) } /// Performs the [HMAC-based One-time Password Algorithm](http://en.wikipedia.org/wiki/HMAC-based_One-time_Password_Algorithm) @@ -42,10 +47,27 @@ pub(crate) fn hotp( digits: u32, ) -> Result { let decoded = decode_secret(secret)?; - encode_digest( - calc_digest(decoded.as_slice(), counter, algorithm).as_ref(), - digits, - ) + let digest = encode_digest(calc_digest(decoded.as_slice(), counter, algorithm).as_ref())?; + Ok(digest % 10_u32.pow(digits)) +} + +pub(crate) fn steam(secret: &str) -> Result { + let mut token = hotp( + secret, + STEAM_DEFAULT_COUNTER, + Algorithm::SHA1.into(), + STEAM_DEFAULT_DIGITS, + )?; + let mut code = String::new(); + let total_chars = STEAM_CHARS.len() as u32; + for i in 0..5 { + let pos = token % total_chars; + println!("{:#?}", pos); + let charachter = STEAM_CHARS.chars().nth(pos as usize).unwrap(); + code.push(charachter); + token = token / total_chars; + } + Ok(code) } pub(crate) fn format(code: u32, digits: usize) -> String { diff --git a/src/widgets/preferences/password_page.rs b/src/widgets/preferences/password_page.rs index b7a8e06..d92d16a 100644 --- a/src/widgets/preferences/password_page.rs +++ b/src/widgets/preferences/password_page.rs @@ -1,4 +1,4 @@ -use crate::{config, helpers::Keyring}; +use crate::{config, models::Keyring}; use glib::clone; use gtk::{gio, glib, prelude::*, subclass::prelude::*, CompositeTemplate}; use gtk_macros::{action, get_action}; diff --git a/src/widgets/window.rs b/src/widgets/window.rs index 56066c7..c81b76e 100644 --- a/src/widgets/window.rs +++ b/src/widgets/window.rs @@ -1,8 +1,7 @@ use crate::{ application::Application, config, - helpers::Keyring, - models::{Account, ProvidersModel}, + models::{Account, Keyring, ProvidersModel}, widgets::{accounts::AccountDetailsPage, providers::ProvidersList, AccountAddDialog}, window_state, };