More connection testing

This commit is contained in:
hodasemi 2023-09-25 11:30:54 +02:00
parent 3122f03e9e
commit 1fdd2a3f1e
7 changed files with 141 additions and 57 deletions

View file

@ -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"

View file

@ -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]

View file

@ -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():

View file

@ -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")?;

View file

@ -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(())
}
}

View file

@ -176,9 +176,7 @@ mod test {
#[tokio::test]
async fn discover() -> Result<()> {
let devices = Startup::discover().await?;
println!("{devices:#?}");
Startup::discover().await?;
Ok(())
}

View file

@ -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);
}
}