Add mobile client IDs and improve hash cash logic

This commit is contained in:
Roderick van Domburg 2022-08-26 22:29:00 +02:00
parent 111c7781d2
commit 10650712a7
No known key found for this signature in database
GPG key ID: FE2585E713F9F30A
2 changed files with 36 additions and 9 deletions

View file

@ -3,6 +3,8 @@ use std::{fmt, path::PathBuf, str::FromStr};
use url::Url; use url::Url;
pub(crate) const KEYMASTER_CLIENT_ID: &str = "65b708073fc0480ea92a077233ca87bd"; pub(crate) const KEYMASTER_CLIENT_ID: &str = "65b708073fc0480ea92a077233ca87bd";
pub(crate) const ANDROID_CLIENT_ID: &str = "9a8d2f0ce77a4e248bb71fefcb557637";
pub(crate) const IOS_CLIENT_ID: &str = "58bd3c95768941ea9eb4350aaa033eb3";
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct SessionConfig { pub struct SessionConfig {
@ -16,8 +18,15 @@ pub struct SessionConfig {
impl Default for SessionConfig { impl Default for SessionConfig {
fn default() -> SessionConfig { fn default() -> SessionConfig {
let device_id = uuid::Uuid::new_v4().as_hyphenated().to_string(); let device_id = uuid::Uuid::new_v4().as_hyphenated().to_string();
let client_id = match std::env::consts::OS {
"android" => ANDROID_CLIENT_ID,
"ios" => IOS_CLIENT_ID,
_ => KEYMASTER_CLIENT_ID,
}
.to_owned();
SessionConfig { SessionConfig {
client_id: KEYMASTER_CLIENT_ID.to_owned(), client_id,
device_id, device_id,
proxy: None, proxy: None,
ap_port: None, ap_port: None,

View file

@ -1,5 +1,6 @@
use std::{ use std::{
convert::TryInto, convert::TryInto,
env::consts::OS,
fmt::Write, fmt::Write,
time::{Duration, Instant}, time::{Duration, Instant},
}; };
@ -22,7 +23,7 @@ use thiserror::Error;
use crate::{ use crate::{
apresolve::SocketAddress, apresolve::SocketAddress,
cdn_url::CdnUrl, cdn_url::CdnUrl,
config::KEYMASTER_CLIENT_ID, config::SessionConfig,
error::ErrorKind, error::ErrorKind,
protocol::{ protocol::{
canvaz::EntityCanvazRequest, canvaz::EntityCanvazRequest,
@ -165,9 +166,17 @@ impl SpClient {
let client_data = message.mut_client_data(); let client_data = message.mut_client_data();
client_data.set_client_version(spotify_version()); client_data.set_client_version(spotify_version());
// using 9a8d2f0ce77a4e248bb71fefcb557637 on Android // Current state of affairs: keymaster ID works on all tested platforms, but may be phased out,
// instead of the keymaster ID presents a hash cash challenge // so it seems a good idea to mimick the real clients. `self.session().client_id()` returns the
client_data.set_client_id(KEYMASTER_CLIENT_ID.to_string()); // ID of the client that last connected, but requesting a client token with this ID only works
// on macOS and Windows. On Android and iOS we can send a platform-specific client ID and are
// then presented with a hash cash challenge. On Linux, we have to pass the old keymaster ID.
// We delegate most of this logic to `SessionConfig`.
let client_id = match OS {
"macos" | "windows" => self.session().client_id(),
_ => SessionConfig::default().client_id,
};
client_data.set_client_id(client_id);
let connectivity_data = client_data.mut_connectivity_sdk_data(); let connectivity_data = client_data.mut_connectivity_sdk_data();
connectivity_data.set_device_id(self.session().device_id().to_string()); connectivity_data.set_device_id(self.session().device_id().to_string());
@ -178,7 +187,7 @@ impl SpClient {
let os_version = sys.os_version().unwrap_or_else(|| String::from("0")); let os_version = sys.os_version().unwrap_or_else(|| String::from("0"));
let kernel_version = sys.kernel_version().unwrap_or_else(|| String::from("0")); let kernel_version = sys.kernel_version().unwrap_or_else(|| String::from("0"));
match std::env::consts::OS { match OS {
"windows" => { "windows" => {
let os_version = os_version.parse::<f32>().unwrap_or(10.) as i32; let os_version = os_version.parse::<f32>().unwrap_or(10.) as i32;
let kernel_version = kernel_version.parse::<i32>().unwrap_or(21370); let kernel_version = kernel_version.parse::<i32>().unwrap_or(21370);
@ -238,7 +247,7 @@ impl SpClient {
// or are presented a hash cash challenge to solve first // or are presented a hash cash challenge to solve first
Some(ClientTokenResponseType::RESPONSE_GRANTED_TOKEN_RESPONSE) => break message, Some(ClientTokenResponseType::RESPONSE_GRANTED_TOKEN_RESPONSE) => break message,
Some(ClientTokenResponseType::RESPONSE_CHALLENGES_RESPONSE) => { Some(ClientTokenResponseType::RESPONSE_CHALLENGES_RESPONSE) => {
trace!("received a hash cash challenge"); trace!("received a hash cash challenge, solving...");
let challenges = message.get_challenges().clone(); let challenges = message.get_challenges().clone();
let state = challenges.get_state(); let state = challenges.get_state();
@ -248,7 +257,7 @@ impl SpClient {
let ctx = vec![]; let ctx = vec![];
let prefix = hex::decode(&hash_cash_challenge.prefix).map_err(|e| { let prefix = hex::decode(&hash_cash_challenge.prefix).map_err(|e| {
Error::failed_precondition(format!( Error::failed_precondition(format!(
"unable to decode Hashcash challenge: {}", "unable to decode hash cash challenge: {}",
e e
)) ))
})?; })?;
@ -274,7 +283,16 @@ impl SpClient {
challenge_answers.state = state.to_string(); challenge_answers.state = state.to_string();
challenge_answers.answers.push(challenge_answer); challenge_answers.answers.push(challenge_answer);
response = self.client_token_request(&answer_message).await?; trace!("answering hash cash challenge");
match self.client_token_request(&answer_message).await {
Ok(token) => response = token,
Err(e) => {
return Err(Error::failed_precondition(format!(
"unable to solve this challenge: {}",
e
)))
}
}
// we should have been granted a token now // we should have been granted a token now
continue; continue;