Merge pull request #154 from dyc3/more-memory-protection
Mark all sensitive fields in SteamGuardAccount as secret
This commit is contained in:
commit
1ee063f58e
9 changed files with 172 additions and 58 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -1857,6 +1857,7 @@ dependencies = [
|
||||||
"standback",
|
"standback",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"uuid",
|
"uuid",
|
||||||
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -7,7 +7,7 @@ use std::fs::File;
|
||||||
use std::io::{BufReader, Read, Write};
|
use std::io::{BufReader, Read, Write};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use steamguard::SteamGuardAccount;
|
use steamguard::{ExposeSecret, SteamGuardAccount};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
@ -169,7 +169,10 @@ impl Manifest {
|
||||||
|
|
||||||
pub fn add_account(&mut self, account: SteamGuardAccount) {
|
pub fn add_account(&mut self, account: SteamGuardAccount) {
|
||||||
debug!("adding account to manifest: {}", account.account_name);
|
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 {
|
self.entries.push(ManifestEntry {
|
||||||
filename: format!("{}.maFile", &account.account_name),
|
filename: format!("{}.maFile", &account.account_name),
|
||||||
steam_id: steamid,
|
steam_id: steamid,
|
||||||
|
@ -372,6 +375,7 @@ impl From<std::io::Error> for ManifestAccountLoadError {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use steamguard::ExposeSecret;
|
||||||
use tempdir::TempDir;
|
use tempdir::TempDir;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -390,7 +394,7 @@ mod tests {
|
||||||
let mut manifest = Manifest::new(manifest_path.as_path());
|
let mut manifest = Manifest::new(manifest_path.as_path());
|
||||||
let mut account = SteamGuardAccount::new();
|
let mut account = SteamGuardAccount::new();
|
||||||
account.account_name = "asdf1234".into();
|
account.account_name = "asdf1234".into();
|
||||||
account.revocation_code = "R12345".into();
|
account.revocation_code = String::from("R12345").into();
|
||||||
account.shared_secret = steamguard::token::TwoFactorSecret::parse_shared_secret(
|
account.shared_secret = steamguard::token::TwoFactorSecret::parse_shared_secret(
|
||||||
"zvIayp3JPvtvX/QGHqsqKBk/44s=".into(),
|
"zvIayp3JPvtvX/QGHqsqKBk/44s=".into(),
|
||||||
)?;
|
)?;
|
||||||
|
@ -419,7 +423,8 @@ mod tests {
|
||||||
.get_account(&account_name)?
|
.get_account(&account_name)?
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.revocation_code,
|
.revocation_code
|
||||||
|
.expose_secret(),
|
||||||
"R12345"
|
"R12345"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -443,7 +448,7 @@ mod tests {
|
||||||
let mut manifest = Manifest::new(manifest_path.as_path());
|
let mut manifest = Manifest::new(manifest_path.as_path());
|
||||||
let mut account = SteamGuardAccount::new();
|
let mut account = SteamGuardAccount::new();
|
||||||
account.account_name = "asdf1234".into();
|
account.account_name = "asdf1234".into();
|
||||||
account.revocation_code = "R12345".into();
|
account.revocation_code = String::from("R12345").into();
|
||||||
account.shared_secret = steamguard::token::TwoFactorSecret::parse_shared_secret(
|
account.shared_secret = steamguard::token::TwoFactorSecret::parse_shared_secret(
|
||||||
"zvIayp3JPvtvX/QGHqsqKBk/44s=".into(),
|
"zvIayp3JPvtvX/QGHqsqKBk/44s=".into(),
|
||||||
)?;
|
)?;
|
||||||
|
@ -479,7 +484,8 @@ mod tests {
|
||||||
.get_account(&account_name)?
|
.get_account(&account_name)?
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.revocation_code,
|
.revocation_code
|
||||||
|
.expose_secret(),
|
||||||
"R12345"
|
"R12345"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -504,12 +510,12 @@ mod tests {
|
||||||
let mut manifest = Manifest::new(manifest_path.as_path());
|
let mut manifest = Manifest::new(manifest_path.as_path());
|
||||||
let mut account = SteamGuardAccount::new();
|
let mut account = SteamGuardAccount::new();
|
||||||
account.account_name = "asdf1234".into();
|
account.account_name = "asdf1234".into();
|
||||||
account.revocation_code = "R12345".into();
|
account.revocation_code = String::from("R12345").into();
|
||||||
account.shared_secret = steamguard::token::TwoFactorSecret::parse_shared_secret(
|
account.shared_secret = steamguard::token::TwoFactorSecret::parse_shared_secret(
|
||||||
"zvIayp3JPvtvX/QGHqsqKBk/44s=".into(),
|
"zvIayp3JPvtvX/QGHqsqKBk/44s=".into(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
account.uri = "otpauth://;laksdjf;lkasdjf;lkasdj;flkasdjlkf;asjdlkfjslk;adjfl;kasdjf;lksdjflk;asjd;lfajs;ldkfjaslk;djf;lsakdjf;lksdj".into();
|
account.uri = String::from("otpauth://;laksdjf;lkasdjf;lkasdj;flkasdjlkf;asjdlkfjslk;adjfl;kasdjf;lksdjflk;asjd;lfajs;ldkfjaslk;djf;lsakdjf;lksdj").into();
|
||||||
account.token_gid = "asdf1234".into();
|
account.token_gid = "asdf1234".into();
|
||||||
manifest.add_account(account);
|
manifest.add_account(account);
|
||||||
manifest.submit_passkey(passkey.clone());
|
manifest.submit_passkey(passkey.clone());
|
||||||
|
@ -539,7 +545,8 @@ mod tests {
|
||||||
.get_account(&account_name)?
|
.get_account(&account_name)?
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.revocation_code,
|
.revocation_code
|
||||||
|
.expose_secret(),
|
||||||
"R12345"
|
"R12345"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -564,7 +571,7 @@ mod tests {
|
||||||
let mut manifest = Manifest::new(manifest_path.as_path());
|
let mut manifest = Manifest::new(manifest_path.as_path());
|
||||||
let mut account = SteamGuardAccount::new();
|
let mut account = SteamGuardAccount::new();
|
||||||
account.account_name = "asdf1234".into();
|
account.account_name = "asdf1234".into();
|
||||||
account.revocation_code = "R12345".into();
|
account.revocation_code = String::from("R12345").into();
|
||||||
account.shared_secret = steamguard::token::TwoFactorSecret::parse_shared_secret(
|
account.shared_secret = steamguard::token::TwoFactorSecret::parse_shared_secret(
|
||||||
"zvIayp3JPvtvX/QGHqsqKBk/44s=".into(),
|
"zvIayp3JPvtvX/QGHqsqKBk/44s=".into(),
|
||||||
)
|
)
|
||||||
|
@ -603,7 +610,8 @@ mod tests {
|
||||||
.get_account(&account_name)?
|
.get_account(&account_name)?
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.revocation_code,
|
.revocation_code
|
||||||
|
.expose_secret(),
|
||||||
"R12345"
|
"R12345"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -674,7 +682,14 @@ mod tests {
|
||||||
let account = manifest.get_account(&account_name)?;
|
let account = manifest.get_account(&account_name)?;
|
||||||
assert_eq!(account_name, account.lock().unwrap().account_name);
|
assert_eq!(account_name, account.lock().unwrap().account_name);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
account.lock().unwrap().session.as_ref().unwrap().web_cookie,
|
account
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.session
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.expose_secret()
|
||||||
|
.web_cookie,
|
||||||
None
|
None
|
||||||
);
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -690,18 +705,38 @@ mod tests {
|
||||||
let account_name = manifest.entries[0].account_name.clone();
|
let account_name = manifest.entries[0].account_name.clone();
|
||||||
let account = manifest.get_account(&account_name)?;
|
let account = manifest.get_account(&account_name)?;
|
||||||
assert_eq!(account_name, account.lock().unwrap().account_name);
|
assert_eq!(account_name, account.lock().unwrap().account_name);
|
||||||
assert_eq!(account.lock().unwrap().revocation_code, "R12345");
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
account.lock().unwrap().session.as_ref().unwrap().steam_id,
|
account.lock().unwrap().revocation_code.expose_secret(),
|
||||||
|
"R12345"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
account
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.session
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.expose_secret()
|
||||||
|
.steam_id,
|
||||||
1234
|
1234
|
||||||
);
|
);
|
||||||
|
|
||||||
let account_name = manifest.entries[1].account_name.clone();
|
let account_name = manifest.entries[1].account_name.clone();
|
||||||
let account = manifest.get_account(&account_name)?;
|
let account = manifest.get_account(&account_name)?;
|
||||||
assert_eq!(account_name, account.lock().unwrap().account_name);
|
assert_eq!(account_name, account.lock().unwrap().account_name);
|
||||||
assert_eq!(account.lock().unwrap().revocation_code, "R56789");
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
account.lock().unwrap().session.as_ref().unwrap().steam_id,
|
account.lock().unwrap().revocation_code.expose_secret(),
|
||||||
|
"R56789"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
account
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.session
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.expose_secret()
|
||||||
|
.steam_id,
|
||||||
5678
|
5678
|
||||||
);
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
10
src/main.rs
10
src/main.rs
|
@ -7,8 +7,8 @@ use std::{
|
||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
};
|
};
|
||||||
use steamguard::{
|
use steamguard::{
|
||||||
steamapi, AccountLinkError, AccountLinker, Confirmation, FinalizeLinkError, LoginError,
|
steamapi, AccountLinkError, AccountLinker, Confirmation, ExposeSecret, FinalizeLinkError,
|
||||||
SteamGuardAccount, UserLogin,
|
LoginError, SteamGuardAccount, UserLogin,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::accountmanager::ManifestAccountLoadError;
|
use crate::accountmanager::ManifestAccountLoadError;
|
||||||
|
@ -227,7 +227,7 @@ fn do_login(account: &mut SteamGuardAccount) -> anyhow::Result<()> {
|
||||||
} else {
|
} else {
|
||||||
debug!("password is empty");
|
debug!("password is empty");
|
||||||
}
|
}
|
||||||
account.session = Some(do_login_impl(
|
account.set_session(do_login_impl(
|
||||||
account.account_name.clone(),
|
account.account_name.clone(),
|
||||||
password,
|
password,
|
||||||
Some(account),
|
Some(account),
|
||||||
|
@ -391,7 +391,7 @@ fn do_subcmd_setup(
|
||||||
let account_arc = manifest.get_account(&account_name).unwrap();
|
let account_arc = manifest.get_account(&account_name).unwrap();
|
||||||
let mut account = account_arc.lock().unwrap();
|
let mut account = account_arc.lock().unwrap();
|
||||||
|
|
||||||
println!("Authenticator has not yet been linked. Before continuing with finalization, please take the time to write down your revocation code: {}", account.revocation_code);
|
println!("Authenticator has not yet been linked. Before continuing with finalization, please take the time to write down your revocation code: {}", account.revocation_code.expose_secret());
|
||||||
tui::pause();
|
tui::pause();
|
||||||
|
|
||||||
debug!("attempting link finalization");
|
debug!("attempting link finalization");
|
||||||
|
@ -430,7 +430,7 @@ fn do_subcmd_setup(
|
||||||
|
|
||||||
println!(
|
println!(
|
||||||
"Authenticator has been finalized. Please actually write down your revocation code: {}",
|
"Authenticator has been finalized. Please actually write down your revocation code: {}",
|
||||||
account.revocation_code
|
account.revocation_code.expose_secret()
|
||||||
);
|
);
|
||||||
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
|
|
@ -29,3 +29,4 @@ scraper = "0.12.0"
|
||||||
maplit = "1.0.2"
|
maplit = "1.0.2"
|
||||||
thiserror = "1.0.26"
|
thiserror = "1.0.26"
|
||||||
secrecy = { version = "0.8", features = ["serde"] }
|
secrecy = { version = "0.8", features = ["serde"] }
|
||||||
|
zeroize = "^1.4.3"
|
||||||
|
|
|
@ -27,7 +27,7 @@ impl AccountLinker {
|
||||||
finalized: false,
|
finalized: false,
|
||||||
sent_confirmation_email: false,
|
sent_confirmation_email: false,
|
||||||
session: session.clone(),
|
session: session.clone(),
|
||||||
client: SteamApiClient::new(Some(session)),
|
client: SteamApiClient::new(Some(secrecy::Secret::new(session))),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ use reqwest::{
|
||||||
Url,
|
Url,
|
||||||
};
|
};
|
||||||
use scraper::{Html, Selector};
|
use scraper::{Html, Selector};
|
||||||
|
pub use secrecy::{ExposeSecret, SecretString};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{collections::HashMap, convert::TryInto};
|
use std::{collections::HashMap, convert::TryInto};
|
||||||
use steamapi::SteamApiClient;
|
use steamapi::SteamApiClient;
|
||||||
|
@ -24,6 +25,7 @@ extern crate maplit;
|
||||||
|
|
||||||
mod accountlinker;
|
mod accountlinker;
|
||||||
mod confirmation;
|
mod confirmation;
|
||||||
|
mod secret_string;
|
||||||
pub mod steamapi;
|
pub mod steamapi;
|
||||||
pub mod token;
|
pub mod token;
|
||||||
mod userlogin;
|
mod userlogin;
|
||||||
|
@ -43,17 +45,21 @@ extern crate hmacsha1;
|
||||||
pub struct SteamGuardAccount {
|
pub struct SteamGuardAccount {
|
||||||
pub account_name: String,
|
pub account_name: String,
|
||||||
pub serial_number: String,
|
pub serial_number: String,
|
||||||
pub revocation_code: String,
|
#[serde(with = "secret_string")]
|
||||||
|
pub revocation_code: SecretString,
|
||||||
pub shared_secret: TwoFactorSecret,
|
pub shared_secret: TwoFactorSecret,
|
||||||
pub token_gid: String,
|
pub token_gid: String,
|
||||||
pub identity_secret: String,
|
#[serde(with = "secret_string")]
|
||||||
|
pub identity_secret: SecretString,
|
||||||
pub server_time: u64,
|
pub server_time: u64,
|
||||||
pub uri: String,
|
#[serde(with = "secret_string")]
|
||||||
|
pub uri: SecretString,
|
||||||
pub fully_enrolled: bool,
|
pub fully_enrolled: bool,
|
||||||
pub device_id: String,
|
pub device_id: String,
|
||||||
pub secret_1: String,
|
#[serde(with = "secret_string")]
|
||||||
|
pub secret_1: SecretString,
|
||||||
#[serde(default, rename = "Session")]
|
#[serde(default, rename = "Session")]
|
||||||
pub session: Option<steamapi::Session>,
|
pub session: Option<secrecy::Secret<steamapi::Session>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_time_bytes(time: i64) -> [u8; 8] {
|
fn build_time_bytes(time: i64) -> [u8; 8] {
|
||||||
|
@ -75,32 +81,36 @@ impl SteamGuardAccount {
|
||||||
return SteamGuardAccount {
|
return SteamGuardAccount {
|
||||||
account_name: String::from(""),
|
account_name: String::from(""),
|
||||||
serial_number: String::from(""),
|
serial_number: String::from(""),
|
||||||
revocation_code: String::from(""),
|
revocation_code: String::from("").into(),
|
||||||
shared_secret: TwoFactorSecret::new(),
|
shared_secret: TwoFactorSecret::new(),
|
||||||
token_gid: String::from(""),
|
token_gid: String::from(""),
|
||||||
identity_secret: String::from(""),
|
identity_secret: String::from("").into(),
|
||||||
server_time: 0,
|
server_time: 0,
|
||||||
uri: String::from(""),
|
uri: String::from("").into(),
|
||||||
fully_enrolled: false,
|
fully_enrolled: false,
|
||||||
device_id: String::from(""),
|
device_id: String::from(""),
|
||||||
secret_1: "".into(),
|
secret_1: String::from("").into(),
|
||||||
session: Option::None,
|
session: Option::None,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_session(&mut self, session: steamapi::Session) {
|
||||||
|
self.session = Some(session.into());
|
||||||
|
}
|
||||||
|
|
||||||
pub fn generate_code(&self, time: i64) -> String {
|
pub fn generate_code(&self, time: i64) -> String {
|
||||||
return self.shared_secret.generate_code(time);
|
return self.shared_secret.generate_code(time);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_confirmation_query_params(&self, tag: &str) -> HashMap<&str, String> {
|
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 time = steamapi::get_server_time();
|
||||||
let mut params = HashMap::new();
|
let mut params = HashMap::new();
|
||||||
params.insert("p", self.device_id.clone());
|
params.insert("p", self.device_id.clone());
|
||||||
params.insert("a", session.steam_id.to_string());
|
params.insert("a", session.steam_id.to_string());
|
||||||
params.insert(
|
params.insert(
|
||||||
"k",
|
"k",
|
||||||
generate_confirmation_hash_for_time(time, tag, &self.identity_secret),
|
generate_confirmation_hash_for_time(time, tag, &self.identity_secret.expose_secret()),
|
||||||
);
|
);
|
||||||
params.insert("t", time.to_string());
|
params.insert("t", time.to_string());
|
||||||
params.insert("m", String::from("android"));
|
params.insert("m", String::from("android"));
|
||||||
|
@ -111,13 +121,12 @@ impl SteamGuardAccount {
|
||||||
fn build_cookie_jar(&self) -> reqwest::cookie::Jar {
|
fn build_cookie_jar(&self) -> reqwest::cookie::Jar {
|
||||||
let url = "https://steamcommunity.com".parse::<Url>().unwrap();
|
let url = "https://steamcommunity.com".parse::<Url>().unwrap();
|
||||||
let cookies = reqwest::cookie::Jar::default();
|
let cookies = reqwest::cookie::Jar::default();
|
||||||
let session = self.session.clone().unwrap();
|
let session = self.session.as_ref().unwrap().expose_secret();
|
||||||
let session_id = session.session_id;
|
|
||||||
cookies.add_cookie_str("mobileClientVersion=0 (2.1.3)", &url);
|
cookies.add_cookie_str("mobileClientVersion=0 (2.1.3)", &url);
|
||||||
cookies.add_cookie_str("mobileClient=android", &url);
|
cookies.add_cookie_str("mobileClient=android", &url);
|
||||||
cookies.add_cookie_str("Steam_Language=english", &url);
|
cookies.add_cookie_str("Steam_Language=english", &url);
|
||||||
cookies.add_cookie_str("dob=", &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!("steamid={}", session.steam_id).as_str(), &url);
|
||||||
cookies.add_cookie_str(format!("steamLogin={}", session.steam_login).as_str(), &url);
|
cookies.add_cookie_str(format!("steamLogin={}", session.steam_login).as_str(), &url);
|
||||||
cookies.add_cookie_str(
|
cookies.add_cookie_str(
|
||||||
|
@ -226,12 +235,13 @@ impl SteamGuardAccount {
|
||||||
/// Returns whether or not the operation was successful.
|
/// Returns whether or not the operation was successful.
|
||||||
pub fn remove_authenticator(&self, revocation_code: Option<String>) -> anyhow::Result<bool> {
|
pub fn remove_authenticator(&self, revocation_code: Option<String>) -> anyhow::Result<bool> {
|
||||||
ensure!(
|
ensure!(
|
||||||
matches!(revocation_code, Some(_)) || !self.revocation_code.is_empty(),
|
matches!(revocation_code, Some(_)) || !self.revocation_code.expose_secret().is_empty(),
|
||||||
"Revocation code not provided."
|
"Revocation code not provided."
|
||||||
);
|
);
|
||||||
let client: SteamApiClient = SteamApiClient::new(self.session.clone());
|
let client: SteamApiClient = SteamApiClient::new(self.session.clone());
|
||||||
let resp =
|
let resp = client.remove_authenticator(
|
||||||
client.remove_authenticator(revocation_code.unwrap_or(self.revocation_code.clone()))?;
|
revocation_code.unwrap_or(self.revocation_code.expose_secret().to_owned()),
|
||||||
|
)?;
|
||||||
Ok(resp.success)
|
Ok(resp.success)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
53
steamguard/src/secret_string.rs
Normal file
53
steamguard/src/secret_string.rs
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
use secrecy::{ExposeSecret, SecretString};
|
||||||
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
|
|
||||||
|
/// Helper to allow serializing a [secrecy::SecretString] as a [String]
|
||||||
|
pub(crate) fn serialize<S>(secret_string: &SecretString, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
serializer.serialize_str(secret_string.expose_secret())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper to allow deserializing a [String] as a [secrecy::SecretString]
|
||||||
|
pub(crate) fn deserialize<'de, D>(d: D) -> Result<secrecy::SecretString, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let s = String::deserialize(d)?;
|
||||||
|
Ok(SecretString::new(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_secret_string_round_trip() {
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
struct Foo {
|
||||||
|
#[serde(with = "super")]
|
||||||
|
secret: SecretString,
|
||||||
|
}
|
||||||
|
|
||||||
|
let foo = Foo {
|
||||||
|
secret: String::from("hello").into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let s = serde_json::to_string(&foo).unwrap();
|
||||||
|
let foo2: Foo = serde_json::from_str(&s).unwrap();
|
||||||
|
assert_eq!(foo.secret.expose_secret(), foo2.secret.expose_secret());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_secret_string_deserialize() {
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
struct Foo {
|
||||||
|
#[serde(with = "super")]
|
||||||
|
secret: SecretString,
|
||||||
|
}
|
||||||
|
|
||||||
|
let foo: Foo = serde_json::from_str("{\"secret\": \"hello\"}").unwrap();
|
||||||
|
assert_eq!(foo.secret.expose_secret(), "hello");
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,11 +8,13 @@ use reqwest::{
|
||||||
header::{HeaderMap, HeaderName, HeaderValue, SET_COOKIE},
|
header::{HeaderMap, HeaderName, HeaderValue, SET_COOKIE},
|
||||||
Url,
|
Url,
|
||||||
};
|
};
|
||||||
|
use secrecy::{CloneableSecret, DebugSecret, ExposeSecret, SerializableSecret};
|
||||||
use serde::{Deserialize, Deserializer, Serialize};
|
use serde::{Deserialize, Deserializer, Serialize};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::iter::FromIterator;
|
use std::iter::FromIterator;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
use zeroize::Zeroize;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref STEAM_COOKIE_URL: Url = "https://steamcommunity.com".parse::<Url>().unwrap();
|
static ref STEAM_COOKIE_URL: Url = "https://steamcommunity.com".parse::<Url>().unwrap();
|
||||||
|
@ -90,7 +92,8 @@ pub struct OAuthData {
|
||||||
webcookie: String,
|
webcookie: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize, Zeroize)]
|
||||||
|
#[zeroize(drop)]
|
||||||
pub struct Session {
|
pub struct Session {
|
||||||
#[serde(rename = "SessionID")]
|
#[serde(rename = "SessionID")]
|
||||||
pub session_id: String,
|
pub session_id: String,
|
||||||
|
@ -106,6 +109,10 @@ pub struct Session {
|
||||||
pub steam_id: u64,
|
pub steam_id: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl SerializableSecret for Session {}
|
||||||
|
impl CloneableSecret for Session {}
|
||||||
|
impl DebugSecret for Session {}
|
||||||
|
|
||||||
pub fn get_server_time() -> i64 {
|
pub fn get_server_time() -> i64 {
|
||||||
let client = reqwest::blocking::Client::new();
|
let client = reqwest::blocking::Client::new();
|
||||||
let resp = client
|
let resp = client
|
||||||
|
@ -124,11 +131,11 @@ pub fn get_server_time() -> i64 {
|
||||||
pub struct SteamApiClient {
|
pub struct SteamApiClient {
|
||||||
cookies: reqwest::cookie::Jar,
|
cookies: reqwest::cookie::Jar,
|
||||||
client: reqwest::blocking::Client,
|
client: reqwest::blocking::Client,
|
||||||
pub session: Option<Session>,
|
pub session: Option<secrecy::Secret<Session>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SteamApiClient {
|
impl SteamApiClient {
|
||||||
pub fn new(session: Option<Session>) -> SteamApiClient {
|
pub fn new(session: Option<secrecy::Secret<Session>>) -> SteamApiClient {
|
||||||
SteamApiClient {
|
SteamApiClient {
|
||||||
cookies: reqwest::cookie::Jar::default(),
|
cookies: reqwest::cookie::Jar::default(),
|
||||||
client: reqwest::blocking::ClientBuilder::new()
|
client: reqwest::blocking::ClientBuilder::new()
|
||||||
|
@ -195,7 +202,7 @@ impl SteamApiClient {
|
||||||
.add_cookie_str("Steam_Language=english", &STEAM_COOKIE_URL);
|
.add_cookie_str("Steam_Language=english", &STEAM_COOKIE_URL);
|
||||||
if let Some(session) = &self.session {
|
if let Some(session) = &self.session {
|
||||||
self.cookies.add_cookie_str(
|
self.cookies.add_cookie_str(
|
||||||
format!("sessionid={}", session.session_id).as_str(),
|
format!("sessionid={}", session.expose_secret().session_id).as_str(),
|
||||||
&STEAM_COOKIE_URL,
|
&STEAM_COOKIE_URL,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -270,7 +277,7 @@ impl SteamApiClient {
|
||||||
let login_resp: LoginResponse = serde_json::from_str(text.as_str())?;
|
let login_resp: LoginResponse = serde_json::from_str(text.as_str())?;
|
||||||
|
|
||||||
if let Some(oauth) = &login_resp.oauth {
|
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);
|
return Ok(login_resp);
|
||||||
|
@ -295,7 +302,7 @@ impl SteamApiClient {
|
||||||
wgtoken_secure: params.token_secure,
|
wgtoken_secure: params.token_secure,
|
||||||
webcookie: params.webcookie,
|
webcookie: params.webcookie,
|
||||||
};
|
};
|
||||||
self.session = Some(self.build_session(&oauth));
|
self.session = Some(secrecy::Secret::new(self.build_session(&oauth)));
|
||||||
return Ok(oauth);
|
return Ok(oauth);
|
||||||
}
|
}
|
||||||
(None, None) => {
|
(None, None) => {
|
||||||
|
@ -319,7 +326,7 @@ impl SteamApiClient {
|
||||||
let mut params = hashmap! {
|
let mut params = hashmap! {
|
||||||
"op" => op,
|
"op" => op,
|
||||||
"arg" => arg,
|
"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" {
|
if op == "check_sms_code" {
|
||||||
params.insert("checkfortos", "0");
|
params.insert("checkfortos", "0");
|
||||||
|
@ -365,7 +372,7 @@ impl SteamApiClient {
|
||||||
let params = hashmap! {
|
let params = hashmap! {
|
||||||
"op" => op,
|
"op" => op,
|
||||||
"input" => input,
|
"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
|
let resp = self
|
||||||
|
@ -421,8 +428,8 @@ impl SteamApiClient {
|
||||||
) -> anyhow::Result<AddAuthenticatorResponse> {
|
) -> anyhow::Result<AddAuthenticatorResponse> {
|
||||||
ensure!(matches!(self.session, Some(_)));
|
ensure!(matches!(self.session, Some(_)));
|
||||||
let params = hashmap! {
|
let params = hashmap! {
|
||||||
"access_token" => self.session.as_ref().unwrap().token.clone(),
|
"access_token" => self.session.as_ref().unwrap().expose_secret().token.clone(),
|
||||||
"steamid" => self.session.as_ref().unwrap().steam_id.to_string(),
|
"steamid" => self.session.as_ref().unwrap().expose_secret().steam_id.to_string(),
|
||||||
"authenticator_type" => "1".into(),
|
"authenticator_type" => "1".into(),
|
||||||
"device_identifier" => device_id,
|
"device_identifier" => device_id,
|
||||||
"sms_phone_id" => "1".into(),
|
"sms_phone_id" => "1".into(),
|
||||||
|
@ -454,8 +461,8 @@ impl SteamApiClient {
|
||||||
) -> anyhow::Result<FinalizeAddAuthenticatorResponse> {
|
) -> anyhow::Result<FinalizeAddAuthenticatorResponse> {
|
||||||
ensure!(matches!(self.session, Some(_)));
|
ensure!(matches!(self.session, Some(_)));
|
||||||
let params = hashmap! {
|
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(),
|
||||||
"access_token" => self.session.as_ref().unwrap().token.clone(),
|
"access_token" => self.session.as_ref().unwrap().expose_secret().token.clone(),
|
||||||
"activation_code" => sms_code,
|
"activation_code" => sms_code,
|
||||||
"authenticator_code" => code_2fa,
|
"authenticator_code" => code_2fa,
|
||||||
"authenticator_time" => time_2fa.to_string(),
|
"authenticator_time" => time_2fa.to_string(),
|
||||||
|
@ -485,10 +492,10 @@ impl SteamApiClient {
|
||||||
revocation_code: String,
|
revocation_code: String,
|
||||||
) -> anyhow::Result<RemoveAuthenticatorResponse> {
|
) -> anyhow::Result<RemoveAuthenticatorResponse> {
|
||||||
let params = hashmap! {
|
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(),
|
"steamguard_scheme" => "2".into(),
|
||||||
"revocation_code" => revocation_code,
|
"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
|
let resp = self
|
||||||
|
@ -612,13 +619,13 @@ impl AddAuthenticatorResponse {
|
||||||
SteamGuardAccount {
|
SteamGuardAccount {
|
||||||
shared_secret: TwoFactorSecret::parse_shared_secret(self.shared_secret).unwrap(),
|
shared_secret: TwoFactorSecret::parse_shared_secret(self.shared_secret).unwrap(),
|
||||||
serial_number: self.serial_number.clone(),
|
serial_number: self.serial_number.clone(),
|
||||||
revocation_code: self.revocation_code.clone(),
|
revocation_code: self.revocation_code.into(),
|
||||||
uri: self.uri.clone(),
|
uri: self.uri.into(),
|
||||||
server_time: self.server_time,
|
server_time: self.server_time,
|
||||||
account_name: self.account_name.clone(),
|
account_name: self.account_name.clone(),
|
||||||
token_gid: self.token_gid.clone(),
|
token_gid: self.token_gid.clone(),
|
||||||
identity_secret: self.identity_secret.clone(),
|
identity_secret: self.identity_secret.into(),
|
||||||
secret_1: self.secret_1.clone(),
|
secret_1: self.secret_1.into(),
|
||||||
fully_enrolled: false,
|
fully_enrolled: false,
|
||||||
device_id: "".into(),
|
device_id: "".into(),
|
||||||
session: None,
|
session: None,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::steamapi::{LoginResponse, RsaResponse, Session, SteamApiClient};
|
use crate::steamapi::{LoginResponse, RsaResponse, Session, SteamApiClient};
|
||||||
use log::*;
|
use log::*;
|
||||||
use rsa::{PublicKey, RsaPublicKey};
|
use rsa::{PublicKey, RsaPublicKey};
|
||||||
|
use secrecy::ExposeSecret;
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -154,7 +155,13 @@ impl UserLogin {
|
||||||
self.client.transfer_login(login_resp)?;
|
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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue