This memo describes an idea for secure protocol used for communication with NRF25L01 chips in pair with STM32F103 chips.
This "format" used instead of standard TLS (like HTTPS over TLS over TCP/IP) because you can't really afford having these with something like 20K of RAM and 64K of ROM.
NRF devices should turn off the NRF's packet confirmation, configure address size to 3 bytes, switch to default channel and listen on default address.
Communication uses RSA encryption (2048 bit) to exchange AES key (128 bit), the latter then is used to encrypt the messages.
There is a master key pair, each device has a public key of it (ca.pub
). Also, each device has a key pair for itself (device.pub
, device.pem
).
Eahc device has device.sig
which is a signature of a Public Key + DEVICE_ID
with private master key. Signature is calculated using RSA.sign(device.pub + device_id, ca.pem)
. It is used as a proof that device's public key is legit within the network.
In ourder to achieve successfull encrypted communication, devices should:
- Find each other (
0x1 Discover
/0x2 Hello
) - Exchange RSA public keys (
0x3 Get Public Key
/0x4 Public Key
) - Negotiate on a AES key (
0x5 Key Negotiate
/0x6 Key Data
)
Then they could do requests:
- Open a connection (
0x7 Request
) - Approve the connection (
0x8 Proceed
) - Send the data (
0x9 Request Body
) - Receive the response (
0xA Response
/0xB Response Body
)
Kind | Length | Description |
---|---|---|
CRC |
4 bytes | CRC32 hash of packet including flags |
FLAGS |
1 byte | Packet Flags |
- The rest of the packet depends on the packet type (
TYP
) - Packet size is handled by NRF's protocol itself
- If
CRC
does not match, the packet should be dropped
Name | Bits | Description |
---|---|---|
TYP |
first 4 bits, 0x0F | Kind of the packet |
Reserved | Last 4 bits, 0xF0 | Reserved |
First 4 bits of the packet flags (TYP
) contain packet type
This packet is sent to find devices nearby. By default, NRFs should turn off packet confirmation, switch to default channel and listed to default address.
Kind | Length | Description / Value |
---|---|---|
CRC |
4 bytes | CRC32 |
FLAGS |
1 byte | TYP = 0x1 |
V |
1 byte | Protocol version = 0x01 |
DEVICE_ID |
5 bytes | ID of the current device |
Everyone who receives the packet should respond within specific time window with packet 0x2
Hello. The time to wait is picked randomly.
A response to 0x1
Discover packet. Normally, everyone sho receive the message should respond:
Kind | Length | Description / Value |
---|---|---|
CRC |
4 bytes | CRC32 |
FLAGS |
1 byte | TYP = 0x2 |
DEVICE_ID |
5 bytes | ID of the current device |
PUB |
4 bytes | CRC32 of Public Key + DEVICE_ID + Public Key Signature of the device |
PUB_K |
2 bytes | Size of Public Key in bytes |
PUB_SIG |
2 bytes | Size of Public Key Signature in bytes |
If device chooses to connect to another device, but do not yet have a public key of it, it needs to download it first.
Kind | Length | Description / Value |
---|---|---|
CRC |
4 bytes | CRC32 |
FLAGS |
1 byte | TYP = 0x3 |
DEVICE_ID |
5 bytes | ID of the target device |
ADDR_T |
3 bytes | Random NRF Address |
ADDR_S |
3 bytes | Random NRF Address |
CH |
1 byte | Random NRF Channel |
After receiving this message, the sender will switch to randomly picked channel (CH
) and start listening on random address ADDR_S
for limited time. This is implemented to reduce traffic issues.
If a target device received this message, it should switch to the channel CH
, listen on address ADDR_T
and respond with 0x4 Public Key
message to address ADDR_S
. If the target device is busy, it should ignore this message, and the sender will fail with timeout.
If a device received a 0x3 Get Public Key
message it should switch on channel/address required and respond with this message.
Public key of 2048 bits has length of ~259 bytes, so considering limitation of NRF's max packet size (32 bytes), it has to be delivered sequentially.
The number of packets (and lenght of the Public Key + DEVICE_ID
+ Public Key Signature) is received with 0x2 Hello
. It is calculated by formula (Public Key size + Public Key Signature size) / 26 (rounding up).
Kind | Length | Description / Value |
---|---|---|
CRC |
4 bytes | CRC32 |
FLAGS |
1 byte | TYP = 0x4 |
SEQ |
1 byte | Number of the packet in the requence |
DATA |
up to 26 bytes | Part of Public Key / Public Key Signature data |
Packets are sent one by one, without confirmation for the receiving end. If a packet is received out of order, or same packet received twice, the receiver should just drop the request and switch to default channel / default address.
If the last packet is received in order, the receiving party should:
- Ensure the CRC32 of the Public Key +
DEVICE_ID
+ Public Key Signature matches the one stated in0x2 Hello
. - Verify the Public Key signature doing
RSA.verify(public_key.pub + DEVICE_ID, ca.pub, signature)
If the public key is validated, the public key should be stored in ROM by DEVICE_ID
. The sender should immeadetely switch back to the default channel / address after sending the last packet out of sequesnce.
If a device wants to send a request to another device, but they have not yet negotiated on Key Data, the device should send this message first.
Kind | Length | Description / Value |
---|---|---|
CRC |
4 bytes | CRC32 |
FLAGS |
1 byte | TYP = 0x5 |
DEVICE_SRC |
5 bytes | ID of the current device |
DEVICE_TGT |
5 bytes | ID of the target device |
PUB |
4 bytes | CRC32 of Public Key of the sender + DEVICE_SRC + Public Key Signature of the sender device |
ADDR_T |
3 bytes | Random NRF Address |
ADDR_S |
3 bytes | Random NRF Address |
CH |
1 byte | Random NRF Channel |
If the message was received by device with different DEVICE_ID
from DEVICE_TGT
, the packet should be ignored. The Public Key of the target device should be obtained first (see 0x3 Get Public Key
).
After receiving this message, the sender will switch to randomly picked channel (CH
) and start listening on random address ADDR_S
for limited time. This is implemented to reduce traffic issues.
If a target device received this message, it should switch to the channel CH
, listen on address ADDR_T
. If the target device is busy, it should ignore this message, and the sender will fail the request with timeout.
If the target device does not have the Public Key of the current device yet, it should respond with 0x3 Get Public Key
on the address ADDR_S
channel CH
.
If the target device has the Public Key of the current device, it should generate a random Key Data, store it into the RAM, and respond with 0x6 Key Data
on the address ADDR_S
/ channel CH
.
After receiving the 0x5 Key Negotiate
message, the receiver should
- Generate random AES key (128 bits)
- Generate random Initial Vector (
IV
) (128 bits) - Generate a random channel
NG_CH
(1 byte) - Generate random pair
SND_ADDRESS
/RCV_ADDRESS
(3 + 3 bytes)
Referred as Key Data, then:
- Encrypt Key Data (39 bytes) with Public Key of the sender
- Store the data into RAM
- Respond with this message on the address
ADDR_S
/ channelCH
.
Because of length of the encrypted data (245 bytes), it should be delivered sequentially.
Kind | Length | Description / Value |
---|---|---|
CRC |
4 bytes | CRC32 |
FLAGS |
1 byte | TYP = 0x6 |
SEQ |
1 byte | Number of the packet in the requence |
DATA |
up to 26 bytes | Part of encrypted Key Data |
Packets are sent one by one, without confirmation for the receiving end. If a packet is received out of order, or same packet received twice, the receiver should just drop the request and switch to default channel / default address.
If the last packet is received in order, the receiving party should:
- Ensure the Signature
PUB
matches the one stated in0x5 Key Negotiate
. - Decrypt the encrypted Key Data with Private key of the current device
If the RSA key was successfully decrypted, the Key Data should be stored in RAM by DEVICE_ID
.
Regardless if key was delivered or not, bot device should switch back to the default address / channel.
Key Data should be automatically invalidated in reasonable amount of time.
If the two devices have exchanged the Public Keys (0x3 Get Public Key
/ 0x4 Public Key
) and negotiated on Key Data (0x5 Negotiate
/ 0x6 Key Data
) they might open a request exchange.
Kind | Length | Description / Value |
---|---|---|
CRC |
4 bytes | CRC32 |
FLAGS |
1 byte | TYP = 0x7 |
DEVICE_SRC |
5 bytes | ID of the current device |
DEVICE_TGT |
5 bytes | ID of the target device |
KEY_CRC |
4 bytes | CRC32 of a Key Data + sender's DEVICE_ID |
REQ_CRC |
4 bytes | CRC32 of encrypted Request Body |
REQ_SZ |
1 byte | Size of the Request Body in chunks of 128 bit |
If the target device has negotiated on Key Data, it should switch to channel NG_CH
, listen on RCV_ADDRESS
from SND_ADDRESS
. If the target device has not yet negotiated with the current device, it should ignore this message.
The currenct device should also switch to channel NG_CH
, listen on SND_ADDRESS
for message 0x8 Proceed
for short time limit. If the device does not receive a 0x8 Proceed
confirmation (or receives one that does not compute) within the time window, it should several retries, and then discard the current negotiated Key Data and start the 0x5 Key Negotiate
/ 0x6 Key Data
process again.
If a device received a 0x7 Request
request, and the current device has negotiated the Key Data with the sender device, the device should:
- Validate the CRC32 of the
KEY_CRC
with CRC32 of a negotiated Key Data + current device'sDEVICE_ID
- Calculate the CRC32 of a negotiated Key Data + target address
DEVICE_ID
- Respond back with this message
Kind | Length | Description / Value |
---|---|---|
CRC |
4 bytes | CRC32 |
FLAGS |
1 byte | TYP = 0x8 |
PR_CRC |
4 bytes | CRC32 of a Key Data + IV + receiver's DEVICE_ID |
After receiving this message, the original sender of 0x7 Request
should start sending the request body 0x9 Request Body
within specific time window.
After receiving the 0x8 Proceed
message, the original sender of 0x7 Request
should:
- Validate the
PR_CRC
of the0x8 Proceed
message. - Encrypt the body with AES key /
IV
(actually it whould be encrypted way back at0x7 Request
because ofREQ_CRC
) - Start sending the request body within specific time window.
Because of length of the encrypted data (REQ_SZ
* 16, up to 256 bytes), it should be delivered sequentially.
Kind | Length | Description / Value |
---|---|---|
CRC |
4 bytes | CRC32 |
FLAGS |
1 byte | TYP = 0x9 |
SEQ |
1 byte | Number of the packet in the requence |
DATA |
16 bytes | 128 bits of encrypted data |
Packets are sent one by one, without confirmation for the receiving end. If a packet is received out of order, or same packet received twice, the receiver should just drop the request and switch to default channel / default address.
After delivering the message, the sender should wait on same channel / address for 0xA Response Body
If a device received a 0x9 Request Body
request, and the current device has negotiated the key with the sender device, the device should:
- Decrypt the received Request Body with RSA key / Initial Vector (
IV
) - Process the request
- Send this message back
Kind | Length | Description / Value |
---|---|---|
CRC |
4 bytes | CRC32 |
FLAGS |
1 byte | TYP = 0xA |
RSP_CRC |
4 bytes | CRC32 of encrypted Response Body |
RSP_SZ |
1 byte | Size of the Response Body in chunks of 128 bit |
After receiving this message, the original sender of 0x9 Request Body
should listen form following messages 0xB Response Body
within specific time window.
After receiving the 0xA Response
message, the original sender will follow up with this message.
Because of length of the encrypted data (REQ_SZ
* 16, up to 256 bytes), it should be delivered sequentially.
Kind | Length | Description / Value |
---|---|---|
CRC |
4 bytes | CRC32 |
FLAGS |
1 byte | TYP = 0xB |
SEQ |
1 byte | Number of the packet in the requence |
DATA |
16 bytes | 128 bits of encrypted data |
Packets are sent one by one, without confirmation for the receiving end. If a packet is received out of order, or same packet received twice, the receiver should just drop the request and switch to default channel / default address.
After receiving the last message of sequence, the receiver should validate the RSP_CRC
of the 0xA Response
message, then decrypt the Response Body, process it.
After delivering the response, both the sender and receiver should switch back to the default channel / default address.