Compare commits
1 commit
Author | SHA1 | Date | |
---|---|---|---|
4ab0f7d940 |
7 changed files with 77 additions and 92 deletions
|
@ -1,7 +1,7 @@
|
|||
[package]
|
||||
name = "spotifyio-downloader"
|
||||
description = "CLI for downloading music from Spotify"
|
||||
version = "0.5.1"
|
||||
version = "0.5.0"
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
|
|
|
@ -1114,57 +1114,20 @@ impl SpotifyDownloader {
|
|||
if let Some(item) = item {
|
||||
return Ok(Some(item.subdir_path));
|
||||
} else {
|
||||
let row = sqlx::query!(
|
||||
"select album, album_id, path from tracks where id=$1",
|
||||
id_str
|
||||
)
|
||||
let row = sqlx::query!("select path from tracks where id=$1", id_str)
|
||||
.fetch_optional(&self.i.pool)
|
||||
.await?;
|
||||
if let Some(row) = row {
|
||||
if let Some(subdir_path) = row.path {
|
||||
if let Some(subdir_path) = row.and_then(|r| r.path) {
|
||||
let audio_path = path!(self.i.base_dir / subdir_path);
|
||||
if audio_path.is_file() {
|
||||
return Ok(Some(path!(subdir_path)));
|
||||
} else {
|
||||
if let (Some(album), Some(album_id)) =
|
||||
(row.album, row.album_id)
|
||||
{
|
||||
// Try new path (with album ID)
|
||||
let pb = PathBuf::from(subdir_path);
|
||||
let mut cmp = pb.components().collect::<Vec<_>>();
|
||||
if cmp.len() > 1 {
|
||||
let album_dirname = better_filenamify(
|
||||
&album,
|
||||
Some(&format!(" [{}]", &album_id[..8])),
|
||||
);
|
||||
cmp[1] = std::path::Component::Normal(
|
||||
std::ffi::OsStr::new(&album_dirname),
|
||||
);
|
||||
|
||||
let subdir_path =
|
||||
cmp.into_iter().collect::<PathBuf>();
|
||||
let audio_path =
|
||||
path!(self.i.base_dir / subdir_path);
|
||||
if audio_path.is_file() {
|
||||
tracing::info!(
|
||||
"Audio file moved, new path: {}",
|
||||
subdir_path.to_string_lossy()
|
||||
);
|
||||
return Ok(Some(subdir_path));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tracing::error!(
|
||||
"[{id}] audio file not found: {}",
|
||||
audio_path.to_string_lossy()
|
||||
);
|
||||
success.store(false, Ordering::SeqCst);
|
||||
}
|
||||
} else {
|
||||
tracing::error!("[{id}] audio file path not found in db");
|
||||
success.store(false, Ordering::SeqCst);
|
||||
}
|
||||
} else {
|
||||
tracing::error!("[{id}] audio file not found in db");
|
||||
success.store(false, Ordering::SeqCst);
|
||||
|
|
|
@ -52,17 +52,17 @@ byteorder = "1.0"
|
|||
futures-util = { version = "0.3", features = ["sink"] }
|
||||
num-derive = "0.4"
|
||||
num-traits = "0.2"
|
||||
num-bigint = { version = "0.4", features = ["rand"] }
|
||||
crypto-bigint = { version = "0.7.0-pre.3", features = ["rand"] }
|
||||
num-integer = "0.1"
|
||||
url = "2"
|
||||
shannon = "0.2"
|
||||
tokio-util = { version = "0.7", features = ["codec"] }
|
||||
hmac = "0.12"
|
||||
rand = "0.8"
|
||||
rand = "0.9"
|
||||
rsa = "0.9.2"
|
||||
httparse = "1.7"
|
||||
base64 = "0.22"
|
||||
oauth2 = { version = "5.0.0", default-features = false, features = [
|
||||
oauth2 = { version = "5.0.0-rc.1", default-features = false, features = [
|
||||
"reqwest",
|
||||
], optional = true }
|
||||
pin-project-lite = "0.2"
|
||||
|
|
|
@ -1,47 +1,44 @@
|
|||
use num_bigint::{BigUint, RandBigInt};
|
||||
use num_integer::Integer;
|
||||
use num_traits::{One, Zero};
|
||||
use once_cell::sync::Lazy;
|
||||
use crypto_bigint::{Integer, NonZero, Random, Zero, U768};
|
||||
use rand::{CryptoRng, Rng};
|
||||
|
||||
static DH_GENERATOR: Lazy<BigUint> = Lazy::new(|| BigUint::from_bytes_be(&[0x02]));
|
||||
static DH_PRIME: Lazy<BigUint> = Lazy::new(|| {
|
||||
BigUint::from_bytes_be(&[
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc9, 0x0f, 0xda, 0xa2, 0x21, 0x68, 0xc2,
|
||||
0x34, 0xc4, 0xc6, 0x62, 0x8b, 0x80, 0xdc, 0x1c, 0xd1, 0x29, 0x02, 0x4e, 0x08, 0x8a, 0x67,
|
||||
0xcc, 0x74, 0x02, 0x0b, 0xbe, 0xa6, 0x3b, 0x13, 0x9b, 0x22, 0x51, 0x4a, 0x08, 0x79, 0x8e,
|
||||
0x34, 0x04, 0xdd, 0xef, 0x95, 0x19, 0xb3, 0xcd, 0x3a, 0x43, 0x1b, 0x30, 0x2b, 0x0a, 0x6d,
|
||||
0xf2, 0x5f, 0x14, 0x37, 0x4f, 0xe1, 0x35, 0x6d, 0x6d, 0x51, 0xc2, 0x45, 0xe4, 0x85, 0xb5,
|
||||
0x76, 0x62, 0x5e, 0x7e, 0xc6, 0xf4, 0x4c, 0x42, 0xe9, 0xa6, 0x3a, 0x36, 0x20, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
])
|
||||
});
|
||||
const DH_GENERATOR: U768 = U768::from_u8(0x02);
|
||||
const DH_PRIME: U768 = U768::from_be_slice(&[
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc9, 0x0f, 0xda, 0xa2, 0x21, 0x68, 0xc2, 0x34,
|
||||
0xc4, 0xc6, 0x62, 0x8b, 0x80, 0xdc, 0x1c, 0xd1, 0x29, 0x02, 0x4e, 0x08, 0x8a, 0x67, 0xcc, 0x74,
|
||||
0x02, 0x0b, 0xbe, 0xa6, 0x3b, 0x13, 0x9b, 0x22, 0x51, 0x4a, 0x08, 0x79, 0x8e, 0x34, 0x04, 0xdd,
|
||||
0xef, 0x95, 0x19, 0xb3, 0xcd, 0x3a, 0x43, 0x1b, 0x30, 0x2b, 0x0a, 0x6d, 0xf2, 0x5f, 0x14, 0x37,
|
||||
0x4f, 0xe1, 0x35, 0x6d, 0x6d, 0x51, 0xc2, 0x45, 0xe4, 0x85, 0xb5, 0x76, 0x62, 0x5e, 0x7e, 0xc6,
|
||||
0xf4, 0x4c, 0x42, 0xe9, 0xa6, 0x3a, 0x36, 0x20, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
]);
|
||||
|
||||
fn powm(base: &BigUint, exp: &BigUint, modulus: &BigUint) -> BigUint {
|
||||
let mut base = base.clone();
|
||||
let mut exp = exp.clone();
|
||||
let mut result: BigUint = One::one();
|
||||
fn powm(mut base: U768, mut exp: U768, modulus: U768) -> U768 {
|
||||
let modulus = NonZero::new(modulus).unwrap();
|
||||
let mut result: U768 = U768::one();
|
||||
|
||||
while !exp.is_zero() {
|
||||
if exp.is_odd() {
|
||||
result = (result * &base) % modulus;
|
||||
while !bool::from(exp.is_zero()) {
|
||||
if bool::from(exp.is_odd()) {
|
||||
result = result.mul_mod(&base, &modulus);
|
||||
}
|
||||
exp >>= 1;
|
||||
base = (&base * &base) % modulus;
|
||||
base = base.mul_mod(&base, &modulus);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub struct DhLocalKeys {
|
||||
private_key: BigUint,
|
||||
public_key: BigUint,
|
||||
private_key: U768,
|
||||
public_key: U768,
|
||||
}
|
||||
|
||||
impl DhLocalKeys {
|
||||
pub fn random<R: Rng + CryptoRng>(rng: &mut R) -> DhLocalKeys {
|
||||
let private_key = rng.gen_biguint(95 * 8);
|
||||
let public_key = powm(&DH_GENERATOR, &private_key, &DH_PRIME);
|
||||
let mut private_key = U768::random(rng);
|
||||
let b0: U768 = U768::from_u8(0xff) << 95;
|
||||
let mask = b0.not();
|
||||
private_key &= mask;
|
||||
|
||||
let public_key = powm(DH_GENERATOR, private_key, DH_PRIME);
|
||||
|
||||
DhLocalKeys {
|
||||
private_key,
|
||||
|
@ -50,15 +47,32 @@ impl DhLocalKeys {
|
|||
}
|
||||
|
||||
pub fn public_key(&self) -> Vec<u8> {
|
||||
self.public_key.to_bytes_be()
|
||||
self.public_key.to_be_bytes().to_vec()
|
||||
}
|
||||
|
||||
pub fn shared_secret(&self, remote_key: &[u8]) -> Vec<u8> {
|
||||
let shared_key = powm(
|
||||
&BigUint::from_bytes_be(remote_key),
|
||||
&self.private_key,
|
||||
&DH_PRIME,
|
||||
);
|
||||
shared_key.to_bytes_be()
|
||||
let remote_key = U768::from_be_slice(remote_key);
|
||||
let shared_key = powm(remote_key, self.private_key, DH_PRIME);
|
||||
shared_key.to_be_bytes().to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crypto_bigint::Encoding;
|
||||
use hex_lit::hex;
|
||||
|
||||
#[test]
|
||||
fn shared_secret() {
|
||||
let private_key = U768::from_be_bytes(hex!("7D390DB1F04214A6EE49176618BFC3293C8441C0129601C5BA1D6D47CEF3F7E39D5F4686238FFD96D6B31CE1E37A759939251CFDCC231FC604E4BA656999FED8FC2AC43284DAC6896BB2107208A0E3BFA8F1600028FAFF92E030B7298E9CE0C2"));
|
||||
let public_key = powm(DH_GENERATOR, private_key, DH_PRIME);
|
||||
let keys = DhLocalKeys {
|
||||
private_key,
|
||||
public_key,
|
||||
};
|
||||
|
||||
let ssc = keys.shared_secret(&hex!("6D6344F664177F93EC376C538A54FD8D156265A5E697F66579C9C268B660C6E47C36F3BD154F2DC09167E7CAB9B1BC7202770C1677AD1DA83926BC11DFEC49DAC66C38656A5223D3F3B936C0D6915743FEB799BD25047C1DA0FE9C93732296A1"));
|
||||
assert_eq!(ssc, hex!("a320b52ab9b49fd99e539868d7c491d4567fd08107fe857f5a05a8c6d1c3fe1f60f61843b843b65f215cdfc46d3eaddeffea6f43cbb4e08b405a358de050483cc8ef231512ca2b58cdfa9c9d33b7791c2a55bacd476f1ebe82373f8c1332d051"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::{env::consts::ARCH, io};
|
|||
use byteorder::{BigEndian, ByteOrder, WriteBytesExt};
|
||||
use hmac::{Hmac, Mac};
|
||||
use protobuf::Message;
|
||||
use rand::{thread_rng, RngCore};
|
||||
use rand::RngCore;
|
||||
use rsa::{BigUint, Pkcs1v15Sign, RsaPublicKey};
|
||||
use sha1::{Digest, Sha1};
|
||||
use thiserror::Error;
|
||||
|
@ -46,10 +46,11 @@ pub enum HandshakeError {
|
|||
pub async fn handshake<T: AsyncRead + AsyncWrite + Unpin>(
|
||||
mut connection: T,
|
||||
) -> io::Result<Framed<T, ApCodec>> {
|
||||
let local_keys = DhLocalKeys::random(&mut thread_rng());
|
||||
let local_keys = DhLocalKeys::random(&mut rand::rng());
|
||||
let gc = local_keys.public_key();
|
||||
let mut accumulator = client_hello(&mut connection, gc).await?;
|
||||
let message: APResponseMessage = recv_packet(&mut connection, &mut accumulator).await?;
|
||||
// remote key: 96B
|
||||
let remote_key = message
|
||||
.challenge
|
||||
.get_or_default()
|
||||
|
@ -59,6 +60,7 @@ pub async fn handshake<T: AsyncRead + AsyncWrite + Unpin>(
|
|||
.get_or_default()
|
||||
.gs()
|
||||
.to_owned();
|
||||
// remote signature: 256B
|
||||
let remote_signature = message
|
||||
.challenge
|
||||
.get_or_default()
|
||||
|
@ -91,6 +93,12 @@ pub async fn handshake<T: AsyncRead + AsyncWrite + Unpin>(
|
|||
})?;
|
||||
|
||||
// OK to proceed
|
||||
if remote_key.len() != 96 {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
HandshakeError::InvalidLength,
|
||||
));
|
||||
}
|
||||
let shared_secret = local_keys.shared_secret(&remote_key);
|
||||
let (challenge, send_key, recv_key) = compute_keys(&shared_secret, &accumulator)?;
|
||||
let codec = ApCodec::new(&send_key, &recv_key);
|
||||
|
@ -105,7 +113,7 @@ where
|
|||
T: AsyncWrite + Unpin,
|
||||
{
|
||||
let mut client_nonce = vec![0; 0x10];
|
||||
thread_rng().fill_bytes(&mut client_nonce);
|
||||
rand::rng().fill_bytes(&mut client_nonce);
|
||||
|
||||
let platform = match std::env::consts::OS {
|
||||
"android" => Platform::PLATFORM_ANDROID_ARM,
|
||||
|
|
|
@ -134,7 +134,7 @@ impl<T> ClientPool<T> {
|
|||
///
|
||||
/// If no valid clients were found, [`PoolError::Empty`] is returned
|
||||
pub fn random(&self) -> Result<&T, PoolError> {
|
||||
let mut pos = rand::thread_rng().gen_range(0..self.len());
|
||||
let mut pos = rand::rng().random_range(0..self.len());
|
||||
|
||||
for _ in 0..((self.len() - 1).max(1)) {
|
||||
let entry = &self.entries[pos].entry;
|
||||
|
|
|
@ -235,7 +235,7 @@ impl Session {
|
|||
self.inc_auth_errors();
|
||||
return Err(e);
|
||||
} else {
|
||||
warn!("Try another access point...");
|
||||
warn!("Try another access point ({e})...");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue