prompt for passkey if not provided

This commit is contained in:
Carson McManus 2021-08-16 21:13:58 -04:00
parent 1b1f12f423
commit 531e69ea88
4 changed files with 81 additions and 28 deletions

1
Cargo.lock generated
View file

@ -1797,6 +1797,7 @@ dependencies = [
"tempdir",
"termion",
"text_io",
"thiserror",
"uuid",
]

View file

@ -37,6 +37,7 @@ dirs = "3.0.2"
ring = "0.16.20"
aes = "0.7.4"
block-modes = "0.8.1"
thiserror = "1.0.26"
[dev-dependencies]
tempdir = "0.3"

View file

@ -10,6 +10,7 @@ use std::io::{BufReader, Read, Write};
use std::path::Path;
use std::sync::{Arc, Mutex};
use steamguard::SteamGuardAccount;
use thiserror::Error;
type Aes256Cbc = Cbc<Aes256, NoPadding>;
@ -91,7 +92,10 @@ impl Manifest {
return Ok(manifest);
}
pub fn load_accounts(&mut self, passkey: Option<&str>) -> anyhow::Result<()> {
pub fn load_accounts(
&mut self,
passkey: &Option<String>,
) -> anyhow::Result<(), ManifestAccountLoadError> {
for entry in &mut self.entries {
let path = Path::new(&self.folder).join(&entry.filename);
debug!("loading account: {:?}", path);
@ -112,14 +116,17 @@ impl Manifest {
let mut buffer = vec![0xffu8; 16 * size];
buffer[..ciphertext.len()].copy_from_slice(&ciphertext);
let decrypted = cipher.decrypt(&mut buffer)?;
let mut padded = &decrypted[..ciphertext.len()]; // This padding doesn't make any sense.
let unpadded = Pkcs7::unpad(&mut padded).unwrap();
// This padding doesn't make any sense.
let mut padded = &decrypted[..ciphertext.len()];
// Also, UnpadError does not implement Error for some fucking reason, so we have to do this.
let unpadded = Pkcs7::unpad(&mut padded)
.map_err(|_| ManifestAccountLoadError::DecryptionFailed)?;
let s = std::str::from_utf8(&unpadded).unwrap();
account = serde_json::from_str(&s)?;
}
(None, Some(_)) => {
bail!("maFiles are encrypted, but no passkey was provided.");
return Err(ManifestAccountLoadError::MissingPasskey);
}
(_, None) => {
account = serde_json::from_reader(reader)?;
@ -166,7 +173,7 @@ impl Manifest {
self.entries.remove(index);
}
pub fn save(&self, passkey: Option<&str>) -> anyhow::Result<()> {
pub fn save(&self, passkey: &Option<String>) -> anyhow::Result<()> {
ensure!(
self.entries.len() == self.accounts.len(),
"Manifest entries don't match accounts."
@ -252,6 +259,41 @@ impl EntryEncryptionParams {
}
}
#[derive(Debug, Error)]
pub enum ManifestAccountLoadError {
#[error("Manifest accounts are encrypted, but no passkey was provided.")]
MissingPasskey,
#[error("Failed to decrypt account.")]
#[from(block_modes::block_padding::UnpadError)]
DecryptionFailed,
#[error("Failed to deserialize the account.")]
DeserializationFailed(#[from] serde_json::Error),
#[error(transparent)]
Unknown(#[from] anyhow::Error),
}
/// For some reason, these errors do not get converted to `ManifestAccountLoadError`s, even though they get converted into `anyhow::Error` just fine. I am too lazy to figure out why right now.
impl From<block_modes::BlockModeError> for ManifestAccountLoadError {
fn from(error: block_modes::BlockModeError) -> Self {
return Self::Unknown(anyhow::Error::from(error));
}
}
impl From<base64::DecodeError> for ManifestAccountLoadError {
fn from(error: base64::DecodeError) -> Self {
return Self::Unknown(anyhow::Error::from(error));
}
}
impl From<block_modes::InvalidKeyIvLength> for ManifestAccountLoadError {
fn from(error: block_modes::InvalidKeyIvLength) -> Self {
return Self::Unknown(anyhow::Error::from(error));
}
}
impl From<std::io::Error> for ManifestAccountLoadError {
fn from(error: std::io::Error) -> Self {
return Self::Unknown(anyhow::Error::from(error));
}
}
#[cfg(test)]
mod tests {
use super::*;
@ -262,7 +304,7 @@ mod tests {
let tmp_dir = TempDir::new("steamguard-cli-test").unwrap();
let manifest_path = tmp_dir.path().join("manifest.json");
let manifest = Manifest::new(manifest_path.as_path());
assert!(matches!(manifest.save(None), Ok(_)));
assert!(matches!(manifest.save(&None), Ok(_)));
}
#[test]
@ -275,12 +317,12 @@ mod tests {
account.revocation_code = "R12345".into();
account.shared_secret = "secret".into();
manifest.add_account(account);
assert!(matches!(manifest.save(None), Ok(_)));
assert!(matches!(manifest.save(&None), Ok(_)));
let mut loaded_manifest = Manifest::load(manifest_path.as_path()).unwrap();
assert_eq!(loaded_manifest.entries.len(), 1);
assert_eq!(loaded_manifest.entries[0].filename, "asdf1234.maFile");
assert!(matches!(loaded_manifest.load_accounts(None), Ok(_)));
assert!(matches!(loaded_manifest.load_accounts(&None), Ok(_)));
assert_eq!(
loaded_manifest.entries.len(),
loaded_manifest.accounts.len()
@ -301,7 +343,7 @@ mod tests {
#[test]
fn test_should_save_and_load_manifest_encrypted() {
let passkey: Option<&str> = Some("password");
let passkey: Option<String> = Some("password".into());
let tmp_dir = TempDir::new("steamguard-cli-test").unwrap();
let manifest_path = tmp_dir.path().join("manifest.json");
let mut manifest = Manifest::new(manifest_path.as_path());
@ -311,12 +353,12 @@ mod tests {
account.shared_secret = "secret".into();
manifest.add_account(account);
manifest.entries[0].encryption = Some(EntryEncryptionParams::generate());
assert!(matches!(manifest.save(passkey), Ok(_)));
assert!(matches!(manifest.save(&passkey), Ok(_)));
let mut loaded_manifest = Manifest::load(manifest_path.as_path()).unwrap();
assert_eq!(loaded_manifest.entries.len(), 1);
assert_eq!(loaded_manifest.entries[0].filename, "asdf1234.maFile");
assert!(matches!(loaded_manifest.load_accounts(passkey), Ok(_)));
assert!(matches!(loaded_manifest.load_accounts(&passkey), Ok(_)));
assert_eq!(
loaded_manifest.entries.len(),
loaded_manifest.accounts.len()
@ -345,7 +387,7 @@ mod tests {
account.revocation_code = "R12345".into();
account.shared_secret = "secret".into();
manifest.add_account(account);
assert!(matches!(manifest.save(None), Ok(_)));
assert!(matches!(manifest.save(&None), Ok(_)));
std::fs::remove_file(&manifest_path).unwrap();
let mut loaded_manifest = Manifest::new(manifest_path.as_path());
@ -386,7 +428,7 @@ mod tests {
assert!(matches!(result, Ok(_)));
let mut manifest = result.unwrap();
assert!(matches!(manifest.entries.last().unwrap().encryption, None));
assert!(matches!(manifest.load_accounts(None), Ok(_)));
assert!(matches!(manifest.load_accounts(&None), Ok(_)));
assert_eq!(
manifest.entries.last().unwrap().account_name,
manifest
@ -410,7 +452,7 @@ mod tests {
manifest.entries.last().unwrap().encryption,
Some(_)
));
let result = manifest.load_accounts(Some("password"));
let result = manifest.load_accounts(&Some("password".into()));
assert!(
matches!(result, Ok(_)),
"error when loading accounts: {:?}",

View file

@ -182,11 +182,24 @@ fn main() {
}
}
let passkey = matches.value_of("passkey");
let mut passkey: Option<String> = matches.value_of("passkey").map(|s| s.into());
manifest
.load_accounts(passkey)
.expect("Failed to load accounts in manifest");
loop {
match manifest.load_accounts(&passkey) {
Ok(_) => break,
Err(
accountmanager::ManifestAccountLoadError::MissingPasskey
| accountmanager::ManifestAccountLoadError::DecryptionFailed,
) => {
error!("Incorrect passkey");
passkey = rpassword::prompt_password_stdout("Enter encryption passkey: ").ok();
}
Err(e) => {
error!("Could not load accounts: {}", e);
return;
}
}
}
if matches.is_present("setup") {
println!("Log in to the account that you want to link to steamguard-cli");
@ -227,7 +240,7 @@ fn main() {
}
}
manifest.add_account(account);
match manifest.save(passkey) {
match manifest.save(&passkey) {
Ok(_) => {}
Err(err) => {
error!("Aborting the account linking process because we failed to save the manifest. This is really bad. Here is the error: {}", err);
@ -272,7 +285,7 @@ fn main() {
}
println!("Authenticator finalized.");
match manifest.save(None) {
match manifest.save(&None) {
Ok(_) => {}
Err(err) => {
println!(
@ -296,13 +309,13 @@ fn main() {
}
}
manifest.save(passkey).expect("Failed to save manifest.");
manifest.save(&passkey).expect("Failed to save manifest.");
return;
} else if matches.is_present("encrypt") {
for entry in &mut manifest.entries {
entry.encryption = Some(accountmanager::EntryEncryptionParams::generate());
}
manifest.save(passkey).expect("Failed to save manifest.");
manifest.save(&passkey).expect("Failed to save manifest.");
return;
}
@ -379,9 +392,7 @@ fn main() {
}
}
manifest
.save(passkey)
.expect("Failed to save manifest");
manifest.save(&passkey).expect("Failed to save manifest");
} else if let Some(_) = matches.subcommand_matches("remove") {
println!(
"This will remove the mobile authenticator from {} accounts: {}",
@ -429,9 +440,7 @@ fn main() {
manifest.remove_account(account_name);
}
manifest
.save(passkey)
.expect("Failed to save manifest.");
manifest.save(&passkey).expect("Failed to save manifest.");
} else {
let server_time = steamapi::get_server_time();
for account in selected_accounts {