mirror of
https://gitlab.gnome.org/World/Authenticator.git
synced 2025-03-04 08:44:40 +01:00
parent
e40843c102
commit
733af58e89
14 changed files with 57 additions and 83 deletions
1
migrations/2021-01-23-022153_steam_provider/down.sql
Normal file
1
migrations/2021-01-23-022153_steam_provider/down.sql
Normal file
|
@ -0,0 +1 @@
|
||||||
|
DELETE FROM "providers" WHERE "name"="Steam";
|
1
migrations/2021-01-23-022153_steam_provider/up.sql
Normal file
1
migrations/2021-01-23-022153_steam_provider/up.sql
Normal 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");
|
|
@ -1,7 +1,6 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
config,
|
config,
|
||||||
helpers::Keyring,
|
models::{Keyring, ProvidersModel},
|
||||||
models::ProvidersModel,
|
|
||||||
widgets::{PreferencesWindow, ProvidersDialog, Window},
|
widgets::{PreferencesWindow, ProvidersDialog, Window},
|
||||||
};
|
};
|
||||||
use gettextrs::gettext;
|
use gettextrs::gettext;
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
mod keyring;
|
|
||||||
pub mod qrcode;
|
|
||||||
|
|
||||||
pub use keyring::Keyring;
|
|
|
@ -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(())
|
|
||||||
}
|
|
|
@ -9,7 +9,6 @@ use gettextrs::*;
|
||||||
mod application;
|
mod application;
|
||||||
mod backup;
|
mod backup;
|
||||||
mod config;
|
mod config;
|
||||||
mod helpers;
|
|
||||||
mod models;
|
mod models;
|
||||||
mod schema;
|
mod schema;
|
||||||
mod static_resources;
|
mod static_resources;
|
||||||
|
|
|
@ -42,17 +42,15 @@ sources = files(
|
||||||
'backup/freeotp.rs',
|
'backup/freeotp.rs',
|
||||||
'backup/legacy.rs',
|
'backup/legacy.rs',
|
||||||
'backup/mod.rs',
|
'backup/mod.rs',
|
||||||
'helpers/database.rs',
|
|
||||||
'helpers/keyring.rs',
|
|
||||||
'helpers/mod.rs',
|
|
||||||
'helpers/qrcode.rs',
|
|
||||||
'models/account_sorter.rs',
|
'models/account_sorter.rs',
|
||||||
'models/account.rs',
|
'models/account.rs',
|
||||||
'models/accounts.rs',
|
'models/accounts.rs',
|
||||||
'models/algorithm.rs',
|
'models/algorithm.rs',
|
||||||
'models/database.rs',
|
'models/database.rs',
|
||||||
'models/favicon.rs',
|
'models/favicon.rs',
|
||||||
|
'models/keyring.rs',
|
||||||
'models/mod.rs',
|
'models/mod.rs',
|
||||||
|
'models/otp.rs',
|
||||||
'models/otp_uri.rs',
|
'models/otp_uri.rs',
|
||||||
'models/provider_sorter.rs',
|
'models/provider_sorter.rs',
|
||||||
'models/provider.rs',
|
'models/provider.rs',
|
||||||
|
|
|
@ -3,8 +3,7 @@ use super::{
|
||||||
OTPMethod, OTPUri,
|
OTPMethod, OTPUri,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
helpers::Keyring,
|
models::{database, otp, Keyring},
|
||||||
models::{database, otp},
|
|
||||||
schema::accounts,
|
schema::accounts,
|
||||||
widgets::QRCodeData,
|
widgets::QRCodeData,
|
||||||
};
|
};
|
||||||
|
@ -268,7 +267,7 @@ impl Account {
|
||||||
let provider = self.provider();
|
let provider = self.provider();
|
||||||
|
|
||||||
let counter = match provider.method() {
|
let counter = match provider.method() {
|
||||||
OTPMethod::TOTP => {
|
OTPMethod::TOTP | OTPMethod::Steam => {
|
||||||
let timestamp = SystemTime::now()
|
let timestamp = SystemTime::now()
|
||||||
.duration_since(UNIX_EPOCH)
|
.duration_since(UNIX_EPOCH)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -282,21 +281,30 @@ impl Account {
|
||||||
}
|
}
|
||||||
old_counter as u64
|
old_counter as u64
|
||||||
}
|
}
|
||||||
OTPMethod::Steam => 1,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let label = match otp::hotp(
|
let otp_password: Result<String> = match provider.method() {
|
||||||
&self.token(),
|
OTPMethod::Steam => otp::steam(&self.token()),
|
||||||
counter,
|
_ => {
|
||||||
provider.algorithm().into(),
|
let token = otp::hotp(
|
||||||
provider.digits() as u32,
|
&self.token(),
|
||||||
) {
|
counter,
|
||||||
Ok(otp) => otp::format(otp, provider.digits() as usize),
|
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) => {
|
Err(err) => {
|
||||||
debug!("Could not generate HOTP {:?}", err);
|
warn!("Failed to generate the OTP {}", err);
|
||||||
"Error".to_string()
|
"Error".to_string()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
self.set_property("otp", &label).unwrap();
|
self.set_property("otp", &label).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
mod account;
|
mod account;
|
||||||
mod account_sorter;
|
mod account_sorter;
|
||||||
mod accounts;
|
mod accounts;
|
||||||
mod algorithm;
|
mod algorithm;
|
||||||
pub mod database;
|
pub mod database;
|
||||||
mod favicon;
|
mod favicon;
|
||||||
|
mod keyring;
|
||||||
pub mod otp;
|
pub mod otp;
|
||||||
mod otp_uri;
|
mod otp_uri;
|
||||||
mod provider;
|
mod provider;
|
||||||
|
@ -21,6 +21,7 @@ pub use self::{
|
||||||
accounts::AccountsModel,
|
accounts::AccountsModel,
|
||||||
algorithm::{Algorithm, OTPMethod},
|
algorithm::{Algorithm, OTPMethod},
|
||||||
favicon::{FaviconError, FaviconScrapper},
|
favicon::{FaviconError, FaviconScrapper},
|
||||||
|
keyring::Keyring,
|
||||||
otp_uri::OTPUri,
|
otp_uri::OTPUri,
|
||||||
provider::Provider,
|
provider::Provider,
|
||||||
provider_sorter::ProviderSorter,
|
provider_sorter::ProviderSorter,
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
|
use super::Algorithm;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use data_encoding::BASE32_NOPAD;
|
use data_encoding::BASE32_NOPAD;
|
||||||
use ring::hmac;
|
use ring::hmac;
|
||||||
use std::convert::TryInto;
|
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.
|
/// Code graciously taken from the rust-top crate.
|
||||||
/// https://github.com/TimDumol/rust-otp/blob/master/src/lib.rs
|
/// 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.
|
/// 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() {
|
let offset = match digest.last() {
|
||||||
Some(x) => *x & 0xf,
|
Some(x) => *x & 0xf,
|
||||||
None => anyhow::bail!("Invalid digest"),
|
None => anyhow::bail!("Invalid digest"),
|
||||||
|
@ -30,7 +35,7 @@ fn encode_digest(digest: &[u8], digits: u32) -> Result<u32> {
|
||||||
Err(_) => anyhow::bail!("Invalid digest"),
|
Err(_) => anyhow::bail!("Invalid digest"),
|
||||||
};
|
};
|
||||||
let code = u32::from_be_bytes(code_bytes);
|
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)
|
/// 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,
|
digits: u32,
|
||||||
) -> Result<u32> {
|
) -> Result<u32> {
|
||||||
let decoded = decode_secret(secret)?;
|
let decoded = decode_secret(secret)?;
|
||||||
encode_digest(
|
let digest = encode_digest(calc_digest(decoded.as_slice(), counter, algorithm).as_ref())?;
|
||||||
calc_digest(decoded.as_slice(), counter, algorithm).as_ref(),
|
Ok(digest % 10_u32.pow(digits))
|
||||||
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 {
|
pub(crate) fn format(code: u32, digits: usize) -> String {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{config, helpers::Keyring};
|
use crate::{config, models::Keyring};
|
||||||
use glib::clone;
|
use glib::clone;
|
||||||
use gtk::{gio, glib, prelude::*, subclass::prelude::*, CompositeTemplate};
|
use gtk::{gio, glib, prelude::*, subclass::prelude::*, CompositeTemplate};
|
||||||
use gtk_macros::{action, get_action};
|
use gtk_macros::{action, get_action};
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
application::Application,
|
application::Application,
|
||||||
config,
|
config,
|
||||||
helpers::Keyring,
|
models::{Account, Keyring, ProvidersModel},
|
||||||
models::{Account, ProvidersModel},
|
|
||||||
widgets::{accounts::AccountDetailsPage, providers::ProvidersList, AccountAddDialog},
|
widgets::{accounts::AccountDetailsPage, providers::ProvidersList, AccountAddDialog},
|
||||||
window_state,
|
window_state,
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Reference in a new issue