discovery.rs 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. use base64;
  2. use crypto;
  3. use crypto::digest::Digest;
  4. use crypto::mac::Mac;
  5. use futures::{Future, Poll, Stream};
  6. use futures::sync::mpsc;
  7. use hyper::{self, Get, Post, StatusCode};
  8. use hyper::server::{Http, Request, Response, Service};
  9. #[cfg(feature = "with-dns-sd")]
  10. use dns_sd::DNSService;
  11. #[cfg(not(feature = "with-dns-sd"))]
  12. use mdns;
  13. use num_bigint::BigUint;
  14. use rand;
  15. use std::collections::BTreeMap;
  16. use std::io;
  17. use std::sync::Arc;
  18. use tokio_core::reactor::Handle;
  19. use url;
  20. use core::authentication::Credentials;
  21. use core::config::ConnectConfig;
  22. use core::diffie_hellman::{DH_GENERATOR, DH_PRIME};
  23. use core::util;
  24. #[derive(Clone)]
  25. struct Discovery(Arc<DiscoveryInner>);
  26. struct DiscoveryInner {
  27. config: ConnectConfig,
  28. device_id: String,
  29. private_key: BigUint,
  30. public_key: BigUint,
  31. tx: mpsc::UnboundedSender<Credentials>,
  32. }
  33. impl Discovery {
  34. fn new(
  35. config: ConnectConfig,
  36. device_id: String,
  37. ) -> (Discovery, mpsc::UnboundedReceiver<Credentials>) {
  38. let (tx, rx) = mpsc::unbounded();
  39. let key_data = util::rand_vec(&mut rand::thread_rng(), 95);
  40. let private_key = BigUint::from_bytes_be(&key_data);
  41. let public_key = util::powm(&DH_GENERATOR, &private_key, &DH_PRIME);
  42. let discovery = Discovery(Arc::new(DiscoveryInner {
  43. config: config,
  44. device_id: device_id,
  45. private_key: private_key,
  46. public_key: public_key,
  47. tx: tx,
  48. }));
  49. (discovery, rx)
  50. }
  51. }
  52. impl Discovery {
  53. fn handle_get_info(
  54. &self,
  55. _params: &BTreeMap<String, String>,
  56. ) -> ::futures::Finished<Response, hyper::Error> {
  57. let public_key = self.0.public_key.to_bytes_be();
  58. let public_key = base64::encode(&public_key);
  59. let result = json!({
  60. "status": 101,
  61. "statusString": "ERROR-OK",
  62. "spotifyError": 0,
  63. "version": "2.1.0",
  64. "deviceID": (self.0.device_id),
  65. "remoteName": (self.0.config.name),
  66. "activeUser": "",
  67. "publicKey": (public_key),
  68. "deviceType": (self.0.config.device_type.to_string().to_uppercase()),
  69. "libraryVersion": "0.1.0",
  70. "accountReq": "PREMIUM",
  71. "brandDisplayName": "librespot",
  72. "modelDisplayName": "librespot",
  73. });
  74. let body = result.to_string();
  75. ::futures::finished(Response::new().with_body(body))
  76. }
  77. fn handle_add_user(
  78. &self,
  79. params: &BTreeMap<String, String>,
  80. ) -> ::futures::Finished<Response, hyper::Error> {
  81. let username = params.get("userName").unwrap();
  82. let encrypted_blob = params.get("blob").unwrap();
  83. let client_key = params.get("clientKey").unwrap();
  84. let encrypted_blob = base64::decode(encrypted_blob).unwrap();
  85. let client_key = base64::decode(client_key).unwrap();
  86. let client_key = BigUint::from_bytes_be(&client_key);
  87. let shared_key = util::powm(&client_key, &self.0.private_key, &DH_PRIME);
  88. let iv = &encrypted_blob[0..16];
  89. let encrypted = &encrypted_blob[16..encrypted_blob.len() - 20];
  90. let cksum = &encrypted_blob[encrypted_blob.len() - 20..encrypted_blob.len()];
  91. let base_key = {
  92. let mut data = [0u8; 20];
  93. let mut h = crypto::sha1::Sha1::new();
  94. h.input(&shared_key.to_bytes_be());
  95. h.result(&mut data);
  96. data[..16].to_owned()
  97. };
  98. let checksum_key = {
  99. let mut h = crypto::hmac::Hmac::new(crypto::sha1::Sha1::new(), &base_key);
  100. h.input(b"checksum");
  101. h.result().code().to_owned()
  102. };
  103. let encryption_key = {
  104. let mut h = crypto::hmac::Hmac::new(crypto::sha1::Sha1::new(), &base_key);
  105. h.input(b"encryption");
  106. h.result().code().to_owned()
  107. };
  108. let mac = {
  109. let mut h = crypto::hmac::Hmac::new(crypto::sha1::Sha1::new(), &checksum_key);
  110. h.input(encrypted);
  111. h.result().code().to_owned()
  112. };
  113. assert_eq!(&mac[..], cksum);
  114. let decrypted = {
  115. let mut data = vec![0u8; encrypted.len()];
  116. let mut cipher =
  117. crypto::aes::ctr(crypto::aes::KeySize::KeySize128, &encryption_key[0..16], iv);
  118. cipher.process(encrypted, &mut data);
  119. String::from_utf8(data).unwrap()
  120. };
  121. let credentials = Credentials::with_blob(username.to_owned(), &decrypted, &self.0.device_id);
  122. self.0.tx.unbounded_send(credentials).unwrap();
  123. let result = json!({
  124. "status": 101,
  125. "spotifyError": 0,
  126. "statusString": "ERROR-OK"
  127. });
  128. let body = result.to_string();
  129. ::futures::finished(Response::new().with_body(body))
  130. }
  131. fn not_found(&self) -> ::futures::Finished<Response, hyper::Error> {
  132. ::futures::finished(Response::new().with_status(StatusCode::NotFound))
  133. }
  134. }
  135. impl Service for Discovery {
  136. type Request = Request;
  137. type Response = Response;
  138. type Error = hyper::Error;
  139. type Future = Box<Future<Item = Response, Error = hyper::Error>>;
  140. fn call(&self, request: Request) -> Self::Future {
  141. let mut params = BTreeMap::new();
  142. let (method, uri, _, _, body) = request.deconstruct();
  143. if let Some(query) = uri.query() {
  144. params.extend(url::form_urlencoded::parse(query.as_bytes()).into_owned());
  145. }
  146. if method != Get {
  147. debug!("{:?} {:?} {:?}", method, uri.path(), params);
  148. }
  149. let this = self.clone();
  150. Box::new(
  151. body.fold(Vec::new(), |mut acc, chunk| {
  152. acc.extend_from_slice(chunk.as_ref());
  153. Ok::<_, hyper::Error>(acc)
  154. }).map(move |body| {
  155. params.extend(url::form_urlencoded::parse(&body).into_owned());
  156. params
  157. })
  158. .and_then(
  159. move |params| match (method, params.get("action").map(AsRef::as_ref)) {
  160. (Get, Some("getInfo")) => this.handle_get_info(&params),
  161. (Post, Some("addUser")) => this.handle_add_user(&params),
  162. _ => this.not_found(),
  163. },
  164. ),
  165. )
  166. }
  167. }
  168. #[cfg(feature = "with-dns-sd")]
  169. pub struct DiscoveryStream {
  170. credentials: mpsc::UnboundedReceiver<Credentials>,
  171. _svc: DNSService,
  172. }
  173. #[cfg(not(feature = "with-dns-sd"))]
  174. pub struct DiscoveryStream {
  175. credentials: mpsc::UnboundedReceiver<Credentials>,
  176. _svc: mdns::Service,
  177. }
  178. pub fn discovery(
  179. handle: &Handle,
  180. config: ConnectConfig,
  181. device_id: String,
  182. port: u16,
  183. ) -> io::Result<DiscoveryStream> {
  184. let (discovery, creds_rx) = Discovery::new(config.clone(), device_id);
  185. let serve = {
  186. let http = Http::new();
  187. debug!("Zeroconf server listening on 0.0.0.0:{}", port);
  188. http.serve_addr_handle(
  189. &format!("0.0.0.0:{}", port).parse().unwrap(),
  190. &handle,
  191. move || Ok(discovery.clone()),
  192. ).unwrap()
  193. };
  194. let s_port = serve.incoming_ref().local_addr().port();
  195. let server_future = {
  196. let handle = handle.clone();
  197. serve
  198. .for_each(move |connection| {
  199. handle.spawn(connection.then(|_| Ok(())));
  200. Ok(())
  201. })
  202. .then(|_| Ok(()))
  203. };
  204. handle.spawn(server_future);
  205. #[cfg(feature = "with-dns-sd")]
  206. let svc = DNSService::register(
  207. Some(&*config.name),
  208. "_spotify-connect._tcp",
  209. None,
  210. None,
  211. s_port,
  212. &["VERSION=1.0", "CPath=/"],
  213. ).unwrap();
  214. #[cfg(not(feature = "with-dns-sd"))]
  215. let responder = mdns::Responder::spawn(&handle)?;
  216. #[cfg(not(feature = "with-dns-sd"))]
  217. let svc = responder.register(
  218. "_spotify-connect._tcp".to_owned(),
  219. config.name,
  220. s_port,
  221. &["VERSION=1.0", "CPath=/"],
  222. );
  223. Ok(DiscoveryStream {
  224. credentials: creds_rx,
  225. _svc: svc,
  226. })
  227. }
  228. impl Stream for DiscoveryStream {
  229. type Item = Credentials;
  230. type Error = ();
  231. fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
  232. self.credentials.poll()
  233. }
  234. }