2016-01-01 23:16:12 +00:00
|
|
|
use byteorder::{BigEndian, ByteOrder};
|
|
|
|
use crypto;
|
|
|
|
use crypto::aes;
|
2016-01-26 23:01:45 +00:00
|
|
|
use crypto::digest::Digest;
|
|
|
|
use crypto::hmac::Hmac;
|
2016-01-01 23:16:12 +00:00
|
|
|
use crypto::pbkdf2::pbkdf2;
|
|
|
|
use crypto::sha1::Sha1;
|
2016-03-13 20:45:31 +00:00
|
|
|
use protobuf::ProtobufEnum;
|
2016-03-13 22:35:09 +00:00
|
|
|
use std::io::{self, Read, Write};
|
|
|
|
use std::fs::File;
|
|
|
|
use std::path::Path;
|
|
|
|
use rustc_serialize::base64::{self, FromBase64, ToBase64};
|
|
|
|
use rustc_serialize::json;
|
2016-01-01 23:16:12 +00:00
|
|
|
|
2016-01-27 10:40:00 +00:00
|
|
|
use protocol::authentication::AuthenticationType;
|
2016-01-01 23:16:12 +00:00
|
|
|
|
2016-03-13 22:35:09 +00:00
|
|
|
#[derive(Debug, Clone)]
|
2016-03-13 20:45:31 +00:00
|
|
|
pub struct Credentials {
|
|
|
|
pub username: String,
|
|
|
|
pub auth_type: AuthenticationType,
|
|
|
|
pub auth_data: Vec<u8>,
|
2016-01-01 23:16:12 +00:00
|
|
|
}
|
|
|
|
|
2016-03-13 22:35:09 +00:00
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
#[derive(RustcDecodable, RustcEncodable)]
|
|
|
|
struct StoredCredentials {
|
|
|
|
pub username: String,
|
|
|
|
pub auth_type: i32,
|
|
|
|
pub auth_data: String,
|
|
|
|
}
|
|
|
|
|
2016-03-13 20:45:31 +00:00
|
|
|
impl Credentials {
|
|
|
|
pub fn with_password(username: String, password: String) -> Credentials {
|
|
|
|
Credentials {
|
|
|
|
username: username,
|
|
|
|
auth_type: AuthenticationType::AUTHENTICATION_USER_PASS,
|
|
|
|
auth_data: password.into_bytes(),
|
|
|
|
}
|
2016-01-26 23:01:45 +00:00
|
|
|
}
|
|
|
|
|
2016-03-13 20:45:31 +00:00
|
|
|
pub fn with_blob(username: String, encrypted_blob: &str, device_id: &str) -> Credentials {
|
|
|
|
fn read_u8<R: Read>(stream: &mut R) -> io::Result<u8> {
|
|
|
|
let mut data = [0u8];
|
|
|
|
try!(stream.read_exact(&mut data));
|
|
|
|
Ok(data[0])
|
|
|
|
}
|
2016-01-26 23:01:45 +00:00
|
|
|
|
2016-03-13 20:45:31 +00:00
|
|
|
fn read_int<R: Read>(stream: &mut R) -> io::Result<u32> {
|
|
|
|
let lo = try!(read_u8(stream)) as u32;
|
|
|
|
if lo & 0x80 == 0 {
|
|
|
|
return Ok(lo);
|
2016-01-01 23:16:12 +00:00
|
|
|
}
|
2016-01-26 23:01:45 +00:00
|
|
|
|
2016-03-13 20:45:31 +00:00
|
|
|
let hi = try!(read_u8(stream)) as u32;
|
|
|
|
Ok(lo & 0x7f | hi << 7)
|
|
|
|
}
|
2016-01-01 23:16:12 +00:00
|
|
|
|
2016-03-13 20:45:31 +00:00
|
|
|
fn read_bytes<R: Read>(stream: &mut R) -> io::Result<Vec<u8>> {
|
|
|
|
let length = try!(read_int(stream));
|
|
|
|
let mut data = vec![0u8; length as usize];
|
|
|
|
try!(stream.read_exact(&mut data));
|
2016-01-01 23:16:12 +00:00
|
|
|
|
2016-03-13 20:45:31 +00:00
|
|
|
Ok(data)
|
2016-01-01 23:16:12 +00:00
|
|
|
}
|
|
|
|
|
2016-03-13 20:45:31 +00:00
|
|
|
let encrypted_blob = encrypted_blob.from_base64().unwrap();
|
2016-01-01 23:16:12 +00:00
|
|
|
|
|
|
|
let secret = {
|
|
|
|
let mut data = [0u8; 20];
|
|
|
|
let mut h = crypto::sha1::Sha1::new();
|
2016-03-13 20:45:31 +00:00
|
|
|
h.input(device_id.as_bytes());
|
2016-01-01 23:16:12 +00:00
|
|
|
h.result(&mut data);
|
|
|
|
data
|
|
|
|
};
|
|
|
|
|
|
|
|
let key = {
|
|
|
|
let mut data = [0u8; 24];
|
|
|
|
let mut mac = Hmac::new(Sha1::new(), &secret);
|
|
|
|
pbkdf2(&mut mac, username.as_bytes(), 0x100, &mut data[0..20]);
|
|
|
|
|
|
|
|
let mut hash = Sha1::new();
|
|
|
|
hash.input(&data[0..20]);
|
|
|
|
hash.result(&mut data[0..20]);
|
|
|
|
BigEndian::write_u32(&mut data[20..], 20);
|
|
|
|
data
|
|
|
|
};
|
|
|
|
|
|
|
|
let blob = {
|
|
|
|
// Anyone know what this block mode is ?
|
2016-03-13 20:45:31 +00:00
|
|
|
let mut data = vec![0u8; encrypted_blob.len()];
|
2016-01-02 15:19:39 +00:00
|
|
|
let mut cipher = aes::ecb_decryptor(aes::KeySize::KeySize192,
|
|
|
|
&key,
|
|
|
|
crypto::blockmodes::NoPadding);
|
2016-03-13 20:45:31 +00:00
|
|
|
cipher.decrypt(&mut crypto::buffer::RefReadBuffer::new(&encrypted_blob),
|
2016-01-01 23:16:12 +00:00
|
|
|
&mut crypto::buffer::RefWriteBuffer::new(&mut data),
|
2016-01-02 15:19:39 +00:00
|
|
|
true)
|
|
|
|
.unwrap();
|
2016-01-01 23:16:12 +00:00
|
|
|
|
2016-03-13 20:45:31 +00:00
|
|
|
let l = encrypted_blob.len();
|
2016-01-02 15:19:39 +00:00
|
|
|
for i in 0..l - 0x10 {
|
2016-01-01 23:16:12 +00:00
|
|
|
data[l - i - 1] ^= data[l - i - 0x11];
|
|
|
|
}
|
|
|
|
|
|
|
|
data
|
|
|
|
};
|
|
|
|
|
|
|
|
let mut cursor = io::Cursor::new(&blob);
|
|
|
|
read_u8(&mut cursor).unwrap();
|
|
|
|
read_bytes(&mut cursor).unwrap();
|
|
|
|
read_u8(&mut cursor).unwrap();
|
|
|
|
let auth_type = read_int(&mut cursor).unwrap();
|
|
|
|
let auth_type = AuthenticationType::from_i32(auth_type as i32).unwrap();
|
|
|
|
read_u8(&mut cursor).unwrap();
|
|
|
|
let auth_data = read_bytes(&mut cursor).unwrap();;
|
|
|
|
|
2016-03-13 20:45:31 +00:00
|
|
|
Credentials {
|
|
|
|
username: username,
|
|
|
|
auth_type: auth_type,
|
|
|
|
auth_data: auth_data,
|
|
|
|
}
|
2016-01-01 23:16:12 +00:00
|
|
|
}
|
2016-03-13 22:35:09 +00:00
|
|
|
|
|
|
|
pub fn from_reader<R: Read>(mut reader: R) -> Credentials {
|
|
|
|
let mut contents = String::new();
|
|
|
|
reader.read_to_string(&mut contents).unwrap();
|
|
|
|
|
|
|
|
json::decode::<StoredCredentials>(&contents).unwrap().into()
|
|
|
|
}
|
|
|
|
|
2016-03-16 04:07:04 +00:00
|
|
|
pub fn from_file<P: AsRef<Path>>(path: P) -> Option<Credentials> {
|
|
|
|
File::open(path).ok().map(Credentials::from_reader)
|
2016-03-13 22:35:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn save_to_writer<W: Write>(&self, writer: &mut W) {
|
|
|
|
let contents = json::encode::<StoredCredentials>(&self.clone().into()).unwrap();
|
|
|
|
writer.write_all(contents.as_bytes()).unwrap();
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn save_to_file<P: AsRef<Path>>(&self, path: P) {
|
|
|
|
let mut file = File::create(path).unwrap();
|
|
|
|
self.save_to_writer(&mut file)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<Credentials> for StoredCredentials {
|
|
|
|
fn from(credentials: Credentials) -> StoredCredentials {
|
|
|
|
StoredCredentials {
|
|
|
|
username: credentials.username,
|
|
|
|
auth_type: credentials.auth_type.value(),
|
|
|
|
auth_data: credentials.auth_data.to_base64(base64::STANDARD),
|
|
|
|
}
|
|
|
|
}
|
2016-01-01 23:16:12 +00:00
|
|
|
}
|
2016-03-13 22:35:09 +00:00
|
|
|
|
|
|
|
impl From<StoredCredentials> for Credentials {
|
|
|
|
fn from(credentials: StoredCredentials) -> Credentials {
|
|
|
|
Credentials {
|
|
|
|
username: credentials.username,
|
|
|
|
auth_type: AuthenticationType::from_i32(credentials.auth_type).unwrap(),
|
|
|
|
auth_data: credentials.auth_data.from_base64().unwrap(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-17 03:06:56 +00:00
|
|
|
#[cfg(feature = "discovery")]
|
|
|
|
mod discovery;
|
|
|
|
#[cfg(feature = "discovery")]
|
|
|
|
pub use self::discovery::discovery_login;
|
|
|
|
#[cfg(not(feature = "discovery"))]
|
2016-03-20 16:16:11 +00:00
|
|
|
pub fn discovery_login(_device_name: &str, _device_id: &str) -> Result<Credentials, ()> {
|
2016-03-17 03:06:56 +00:00
|
|
|
Err(())
|
|
|
|
}
|
|
|
|
|
2016-03-20 13:55:20 +00:00
|
|
|
#[cfg(feature = "facebook")]
|
|
|
|
mod facebook;
|
|
|
|
#[cfg(feature = "facebook")]
|
|
|
|
pub use self::facebook::facebook_login;
|
2016-03-17 03:06:56 +00:00
|
|
|
#[cfg(not(feature = "facebook"))]
|
|
|
|
pub fn facebook_login() -> Result<Credentials, ()> {
|
|
|
|
Err(())
|
|
|
|
}
|