From 2aec54c7a75ecb18623c077bc628b82aca3299e2 Mon Sep 17 00:00:00 2001 From: hodasemi Date: Mon, 2 Oct 2023 09:30:58 +0200 Subject: [PATCH] Implement 8370 message encryption --- device.py | 14 +++++++ midea.py | 25 ------------- src/command/request.rs | 19 +++++++++- src/device.rs | 14 ++++++- src/devices/e1.rs | 17 ++++++++- src/packet_builder.rs | 6 +++ src/security.rs | 85 +++++++++++++++++++++++++++++++++++++----- 7 files changed, 140 insertions(+), 40 deletions(-) diff --git a/device.py b/device.py index adff41f..05aca39 100644 --- a/device.py +++ b/device.py @@ -171,12 +171,26 @@ class MiedaDevice(threading.Thread): def send_message_v3(self, data, msg_type=MSGTYPE_ENCRYPTED_REQUEST): data = self._security.encode_8370(data, msg_type) + + print("=== encrypted ===") + print(data) + self.send_message_v2(data) def build_send(self, cmd): data = cmd.serialize() + + print("=== data ===") + print(data) + print + _LOGGER.debug(f"[{self._device_id}] Sending: {cmd}") msg = PacketBuilder(self._device_id, data).finalize() + + print("=== msg ===") + print(msg) + print + self.send_message(msg) def refresh_status(self, wait_response=False): diff --git a/midea.py b/midea.py index 188cc94..c54fbaa 100644 --- a/midea.py +++ b/midea.py @@ -71,31 +71,6 @@ async def test(): if dev.connect(True): return dev - - -t = datetime.datetime.now().strftime("%Y%m%d%H%M%S%f")[:16] - -t = "20230929142324212188673"[:16] - -print(t) - -b = bytearray() - -for i in range(0, len(t), 2): - - - tmp = t[i:i+2] - d = int(tmp) - - - b.insert(0, d) - - -print(b) - -b_ref = bytearray([21, 24, 23, 14, 29, 9, 23, 20]) -print(b_ref) - dev = asyncio.run(test()) print(dev) diff --git a/src/command/request.rs b/src/command/request.rs index f1c1d7e..18641cc 100644 --- a/src/command/request.rs +++ b/src/command/request.rs @@ -6,6 +6,9 @@ pub trait RequestSerializer { } pub struct CommandRequest { + #[cfg(debug_assertions)] + pub name: &'static str, + command: Command, } @@ -19,6 +22,7 @@ impl CommandRequest { device_type: u8, message_type: MessageType, body: Body, + #[cfg(debug_assertions)] name: &'static str, ) -> Self { Self { command: Command { @@ -30,6 +34,8 @@ impl CommandRequest { ), body, }, + + name, } } @@ -69,7 +75,9 @@ impl CommandQuerySubtype { 0, device_type, MessageType::QuerySubtype, - Body::from([0x00; 18].as_slice()), + Body::from(([0x00; 18].as_slice(), 0x00)), + #[cfg(debug_assertions)] + "QuerySubtype", ), } } @@ -100,7 +108,14 @@ pub struct CommandQueryCustom { impl CommandQueryCustom { pub fn new(device_type: u8, message_type: MessageType, body: Body) -> Self { Self { - command: CommandRequest::new(0, device_type, message_type, body), + command: CommandRequest::new( + 0, + device_type, + message_type, + body, + #[cfg(debug_assertions)] + "QueryCustom", + ), } } diff --git a/src/device.rs b/src/device.rs index a4c784d..2c204c3 100644 --- a/src/device.rs +++ b/src/device.rs @@ -151,7 +151,7 @@ impl Device { } fn parse_message(&mut self, msg: &[u8]) -> Result { - let (messages, buffer) = self.security.decode_8370(&self.buffer, msg)?; + let (messages, buffer) = self.security.decode_8370([&self.buffer, msg].concat())?; self.buffer = buffer; if messages.is_empty() { @@ -207,6 +207,12 @@ impl Device { fn send_message(&self, msg: &[u8]) -> Result<()> { let data = self.security.encode_8370(msg, MsgType::ENCRYPTED_REQUEST)?; + + // encrypted + let e = b"\x83p\x00~ fq\xa8\xa9b(\x8c\r,\x96X\xbdT\x1d\x06\xa1/\xb5@\xa2\xeb\x96\x0c\x01s\xf0\x8c\x98ELT\x89\x81\xcc\x9d\xaa\xb6[dq\xcf\x98\xd1s\x8c\x08\x0e\xe6u1D\x80\x17I\xe2\x987s\xbe\xb2\xa9\x13\x86\xce\xb3qq\xfe\xa6\x11\x10\xcfi\xc2\xaeXJH\xb8\xa8\x0b5\x08z\x00\xec\xa2t\x13\xeds\xe7:\x0f\x0eP\xfe\x80w7\xbb\xdf\x0f\x14D\xfd9\xceZ\xda\x1a\xda\xfb\x0b\xe0\x92\xc2D\xb4\xdfWE\x89_\xd9\xd0\xb2\xb6\xd9"; + + // assert_eq!(data, e); + self.socket.lock().unwrap().write(&data)?; Ok(()) @@ -219,6 +225,12 @@ impl Device { fn build_send(&self, cmd: impl RequestSerializer) -> Result<()> { let data = PacketBuilder::builder(self.info.id, cmd).finalize(1); + + // msg + let d = b"ZZ\x01\x11X\x00 \x00\x00\x00\x00\x00\x0435\x06\x02\n\x17\x14\x02\x86\x02\x00\x00\x8b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x17G\x0b\xc3\xe7\r\xaa\xb93\x81r0?\x02s\xf8\x88\xe9G)\xa5\x127\x99\xdd\x8afi\xbd\xf6\xc3\xf5\xf1B\x84\x8a&n\xd9\xdb\xba\xee(\\\'\xb2\x0c\x02"; + + // assert_eq!(data, d); + self.send_message(&data) } } diff --git a/src/devices/e1.rs b/src/devices/e1.rs index 6e35b69..2b94271 100644 --- a/src/devices/e1.rs +++ b/src/devices/e1.rs @@ -293,9 +293,14 @@ pub struct CommandE1Base { } impl CommandE1Base { - pub fn new(device_protocol_version: u8, message_type: MessageType, body: Body) -> Self { + pub fn new( + device_protocol_version: u8, + message_type: MessageType, + body: Body, + #[cfg(debug_assertions)] name: &'static str, + ) -> Self { Self { - command: CommandRequest::new(device_protocol_version, 0xE1, message_type, body), + command: CommandRequest::new(device_protocol_version, 0xE1, message_type, body, name), } } } @@ -325,6 +330,8 @@ impl CommandPower { device_protocol_version, MessageType::Set, Body::from(([0x00; 4].as_slice(), 0x08)), + #[cfg(debug_assertions)] + "Power", ), } } @@ -361,6 +368,8 @@ impl CommandLock { device_protocol_version, MessageType::Set, Body::from((concat!(vec![0x04], vec![0x00; 36],).as_slice(), 0x08)), + #[cfg(debug_assertions)] + "Lock", ), } } @@ -400,6 +409,8 @@ impl CommandStorage { concat!(vec![0x00; 4], vec![0xFF; 6], vec![0x00; 27],).as_slice(), 0x81, )), + #[cfg(debug_assertions)] + "Storage", ), } } @@ -436,6 +447,8 @@ impl CommandQuery { device_protocol_version, MessageType::Query, Body::from(([].as_slice(), 0x00)), + #[cfg(debug_assertions)] + "Query", ), } } diff --git a/src/packet_builder.rs b/src/packet_builder.rs index 29218d3..a61ead1 100644 --- a/src/packet_builder.rs +++ b/src/packet_builder.rs @@ -63,6 +63,12 @@ impl PacketBuilder { packet[6] = 0x7b; } else { let mut data = self.command.serialize(); + + // data + let d = b"\xaa\x1d\xe1\x00\x00\x00\x00\x00\x00\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00b"; + + assert_eq!(data, d); + Security::aes_encrypt(&mut data); packet.extend(data); diff --git a/src/security.rs b/src/security.rs index 88e13ee..b236004 100644 --- a/src/security.rs +++ b/src/security.rs @@ -74,13 +74,18 @@ impl Security { } } - pub fn aes_cbc_encrypt(&self, raw: [u8; 32], key: &[u8; 32]) -> [u8; 32] { + pub fn aes_cbc_encrypt(&self, raw: &[u8], key: &[u8; 32]) -> Vec { type Aes256CbcEnc = cbc::Encryptor; - Aes256CbcEnc::new(key.into(), &Self::IV.into()) - .encrypt_padded_vec_mut::(&raw) - .try_into() - .unwrap() + debug_assert_eq!(raw.len() % key.len(), 0); + + raw.chunks(key.len()) + .map(|r| { + Aes256CbcEnc::new(key.into(), &Self::IV.into()) + .encrypt_padded_vec_mut::(r) + }) + .flatten() + .collect() } pub fn aes_cbc_decrypt(&self, raw: [u8; 32], key: &[u8; 32]) -> [u8; 32] { @@ -163,9 +168,7 @@ impl Security { .into_iter() .collect(); - data = self - .aes_cbc_encrypt(data.try_into().unwrap(), self.tcp_key.as_ref().unwrap()) - .to_vec(); + data = self.aes_cbc_encrypt(&data, self.tcp_key.as_ref().unwrap()); data.extend(sign); } @@ -178,8 +181,70 @@ impl Security { lhs } - pub fn decode_8370(&self, buffer: &[u8], msg: &[u8]) -> Result<(Vec>, Vec)> { - todo!() + pub fn decode_8370(&self, mut data: Vec) -> Result<(Vec>, Vec)> { + if data.len() < 6 { + return Ok((Vec::new(), data)); + } + + let header = data[..6].to_vec(); + + if header[0] != 0x83 || header[1] != 0x70 { + bail!("no an 8370 message"); + } else if header[4] != 0x20 { + bail!("missing byte 4"); + } + + let size = u16::from_be_bytes(header[2..4].try_into().unwrap()) as usize + 8; + + let mut leftover = Vec::new(); + + if data.len() < size { + return Ok((Vec::new(), data.to_vec())); + } else if data.len() > size { + leftover = data[size..].to_vec(); + data = data[..size].to_vec(); + } + + let padding = header[5] >> 4; + let msgtype = header[5] & 0xf; + data = data[6..].to_vec(); + + if msgtype == MsgType::ENCRYPTED_RESPONSE as u8 + || msgtype == MsgType::ENCRYPTED_REQUEST as u8 + { + let sign = data[(data.len() - 32)..].to_vec(); + data = data[..(data.len() - 32)].to_vec(); + data = self + .aes_cbc_decrypt(data.try_into().unwrap(), &self.tcp_key.unwrap()) + .to_vec(); + + let compare_sign: Vec = Sha256::digest(&[header.to_vec(), data.clone()].concat()) + .into_iter() + .collect(); + + if sign != compare_sign { + bail!("sign does not match"); + } + + if padding != 0 { + data = data[..(data.len() - padding as usize)].to_vec(); + } + } + + self.response_count + .store(u16::from_be_bytes(data[..2].try_into().unwrap()), SeqCst); + + data = data[2..].to_vec(); + + if leftover.len() > 0 { + let (mut packets, incomplete) = self.decode_8370(leftover)?; + + packets.insert(0, data); + + Ok((packets, incomplete)) + } else { + Ok((vec![data], Vec::new())) + } } }