Almost eliminate util module

This commit is contained in:
johannesd3 2021-03-18 17:51:50 +01:00
parent 9378ae5b6f
commit e688e7e886
9 changed files with 59 additions and 67 deletions

3
Cargo.lock generated
View file

@ -1335,6 +1335,7 @@ dependencies = [
"base64", "base64",
"block-modes", "block-modes",
"dns-sd", "dns-sd",
"form_urlencoded",
"futures-core", "futures-core",
"futures-util", "futures-util",
"hmac", "hmac",
@ -1363,6 +1364,7 @@ dependencies = [
"byteorder", "byteorder",
"bytes", "bytes",
"env_logger", "env_logger",
"form_urlencoded",
"futures-core", "futures-core",
"futures-util", "futures-util",
"hmac", "hmac",
@ -1640,6 +1642,7 @@ dependencies = [
"autocfg", "autocfg",
"num-integer", "num-integer",
"num-traits", "num-traits",
"rand",
] ]
[[package]] [[package]]

View file

@ -11,6 +11,7 @@ edition = "2018"
aes-ctr = "0.6" aes-ctr = "0.6"
base64 = "0.13" base64 = "0.13"
block-modes = "0.7" block-modes = "0.7"
form_urlencoded = "1.0"
futures-core = "0.3" futures-core = "0.3"
futures-util = { version = "0.3", default_features = false } futures-util = { version = "0.3", default_features = false }
hmac = "0.10" hmac = "0.10"

View file

@ -7,7 +7,6 @@ use crate::core::config::{ConnectConfig, VolumeCtrl};
use crate::core::mercury::{MercuryError, MercurySender}; use crate::core::mercury::{MercuryError, MercurySender};
use crate::core::session::Session; use crate::core::session::Session;
use crate::core::spotify_id::{SpotifyAudioType, SpotifyId, SpotifyIdError}; use crate::core::spotify_id::{SpotifyAudioType, SpotifyId, SpotifyIdError};
use crate::core::util::url_encode;
use crate::core::util::SeqGenerator; use crate::core::util::SeqGenerator;
use crate::core::version; use crate::core::version;
use crate::playback::mixer::Mixer; use crate::playback::mixer::Mixer;
@ -244,6 +243,10 @@ fn volume_to_mixer(volume: u16, volume_ctrl: &VolumeCtrl) -> u16 {
} }
} }
fn url_encode(bytes: impl AsRef<[u8]>) -> String {
form_urlencoded::byte_serialize(bytes.as_ref()).collect()
}
impl Spirc { impl Spirc {
pub fn new( pub fn new(
config: ConnectConfig, config: ConnectConfig,
@ -256,7 +259,7 @@ impl Spirc {
let ident = session.device_id().to_owned(); let ident = session.device_id().to_owned();
// Uri updated in response to issue #288 // Uri updated in response to issue #288
debug!("canonical_username: {}", url_encode(&session.username())); debug!("canonical_username: {}", &session.username());
let uri = format!("hm://remote/user/{}/", url_encode(&session.username())); let uri = format!("hm://remote/user/{}/", url_encode(&session.username()));
let subscription = Box::pin( let subscription = Box::pin(

View file

@ -17,6 +17,7 @@ aes = "0.6"
base64 = "0.13" base64 = "0.13"
byteorder = "1.4" byteorder = "1.4"
bytes = "1.0" bytes = "1.0"
form_urlencoded = "1.0"
futures-core = { version = "0.3", default-features = false } futures-core = { version = "0.3", default-features = false }
futures-util = { version = "0.3", default-features = false, features = ["alloc", "bilock", "unstable", "sink"] } futures-util = { version = "0.3", default-features = false, features = ["alloc", "bilock", "unstable", "sink"] }
hmac = "0.10" hmac = "0.10"
@ -25,7 +26,7 @@ http = "0.2"
hyper = { version = "0.14", optional = true, features = ["client", "tcp", "http1"] } hyper = { version = "0.14", optional = true, features = ["client", "tcp", "http1"] }
hyper-proxy = { version = "0.9.1", optional = true, default-features = false } hyper-proxy = { version = "0.9.1", optional = true, default-features = false }
log = "0.4" log = "0.4"
num-bigint = "0.4" num-bigint = { version = "0.4", features = ["rand"] }
num-integer = "0.1" num-integer = "0.1"
num-traits = "0.2" num-traits = "0.2"
once_cell = "1.5.2" once_cell = "1.5.2"

View file

@ -1,7 +1,7 @@
use byteorder::{BigEndian, ByteOrder, WriteBytesExt}; use byteorder::{BigEndian, ByteOrder, WriteBytesExt};
use hmac::{Hmac, Mac, NewMac}; use hmac::{Hmac, Mac, NewMac};
use protobuf::{self, Message}; use protobuf::{self, Message};
use rand::thread_rng; use rand::{thread_rng, RngCore};
use sha1::Sha1; use sha1::Sha1;
use std::io; use std::io;
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
@ -11,7 +11,6 @@ use super::codec::ApCodec;
use crate::diffie_hellman::DhLocalKeys; use crate::diffie_hellman::DhLocalKeys;
use crate::protocol; use crate::protocol;
use crate::protocol::keyexchange::{APResponseMessage, ClientHello, ClientResponsePlaintext}; use crate::protocol::keyexchange::{APResponseMessage, ClientHello, ClientResponsePlaintext};
use crate::util;
pub async fn handshake<T: AsyncRead + AsyncWrite + Unpin>( pub async fn handshake<T: AsyncRead + AsyncWrite + Unpin>(
mut connection: T, mut connection: T,
@ -40,6 +39,9 @@ async fn client_hello<T>(connection: &mut T, gc: Vec<u8>) -> io::Result<Vec<u8>>
where where
T: AsyncWrite + Unpin, T: AsyncWrite + Unpin,
{ {
let mut client_nonce = vec![0; 0x10];
thread_rng().fill_bytes(&mut client_nonce);
let mut packet = ClientHello::new(); let mut packet = ClientHello::new();
packet packet
.mut_build_info() .mut_build_info()
@ -59,7 +61,7 @@ where
.mut_login_crypto_hello() .mut_login_crypto_hello()
.mut_diffie_hellman() .mut_diffie_hellman()
.set_server_keys_known(1); .set_server_keys_known(1);
packet.set_client_nonce(util::rand_vec(&mut thread_rng(), 0x10)); packet.set_client_nonce(client_nonce);
packet.set_padding(vec![0x1e]); packet.set_padding(vec![0x1e]);
let mut buffer = vec![0, 4]; let mut buffer = vec![0, 4];

View file

@ -1,11 +1,11 @@
use num_bigint::BigUint; use num_bigint::{BigUint, RandBigInt};
use num_integer::Integer;
use num_traits::{One, Zero};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use rand::Rng; use rand::{CryptoRng, Rng};
use crate::util; static DH_GENERATOR: Lazy<BigUint> = Lazy::new(|| BigUint::from_bytes_be(&[0x02]));
static DH_PRIME: Lazy<BigUint> = Lazy::new(|| {
pub static DH_GENERATOR: Lazy<BigUint> = Lazy::new(|| BigUint::from_bytes_be(&[0x02]));
pub static DH_PRIME: Lazy<BigUint> = Lazy::new(|| {
BigUint::from_bytes_be(&[ BigUint::from_bytes_be(&[
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc9, 0x0f, 0xda, 0xa2, 0x21, 0x68, 0xc2, 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, 0x34, 0xc4, 0xc6, 0x62, 0x8b, 0x80, 0xdc, 0x1c, 0xd1, 0x29, 0x02, 0x4e, 0x08, 0x8a, 0x67,
@ -17,17 +17,31 @@ pub static DH_PRIME: Lazy<BigUint> = Lazy::new(|| {
]) ])
}); });
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();
while !exp.is_zero() {
if exp.is_odd() {
result = (result * &base) % modulus;
}
exp >>= 1;
base = (&base * &base) % modulus;
}
result
}
pub struct DhLocalKeys { pub struct DhLocalKeys {
private_key: BigUint, private_key: BigUint,
public_key: BigUint, public_key: BigUint,
} }
impl DhLocalKeys { impl DhLocalKeys {
pub fn random<R: Rng>(rng: &mut R) -> DhLocalKeys { pub fn random<R: Rng + CryptoRng>(rng: &mut R) -> DhLocalKeys {
let key_data = util::rand_vec(rng, 95); let private_key = rng.gen_biguint(95 * 8);
let public_key = powm(&DH_GENERATOR, &private_key, &DH_PRIME);
let private_key = BigUint::from_bytes_be(&key_data);
let public_key = util::powm(&DH_GENERATOR, &private_key, &DH_PRIME);
DhLocalKeys { DhLocalKeys {
private_key, private_key,
@ -40,7 +54,7 @@ impl DhLocalKeys {
} }
pub fn shared_secret(&self, remote_key: &[u8]) -> Vec<u8> { pub fn shared_secret(&self, remote_key: &[u8]) -> Vec<u8> {
let shared_key = util::powm( let shared_key = powm(
&BigUint::from_bytes_be(remote_key), &BigUint::from_bytes_be(remote_key),
&self.private_key, &self.private_key,
&DH_PRIME, &DH_PRIME,

View file

@ -14,12 +14,14 @@ pub mod cache;
pub mod channel; pub mod channel;
pub mod config; pub mod config;
mod connection; mod connection;
#[doc(hidden)]
pub mod diffie_hellman; pub mod diffie_hellman;
pub mod keymaster; pub mod keymaster;
pub mod mercury; pub mod mercury;
mod proxytunnel; mod proxytunnel;
pub mod session; pub mod session;
pub mod spotify_id; pub mod spotify_id;
#[doc(hidden)]
pub mod util; pub mod util;
pub mod version; pub mod version;

View file

@ -10,7 +10,6 @@ use bytes::Bytes;
use tokio::sync::{mpsc, oneshot}; use tokio::sync::{mpsc, oneshot};
use crate::protocol; use crate::protocol;
use crate::util::url_encode;
use crate::util::SeqGenerator; use crate::util::SeqGenerator;
mod types; mod types;
@ -199,7 +198,7 @@ impl MercuryManager {
let header: protocol::mercury::Header = protobuf::parse_from_bytes(&header_data).unwrap(); let header: protocol::mercury::Header = protobuf::parse_from_bytes(&header_data).unwrap();
let response = MercuryResponse { let response = MercuryResponse {
uri: url_encode(header.get_uri()), uri: header.get_uri().to_string(),
status_code: header.get_status_code(), status_code: header.get_status_code(),
payload: pending.parts, payload: pending.parts,
}; };
@ -214,8 +213,21 @@ impl MercuryManager {
} else if cmd == 0xb5 { } else if cmd == 0xb5 {
self.lock(|inner| { self.lock(|inner| {
let mut found = false; let mut found = false;
// TODO: This is just a workaround to make utf-8 encoded usernames work.
// A better solution would be to use an uri struct and urlencode it directly
// before sending while saving the subscription under its unencoded form.
let mut uri_split = response.uri.split('/');
let encoded_uri = std::iter::once(uri_split.next().unwrap().to_string())
.chain(uri_split.map(|component| {
form_urlencoded::byte_serialize(component.as_bytes()).collect::<String>()
}))
.collect::<Vec<String>>()
.join("/");
inner.subscriptions.retain(|&(ref prefix, ref sub)| { inner.subscriptions.retain(|&(ref prefix, ref sub)| {
if response.uri.starts_with(prefix) { if encoded_uri.starts_with(prefix) {
found = true; found = true;
// if send fails, remove from list of subs // if send fails, remove from list of subs

View file

@ -1,50 +1,4 @@
use num_bigint::BigUint;
use num_integer::Integer;
use num_traits::{One, Zero};
use rand::Rng;
use std::mem; use std::mem;
use std::ops::{Mul, Rem, Shr};
pub fn rand_vec<G: Rng>(rng: &mut G, size: usize) -> Vec<u8> {
::std::iter::repeat(())
.map(|()| rng.gen())
.take(size)
.collect()
}
pub fn url_encode(inp: &str) -> String {
let mut encoded = String::new();
for c in inp.as_bytes().iter() {
match *c as char {
'A'..='Z' | 'a'..='z' | '0'..='9' | '-' | '_' | '.' | '~' | ':' | '/' => {
encoded.push(*c as char)
}
c => encoded.push_str(format!("%{:02X}", c as u32).as_str()),
};
}
encoded
}
pub 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();
while !exp.is_zero() {
if exp.is_odd() {
result = result.mul(&base).rem(modulus);
}
exp = exp.shr(1);
base = (&base).mul(&base).rem(modulus);
}
result
}
pub trait ReadSeek: ::std::io::Read + ::std::io::Seek {}
impl<T: ::std::io::Read + ::std::io::Seek> ReadSeek for T {}
pub trait Seq { pub trait Seq {
fn next(&self) -> Self; fn next(&self) -> Self;