title | description | icon |
---|---|---|
iown-homecontrol - Data Link and Network Layer |
io-homecontrol protocol and data format specification |
material/layers-triple |
There are at least two known protocols with some kind of third protocol especially for OEMs ("EMS2) that gets triggered with a long unmodulated carrier wave...
---
title: io-homecontrol Frame
---
%%{init:{"fontFamily":"monospace"}}%%
block-beta
columns 9
Frame["Frame: 1W"]:9
block:frame:9
cb1["CTRL<br/>BYTE1"]
cb2["CTRL<br/>BYTE2"]
sa["SOURCE<br/>NodeID"]
da["DEST<br/>NodeID"]
cmd["Command<br/>ID"]
data["Main<br/>Parameter"]
rc["Rolling<br/>Code"]
hmac["HMAC"]
crc["CRC"]
end
space:1
size["Size"]:7
space:1
checksum["CRC-16/KERMIT"]:8
- Length
- Minimum: 11 bytes (i.e. without any DATA)
- Maximum: 32 bytes (i.e. maximum 21 bytes of DATA)
0b11111
= 31 = 0x1F
getMasterAddress getRSSILevel getNodeAddress getFrameType getCtrlByte0 getCtrlByte1 getCtrlByteManufCode getCtrlByteProtocolVersion getCommand getData isOneWay getDataLen
Message_GetBroadcastType (node_id_t NODE_ID) {
if(NODE_ID[0]) return 0xD;
if(NODE_ID[1] || (NODE_ID[2] & 0xC0) != 0) {
switch (NODE_ID[2] & 0x3F) {
case 0x3B: return 0x7;
case 0x3C: return 0x8;
case 0x3D: return 0x9;
case 0x3E: return 0xA;
case 0x3F: return 0xB;
default: return 0xC;
}}
else if (NODE_ID[2]) {
if(NODE_ID[2] <= 0x3A) {
return 1; }
else {
switch (NODE_ID[2]) {
case 0x3B: return 2;
case 0x3C: return 3;
case 0x3D: return 4;
case 0x3E: return 5;
default: return 6;
}}}
return NODE_ID[2]
―———————————R T F M―———————————
BIT | 7-6 | 5 | 4-0 |
---|---|---|---|
NAME | Order | Protocol Mode |
Size |
-
Order
: Indicates Relationship (Logical and/or Temporal) between transmitted Orders.# bit [7]
bit [6]
Command Order Relationship
(First/Last Frame in Session)0 0 0 Single = Last/First Session Frame: No/No
1 0 1 Next in Series = Last/First Session Frame: No/Yes
2 1 0 Next in Parallel = Last/First Session Frame: Yes/No
3 1 1 Command Group End = Last/First Session Frame: Yes/Yes
-
isOneWay
: Protocol Mode- 0 = 2W = Two Way
- 1 = 1W = One Way
-
Size
: Frame Length in byte excludingControl Byte 0
and theCRC
BIT | 7 | 6 | 5 | 4 | 3 | 2 | 1-0 |
---|---|---|---|---|---|---|---|
NAME | Use Beacon | Routed | Low Power Mode | Ack | ? | ? | Protocol Version |
- Use Beacon (
isBeacon
): Repeater Mode = Allow routing the Frame through the Network- 0: Do Not use Beacon
- 1: Use Beacon
- Note: Max. 1 Hop!
- Routed (
isRouted
): Indicates if Frame has already been routed through a Beacon (Repeater)- 0 frame has not been routed
- 1 frame has been routed
- Low Power Mode (
PowerSaveMode
): Indicates if Frame is sent to Device in Low Power Mode (LPM):- 0 = Destination Device is not in Low Power Mode
- 1 = Destination Device is in Low Power Mode
- Ack indicates if a response can be handled (2W Devices Only)
- 0 = NACK: 1W Device
- 1 = ACK: 2W Device
Addresses are 3 bytes long and can range from 01 00 00
to ff ff ff
.
Addresses are also refered to as Node ID which resembles the MS-B of a typical MAC-Address. The Node Type is a numerical value defines which
00 00 3F
is broadcast00 00 3B
??00 00 00
= GroupFF FF FF
= Broadcast
FFFFFE
= SRC for P2P and BROADCASTFFFDFF
= RS485 (SDN) Setting Tool
Go further down the rabbit hole -> Commands
- Data Length
- Minimum = 0 bytes (no DATA)
- Maximum = 21 bytes
Depending on the frame type it can correspond to raw data, io-homecontrol command parameters or manufacturer-specific data.
Note that in authenticated 1-Way frames, a 2-bytes sequence number and a 6-byte long message authentication code are appended:
X - Y | (Y + 1) - (Y + 2) | (Y + 3) - (Y + 9) |
---|---|---|
Data | Sequence number | MAC |
After the payload, there is a 16-bit CRC with polynomial 0x(1)8408 over the data packet (i.e. length byte + payload). The CRC's initial value is 0 and it does not employ an XOR/NOT. The least significant byte of the CRC is transmitted first.
The C# implementation shown here can be found in the Starter Kit software from Semtech for their SX12xx line of radios. Just use a .NET decompiler (dotPeek, ILSpy, etc.) and search for it:
// dumped ioHC CRC code from the Semtech SX12xx series software
ushort Crc(byte[] packet){
for (i=0; i<packet.Length; i++) {
packet[i]=(byte)((packet[i] * 0x802 & 0x22110 | packet[i] * 0x8020 & 0x88440) * 0x10101 >> 0x10);
}
return 0;
}
... and now for something completely different^^
Each two-ways controller has a stack or system key. This is an AES-128 key used to sign io frames with the trailing MAC.
Advanced Encryption Standard (AES) 128-bit block encryption/decryption with 128 bits key size. Electronic Code Book (ECB) and Cipher Block Chaining Mode 1 (CBC Mode 1) are supported. The AES engine is enabled on the ADF7022 by downloading the AES software module to program RAM.
The key generation on Kizboxes is insecure because it relies on the LuaJIT math.random
:
-- seed
kizbox_id = "xxxx-xxxx-xxxx" -- Kizbox serial
math.randomseed(tonumber(string.gsub("1" .. kizbox_id, "-", "")) - os.time())
-- key generation
key = {}
keySize = 16 -- 128 bits
for i = 1, keySize do
key[i] = math.random(0, 255)
end
This mechanism only leaves roughly 31,536,000 keys possible if the serial number of the TaHoma and the year of setup are known (other calls to math.random are made so the actual number may be a bit above this figure but once the seed and the number of times math.random is called are figured out by an attacker, they can completly determine the key).
sequenceDiagram
participant 1W as Remote
participant 2W as Actuator
Note over 1W,2W: 1-Way Discovery
1W->>2W: Command ID: 0x39<br/>Remove 1-Way Remote
activate 2W
loop 0x39
2W->>2W: Remove<br/>1-Way Key
end
deactivate 2W
1W-->>2W: Command ID: 0x30<br/>Send Encrypted 1-Way Key
activate 2W
loop 0x30
2W->>2W: Store<br/>1-Way Key
end
deactivate 2W
In 1-way mode, the controller does not get any answer. So this is the simplest discovery mode, it asks for exclusion using 0x39 and then sends its 1-W encrypted key using 0x30.
- Controller sends command
0x28
(discover) when entering discover mode - Device in pairing mode answers with command
0x29
(discover answer) and gives metadata to identify itself to the controller - Controller confirms having received discovery information from device
- Devices acks confirmation
# Discover request
C8 00 00003B F00F00 28 1234
# Discover answer: node type REMOTE_CONTROLLER, subtype 0, node address feefee, manufacturer Atlantic
# multi info byte 0xcc, timestamp 0000
D1 00 F00F00 FEEFEE 29 FFC0 FEEFEE 0C CC 0000 1234
# Discover confirmation
48 00 FEEFEE F00F00 2C 1234
## Discover confirmation ack
88 00 F00F00 FEEFEE 2D 1234
Must send a 0x38
key transfer, just after the 0x2D
discover confirmation ack to effectivly have the device added
# Controller ask for key transfer using challenge 123456789ABC
4E 04 FEEFEE F00F00 38 123456789ABC 23B6
- Controller sends command 0x2a (specialized discover) with specific data not identified to this day
- Device in pairing mode answers with 0x2b and same information fields as in 0x29
- Controller and device exchange 0x2c and 0x2d just like in the general discover
- Controller sends command 0x36, authenticates and expects an address in 0x37 answer (see 2-way key exchange push below)
Before being able to communicate with authentication, io-homecontrol nodes must share a common secret as the security of the protocol relies on a symmetric encryption algorithm.
The pairing process consists in transmitting the key. Authentication in adding and verifying challenges.
In both modes (1W/2W), the transferred key is obfuscated using different methods. But both methods imply a shared key probably specified in the protocol documentation.
This key is referred to as transfer key and has the following value, probably hardcoded in the specification:
Important
34C3466ED88F4E8E16AA473949884373
Other Keys found in the io Gateway:
SDNP io Actuator 1W Control Key = FD534F4D4659
=0xFD SOMFY
4275696C64696E6720436F6E74726F6C
=Building Control
Tip
All Crypto functions are available in Iown-ioCrypto.py
In all cases implying encryption, an Initial Vector (IV) is required to feed the AES algorithm using a mode similar to OFB or CFB. In fact, as all payloads are 128-bit long, there is no block chaining and the encryption process is the following:
- Generate IV
- Encrypt IV with Transfer or System Key (depending on data) using AES-128
- XOR encrypted IV with the Secret for Transmission
-
How To Generate IV:
Mode 0-7 8-9 10-11 12-15 1W First 8 byte of Payload Checksum Sequence Number Padding (0x55) 2W First 8 byte of Payload Checksum Challenge (sent beforehand) - Pad Payload if less than 8 byte with 0x55
- Compute Checksum for each Frame byte:
# Checksum Return Value: 2 byte Tuple (byte 0 & byte 1) # Initialization Value for both bytes: 0 def computeChecksum(frame_byte, chksum1, chksum2): tmpchksum = frame_byte ^ chksum2 chksum2 = ((chksum1 & 0x7f) << 1) & 0xff if chksum1 & 0x80 == 0: if tmpchksum >= 128: chksum2 |= 1 return (chksum2, (tmpchksum << 1) & 0xff) if tmpchksum >= 128: chksum2 |= 1 return (chksum2 ^ 0x55, ((tmpchksum << 1) ^ 0x5b) & 0xff)
- Frame:
F6 00 00003F 385762 000143D2000000 0599 123456789ABC 5FB0
- Payload:
000143D2000000
(7 byte => needs 1 byte padding with 0x55)- Sequence Number:
0599
Padded Payload:
000143D200000055
IV will be: `000143D2000000550500059955555555`
In 2-way Mode, for the following exchange:
Ask Challenge:
480012195FD35C18 31 28E3
Challenge Request:
0E00D35C1812195F 3C 123456789ABC A9C7
IV will be:
31555555555555550062123456789ABC
Encryption with AES-128 follows this process:
- IV is generated as described above
- IV is encrypted using AES-128 and depending on the use case with either:
- Transfer Key
- Stack Key
- If encrypting a Secret: The Secret is XORed with the Output of the previous Step.
- If creating a MAC: The output of the previous Step is truncated to 6 bytes.
In 1-way mode, the controller will send several times the command 0x30 with its key encrypted with the transfer key listed above and an initial value that consists in its address repeated several times to build a 16-byte long value.
For node with address ABCDEF
, the initial value will be:
ABCDEF ABCDEF ABCDEF ABCDEF ABCDEF AB
Furthermore, the 0x30 message is authenticated using a 1W MAC embedded in the command itself. For the node ABCDEF
with key 01020304050607080910111213141516
and sequence number 0x1234
the 0x30
message will be:
FC 00 00003F ABCDEF 30 7E60491F976ADF653DB0ED785E49A201 02 01 1234 19E81EC43D5E 9BF2
Note
In the examples below, the stack key used is 01020304050607080910111213141516
In 2-Way mode, there are 2 ways keys can be exchanged:
- By pulling the key from a remote node
- RCM: Receive Controller Mode
- By pushing a key to a remote node
- TCM: Transmit Controller Mode
It seems that when a new device is added to a stack, both methods are used. First, the controller will collect the already set device key and then use it to authenticate requests to push its stack key to the device.
- Actions:
- Right after initial discovery, the controller issues command 0x38 (launch key transfer). This command is not authenticated but submits a 6-byte long initial value to be used to encrypt the key used by the device
- The device answers with command 0x32 (key transfer) and sends its key encrypted using the transfer key
- The controller issues a command 0x3c (challenge request) to authenticate the previous command
- The device answers to the challenge with command 0x3d and a response encrypted using the key transmitted just before in command 0x32
Example Device key is
ABCDEF01020304050607080910111213
// Controller ask for key transfer using challenge 123456789ABC
4E 04 FEEFEE F00F00 38 123456789ABC 23B6
// Device creates an initial value based on last frame and the specified challenge
// and use this initial value to encrypt its key before transmission
18 04 F00F00 FEEFEE 32 EA425A7A182885D4EAEEFD416D625E01 6379
// Controller challenges the device for command 0x32 (see authentication below)
// Note: in real life, challenge will be different to the one specified in 0x38
0E 00 FEEFEE F00F00 3C 123456789ABC 5EB1
4E 00 FEEFEE F00F00 3C 123456789ABC EC2A
// Device creates an initial value based on the 0x32 frame and challenge specified in 0x3c
// It will use its own key to authenticate as it has not received one from the controller
8E 00 F00F00 FEEFEE 3D 0AE519A73C99 2400
Note
Stack key push to device has been observed after a bit of information exchange such as general information (0x54
and 0x56
).
Most of these commands are authenticated using 0x3C
and 0x3D
(see authentication below) and so are the push commands.
- Actions:
- First, the controller asks the device a challenge using command
0x31
(ask challenge) - The device then answers using command
0x3C
(challenge request) and a 6-byte long challenge - The controller sends the encrypted stack key to the device node with command
0x32
(key transfer) using the transfer key - The device asks the controller for authentication using
0x3C
- The controller authenticates using its stack key (not the device key anymore) and command 0x3d
- The device answers with command
0x33
to confirm that stack key has been received - To check if the stack key has properly been transferred, the controller sends the
0x36
command and authenticate to finally receive the address of the device in the0x37
command
- First, the controller asks the device a challenge using command
Example
// Send challenge request
48 00 FEEFEE F00F00 31 FB60
// Challenge request
0E 00 F00F00 FEEFEE 3C 123456789ABC 19DB
// Controller creates an initial value based on last frame and the specified challenge
// and use this initial value to encrypt the stack key before transmission
18 00 F00F00 FEEFEE 32 102E49A16D3B69726F3192CF17534AD9 8043
// Device challenges the controller for command 0x32 (see authentication below)
// Note: in real life, challenge will be different to the one specified in previous 0x3c
0E 00 F00F00 FEEFEE 3C 123456789ABC 19DB
// Controller answers to the challenge using the stack key
0E 00 FEEFEE F00F00 3D 8DC9D40DC7A4 F9E5
// Device saves the stack key and sends a confirmation
88 00 F00F00 FEEFEE 33 5BFB
// Controller checks if the device received the stack key by issuing a control command
48 04 FEEFEE F00F00 36 9A02
// Device sends a challenge to the controller
0E 00 F00F00 FEEFEE 3C 123456789ABC 19DB
// Controller answers the challenge for command 0x36
0E 04 FEEFEE F00F00 3D C7FDC0668818 B1E3
// Device sends its address as answer in 0x37
0B 04 F00F00 FEEFEE 37 FEEFEE 7CCF
io-homecontrol frames are authenticated using AES-128 based MACs. There are differences between 1W and 2W modes. In both cases, the MAC is created by truncating the output of the AES-128 algorithm down to 6 bytes.
In 1W mode, a signature of the frame is made using the stack key burned into the 1W controller during manufacturing. This signature is appended to the frame along with a sequence number to prevent replay attacks.
The data and sequence number are handled separately in the initial vector generation (i.e. the sequence number is not handled as frame data and not taken into account in the first part of the IV).
In 2W mode, a node will issue a 0x3C
command frame with a 6-byte long challenge to commands received from other nodes.
The asking node must provide a 0x3D
command with the answer to the 6-byte long challenge (the answer is also 6-byte long).
2W frames do not have any MAC or sequence number incorporated in the frame itself.
For example, here is a 2W sequence of request/answer type:
// Send address request
48 04 FEEFEE F00F00 36 9A02
// Challenge request
0E 00 F00F00 FEEFEE 3C 123456789ABC 19DB
// Challenge answer
0E 04 FEEFEE F00F00 3D C7FDC0668818 B1E3
// Address answer
0B 04 F00F00 FEEFEE 37 FEEFEE 7CCF
Caution
There is a race condition vulnerability: In fact, it would be possible to spoof answers by answering faster than the legitimate device after a 0x3d frame. This allows authentication of the asking node but not authentication of the answering node...
The MAC is generated using a shared key between both nodes. This is referred as Stack Key in the firmware and System Key in the user interface. The same key is shared throughout the installation (by a 2-Way controller).
Below is an extract of the MAC generation code:
def create_2W_hmac(challenge, system_key, frame_data):
iv = constructInitialValue(frame_data, challenge)
cipher = aes.AES(system_key)
return cipher.encrypt_block(iv)[:6]
The initial value is always created using data from the requesting command (see above for details on initial value generation).
Sadly little is known about the EMS2 device and the protocol that is used in the factory to program io-homecontrol devices.
EMS frames in binary, first 0 is D then frame in binary EMS_STOP_BECAUSE_SINE_WAVE_IS_MISSING
Those values get send directly to the radio. Since they are always 3 byte and the numbers are just the right amount apart my assumption is that those values hold the frequencies.
io Tests Terminal: 0E5EC4 0ECD0B 0F5AD4
ideal RF STM32 CAL: 0E4EC4 0EBD0B 0F4AD4000000
io-homecontrol CAL: 0E4EC4 0EBD0B 0F4AD4
(default)
More information can be found in STM32 section...