diff --git a/Cargo.lock b/Cargo.lock index e46c786..accc720 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,18 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aes" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "495ee669413bfbe9e8cace80f4d3d78e6d8c8d99579f97fb93bde351b185f2d4" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", + "opaque-debug", +] + [[package]] name = "aho-corasick" version = "0.7.15" @@ -92,6 +104,22 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +[[package]] +name = "block-modes" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cb03d1bed155d89dce0f845b7899b18a9a163e148fd004e1c28421a783e2d8e" +dependencies = [ + "block-padding", + "cipher", +] + +[[package]] +name = "block-padding" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" + [[package]] name = "bumpalo" version = "3.6.1" @@ -135,6 +163,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +dependencies = [ + "generic-array", +] + [[package]] name = "clap" version = "2.33.3" @@ -211,6 +248,15 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b" +[[package]] +name = "cpufeatures" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66c99696f6c9dd7f35d486b9d04d7e6e202aa3e8c40d553f2fdf5e7e0c6a71ef" +dependencies = [ + "libc", +] + [[package]] name = "crc32fast" version = "1.2.1" @@ -690,9 +736,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.90" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba4aede83fc3617411dc6993bc8c70919750c1c257c6ca6a502aed6e0e2394ae" +checksum = "a7f823d141fe0a24df1e23b4af4e3c7ba9e5966ec514ea068c93024aa7deb765" [[package]] name = "libm" @@ -896,6 +942,12 @@ version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3" +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + [[package]] name = "openssl" version = "0.10.33" @@ -1337,6 +1389,21 @@ dependencies = [ "winreg", ] +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + [[package]] name = "rpassword" version = "5.0.1" @@ -1706,8 +1773,10 @@ dependencies = [ name = "steamguard-cli" version = "0.2.0" dependencies = [ + "aes", "anyhow", "base64", + "block-modes", "clap", "cookie", "dirs", @@ -1717,6 +1786,7 @@ dependencies = [ "rand 0.8.4", "regex", "reqwest", + "ring", "rpassword", "rsa", "serde", @@ -2082,6 +2152,12 @@ dependencies = [ "void", ] +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "url" version = "2.2.1" diff --git a/Cargo.toml b/Cargo.toml index 639b619..aaba523 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,9 @@ uuid = { version = "0.8", features = ["v4"] } termion = "1.5.6" steamguard = { path = "./steamguard" } dirs = "3.0.2" +ring = "0.16.20" +aes = "0.7.4" +block-modes = "0.8.1" [dev-dependencies] tempdir = "0.3" diff --git a/src/accountmanager.rs b/src/accountmanager.rs index 11fb010..bf1e819 100644 --- a/src/accountmanager.rs +++ b/src/accountmanager.rs @@ -1,11 +1,17 @@ +use aes::Aes256; +use block_modes::block_padding::Pkcs7; +use block_modes::{BlockMode, Cbc}; use log::*; +use ring::pbkdf2; use serde::{Deserialize, Serialize}; -use std::io::{BufReader, Write}; +use std::fs::File; +use std::io::{BufReader, Read, Write}; use std::path::Path; use std::sync::{Arc, Mutex}; -use std::{cell::Cell, fs::File}; use steamguard::SteamGuardAccount; +type Aes256Cbc = Cbc; + #[derive(Debug, Serialize, Deserialize)] pub struct Manifest { pub encrypted: bool, @@ -89,8 +95,25 @@ impl Manifest { let path = Path::new(&self.folder).join(&entry.filename); debug!("loading account: {:?}", path); let file = File::open(path)?; - let reader = BufReader::new(file); - let account: SteamGuardAccount = serde_json::from_reader(reader)?; + let mut reader = BufReader::new(file); + let account: SteamGuardAccount; + match (passkey, entry.encryption.as_ref()) { + (Some(passkey), Some(params)) => { + let key = get_encryption_key(&passkey.into(), ¶ms.salt)?; + let iv = base64::decode(¶ms.iv)?; + let cipher = Aes256Cbc::new_from_slices(&key, &iv)?; + let mut ciphertext: Vec = vec![]; + reader.read_to_end(&mut ciphertext)?; + let decrypted = cipher.decrypt(&mut ciphertext)?; + account = serde_json::from_slice(decrypted)?; + } + (None, Some(_)) => { + bail!("maFiles are encrypted, but no passkey was provided."); + } + (_, None) => { + account = serde_json::from_reader(reader)?; + } + }; entry.account_name = account.account_name.clone(); self.accounts.push(Arc::new(Mutex::new(account))); } @@ -159,6 +182,25 @@ impl Manifest { } } +const PBKDF2_ITERATIONS: u32 = 50000; +const SALT_LENGTH: usize = 8; +const KEY_SIZE_BYTES: usize = 32; +const IV_LENGTH: usize = 16; + +fn get_encryption_key(passkey: &String, salt: &String) -> anyhow::Result<[u8; KEY_SIZE_BYTES]> { + let password_bytes = passkey.as_bytes(); + let salt_bytes = base64::decode(salt)?; + let mut full_key: [u8; KEY_SIZE_BYTES] = [0u8; KEY_SIZE_BYTES]; + pbkdf2::derive( + pbkdf2::PBKDF2_HMAC_SHA256, + std::num::NonZeroU32::new(PBKDF2_ITERATIONS).unwrap(), + &salt_bytes, + password_bytes, + &mut full_key, + ); + return Ok(full_key); +} + #[cfg(test)] mod tests { use super::*; @@ -269,4 +311,33 @@ mod tests { .account_name ); } + + #[test] + fn test_sda_compatibility_1_encrypted() { + let path = Path::new("src/fixtures/maFiles/1-account-encrypted/manifest.json"); + assert!(path.is_file()); + let result = Manifest::load(path); + assert!(matches!(result, Ok(_))); + let mut manifest = result.unwrap(); + assert!(matches!( + manifest.entries.last().unwrap().encryption, + Some(_) + )); + let result = manifest.load_accounts(Some("password")); + assert!( + matches!(result, Ok(_)), + "error when loading accounts: {:?}", + result.unwrap_err() + ); + assert_eq!( + manifest.entries.last().unwrap().account_name, + manifest + .accounts + .last() + .unwrap() + .lock() + .unwrap() + .account_name + ); + } } diff --git a/src/main.rs b/src/main.rs index 3799275..7300fec 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,7 +16,9 @@ use steamguard::{ extern crate lazy_static; #[macro_use] extern crate anyhow; +extern crate base64; extern crate dirs; +extern crate ring; mod accountmanager; mod demos; mod tui;