diff --git a/src/authentication.rs b/src/authentication.rs index 6007e162..06197e67 100644 --- a/src/authentication.rs +++ b/src/authentication.rs @@ -1,15 +1,19 @@ use byteorder::{BigEndian, ByteOrder}; use crypto; use crypto::aes; +use crypto::digest::Digest; +use crypto::hmac::Hmac; +use crypto::mac::Mac; use crypto::pbkdf2::pbkdf2; use crypto::sha1::Sha1; -use crypto::hmac::Hmac; -use crypto::digest::Digest; use protobuf::{self, Message, ProtobufEnum}; -use std::io::{self, Read}; +use rand::thread_rng; +use std::io::{self, Read, Write}; use std::result::Result; use rustc_serialize::base64::FromBase64; +use connection::{PlainConnection, CipherConnection}; +use diffie_hellman::DHLocalKeys; use librespot_protocol as protocol; use librespot_protocol::authentication::AuthenticationType; use session::Session; @@ -40,11 +44,99 @@ fn read_bytes(stream: &mut R) -> io::Result> { } impl Session { + pub fn connect(&self) -> CipherConnection { + let local_keys = DHLocalKeys::random(&mut thread_rng()); + + let mut connection = PlainConnection::connect().unwrap(); + + let request = protobuf_init!(protocol::keyexchange::ClientHello::new(), { + build_info => { + product: protocol::keyexchange::Product::PRODUCT_LIBSPOTIFY_EMBEDDED, + platform: protocol::keyexchange::Platform::PLATFORM_LINUX_X86, + version: 0x10800000000, + }, + /* + fingerprints_supported => [ + protocol::keyexchange::Fingerprint::FINGERPRINT_GRAIN + ], + */ + cryptosuites_supported => [ + protocol::keyexchange::Cryptosuite::CRYPTO_SUITE_SHANNON, + //protocol::keyexchange::Cryptosuite::CRYPTO_SUITE_RC4_SHA1_HMAC + ], + /* + powschemes_supported => [ + protocol::keyexchange::Powscheme::POW_HASH_CASH + ], + */ + login_crypto_hello.diffie_hellman => { + gc: local_keys.public_key(), + server_keys_known: 1, + }, + client_nonce: util::rand_vec(&mut thread_rng(), 0x10), + padding: vec![0x1e], + feature_set => { + autoupdate2: true, + } + }); + + let init_client_packet = connection.send_packet_prefix(&[0, 4], + &request.write_to_bytes().unwrap()) + .unwrap(); + let init_server_packet = connection.recv_packet().unwrap(); + + let response: protocol::keyexchange::APResponseMessage = + protobuf::parse_from_bytes(&init_server_packet[4..]).unwrap(); + + let remote_key = response.get_challenge() + .get_login_crypto_challenge() + .get_diffie_hellman() + .get_gs(); + + let shared_secret = local_keys.shared_secret(remote_key); + let (challenge, send_key, recv_key) = { + let mut data = Vec::with_capacity(0x64); + let mut mac = Hmac::new(Sha1::new(), &shared_secret); + + for i in 1..6 { + mac.input(&init_client_packet); + mac.input(&init_server_packet); + mac.input(&[i]); + data.write(&mac.result().code()).unwrap(); + mac.reset(); + } + + mac = Hmac::new(Sha1::new(), &data[..0x14]); + mac.input(&init_client_packet); + mac.input(&init_server_packet); + + (mac.result().code().to_vec(), + data[0x14..0x34].to_vec(), + data[0x34..0x54].to_vec()) + }; + + let packet = protobuf_init!(protocol::keyexchange::ClientResponsePlaintext::new(), { + login_crypto_response.diffie_hellman => { + hmac: challenge + }, + pow_response => {}, + crypto_response => {}, + }); + + + connection.send_packet(&packet.write_to_bytes().unwrap()).unwrap(); + + CipherConnection::new(connection.into_stream(), + &send_key, + &recv_key) + } + fn login(&self, username: String, auth_data: Vec, typ: AuthenticationType) -> Result<(), ()> { + let packet = protobuf_init!(protocol::authentication::ClientResponseEncrypted::new(), { login_credentials => { username: username, @@ -67,15 +159,17 @@ impl Session { } }); - self.connect(); - self.send_packet(0xab, &packet.write_to_bytes().unwrap()).unwrap(); - let (cmd, data) = self.recv(); + let mut connection = self.connect(); + connection.send_packet(0xab, &packet.write_to_bytes().unwrap()).unwrap(); + let (cmd, data) = connection.recv_packet().unwrap(); + match cmd { 0xac => { let welcome_data: protocol::authentication::APWelcome = protobuf::parse_from_bytes(&data).unwrap(); - self.0.data.write().unwrap().canonical_username = - welcome_data.get_canonical_username().to_owned(); + + let username = welcome_data.get_canonical_username().to_owned(); + self.authenticated(username, connection); eprintln!("Authenticated !"); Ok(()) diff --git a/src/discovery.rs b/src/discovery.rs index 46564eb5..4103675c 100644 --- a/src/discovery.rs +++ b/src/discovery.rs @@ -1,12 +1,12 @@ use crypto; use crypto::mac::Mac; use crypto::digest::Digest; -use zeroconf::DNSService; -use tiny_http::{Method, Response, ResponseBox, Server}; use num::BigUint; use url; use rand; use rustc_serialize::base64::{self, ToBase64, FromBase64}; +use tiny_http::{Method, Response, ResponseBox, Server}; +use zeroconf::DNSService; use session::Session; use diffie_hellman::{DH_GENERATOR, DH_PRIME}; diff --git a/src/session.rs b/src/session.rs index ba1858ed..fb762345 100644 --- a/src/session.rs +++ b/src/session.rs @@ -1,27 +1,19 @@ use crypto::digest::Digest; -use crypto::mac::Mac; use crypto::sha1::Sha1; -use crypto::hmac::Hmac; use eventual::Future; -use protobuf::{self, Message}; -use rand::thread_rng; use std::io::Write; use std::path::PathBuf; use std::sync::{Mutex, RwLock, Arc, mpsc}; use audio_key::{AudioKeyManager, AudioKey, AudioKeyError}; use audio_file::{AudioFileManager, AudioFile}; -use connection::{self, PlainConnection, CipherConnection}; +use connection::{self, CipherConnection}; use connection::PacketHandler; -use diffie_hellman::DHLocalKeys; -use librespot_protocol as protocol; use mercury::{MercuryManager, MercuryRequest, MercuryResponse}; use metadata::{MetadataManager, MetadataRef, MetadataTrait}; use stream::{StreamManager, StreamEvent}; use util::{SpotifyId, FileId, mkdir_existing}; -use util; - pub enum Bitrate { Bitrate96, Bitrate160, @@ -37,14 +29,14 @@ pub struct Config { } pub struct SessionData { - pub country: String, - pub canonical_username: String, - pub device_id: String, + country: String, + canonical_username: String, + device_id: String, } pub struct SessionInternal { config: Config, - pub data: RwLock, + data: RwLock, mercury: Mutex, metadata: Mutex, @@ -87,94 +79,10 @@ impl Session { })) } - pub fn connect(&self) { - let local_keys = DHLocalKeys::random(&mut thread_rng()); - - let mut connection = PlainConnection::connect().unwrap(); - - let request = protobuf_init!(protocol::keyexchange::ClientHello::new(), { - build_info => { - product: protocol::keyexchange::Product::PRODUCT_LIBSPOTIFY_EMBEDDED, - platform: protocol::keyexchange::Platform::PLATFORM_LINUX_X86, - version: 0x10800000000, - }, - /* - fingerprints_supported => [ - protocol::keyexchange::Fingerprint::FINGERPRINT_GRAIN - ], - */ - cryptosuites_supported => [ - protocol::keyexchange::Cryptosuite::CRYPTO_SUITE_SHANNON, - //protocol::keyexchange::Cryptosuite::CRYPTO_SUITE_RC4_SHA1_HMAC - ], - /* - powschemes_supported => [ - protocol::keyexchange::Powscheme::POW_HASH_CASH - ], - */ - login_crypto_hello.diffie_hellman => { - gc: local_keys.public_key(), - server_keys_known: 1, - }, - client_nonce: util::rand_vec(&mut thread_rng(), 0x10), - padding: vec![0x1e], - feature_set => { - autoupdate2: true, - } - }); - - let init_client_packet = connection.send_packet_prefix(&[0, 4], - &request.write_to_bytes().unwrap()) - .unwrap(); - let init_server_packet = connection.recv_packet().unwrap(); - - let response: protocol::keyexchange::APResponseMessage = - protobuf::parse_from_bytes(&init_server_packet[4..]).unwrap(); - - let remote_key = response.get_challenge() - .get_login_crypto_challenge() - .get_diffie_hellman() - .get_gs(); - - let shared_secret = local_keys.shared_secret(remote_key); - let (challenge, send_key, recv_key) = { - let mut data = Vec::with_capacity(0x64); - let mut mac = Hmac::new(Sha1::new(), &shared_secret); - - for i in 1..6 { - mac.input(&init_client_packet); - mac.input(&init_server_packet); - mac.input(&[i]); - data.write(&mac.result().code()).unwrap(); - mac.reset(); - } - - mac = Hmac::new(Sha1::new(), &data[..0x14]); - mac.input(&init_client_packet); - mac.input(&init_server_packet); - - (mac.result().code().to_vec(), - data[0x14..0x34].to_vec(), - data[0x34..0x54].to_vec()) - }; - - let packet = protobuf_init!(protocol::keyexchange::ClientResponsePlaintext::new(), { - login_crypto_response.diffie_hellman => { - hmac: challenge - }, - pow_response => {}, - crypto_response => {}, - }); - - - connection.send_packet(&packet.write_to_bytes().unwrap()).unwrap(); - - let cipher_connection = CipherConnection::new(connection.into_stream(), - &send_key, - &recv_key); - - *self.0.rx_connection.lock().unwrap() = Some(cipher_connection.clone()); - *self.0.tx_connection.lock().unwrap() = Some(cipher_connection); + pub fn authenticated(&self, username: String, connection: CipherConnection) { + self.0.data.write().unwrap().canonical_username = username; + *self.0.rx_connection.lock().unwrap() = Some(connection.clone()); + *self.0.tx_connection.lock().unwrap() = Some(connection); } pub fn poll(&self) {