From a993b60ffa962d86ca79f51197e41dd9f69c05c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20Li=C3=A9tar?= Date: Sat, 25 Apr 2015 21:32:07 +0100 Subject: [PATCH] Initial commit. --- .gitignore | 3 + Cargo.lock | 107 +++++++++++++++++++++++ Cargo.toml | 28 +++++++ LICENSE | 21 +++++ build.rs | 43 ++++++++++ protocol/authentication.proto | 51 +++++++++++ protocol/keyexchange.proto | 61 ++++++++++++++ protocol/mercury.proto | 49 +++++++++++ protocol/metadata.proto | 154 ++++++++++++++++++++++++++++++++++ protocol/spirc.proto | 92 ++++++++++++++++++++ protocol/spotify.proto | 0 src/connection.rs | 43 ++++++++++ src/cryptoutil.rs | 91 ++++++++++++++++++++ src/main.rs | 28 +++++++ src/protocol.rs | 3 + src/session.rs | 78 +++++++++++++++++ src/util.rs | 20 +++++ 17 files changed, 872 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 LICENSE create mode 100644 build.rs create mode 100644 protocol/authentication.proto create mode 100644 protocol/keyexchange.proto create mode 100644 protocol/mercury.proto create mode 100644 protocol/metadata.proto create mode 100644 protocol/spirc.proto create mode 100644 protocol/spotify.proto create mode 100644 src/connection.rs create mode 100644 src/cryptoutil.rs create mode 100644 src/main.rs create mode 100644 src/protocol.rs create mode 100644 src/session.rs create mode 100644 src/util.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..c34b8d3a --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +target +Cargo.lock +.cargo diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 00000000..dd36ba88 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,107 @@ +[root] +name = "librespot" +version = "0.1.0" +dependencies = [ + "byteorder 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "mod_path 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "num 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)", + "protobuf 0.0.9 (git+https://github.com/plietar/rust-protobuf.git)", + "protobuf_macros 0.1.0 (git+https://github.com/plietar/rust-protobuf-macros.git)", + "rand 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "rust-crypto 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)", + "rust-gmp 0.2.0 (git+https://github.com/plietar/rust-gmp.git)", +] + +[[package]] +name = "byteorder" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "gcc" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "lazy_static" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libc" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "log" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "mod_path" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "num" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-serialize 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "protobuf" +version = "0.0.9" +source = "git+https://github.com/plietar/rust-protobuf.git#f26efc36c09602109a01885449e16d15a8494cb8" + +[[package]] +name = "protobuf_macros" +version = "0.1.0" +source = "git+https://github.com/plietar/rust-protobuf-macros.git#3631dbaac78e955b36fdb71bb79c9b3cdc4bd4d9" + +[[package]] +name = "rand" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rust-crypto" +version = "0.2.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "gcc 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-serialize 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rust-gmp" +version = "0.2.0" +source = "git+https://github.com/plietar/rust-gmp.git#150cb69f787d413e95860dc7268a6399163f468f" + +[[package]] +name = "rustc-serialize" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "time" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "gcc 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..0f330b45 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "librespot" +version = "0.1.0" +authors = ["Paul LiƩtar "] +build = "build.rs" +links = "gmp" + +#[[bin]] +#name = "librespot" +#path = "src/main.rs" + +[dependencies] +mod_path = "*" +byteorder = "*" +num = "*" +rand = "*" +lazy_static = "0.1.*" +rust-crypto = "*" + +[dependencies.protobuf] +git = "https://github.com/plietar/rust-protobuf.git" + +[dependencies.protobuf_macros] +git = "https://github.com/plietar/rust-protobuf-macros.git" + +[dependencies.rust-gmp] +git = "https://github.com/plietar/rust-gmp.git" + diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..b06ecfb2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Paul Lietar + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/build.rs b/build.rs new file mode 100644 index 00000000..4d006b98 --- /dev/null +++ b/build.rs @@ -0,0 +1,43 @@ +use std::env; +use std::process::{Command, Stdio}; +use std::path::Path; + +#[derive(Debug)] +enum ProtobufError { + IoError(::std::io::Error), + Other +} + +impl std::convert::From<::std::io::Error> for ProtobufError { + fn from(e: ::std::io::Error) -> ProtobufError { + ProtobufError::IoError(e) + } +} + +fn compile(prefix : &Path, files : &[&Path]) -> Result<(),ProtobufError>{ + let mut c = Command::new("protoc"); + c.arg("--rust_out").arg(env::var("OUT_DIR").unwrap()) + .arg("--proto_path").arg(prefix.to_str().unwrap()); + for f in files.iter() { + c.arg(f.to_str().unwrap()); + } + + //c.stdout(Stdio::inherit()); + c.stderr(Stdio::inherit()); + + let mut p = try!(c.spawn()); + let r = try!(p.wait()); + return match r.success() { + true => Ok(()), + false => Err(ProtobufError::Other), + }; +} + +fn main() { + let prefix = Path::new("protocol"); + compile(&prefix, &[ + &prefix.join("keyexchange.proto"), + &prefix.join("authentication.proto") + ]).unwrap(); +} + diff --git a/protocol/authentication.proto b/protocol/authentication.proto new file mode 100644 index 00000000..b1c1eb56 --- /dev/null +++ b/protocol/authentication.proto @@ -0,0 +1,51 @@ +message AuthRequest { + enum LoginMethod { + PASSWORD = 0x0; + TOKEN = 0x3; + } + + message Credentials { + optional string username = 0x0a; + required LoginMethod method = 0x14; + required bytes password = 0x1e; + } + required Credentials credentials = 0x0a; + + message Data1 { + required uint32 data0 = 0x0a; + required uint32 data1 = 0x3c; + required string partner = 0x5a; // "Partner %s %s;%s" % ("lenbrook_bluesound", brand, model) + required string deviceid = 0x64; // sha1(os_device_id).hexdigest() + } + required Data1 data1 = 0x32; + + required string version = 0x46; + + message Data3 { + required uint32 data0 = 0x01; + required bytes appkey1 = 0x02; + required bytes appkey2 = 0x03; + required string data3 = 0x04; + required bytes data4 = 0x05; + } + required Data3 data3 = 0x50; +} + +message AuthSuccess { + required string username = 0x0a; + required uint32 data1 = 0x14; + required uint32 data2 = 0x19; + required uint32 data3 = 0x1e; + required bytes data4 = 0x28; + required bytes data5 = 0x32; +} + +message AuthFailure { + required uint32 code = 0x0a; + required Data1 data1 = 0x32; + + message Data1 { + required string data0 = 0x01; + } +} + diff --git a/protocol/keyexchange.proto b/protocol/keyexchange.proto new file mode 100644 index 00000000..44548fc6 --- /dev/null +++ b/protocol/keyexchange.proto @@ -0,0 +1,61 @@ +message Request { + message Data0 { + required uint32 data0 = 0x0a; // 05 + required uint32 data1 = 0x1e; // 02 + required uint64 data2 = 0x28; // 10800000000 + } + + message Data2 { + message Data0 { + required bytes data0 = 0x0a; // 60 + required uint32 data1 = 0x14; // 01 + } + required Data0 data0 = 0x0a; // 65 + } + + required Data0 data0 = 0x0a; // 0d + required uint32 data1 = 0x1e; // 00 + required Data2 data2 = 0x32; // 67 + required bytes random = 0x3c; // 10 + required bytes data4 = 0x46; // 01 + required bytes data5 = 0x50; // 02 + +} + +message Response { + message Data { + message Data0 { + message Data0 { + required bytes data0 = 0x0a; // 60 + required uint32 data1 = 0x14; + required bytes data2 = 0x1e; // 100 + } + required Data0 data0 = 0x0a; + } + message Data3 { + required bytes data0 = 0x0a; + } + + required Data0 data0 = 0x0a; + required bytes data1 = 0x14; + required bytes data2 = 0x1e; + required Data3 data3 = 0x28; + required bytes data4 = 0x32; + required bytes data5 = 0x3c; + } + + required Data data = 0x0a; +} + +message ChallengePacket { + message Data0 { + message Data0 { + required bytes data0 = 0x0a; + } + required Data0 data0 = 0x0a; + } + required Data0 data0 = 0x0a; + required bytes data1 = 0x14; + required bytes data2 = 0x1e; +} + diff --git a/protocol/mercury.proto b/protocol/mercury.proto new file mode 100644 index 00000000..b7eb9eb4 --- /dev/null +++ b/protocol/mercury.proto @@ -0,0 +1,49 @@ +message MercuryRequest { + required string url = 0x01; + optional string mime = 0x02; + required string method = 0x03; +} + +message MercuryReply { + required string url = 0x01; + required string mime = 0x02; + required sint32 code = 0x04; + repeated Header header = 0x06; + + message Header { + required string key = 0x01; + required bytes value = 0x02; + } +} + +message MercuryGetRequest { + required string url = 0x01; + optional string method = 0x03; +} + +message MercuryMultiGetRequest { + repeated MercuryGetRequest request = 0x01; +} + +message MercuryGetReply { + enum CachePolicy { + CACHE_NO = 1; + CACHE_PRIVATE = 2; + CACHE_PUBLIC = 3; + } + optional sint32 code = 0x01; + optional CachePolicy cache_policy = 0x03; + optional uint32 ttl = 0x04; + optional bytes etag = 0x05; + optional string mime = 0x06; + optional bytes body = 0x07; +} + +message MercuryMultiGetReply { + repeated MercuryGetReply reply = 0x1; +} + +message MercurySubscribed { + required string url = 0x1; +} + diff --git a/protocol/metadata.proto b/protocol/metadata.proto new file mode 100644 index 00000000..fc27aede --- /dev/null +++ b/protocol/metadata.proto @@ -0,0 +1,154 @@ +message TopTracks { + optional string country = 1; + repeated Track track = 2; +} +message ActivityPeriod { + optional sint32 start_year = 1; + optional sint32 end_year = 2; + optional sint32 decade = 3; +} +message Artist { + optional bytes gid = 1; + optional string name = 2; + optional sint32 popularity = 3; + repeated TopTracks top_track = 4; + repeated AlbumGroup album_group = 5; + repeated AlbumGroup single_group = 6; + repeated AlbumGroup compilation_group = 7; + repeated AlbumGroup appears_on_group = 8; + repeated string genre = 9; + repeated ExternalId external_id = 10; + repeated Image portrait = 11; + repeated Biography biography = 12; + repeated ActivityPeriod activity_period = 13; + repeated Restriction restriction = 14; + repeated Artist related = 15; + optional bool is_portrait_album_cover = 16; + optional ImageGroup portrait_group = 17; +} +message AlbumGroup { + repeated Album album = 1; +} +message Date { + optional sint32 year = 1; + optional sint32 month = 2; + optional sint32 day = 3; +} +message Album { + enum Type { + ALBUM = 1; + SINGLE = 2; + COMPILATION = 3; + } + optional bytes gid = 1; + optional string name = 2; + repeated Artist artist = 3; + optional Type type = 4; + optional string label = 5; + optional Date date = 6; + optional sint32 popularity = 7; + repeated string genre = 8; + repeated Image cover = 9; + repeated ExternalId external_id = 10; + repeated Disc disc = 11; + repeated string review = 12; + repeated Copyright copyright = 13; + repeated Restriction restriction = 14; + repeated Album related = 15; + repeated SalePeriod sale_period = 16; + optional ImageGroup cover_group = 17; +} + +message Track { + optional bytes gid = 1; + optional string name = 2; + optional Album album = 3; + repeated Artist artist = 4; + optional sint32 number = 5; + optional sint32 disc_number = 6; + optional sint32 duration = 7; + optional sint32 popularity = 8; + optional bool explicit = 9; + repeated ExternalId external_id = 10; + repeated Restriction restriction = 11; + repeated AudioFile file = 12; + repeated Track alternative = 13; + repeated SalePeriod sale_period = 14; + repeated AudioFile preview = 15; +} +message Image { + enum Size { + DEFAULT = 0; + SMALL = 1; + LARGE = 2; + XLARGE = 3; + } + optional bytes file_id = 1; + optional Size size = 2; + optional sint32 width = 3; + optional sint32 height = 4; +} +message ImageGroup { + repeated Image image = 1; +} +message Biography { + optional string text = 1; + repeated Image portrait = 2; + repeated ImageGroup portrait_group = 3; +} +message Disc { + optional sint32 number = 1; + optional string name = 2; + repeated Track track = 3; +} +message Copyright { + enum Type { + P = 0; + C = 1; + } + optional Type type = 1; + optional string text = 2; +} +message Restriction { + enum Catalogue { + FREE = 0; + PREMIUM = 1; + SHUFFLE = 3; + COMMERCIAL = 4; + } + enum Type { + STREAMING = 0; + } + repeated Catalogue catalogue = 1; + optional string countries_allowed = 2; + optional string countries_forbidden = 3; + optional Type type = 4; + repeated string usage = 5; +} + +message SalePeriod { + repeated Restriction restriction = 1; + optional Date start = 2; + optional Date end = 3; +} + +message ExternalId { + optional string type = 1; + optional string id = 2; +} + +message AudioFile { + enum Format { + OGG_VORBIS_96 = 0; + OGG_VORBIS_160 = 1; + OGG_VORBIS_320 = 2; + MP3_256 = 3; + MP3_320 = 4; + MP3_160 = 5; + MP3_96 = 6; + OTHER1 = 7; // TODO + OTHER2 = 8; // TODO + } + optional bytes gid = 1; + optional Format format = 2; +} diff --git a/protocol/spirc.proto b/protocol/spirc.proto new file mode 100644 index 00000000..ad55c388 --- /dev/null +++ b/protocol/spirc.proto @@ -0,0 +1,92 @@ +enum MessageType { + kMessageTypeHello = 1; + kMessageTypeGoodbye = 2; + kMessageTypeNotify = 10; + kMessageTypeLoad = 20; + kMessageTypePlay = 21; + kMessageTypePause = 22; +// kMessageTypePlayPause = 23; + kMessageTypeSeek = 24; + kMessageTypePrev = 25; + kMessageTypeNext = 26; + kMessageTypeVolume = 27; + kMessageTypeShuffle = 28; + kMessageTypeRepeat = 29; + kMessageTypeQueue = 30; + kMessageTypeVolumeDown = 31; + kMessageTypeVolumeUp = 32; + kMessageTypeAddToQueue = 33; +} +enum PlayStatus { + kPlayStatusStop = 0; + kPlayStatusPlay = 1; + kPlayStatusPause = 2; + kPlayStatusLoading = 3; + kPlayStatusError = 4; +} +message Goodbye { + required string reason = 1; +} +message State { + optional string contextURI = 0x2; + optional uint32 index = 0x3; + optional uint32 position = 0x4; + optional PlayStatus status = 0x5; + + optional uint64 timestamp = 0x7; + optional string context_name = 0x8; + optional uint32 duration = 0x9; + optional uint32 data9 = 0xa; + repeated uint64 data10 = 0xb; + optional bool shuffle = 0xd; + optional bool repeat = 0xe; + + optional string data12 = 0x14; + optional uint32 data13 = 0x15; + optional uint32 data14 = 0x18; + optional uint32 data15 = 0x19; + optional uint32 data16 = 0x1a; + repeated QueuedTrack queued = 0x1b; + message QueuedTrack { + optional bytes gid = 0x1; + optional string local_uri = 0x2; + optional uint32 data1 = 0x3; + } +} + +message Frame { + required uint32 version = 1; + required string source = 2; + required string version_string = 3; + required uint32 msgid = 4; + required uint32 type = 5; + + required DeviceInfo device = 0x7; + + //required Goodbye goodbye = 0xb; + optional State state = 0xc; + + optional uint32 position = 0xd; + optional uint32 volume = 0xe; + + optional uint64 timestamp = 0x11; + optional string destination = 0x12; + + message DeviceInfo { + optional string version = 0x1; + required bool active = 0xa; + required bool foreground = 0xb; + required uint32 volume = 0xc; + required string name = 0xd; + optional uint32 data15 = 0xe; + required uint64 activeTime = 0xf; + repeated Data17 data17 = 0x11; + + message Data17 { + required uint32 data0 = 0x1; + optional uint32 data1 = 0x2; + repeated string data2 = 0x3; + } + } +} + diff --git a/protocol/spotify.proto b/protocol/spotify.proto new file mode 100644 index 00000000..e69de29b diff --git a/src/connection.rs b/src/connection.rs new file mode 100644 index 00000000..263f036f --- /dev/null +++ b/src/connection.rs @@ -0,0 +1,43 @@ +use util; + +use byteorder::{ReadBytesExt, WriteBytesExt, BigEndian}; +use std::io::{Write,Read}; +use std::net::TcpStream; + +pub struct Connection { + stream: TcpStream, +} + +impl Connection { + pub fn connect() -> Connection { + Connection { + stream: TcpStream::connect("lon3-accesspoint-a26.ap.spotify.com:4070").unwrap(), + } + } + + pub fn send_packet(&mut self, data: &[u8]) -> Vec { + self.send_packet_prefix(&[], data) + } + + pub fn send_packet_prefix(&mut self, prefix: &[u8], data: &[u8]) -> Vec { + let size = prefix.len() + 4 + data.len(); + let mut buf = Vec::with_capacity(size); + + buf.write(prefix).unwrap(); + buf.write_u32::(size as u32).unwrap(); + buf.write(data).unwrap(); + self.stream.write(&buf).unwrap(); + + buf + } + + pub fn recv_packet(&mut self) -> Vec { + let size : usize = self.stream.read_u32::().unwrap() as usize; + let mut buffer = util::alloc_buffer(size - 4); + + self.stream.read(&mut buffer).unwrap(); + + buffer + } +} + diff --git a/src/cryptoutil.rs b/src/cryptoutil.rs new file mode 100644 index 00000000..46cb31fb --- /dev/null +++ b/src/cryptoutil.rs @@ -0,0 +1,91 @@ +use rand; +use gmp::Mpz; +use std::num::FromPrimitive; +use crypto; +use crypto::mac::Mac; +use std::io::Write; + +use util; + +lazy_static! { + static ref DH_GENERATOR: Mpz = Mpz::from_u64(0x2).unwrap(); + static ref DH_PRIME: Mpz = Mpz::from_bytes_be(&[ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc9, + 0x0f, 0xda, 0xa2, 0x21, 0x68, 0xc2, 0x34, 0xc4, 0xc6, + 0x62, 0x8b, 0x80, 0xdc, 0x1c, 0xd1, 0x29, 0x02, 0x4e, + 0x08, 0x8a, 0x67, 0xcc, 0x74, 0x02, 0x0b, 0xbe, 0xa6, + 0x3b, 0x13, 0x9b, 0x22, 0x51, 0x4a, 0x08, 0x79, 0x8e, + 0x34, 0x04, 0xdd, 0xef, 0x95, 0x19, 0xb3, 0xcd, 0x3a, + 0x43, 0x1b, 0x30, 0x2b, 0x0a, 0x6d, 0xf2, 0x5f, 0x14, + 0x37, 0x4f, 0xe1, 0x35, 0x6d, 0x6d, 0x51, 0xc2, 0x45, + 0xe4, 0x85, 0xb5, 0x76, 0x62, 0x5e, 0x7e, 0xc6, 0xf4, + 0x4c, 0x42, 0xe9, 0xa6, 0x3a, 0x36, 0x20, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff ]); +} + +pub struct Crypto { + private_key: Mpz, + public_key: Mpz, + shared: Option, +} + +pub struct SharedKeys { + pub challenge: Vec, + pub send_key: Vec, + pub recv_key: Vec +} + +impl Crypto { + pub fn new() -> Crypto { + let key_data = util::rand_vec(&mut rand::thread_rng(), 95); + Self::new_with_key(&key_data) + } + + pub fn new_with_key(key_data: &[u8]) -> Crypto { + let private_key = Mpz::from_bytes_be(key_data); + let public_key = DH_GENERATOR.powm(&private_key, &DH_PRIME); + + Crypto { + private_key: private_key, + public_key: public_key, + shared: None, + } + } + + pub fn setup(&mut self, remote_key: &[u8], client_packet: &[u8], server_packet: &[u8]) { + let shared_key = Mpz::from_bytes_be(remote_key).powm(&self.private_key, &DH_PRIME); + + let mut data = Vec::with_capacity(0x54); + let mut h = crypto::hmac::Hmac::new(crypto::sha1::Sha1::new(), &shared_key.to_bytes_be()); + + for i in 1..6 { + h.input(client_packet); + h.input(server_packet); + h.input(&[i]); + data.write(&h.result().code()).unwrap(); + h.reset(); + } + + h = crypto::hmac::Hmac::new(crypto::sha1::Sha1::new(), &data[..0x14]); + h.input(client_packet); + h.input(server_packet); + + self.shared = Some(SharedKeys{ + challenge: h.result().code().to_vec(), + send_key: data[0x14..0x34].to_vec(), + recv_key: data[0x34..0x54].to_vec() + }); + } + + pub fn public_key(&self) -> Vec { + return self.public_key.to_bytes_be(); + } + + pub fn shared(&self) -> &SharedKeys { + match self.shared { + Some(ref shared) => shared, + None => panic!("ABC") + } + } +} + diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 00000000..fd0f7437 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,28 @@ +#![crate_name = "librespot"] + +#![feature(plugin,core)] + +#![plugin(mod_path)] +#![plugin(protobuf_macros)] +#[macro_use] extern crate lazy_static; + +extern crate byteorder; +extern crate crypto; +extern crate gmp; +extern crate num; +extern crate protobuf; +extern crate rand; + +mod connection; +mod cryptoutil; +mod protocol; +mod session; +mod util; + +use session::Session; + +fn main() { + let mut s = Session::new(); + s.login(); +} + diff --git a/src/protocol.rs b/src/protocol.rs new file mode 100644 index 00000000..5f33fcbd --- /dev/null +++ b/src/protocol.rs @@ -0,0 +1,3 @@ +mod_path! keyexchange (concat!(env!("OUT_DIR"), "/keyexchange.rs")); +mod_path! authentication (concat!(env!("OUT_DIR"), "/authentication.rs")); + diff --git a/src/session.rs b/src/session.rs new file mode 100644 index 00000000..b5912ff8 --- /dev/null +++ b/src/session.rs @@ -0,0 +1,78 @@ +use connection::Connection; +use cryptoutil::Crypto; +use protocol; +use util; +use std::iter::{FromIterator,repeat}; + +use protobuf::*; +use rand::thread_rng; + +pub struct Session { + connection: Connection, + crypto: Crypto, +} + +impl Session { + pub fn new() -> Session { + Session { + connection: Connection::connect(), + crypto: Crypto::new(), + } + } + + pub fn login(&mut self) { + let request = protobuf_init!(protocol::keyexchange::Request::new(), { + data0 => { + data0: 0x05, + data1: 0x01, + data2: 0x10800000000, + }, + data1: 0, + data2.data0 => { + data0: self.crypto.public_key(), + data1: 1, + }, + random: util::rand_vec(&mut thread_rng(), 0x10), + data4: vec![0x1e], + data5: vec![0x08, 0x01] + }); + + let init_client_packet = + self.connection.send_packet_prefix(&[0,4], &request.write_to_bytes().unwrap()); + let init_server_packet = + self.connection.recv_packet(); + + let response : protocol::keyexchange::Response = + parse_from_bytes(&init_server_packet).unwrap(); + + protobuf_bind!(response, { data.data0.data0.data0: remote_key }); + + self.crypto.setup(&remote_key, &init_client_packet, &init_server_packet); + + return; + let appkey = vec![]; + let request = protobuf_init!(protocol::authentication::AuthRequest::new(), { + credentials => { + username: "USERNAME".to_string(), + method: protocol::authentication::AuthRequest_LoginMethod::PASSWORD, + password: b"PASSWORD".to_vec(), + }, + data1 => { + data0: 0, + data1: 0, + partner: "Partner blabla".to_string(), + deviceid: "abc".to_string() + }, + version: "master-v1.8.0-gad9e5b46".to_string(), + data3 => { + data0: 1, + appkey1: appkey[0x1..0x81].to_vec(), + appkey2: appkey[0x81..0x141].to_vec(), + data3: "".to_string(), + data4: Vec::from_iter(repeat(0).take(20)) + } + }); + //println!("{:?}", response); + } +} + diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 00000000..5f892dff --- /dev/null +++ b/src/util.rs @@ -0,0 +1,20 @@ +use rand::{Rng,Rand}; + +pub fn rand_vec(rng: &mut G, size: usize) -> Vec { + let mut vec = Vec::with_capacity(size); + + for _ in 0..size { + vec.push(R::rand(rng)); + } + + return vec +} + +pub fn alloc_buffer(size: usize) -> Vec { + let mut vec = Vec::with_capacity(size); + unsafe { + vec.set_len(size); + } + + vec +}