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:
Paul Liétar 2015-05-09 11:07:24 +01:00 committed by Paul Liétar
parent 15f39607e7
commit 1ad62e6f18
19 changed files with 821 additions and 290 deletions

75
Cargo.lock generated
View file

@ -4,15 +4,24 @@ version = "0.1.0"
dependencies = [
"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)",
"librespot-protocol 0.1.0",
"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)",
"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)",
"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-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]]
name = "byteorder"
version = "0.3.9"
@ -20,7 +29,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "gcc"
version = "0.3.4"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@ -30,9 +39,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "libc"
version = "0.1.6"
version = "0.1.7"
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]]
name = "mod_path"
version = "0.1.5"
@ -44,36 +61,41 @@ version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"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]]
name = "protobuf"
version = "0.0.9"
source = "git+https://github.com/plietar/rust-protobuf.git#f26efc36c09602109a01885449e16d15a8494cb8"
version = "0.0.10"
source = "git+https://github.com/stepancheg/rust-protobuf.git#41fde39aed305e0fb71ef6a8d92b35ee50550bde"
[[package]]
name = "protobuf_macros"
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]]
name = "rand"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
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)",
"gcc 0.3.5 (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)",
"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)",
]
@ -84,15 +106,40 @@ source = "git+https://github.com/plietar/rust-gmp.git#eaf298870d63712d18f8fab6bb
[[package]]
name = "rustc-serialize"
version = "0.3.13"
version = "0.3.14"
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]]
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)",
"gcc 0.3.5 (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)",
]

View file

@ -3,11 +3,9 @@ name = "librespot"
version = "0.1.0"
authors = ["Paul Liétar <paul@lietar.net>"]
build = "build.rs"
links = "gmp"
#[[bin]]
#name = "librespot"
#path = "src/main.rs"
[dependencies.librespot-protocol]
path = "protocol"
[dependencies]
mod_path = "*"
@ -18,7 +16,7 @@ lazy_static = "0.1.*"
rust-crypto = "*"
[dependencies.protobuf]
git = "https://github.com/plietar/rust-protobuf.git"
git = "https://github.com/stepancheg/rust-protobuf.git"
[dependencies.protobuf_macros]
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]
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 = "*"

View file

@ -1,43 +1,6 @@
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),
};
}
extern crate vergen;
fn main() {
let prefix = Path::new("protocol");
compile(&prefix, &[
&prefix.join("keyexchange.proto"),
&prefix.join("authentication.proto")
]).unwrap();
vergen::vergen(vergen::SHORT_SHA);
}

12
protocol/Cargo.toml Normal file
View 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"

View file

@ -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
View 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();
}

View file

@ -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;
}

View 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;
}
}

View 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
}

View file

@ -1,3 +1,8 @@
#![feature(plugin)]
#![plugin(mod_path)]
extern crate protobuf;
mod_path! keyexchange (concat!(env!("OUT_DIR"), "/keyexchange.rs"));
mod_path! authentication (concat!(env!("OUT_DIR"), "/authentication.rs"));

View file

@ -1,43 +1,110 @@
use util;
use byteorder::{ReadBytesExt, WriteBytesExt, BigEndian};
use std::io::{Write,Read};
use byteorder::{self, ReadBytesExt, WriteBytesExt, BigEndian, ByteOrder};
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::result;
pub struct Connection {
stream: TcpStream,
#[derive(Debug)]
pub enum Error {
IoError(io::Error),
Other
}
impl Connection {
pub fn connect() -> Connection {
Connection {
stream: TcpStream::connect("lon3-accesspoint-a26.ap.spotify.com:4070").unwrap(),
pub type Result<T> = result::Result<T, Error>;
impl convert::From<io::Error> for Error {
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 fn send_packet(&mut self, data: &[u8]) -> Vec<u8> {
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]) -> Result<Vec<u8>> {
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 mut buf = Vec::with_capacity(size);
buf.write(prefix).unwrap();
buf.write_u32::<BigEndian>(size as u32).unwrap();
buf.write(data).unwrap();
self.stream.write(&buf).unwrap();
try!(buf.write(prefix));
try!(buf.write_u32::<BigEndian>(size as u32));
try!(buf.write(data));
try!(self.stream.write(&buf));
try!(self.stream.flush());
buf
Ok(buf)
}
pub fn recv_packet(&mut self) -> Vec<u8> {
let size : usize = self.stream.read_u32::<BigEndian>().unwrap() as usize;
let mut buffer = util::alloc_buffer(size - 4);
pub fn recv_packet(&mut self) -> Result<Vec<u8>> {
let size = try!(self.stream.read_u32::<BigEndian>()) as usize;
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))
}
}

View file

@ -1,6 +1,6 @@
use rand;
use gmp::Mpz;
use std::num::FromPrimitive;
use num::FromPrimitive;
use crypto;
use crypto::mac::Mac;
use std::io::Write;
@ -23,69 +23,80 @@ lazy_static! {
0xff, 0xff, 0xff, 0xff, 0xff, 0xff ]);
}
pub struct Crypto {
pub struct PrivateKeys {
private_key: Mpz,
public_key: Mpz,
shared: Option<SharedKeys>,
}
pub struct SharedKeys {
pub challenge: Vec<u8>,
pub send_key: Vec<u8>,
pub recv_key: Vec<u8>
private: PrivateKeys,
challenge: Vec<u8>,
send_key: Vec<u8>,
recv_key: Vec<u8>
}
impl Crypto {
pub fn new() -> Crypto {
impl PrivateKeys {
pub fn new() -> PrivateKeys {
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 {
pub fn new_with_key(key_data: &[u8]) -> PrivateKeys {
let private_key = Mpz::from_bytes_be(key_data);
let public_key = DH_GENERATOR.powm(&private_key, &DH_PRIME);
Crypto {
PrivateKeys {
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 private_key(&self) -> Vec<u8> {
return self.private_key.to_bytes_be();
}
pub fn public_key(&self) -> Vec<u8> {
return self.public_key.to_bytes_be();
}
pub fn shared(&self) -> &SharedKeys {
match self.shared {
Some(ref shared) => shared,
None => panic!("ABC")
pub fn add_remote_key(self, remote_key: &[u8], client_packet: &[u8], server_packet: &[u8]) -> SharedKeys {
let shared_key = Mpz::from_bytes_be(remote_key).powm(&self.private_key, &DH_PRIME);
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
}
}

View file

@ -1,8 +1,7 @@
#![crate_name = "librespot"]
#![feature(plugin,core)]
#![feature(plugin)]
#![plugin(mod_path)]
#![plugin(protobuf_macros)]
#[macro_use] extern crate lazy_static;
@ -11,18 +10,39 @@ extern crate crypto;
extern crate gmp;
extern crate num;
extern crate protobuf;
extern crate shannon;
extern crate rand;
extern crate readall;
extern crate librespot_protocol;
mod connection;
mod cryptoutil;
mod protocol;
mod keys;
mod session;
mod util;
use session::Session;
use std::fs::File;
use std::io::Read;
use std::path::Path;
use session::{Session,Config};
fn main() {
let mut s = Session::new();
s.login();
let mut args = std::env::args().skip(1);
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);
}

View file

@ -1,78 +1,132 @@
use connection::Connection;
use cryptoutil::Crypto;
use protocol;
use connection::{PlainConnection, CipherConnection};
use keys::PrivateKeys;
use librespot_protocol as protocol;
use util;
use std::iter::{FromIterator,repeat};
use crypto::sha1::Sha1;
use crypto::digest::Digest;
use protobuf::*;
use rand::thread_rng;
pub struct Config {
pub application_key: Vec<u8>,
pub user_agent: String,
pub device_id: String,
}
pub struct Session {
connection: Connection,
crypto: Crypto,
config: Config,
connection: CipherConnection,
}
impl Session {
pub fn new() -> Session {
Session {
connection: Connection::connect(),
crypto: Crypto::new(),
}
}
pub fn new(mut config: Config) -> Session {
config.device_id = {
let mut h = Sha1::new();
h.input_str(&config.device_id);
h.result_str()
};
pub fn login(&mut self) {
let request = protobuf_init!(protocol::keyexchange::Request::new(), {
data0 => {
data0: 0x05,
data1: 0x01,
data2: 0x10800000000,
let keys = PrivateKeys::new();
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,
},
data1: 0,
data2.data0 => {
data0: self.crypto.public_key(),
data1: 1,
/*
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: keys.public_key(),
server_keys_known: 1,
},
random: util::rand_vec(&mut thread_rng(), 0x10),
data4: vec![0x1e],
data5: vec![0x08, 0x01]
client_nonce: util::rand_vec(&mut thread_rng(), 0x10),
padding: vec![0x1e],
feature_set => {
autoupdate2: true,
}
});
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 =
self.connection.recv_packet();
connection.recv_packet().unwrap();
let response : protocol::keyexchange::Response =
parse_from_bytes(&init_server_packet).unwrap();
let response : protocol::keyexchange::APResponseMessage =
parse_from_bytes(&init_server_packet[4..]).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))
protobuf_bind!(response, {
challenge => {
login_crypto_challenge.diffie_hellman => {
gs: remote_key,
}
}
});
//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);
}
}
}

View file

@ -18,3 +18,12 @@ pub fn alloc_buffer(size: usize) -> Vec<u8> {
vec
}
pub mod version {
include!(concat!(env!("OUT_DIR"), "/version.rs"));
pub fn version_string() -> String {
format!("librespot-{}", short_sha())
}
}