add the ability to generate a QR code for 2fa secrets

This commit is contained in:
Carson McManus 2022-12-05 08:47:33 -05:00
parent db6557ca0d
commit 5599e28c9f
5 changed files with 127 additions and 1 deletions

54
Cargo.lock generated
View file

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

View file

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

View file

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

View file

@ -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)]
@ -174,3 +176,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,
}

View file

@ -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());
@ -690,3 +696,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(());
}