tsar_sdk/structs/
client.rs1use super::user::User;
2use crate::errors::TsarError;
3use base64::prelude::*;
4use hardware_id::get_id;
5use p256::{
6 ecdsa::{signature::Verifier, Signature, VerifyingKey},
7 pkcs8::DecodePublicKey,
8};
9use reqwest::StatusCode;
10use rsntp::SntpClient;
11use serde::de::DeserializeOwned;
12use serde::Deserialize;
13use serde_json::Value;
14use sha2::Digest;
15use sha2::Sha256;
16use std::env::current_exe;
17use std::fs::File;
18use std::io::Read;
19use std::time::{Duration, SystemTime, UNIX_EPOCH};
20
21#[derive(Debug)]
23pub struct Client {
24 pub app_id: String,
26 pub client_key: String,
28 pub dashboard_hostname: String,
30}
31
32#[derive(Debug)]
34pub struct ClientParams {
35 pub app_id: String,
37 pub client_key: String,
39}
40
41#[derive(Debug)]
43pub struct AuthParams {
44 pub open_browser: bool,
47}
48
49impl Default for AuthParams {
50 fn default() -> Self {
51 Self { open_browser: true }
52 }
53}
54
55#[derive(Deserialize)]
57struct InitializeReturnData {
58 dashboard_hostname: String,
59}
60
61impl Client {
62 pub fn get_hwid() -> Result<String, TsarError> {
64 get_id().or(Err(TsarError::FailedToGetHWID))
65 }
66
67 pub fn get_hash() -> Result<String, TsarError> {
69 let current_exe = current_exe().or(Err(TsarError::FailedToGetHash))?;
70
71 let mut file = File::open(¤t_exe).or(Err(TsarError::FailedToGetHash))?;
72 let mut hasher = Sha256::new();
73 let mut buffer = vec![0; 1024];
74
75 loop {
76 let count = file.read(&mut buffer).or(Err(TsarError::FailedToGetHash))?;
77 if count == 0 {
78 break;
79 }
80 hasher.update(&buffer[..count]);
81 }
82
83 let hash_result = hasher.finalize();
84 Ok(format!("{:x}", hash_result))
85 }
86
87 pub fn create(options: ClientParams) -> Result<Self, TsarError> {
89 if options.app_id.len() != 36 {
91 return Err(TsarError::InvalidAppId);
92 }
93
94 if options.client_key.len() != 124 {
95 return Err(TsarError::InvalidClientKey);
96 }
97
98 let params = vec![("app_id", options.app_id.as_str())];
100
101 let init_result = Client::encrypted_api_call::<InitializeReturnData>(
102 "initialize",
103 options.client_key.as_str(),
104 params,
105 )?;
106
107 Ok(Self {
108 app_id: options.app_id.to_string(),
109 client_key: options.client_key.to_string(),
110 dashboard_hostname: init_result.dashboard_hostname,
111 })
112 }
113
114 pub fn authenticate(&self, options: AuthParams) -> Result<User, TsarError> {
117 let params = vec![("app_id", self.app_id.as_str())];
118
119 let auth_result =
120 Client::encrypted_api_call::<User>("authenticate", &self.client_key, params);
121
122 let hwid = Self::get_hwid()?;
123
124 match auth_result {
125 Ok(user) => return Ok(user),
126 Err(TsarError::Unauthorized) => {
127 if options.open_browser {
128 let _ =
129 open::that(format!("https://{}/auth/{}", self.dashboard_hostname, hwid));
130 }
131 return Err(TsarError::Unauthorized);
132 }
133 Err(TsarError::HashUnauthorized) => {
134 if options.open_browser {
135 let _ = open::that(format!(
136 "https://{}/assets?outdated=true",
137 self.dashboard_hostname
138 ));
139 }
140 return Err(TsarError::HashUnauthorized);
141 }
142 Err(err) => return Err(err),
143 }
144 }
145
146 pub fn encrypted_api_call<T: DeserializeOwned>(
148 path: &str,
149 public_key: &str,
150 params: Vec<(&str, &str)>,
152 ) -> Result<T, TsarError> {
153 let hwid = Client::get_hwid()?;
154 let hash = Client::get_hash()?;
155
156 let pub_key_bytes = BASE64_STANDARD
158 .decode(public_key)
159 .or(Err(TsarError::InvalidClientKey))
160 .unwrap();
161
162 let pub_key: VerifyingKey =
164 VerifyingKey::from_public_key_der(pub_key_bytes[..].try_into().unwrap())
165 .or(Err(TsarError::InvalidClientKey))?;
166
167 let path = if path.starts_with('/') {
169 path.to_string()
170 } else {
171 format!("/{}", path)
172 };
173
174 let mut full_params = params.to_vec();
176 full_params.push(("hwid", &hwid));
177 full_params.push(("hash", &hash));
178
179 let url = reqwest::Url::parse_with_params(
181 &format!("https://tsar.cc/api/client{}", path),
182 &full_params,
183 )
184 .or(Err(TsarError::RequestFailed))?;
185
186 let response = reqwest::blocking::get(url).or(Err(TsarError::RequestFailed))?;
187
188 if !response.status().is_success() {
189 match response.status() {
190 StatusCode::BAD_REQUEST => return Err(TsarError::BadRequest),
191 StatusCode::NOT_FOUND => return Err(TsarError::AppNotFound),
192 StatusCode::UNAUTHORIZED => return Err(TsarError::Unauthorized),
193 StatusCode::TOO_MANY_REQUESTS => return Err(TsarError::RateLimited),
194 StatusCode::SERVICE_UNAVAILABLE => return Err(TsarError::AppPaused),
195 StatusCode::FORBIDDEN => return Err(TsarError::HashUnauthorized),
196 _ => return Err(TsarError::ServerError),
197 }
198 }
199
200 let data = response
202 .json::<Value>()
203 .or(Err(TsarError::FailedToDecode))?;
204
205 let base64_data = data
207 .get("data")
208 .and_then(|v| v.as_str())
209 .ok_or(TsarError::FailedToDecode)?;
210
211 let base64_signature = data
213 .get("signature")
214 .and_then(|v| v.as_str())
215 .ok_or(TsarError::FailedToDecode)?;
216
217 let data_bytes = BASE64_STANDARD
219 .decode(base64_data)
220 .or(Err(TsarError::FailedToDecode))?;
221
222 let json_string =
224 String::from_utf8(data_bytes.clone()).or(Err(TsarError::FailedToDecode))?;
225
226 let json: Value = serde_json::from_str(&json_string).or(Err(TsarError::FailedToDecode))?;
228
229 if let Some(hwid_value) = json.get("hwid") {
231 if let Some(hwid_str) = hwid_value.as_str() {
232 if hwid != hwid_str {
233 return Err(TsarError::StateMismatch);
234 }
235 } else {
236 return Err(TsarError::FailedToDecode);
237 }
238 } else {
239 return Err(TsarError::FailedToDecode);
240 }
241
242 let timestamp = match json.get("timestamp").and_then(|ts| ts.as_u64()) {
244 Some(ts_secs) => {
245 let duration_secs = Duration::from_secs(ts_secs);
246 UNIX_EPOCH
247 .checked_add(duration_secs)
248 .ok_or(TsarError::FailedToDecode)?
249 }
250 None => return Err(TsarError::FailedToDecode),
251 };
252
253 let client = SntpClient::new();
255 let ntp_time = client
256 .synchronize("time.cloudflare.com")
257 .unwrap()
258 .datetime()
259 .into_system_time()
260 .unwrap();
261
262 let system_time = SystemTime::now();
264
265 let duration = if ntp_time > system_time {
266 ntp_time.duration_since(system_time).unwrap()
267 } else {
268 system_time.duration_since(ntp_time).unwrap()
269 };
270
271 if duration.as_millis() > 300000 || timestamp < (system_time - Duration::from_secs(300)) {
273 return Err(TsarError::TamperedResponse);
274 }
275
276 let signature_bytes = BASE64_STANDARD
278 .decode(base64_signature)
279 .or(Err(TsarError::FailedToDecode))?;
280
281 let mut signature = Signature::from_bytes(signature_bytes[..].try_into().unwrap())
283 .or(Err(TsarError::FailedToDecode))?;
284
285 signature = signature.normalize_s().unwrap_or(signature);
286
287 let result = pub_key.verify(&data_bytes, &signature);
289
290 if result.is_ok() {
291 if std::any::type_name::<T>() == "()" {
292 return Ok(serde_json::from_value(Value::Null).unwrap());
293 }
294
295 let actual_data = json.get("data").ok_or(TsarError::FailedToDecode)?;
297 return Ok(serde_json::from_value(actual_data.clone()).unwrap());
298 }
299
300 Err(TsarError::FailedToDecode)
301 }
302}