mirror of
https://github.com/librespot-org/librespot.git
synced 2024-12-18 17:11:53 +00:00
First working version of protocol handshake.
Key exchange and authentication is functional. Protocol definition has been moved to separate crate to speed up build time. Various cleanups. Take login info from command line, rather than hardcoded.
This commit is contained in:
parent
15f39607e7
commit
1ad62e6f18
19 changed files with 821 additions and 290 deletions
75
Cargo.lock
generated
75
Cargo.lock
generated
|
@ -4,15 +4,24 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"byteorder 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
"byteorder 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"lazy_static 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
"lazy_static 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"librespot-protocol 0.1.0",
|
||||||
"mod_path 0.1.5 (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.24 (registry+https://github.com/rust-lang/crates.io-index)",
|
"num 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"protobuf 0.0.9 (git+https://github.com/plietar/rust-protobuf.git)",
|
"protobuf 0.0.10 (git+https://github.com/stepancheg/rust-protobuf.git)",
|
||||||
"protobuf_macros 0.1.0 (git+https://github.com/plietar/rust-protobuf-macros.git)",
|
"protobuf_macros 0.1.0 (git+https://github.com/plietar/rust-protobuf-macros.git)",
|
||||||
"rand 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rand 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"readall 0.1.0 (git+https://github.com/plietar/rust-readall.git)",
|
||||||
"rust-crypto 0.2.31 (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)",
|
"rust-gmp 0.2.0 (git+https://github.com/plietar/rust-gmp.git)",
|
||||||
|
"shannon 0.1.0 (git+https://github.com/plietar/rust-shannon.git)",
|
||||||
|
"vergen 0.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "byteorder"
|
name = "byteorder"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
|
@ -20,7 +29,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gcc"
|
name = "gcc"
|
||||||
version = "0.3.4"
|
version = "0.3.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -30,9 +39,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.1.6"
|
version = "0.1.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "librespot-protocol"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"mod_path 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"protobuf 0.0.10 (git+https://github.com/stepancheg/rust-protobuf.git)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mod_path"
|
name = "mod_path"
|
||||||
version = "0.1.5"
|
version = "0.1.5"
|
||||||
|
@ -44,36 +61,41 @@ version = "0.1.24"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rand 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rand 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"rustc-serialize 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rustc-serialize 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "protobuf"
|
name = "protobuf"
|
||||||
version = "0.0.9"
|
version = "0.0.10"
|
||||||
source = "git+https://github.com/plietar/rust-protobuf.git#f26efc36c09602109a01885449e16d15a8494cb8"
|
source = "git+https://github.com/stepancheg/rust-protobuf.git#41fde39aed305e0fb71ef6a8d92b35ee50550bde"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "protobuf_macros"
|
name = "protobuf_macros"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/plietar/rust-protobuf-macros.git#3631dbaac78e955b36fdb71bb79c9b3cdc4bd4d9"
|
source = "git+https://github.com/plietar/rust-protobuf-macros.git#e95dbc5bdf6c13787e2385d66d9d003afcaf9f17"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand"
|
name = "rand"
|
||||||
version = "0.3.8"
|
version = "0.3.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "readall"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "git+https://github.com/plietar/rust-readall.git#d2bcc1de325705230e79ba444cde2f39b469f891"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rust-crypto"
|
name = "rust-crypto"
|
||||||
version = "0.2.31"
|
version = "0.2.31"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"gcc 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
"gcc 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"libc 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"rand 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rand 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"rustc-serialize 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rustc-serialize 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"time 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)",
|
"time 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -84,15 +106,40 @@ source = "git+https://github.com/plietar/rust-gmp.git#eaf298870d63712d18f8fab6bb
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc-serialize"
|
name = "rustc-serialize"
|
||||||
version = "0.3.13"
|
version = "0.3.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "shannon"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "git+https://github.com/plietar/rust-shannon.git#83a49c3397e1e546e6079cf54a0e5b2f85c6b13f"
|
||||||
|
dependencies = [
|
||||||
|
"shannon-sys 0.1.0 (git+https://github.com/plietar/rust-shannon.git)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "shannon-sys"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "git+https://github.com/plietar/rust-shannon.git#83a49c3397e1e546e6079cf54a0e5b2f85c6b13f"
|
||||||
|
dependencies = [
|
||||||
|
"gcc 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time"
|
name = "time"
|
||||||
version = "0.1.25"
|
version = "0.1.25"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"gcc 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
"gcc 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"libc 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "vergen"
|
||||||
|
version = "0.0.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"time 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
16
Cargo.toml
16
Cargo.toml
|
@ -3,11 +3,9 @@ name = "librespot"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Paul Liétar <paul@lietar.net>"]
|
authors = ["Paul Liétar <paul@lietar.net>"]
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
links = "gmp"
|
|
||||||
|
|
||||||
#[[bin]]
|
[dependencies.librespot-protocol]
|
||||||
#name = "librespot"
|
path = "protocol"
|
||||||
#path = "src/main.rs"
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
mod_path = "*"
|
mod_path = "*"
|
||||||
|
@ -18,7 +16,7 @@ lazy_static = "0.1.*"
|
||||||
rust-crypto = "*"
|
rust-crypto = "*"
|
||||||
|
|
||||||
[dependencies.protobuf]
|
[dependencies.protobuf]
|
||||||
git = "https://github.com/plietar/rust-protobuf.git"
|
git = "https://github.com/stepancheg/rust-protobuf.git"
|
||||||
|
|
||||||
[dependencies.protobuf_macros]
|
[dependencies.protobuf_macros]
|
||||||
git = "https://github.com/plietar/rust-protobuf-macros.git"
|
git = "https://github.com/plietar/rust-protobuf-macros.git"
|
||||||
|
@ -26,3 +24,11 @@ git = "https://github.com/plietar/rust-protobuf-macros.git"
|
||||||
[dependencies.rust-gmp]
|
[dependencies.rust-gmp]
|
||||||
git = "https://github.com/plietar/rust-gmp.git"
|
git = "https://github.com/plietar/rust-gmp.git"
|
||||||
|
|
||||||
|
[dependencies.shannon]
|
||||||
|
git = "https://github.com/plietar/rust-shannon.git"
|
||||||
|
|
||||||
|
[dependencies.readall]
|
||||||
|
git = "https://github.com/plietar/rust-readall.git"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
vergen = "*"
|
||||||
|
|
41
build.rs
41
build.rs
|
@ -1,43 +1,6 @@
|
||||||
use std::env;
|
extern crate vergen;
|
||||||
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() {
|
fn main() {
|
||||||
let prefix = Path::new("protocol");
|
vergen::vergen(vergen::SHORT_SHA);
|
||||||
compile(&prefix, &[
|
|
||||||
&prefix.join("keyexchange.proto"),
|
|
||||||
&prefix.join("authentication.proto")
|
|
||||||
]).unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
12
protocol/Cargo.toml
Normal file
12
protocol/Cargo.toml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
[package]
|
||||||
|
name = "librespot-protocol"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Paul Liétar <paul@lietar.net>"]
|
||||||
|
build = "build.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
mod_path = "*"
|
||||||
|
|
||||||
|
[dependencies.protobuf]
|
||||||
|
git = "https://github.com/stepancheg/rust-protobuf.git"
|
||||||
|
|
|
@ -1,51 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
45
protocol/build.rs
Normal file
45
protocol/build.rs
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
use std::env;
|
||||||
|
use std::process::{Command, Stdio};
|
||||||
|
use std::path::{Path,PathBuf};
|
||||||
|
|
||||||
|
#[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 root = PathBuf::from(&env::var("CARGO_MANIFEST_DIR").unwrap());
|
||||||
|
let proto = root.join("proto");
|
||||||
|
compile(&proto, &[
|
||||||
|
&proto.join("keyexchange.proto"),
|
||||||
|
&proto.join("authentication.proto")
|
||||||
|
]).unwrap();
|
||||||
|
}
|
||||||
|
|
|
@ -1,61 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
165
protocol/proto/authentication.proto
Normal file
165
protocol/proto/authentication.proto
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
// size=30
|
||||||
|
message ClientResponseEncrypted {
|
||||||
|
required LoginCredentials login_credentials = 0xa; // idx=0 offset=c
|
||||||
|
optional AccountCreation account_creation = 0x14; // idx=1 offset=10
|
||||||
|
optional FingerprintResponseUnion fingerprint_response = 0x1e; // idx=2 offset=14
|
||||||
|
optional PeerTicketUnion peer_ticket = 0x28; // idx=3 offset=18
|
||||||
|
required SystemInfo system_info = 0x32; // idx=4 offset=1c
|
||||||
|
optional string platform_model = 0x3c; // idx=5 offset=20
|
||||||
|
optional string version_string = 0x46; // idx=6 offset=24
|
||||||
|
optional LibspotifyAppKey appkey = 0x50; // idx=7 offset=28
|
||||||
|
optional ClientInfo client_info = 0x5a; // idx=8 offset=2c
|
||||||
|
}
|
||||||
|
|
||||||
|
// size=18
|
||||||
|
message LoginCredentials {
|
||||||
|
optional string username = 0xa; // idx=0 offset=c
|
||||||
|
required Type typ = 0x14; // idx=1 offset=10
|
||||||
|
optional bytes auth_data = 0x1e; // idx=2 offset=14
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Type {
|
||||||
|
AUTHENTICATION_USER_PASS = 0x0;
|
||||||
|
AUTHENTICATION_STORED_SPOTIFY_CREDENTIALS = 0x1;
|
||||||
|
AUTHENTICATION_STORED_FACEBOOK_CREDENTIALS = 0x2;
|
||||||
|
AUTHENTICATION_SPOTIFY_TOKEN = 0x3;
|
||||||
|
AUTHENTICATION_FACEBOOK_TOKEN = 0x4;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AccountCreation {
|
||||||
|
ACCOUNT_CREATION_ALWAYS_PROMPT = 0x1;
|
||||||
|
ACCOUNT_CREATION_ALWAYS_CREATE = 0x3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// size=14
|
||||||
|
message FingerprintResponseUnion {
|
||||||
|
optional FingerprintGrainResponse grain = 0xa; // idx=0 offset=c
|
||||||
|
optional FingerprintHmacRipemdResponse hmac_ripemd = 0x14; // idx=1 offset=10
|
||||||
|
}
|
||||||
|
|
||||||
|
// size=1c
|
||||||
|
message FingerprintGrainResponse {
|
||||||
|
required bytes encrypted_key = 0xa; // idx=0 offset=c size=f
|
||||||
|
}
|
||||||
|
|
||||||
|
// size=20
|
||||||
|
message FingerprintHmacRipemdResponse {
|
||||||
|
required bytes hmac = 0xa; // idx=0 offset=c size=13
|
||||||
|
}
|
||||||
|
|
||||||
|
// size=14
|
||||||
|
message PeerTicketUnion {
|
||||||
|
optional PeerTicketPublicKey public_key = 0xa; // idx=0 offset=c
|
||||||
|
optional PeerTicketOld old_ticket = 0x14; // idx=1 offset=10
|
||||||
|
}
|
||||||
|
|
||||||
|
// size=8c
|
||||||
|
message PeerTicketPublicKey {
|
||||||
|
required bytes public_key = 0xa; // idx=0 offset=c size=7f
|
||||||
|
}
|
||||||
|
|
||||||
|
// size=90
|
||||||
|
message PeerTicketOld {
|
||||||
|
required bytes peer_ticket = 0xa; // idx=0 offset=c
|
||||||
|
required bytes peer_ticket_signature = 0x14; // idx=1 offset=10 size=7f
|
||||||
|
}
|
||||||
|
|
||||||
|
// size=34
|
||||||
|
message SystemInfo {
|
||||||
|
required CpuFamily cpu_family = 0xa; // idx=0 offset=c
|
||||||
|
optional uint32 cpu_subtype = 0x14; // idx=1 offset=10
|
||||||
|
optional uint32 cpu_ext = 0x1e; // idx=2 offset=14
|
||||||
|
optional Brand brand = 0x28; // idx=3 offset=18
|
||||||
|
optional uint32 brand_flags = 0x32; // idx=4 offset=1c
|
||||||
|
required Os os = 0x3c; // idx=5 offset=20
|
||||||
|
optional uint32 os_version = 0x46; // idx=6 offset=24
|
||||||
|
optional uint32 os_ext = 0x50; // idx=7 offset=28
|
||||||
|
optional string system_information_string = 0x5a; // idx=8 offset=2c
|
||||||
|
optional string device_id = 0x64; // idx=9 offset=30
|
||||||
|
}
|
||||||
|
|
||||||
|
enum CpuFamily {
|
||||||
|
CPU_UNKNOWN = 0x0;
|
||||||
|
CPU_X86 = 0x1;
|
||||||
|
CPU_X86_64 = 0x2;
|
||||||
|
CPU_PPC = 0x3;
|
||||||
|
CPU_PPC_64 = 0x4;
|
||||||
|
CPU_ARM = 0x5;
|
||||||
|
CPU_IA64 = 0x6;
|
||||||
|
CPU_SH = 0x7;
|
||||||
|
CPU_MIPS = 0x8;
|
||||||
|
CPU_BLACKFIN = 0x9;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Brand {
|
||||||
|
BRAND_UNBRANDED = 0x0;
|
||||||
|
BRAND_INQ = 0x1;
|
||||||
|
BRAND_HTC = 0x2;
|
||||||
|
BRAND_NOKIA = 0x3;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Os {
|
||||||
|
OS_UNKNOWN = 0x0;
|
||||||
|
OS_WINDOWS = 0x1;
|
||||||
|
OS_OSX = 0x2;
|
||||||
|
OS_IPHONE = 0x3;
|
||||||
|
OS_S60 = 0x4;
|
||||||
|
OS_LINUX = 0x5;
|
||||||
|
OS_WINDOWS_CE = 0x6;
|
||||||
|
OS_ANDROID = 0x7;
|
||||||
|
OS_PALM = 0x8;
|
||||||
|
OS_FREEBSD = 0x9;
|
||||||
|
OS_BLACKBERRY = 0xa;
|
||||||
|
OS_SONOS = 0xb;
|
||||||
|
OS_LOGITECH = 0xc;
|
||||||
|
OS_WP7 = 0xd;
|
||||||
|
OS_ONKYO = 0xe;
|
||||||
|
OS_PHILIPS = 0xf;
|
||||||
|
OS_WD = 0x10;
|
||||||
|
OS_VOLVO = 0x11;
|
||||||
|
OS_TIVO = 0x12;
|
||||||
|
OS_AWOX = 0x13;
|
||||||
|
OS_MEEGO = 0x14;
|
||||||
|
OS_QNXNTO = 0x15;
|
||||||
|
OS_BCO = 0x16;
|
||||||
|
}
|
||||||
|
|
||||||
|
// size=168
|
||||||
|
message LibspotifyAppKey {
|
||||||
|
required uint32 version = 0x1; // idx=0 offset=c
|
||||||
|
required bytes devkey = 0x2; // idx=1 offset=10 size=7f
|
||||||
|
required bytes signature = 0x3; // idx=2 offset=90 size=bf
|
||||||
|
required string useragent = 0x4; // idx=3 offset=150
|
||||||
|
required bytes callback_hash = 0x5; // idx=4 offset=154 size=13
|
||||||
|
}
|
||||||
|
|
||||||
|
// size=18
|
||||||
|
message ClientInfo {
|
||||||
|
optional bool limited = 0x1; // idx=0 offset=c
|
||||||
|
optional ClientInfoFacebook fb = 0x2; // idx=1 offset=10
|
||||||
|
optional string language = 0x3; // idx=2 offset=14
|
||||||
|
}
|
||||||
|
|
||||||
|
// size=10
|
||||||
|
message ClientInfoFacebook {
|
||||||
|
optional string machine_id = 0x1; // idx=0 offset=c
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
239
protocol/proto/keyexchange.proto
Normal file
239
protocol/proto/keyexchange.proto
Normal file
|
@ -0,0 +1,239 @@
|
||||||
|
// size=80
|
||||||
|
message ClientHello {
|
||||||
|
required BuildInfo build_info = 0xa; // idx=0 offset=c
|
||||||
|
repeated Fingerprint fingerprints_supported = 0x14; // idx=ffff offset=10
|
||||||
|
repeated Cryptosuite cryptosuites_supported = 0x1e; // idx=ffff offset=2c
|
||||||
|
repeated Powscheme powschemes_supported = 0x28; // idx=ffff offset=48
|
||||||
|
required LoginCryptoHelloUnion login_crypto_hello = 0x32; // idx=1 offset=64
|
||||||
|
required bytes client_nonce = 0x3c; // idx=2 offset=68 size=f
|
||||||
|
optional bytes padding = 0x46; // idx=3 offset=78
|
||||||
|
optional FeatureSet feature_set = 0x50; // idx=4 offset=7c
|
||||||
|
}
|
||||||
|
|
||||||
|
// size=38
|
||||||
|
message BuildInfo {
|
||||||
|
required Product product = 0xa; // idx=0 offset=c
|
||||||
|
repeated ProductFlags product_flags = 0x14; // idx=ffff offset=10
|
||||||
|
required Platform platform = 0x1e; // idx=1 offset=2c
|
||||||
|
required uint64 version = 0x28; // idx=2 offset=30 extra=246558
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Product {
|
||||||
|
PRODUCT_CLIENT = 0x0;
|
||||||
|
PRODUCT_LIBSPOTIFY= 0x1;
|
||||||
|
PRODUCT_MOBILE = 0x2;
|
||||||
|
PRODUCT_PARTNER = 0x3;
|
||||||
|
PRODUCT_LIBSPOTIFY_EMBEDDED = 0x5;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ProductFlags {
|
||||||
|
PRODUCT_FLAG_NONE = 0x0;
|
||||||
|
PRODUCT_FLAG_DEV_BUILD = 0x1;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Platform {
|
||||||
|
PLATFORM_WIN32_X86 = 0x0;
|
||||||
|
PLATFORM_OSX_X86 = 0x1;
|
||||||
|
PLATFORM_LINUX_X86 = 0x2;
|
||||||
|
PLATFORM_IPHONE_ARM = 0x3;
|
||||||
|
PLATFORM_S60_ARM = 0x4;
|
||||||
|
PLATFORM_OSX_PPC = 0x5;
|
||||||
|
PLATFORM_ANDROID_ARM = 0x6;
|
||||||
|
PLATFORM_WINDOWS_CE_ARM = 0x7;
|
||||||
|
PLATFORM_LINUX_X86_64 = 0x8;
|
||||||
|
PLATFORM_OSX_X86_64 = 0x9;
|
||||||
|
PLATFORM_PALM_ARM = 0xa;
|
||||||
|
PLATFORM_LINUX_SH = 0xb;
|
||||||
|
PLATFORM_FREEBSD_X86 = 0xc;
|
||||||
|
PLATFORM_FREEBSD_X86_64 = 0xd;
|
||||||
|
PLATFORM_BLACKBERRY_ARM = 0xe;
|
||||||
|
PLATFORM_SONOS = 0xf;
|
||||||
|
PLATFORM_LINUX_MIPS = 0x10;
|
||||||
|
PLATFORM_LINUX_ARM = 0x11;
|
||||||
|
PLATFORM_LOGITECH_ARM = 0x12;
|
||||||
|
PLATFORM_LINUX_BLACKFIN = 0x13;
|
||||||
|
PLATFORM_WP7_ARM = 0x14;
|
||||||
|
PLATFORM_ONKYO_ARM = 0x15;
|
||||||
|
PLATFORM_QNXNTO_ARM = 0x16;
|
||||||
|
PLATFORM_BCO_ARM = 0x17;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Fingerprint {
|
||||||
|
FINGERPRINT_GRAIN = 0x0;
|
||||||
|
FINGERPRINT_HMAC_RIPEMD = 0x1;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Cryptosuite {
|
||||||
|
CRYPTO_SUITE_SHANNON = 0x0;
|
||||||
|
CRYPTO_SUITE_RC4_SHA1_HMAC = 0x1;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Powscheme {
|
||||||
|
POW_HASH_CASH = 0x0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// size=10
|
||||||
|
message LoginCryptoHelloUnion {
|
||||||
|
optional LoginCryptoDiffieHellmanHello diffie_hellman = 0xa; // idx=0 offset=c
|
||||||
|
}
|
||||||
|
|
||||||
|
// size=70
|
||||||
|
message LoginCryptoDiffieHellmanHello {
|
||||||
|
required bytes gc = 0xa; // idx=0 offset=c size=5f
|
||||||
|
required uint32 server_keys_known = 0x14; // idx=1 offset=6c
|
||||||
|
}
|
||||||
|
|
||||||
|
// size=10
|
||||||
|
message FeatureSet {
|
||||||
|
optional bool autoupdate2 = 0x1; // idx=0 offset=c
|
||||||
|
optional bool current_location = 0x2; // idx=1 offset=d
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// size=18
|
||||||
|
message APResponseMessage {
|
||||||
|
optional APChallenge challenge = 0xa; // idx=0 offset=c
|
||||||
|
optional UpgradeRequiredMessage upgrade = 0x14; // idx=1 offset=10
|
||||||
|
optional APLoginFailed login_failed = 0x1e; // idx=2 offset=14
|
||||||
|
}
|
||||||
|
|
||||||
|
// size=30
|
||||||
|
message APChallenge {
|
||||||
|
required LoginCryptoChallengeUnion login_crypto_challenge = 0xa; // idx=0 offset=c
|
||||||
|
required FingerprintChallengeUnion fingerprint_challenge = 0x14; // idx=1 offset=10
|
||||||
|
required PoWChallengeUnion pow_challenge = 0x1e; // idx=2 offset=14
|
||||||
|
required CryptoChallengeUnion crypto_challenge = 0x28; // idx=3 offset=18
|
||||||
|
required bytes server_nonce = 0x32; // idx=4 offset=1c size=f
|
||||||
|
optional bytes padding = 0x3c; // idx=5 offset=2c
|
||||||
|
}
|
||||||
|
|
||||||
|
// size=10
|
||||||
|
message LoginCryptoChallengeUnion {
|
||||||
|
optional LoginCryptoDiffieHellmanChallenge diffie_hellman = 0xa; // idx=0 offset=c
|
||||||
|
}
|
||||||
|
|
||||||
|
// size=170
|
||||||
|
message LoginCryptoDiffieHellmanChallenge {
|
||||||
|
required bytes gs = 0xa; // idx=0 offset=c size=5f
|
||||||
|
required int32 server_signature_key = 0x14; // idx=1 offset=6c type=int8
|
||||||
|
required bytes gs_signature = 0x1e; // idx=2 offset=6d size=ff
|
||||||
|
}
|
||||||
|
|
||||||
|
// size=14
|
||||||
|
message FingerprintChallengeUnion {
|
||||||
|
optional FingerprintGrainChallenge grain = 0xa; // idx=0 offset=c
|
||||||
|
optional FingerprintHmacRipemdChallenge hmac_ripemd = 0x14; // idx=1 offset=10
|
||||||
|
}
|
||||||
|
|
||||||
|
// size=1c
|
||||||
|
message FingerprintGrainChallenge {
|
||||||
|
required bytes kek = 0xa; // idx=0 offset=c size=f
|
||||||
|
}
|
||||||
|
|
||||||
|
// size=20
|
||||||
|
message FingerprintHmacRipemdChallenge {
|
||||||
|
required bytes challenge = 0xa; // idx=0 offset=c size=13
|
||||||
|
}
|
||||||
|
|
||||||
|
// size=10
|
||||||
|
message PoWChallengeUnion {
|
||||||
|
optional PoWHashCashChallenge hash_cash = 0xa; // idx=0 offset=c
|
||||||
|
}
|
||||||
|
|
||||||
|
// size=24
|
||||||
|
message PoWHashCashChallenge {
|
||||||
|
optional bytes prefix = 0xa; // idx=0 offset=c size=f
|
||||||
|
optional int32 length = 0x14; // idx=1 offset=1c type=int8
|
||||||
|
optional int32 target = 0x1e; // idx=2 offset=20
|
||||||
|
}
|
||||||
|
|
||||||
|
// size=14
|
||||||
|
message CryptoChallengeUnion {
|
||||||
|
optional CryptoShannonChallenge shannon = 0xa; // idx=0 offset=c
|
||||||
|
optional CryptoRc4Sha1HmacChallenge rc4_sha1_hmac = 0x14; // idx=1 offset=10
|
||||||
|
}
|
||||||
|
|
||||||
|
// size=8
|
||||||
|
message CryptoShannonChallenge {
|
||||||
|
}
|
||||||
|
|
||||||
|
// size=8
|
||||||
|
message CryptoRc4Sha1HmacChallenge {
|
||||||
|
}
|
||||||
|
|
||||||
|
// size=18
|
||||||
|
message UpgradeRequiredMessage {
|
||||||
|
required bytes upgrade_signed_part = 0xa; // idx=0 offset=c
|
||||||
|
required bytes signature = 0x14; // idx=1 offset=10
|
||||||
|
optional string http_suffix = 0x1e; // idx=2 offset=14
|
||||||
|
}
|
||||||
|
|
||||||
|
// size=1c
|
||||||
|
message APLoginFailed {
|
||||||
|
required ErrorCode error_code = 0xa; // idx=0 offset=c
|
||||||
|
optional int32 retry_delay = 0x14; // idx=1 offset=10
|
||||||
|
optional int32 expiry = 0x1e; // idx=2 offset=14
|
||||||
|
optional string error_description = 0x28; // idx=3 offset=18
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ErrorCode {
|
||||||
|
ProtocolError = 0x0;
|
||||||
|
TryAnotherAP = 0x2;
|
||||||
|
BadConnectionId = 0x5;
|
||||||
|
TravelRestriction = 0x9;
|
||||||
|
PremiumAccountRequired = 0xb;
|
||||||
|
BadCredentials = 0xc;
|
||||||
|
CouldNotValidateCredentials = 0xd;
|
||||||
|
AccountExists = 0xe;
|
||||||
|
ExtraVerificationRequired = 0xf;
|
||||||
|
InvalidAppKey = 0x10;
|
||||||
|
ApplicationBanned = 0x11;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// size=18
|
||||||
|
message ClientResponsePlaintext {
|
||||||
|
required LoginCryptoResponseUnion login_crypto_response = 0xa; // idx=0 offset=c
|
||||||
|
required PoWResponseUnion pow_response = 0x14; // idx=1 offset=10
|
||||||
|
required CryptoResponseUnion crypto_response = 0x1e; // idx=2 offset=14
|
||||||
|
}
|
||||||
|
|
||||||
|
// size=10
|
||||||
|
message LoginCryptoResponseUnion {
|
||||||
|
optional LoginCryptoDiffieHellmanResponse diffie_hellman = 0xa; // idx=0 offset=c
|
||||||
|
}
|
||||||
|
|
||||||
|
// size=20
|
||||||
|
message LoginCryptoDiffieHellmanResponse {
|
||||||
|
required bytes hmac = 0xa; // idx=0 offset=c size=13
|
||||||
|
}
|
||||||
|
|
||||||
|
// size=10
|
||||||
|
message PoWResponseUnion {
|
||||||
|
optional PoWHashCashResponse hash_cash = 0xa; // idx=0 offset=c
|
||||||
|
}
|
||||||
|
|
||||||
|
// size=1c
|
||||||
|
message PoWHashCashResponse {
|
||||||
|
required bytes hash_suffix = 0xa; // idx=0 offset=c size=f
|
||||||
|
}
|
||||||
|
|
||||||
|
// size=14
|
||||||
|
message CryptoResponseUnion {
|
||||||
|
optional CryptoShannonResponse shannon = 0xa; // idx=0 offset=c
|
||||||
|
optional CryptoRc4Sha1HmacResponse rc4_sha1_hmac = 0x14; // idx=1 offset=10
|
||||||
|
}
|
||||||
|
|
||||||
|
// size=10
|
||||||
|
message CryptoShannonResponse {
|
||||||
|
optional int32 dummy = 0x1; // idx=0 offset=c type=uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
// size=10
|
||||||
|
message CryptoRc4Sha1HmacResponse {
|
||||||
|
optional int32 dummy = 0x1; // idx=0 offset=c type=uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
#![feature(plugin)]
|
||||||
|
#![plugin(mod_path)]
|
||||||
|
|
||||||
|
extern crate protobuf;
|
||||||
|
|
||||||
mod_path! keyexchange (concat!(env!("OUT_DIR"), "/keyexchange.rs"));
|
mod_path! keyexchange (concat!(env!("OUT_DIR"), "/keyexchange.rs"));
|
||||||
mod_path! authentication (concat!(env!("OUT_DIR"), "/authentication.rs"));
|
mod_path! authentication (concat!(env!("OUT_DIR"), "/authentication.rs"));
|
||||||
|
|
|
@ -1,43 +1,110 @@
|
||||||
use util;
|
use util;
|
||||||
|
|
||||||
use byteorder::{ReadBytesExt, WriteBytesExt, BigEndian};
|
use byteorder::{self, ReadBytesExt, WriteBytesExt, BigEndian, ByteOrder};
|
||||||
use std::io::{Write,Read};
|
use keys::SharedKeys;
|
||||||
|
use readall::ReadAllExt;
|
||||||
|
use shannon::ShannonStream;
|
||||||
|
use std::convert;
|
||||||
|
use std::io;
|
||||||
|
use std::io::Write;
|
||||||
use std::net::TcpStream;
|
use std::net::TcpStream;
|
||||||
|
use std::result;
|
||||||
|
|
||||||
pub struct Connection {
|
#[derive(Debug)]
|
||||||
stream: TcpStream,
|
pub enum Error {
|
||||||
|
IoError(io::Error),
|
||||||
|
Other
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Connection {
|
pub type Result<T> = result::Result<T, Error>;
|
||||||
pub fn connect() -> Connection {
|
|
||||||
Connection {
|
impl convert::From<io::Error> for Error {
|
||||||
stream: TcpStream::connect("lon3-accesspoint-a26.ap.spotify.com:4070").unwrap(),
|
fn from(err: io::Error) -> Error {
|
||||||
|
Error::IoError(err)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl convert::From<byteorder::Error> for Error {
|
||||||
|
fn from(err: byteorder::Error) -> Error {
|
||||||
|
match err {
|
||||||
|
byteorder::Error::Io(e) => Error::IoError(e),
|
||||||
|
_ => Error::Other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PlainConnection {
|
||||||
|
stream: TcpStream
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CipherConnection {
|
||||||
|
stream: ShannonStream<TcpStream>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PlainConnection {
|
||||||
|
pub fn connect() -> Result<PlainConnection> {
|
||||||
|
Ok(PlainConnection {
|
||||||
|
stream: try!(TcpStream::connect("lon3-accesspoint-a26.ap.spotify.com:4070")),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_packet(&mut self, data: &[u8]) -> Vec<u8> {
|
pub fn send_packet(&mut self, data: &[u8]) -> Result<Vec<u8>> {
|
||||||
self.send_packet_prefix(&[], data)
|
self.send_packet_prefix(&[], data)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_packet_prefix(&mut self, prefix: &[u8], data: &[u8]) -> Vec<u8> {
|
pub fn send_packet_prefix(&mut self, prefix: &[u8], data: &[u8]) -> Result<Vec<u8>> {
|
||||||
let size = prefix.len() + 4 + data.len();
|
let size = prefix.len() + 4 + data.len();
|
||||||
let mut buf = Vec::with_capacity(size);
|
let mut buf = Vec::with_capacity(size);
|
||||||
|
|
||||||
buf.write(prefix).unwrap();
|
try!(buf.write(prefix));
|
||||||
buf.write_u32::<BigEndian>(size as u32).unwrap();
|
try!(buf.write_u32::<BigEndian>(size as u32));
|
||||||
buf.write(data).unwrap();
|
try!(buf.write(data));
|
||||||
self.stream.write(&buf).unwrap();
|
try!(self.stream.write(&buf));
|
||||||
|
try!(self.stream.flush());
|
||||||
|
|
||||||
buf
|
Ok(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn recv_packet(&mut self) -> Vec<u8> {
|
pub fn recv_packet(&mut self) -> Result<Vec<u8>> {
|
||||||
let size : usize = self.stream.read_u32::<BigEndian>().unwrap() as usize;
|
let size = try!(self.stream.read_u32::<BigEndian>()) as usize;
|
||||||
let mut buffer = util::alloc_buffer(size - 4);
|
let mut buffer = util::alloc_buffer(size);
|
||||||
|
|
||||||
self.stream.read(&mut buffer).unwrap();
|
BigEndian::write_u32(&mut buffer, size as u32);
|
||||||
|
try!(self.stream.read_all(&mut buffer[4..]));
|
||||||
|
|
||||||
buffer
|
Ok(buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setup_cipher(self, keys: SharedKeys) -> CipherConnection {
|
||||||
|
CipherConnection{
|
||||||
|
stream: ShannonStream::new(self.stream, &keys.send_key(), &keys.recv_key())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl CipherConnection {
|
||||||
|
pub fn send_encrypted_packet(&mut self, cmd: u8, data: &[u8]) -> Result<()> {
|
||||||
|
try!(self.stream.write_u8(cmd)); try!(self.stream.write_u16::<BigEndian>(data.len() as u16));
|
||||||
|
try!(self.stream.write(data));
|
||||||
|
|
||||||
|
try!(self.stream.finish_send());
|
||||||
|
try!(self.stream.flush());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn recv_packet(&mut self) -> Result<(u8, Vec<u8>)> {
|
||||||
|
let cmd = try!(self.stream.read_u8());
|
||||||
|
let size = try!(self.stream.read_u16::<BigEndian>()) as usize;
|
||||||
|
|
||||||
|
let mut data = vec![0; size];
|
||||||
|
try!(self.stream.read_all(&mut data));
|
||||||
|
|
||||||
|
try!(self.stream.finish_recv());
|
||||||
|
|
||||||
|
Ok((cmd, data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use rand;
|
use rand;
|
||||||
use gmp::Mpz;
|
use gmp::Mpz;
|
||||||
use std::num::FromPrimitive;
|
use num::FromPrimitive;
|
||||||
use crypto;
|
use crypto;
|
||||||
use crypto::mac::Mac;
|
use crypto::mac::Mac;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
@ -23,69 +23,80 @@ lazy_static! {
|
||||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff ]);
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff ]);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Crypto {
|
pub struct PrivateKeys {
|
||||||
private_key: Mpz,
|
private_key: Mpz,
|
||||||
public_key: Mpz,
|
public_key: Mpz,
|
||||||
shared: Option<SharedKeys>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SharedKeys {
|
pub struct SharedKeys {
|
||||||
pub challenge: Vec<u8>,
|
private: PrivateKeys,
|
||||||
pub send_key: Vec<u8>,
|
challenge: Vec<u8>,
|
||||||
pub recv_key: Vec<u8>
|
send_key: Vec<u8>,
|
||||||
|
recv_key: Vec<u8>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Crypto {
|
impl PrivateKeys {
|
||||||
pub fn new() -> Crypto {
|
pub fn new() -> PrivateKeys {
|
||||||
let key_data = util::rand_vec(&mut rand::thread_rng(), 95);
|
let key_data = util::rand_vec(&mut rand::thread_rng(), 95);
|
||||||
Self::new_with_key(&key_data)
|
Self::new_with_key(&key_data)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_with_key(key_data: &[u8]) -> Crypto {
|
pub fn new_with_key(key_data: &[u8]) -> PrivateKeys {
|
||||||
let private_key = Mpz::from_bytes_be(key_data);
|
let private_key = Mpz::from_bytes_be(key_data);
|
||||||
let public_key = DH_GENERATOR.powm(&private_key, &DH_PRIME);
|
let public_key = DH_GENERATOR.powm(&private_key, &DH_PRIME);
|
||||||
|
|
||||||
Crypto {
|
PrivateKeys {
|
||||||
private_key: private_key,
|
private_key: private_key,
|
||||||
public_key: public_key,
|
public_key: public_key,
|
||||||
shared: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setup(&mut self, remote_key: &[u8], client_packet: &[u8], server_packet: &[u8]) {
|
pub fn private_key(&self) -> Vec<u8> {
|
||||||
let shared_key = Mpz::from_bytes_be(remote_key).powm(&self.private_key, &DH_PRIME);
|
return self.private_key.to_bytes_be();
|
||||||
|
|
||||||
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<u8> {
|
pub fn public_key(&self) -> Vec<u8> {
|
||||||
return self.public_key.to_bytes_be();
|
return self.public_key.to_bytes_be();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn shared(&self) -> &SharedKeys {
|
pub fn add_remote_key(self, remote_key: &[u8], client_packet: &[u8], server_packet: &[u8]) -> SharedKeys {
|
||||||
match self.shared {
|
let shared_key = Mpz::from_bytes_be(remote_key).powm(&self.private_key, &DH_PRIME);
|
||||||
Some(ref shared) => shared,
|
|
||||||
None => panic!("ABC")
|
let mut data = Vec::with_capacity(0x64);
|
||||||
|
let mut mac = crypto::hmac::Hmac::new(crypto::sha1::Sha1::new(), &shared_key.to_bytes_be());
|
||||||
|
|
||||||
|
for i in 1..6 {
|
||||||
|
mac.input(client_packet);
|
||||||
|
mac.input(server_packet);
|
||||||
|
mac.input(&[i]);
|
||||||
|
data.write(&mac.result().code()).unwrap();
|
||||||
|
mac.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
mac = crypto::hmac::Hmac::new(crypto::sha1::Sha1::new(), &data[..0x14]);
|
||||||
|
mac.input(client_packet);
|
||||||
|
mac.input(server_packet);
|
||||||
|
|
||||||
|
SharedKeys {
|
||||||
|
private: self,
|
||||||
|
challenge: mac.result().code().to_vec(),
|
||||||
|
send_key: data[0x14..0x34].to_vec(),
|
||||||
|
recv_key: data[0x34..0x54].to_vec(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl SharedKeys {
|
||||||
|
pub fn challenge(&self) -> &[u8] {
|
||||||
|
&self.challenge
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_key(&self) -> &[u8] {
|
||||||
|
&self.send_key
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn recv_key(&self) -> &[u8] {
|
||||||
|
&self.recv_key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
34
src/main.rs
34
src/main.rs
|
@ -1,8 +1,7 @@
|
||||||
#![crate_name = "librespot"]
|
#![crate_name = "librespot"]
|
||||||
|
|
||||||
#![feature(plugin,core)]
|
#![feature(plugin)]
|
||||||
|
|
||||||
#![plugin(mod_path)]
|
|
||||||
#![plugin(protobuf_macros)]
|
#![plugin(protobuf_macros)]
|
||||||
#[macro_use] extern crate lazy_static;
|
#[macro_use] extern crate lazy_static;
|
||||||
|
|
||||||
|
@ -11,18 +10,39 @@ extern crate crypto;
|
||||||
extern crate gmp;
|
extern crate gmp;
|
||||||
extern crate num;
|
extern crate num;
|
||||||
extern crate protobuf;
|
extern crate protobuf;
|
||||||
|
extern crate shannon;
|
||||||
extern crate rand;
|
extern crate rand;
|
||||||
|
extern crate readall;
|
||||||
|
|
||||||
|
extern crate librespot_protocol;
|
||||||
|
|
||||||
mod connection;
|
mod connection;
|
||||||
mod cryptoutil;
|
mod keys;
|
||||||
mod protocol;
|
|
||||||
mod session;
|
mod session;
|
||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
use session::Session;
|
use std::fs::File;
|
||||||
|
use std::io::Read;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use session::{Session,Config};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut s = Session::new();
|
let mut args = std::env::args().skip(1);
|
||||||
s.login();
|
let mut appkey_file = File::open(Path::new(&args.next().unwrap())).unwrap();
|
||||||
|
let username = args.next().unwrap();
|
||||||
|
let password = args.next().unwrap();
|
||||||
|
|
||||||
|
let mut appkey = Vec::new();
|
||||||
|
appkey_file.read_to_end(&mut appkey).unwrap();
|
||||||
|
|
||||||
|
let config = Config {
|
||||||
|
application_key: appkey,
|
||||||
|
user_agent: "ABC".to_string(),
|
||||||
|
device_id: "ABC".to_string()
|
||||||
|
};
|
||||||
|
let mut s = Session::new(config);
|
||||||
|
|
||||||
|
s.login(username, password);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
164
src/session.rs
164
src/session.rs
|
@ -1,78 +1,132 @@
|
||||||
use connection::Connection;
|
use connection::{PlainConnection, CipherConnection};
|
||||||
use cryptoutil::Crypto;
|
use keys::PrivateKeys;
|
||||||
use protocol;
|
use librespot_protocol as protocol;
|
||||||
use util;
|
use util;
|
||||||
use std::iter::{FromIterator,repeat};
|
|
||||||
|
|
||||||
|
use crypto::sha1::Sha1;
|
||||||
|
use crypto::digest::Digest;
|
||||||
use protobuf::*;
|
use protobuf::*;
|
||||||
use rand::thread_rng;
|
use rand::thread_rng;
|
||||||
|
|
||||||
|
pub struct Config {
|
||||||
|
pub application_key: Vec<u8>,
|
||||||
|
pub user_agent: String,
|
||||||
|
pub device_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Session {
|
pub struct Session {
|
||||||
connection: Connection,
|
config: Config,
|
||||||
crypto: Crypto,
|
connection: CipherConnection,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Session {
|
impl Session {
|
||||||
pub fn new() -> Session {
|
pub fn new(mut config: Config) -> Session {
|
||||||
Session {
|
config.device_id = {
|
||||||
connection: Connection::connect(),
|
let mut h = Sha1::new();
|
||||||
crypto: Crypto::new(),
|
h.input_str(&config.device_id);
|
||||||
}
|
h.result_str()
|
||||||
}
|
};
|
||||||
|
|
||||||
pub fn login(&mut self) {
|
|
||||||
let request = protobuf_init!(protocol::keyexchange::Request::new(), {
|
let keys = PrivateKeys::new();
|
||||||
data0 => {
|
let mut connection = PlainConnection::connect().unwrap();
|
||||||
data0: 0x05,
|
|
||||||
data1: 0x01,
|
let request = protobuf_init!(protocol::keyexchange::ClientHello::new(), {
|
||||||
data2: 0x10800000000,
|
build_info => {
|
||||||
|
product: protocol::keyexchange::Product::PRODUCT_LIBSPOTIFY_EMBEDDED,
|
||||||
|
platform: protocol::keyexchange::Platform::PLATFORM_LINUX_X86,
|
||||||
|
version: 0x10800000000,
|
||||||
},
|
},
|
||||||
data1: 0,
|
/*
|
||||||
data2.data0 => {
|
fingerprints_supported => [
|
||||||
data0: self.crypto.public_key(),
|
protocol::keyexchange::Fingerprint::FINGERPRINT_GRAIN
|
||||||
data1: 1,
|
],
|
||||||
|
*/
|
||||||
|
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: keys.public_key(),
|
||||||
|
server_keys_known: 1,
|
||||||
},
|
},
|
||||||
random: util::rand_vec(&mut thread_rng(), 0x10),
|
client_nonce: util::rand_vec(&mut thread_rng(), 0x10),
|
||||||
data4: vec![0x1e],
|
padding: vec![0x1e],
|
||||||
data5: vec![0x08, 0x01]
|
feature_set => {
|
||||||
|
autoupdate2: true,
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let init_client_packet =
|
let init_client_packet =
|
||||||
self.connection.send_packet_prefix(&[0,4], &request.write_to_bytes().unwrap());
|
connection.send_packet_prefix(&[0,4], &request.write_to_bytes().unwrap()).unwrap();
|
||||||
let init_server_packet =
|
let init_server_packet =
|
||||||
self.connection.recv_packet();
|
connection.recv_packet().unwrap();
|
||||||
|
|
||||||
let response : protocol::keyexchange::Response =
|
let response : protocol::keyexchange::APResponseMessage =
|
||||||
parse_from_bytes(&init_server_packet).unwrap();
|
parse_from_bytes(&init_server_packet[4..]).unwrap();
|
||||||
|
|
||||||
protobuf_bind!(response, { data.data0.data0.data0: remote_key });
|
protobuf_bind!(response, {
|
||||||
|
challenge => {
|
||||||
self.crypto.setup(&remote_key, &init_client_packet, &init_server_packet);
|
login_crypto_challenge.diffie_hellman => {
|
||||||
|
gs: remote_key,
|
||||||
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);
|
|
||||||
|
let shared_keys = keys.add_remote_key(remote_key, &init_client_packet, &init_server_packet);
|
||||||
|
|
||||||
|
let packet = protobuf_init!(protocol::keyexchange::ClientResponsePlaintext::new(), {
|
||||||
|
login_crypto_response.diffie_hellman => {
|
||||||
|
hmac: shared_keys.challenge().to_vec()
|
||||||
|
},
|
||||||
|
pow_response => {},
|
||||||
|
crypto_response => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
connection.send_packet(&packet.write_to_bytes().unwrap()).unwrap();
|
||||||
|
|
||||||
|
Session {
|
||||||
|
config: config,
|
||||||
|
connection: connection.setup_cipher(shared_keys)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn login(&mut self, username: String, password: String) {
|
||||||
|
let packet = protobuf_init!(protocol::authentication::ClientResponseEncrypted::new(), {
|
||||||
|
login_credentials => {
|
||||||
|
username: username,
|
||||||
|
typ: protocol::authentication::Type::AUTHENTICATION_USER_PASS,
|
||||||
|
auth_data: password.into_bytes(),
|
||||||
|
},
|
||||||
|
system_info => {
|
||||||
|
cpu_family: protocol::authentication::CpuFamily::CPU_UNKNOWN,
|
||||||
|
os: protocol::authentication::Os::OS_UNKNOWN,
|
||||||
|
system_information_string: "librespot".to_string(),
|
||||||
|
device_id: self.config.device_id.clone()
|
||||||
|
},
|
||||||
|
version_string: util::version::version_string(),
|
||||||
|
appkey => {
|
||||||
|
version: self.config.application_key[0] as u32,
|
||||||
|
devkey: self.config.application_key[0x1..0x81].to_vec(),
|
||||||
|
signature: self.config.application_key[0x81..0x141].to_vec(),
|
||||||
|
useragent: self.config.user_agent.clone(),
|
||||||
|
callback_hash: vec![0; 20],
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
self.connection.send_encrypted_packet(
|
||||||
|
0xab,
|
||||||
|
&packet.write_to_bytes().unwrap()).unwrap();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let (cmd, data) = self.connection.recv_packet().unwrap();
|
||||||
|
println!("{:x}", cmd);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,3 +18,12 @@ pub fn alloc_buffer(size: usize) -> Vec<u8> {
|
||||||
|
|
||||||
vec
|
vec
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub mod version {
|
||||||
|
include!(concat!(env!("OUT_DIR"), "/version.rs"));
|
||||||
|
|
||||||
|
pub fn version_string() -> String {
|
||||||
|
format!("librespot-{}", short_sha())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue