Implement 8370 message encryption
This commit is contained in:
parent
d1bef25ac9
commit
2aec54c7a7
7 changed files with 140 additions and 40 deletions
14
device.py
14
device.py
|
@ -171,12 +171,26 @@ class MiedaDevice(threading.Thread):
|
||||||
|
|
||||||
def send_message_v3(self, data, msg_type=MSGTYPE_ENCRYPTED_REQUEST):
|
def send_message_v3(self, data, msg_type=MSGTYPE_ENCRYPTED_REQUEST):
|
||||||
data = self._security.encode_8370(data, msg_type)
|
data = self._security.encode_8370(data, msg_type)
|
||||||
|
|
||||||
|
print("=== encrypted ===")
|
||||||
|
print(data)
|
||||||
|
|
||||||
self.send_message_v2(data)
|
self.send_message_v2(data)
|
||||||
|
|
||||||
def build_send(self, cmd):
|
def build_send(self, cmd):
|
||||||
data = cmd.serialize()
|
data = cmd.serialize()
|
||||||
|
|
||||||
|
print("=== data ===")
|
||||||
|
print(data)
|
||||||
|
print
|
||||||
|
|
||||||
_LOGGER.debug(f"[{self._device_id}] Sending: {cmd}")
|
_LOGGER.debug(f"[{self._device_id}] Sending: {cmd}")
|
||||||
msg = PacketBuilder(self._device_id, data).finalize()
|
msg = PacketBuilder(self._device_id, data).finalize()
|
||||||
|
|
||||||
|
print("=== msg ===")
|
||||||
|
print(msg)
|
||||||
|
print
|
||||||
|
|
||||||
self.send_message(msg)
|
self.send_message(msg)
|
||||||
|
|
||||||
def refresh_status(self, wait_response=False):
|
def refresh_status(self, wait_response=False):
|
||||||
|
|
25
midea.py
25
midea.py
|
@ -71,31 +71,6 @@ async def test():
|
||||||
if dev.connect(True):
|
if dev.connect(True):
|
||||||
return dev
|
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())
|
dev = asyncio.run(test())
|
||||||
|
|
||||||
print(dev)
|
print(dev)
|
||||||
|
|
|
@ -6,6 +6,9 @@ pub trait RequestSerializer {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct CommandRequest {
|
pub struct CommandRequest {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
pub name: &'static str,
|
||||||
|
|
||||||
command: Command,
|
command: Command,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,6 +22,7 @@ impl CommandRequest {
|
||||||
device_type: u8,
|
device_type: u8,
|
||||||
message_type: MessageType,
|
message_type: MessageType,
|
||||||
body: Body,
|
body: Body,
|
||||||
|
#[cfg(debug_assertions)] name: &'static str,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
command: Command {
|
command: Command {
|
||||||
|
@ -30,6 +34,8 @@ impl CommandRequest {
|
||||||
),
|
),
|
||||||
body,
|
body,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
name,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,7 +75,9 @@ impl CommandQuerySubtype {
|
||||||
0,
|
0,
|
||||||
device_type,
|
device_type,
|
||||||
MessageType::QuerySubtype,
|
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 {
|
impl CommandQueryCustom {
|
||||||
pub fn new(device_type: u8, message_type: MessageType, body: Body) -> Self {
|
pub fn new(device_type: u8, message_type: MessageType, body: Body) -> Self {
|
||||||
Self {
|
Self {
|
||||||
command: CommandRequest::new(0, device_type, message_type, body),
|
command: CommandRequest::new(
|
||||||
|
0,
|
||||||
|
device_type,
|
||||||
|
message_type,
|
||||||
|
body,
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
"QueryCustom",
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -151,7 +151,7 @@ impl Device {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_message(&mut self, msg: &[u8]) -> Result<ParseMessage> {
|
fn parse_message(&mut self, msg: &[u8]) -> Result<ParseMessage> {
|
||||||
let (messages, buffer) = self.security.decode_8370(&self.buffer, msg)?;
|
let (messages, buffer) = self.security.decode_8370([&self.buffer, msg].concat())?;
|
||||||
self.buffer = buffer;
|
self.buffer = buffer;
|
||||||
|
|
||||||
if messages.is_empty() {
|
if messages.is_empty() {
|
||||||
|
@ -207,6 +207,12 @@ impl Device {
|
||||||
|
|
||||||
fn send_message(&self, msg: &[u8]) -> Result<()> {
|
fn send_message(&self, msg: &[u8]) -> Result<()> {
|
||||||
let data = self.security.encode_8370(msg, MsgType::ENCRYPTED_REQUEST)?;
|
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)?;
|
self.socket.lock().unwrap().write(&data)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -219,6 +225,12 @@ impl Device {
|
||||||
|
|
||||||
fn build_send(&self, cmd: impl RequestSerializer) -> Result<()> {
|
fn build_send(&self, cmd: impl RequestSerializer) -> Result<()> {
|
||||||
let data = PacketBuilder::builder(self.info.id, cmd).finalize(1);
|
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)
|
self.send_message(&data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -293,9 +293,14 @@ pub struct CommandE1Base {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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 {
|
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,
|
device_protocol_version,
|
||||||
MessageType::Set,
|
MessageType::Set,
|
||||||
Body::from(([0x00; 4].as_slice(), 0x08)),
|
Body::from(([0x00; 4].as_slice(), 0x08)),
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
"Power",
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -361,6 +368,8 @@ impl CommandLock {
|
||||||
device_protocol_version,
|
device_protocol_version,
|
||||||
MessageType::Set,
|
MessageType::Set,
|
||||||
Body::from((concat!(vec![0x04], vec![0x00; 36],).as_slice(), 0x08)),
|
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(),
|
concat!(vec![0x00; 4], vec![0xFF; 6], vec![0x00; 27],).as_slice(),
|
||||||
0x81,
|
0x81,
|
||||||
)),
|
)),
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
"Storage",
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -436,6 +447,8 @@ impl CommandQuery {
|
||||||
device_protocol_version,
|
device_protocol_version,
|
||||||
MessageType::Query,
|
MessageType::Query,
|
||||||
Body::from(([].as_slice(), 0x00)),
|
Body::from(([].as_slice(), 0x00)),
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
"Query",
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,6 +63,12 @@ impl<S: RequestSerializer> PacketBuilder<S> {
|
||||||
packet[6] = 0x7b;
|
packet[6] = 0x7b;
|
||||||
} else {
|
} else {
|
||||||
let mut data = self.command.serialize();
|
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);
|
Security::aes_encrypt(&mut data);
|
||||||
|
|
||||||
packet.extend(data);
|
packet.extend(data);
|
||||||
|
|
|
@ -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<u8> {
|
||||||
type Aes256CbcEnc = cbc::Encryptor<aes::Aes256>;
|
type Aes256CbcEnc = cbc::Encryptor<aes::Aes256>;
|
||||||
|
|
||||||
Aes256CbcEnc::new(key.into(), &Self::IV.into())
|
debug_assert_eq!(raw.len() % key.len(), 0);
|
||||||
.encrypt_padded_vec_mut::<NoPadding>(&raw)
|
|
||||||
.try_into()
|
raw.chunks(key.len())
|
||||||
.unwrap()
|
.map(|r| {
|
||||||
|
Aes256CbcEnc::new(key.into(), &Self::IV.into())
|
||||||
|
.encrypt_padded_vec_mut::<NoPadding>(r)
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn aes_cbc_decrypt(&self, raw: [u8; 32], key: &[u8; 32]) -> [u8; 32] {
|
pub fn aes_cbc_decrypt(&self, raw: [u8; 32], key: &[u8; 32]) -> [u8; 32] {
|
||||||
|
@ -163,9 +168,7 @@ impl Security {
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
data = self
|
data = self.aes_cbc_encrypt(&data, self.tcp_key.as_ref().unwrap());
|
||||||
.aes_cbc_encrypt(data.try_into().unwrap(), self.tcp_key.as_ref().unwrap())
|
|
||||||
.to_vec();
|
|
||||||
data.extend(sign);
|
data.extend(sign);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,8 +181,70 @@ impl Security {
|
||||||
lhs
|
lhs
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn decode_8370(&self, buffer: &[u8], msg: &[u8]) -> Result<(Vec<Vec<u8>>, Vec<u8>)> {
|
pub fn decode_8370(&self, mut data: Vec<u8>) -> Result<(Vec<Vec<u8>>, Vec<u8>)> {
|
||||||
todo!()
|
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<u8> = 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()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue