librespot_core/
authentication.rs1use std::io::{self, Read};
2
3use aes::Aes192;
4use base64::engine::Engine as _;
5use base64::engine::general_purpose::STANDARD as BASE64;
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::{Error, protocol::authentication::AuthenticationType};
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#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
31pub struct Credentials {
32 pub username: Option<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 pub fn with_password(username: impl Into<String>, password: impl Into<String>) -> Self {
54 Self {
55 username: Some(username.into()),
56 auth_type: AuthenticationType::AUTHENTICATION_USER_PASS,
57 auth_data: password.into().into_bytes(),
58 }
59 }
60
61 pub fn with_access_token(token: impl Into<String>) -> Self {
62 Self {
63 username: None,
64 auth_type: AuthenticationType::AUTHENTICATION_SPOTIFY_TOKEN,
65 auth_data: token.into().into_bytes(),
66 }
67 }
68
69 #[expect(deprecated)]
70 pub fn with_blob(
71 username: impl Into<String>,
72 encrypted_blob: impl AsRef<[u8]>,
73 device_id: impl AsRef<[u8]>,
74 ) -> Result<Self, Error> {
75 fn read_u8<R: Read>(stream: &mut R) -> io::Result<u8> {
76 let mut data = [0u8];
77 stream.read_exact(&mut data)?;
78 Ok(data[0])
79 }
80
81 fn read_int<R: Read>(stream: &mut R) -> io::Result<u32> {
82 let lo = read_u8(stream)? as u32;
83 if lo & 0x80 == 0 {
84 return Ok(lo);
85 }
86
87 let hi = read_u8(stream)? as u32;
88 Ok(lo & 0x7f | (hi << 7))
89 }
90
91 fn read_bytes<R: Read>(stream: &mut R) -> io::Result<Vec<u8>> {
92 let length = read_int(stream)?;
93 let mut data = vec![0u8; length as usize];
94 stream.read_exact(&mut data)?;
95
96 Ok(data)
97 }
98
99 let username = username.into();
100
101 let secret = Sha1::digest(device_id.as_ref());
102
103 let key = {
104 let mut key = [0u8; 24];
105 if key.len() < 20 {
106 return Err(AuthenticationError::Key.into());
107 }
108
109 pbkdf2_hmac::<Sha1>(&secret, username.as_bytes(), 0x100, &mut key[0..20]);
110
111 let hash = &Sha1::digest(&key[..20]);
112 key[..20].copy_from_slice(hash);
113 BigEndian::write_u32(&mut key[20..], 20);
114 key
115 };
116
117 let blob = {
119 use aes::cipher::generic_array::GenericArray;
120 use aes::cipher::{BlockDecrypt, BlockSizeUser, KeyInit};
121
122 let mut data = BASE64.decode(encrypted_blob)?;
123 let cipher = Aes192::new(GenericArray::from_slice(&key));
124 let block_size = Aes192::block_size();
125
126 for chunk in data.chunks_exact_mut(block_size) {
127 cipher.decrypt_block(GenericArray::from_mut_slice(chunk));
128 }
129
130 let l = data.len();
131 for i in 0..l - 0x10 {
132 data[l - i - 1] ^= data[l - i - 0x11];
133 }
134
135 data
136 };
137
138 let mut cursor = io::Cursor::new(blob.as_slice());
139 read_u8(&mut cursor)?;
140 read_bytes(&mut cursor)?;
141 read_u8(&mut cursor)?;
142 let auth_type = read_int(&mut cursor)?;
143 let auth_type = AuthenticationType::from_i32(auth_type as i32)
144 .ok_or(AuthenticationError::AuthType(auth_type))?;
145 read_u8(&mut cursor)?;
146 let auth_data = read_bytes(&mut cursor)?;
147
148 Ok(Self {
149 username: Some(username),
150 auth_type,
151 auth_data,
152 })
153 }
154}
155
156fn serialize_protobuf_enum<T, S>(v: &T, ser: S) -> Result<S::Ok, S::Error>
157where
158 T: Enum,
159 S: serde::Serializer,
160{
161 serde::Serialize::serialize(&v.value(), ser)
162}
163
164fn deserialize_protobuf_enum<'de, T, D>(de: D) -> Result<T, D::Error>
165where
166 T: Enum,
167 D: serde::Deserializer<'de>,
168{
169 let v: i32 = serde::Deserialize::deserialize(de)?;
170 T::from_i32(v).ok_or_else(|| serde::de::Error::custom("Invalid enum value"))
171}
172
173fn serialize_base64<T, S>(v: &T, ser: S) -> Result<S::Ok, S::Error>
174where
175 T: AsRef<[u8]>,
176 S: serde::Serializer,
177{
178 serde::Serialize::serialize(&BASE64.encode(v.as_ref()), ser)
179}
180
181fn deserialize_base64<'de, D>(de: D) -> Result<Vec<u8>, D::Error>
182where
183 D: serde::Deserializer<'de>,
184{
185 let v: String = serde::Deserialize::deserialize(de)?;
186 BASE64
187 .decode(v)
188 .map_err(|e| serde::de::Error::custom(e.to_string()))
189}