More connection testing
This commit is contained in:
parent
3122f03e9e
commit
1fdd2a3f1e
7 changed files with 141 additions and 57 deletions
|
@ -15,9 +15,10 @@ serde_json = "1.0.107"
|
|||
reqwest = "0.11.20"
|
||||
tokio = { version = "1.32.0", features=["macros", "rt-multi-thread"] }
|
||||
futures = "0.3.28"
|
||||
serial_test = "2.0.0"
|
||||
|
||||
#crypto
|
||||
cbc = "0.1.2"
|
||||
cbc = { version = "0.1.2", features = ["alloc"] }
|
||||
md5 = "0.7.0"
|
||||
base64 = "0.21.4"
|
||||
hmac = "0.12.1"
|
||||
|
|
15
device.py
15
device.py
|
@ -151,9 +151,24 @@ class MiedaDevice(threading.Thread):
|
|||
request = self._security.encode_8370(
|
||||
self._token, MSGTYPE_HANDSHAKE_REQUEST)
|
||||
_LOGGER.debug(f"[{self._device_id}] Handshaking")
|
||||
print("REQUEST")
|
||||
print(request)
|
||||
|
||||
req = bytearray([131, 112, 0, 64, 32, 0, 0, 0, 112, 43, 157, 252, 58, 198, 200, 41, 121, 152, 110, 227, 160, 83, 167, 111, 117, 249, 233, 199, 99, 206, 92, 37, 175, 92, 44, 201, 130, 247, 151, 169, 64, 154, 223, 243, 116, 94, 35, 254, 227, 164, 100, 215, 69, 224, 5, 200, 57, 239, 176, 184, 64, 130, 172, 201, 98, 229, 154, 184, 104, 62, 2, 153])
|
||||
|
||||
if (req == request):
|
||||
print("both requests are the same")
|
||||
|
||||
self._socket.send(req)
|
||||
response = self._socket.recv(512)
|
||||
|
||||
print(response)
|
||||
|
||||
self._socket.send(request)
|
||||
response = self._socket.recv(512)
|
||||
|
||||
print(response)
|
||||
|
||||
if len(response) < 20:
|
||||
raise AuthException()
|
||||
response = response[8: 72]
|
||||
|
|
10
midea.py
10
midea.py
|
@ -17,8 +17,8 @@ async def test():
|
|||
|
||||
if len(devices) > 0:
|
||||
for device_id in devices:
|
||||
token = "06df24fc4e8e950c6d9783051b8e38d971e5fbc617da259459d30d5e7d7fc05b4ccb708fe3a085f6f0af0f8cc961fa39dabfd0746f7bbcfbf7404d9cc5c2b077"
|
||||
key = "2a5b5200c2c04d4c811d0550e1dc5b31435436b95b774d2a88d7e46d61fd9669"
|
||||
token = "702b9dfc3ac6c82979986ee3a053a76f75f9e9c763ce5c25af5c2cc982f797a9409adff3745e23fee3a464d745e005c839efb0b84082acc962e59ab8683e0299"
|
||||
key = "52b2feee353841588994e630dcb59819ec71ce1ffacb48628f4f436f5c54f11e"
|
||||
|
||||
device_info = devices[device_id]
|
||||
|
||||
|
@ -35,7 +35,11 @@ async def test():
|
|||
attributes={}
|
||||
)
|
||||
|
||||
dev.connect(False)
|
||||
if dev.connect(False):
|
||||
print("success")
|
||||
else:
|
||||
print("fail")
|
||||
|
||||
|
||||
|
||||
# if await cl.login():
|
||||
|
|
21
src/cloud.rs
21
src/cloud.rs
|
@ -14,6 +14,7 @@ use serde::{Deserialize, Serialize};
|
|||
use serde_json::{from_str, to_string};
|
||||
|
||||
use crate::cloud_security::CloudSecurity;
|
||||
use crate::hex;
|
||||
|
||||
pub struct Cloud {
|
||||
device_id: String,
|
||||
|
@ -238,7 +239,7 @@ impl Cloud {
|
|||
}
|
||||
|
||||
pub async fn keys(&self, device_id: u64) -> Result<(String, String)> {
|
||||
for method in [1, 2] {
|
||||
for method in [2] {
|
||||
let udp_id = CloudSecurity::udp_id(device_id, method);
|
||||
|
||||
let mut data = self.make_general_data();
|
||||
|
@ -252,13 +253,26 @@ impl Cloud {
|
|||
|
||||
for token in response.token_list() {
|
||||
if token.udpId == udp_id {
|
||||
return Ok((token.token.clone(), token.key.clone()));
|
||||
return Ok((
|
||||
Self::hex_to_lower(&token.token)?,
|
||||
Self::hex_to_lower(&token.key)?,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bail!("no keys found")
|
||||
}
|
||||
|
||||
fn hex_to_lower(h: &str) -> Result<String> {
|
||||
let lower = hex(h)?
|
||||
.iter()
|
||||
.map(|b| format!("{b:02x}"))
|
||||
.collect::<Vec<String>>()
|
||||
.join("");
|
||||
|
||||
Ok(lower)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -453,6 +467,7 @@ struct Data<'a> {
|
|||
mod test {
|
||||
use anyhow::Result;
|
||||
use futures::future::try_join;
|
||||
use serial_test::serial;
|
||||
|
||||
use crate::Startup;
|
||||
|
||||
|
@ -480,6 +495,7 @@ mod test {
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn login() -> Result<()> {
|
||||
let mut cloud = Cloud::new("michaelh.95@t-online.de", "Hoda.semi1")?;
|
||||
|
||||
|
@ -489,6 +505,7 @@ mod test {
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn keys() -> Result<()> {
|
||||
let mut cloud = Cloud::new("michaelh.95@t-online.de", "Hoda.semi1")?;
|
||||
|
||||
|
|
|
@ -64,13 +64,21 @@ impl Device {
|
|||
.security
|
||||
.encode_8370(&self.token, MsgType::HANDSHAKE_REQUEST)?;
|
||||
|
||||
const PY_REQUEST :&[u8;72] = b"\x83p\x00@ \x00\x00\x00p+\x9d\xfc:\xc6\xc8)y\x98n\xe3\xa0S\xa7ou\xf9\xe9\xc7c\xce\\%\xaf\\,\xc9\x82\xf7\x97\xa9@\x9a\xdf\xf3t^#\xfe\xe3\xa4d\xd7E\xe0\x05\xc89\xef\xb0\xb8@\x82\xac\xc9b\xe5\x9a\xb8h>\x02\x99";
|
||||
|
||||
// assert_eq!(request, PY_REQUEST);
|
||||
|
||||
println!("writing request to stream: {request:?}");
|
||||
self.socket.write(&request)?;
|
||||
|
||||
let mut buffer = [0; 512];
|
||||
let bytes_read = self.socket.read(&mut buffer)?;
|
||||
|
||||
if bytes_read < 20 {
|
||||
bail!("Authentication failed!");
|
||||
bail!(
|
||||
"Authentication failed! (answer too short) {:?}",
|
||||
&buffer[..bytes_read]
|
||||
);
|
||||
}
|
||||
|
||||
self.security.tcp_key(&buffer[8..72], &self.key)?;
|
||||
|
@ -87,25 +95,24 @@ impl Device {
|
|||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::net::UdpSocket;
|
||||
|
||||
use anyhow::Result;
|
||||
use anyhow::{Context, Result};
|
||||
use futures::future::try_join;
|
||||
use serial_test::serial;
|
||||
|
||||
use crate::{device::Device, Cloud, Startup};
|
||||
|
||||
#[tokio::test]
|
||||
async fn connect() -> Result<()> {
|
||||
async fn verify_hex() -> Result<()> {
|
||||
let devices = Startup::discover().await?;
|
||||
|
||||
const TOKEN:&str = "06df24fc4e8e950c6d9783051b8e38d971e5fbc617da259459d30d5e7d7fc05b4ccb708fe3a085f6f0af0f8cc961fa39dabfd0746f7bbcfbf7404d9cc5c2b077";
|
||||
const KEY: &str = "2a5b5200c2c04d4c811d0550e1dc5b31435436b95b774d2a88d7e46d61fd9669";
|
||||
const PY_TOKEN: &str = "06df24fc4e8e950c6d9783051b8e38d971e5fbc617da259459d30d5e7d7fc05b4ccb708fe3a085f6f0af0f8cc961fa39dabfd0746f7bbcfbf7404d9cc5c2b077";
|
||||
const PY_KEY: &str = "2a5b5200c2c04d4c811d0550e1dc5b31435436b95b774d2a88d7e46d61fd9669";
|
||||
|
||||
let token_hex = b"\x06\xdf$\xfcN\x8e\x95\x0cm\x97\x83\x05\x1b\x8e8\xd9q\xe5\xfb\xc6\x17\xda%\x94Y\xd3\r^}\x7f\xc0[L\xcbp\x8f\xe3\xa0\x85\xf6\xf0\xaf\x0f\x8c\xc9a\xfa9\xda\xbf\xd0to{\xbc\xfb\xf7@M\x9c\xc5\xc2\xb0w";
|
||||
let key_hex = b"*[R\x00\xc2\xc0ML\x81\x1d\x05P\xe1\xdc[1CT6\xb9[wM*\x88\xd7\xe4ma\xfd\x96i";
|
||||
|
||||
for device_info in devices {
|
||||
let device = Device::connect(device_info, TOKEN, KEY)?;
|
||||
let device = Device::connect(device_info, PY_TOKEN, PY_KEY)?;
|
||||
|
||||
assert_eq!(&device.token, token_hex);
|
||||
assert_eq!(&device.key, key_hex);
|
||||
|
@ -115,6 +122,35 @@ mod test {
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn connect_py_token() -> Result<()> {
|
||||
let devices = Startup::discover().await?;
|
||||
|
||||
const PY_TOKEN: &str = "18a821cb88293c6552dc576f0672d8b9445205f74b636764929de5e8badfa48a24caa9d741f632a18e1a9fee67c40b0b40edc21ac7c4c40b6352181cd4000203";
|
||||
const PY_KEY: &str = "0fc0c56ea8124414a362e6449ee45ba92558a54f159d4937af697e405f2326b9";
|
||||
|
||||
for device_info in devices {
|
||||
Device::connect(device_info, PY_TOKEN, PY_KEY)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn connect_rust_token() -> Result<()> {
|
||||
let devices = Startup::discover().await?;
|
||||
|
||||
const TOKEN: &str = "702b9dfc3ac6c82979986ee3a053a76f75f9e9c763ce5c25af5c2cc982f797a9409adff3745e23fee3a464d745e005c839efb0b84082acc962e59ab8683e0299";
|
||||
const KEY: &str = "52b2feee353841588994e630dcb59819ec71ce1ffacb48628f4f436f5c54f11e";
|
||||
|
||||
for device_info in devices {
|
||||
Device::connect(device_info, TOKEN, KEY)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn full_flow() -> Result<()> {
|
||||
let mut cloud = Cloud::new("michaelh.95@t-online.de", "Hoda.semi1")?;
|
||||
|
||||
|
@ -123,22 +159,10 @@ mod test {
|
|||
for device_info in devices {
|
||||
let (token, key) = cloud.keys(device_info.id).await?;
|
||||
|
||||
Device::connect(device_info, &token, &key)?;
|
||||
Device::connect(device_info, &token, &key)
|
||||
.context(format!("\ntoken: {token}\nkey: {key}"))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn local_socket() -> Result<()> {
|
||||
let socket = UdpSocket::bind("0.0.0.0:0")?;
|
||||
socket.connect("192.168.178.94:6445")?;
|
||||
|
||||
socket.send(b"test")?;
|
||||
|
||||
let mut buffer = [0; 512];
|
||||
let bytes_read = socket.recv(&mut buffer)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -176,9 +176,7 @@ mod test {
|
|||
|
||||
#[tokio::test]
|
||||
async fn discover() -> Result<()> {
|
||||
let devices = Startup::discover().await?;
|
||||
|
||||
println!("{devices:#?}");
|
||||
Startup::discover().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use aes::{
|
||||
cipher::{
|
||||
block_padding::Pkcs7, generic_array::GenericArray, BlockDecrypt, BlockDecryptMut,
|
||||
block_padding::NoPadding, generic_array::GenericArray, BlockDecrypt, BlockDecryptMut,
|
||||
BlockEncryptMut, KeyInit, KeyIvInit,
|
||||
},
|
||||
Aes128,
|
||||
|
@ -16,7 +16,7 @@ use crate::hex;
|
|||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
|
||||
pub enum MsgType {
|
||||
HANDSHAKE_REQUEST = 0x0,
|
||||
ANDSHAKE_RESPONSE = 0x1,
|
||||
HANDSHAKE_RESPONSE = 0x1,
|
||||
ENCRYPTED_RESPONSE = 0x3,
|
||||
ENCRYPTED_REQUEST = 0x6,
|
||||
}
|
||||
|
@ -46,47 +46,45 @@ impl Security {
|
|||
data
|
||||
}
|
||||
|
||||
pub fn aes_cbc_encrypt(&self, raw: &[u8], key: &[u8; 32]) -> Vec<u8> {
|
||||
pub fn aes_cbc_encrypt(&self, raw: [u8; 32], key: &[u8; 32]) -> [u8; 32] {
|
||||
type Aes256CbcEnc = cbc::Encryptor<aes::Aes256>;
|
||||
|
||||
let mut buf = vec![0; raw.len()];
|
||||
buf.copy_from_slice(raw);
|
||||
|
||||
Aes256CbcEnc::new(key.into(), &Self::IV.into())
|
||||
.encrypt_padded_mut::<Pkcs7>(&mut buf, raw.len())
|
||||
.unwrap();
|
||||
|
||||
buf
|
||||
.encrypt_padded_vec_mut::<NoPadding>(&raw)
|
||||
.try_into()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn aes_cbc_decrypt(&self, raw: &[u8], key: &[u8; 32]) -> Vec<u8> {
|
||||
pub fn aes_cbc_decrypt(&self, raw: [u8; 32], key: &[u8; 32]) -> [u8; 32] {
|
||||
type Aes256CbcDec = cbc::Decryptor<aes::Aes256>;
|
||||
|
||||
let mut buf = vec![0; raw.len()];
|
||||
buf.copy_from_slice(raw);
|
||||
|
||||
Aes256CbcDec::new(key.into(), &Self::IV.into())
|
||||
.decrypt_padded_mut::<Pkcs7>(&mut buf)
|
||||
.unwrap();
|
||||
|
||||
buf
|
||||
.decrypt_padded_vec_mut::<NoPadding>(&raw)
|
||||
.unwrap()
|
||||
.try_into()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn tcp_key(&mut self, response: &[u8], key: &[u8; 32]) -> Result<()> {
|
||||
if response == b"ERROR" {
|
||||
bail!("authentication failed!");
|
||||
bail!("authentication failed! (code ERROR)");
|
||||
}
|
||||
|
||||
let payload = &response[0..32];
|
||||
let payload: [u8; 32] = response[0..32]
|
||||
.iter()
|
||||
.map(|&b| b)
|
||||
.collect::<Vec<u8>>()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
|
||||
let sign = &response[32..];
|
||||
let result = self.aes_cbc_decrypt(payload, &key);
|
||||
|
||||
let plain = self.aes_cbc_decrypt(payload, &key);
|
||||
|
||||
if Sha256::digest(&plain).into_iter().collect::<Vec<u8>>() != sign {
|
||||
if Sha256::digest(&result).into_iter().collect::<Vec<u8>>() != sign {
|
||||
bail!("sign does not match");
|
||||
}
|
||||
|
||||
self.tcp_key = Some(Self::xorstr(&plain, key).try_into().unwrap());
|
||||
self.tcp_key = Some(Self::xorstr(&result, key).try_into().unwrap());
|
||||
self.request_count = 0;
|
||||
self.response_count = 0;
|
||||
|
||||
|
@ -94,7 +92,7 @@ impl Security {
|
|||
}
|
||||
|
||||
fn xorstr(lhs: &[u8], rhs: &[u8]) -> Vec<u8> {
|
||||
assert_eq!(lhs.len(), rhs.len());
|
||||
debug_assert_eq!(lhs.len(), rhs.len());
|
||||
|
||||
lhs.iter().zip(rhs.iter()).map(|(&l, &r)| l ^ r).collect()
|
||||
}
|
||||
|
@ -135,7 +133,9 @@ impl Security {
|
|||
.into_iter()
|
||||
.collect();
|
||||
|
||||
data = self.aes_cbc_encrypt(&data, self.tcp_key.as_ref().unwrap());
|
||||
data = self
|
||||
.aes_cbc_encrypt(data.try_into().unwrap(), self.tcp_key.as_ref().unwrap())
|
||||
.to_vec();
|
||||
data.extend(sign);
|
||||
}
|
||||
|
||||
|
@ -148,3 +148,28 @@ impl Security {
|
|||
lhs
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn aes_cbc_decrypt() {
|
||||
let payload: [u8; 32] =
|
||||
b",\xcbq_T\x81L\x96\xfa\xe7\xe4\xa7\xc5\xabU \r\xf5x\xd6\x08\x94_\\\xce\x8br\x1b\xa5\xbe\xc6\x1a"
|
||||
.iter()
|
||||
.map(|&b| b)
|
||||
.collect::<Vec<u8>>()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
|
||||
let key = b"*[R\x00\xc2\xc0ML\x81\x1d\x05P\xe1\xdc[1CT6\xb9[wM*\x88\xd7\xe4ma\xfd\x96i";
|
||||
let plain = b"\x9b\xaa\xdf\xff\x07\x1a\xd2\xe4\xb7TY\xe2\xf9\x8c\xdf\xe7!+\xda\xe4\x86GY\xe6j\x94\xdb\xe7\xb9b\xda\xe6";
|
||||
|
||||
let security = Security::default();
|
||||
|
||||
let result = security.aes_cbc_decrypt(payload, key);
|
||||
|
||||
assert_eq!(&result, plain);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue