otp: implement steam algorithm

fixes #115
This commit is contained in:
Bilal Elmoussaoui 2021-01-23 02:53:53 +01:00
parent e40843c102
commit 733af58e89
14 changed files with 57 additions and 83 deletions

View File

@ -0,0 +1 @@
DELETE FROM "providers" WHERE "name"="Steam";

View File

@ -0,0 +1 @@
INSERT INTO "providers" ("name", "website", "help_url", "digits", "method") VALUES ("Steam", "https://steamcommunity.com/", "https://steamcommunity.com/favicon.ico", 5, "steam");

View File

@ -1,7 +1,6 @@
use crate::{
config,
helpers::Keyring,
models::ProvidersModel,
models::{Keyring, ProvidersModel},
widgets::{PreferencesWindow, ProvidersDialog, Window},
};
use gettextrs::gettext;

View File

@ -1,4 +0,0 @@
mod keyring;
pub mod qrcode;
pub use keyring::Keyring;

View File

@ -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<OTPUri> {
let (data, _) = screenshot.load_contents(gio::NONE_CANCELLABLE)?;
let img = image::load_from_memory(&data)?;
let (width, height) = img.dimensions();
let img_data: Vec<u8> = 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<F: FnOnce(gio::File)>(
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<Screenshot>| {
if let Ok(screenshot) = response {
callback(gio::File::new_for_uri(&screenshot.uri));
}
})?;
Ok(())
}

View File

@ -9,7 +9,6 @@ use gettextrs::*;
mod application;
mod backup;
mod config;
mod helpers;
mod models;
mod schema;
mod static_resources;

View File

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

View File

@ -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<String> = 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();
}

View File

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

View File

@ -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<u32> {
fn encode_digest(digest: &[u8]) -> Result<u32> {
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<u32> {
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<u32> {
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<String> {
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 {

View File

@ -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};

View File

@ -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,
};