2017-02-09 01:27:52 +00:00
|
|
|
use base64;
|
2018-07-23 13:41:39 +00:00
|
|
|
use sha1::{Sha1, Digest};
|
|
|
|
use hmac::{Hmac, Mac};
|
|
|
|
use aes::Aes128;
|
2017-01-18 15:17:10 +00:00
|
|
|
use futures::sync::mpsc;
|
2018-03-23 05:13:01 +00:00
|
|
|
use futures::{Future, Poll, Stream};
|
2018-02-11 17:52:53 +00:00
|
|
|
use hyper::server::{Http, Request, Response, Service};
|
2018-03-23 05:13:01 +00:00
|
|
|
use hyper::{self, Get, Post, StatusCode};
|
2018-01-31 11:00:53 +00:00
|
|
|
|
|
|
|
#[cfg(feature = "with-dns-sd")]
|
|
|
|
use dns_sd::DNSService;
|
|
|
|
|
|
|
|
#[cfg(not(feature = "with-dns-sd"))]
|
2017-01-18 15:17:10 +00:00
|
|
|
use mdns;
|
2018-01-31 11:00:53 +00:00
|
|
|
|
2017-01-29 12:50:18 +00:00
|
|
|
use num_bigint::BigUint;
|
2016-03-17 03:06:56 +00:00
|
|
|
use rand;
|
|
|
|
use std::collections::BTreeMap;
|
2017-01-29 12:50:18 +00:00
|
|
|
use std::io;
|
2017-01-18 15:17:10 +00:00
|
|
|
use std::sync::Arc;
|
|
|
|
use tokio_core::reactor::Handle;
|
2017-01-29 12:50:18 +00:00
|
|
|
use url;
|
2016-03-17 03:06:56 +00:00
|
|
|
|
2017-08-03 18:58:44 +00:00
|
|
|
use core::authentication::Credentials;
|
|
|
|
use core::config::ConnectConfig;
|
2018-02-11 17:52:53 +00:00
|
|
|
use core::diffie_hellman::{DH_GENERATOR, DH_PRIME};
|
|
|
|
use core::util;
|
2016-03-17 03:06:56 +00:00
|
|
|
|
2018-07-23 13:41:39 +00:00
|
|
|
type HmacSha1 = Hmac<Sha1>;
|
|
|
|
|
2017-01-18 15:17:10 +00:00
|
|
|
#[derive(Clone)]
|
|
|
|
struct Discovery(Arc<DiscoveryInner>);
|
|
|
|
struct DiscoveryInner {
|
2017-08-03 18:31:15 +00:00
|
|
|
config: ConnectConfig,
|
|
|
|
device_id: String,
|
2016-03-17 03:06:56 +00:00
|
|
|
private_key: BigUint,
|
|
|
|
public_key: BigUint,
|
2017-01-18 15:17:10 +00:00
|
|
|
tx: mpsc::UnboundedSender<Credentials>,
|
2016-03-17 03:06:56 +00:00
|
|
|
}
|
|
|
|
|
2017-01-18 15:17:10 +00:00
|
|
|
impl Discovery {
|
2018-02-11 17:52:53 +00:00
|
|
|
fn new(
|
|
|
|
config: ConnectConfig,
|
|
|
|
device_id: String,
|
|
|
|
) -> (Discovery, mpsc::UnboundedReceiver<Credentials>) {
|
2017-01-18 15:17:10 +00:00
|
|
|
let (tx, rx) = mpsc::unbounded();
|
|
|
|
|
|
|
|
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 discovery = Discovery(Arc::new(DiscoveryInner {
|
2017-08-03 18:31:15 +00:00
|
|
|
config: config,
|
|
|
|
device_id: device_id,
|
2017-01-18 15:17:10 +00:00
|
|
|
private_key: private_key,
|
|
|
|
public_key: public_key,
|
|
|
|
tx: tx,
|
|
|
|
}));
|
|
|
|
|
|
|
|
(discovery, rx)
|
|
|
|
}
|
|
|
|
}
|
2016-03-17 03:06:56 +00:00
|
|
|
|
2017-01-18 15:17:10 +00:00
|
|
|
impl Discovery {
|
2018-02-11 17:52:53 +00:00
|
|
|
fn handle_get_info(
|
|
|
|
&self,
|
|
|
|
_params: &BTreeMap<String, String>,
|
|
|
|
) -> ::futures::Finished<Response, hyper::Error> {
|
2017-02-09 01:27:52 +00:00
|
|
|
let public_key = self.0.public_key.to_bytes_be();
|
|
|
|
let public_key = base64::encode(&public_key);
|
2016-03-17 03:06:56 +00:00
|
|
|
|
|
|
|
let result = json!({
|
|
|
|
"status": 101,
|
|
|
|
"statusString": "ERROR-OK",
|
|
|
|
"spotifyError": 0,
|
|
|
|
"version": "2.1.0",
|
2017-01-18 15:17:10 +00:00
|
|
|
"deviceID": (self.0.device_id),
|
2017-08-03 18:31:15 +00:00
|
|
|
"remoteName": (self.0.config.name),
|
2016-03-17 03:06:56 +00:00
|
|
|
"activeUser": "",
|
|
|
|
"publicKey": (public_key),
|
2017-08-03 18:31:15 +00:00
|
|
|
"deviceType": (self.0.config.device_type.to_string().to_uppercase()),
|
2016-03-17 03:06:56 +00:00
|
|
|
"libraryVersion": "0.1.0",
|
|
|
|
"accountReq": "PREMIUM",
|
|
|
|
"brandDisplayName": "librespot",
|
|
|
|
"modelDisplayName": "librespot",
|
|
|
|
});
|
|
|
|
|
2017-01-18 15:17:10 +00:00
|
|
|
let body = result.to_string();
|
|
|
|
::futures::finished(Response::new().with_body(body))
|
2016-03-17 03:06:56 +00:00
|
|
|
}
|
|
|
|
|
2018-02-11 17:52:53 +00:00
|
|
|
fn handle_add_user(
|
|
|
|
&self,
|
|
|
|
params: &BTreeMap<String, String>,
|
|
|
|
) -> ::futures::Finished<Response, hyper::Error> {
|
2016-03-17 03:06:56 +00:00
|
|
|
let username = params.get("userName").unwrap();
|
|
|
|
let encrypted_blob = params.get("blob").unwrap();
|
|
|
|
let client_key = params.get("clientKey").unwrap();
|
|
|
|
|
2017-02-09 01:27:52 +00:00
|
|
|
let encrypted_blob = base64::decode(encrypted_blob).unwrap();
|
2016-03-17 03:06:56 +00:00
|
|
|
|
2017-02-09 01:27:52 +00:00
|
|
|
let client_key = base64::decode(client_key).unwrap();
|
2016-03-17 03:06:56 +00:00
|
|
|
let client_key = BigUint::from_bytes_be(&client_key);
|
|
|
|
|
2017-01-18 15:17:10 +00:00
|
|
|
let shared_key = util::powm(&client_key, &self.0.private_key, &DH_PRIME);
|
2016-03-17 03:06:56 +00:00
|
|
|
|
|
|
|
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()];
|
|
|
|
|
2018-07-23 13:41:39 +00:00
|
|
|
let base_key = Sha1::digest(&shared_key.to_bytes_be());
|
|
|
|
let base_key = &base_key[..16];
|
2016-03-17 03:06:56 +00:00
|
|
|
|
|
|
|
let checksum_key = {
|
2018-07-23 13:41:39 +00:00
|
|
|
let mut h = HmacSha1::new_varkey(base_key)
|
|
|
|
.expect("HMAC can take key of any size");
|
2017-02-09 01:32:18 +00:00
|
|
|
h.input(b"checksum");
|
2018-07-23 13:41:39 +00:00
|
|
|
h.result().code()
|
2016-03-17 03:06:56 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
let encryption_key = {
|
2018-07-23 13:41:39 +00:00
|
|
|
let mut h = HmacSha1::new_varkey(&base_key)
|
|
|
|
.expect("HMAC can take key of any size");
|
2017-02-09 01:32:18 +00:00
|
|
|
h.input(b"encryption");
|
2018-07-23 13:41:39 +00:00
|
|
|
h.result().code()
|
2016-03-17 03:06:56 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
let mac = {
|
2018-07-23 13:41:39 +00:00
|
|
|
let mut h = HmacSha1::new_varkey(&checksum_key)
|
|
|
|
.expect("HMAC can take key of any size");
|
2016-03-17 03:06:56 +00:00
|
|
|
h.input(encrypted);
|
2018-07-23 13:41:39 +00:00
|
|
|
h.result().code()
|
2016-03-17 03:06:56 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
assert_eq!(&mac[..], cksum);
|
|
|
|
|
|
|
|
let decrypted = {
|
|
|
|
let mut data = vec![0u8; encrypted.len()];
|
2018-07-23 13:41:39 +00:00
|
|
|
//let mut cipher =
|
|
|
|
// crypto::aes::ctr(crypto::aes::KeySize::KeySize128, &encryption_key[0..16], iv);
|
|
|
|
//cipher.process(encrypted, &mut data);
|
2016-03-17 03:06:56 +00:00
|
|
|
String::from_utf8(data).unwrap()
|
|
|
|
};
|
|
|
|
|
2017-01-18 15:17:10 +00:00
|
|
|
let credentials = Credentials::with_blob(username.to_owned(), &decrypted, &self.0.device_id);
|
2016-03-17 03:06:56 +00:00
|
|
|
|
2018-01-21 19:38:30 +00:00
|
|
|
self.0.tx.unbounded_send(credentials).unwrap();
|
2016-03-17 03:06:56 +00:00
|
|
|
|
|
|
|
let result = json!({
|
|
|
|
"status": 101,
|
|
|
|
"spotifyError": 0,
|
|
|
|
"statusString": "ERROR-OK"
|
|
|
|
});
|
|
|
|
|
2017-01-18 15:17:10 +00:00
|
|
|
let body = result.to_string();
|
|
|
|
::futures::finished(Response::new().with_body(body))
|
2016-03-17 03:06:56 +00:00
|
|
|
}
|
|
|
|
|
2018-02-11 17:52:53 +00:00
|
|
|
fn not_found(&self) -> ::futures::Finished<Response, hyper::Error> {
|
2017-01-18 15:17:10 +00:00
|
|
|
::futures::finished(Response::new().with_status(StatusCode::NotFound))
|
2016-03-17 03:06:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-18 15:17:10 +00:00
|
|
|
impl Service for Discovery {
|
|
|
|
type Request = Request;
|
|
|
|
type Response = Response;
|
|
|
|
type Error = hyper::Error;
|
2018-01-21 19:29:31 +00:00
|
|
|
type Future = Box<Future<Item = Response, Error = hyper::Error>>;
|
2016-03-17 03:06:56 +00:00
|
|
|
|
2017-01-18 15:17:10 +00:00
|
|
|
fn call(&self, request: Request) -> Self::Future {
|
|
|
|
let mut params = BTreeMap::new();
|
2016-04-24 11:15:53 +00:00
|
|
|
|
2017-01-18 15:17:10 +00:00
|
|
|
let (method, uri, _, _, body) = request.deconstruct();
|
|
|
|
if let Some(query) = uri.query() {
|
|
|
|
params.extend(url::form_urlencoded::parse(query.as_bytes()).into_owned());
|
2016-03-17 03:06:56 +00:00
|
|
|
}
|
2017-01-18 15:17:10 +00:00
|
|
|
|
2017-02-22 04:17:04 +00:00
|
|
|
if method != Get {
|
|
|
|
debug!("{:?} {:?} {:?}", method, uri.path(), params);
|
|
|
|
}
|
2017-01-18 15:17:10 +00:00
|
|
|
|
|
|
|
let this = self.clone();
|
2018-02-11 17:52:53 +00:00
|
|
|
Box::new(
|
|
|
|
body.fold(Vec::new(), |mut acc, chunk| {
|
|
|
|
acc.extend_from_slice(chunk.as_ref());
|
|
|
|
Ok::<_, hyper::Error>(acc)
|
|
|
|
}).map(move |body| {
|
|
|
|
params.extend(url::form_urlencoded::parse(&body).into_owned());
|
|
|
|
params
|
|
|
|
})
|
|
|
|
.and_then(
|
|
|
|
move |params| match (method, params.get("action").map(AsRef::as_ref)) {
|
|
|
|
(Get, Some("getInfo")) => this.handle_get_info(¶ms),
|
|
|
|
(Post, Some("addUser")) => this.handle_add_user(¶ms),
|
|
|
|
_ => this.not_found(),
|
|
|
|
},
|
|
|
|
),
|
|
|
|
)
|
2017-01-18 15:17:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-31 11:00:53 +00:00
|
|
|
#[cfg(feature = "with-dns-sd")]
|
|
|
|
pub struct DiscoveryStream {
|
|
|
|
credentials: mpsc::UnboundedReceiver<Credentials>,
|
|
|
|
_svc: DNSService,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(not(feature = "with-dns-sd"))]
|
2017-02-22 04:17:04 +00:00
|
|
|
pub struct DiscoveryStream {
|
|
|
|
credentials: mpsc::UnboundedReceiver<Credentials>,
|
|
|
|
_svc: mdns::Service,
|
|
|
|
}
|
2017-01-20 02:37:02 +00:00
|
|
|
|
2018-02-11 17:52:53 +00:00
|
|
|
pub fn discovery(
|
|
|
|
handle: &Handle,
|
|
|
|
config: ConnectConfig,
|
|
|
|
device_id: String,
|
|
|
|
port: u16,
|
|
|
|
) -> io::Result<DiscoveryStream> {
|
2017-08-03 18:31:15 +00:00
|
|
|
let (discovery, creds_rx) = Discovery::new(config.clone(), device_id);
|
2017-01-20 02:37:02 +00:00
|
|
|
|
2018-01-21 22:35:50 +00:00
|
|
|
let serve = {
|
|
|
|
let http = Http::new();
|
2018-01-30 20:38:54 +00:00
|
|
|
debug!("Zeroconf server listening on 0.0.0.0:{}", port);
|
2018-02-11 17:52:53 +00:00
|
|
|
http.serve_addr_handle(
|
|
|
|
&format!("0.0.0.0:{}", port).parse().unwrap(),
|
|
|
|
&handle,
|
|
|
|
move || Ok(discovery.clone()),
|
|
|
|
).unwrap()
|
2018-01-21 22:35:50 +00:00
|
|
|
};
|
2018-01-31 11:00:53 +00:00
|
|
|
|
2018-02-02 18:37:15 +00:00
|
|
|
let s_port = serve.incoming_ref().local_addr().port();
|
2018-01-31 11:08:23 +00:00
|
|
|
|
2018-01-21 22:35:50 +00:00
|
|
|
let server_future = {
|
|
|
|
let handle = handle.clone();
|
2018-02-11 17:52:53 +00:00
|
|
|
serve
|
|
|
|
.for_each(move |connection| {
|
2018-01-21 22:35:50 +00:00
|
|
|
handle.spawn(connection.then(|_| Ok(())));
|
|
|
|
Ok(())
|
|
|
|
})
|
|
|
|
.then(|_| Ok(()))
|
|
|
|
};
|
|
|
|
handle.spawn(server_future);
|
2016-03-17 03:06:56 +00:00
|
|
|
|
2018-01-31 11:00:53 +00:00
|
|
|
#[cfg(feature = "with-dns-sd")]
|
2018-02-11 17:52:53 +00:00
|
|
|
let svc = DNSService::register(
|
|
|
|
Some(&*config.name),
|
|
|
|
"_spotify-connect._tcp",
|
|
|
|
None,
|
|
|
|
None,
|
|
|
|
s_port,
|
|
|
|
&["VERSION=1.0", "CPath=/"],
|
|
|
|
).unwrap();
|
2018-01-31 11:00:53 +00:00
|
|
|
|
|
|
|
#[cfg(not(feature = "with-dns-sd"))]
|
2017-02-22 04:17:04 +00:00
|
|
|
let responder = mdns::Responder::spawn(&handle)?;
|
2018-02-11 17:52:53 +00:00
|
|
|
|
2018-01-31 11:00:53 +00:00
|
|
|
#[cfg(not(feature = "with-dns-sd"))]
|
2017-02-22 04:17:04 +00:00
|
|
|
let svc = responder.register(
|
2017-01-20 02:37:02 +00:00
|
|
|
"_spotify-connect._tcp".to_owned(),
|
2017-08-03 18:31:15 +00:00
|
|
|
config.name,
|
2018-02-02 18:37:15 +00:00
|
|
|
s_port,
|
2018-02-11 17:52:53 +00:00
|
|
|
&["VERSION=1.0", "CPath=/"],
|
|
|
|
);
|
2016-03-17 03:06:56 +00:00
|
|
|
|
2017-02-22 04:17:04 +00:00
|
|
|
Ok(DiscoveryStream {
|
|
|
|
credentials: creds_rx,
|
|
|
|
_svc: svc,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Stream for DiscoveryStream {
|
|
|
|
type Item = Credentials;
|
2018-01-21 22:35:50 +00:00
|
|
|
type Error = ();
|
2017-02-22 04:17:04 +00:00
|
|
|
|
|
|
|
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
2018-01-21 22:35:50 +00:00
|
|
|
self.credentials.poll()
|
2017-02-22 04:17:04 +00:00
|
|
|
}
|
2016-03-17 03:06:56 +00:00
|
|
|
}
|