mirror of
https://github.com/librespot-org/librespot.git
synced 2024-12-18 17:11:53 +00:00
Move authentication to a different directory, and make discover use hyper.
This commit is contained in:
parent
85903a0da5
commit
0770f6ce61
12 changed files with 223 additions and 328 deletions
100
Cargo.lock
generated
100
Cargo.lock
generated
|
@ -26,7 +26,6 @@ dependencies = [
|
||||||
"syntex 0.29.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"syntex 0.29.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"tempfile 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"tempfile 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"time 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)",
|
"time 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"tiny_http 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"tremor 0.1.0 (git+https://github.com/plietar/rust-tremor)",
|
"tremor 0.1.0 (git+https://github.com/plietar/rust-tremor)",
|
||||||
"url 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
"url 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"vergen 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"vergen 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
@ -41,11 +40,6 @@ dependencies = [
|
||||||
"memchr 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
"memchr 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ascii"
|
|
||||||
version = "0.5.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aster"
|
name = "aster"
|
||||||
version = "0.13.1"
|
version = "0.13.1"
|
||||||
|
@ -92,20 +86,6 @@ name = "byteorder"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "chrono"
|
|
||||||
version = "0.2.20"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"num 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"time 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "chunked_transfer"
|
|
||||||
version = "0.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clippy"
|
name = "clippy"
|
||||||
version = "0.0.53"
|
version = "0.0.53"
|
||||||
|
@ -137,63 +117,6 @@ dependencies = [
|
||||||
"pkg-config 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
"pkg-config 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "encoding"
|
|
||||||
version = "0.2.32"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"encoding-index-japanese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"encoding-index-korean 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"encoding-index-simpchinese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"encoding-index-singlebyte 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"encoding-index-tradchinese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "encoding-index-japanese"
|
|
||||||
version = "1.20141219.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "encoding-index-korean"
|
|
||||||
version = "1.20141219.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "encoding-index-simpchinese"
|
|
||||||
version = "1.20141219.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "encoding-index-singlebyte"
|
|
||||||
version = "1.20141219.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "encoding-index-tradchinese"
|
|
||||||
version = "1.20141219.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "encoding_index_tests"
|
|
||||||
version = "0.1.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "eventual"
|
name = "eventual"
|
||||||
version = "0.1.5"
|
version = "0.1.5"
|
||||||
|
@ -691,19 +614,6 @@ dependencies = [
|
||||||
"winapi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"winapi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tiny_http"
|
|
||||||
version = "0.5.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"ascii 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"chrono 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"chunked_transfer 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"encoding 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"rustc-serialize 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"url 0.2.38 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml"
|
name = "toml"
|
||||||
version = "0.1.28"
|
version = "0.1.28"
|
||||||
|
@ -767,16 +677,6 @@ name = "unicode-xid"
|
||||||
version = "0.0.3"
|
version = "0.0.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "url"
|
|
||||||
version = "0.2.38"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"rustc-serialize 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"uuid 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "url"
|
name = "url"
|
||||||
version = "0.5.7"
|
version = "0.5.7"
|
||||||
|
|
|
@ -32,7 +32,6 @@ rust-crypto = "~0.2.34"
|
||||||
rustc-serialize = "~0.3.16"
|
rustc-serialize = "~0.3.16"
|
||||||
tempfile = "~2.0.0"
|
tempfile = "~2.0.0"
|
||||||
time = "~0.1.34"
|
time = "~0.1.34"
|
||||||
tiny_http = "~0.5.1"
|
|
||||||
url = "~0.5.2"
|
url = "~0.5.2"
|
||||||
vorbis = "~0.0.14"
|
vorbis = "~0.0.14"
|
||||||
|
|
||||||
|
|
188
src/authentication/discovery.rs
Normal file
188
src/authentication/discovery.rs
Normal file
|
@ -0,0 +1,188 @@
|
||||||
|
use crypto;
|
||||||
|
use crypto::mac::Mac;
|
||||||
|
use crypto::digest::Digest;
|
||||||
|
use dns_sd::DNSService;
|
||||||
|
use hyper;
|
||||||
|
use hyper::net::NetworkListener;
|
||||||
|
use num::BigUint;
|
||||||
|
use url;
|
||||||
|
use rand;
|
||||||
|
use rustc_serialize::base64::{self, ToBase64, FromBase64};
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
use std::sync::{mpsc, Mutex};
|
||||||
|
|
||||||
|
use authentication::Credentials;
|
||||||
|
use diffie_hellman::{DH_GENERATOR, DH_PRIME};
|
||||||
|
use util;
|
||||||
|
|
||||||
|
struct ServerHandler {
|
||||||
|
credentials_tx: Mutex<mpsc::Sender<Credentials>>,
|
||||||
|
private_key: BigUint,
|
||||||
|
public_key: BigUint,
|
||||||
|
device_id: String,
|
||||||
|
device_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ServerHandler {
|
||||||
|
fn handle_get_info(&self, _params: &BTreeMap<String, String>,
|
||||||
|
mut response: hyper::server::Response<hyper::net::Fresh>) {
|
||||||
|
|
||||||
|
let public_key = self.public_key.to_bytes_be()
|
||||||
|
.to_base64(base64::STANDARD);
|
||||||
|
|
||||||
|
let result = json!({
|
||||||
|
"status": 101,
|
||||||
|
"statusString": "ERROR-OK",
|
||||||
|
"spotifyError": 0,
|
||||||
|
"version": "2.1.0",
|
||||||
|
"deviceID": (self.device_id),
|
||||||
|
"remoteName": (self.device_name),
|
||||||
|
"activeUser": "",
|
||||||
|
"publicKey": (public_key),
|
||||||
|
"deviceType": "UNKNOWN",
|
||||||
|
"libraryVersion": "0.1.0",
|
||||||
|
"accountReq": "PREMIUM",
|
||||||
|
"brandDisplayName": "librespot",
|
||||||
|
"modelDisplayName": "librespot",
|
||||||
|
});
|
||||||
|
|
||||||
|
*response.status_mut() = hyper::status::StatusCode::Ok;
|
||||||
|
response.start().unwrap().write_all(result.to_string().as_bytes()).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_add_user(&self, params: &BTreeMap<String, String>,
|
||||||
|
mut response: hyper::server::Response<hyper::net::Fresh>) {
|
||||||
|
|
||||||
|
let username = params.get("userName").unwrap();
|
||||||
|
let encrypted_blob = params.get("blob").unwrap();
|
||||||
|
let client_key = params.get("clientKey").unwrap();
|
||||||
|
|
||||||
|
let encrypted_blob = encrypted_blob.from_base64().unwrap();
|
||||||
|
|
||||||
|
let client_key = client_key.from_base64().unwrap();
|
||||||
|
let client_key = BigUint::from_bytes_be(&client_key);
|
||||||
|
|
||||||
|
let shared_key = util::powm(&client_key, &self.private_key, &DH_PRIME);
|
||||||
|
|
||||||
|
let iv = &encrypted_blob[0..16];
|
||||||
|
let encrypted = &encrypted_blob[16..encrypted_blob.len() - 20];
|
||||||
|
let cksum = &encrypted_blob[encrypted_blob.len() - 20..encrypted_blob.len()];
|
||||||
|
|
||||||
|
let base_key = {
|
||||||
|
let mut data = [0u8; 20];
|
||||||
|
let mut h = crypto::sha1::Sha1::new();
|
||||||
|
h.input(&shared_key.to_bytes_be());
|
||||||
|
h.result(&mut data);
|
||||||
|
data[..16].to_owned()
|
||||||
|
};
|
||||||
|
|
||||||
|
let checksum_key = {
|
||||||
|
let mut h = crypto::hmac::Hmac::new(crypto::sha1::Sha1::new(), &base_key);
|
||||||
|
h.input("checksum".as_bytes());
|
||||||
|
h.result().code().to_owned()
|
||||||
|
};
|
||||||
|
|
||||||
|
let encryption_key = {
|
||||||
|
let mut h = crypto::hmac::Hmac::new(crypto::sha1::Sha1::new(), &base_key);
|
||||||
|
h.input("encryption".as_bytes());
|
||||||
|
h.result().code().to_owned()
|
||||||
|
};
|
||||||
|
|
||||||
|
let mac = {
|
||||||
|
let mut h = crypto::hmac::Hmac::new(crypto::sha1::Sha1::new(), &checksum_key);
|
||||||
|
h.input(encrypted);
|
||||||
|
h.result().code().to_owned()
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(&mac[..], cksum);
|
||||||
|
|
||||||
|
let decrypted = {
|
||||||
|
let mut data = vec![0u8; encrypted.len()];
|
||||||
|
let mut cipher = crypto::aes::ctr(crypto::aes::KeySize::KeySize128,
|
||||||
|
&encryption_key[0..16],
|
||||||
|
&iv);
|
||||||
|
cipher.process(&encrypted, &mut data);
|
||||||
|
String::from_utf8(data).unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
let credentials = Credentials::with_blob(username.to_owned(), &decrypted, &self.device_id);
|
||||||
|
|
||||||
|
self.credentials_tx.lock().unwrap().send(credentials).unwrap();
|
||||||
|
|
||||||
|
let result = json!({
|
||||||
|
"status": 101,
|
||||||
|
"spotifyError": 0,
|
||||||
|
"statusString": "ERROR-OK"
|
||||||
|
});
|
||||||
|
|
||||||
|
*response.status_mut() = hyper::status::StatusCode::Ok;
|
||||||
|
response.start().unwrap().write_all(result.to_string().as_bytes()).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn not_found(&self, mut response: hyper::server::Response<hyper::net::Fresh>) {
|
||||||
|
|
||||||
|
*response.status_mut() = hyper::status::StatusCode::NotFound
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl hyper::server::Handler for ServerHandler {
|
||||||
|
fn handle<'a, 'k>(&'a self,
|
||||||
|
mut request: hyper::server::Request<'a, 'k>,
|
||||||
|
response: hyper::server::Response<'a, hyper::net::Fresh>) {
|
||||||
|
|
||||||
|
if let hyper::uri::RequestUri::AbsolutePath(path) = request.uri.clone() {
|
||||||
|
let (_, query, _) = url::parse_path(&path).unwrap();
|
||||||
|
let mut params = query.map_or(vec![], |q| url::form_urlencoded::parse(q.as_bytes()))
|
||||||
|
.into_iter().collect::<BTreeMap<_,_>>();
|
||||||
|
|
||||||
|
if request.method == hyper::method::Method::Post {
|
||||||
|
let mut body = Vec::new();
|
||||||
|
request.read_to_end(&mut body).unwrap();
|
||||||
|
let form = url::form_urlencoded::parse(&body);
|
||||||
|
params.extend(form);
|
||||||
|
}
|
||||||
|
|
||||||
|
match params.get("action").map(AsRef::as_ref) {
|
||||||
|
Some("getInfo") => self.handle_get_info(¶ms, response),
|
||||||
|
Some("addUser") => self.handle_add_user(¶ms, response),
|
||||||
|
_ => self.not_found(response),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.not_found(response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn discovery_login(device_name: &str, device_id: &str) -> Result<Credentials, ()> {
|
||||||
|
let (tx, rx) = mpsc::channel();
|
||||||
|
|
||||||
|
let key_data = util::rand_vec(&mut rand::thread_rng(), 95);
|
||||||
|
let private_key = BigUint::from_bytes_be(&key_data);
|
||||||
|
let public_key = util::powm(&DH_GENERATOR, &private_key, &DH_PRIME);
|
||||||
|
|
||||||
|
let handler = ServerHandler {
|
||||||
|
device_name: device_name.to_owned(),
|
||||||
|
device_id: device_id.to_owned(),
|
||||||
|
private_key: private_key,
|
||||||
|
public_key: public_key,
|
||||||
|
credentials_tx: Mutex::new(tx),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut listener = hyper::net::HttpListener::new("0.0.0.0:0").unwrap();
|
||||||
|
let port = listener.local_addr().unwrap().port();
|
||||||
|
|
||||||
|
let mut server = hyper::Server::new(listener).handle(handler).unwrap();
|
||||||
|
|
||||||
|
let _svc = DNSService::register(Some(device_name),
|
||||||
|
"_spotify-connect._tcp",
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
port,
|
||||||
|
&["VERSION=1.0", "CPath=/"]
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
|
let cred = rx.recv().unwrap();
|
||||||
|
server.close().unwrap();
|
||||||
|
Ok(cred)
|
||||||
|
}
|
|
@ -72,7 +72,7 @@ impl hyper::server::Handler for ServerHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn facebook_get_me_id(token: &str) -> Result<String, ()> {
|
fn facebook_get_me_id(token: &str) -> Result<String, ()> {
|
||||||
let url = format!("https://graph.facebook.com/me?fields=id&access_token={}", token);
|
let url = format!("https://graph.facebook.com/me?fields=id&access_token={}", token);
|
||||||
|
|
||||||
let client = hyper::Client::new();
|
let client = hyper::Client::new();
|
|
@ -163,3 +163,20 @@ impl From<StoredCredentials> for Credentials {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "discovery")]
|
||||||
|
mod discovery;
|
||||||
|
#[cfg(feature = "discovery")]
|
||||||
|
pub use self::discovery::discovery_login;
|
||||||
|
#[cfg(not(feature = "discovery"))]
|
||||||
|
pub fn discovery_login(device_name: &str, device_id: &str) -> Result<Credentials, ()> {
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "facebook")]
|
||||||
|
mod facebook;
|
||||||
|
#[cfg(feature = "facebook")]
|
||||||
|
pub use self::facebook::facebook_login;
|
||||||
|
#[cfg(not(feature = "facebook"))]
|
||||||
|
pub fn facebook_login() -> Result<Credentials, ()> {
|
||||||
|
Err(())
|
||||||
|
}
|
167
src/discovery.rs
167
src/discovery.rs
|
@ -1,167 +0,0 @@
|
||||||
use crypto;
|
|
||||||
use crypto::mac::Mac;
|
|
||||||
use crypto::digest::Digest;
|
|
||||||
use num::BigUint;
|
|
||||||
use url;
|
|
||||||
use rand;
|
|
||||||
use rustc_serialize::base64::{self, ToBase64, FromBase64};
|
|
||||||
use tiny_http::{Method, Request, Response, ResponseBox, Server};
|
|
||||||
use zeroconf::DNSService;
|
|
||||||
|
|
||||||
use authentication::Credentials;
|
|
||||||
use session::Session;
|
|
||||||
use diffie_hellman::{DH_GENERATOR, DH_PRIME};
|
|
||||||
use util;
|
|
||||||
|
|
||||||
pub struct DiscoveryManager {
|
|
||||||
session: Session,
|
|
||||||
private_key: BigUint,
|
|
||||||
public_key: BigUint,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn not_found() -> ResponseBox {
|
|
||||||
Response::from_string("Not found").with_status_code(404).boxed()
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DiscoveryManager {
|
|
||||||
pub fn new(session: Session) -> DiscoveryManager {
|
|
||||||
let key_data = util::rand_vec(&mut rand::thread_rng(), 95);
|
|
||||||
let private_key = BigUint::from_bytes_be(&key_data);
|
|
||||||
let public_key = util::powm(&DH_GENERATOR, &private_key, &DH_PRIME);
|
|
||||||
|
|
||||||
DiscoveryManager {
|
|
||||||
session: session,
|
|
||||||
private_key: private_key,
|
|
||||||
public_key: public_key,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_info(&self) -> ResponseBox {
|
|
||||||
let public_key = self.public_key.to_bytes_be().to_base64(base64::STANDARD);
|
|
||||||
Response::from_string(json!({
|
|
||||||
"status": 101,
|
|
||||||
"statusString": "ERROR-OK",
|
|
||||||
"spotifyError": 0,
|
|
||||||
"version": "2.1.0",
|
|
||||||
"deviceID": (self.session.device_id()),
|
|
||||||
"remoteName": (self.session.config().device_name),
|
|
||||||
"activeUser": "",
|
|
||||||
"publicKey": (public_key),
|
|
||||||
"deviceType": "UNKNOWN",
|
|
||||||
"libraryVersion": "0.1.0",
|
|
||||||
"accountReq": "PREMIUM",
|
|
||||||
"brandDisplayName": "librespot",
|
|
||||||
"modelDisplayName": "librespot",
|
|
||||||
}).to_string()).boxed()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_user(&self, params: &[(String, String)]) -> (ResponseBox, Credentials) {
|
|
||||||
let &(_, ref username) = params.iter().find(|&&(ref key, _)| key == "userName").unwrap();
|
|
||||||
let &(_, ref encrypted_blob) = params.iter().find(|&&(ref key, _)| key == "blob").unwrap();
|
|
||||||
let &(_, ref client_key) = params.iter().find(|&&(ref key, _)| key == "clientKey").unwrap();
|
|
||||||
|
|
||||||
let encrypted_blob = encrypted_blob.from_base64().unwrap();
|
|
||||||
|
|
||||||
let client_key = client_key.from_base64().unwrap();
|
|
||||||
let client_key = BigUint::from_bytes_be(&client_key);
|
|
||||||
|
|
||||||
let shared_key = util::powm(&client_key, &self.private_key, &DH_PRIME);
|
|
||||||
|
|
||||||
let iv = &encrypted_blob[0..16];
|
|
||||||
let encrypted = &encrypted_blob[16..encrypted_blob.len() - 20];
|
|
||||||
let cksum = &encrypted_blob[encrypted_blob.len() - 20..encrypted_blob.len()];
|
|
||||||
|
|
||||||
let base_key = {
|
|
||||||
let mut data = [0u8; 20];
|
|
||||||
let mut h = crypto::sha1::Sha1::new();
|
|
||||||
h.input(&shared_key.to_bytes_be());
|
|
||||||
h.result(&mut data);
|
|
||||||
data[..16].to_owned()
|
|
||||||
};
|
|
||||||
|
|
||||||
let checksum_key = {
|
|
||||||
let mut h = crypto::hmac::Hmac::new(crypto::sha1::Sha1::new(), &base_key);
|
|
||||||
h.input("checksum".as_bytes());
|
|
||||||
h.result().code().to_owned()
|
|
||||||
};
|
|
||||||
|
|
||||||
let encryption_key = {
|
|
||||||
let mut h = crypto::hmac::Hmac::new(crypto::sha1::Sha1::new(), &base_key);
|
|
||||||
h.input("encryption".as_bytes());
|
|
||||||
h.result().code().to_owned()
|
|
||||||
};
|
|
||||||
|
|
||||||
let mac = {
|
|
||||||
let mut h = crypto::hmac::Hmac::new(crypto::sha1::Sha1::new(), &checksum_key);
|
|
||||||
h.input(encrypted);
|
|
||||||
h.result().code().to_owned()
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_eq!(&mac[..], cksum);
|
|
||||||
|
|
||||||
let decrypted = {
|
|
||||||
let mut data = vec![0u8; encrypted.len()];
|
|
||||||
let mut cipher = crypto::aes::ctr(crypto::aes::KeySize::KeySize128,
|
|
||||||
&encryption_key[0..16],
|
|
||||||
&iv);
|
|
||||||
cipher.process(&encrypted, &mut data);
|
|
||||||
String::from_utf8(data).unwrap()
|
|
||||||
};
|
|
||||||
|
|
||||||
let response = Response::from_string(json!({
|
|
||||||
"status": 101,
|
|
||||||
"spotifyError": 0,
|
|
||||||
"statusString": "ERROR-OK"
|
|
||||||
}).to_string()).boxed();
|
|
||||||
|
|
||||||
let credentials = Credentials::with_blob(username.to_owned(), &decrypted, &self.session.device_id());
|
|
||||||
|
|
||||||
(response, credentials)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_request(&self, mut request: Request) -> Option<Credentials> {
|
|
||||||
let (_, query, _) = url::parse_path(request.url()).unwrap();
|
|
||||||
let mut params = query.map_or(vec![], |q| url::form_urlencoded::parse(q.as_bytes()));
|
|
||||||
|
|
||||||
if *request.method() == Method::Post {
|
|
||||||
let mut body = Vec::new();
|
|
||||||
request.as_reader().read_to_end(&mut body).unwrap();
|
|
||||||
let form = url::form_urlencoded::parse(&body);
|
|
||||||
params.extend(form);
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("{:?}", params);
|
|
||||||
|
|
||||||
let &(_, ref action) = params.iter().find(|&&(ref key, _)| key == "action").unwrap();
|
|
||||||
let (response, credentials) = match action.as_ref() {
|
|
||||||
"getInfo" => (self.get_info(), None),
|
|
||||||
"addUser" => {
|
|
||||||
let (response, credentials) = self.add_user(¶ms);
|
|
||||||
(response, Some(credentials))
|
|
||||||
}
|
|
||||||
_ => (not_found(), None)
|
|
||||||
};
|
|
||||||
|
|
||||||
request.respond(response).unwrap();
|
|
||||||
credentials
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run(&mut self) -> Credentials {
|
|
||||||
let server = Server::http("0.0.0.0:8000").unwrap();
|
|
||||||
let _svc = DNSService::register(Some(&self.session.config().device_name),
|
|
||||||
"_spotify-connect._tcp",
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
8000,
|
|
||||||
&["VERSION=1.0", "CPath=/"]
|
|
||||||
).unwrap();
|
|
||||||
|
|
||||||
for request in server.incoming_requests() {
|
|
||||||
if let Some(credentials) = self.handle_request(request) {
|
|
||||||
return credentials;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
panic!("No credentials obtained !");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -8,7 +8,6 @@ pub mod authentication;
|
||||||
pub mod cache;
|
pub mod cache;
|
||||||
mod connection;
|
mod connection;
|
||||||
mod diffie_hellman;
|
mod diffie_hellman;
|
||||||
pub mod discovery;
|
|
||||||
pub mod mercury;
|
pub mod mercury;
|
||||||
pub mod metadata;
|
pub mod metadata;
|
||||||
pub mod player;
|
pub mod player;
|
||||||
|
@ -17,9 +16,5 @@ pub mod spirc;
|
||||||
pub mod link;
|
pub mod link;
|
||||||
pub mod stream;
|
pub mod stream;
|
||||||
pub mod apresolve;
|
pub mod apresolve;
|
||||||
mod zeroconf;
|
|
||||||
|
|
||||||
#[cfg(feature = "facebook")]
|
|
||||||
pub mod facebook;
|
|
||||||
|
|
||||||
pub use album_cover::get_album_cover;
|
pub use album_cover::get_album_cover;
|
||||||
|
|
|
@ -23,7 +23,6 @@ extern crate shannon;
|
||||||
extern crate rand;
|
extern crate rand;
|
||||||
extern crate rustc_serialize;
|
extern crate rustc_serialize;
|
||||||
extern crate time;
|
extern crate time;
|
||||||
extern crate tiny_http;
|
|
||||||
extern crate tempfile;
|
extern crate tempfile;
|
||||||
extern crate url;
|
extern crate url;
|
||||||
|
|
||||||
|
|
28
src/main.rs
28
src/main.rs
|
@ -10,20 +10,12 @@ use std::path::PathBuf;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
|
||||||
use librespot::audio_sink::DefaultSink;
|
use librespot::audio_sink::DefaultSink;
|
||||||
use librespot::authentication::Credentials;
|
use librespot::authentication::{Credentials, facebook_login, discovery_login};
|
||||||
use librespot::discovery::DiscoveryManager;
|
use librespot::cache::{Cache, DefaultCache, NoCache};
|
||||||
use librespot::player::Player;
|
use librespot::player::Player;
|
||||||
use librespot::session::{Bitrate, Config, Session};
|
use librespot::session::{Bitrate, Config, Session};
|
||||||
use librespot::spirc::SpircManager;
|
use librespot::spirc::SpircManager;
|
||||||
use librespot::util::version::version_string;
|
use librespot::util::version::version_string;
|
||||||
use librespot::cache::{Cache, DefaultCache, NoCache};
|
|
||||||
|
|
||||||
#[cfg(feature = "facebook")]
|
|
||||||
use librespot::facebook::facebook_login;
|
|
||||||
#[cfg(not(feature = "facebook"))]
|
|
||||||
fn facebook_login() -> Result<Credentials, ()> {
|
|
||||||
Err(())
|
|
||||||
}
|
|
||||||
|
|
||||||
static PASSWORD_ENV_NAME: &'static str = "SPOTIFY_PASSWORD";
|
static PASSWORD_ENV_NAME: &'static str = "SPOTIFY_PASSWORD";
|
||||||
|
|
||||||
|
@ -117,13 +109,15 @@ fn main() {
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}).or_else(|| session.cache().get_credentials())
|
}).or_else(|| {
|
||||||
.unwrap_or_else(|| {
|
if cfg!(feature = "discovery") {
|
||||||
println!("No username provided and no stored credentials, starting discovery ...");
|
println!("No username provided and no stored credentials, starting discovery ...");
|
||||||
|
Some(discovery_login(&session.config().device_name,
|
||||||
let mut discovery = DiscoveryManager::new(session.clone());
|
session.device_id()).unwrap())
|
||||||
discovery.run()
|
} else {
|
||||||
});
|
None
|
||||||
|
}
|
||||||
|
}).expect("No username provided and no stored credentials.");
|
||||||
|
|
||||||
std::env::remove_var(PASSWORD_ENV_NAME);
|
std::env::remove_var(PASSWORD_ENV_NAME);
|
||||||
|
|
||||||
|
|
|
@ -42,11 +42,11 @@ pub struct Config {
|
||||||
pub struct SessionData {
|
pub struct SessionData {
|
||||||
country: String,
|
country: String,
|
||||||
canonical_username: String,
|
canonical_username: String,
|
||||||
device_id: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SessionInternal {
|
pub struct SessionInternal {
|
||||||
config: Config,
|
config: Config,
|
||||||
|
device_id: String,
|
||||||
data: RwLock<SessionData>,
|
data: RwLock<SessionData>,
|
||||||
|
|
||||||
cache: Box<Cache + Send + Sync>,
|
cache: Box<Cache + Send + Sync>,
|
||||||
|
@ -71,10 +71,10 @@ impl Session {
|
||||||
|
|
||||||
Session(Arc::new(SessionInternal {
|
Session(Arc::new(SessionInternal {
|
||||||
config: config,
|
config: config,
|
||||||
|
device_id: device_id,
|
||||||
data: RwLock::new(SessionData {
|
data: RwLock::new(SessionData {
|
||||||
country: String::new(),
|
country: String::new(),
|
||||||
canonical_username: String::new(),
|
canonical_username: String::new(),
|
||||||
device_id: device_id,
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
rx_connection: Mutex::new(None),
|
rx_connection: Mutex::new(None),
|
||||||
|
@ -190,7 +190,7 @@ impl Session {
|
||||||
cpu_family: protocol::authentication::CpuFamily::CPU_UNKNOWN,
|
cpu_family: protocol::authentication::CpuFamily::CPU_UNKNOWN,
|
||||||
os: protocol::authentication::Os::OS_UNKNOWN,
|
os: protocol::authentication::Os::OS_UNKNOWN,
|
||||||
system_information_string: "librespot".to_owned(),
|
system_information_string: "librespot".to_owned(),
|
||||||
device_id: self.device_id(),
|
device_id: self.device_id().to_owned(),
|
||||||
},
|
},
|
||||||
version_string: util::version::version_string(),
|
version_string: util::version::version_string(),
|
||||||
appkey => {
|
appkey => {
|
||||||
|
@ -348,7 +348,7 @@ impl Session {
|
||||||
self.0.data.read().unwrap().country.clone()
|
self.0.data.read().unwrap().country.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn device_id(&self) -> String {
|
pub fn device_id(&self) -> &str {
|
||||||
self.0.data.read().unwrap().device_id.clone()
|
&self.0.device_id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,7 +46,7 @@ struct SpircInternal {
|
||||||
|
|
||||||
impl SpircManager {
|
impl SpircManager {
|
||||||
pub fn new(session: Session, player: Player) -> SpircManager {
|
pub fn new(session: Session, player: Player) -> SpircManager {
|
||||||
let ident = session.device_id();
|
let ident = session.device_id().to_owned();
|
||||||
let name = session.config().device_name.clone();
|
let name = session.config().device_name.clone();
|
||||||
|
|
||||||
SpircManager(Arc::new(Mutex::new(SpircInternal {
|
SpircManager(Arc::new(Mutex::new(SpircInternal {
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
#[cfg(feature = "dns-sd")]
|
|
||||||
pub use dns_sd::*;
|
|
||||||
#[cfg(not(feature = "dns-sd"))]
|
|
||||||
pub use self::stub::*;
|
|
||||||
|
|
||||||
#[cfg(not(feature = "dns-sd"))]
|
|
||||||
pub mod stub {
|
|
||||||
use std;
|
|
||||||
use std::io::Write;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct DNSService;
|
|
||||||
|
|
||||||
pub type DNSError = ();
|
|
||||||
|
|
||||||
impl DNSService {
|
|
||||||
pub fn register(_: Option<&str>,
|
|
||||||
_: &str,
|
|
||||||
_: Option<&str>,
|
|
||||||
_: Option<&str>,
|
|
||||||
_: u16,
|
|
||||||
_: &[&str])
|
|
||||||
-> std::result::Result<DNSService, DNSError> {
|
|
||||||
writeln!(&mut std::io::stderr(),
|
|
||||||
"WARNING: dns-sd is not enabled. Service will probably not be visible")
|
|
||||||
.unwrap();
|
|
||||||
Ok(DNSService)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue