add the ability to generate a QR code for 2fa secrets
This commit is contained in:
parent
db6557ca0d
commit
5599e28c9f
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"
|
||||
checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aaa3a8d9a1ca92e282c96a32d6511b695d7d994d1d102ba85d279f9b2756947f"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.4.3"
|
||||
|
@ -159,6 +165,12 @@ version = "1.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "checked_int_cast"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17cc5e6b5ab06331c33589842070416baa137e8b0eb912b008cfd4a78ada7919"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.19"
|
||||
|
@ -229,6 +241,12 @@ dependencies = [
|
|||
"os_str_bytes",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "color_quant"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
||||
|
||||
[[package]]
|
||||
name = "const-oid"
|
||||
version = "0.6.2"
|
||||
|
@ -748,6 +766,20 @@ dependencies = [
|
|||
"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]]
|
||||
name = "indexmap"
|
||||
version = "1.8.2"
|
||||
|
@ -956,6 +988,17 @@ dependencies = [
|
|||
"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]]
|
||||
name = "num-traits"
|
||||
version = "0.2.15"
|
||||
|
@ -1236,6 +1279,16 @@ dependencies = [
|
|||
"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]]
|
||||
name = "quick-error"
|
||||
version = "1.2.3"
|
||||
|
@ -1933,6 +1986,7 @@ dependencies = [
|
|||
"lazy_static 1.4.0",
|
||||
"log",
|
||||
"proptest",
|
||||
"qrcode",
|
||||
"rand 0.8.5",
|
||||
"regex",
|
||||
"reqwest",
|
||||
|
|
|
@ -15,6 +15,10 @@ categories = ["command-line-utilities"]
|
|||
repository = "https://github.com/dyc3/steamguard-cli"
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[features]
|
||||
default = ["qr"]
|
||||
qr = ["qrcode"]
|
||||
|
||||
[[bin]]
|
||||
name = "steamguard-cli"
|
||||
# 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"
|
||||
thiserror = "1.0.26"
|
||||
crossterm = { version = "0.23.2", features = ["event-stream"] }
|
||||
qrcode = { version = "0.12.0", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tempdir = "0.3"
|
||||
|
|
13
README.md
13
README.md
|
@ -55,7 +55,18 @@ steamguard-cli | xclip -selection clipboard
|
|||
|
||||
## 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
|
||||
|
||||
|
|
13
src/cli.rs
13
src/cli.rs
|
@ -55,6 +55,8 @@ pub(crate) enum Subcommands {
|
|||
Encrypt(ArgsEncrypt),
|
||||
Decrypt(ArgsDecrypt),
|
||||
Code(ArgsCode),
|
||||
#[cfg(feature = "qr")]
|
||||
Qr(ArgsQr),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, ArgEnum)]
|
||||
|
@ -174,3 +176,14 @@ impl From<Args> for ArgsCode {
|
|||
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 crossterm::tty::IsTty;
|
||||
use log::*;
|
||||
#[cfg(feature = "qr")]
|
||||
use qrcode::QrCode;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use std::{
|
||||
io::{stdout, Write},
|
||||
|
@ -179,6 +181,10 @@ fn run() -> anyhow::Result<()> {
|
|||
cli::Subcommands::Code(args) => {
|
||||
return do_subcmd_code(args, selected_accounts);
|
||||
}
|
||||
#[cfg(feature = "qr")]
|
||||
cli::Subcommands::Qr(args) => {
|
||||
return do_subcmd_qr(args, selected_accounts);
|
||||
}
|
||||
s => {
|
||||
error!("Unknown subcommand: {:?}", s);
|
||||
return Err(errors::UserError::UnknownSubcommand.into());
|
||||
|
@ -690,3 +696,40 @@ fn do_subcmd_code(
|
|||
}
|
||||
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