librespot/core/src/authentication.rs

180 lines
5.2 KiB
Rust
Raw Normal View History

use std::io::{self, Read};
use aes::Aes192;
2016-01-01 23:16:12 +00:00
use byteorder::{BigEndian, ByteOrder};
use hmac::Hmac;
use pbkdf2::pbkdf2;
use protobuf::ProtobufEnum;
use serde::{Deserialize, Serialize};
use sha1::{Digest, Sha1};
use thiserror::Error;
2016-01-01 23:16:12 +00:00
use crate::{protocol::authentication::AuthenticationType, Error};
#[derive(Debug, Error)]
pub enum AuthenticationError {
#[error("unknown authentication type {0}")]
AuthType(u32),
2021-12-29 22:15:08 +00:00
#[error("invalid key")]
Key,
}
impl From<AuthenticationError> for Error {
fn from(err: AuthenticationError) -> Self {
Error::invalid_argument(err)
}
}
2016-01-01 23:16:12 +00:00
/// The credentials are used to log into the Spotify API.
2018-02-11 11:37:08 +00:00
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Credentials {
pub username: String,
2016-07-06 01:29:38 +00:00
2018-02-11 11:37:08 +00:00
#[serde(serialize_with = "serialize_protobuf_enum")]
#[serde(deserialize_with = "deserialize_protobuf_enum")]
pub auth_type: AuthenticationType,
2016-01-01 23:16:12 +00:00
#[serde(alias = "encoded_auth_blob")]
2018-02-11 11:37:08 +00:00
#[serde(serialize_with = "serialize_base64")]
#[serde(deserialize_with = "deserialize_base64")]
2016-07-06 01:29:38 +00:00
pub auth_data: Vec<u8>,
}
impl Credentials {
/// Intialize these credentials from a username and a password.
///
/// ### Example
/// ```rust
/// use librespot_core::authentication::Credentials;
2021-04-01 16:10:42 +00:00
///
/// let creds = Credentials::with_password("my account", "my password");
/// ```
2021-04-01 16:10:42 +00:00
pub fn with_password(username: impl Into<String>, password: impl Into<String>) -> Credentials {
Credentials {
username: username.into(),
auth_type: AuthenticationType::AUTHENTICATION_USER_PASS,
auth_data: password.into().into_bytes(),
}
}
pub fn with_blob(
username: impl Into<String>,
encrypted_blob: impl AsRef<[u8]>,
device_id: impl AsRef<[u8]>,
) -> Result<Credentials, Error> {
fn read_u8<R: Read>(stream: &mut R) -> io::Result<u8> {
let mut data = [0u8];
stream.read_exact(&mut data)?;
Ok(data[0])
}
fn read_int<R: Read>(stream: &mut R) -> io::Result<u32> {
let lo = read_u8(stream)? as u32;
if lo & 0x80 == 0 {
return Ok(lo);
2016-01-01 23:16:12 +00:00
}
let hi = read_u8(stream)? as u32;
Ok(lo & 0x7f | hi << 7)
}
2016-01-01 23:16:12 +00:00
fn read_bytes<R: Read>(stream: &mut R) -> io::Result<Vec<u8>> {
let length = read_int(stream)?;
let mut data = vec![0u8; length as usize];
stream.read_exact(&mut data)?;
2016-01-01 23:16:12 +00:00
Ok(data)
2016-01-01 23:16:12 +00:00
}
let username = username.into();
let secret = Sha1::digest(device_id.as_ref());
2016-01-01 23:16:12 +00:00
let key = {
let mut key = [0u8; 24];
2021-12-29 22:15:08 +00:00
if key.len() < 20 {
return Err(AuthenticationError::Key.into());
}
pbkdf2::<Hmac<Sha1>>(&secret, username.as_bytes(), 0x100, &mut key[0..20]);
let hash = &Sha1::digest(&key[..20]);
key[..20].copy_from_slice(hash);
BigEndian::write_u32(&mut key[20..], 20);
key
2016-01-01 23:16:12 +00:00
};
// decrypt data using ECB mode without padding
2016-01-01 23:16:12 +00:00
let blob = {
2021-02-13 18:10:34 +00:00
use aes::cipher::generic_array::typenum::Unsigned;
use aes::cipher::generic_array::GenericArray;
use aes::cipher::{BlockCipher, NewBlockCipher};
let mut data = base64::decode(encrypted_blob)?;
let cipher = Aes192::new(GenericArray::from_slice(&key));
let block_size = <Aes192 as BlockCipher>::BlockSize::to_usize();
assert_eq!(data.len() % block_size, 0);
for chunk in data.chunks_exact_mut(block_size) {
cipher.decrypt_block(GenericArray::from_mut_slice(chunk));
}
let l = data.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.as_slice());
read_u8(&mut cursor)?;
read_bytes(&mut cursor)?;
read_u8(&mut cursor)?;
let auth_type = read_int(&mut cursor)?;
let auth_type = AuthenticationType::from_i32(auth_type as i32)
.ok_or(AuthenticationError::AuthType(auth_type))?;
read_u8(&mut cursor)?;
let auth_data = read_bytes(&mut cursor)?;
Ok(Credentials {
2021-03-01 02:37:22 +00:00
username,
auth_type,
auth_data,
})
2016-01-01 23:16:12 +00:00
}
}
2017-01-29 12:50:18 +00:00
fn serialize_protobuf_enum<T, S>(v: &T, ser: S) -> Result<S::Ok, S::Error>
2018-02-11 11:37:08 +00:00
where
T: ProtobufEnum,
S: serde::Serializer,
{
2016-07-06 01:29:38 +00:00
serde::Serialize::serialize(&v.value(), ser)
2016-01-01 23:16:12 +00:00
}
2019-03-15 14:50:15 +00:00
fn deserialize_protobuf_enum<'de, T, D>(de: D) -> Result<T, D::Error>
2018-02-11 11:37:08 +00:00
where
T: ProtobufEnum,
2019-03-15 14:50:15 +00:00
D: serde::Deserializer<'de>,
2018-02-11 11:37:08 +00:00
{
let v: i32 = serde::Deserialize::deserialize(de)?;
2017-02-09 01:32:18 +00:00
T::from_i32(v).ok_or_else(|| serde::de::Error::custom("Invalid enum value"))
2016-07-06 01:29:38 +00:00
}
2017-01-29 12:50:18 +00:00
fn serialize_base64<T, S>(v: &T, ser: S) -> Result<S::Ok, S::Error>
2018-02-11 11:37:08 +00:00
where
T: AsRef<[u8]>,
S: serde::Serializer,
{
serde::Serialize::serialize(&base64::encode(v.as_ref()), ser)
2016-07-06 01:29:38 +00:00
}
2019-03-15 14:50:15 +00:00
fn deserialize_base64<'de, D>(de: D) -> Result<Vec<u8>, D::Error>
2018-02-11 11:37:08 +00:00
where
2019-03-15 14:50:15 +00:00
D: serde::Deserializer<'de>,
2018-02-11 11:37:08 +00:00
{
let v: String = serde::Deserialize::deserialize(de)?;
base64::decode(&v).map_err(|e| serde::de::Error::custom(e.to_string()))
}