From eeded86641090f502cb1cebe6001beca50189863 Mon Sep 17 00:00:00 2001 From: Carson McManus Date: Sun, 19 Jun 2022 14:42:07 -0400 Subject: [PATCH] protect `Session` memory, values are zeroized when dropped --- Cargo.lock | 1 + src/accountmanager.rs | 10 +++++----- src/main.rs | 2 +- steamguard/Cargo.toml | 1 + steamguard/src/accountlinker.rs | 2 +- steamguard/src/lib.rs | 13 +++++++----- steamguard/src/steamapi.rs | 35 ++++++++++++++++++++------------- steamguard/src/userlogin.rs | 3 ++- 8 files changed, 40 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2ff717f..fe14a2f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1857,6 +1857,7 @@ dependencies = [ "standback", "thiserror", "uuid", + "zeroize", ] [[package]] diff --git a/src/accountmanager.rs b/src/accountmanager.rs index 18a24d4..f9d89ec 100644 --- a/src/accountmanager.rs +++ b/src/accountmanager.rs @@ -7,7 +7,7 @@ use std::fs::File; use std::io::{BufReader, Read, Write}; use std::path::Path; use std::sync::{Arc, Mutex}; -use steamguard::SteamGuardAccount; +use steamguard::{SteamGuardAccount, ExposeSecret}; use thiserror::Error; #[derive(Debug, Serialize, Deserialize)] @@ -169,7 +169,7 @@ impl Manifest { pub fn add_account(&mut self, account: SteamGuardAccount) { debug!("adding account to manifest: {}", account.account_name); - let steamid = account.session.as_ref().map_or(0, |s| s.steam_id); + let steamid = account.session.as_ref().map_or(0, |s| s.expose_secret().steam_id); self.entries.push(ManifestEntry { filename: format!("{}.maFile", &account.account_name), steam_id: steamid, @@ -675,7 +675,7 @@ use tempdir::TempDir; let account = manifest.get_account(&account_name)?; assert_eq!(account_name, account.lock().unwrap().account_name); assert_eq!( - account.lock().unwrap().session.as_ref().unwrap().web_cookie, + account.lock().unwrap().session.as_ref().unwrap().expose_secret().web_cookie, None ); Ok(()) @@ -693,7 +693,7 @@ use tempdir::TempDir; assert_eq!(account_name, account.lock().unwrap().account_name); assert_eq!(account.lock().unwrap().revocation_code.expose_secret(), "R12345"); assert_eq!( - account.lock().unwrap().session.as_ref().unwrap().steam_id, + account.lock().unwrap().session.as_ref().unwrap().expose_secret().steam_id, 1234 ); @@ -702,7 +702,7 @@ use tempdir::TempDir; assert_eq!(account_name, account.lock().unwrap().account_name); assert_eq!(account.lock().unwrap().revocation_code.expose_secret(), "R56789"); assert_eq!( - account.lock().unwrap().session.as_ref().unwrap().steam_id, + account.lock().unwrap().session.as_ref().unwrap().expose_secret().steam_id, 5678 ); Ok(()) diff --git a/src/main.rs b/src/main.rs index 0153403..03753be 100644 --- a/src/main.rs +++ b/src/main.rs @@ -227,7 +227,7 @@ fn do_login(account: &mut SteamGuardAccount) -> anyhow::Result<()> { } else { debug!("password is empty"); } - account.session = Some(do_login_impl( + account.set_session(do_login_impl( account.account_name.clone(), password, Some(account), diff --git a/steamguard/Cargo.toml b/steamguard/Cargo.toml index a3a091c..019e2f7 100644 --- a/steamguard/Cargo.toml +++ b/steamguard/Cargo.toml @@ -29,3 +29,4 @@ scraper = "0.12.0" maplit = "1.0.2" thiserror = "1.0.26" secrecy = { version = "0.8", features = ["serde"] } +zeroize = "^1.4.3" diff --git a/steamguard/src/accountlinker.rs b/steamguard/src/accountlinker.rs index 7a6e036..9f1693e 100644 --- a/steamguard/src/accountlinker.rs +++ b/steamguard/src/accountlinker.rs @@ -27,7 +27,7 @@ impl AccountLinker { finalized: false, sent_confirmation_email: false, session: session.clone(), - client: SteamApiClient::new(Some(session)), + client: SteamApiClient::new(Some(secrecy::Secret::new(session))), }; } diff --git a/steamguard/src/lib.rs b/steamguard/src/lib.rs index 5457741..903e4f2 100644 --- a/steamguard/src/lib.rs +++ b/steamguard/src/lib.rs @@ -60,7 +60,7 @@ pub struct SteamGuardAccount { #[serde(with = "secret_string")] pub secret_1: SecretString, #[serde(default, rename = "Session")] - pub session: Option, + pub session: Option>, } fn build_time_bytes(time: i64) -> [u8; 8] { @@ -95,12 +95,16 @@ impl SteamGuardAccount { }; } + pub fn set_session(&mut self, session: steamapi::Session) { + self.session = Some(session.into()); + } + pub fn generate_code(&self, time: i64) -> String { return self.shared_secret.generate_code(time); } fn get_confirmation_query_params(&self, tag: &str) -> HashMap<&str, String> { - let session = self.session.clone().unwrap(); + let session = self.session.as_ref().unwrap().expose_secret(); let time = steamapi::get_server_time(); let mut params = HashMap::new(); params.insert("p", self.device_id.clone()); @@ -118,13 +122,12 @@ impl SteamGuardAccount { fn build_cookie_jar(&self) -> reqwest::cookie::Jar { let url = "https://steamcommunity.com".parse::().unwrap(); let cookies = reqwest::cookie::Jar::default(); - let session = self.session.clone().unwrap(); - let session_id = session.session_id; + let session = self.session.as_ref().unwrap().expose_secret(); cookies.add_cookie_str("mobileClientVersion=0 (2.1.3)", &url); cookies.add_cookie_str("mobileClient=android", &url); cookies.add_cookie_str("Steam_Language=english", &url); cookies.add_cookie_str("dob=", &url); - cookies.add_cookie_str(format!("sessionid={}", session_id).as_str(), &url); + cookies.add_cookie_str(format!("sessionid={}", session.session_id).as_str(), &url); cookies.add_cookie_str(format!("steamid={}", session.steam_id).as_str(), &url); cookies.add_cookie_str(format!("steamLogin={}", session.steam_login).as_str(), &url); cookies.add_cookie_str( diff --git a/steamguard/src/steamapi.rs b/steamguard/src/steamapi.rs index af98e19..adf0551 100644 --- a/steamguard/src/steamapi.rs +++ b/steamguard/src/steamapi.rs @@ -8,6 +8,8 @@ use reqwest::{ header::{HeaderMap, HeaderName, HeaderValue, SET_COOKIE}, Url, }; +use secrecy::{SerializableSecret, CloneableSecret, DebugSecret, ExposeSecret}; +use zeroize::Zeroize; use serde::{Deserialize, Deserializer, Serialize}; use serde_json::Value; use std::iter::FromIterator; @@ -90,7 +92,8 @@ pub struct OAuthData { webcookie: String, } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, Zeroize)] +#[zeroize(drop)] pub struct Session { #[serde(rename = "SessionID")] pub session_id: String, @@ -106,6 +109,10 @@ pub struct Session { pub steam_id: u64, } +impl SerializableSecret for Session {} +impl CloneableSecret for Session {} +impl DebugSecret for Session {} + pub fn get_server_time() -> i64 { let client = reqwest::blocking::Client::new(); let resp = client @@ -124,11 +131,11 @@ pub fn get_server_time() -> i64 { pub struct SteamApiClient { cookies: reqwest::cookie::Jar, client: reqwest::blocking::Client, - pub session: Option, + pub session: Option>, } impl SteamApiClient { - pub fn new(session: Option) -> SteamApiClient { + pub fn new(session: Option>) -> SteamApiClient { SteamApiClient { cookies: reqwest::cookie::Jar::default(), client: reqwest::blocking::ClientBuilder::new() @@ -195,7 +202,7 @@ impl SteamApiClient { .add_cookie_str("Steam_Language=english", &STEAM_COOKIE_URL); if let Some(session) = &self.session { self.cookies.add_cookie_str( - format!("sessionid={}", session.session_id).as_str(), + format!("sessionid={}", session.expose_secret().session_id).as_str(), &STEAM_COOKIE_URL, ); } @@ -270,7 +277,7 @@ impl SteamApiClient { let login_resp: LoginResponse = serde_json::from_str(text.as_str())?; if let Some(oauth) = &login_resp.oauth { - self.session = Some(self.build_session(&oauth)); + self.session = Some(secrecy::Secret::new(self.build_session(&oauth))); } return Ok(login_resp); @@ -295,7 +302,7 @@ impl SteamApiClient { wgtoken_secure: params.token_secure, webcookie: params.webcookie, }; - self.session = Some(self.build_session(&oauth)); + self.session = Some(secrecy::Secret::new(self.build_session(&oauth))); return Ok(oauth); } (None, None) => { @@ -319,7 +326,7 @@ impl SteamApiClient { let mut params = hashmap! { "op" => op, "arg" => arg, - "sessionid" => self.session.as_ref().unwrap().session_id.as_str(), + "sessionid" => self.session.as_ref().unwrap().expose_secret().session_id.as_str(), }; if op == "check_sms_code" { params.insert("checkfortos", "0"); @@ -365,7 +372,7 @@ impl SteamApiClient { let params = hashmap! { "op" => op, "input" => input, - "sessionid" => self.session.as_ref().unwrap().session_id.as_str(), + "sessionid" => self.session.as_ref().unwrap().expose_secret().session_id.as_str(), }; let resp = self @@ -421,8 +428,8 @@ impl SteamApiClient { ) -> anyhow::Result { ensure!(matches!(self.session, Some(_))); let params = hashmap! { - "access_token" => self.session.as_ref().unwrap().token.clone(), - "steamid" => self.session.as_ref().unwrap().steam_id.to_string(), + "access_token" => self.session.as_ref().unwrap().expose_secret().token.clone(), + "steamid" => self.session.as_ref().unwrap().expose_secret().steam_id.to_string(), "authenticator_type" => "1".into(), "device_identifier" => device_id, "sms_phone_id" => "1".into(), @@ -454,8 +461,8 @@ impl SteamApiClient { ) -> anyhow::Result { ensure!(matches!(self.session, Some(_))); let params = hashmap! { - "steamid" => self.session.as_ref().unwrap().steam_id.to_string(), - "access_token" => self.session.as_ref().unwrap().token.clone(), + "steamid" => self.session.as_ref().unwrap().expose_secret().steam_id.to_string(), + "access_token" => self.session.as_ref().unwrap().expose_secret().token.clone(), "activation_code" => sms_code, "authenticator_code" => code_2fa, "authenticator_time" => time_2fa.to_string(), @@ -485,10 +492,10 @@ impl SteamApiClient { revocation_code: String, ) -> anyhow::Result { let params = hashmap! { - "steamid" => self.session.as_ref().unwrap().steam_id.to_string(), + "steamid" => self.session.as_ref().unwrap().expose_secret().steam_id.to_string(), "steamguard_scheme" => "2".into(), "revocation_code" => revocation_code, - "access_token" => self.session.as_ref().unwrap().token.to_string(), + "access_token" => self.session.as_ref().unwrap().expose_secret().token.to_string(), }; let resp = self diff --git a/steamguard/src/userlogin.rs b/steamguard/src/userlogin.rs index 6c60703..f285b41 100644 --- a/steamguard/src/userlogin.rs +++ b/steamguard/src/userlogin.rs @@ -1,6 +1,7 @@ use crate::steamapi::{LoginResponse, RsaResponse, Session, SteamApiClient}; use log::*; use rsa::{PublicKey, RsaPublicKey}; +use secrecy::ExposeSecret; use std::time::{SystemTime, UNIX_EPOCH}; #[derive(Debug)] @@ -154,7 +155,7 @@ impl UserLogin { self.client.transfer_login(login_resp)?; } - return Ok(self.client.session.clone().unwrap()); + return Ok(self.client.session.as_ref().unwrap().expose_secret().to_owned()); } }