1mod confirmation;
2mod guard_data;
3
4use crate::connection::raw::RawConnection;
5use crate::connection::unauthenticated::service_method_un_authenticated;
6use crate::message::NetMessage;
7use crate::message::{MalformedBody, ServiceMethodMessage};
8use crate::net::NetworkError;
9use crate::proto::enums::ESessionPersistence;
10use crate::proto::steammessages_auth_steamclient::CAuthentication_GetPasswordRSAPublicKey_Request;
11use crate::proto::steammessages_auth_steamclient::{
12 CAuthentication_AllowedConfirmation, CAuthentication_BeginAuthSessionViaCredentials_Request,
13 CAuthentication_BeginAuthSessionViaCredentials_Response, CAuthentication_DeviceDetails,
14 CAuthentication_PollAuthSessionStatus_Request, CAuthentication_PollAuthSessionStatus_Response,
15 CAuthentication_UpdateAuthSessionWithSteamGuardCode_Request, EAuthSessionGuardType,
16 EAuthTokenPlatformType,
17};
18use crate::session::{ConnectionError, LoginError};
19use base64::prelude::BASE64_STANDARD;
20use base64::Engine;
21pub use confirmation::*;
22pub use guard_data::*;
23use num_bigint_dig::BigUint;
24use num_traits::Num;
25use protobuf::{EnumOrUnknown, MessageField};
26use rsa::RsaPublicKey;
27use std::time::Duration;
28use steam_vent_crypto::encrypt_with_key_pkcs1;
29use thiserror::Error;
30use tokio::time::sleep;
31use tracing::{debug, info, instrument};
32
33pub(crate) async fn begin_password_auth(
34 connection: &mut RawConnection,
35 account: &str,
36 password: &str,
37 guard_data: Option<&str>,
38) -> Result<StartedAuth, ConnectionError> {
39 let (pub_key, timestamp) = get_password_rsa(connection, account.into()).await?;
40 let encrypted_password =
41 encrypt_with_key_pkcs1(&pub_key, password.as_bytes()).map_err(LoginError::InvalidPubKey)?;
42 let encoded_password = BASE64_STANDARD.encode(encrypted_password);
43 info!(account, "starting credentials login");
44 let req = CAuthentication_BeginAuthSessionViaCredentials_Request {
45 account_name: Some(account.into()),
46 encrypted_password: Some(encoded_password),
47 encryption_timestamp: Some(timestamp),
48 persistence: Some(EnumOrUnknown::new(
49 ESessionPersistence::k_ESessionPersistence_Persistent,
50 )),
51
52 website_id: Some("Client".into()),
54 device_details: MessageField::some(CAuthentication_DeviceDetails {
55 device_friendly_name: Some("DESKTOP-VENT".into()),
56 platform_type: Some(EnumOrUnknown::new(
57 EAuthTokenPlatformType::k_EAuthTokenPlatformType_SteamClient,
58 )),
59 os_type: Some(1),
60 ..CAuthentication_DeviceDetails::default()
61 }),
62 guard_data: guard_data.map(String::from),
63 ..CAuthentication_BeginAuthSessionViaCredentials_Request::default()
64 };
65 let res = service_method_un_authenticated(connection, req).await?;
66 Ok(StartedAuth::Credentials(res))
67}
68
69pub(crate) enum StartedAuth {
70 Credentials(CAuthentication_BeginAuthSessionViaCredentials_Response),
71}
72
73#[derive(Debug, Error)]
74#[non_exhaustive]
75pub enum ConfirmationError {
76 #[error(transparent)]
77 Network(#[from] NetworkError),
78 #[error("Aborted")]
79 Aborted,
80}
81
82impl StartedAuth {
83 fn raw_confirmations(&self) -> &[CAuthentication_AllowedConfirmation] {
84 match self {
85 StartedAuth::Credentials(res) => res.allowed_confirmations.as_slice(),
86 }
87 }
88
89 pub fn allowed_confirmations(&self) -> Vec<ConfirmationMethod> {
90 self.raw_confirmations()
91 .iter()
92 .cloned()
93 .map(ConfirmationMethod::from)
94 .collect()
95 }
96
97 #[allow(dead_code)]
98 pub fn action_required(&self) -> bool {
99 self.raw_confirmations().iter().any(|method| {
100 method.confirmation_type() != EAuthSessionGuardType::k_EAuthSessionGuardType_None
101 })
102 }
103
104 fn client_id(&self) -> u64 {
105 match self {
106 StartedAuth::Credentials(res) => res.client_id(),
107 }
108 }
109
110 pub fn steam_id(&self) -> u64 {
111 match self {
112 StartedAuth::Credentials(res) => res.steamid(),
113 }
114 }
115
116 fn request_id(&self) -> Vec<u8> {
117 match self {
118 StartedAuth::Credentials(res) => res.request_id().into(),
119 }
120 }
121
122 fn interval(&self) -> f32 {
123 match self {
124 StartedAuth::Credentials(res) => res.interval(),
125 }
126 }
127
128 pub fn poll(&self) -> PendingAuth {
129 PendingAuth {
130 interval: self.interval(),
131 client_id: self.client_id(),
132 request_id: self.request_id(),
133 }
134 }
135
136 pub async fn submit_confirmation(
137 &self,
138 connection: &RawConnection,
139 confirmation: ConfirmationAction,
140 ) -> Result<(), ConfirmationError> {
141 match confirmation {
142 ConfirmationAction::GuardToken(token, ty) => {
143 let req = CAuthentication_UpdateAuthSessionWithSteamGuardCode_Request {
144 client_id: Some(self.client_id()),
145 steamid: Some(self.steam_id()),
146 code: Some(token.0),
147 code_type: Some(EnumOrUnknown::new(ty.into())),
148 ..CAuthentication_UpdateAuthSessionWithSteamGuardCode_Request::default()
149 };
150 let _ = service_method_un_authenticated(connection, req).await?;
151 }
152 ConfirmationAction::None => {}
153 ConfirmationAction::Abort => return Err(ConfirmationError::Aborted),
154 };
155 Ok(())
156 }
157}
158
159#[derive(Debug)]
161pub struct SteamGuardToken(String);
162
163pub(crate) struct PendingAuth {
164 client_id: u64,
165 request_id: Vec<u8>,
166 interval: f32,
167}
168
169impl PendingAuth {
170 pub(crate) async fn wait_for_tokens(
171 self,
172 connection: &RawConnection,
173 ) -> Result<Tokens, NetworkError> {
174 loop {
175 let mut response = poll_until_info(
176 connection,
177 self.client_id,
178 &self.request_id,
179 Duration::from_secs_f32(self.interval),
180 )
181 .await?;
182 if response.has_access_token() {
183 return Ok(Tokens {
184 access_token: Token(response.take_access_token()),
185 refresh_token: Token(response.take_refresh_token()),
186 new_guard_data: response.new_guard_data,
187 });
188 }
189 }
190 }
191}
192
193#[derive(Debug, Clone)]
194pub(crate) struct Token(String);
195
196impl AsRef<str> for Token {
197 fn as_ref(&self) -> &str {
198 self.0.as_ref()
199 }
200}
201
202#[derive(Debug, Clone)]
203pub(crate) struct Tokens {
204 #[allow(dead_code)]
205 pub access_token: Token,
206 pub refresh_token: Token,
207 pub new_guard_data: Option<String>,
208}
209
210async fn poll_until_info(
211 connection: &RawConnection,
212 client_id: u64,
213 request_id: &[u8],
214 interval: Duration,
215) -> Result<CAuthentication_PollAuthSessionStatus_Response, NetworkError> {
216 loop {
217 let req = CAuthentication_PollAuthSessionStatus_Request {
218 client_id: Some(client_id),
219 request_id: Some(request_id.into()),
220 ..CAuthentication_PollAuthSessionStatus_Request::default()
221 };
222
223 let resp = service_method_un_authenticated(connection, req).await?;
224 let has_data = resp.has_access_token()
225 || resp.has_account_name()
226 || resp.has_agreement_session_url()
227 || resp.has_had_remote_interaction()
228 || resp.has_new_challenge_url()
229 || resp.has_new_client_id()
230 || resp.has_new_guard_data()
231 || resp.has_refresh_token();
232
233 if has_data {
234 return Ok(resp);
235 }
236
237 sleep(interval).await;
238 }
239}
240
241#[instrument(skip(connection))]
242async fn get_password_rsa(
243 connection: &mut RawConnection,
244 account: String,
245) -> Result<(RsaPublicKey, u64), NetworkError> {
246 debug!("getting password rsa");
247 let req = CAuthentication_GetPasswordRSAPublicKey_Request {
248 account_name: Some(account),
249 ..CAuthentication_GetPasswordRSAPublicKey_Request::default()
250 };
251 let response = service_method_un_authenticated(connection, req).await?;
252
253 let key_mod =
254 BigUint::from_str_radix(response.publickey_mod.as_deref().unwrap_or_default(), 16)
255 .map_err(|e| {
256 MalformedBody::new(
257 ServiceMethodMessage::<CAuthentication_GetPasswordRSAPublicKey_Request>::KIND,
258 e,
259 )
260 })?;
261 let key_exp =
262 BigUint::from_str_radix(response.publickey_exp.as_deref().unwrap_or_default(), 16)
263 .map_err(|e| {
264 MalformedBody::new(
265 ServiceMethodMessage::<CAuthentication_GetPasswordRSAPublicKey_Request>::KIND,
266 e,
267 )
268 })?;
269 let key = RsaPublicKey::new(key_mod, key_exp).map_err(|e| {
270 MalformedBody::new(
271 ServiceMethodMessage::<CAuthentication_GetPasswordRSAPublicKey_Request>::KIND,
272 e,
273 )
274 })?;
275 Ok((key, response.timestamp.unwrap_or_default()))
276}