NB: this document relies on certain terms and concepts introduced in SimpleIoT Protocol Stack document, please make sure to read it before proceeding.
SimpleIoT/SP (SimpleIoT Security Protocol) aims to provide security guarantees for communications within SimpleIoT environments, in particular, message confidentiality, message integrity guarantees, and protection from replay attacks.
- SimpleIoT Security Protocol (SimpleIoT/SP)
- 1. Definitions
- 2. Security choices
- 3. Security Guarantees
- 4. Scenarios
- 5. SP padding
- 6. SP state
- 7. Events
- 8. Event processing
- 9. Payload Size and SP Packet Size
- 10. Implementation notes
1.1. Packet. A unit of data exchange with other levels/protocols. For the sake of clarity two types of packets are distinguished:
- HLP packet: a packet that is sent to or received from a higher-level protocol. HLP packet data is a payload for SimpleIoT/SP, as it will be discussed in more details below.
- SP packet: a packet that is formed by SimpleIoT/SP and is sent to or received from the communication peer (using an underlying protocol).
- Internally valid SP packet: a packet that has passed authentication based solely on packet data (see also “intra-packet authentication”).
1.2. SP Packet structure
SP packet structure looks as follows:
| SP Header | Security Tag | Encrypted Data |
- SP Header is a non-encrypted part of the packet that contains flags and certain bits of the packet nonce. Header takes 6 bytes.
- Security Tag: data related to encryption and authentication process. Security Tag takes 16 bytes.
- Encrypted Data: encrypted data, which includes certain SimpleIoT/SP information as well as SimpleIoT/SP payload. The same data before encryption (or after decryption) is referred to as “Data Under Encryption
1.3. Packet Nonce: all data used as a packet nonce for purposes of encryption/authentication. PFN consists of:
Nonce Varying Part (Nonce VP): a fixed-size bit sequence uniquely generated by a sending device for each new packet; Nonce VP is 47 bits; as defined herein, in certain contexts it can be treated as a(n unsigned) integer. It can serve as a part of Packet ID when such ID is required.
- Destination Flag: a bit that indicates whether the packet is intended solely to SimpleIoT/SP itself (such as a packet with Error “Old Nonce” Message), or for its higher level protocol. Values of the flag have the following meaning:
- 0: packet is intended for a higher level protocol
- 1: packet is intended for SimpleIoT/SP itself
Peer-Distinguishing Flag: a bit that is set to 0 for one communication peer and to 1 for another peer.
Nonce VP and Destination Flag must be packet as a 6-byte sequence b0 | b1 | ... | b5 in the order of increasing of their addresses in memory. Then the numerical value of Nonce VP is calculated as follows: (uint48)b0 + ((uint48)b1)<<8 + ((uint48)b2)<<16 + ((uint48)b3)<<24 + ((uint48)b4)<<32 + (((uint48)b5)&0x73f)<<40, and the value of Destination Flag is calculated as b5>>7.
1.4. Packet ID (PID): a unique identifier of a packet, when such ID is required.
PID is formed using Nonce VP and Peer-Distinguishing Flag and must be packet as a 6-byte sequence b0 | b1 | ... | b5 in the order of increasing of their addresses in memory so that b0 = (uint8)(Nonce_VP); b1 = (uint8)(Nonce_VP >> 8); b2 = (uint8)(Nonce_VP >> 16); b3 = (uint8)(Nonce_VP >> 24); b4 = (uint8)(Nonce_VP >> 32); b5 = (uint8)(Nonce_VP >> 40) | (Peer_Distinguishing_Flag << 7);.
1.5. Nonce Lower Watermark (NLW): a value supported by a packet receiving side that is used to determine whether a value of Packet Nonce VP (i) has never been used before (if a new packet is received); (ii) has been used with the last received packet (for instance, in case of packet resending); or (iii) a de-synchronization in communication has happened.
1.6. Nonce to use For Sending (NFS): a value supported by a packet sending side that is used to generate a value of Packet Nonce VP that would have never been used before, and that would be verifiable by the communication peer.
1.7. Last Received Packet Signature: [TODO: check whether it is indeed required]
1.8. Packet validation process: a core task of SimpleIoT/SP. Main purpose of the packet validation is to ensure that a packet is actually received is from an intended communication partner, is not modified by a third party on the way, and its content (unless specified otherwise) is protected from reading by not indented parties. On the sending side of communication the packet validation process results in encryption and adding authentication data. On receiving side a process can logically be divided into two steps:
- intra-packet authentication, which is done using solely packet data such as respective headers, nonces, tags, etc, and not using NLW;
- in-sequence authentication, which is based on comparison of a packet nonce Varying Part with the Nonce Lower Watermark.
1.9. Error “Old Nonce” Message: a packet that represents an “old nonce” error report with the lowest possible value of a valid nonce VP (which is equal to a current value of Nonce Lower Watermark plus 1). This packet can be sent, if an otherwise valid packet is received with an “old” nonce VP, that is, with a nonce VP that is less than the Nonce Lower Watermark.
The core of SimpleIoT/SP is packet encryption/decryption and authentication. These processes are based on EAX algorithm (see [EAX]). Design choices with respect the above-mentioned algorithm are:
Encryption method: AES-256
Tag size: 128 bit
EAX Nonce size: 49 bit, consisting of:
- Nonce Varying Part: 47 bit 
- Destination Flag: 1 bit
- Peer-Distinguishing Flag: 1 bit
To reduce the amount of data transferred, Peer-Distinguishing Flag is not actually transferred but just appended to the packet header that actually contains only Nonce Varying Part and Destination Flag to get a Packet Full Nonce:
SP Header: 48 bit, consisting of:
- Nonce Varying Part: 47 bit
- Destination Flag: 1 bit
Rationale: In order to use the same encryption key in both directions of communication each nonce should be unique for packets going in both directions, too. Uniqueness of the nonce going in a particular direction is enforced by packet sender (using nonce VP generation based on NFS). To separates sets of nonces generated by each of two communication peers, a separate bit in the nonce value (Peer-Distinguishing Flag) is used to distinguish between peers so that this bit is set for all nonces generated by one peer and is not set for nonces generated by the other peer. Which peer should have this bit set can be determined, in particular, during set up of communication between two specific devices (for instance, at the same time when encryption key exchange is done), or can be a predefined choice for some types of the devices, if devices of different type participate in communication (for instance, in communication of a Master device with a Slave device Master device may always have the flag set, and Slave device may always have the flag not set).
|||If 47 bit nonce VP is used, then different nonces will be enough for 10 years with packet frequency of 2.25 mks: 10*365*24*60*60*1000000/2^47 = 2.25|
Security of SimpleIoT/SP relies on security of EAX, which is proven as long as underlying cipher (AES128) is secure, and as long as nonces are unique per key.
Within SimpleIoT/SP, keys MUST be unique for each communication pair, and uniqueness of nonces for the pair is guaranteed by:
- Peer-Distinguishing Flag
- for packets sent by each peer, by “Nonce to use for Sending” (NFS)
EAX as such doesn’t guarantee protection from replay attacks, however as nonces are unique, replay attack is not possible as long as SimpleIoT/SP drops packets with repeated nonces. SimpleIoT/SP does drop packets with repeated nonces, with the following exception:
- Error “Old Nonce” Message. For ‘Error “Old Nonce” Message, SimpleIoT/SP does not check the nonce (this is necessary to avoid potential deadlocks). However, replay attack based on these messages is not possible, because SimpleIoT/SP does not allow NLW to decrease, and therefore all replay packets will be ignored by SimpleIoT/SP.
Therefore, SimpleIoT/SP is secure (because of EAX and AES128 being secure) and also provides protection from replay attacks.
Two devices, A and B, participate in packet exchange. Each packet sent is encrypted and authenticated in a way to both guarantee packet integrity and protect from replay attacks. Each packet received has a respective authentication data. Correspondingly, when an HLP packet is being prepared for sending, it is encrypted by an encryption key known to both communication peers, and authentication data is added. It is important that a nonce used for encryption/authentication could be recognized as such (that is, as a value actually used once) by the other communication peer. This is achieved by using Nonce to use For Sending (NFS) on the sending side and Nonce Lower Watermark (NLW) on receiving side.
To avoid replay attacks nonces are commonly used to distinguish between an original message and a message with otherwise the same content that is being replayed. A problem with nonces is to check that a particular value is actually new and has not yet been used ever before. To address this problem, SimpleIoT/SP treats VP of nonces as numerical values and compares a nonce VP from a received packet with a current value of the NLW. If the value of nonce VP is greater than a current value of the NLW, the nonce is considered as new; in this case the value of NLW is set to the value of the nonce VP, and its reuse becomes impossible.
To be economical with the set of values that are greater than a current value of NLW (within a certain range), it is desired that a value of a new nonce VP received be as close (from above) to NLW as possible, ideally, greater by 1. NFS is used to keep track of nonces on the sending side. Initially (for example, at the same time when secret keys are exchanged between the sides) communication partners set NLW on receiving side to the same value as NFS on sending side (namely, NLW = 0, and NFS = 0). Before a new packet is being sent, NFS is incremented, and packet nonce VP is set to a value of NFS. On the receiving side, upon reception of the packet, the value of NLW will become the value of the nonce VP, that is, again equal to NFS on the sending side. The process may be continued until all space of NFS/NLW values is exhausted.
TODO: Nonce Exhaustion/Overflow handling
If a packet is internally valid, but its nonce VP is less than or equal to a current value of NLW, it may indicate that states of the communication peers are out of sync (and not necessarily that a third party attack is detected). In this case, to resynchronize communication process an Error “Old Nonce” Message is formed with the lowest possible acceptable nonce VP, and a packet with this message is sent to a communication partner.
If an Error “Old Nonce” Message is received, the receiving party compares its NFS with the lowest possible value of the nonce within the message, and if NFS is less that value, NFS is set to the value as specified in the message; using such a value of NFS for sending packets will ensure that the packet will pass NLW test at the receiving party.
TODO: exact format of ‘Error “Old Nonce” Message’
SimpleIoT/SP data under encryption is organized as follows:
| First Byte | (opt) complementary size | byte sequence | (opt) padding |
First Byte is a 1 byte field that is treated as follows:
- MSB bit: padding size flag, which is set to 1, if padding is present, and 0 otherwise. Presence of padding implies presence of padding size field as well.
- Remaining 7 bits: a part of payload.
complementary size: SimpleIoT Encoded-Unsigned-Int<max=2> variable-size field, as described in SimpleIoT Protocol Stack; this field is present only if padding size flag is set; in this case the field contains encoded value of a sum of the size of this field and the size of padding (if any). If Encoded-Unsigned-Int has an invalid value (as defined in SimpleIoT Protocol Stack), then SimpleIoT/SP receiving side MUST treat such a packet as an invalid (as the one which didn’t pass internal validation). Note: unless “enforced padding” (see below) is used, SimpleIoT/SP pads data only to the block size; it means that unless “enforced padding” is used, padding size is always <= 15, and therefore Encoded-Unsigned-Int cannot be longer than 1 byte.
byte sequence: variable size field; data that is defined by a higher level protocol.
padding: variable size field; this field is present only if padding size flag is set and complementary size represents a value greater than 1; contains padding up to a target size.
Correspondingly, SimpleIoT/SP payload consists of:
- Remaining 7 bits of the First Byte
- byte sequence
Higher-level protocol is free to use “partial byte” (7 bits) of SimpleIoT/SP payload, or to ignore it; however, this “partial byte” might be useful, for example, to store some bitflags of higher-level protocol, which may allow to save 1 byte of payload.
SimpleIoT/SP padding data MUST be generated using Non-Key Random Stream as described in SimpleIoT Random Number Generation and Key Generation.
In certain scenarios, some information might be extracted from the packet length even though information is encrypted. To support the cases when this is important, SimpleIoT/SP supports a concept of “enforced padding”, which works as follows:
- When sending an HLP, a high-level protocol is allowed to specify enforce-pad-to. For each packet length len, SimpleIoT/SP guarantees that for all the HLPs which have their own size= len and are sent without enforced-pad-to, or which are sent with enforced-pad-to = len, the length of SimpleIoT/SP packet is exactly the same (therefore, preventing any length-based information leak).
To implement it, on receiving such a request SimpleIoT/SP MUST do the following:
- check that enforce-pad-to is greater or equal to the size of packet itself. TODO: specify what to do if it is not (probably different for Master and Slave)
- calculate required-size, the size of the SimpleIoT/SP packet which an HLP with a size of enforce-pad-to would produce
- calculate the size of enforced-padding for current packet (so that SimpleIoT/SP packet produced from current packet, would have size= required-size)
- pad packet, using calculated enforced-padding, and producing ‘enforced-padded’ SimpleIoT/SP packet
TODO: specify handling of enforce-pad-to for the layers between SimpleIoT/SP and SimpleIoT/CCP.
For its operations SimpleIoT/SP keeps the following state on both sides of communication:
- Nonce Lower Watermark (NLW)
- Nonce to use For Sending (NFS)
There are three events that SimpleIoT/SP processes:
- receiving a SP packet from the communication peer
- receiving a packet from a higher level protocol (HLP packet)
- receiving a request from a higher level for nonce variable part
A packet from a higher level protocol is received together with a nonce VP. After a received nonce VP is ensured to be numerically greater than NLS, this packet is encrypted and authentication data is added using a new nonce based on a received nonce VP, a resulting SP packet is to be passed to the communication peer (using underlying protocol).
An SP packet from the communication peer is received (via underlying protocol). The packet can be:
- valid new packet, which means that the packet data passed validation process, and packet nonce VP is greater than the Nonce Lower Watermark;
- old-nonce packet, an otherwise valid packet with a nonce VP less than the Nonce Lower Watermark, which means either de-synchronization in communication, or an attack attempt
- packet with Error “Old Nonce” Message (intended for SimpleIoT/SP itself)
- invalid packet, in particular, corrupted, an attacker’s packet, etc.
Further details of event processing are placed below.
A packet from a higher level protocol is received together with a nonce VP. Nonce VP is compared to the current value of NFS.
- Nonce VP is less than or equal to NFS: no processing is done and an error is reported [TODO: should we provide more details on what such error should result in]
- Nonce VP is greater than NFS: NFS is set to the value of nonce VP; HLP packet is encrypted and authenticated using a new nonce based on a received nonce VP to form an SP packet. This SP packet is sent to the communication peer using underlying protocol.
On receipt of a SP packet, first, an intra-packet authentication is performed as follows:
if intra-packet authentication has failed: the packet is silently dropped as being either corrupted or an attacker’s packet;
if intra-packet authentication is passed: it can be either an error message packet directed to SP itself, or a “regular” packet with payload intended for a higher level protocol.
if a packet is with Error Old Nonce Message [+++structure and detection]: packet nonce VP is not compared to NLW (reason: replay attack is impossible since NFS cannot be decreased as a result of this message, and performing comparison may lead to a deadlock); a value of the lowest possible valid nonce from the packet is compared to the current value of NFS.
- if NFS is less than the value of the lowest possible valid nonce: NFS is set to the value of the lowest possible valid nonce.
- if NFS is greater than or equal to the value of the lowest possible valid nonce: no changes to NFS is done; the packet is ignored.
if packets other than Error Old Nonce Message: packet nonce VP is compared to the Nonce Lower Watermark (NLW). Three cases are possible:
- if nonce VP is less than or equal to NLW: a packet with Error Old Nonce Message is prepared with the lowest possible valid nonce set to a current value of NLW; the packet is authenticated and sent to the communication peer.
- if nonce VP is greater than NLW: a new packet is received: NLW is set to the value of nonce VP of the received packet; LRPS is set to packet signature [TODO: check whether we use it elsewhere]; an HLP packet with payload of the received packet is passed to the higher level protocol together with the nonce VP of the packet nonce.
TODO!: sending packets (encryption etc.)
As SimpleIoT/SP is using 48-bit (= 6 bytes) nonce, a block cipher (AES128) with a block size of 128 bits (=16 bytes), and tag size is chosen as maximum 128 bits, it means that SimpleIoT/SP packet size is always (6+16+k*16)=(22+k*16), where k >= 1.
The following table shows relations between SP packet sizes and SP payload  not including “remaining 7 bits” part (that is, a size of byte sequence part only):
|SimpleIoT/SP packet size, bytes||SimpleIoT/SP payload, bytes|
|38||7bits+0bytes to 7bits+15bytes|
|54||7bits+16bytes to 7bits+31bytes|
|70||7bits+32bytes to 7bits+47bytes|
|86||7bits+48bytes to 7bits+63bytes|
|102||7bits+64bytes to 7bits+79bytes|
|118||7bits+80bytes to 7bits+95bytes|
|||Note that SimpleIoT/SP payload is not the same as, say, SimpleIoT/GDP payload or SimpleIoT/CCP payload: for example, if SimpleIoT/GDP lies right on top of SimpleIoT/SP, then SIoT_GDP_Payload_Size = SIoT_SP_Payload_size - Size_of_SIoT_GDP_Headers.|
For SimpleIoT/SP security, it is critical that nonces are never re-used and are always incremented (never going back). Therefore, implementation MUST enforce it (both for sending side and for receiving side).
Basic secure implementation is rather simple:
- Whenever a new packet is sent, an update value of NSF MUST be saved and committed in in persistent storage; this commit MUST be performed before the packet is actually sent over the air. This is necessary to keep EAX security guarantees.
- Whenever a packet with status “new” is received, an updated value of NLW MUST be saved and committed in persistent storage; this commit MUST be performed before further message processing. This is necessary to avoid using an obsolete value of NLW in case of “dirty” reboot (and thus to avoid a potential for replay attacks).
In cases where basic secure implementation is too resource-intensive (causing too many writes to persistent storage, which can be undesirable, in particular for EEPROM), the following optimizations MAY be used without affecting security; note that implementation described below are ok if and only if all of the steps are implemented (or none is implemented, falling back to the basic schema described above): [TODO: check that boundary handling (‘<’ vs ‘<=’ etc. etc.) is described correctly]
- On program start:
- both NSF and NLW are read from the persistent storage, and stored into the RAM (as ‘Current_NSF’ and ‘Current_NLW’ respectively).
- both NSF and NLW in persistent storage are incremented by a certain value DELTA; this change MUST be committed to persistent storage before any further processing. The value of DELTA can be, for example, 100; DELTA SHOULD NOT be too large, as having it too large, combined with frequent “dirty” reboots, may cause exhaustion of nonce space.
- These incremented values are also stored in RAM (as ‘Last_NSF’ and ‘Last_NLW’).
- Whenever a new value of NSF is needed (for the reasons stated above), if ‘Current_NSF’ is less than ‘Last_NSF’, then new value of NSF is taken as ‘Current_NSF’ and ‘Current_NSF’ is incremented in RAM. This is ok from security perspective, because in case of “dirty reboot” NSF will be still increased, and never repeated.
- Whenever a new value of NSF is needed (for the reasons stated above), and if ‘Current_NSF’ is greated or equal than ‘Last_NSF’, then:
- NSF in persistent storage is incremented by DELTA (or other similar value); this new value MUST be committed to persistent storage before proceeding further
- ‘Last_NSF’ is set to new value of NSF in persistent storage
- ‘Current_NSF’ is returned as the new NSF value, and then incremented
- Whenever a new value of NLW is needed (for the reasons stated above), if ‘Current_NLW’ is less than ‘Last_NLW’, then new value of NLW is taken as ‘Current_NLW’ and ‘Current_NLW’ is incremented in RAM. This is ok from security perspective, because in case of “dirty reboot” NLW will be still increased, and never repeated. Using such policy for NLW might cause an extra ‘Error “Old Nonce” Message’, but this situation will be quickly recovered from.
- Whenever a new value of NLW is needed (for the reasons stated above), and if ‘Current_NLW’ is greated or equal than ‘Last_NLW’, then:
- NLW in persistent storage is incremented by DELTA (or other similar value); this new value MUST be committed to persistent storage before proceeding further
- ‘Last_NLW’ is set to new value of NLW in persistent storage
- ‘Current_NLW’ is returned as the new NLW value, and then incremented
Whenever an actor-implementing-SimpleIoT/SP (such as “SimpleIoT Client” or “SimpleIoT Device”) is restored from backup, it MUST take care to avoid duplicate nonces, in particular:
- amount of time dT (in seconds) between backup and restore MUST be calculated
- if dT is less than min-backup-restore-time, it MUST be set to min-backup-restore-time; normally min-backup-restore-time should be set to a value such as 24 hours.
- if dT is larger than max-backup-restore-time, restore SHOULD be interrupted, the problem SHOULD be explained to the person who’s performing restore, and confirmation SHOULD be obtained before proceeding. This is intended to prevent restores with erroneous clock, which might lead to the erroneous exhaustion of the nonce space. Normally, max-backup-restore-time should be set to a value such as 30*24 hours.
- both NLW and NSF, as stored in persistent storage, MUST be increased by a number equal to: dT*max_number_of_packets_per_second. This increased number MUST be stored and committed to persistent storage before proceeding further. Here, max_number_of_packets_per_second is a constant estimating maximum feasible number of packets which might be sent per second; in general, it depends on the higher-level protocols, but for basic SimpleIoT/CCP it usually can be taken between 100‘000 (1e5) and 1‘000‘000 (1e6).