openlibspot_core/
authentication.rs

1use std::io::{self, Read};
2
3use aes::Aes192;
4use base64::engine::general_purpose::STANDARD as BASE64;
5use base64::engine::Engine as _;
6use byteorder::{BigEndian, ByteOrder};
7use pbkdf2::pbkdf2_hmac;
8use protobuf::Enum;
9use serde::{Deserialize, Serialize};
10use sha1::{Digest, Sha1};
11use thiserror::Error;
12
13use crate::{protocol::authentication::AuthenticationType, Error};
14
15#[derive(Debug, Error)]
16pub enum AuthenticationError {
17    #[error("unknown authentication type {0}")]
18    AuthType(u32),
19    #[error("invalid key")]
20    Key,
21}
22
23impl From<AuthenticationError> for Error {
24    fn from(err: AuthenticationError) -> Self {
25        Error::invalid_argument(err)
26    }
27}
28
29/// The credentials are used to log into the Spotify API.
30#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
31pub struct Credentials {
32    pub username: String,
33
34    #[serde(serialize_with = "serialize_protobuf_enum")]
35    #[serde(deserialize_with = "deserialize_protobuf_enum")]
36    pub auth_type: AuthenticationType,
37
38    #[serde(alias = "encoded_auth_blob")]
39    #[serde(serialize_with = "serialize_base64")]
40    #[serde(deserialize_with = "deserialize_base64")]
41    pub auth_data: Vec<u8>,
42}
43
44impl Credentials {
45    /// Intialize these credentials from a username and a password.
46    ///
47    /// ### Example
48    /// ```rust
49    /// use librespot_core::authentication::Credentials;
50    ///
51    /// let creds = Credentials::with_password("my account", "my password");
52    /// ```
53    pub fn with_password(username: impl Into<String>, password: impl Into<String>) -> Credentials {
54        Credentials {
55            username: username.into(),
56            auth_type: AuthenticationType::AUTHENTICATION_USER_PASS,
57            auth_data: password.into().into_bytes(),
58        }
59    }
60
61    pub fn with_blob(
62        username: impl Into<String>,
63        encrypted_blob: impl AsRef<[u8]>,
64        device_id: impl AsRef<[u8]>,
65    ) -> Result<Credentials, Error> {
66        fn read_u8<R: Read>(stream: &mut R) -> io::Result<u8> {
67            let mut data = [0u8];
68            stream.read_exact(&mut data)?;
69            Ok(data[0])
70        }
71
72        fn read_int<R: Read>(stream: &mut R) -> io::Result<u32> {
73            let lo = read_u8(stream)? as u32;
74            if lo & 0x80 == 0 {
75                return Ok(lo);
76            }
77
78            let hi = read_u8(stream)? as u32;
79            Ok(lo & 0x7f | hi << 7)
80        }
81
82        fn read_bytes<R: Read>(stream: &mut R) -> io::Result<Vec<u8>> {
83            let length = read_int(stream)?;
84            let mut data = vec![0u8; length as usize];
85            stream.read_exact(&mut data)?;
86
87            Ok(data)
88        }
89
90        let username = username.into();
91
92        let secret = Sha1::digest(device_id.as_ref());
93
94        let key = {
95            let mut key = [0u8; 24];
96            if key.len() < 20 {
97                return Err(AuthenticationError::Key.into());
98            }
99
100            pbkdf2_hmac::<Sha1>(&secret, username.as_bytes(), 0x100, &mut key[0..20]);
101
102            let hash = &Sha1::digest(&key[..20]);
103            key[..20].copy_from_slice(hash);
104            BigEndian::write_u32(&mut key[20..], 20);
105            key
106        };
107
108        // decrypt data using ECB mode without padding
109        let blob = {
110            use aes::cipher::generic_array::GenericArray;
111            use aes::cipher::{BlockDecrypt, BlockSizeUser, KeyInit};
112
113            let mut data = BASE64.decode(encrypted_blob)?;
114            let cipher = Aes192::new(GenericArray::from_slice(&key));
115            let block_size = Aes192::block_size();
116
117            for chunk in data.chunks_exact_mut(block_size) {
118                cipher.decrypt_block(GenericArray::from_mut_slice(chunk));
119            }
120
121            let l = data.len();
122            for i in 0..l - 0x10 {
123                data[l - i - 1] ^= data[l - i - 0x11];
124            }
125
126            data
127        };
128
129        let mut cursor = io::Cursor::new(blob.as_slice());
130        read_u8(&mut cursor)?;
131        read_bytes(&mut cursor)?;
132        read_u8(&mut cursor)?;
133        let auth_type = read_int(&mut cursor)?;
134        let auth_type = AuthenticationType::from_i32(auth_type as i32)
135            .ok_or(AuthenticationError::AuthType(auth_type))?;
136        read_u8(&mut cursor)?;
137        let auth_data = read_bytes(&mut cursor)?;
138
139        Ok(Credentials {
140            username,
141            auth_type,
142            auth_data,
143        })
144    }
145}
146
147fn serialize_protobuf_enum<T, S>(v: &T, ser: S) -> Result<S::Ok, S::Error>
148where
149    T: Enum,
150    S: serde::Serializer,
151{
152    serde::Serialize::serialize(&v.value(), ser)
153}
154
155fn deserialize_protobuf_enum<'de, T, D>(de: D) -> Result<T, D::Error>
156where
157    T: Enum,
158    D: serde::Deserializer<'de>,
159{
160    let v: i32 = serde::Deserialize::deserialize(de)?;
161    T::from_i32(v).ok_or_else(|| serde::de::Error::custom("Invalid enum value"))
162}
163
164fn serialize_base64<T, S>(v: &T, ser: S) -> Result<S::Ok, S::Error>
165where
166    T: AsRef<[u8]>,
167    S: serde::Serializer,
168{
169    serde::Serialize::serialize(&BASE64.encode(v.as_ref()), ser)
170}
171
172fn deserialize_base64<'de, D>(de: D) -> Result<Vec<u8>, D::Error>
173where
174    D: serde::Deserializer<'de>,
175{
176    let v: String = serde::Deserialize::deserialize(de)?;
177    BASE64
178        .decode(v)
179        .map_err(|e| serde::de::Error::custom(e.to_string()))
180}