Merge pull request #174 from dyc3/qr
add the ability to generate a QR code for 2fa secrets
This commit is contained in:
commit
daa930fbee
5 changed files with 127 additions and 1 deletions
54
Cargo.lock
generated
54
Cargo.lock
generated
|
@ -135,6 +135,12 @@ version = "3.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3"
|
checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytemuck"
|
||||||
|
version = "1.12.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "aaa3a8d9a1ca92e282c96a32d6511b695d7d994d1d102ba85d279f9b2756947f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "byteorder"
|
name = "byteorder"
|
||||||
version = "1.4.3"
|
version = "1.4.3"
|
||||||
|
@ -159,6 +165,12 @@ version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "checked_int_cast"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "17cc5e6b5ab06331c33589842070416baa137e8b0eb912b008cfd4a78ada7919"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "chrono"
|
name = "chrono"
|
||||||
version = "0.4.19"
|
version = "0.4.19"
|
||||||
|
@ -229,6 +241,12 @@ dependencies = [
|
||||||
"os_str_bytes",
|
"os_str_bytes",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "color_quant"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "const-oid"
|
name = "const-oid"
|
||||||
version = "0.6.2"
|
version = "0.6.2"
|
||||||
|
@ -748,6 +766,20 @@ dependencies = [
|
||||||
"unicode-normalization",
|
"unicode-normalization",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "image"
|
||||||
|
version = "0.23.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1"
|
||||||
|
dependencies = [
|
||||||
|
"bytemuck",
|
||||||
|
"byteorder",
|
||||||
|
"color_quant",
|
||||||
|
"num-iter",
|
||||||
|
"num-rational",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "1.8.2"
|
version = "1.8.2"
|
||||||
|
@ -956,6 +988,17 @@ dependencies = [
|
||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-rational"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg 1.1.0",
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.15"
|
version = "0.2.15"
|
||||||
|
@ -1236,6 +1279,16 @@ dependencies = [
|
||||||
"psl-types",
|
"psl-types",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "qrcode"
|
||||||
|
version = "0.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "16d2f1455f3630c6e5107b4f2b94e74d76dea80736de0981fd27644216cff57f"
|
||||||
|
dependencies = [
|
||||||
|
"checked_int_cast",
|
||||||
|
"image",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quick-error"
|
name = "quick-error"
|
||||||
version = "1.2.3"
|
version = "1.2.3"
|
||||||
|
@ -1933,6 +1986,7 @@ dependencies = [
|
||||||
"lazy_static 1.4.0",
|
"lazy_static 1.4.0",
|
||||||
"log",
|
"log",
|
||||||
"proptest",
|
"proptest",
|
||||||
|
"qrcode",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
"regex",
|
"regex",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
|
|
|
@ -15,6 +15,10 @@ categories = ["command-line-utilities"]
|
||||||
repository = "https://github.com/dyc3/steamguard-cli"
|
repository = "https://github.com/dyc3/steamguard-cli"
|
||||||
license = "GPL-3.0-or-later"
|
license = "GPL-3.0-or-later"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["qr"]
|
||||||
|
qr = ["qrcode"]
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "steamguard-cli"
|
name = "steamguard-cli"
|
||||||
# filename = "steamguard" # TODO: uncomment when https://github.com/rust-lang/cargo/issues/9778 is stablized.
|
# filename = "steamguard" # TODO: uncomment when https://github.com/rust-lang/cargo/issues/9778 is stablized.
|
||||||
|
@ -48,6 +52,7 @@ aes = "0.7.4"
|
||||||
block-modes = "0.8.1"
|
block-modes = "0.8.1"
|
||||||
thiserror = "1.0.26"
|
thiserror = "1.0.26"
|
||||||
crossterm = { version = "0.23.2", features = ["event-stream"] }
|
crossterm = { version = "0.23.2", features = ["event-stream"] }
|
||||||
|
qrcode = { version = "0.12.0", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempdir = "0.3"
|
tempdir = "0.3"
|
||||||
|
|
13
README.md
13
README.md
|
@ -55,7 +55,18 @@ steamguard-cli | xclip -selection clipboard
|
||||||
|
|
||||||
## Importing 2FA Secret Into Other Applications
|
## Importing 2FA Secret Into Other Applications
|
||||||
|
|
||||||
It's possible to import your 2FA secret into other applications, like Google Authenticator or KeeWeb. The `uri` field contains a URI in that starts with `otpauth://...`, which you can create a QR code for.
|
It's possible to import your 2FA secret into other applications. This is useful if you want to use a password manager to generate your 2FA codes, like KeeWeb.
|
||||||
|
|
||||||
|
To make this easy, steamguard-cli can generate a QR code for your 2FA secret. You can then scan this QR code with your password manager.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
steamguard qr # print QR code for the first account in your maFiles
|
||||||
|
steamguard -u <account name> qr # print QR code for a specific account
|
||||||
|
```
|
||||||
|
|
||||||
|
There are some applications that do not generate correct 2fa codes from the secret, so **do not use them**:
|
||||||
|
- Google Authenticator
|
||||||
|
- Authy
|
||||||
|
|
||||||
# Contributing
|
# Contributing
|
||||||
|
|
||||||
|
|
13
src/cli.rs
13
src/cli.rs
|
@ -55,6 +55,8 @@ pub(crate) enum Subcommands {
|
||||||
Encrypt(ArgsEncrypt),
|
Encrypt(ArgsEncrypt),
|
||||||
Decrypt(ArgsDecrypt),
|
Decrypt(ArgsDecrypt),
|
||||||
Code(ArgsCode),
|
Code(ArgsCode),
|
||||||
|
#[cfg(feature = "qr")]
|
||||||
|
Qr(ArgsQr),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, ArgEnum)]
|
#[derive(Debug, Clone, Copy, ArgEnum)]
|
||||||
|
@ -171,3 +173,14 @@ impl From<Args> for ArgsCode {
|
||||||
args.code
|
args.code
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Parser)]
|
||||||
|
#[clap(about = "Generate QR codes. This *will* print sensitive data to stdout.")]
|
||||||
|
#[cfg(feature = "qr")]
|
||||||
|
pub(crate) struct ArgsQr {
|
||||||
|
#[clap(
|
||||||
|
long,
|
||||||
|
help = "Force using ASCII chars to generate QR codes. Useful for terminals that don't support unicode."
|
||||||
|
)]
|
||||||
|
pub ascii: bool,
|
||||||
|
}
|
||||||
|
|
43
src/main.rs
43
src/main.rs
|
@ -2,6 +2,8 @@ extern crate rpassword;
|
||||||
use clap::{IntoApp, Parser};
|
use clap::{IntoApp, Parser};
|
||||||
use crossterm::tty::IsTty;
|
use crossterm::tty::IsTty;
|
||||||
use log::*;
|
use log::*;
|
||||||
|
#[cfg(feature = "qr")]
|
||||||
|
use qrcode::QrCode;
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
use std::{
|
use std::{
|
||||||
io::{stdout, Write},
|
io::{stdout, Write},
|
||||||
|
@ -179,6 +181,10 @@ fn run() -> anyhow::Result<()> {
|
||||||
cli::Subcommands::Code(args) => {
|
cli::Subcommands::Code(args) => {
|
||||||
return do_subcmd_code(args, selected_accounts);
|
return do_subcmd_code(args, selected_accounts);
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "qr")]
|
||||||
|
cli::Subcommands::Qr(args) => {
|
||||||
|
return do_subcmd_qr(args, selected_accounts);
|
||||||
|
}
|
||||||
s => {
|
s => {
|
||||||
error!("Unknown subcommand: {:?}", s);
|
error!("Unknown subcommand: {:?}", s);
|
||||||
return Err(errors::UserError::UnknownSubcommand.into());
|
return Err(errors::UserError::UnknownSubcommand.into());
|
||||||
|
@ -686,3 +692,40 @@ fn do_subcmd_code(
|
||||||
}
|
}
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "qr")]
|
||||||
|
fn do_subcmd_qr(
|
||||||
|
args: cli::ArgsQr,
|
||||||
|
selected_accounts: Vec<Arc<Mutex<SteamGuardAccount>>>,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
use anyhow::Context;
|
||||||
|
|
||||||
|
info!(
|
||||||
|
"Generating QR codes for {} accounts",
|
||||||
|
selected_accounts.len()
|
||||||
|
);
|
||||||
|
|
||||||
|
for account in selected_accounts {
|
||||||
|
let account = account.lock().unwrap();
|
||||||
|
let qr = QrCode::new(account.uri.expose_secret())
|
||||||
|
.context(format!("generating qr code for {}", account.account_name))?;
|
||||||
|
|
||||||
|
info!("Printing QR code for {}", account.account_name);
|
||||||
|
let qr_string = if args.ascii {
|
||||||
|
qr.render()
|
||||||
|
.light_color(' ')
|
||||||
|
.dark_color('#')
|
||||||
|
.module_dimensions(2, 1)
|
||||||
|
.build()
|
||||||
|
} else {
|
||||||
|
use qrcode::render::unicode;
|
||||||
|
qr.render::<unicode::Dense1x2>()
|
||||||
|
.dark_color(unicode::Dense1x2::Light)
|
||||||
|
.light_color(unicode::Dense1x2::Dark)
|
||||||
|
.build()
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("{}", qr_string);
|
||||||
|
}
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue