Implement a secure ICS protocol targeting LoRa Node151 microcontroller for controlling irrigation.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

6.4 KiB

Protocol

The responder will be the node under control (irrigation controller), and the initiator will be the server.

The responder will always respond with one and only one packet. That is, all timeout mechanisms will be handled by the initiator. If the initiator does not receive a response to it’s query, it will resend the request until it does. It is required that the responder be able to detect this, and resend the last response.

The respondent will cache message responses after the session has been confirmed, but before that, it is unneccessary, as there is no harm to reprocess the messages. The more complicated part is dealing w/ a missed confirmed reply, as the state will need to be back tracked (saved) to decode the repeated confirm request.

Key Negotiation

Where type is to be: shared - shared secret ecdh - Results of an ECDH exchange, key = K || K_A || K_B ecdhe - Ephemeral keys + static keys for PFS

In order to handle a reset and prevent a replay attack, an existing session, if any is maintained till the completion of a reset request aka new key negotiation completes. Only after that, does the old connection key get removed. This does mean that after a reset request, it is required that both sides attempt to decode each packet w/ both states. This is safe as it is assumed that the initiator will not initiate a new key negotiation unless it wants it’s previous session to be removed.

The notation below has an implicite recv on the other side of each message. It is left off for conciseness, but must be done and verified, in the case of static data, like reqreset, before any of their respective operations are done.

shared

Both sides initalize:

meta-AD('com.funkthat.lora.irrigation.shared.v0.0.1')
key(<preshared key>)

The preshared key was agreed/preloaded on both sides. They should be a minimum of 16 bytes, but longer, such as pass phrases are allowed, as the key will be properly compressed by Keccak.

The following exchange is required by protocols that are not PFS safe, that is shared and ecdh (not yet defined). This is to prevent replay attacks and ensure that both sides have integrated random data into the protocol, AND that the connection reset was requested by the initiator. This is described in the second paragraph of § C.2 in the strobe paper.

initiator:

send_enc(<16 bytes random data> + 'reqreset')	# nonce injection
send_mac(8)

respondent:

send_enc(<16 bytes random data>)	# nonce injection
send_mac(8)

both:

ratchet()	# prevent backtracking

initiator:

send_enc('confirm')
send_mac(8)

respondent:

send_enc('confirmed')
send_mac(8)

It seems odd to respond to the confirm message, BUT, as using strobe requires explicit hand off (similar to a token), in order for the initiator to send any commands it needs to be “passed back”.

At this point, the new key/session becomes active.

ecdhe

This is the ECDHE key exchange. This does an ephemeral key exchange w/ a known static keys. It follows a protocol similar to the KK handshake pattern of Noise. For reference, it is:

KK:
  -> s
  <- s
  ...
  -> e, es, ss
  <- e, ee, se

It requires that both sides know the public key of the other side (and obviously their own).

As the strobe code includes an implementation of X25519 (ECDH over Curve25519), that is used. This means the various public keys are all 32 bytes in length.

Both start out:

meta-AD('com.funkthat.lora.irrigation.ecdhe.v0.0.1')
key(<initiator pub key> || <responder pub key>)

This is done to both bind the session to this specific public key pair, but to also hide the negotiation request.

The terminology used is the same as used in Noise’s Overview of handshake state machine.

Note: MixHash calls are unnecessary, as they are inherent in the send_enc operations.

The initiator generates an ephemeral key, e.

initiator:

send_enc(e + 'reqreset')	# ephemeral
send_mac(8)
key(DH(e, rs) + DH(s, rs))

The respondent generates an ephemeral key, e.

respondent:

key(DH(s, re) + DH(s, rs))
send_enc(e)	# ephermal
send_mac(8)
key(DH(e, re) + DH(e, rs))

initiator:

key(DH(e, re) + DH(s, re))
send_enc('confirm')
send_mac(8)

respondent:

send_enc('confirmed')
send_mac(8)

It seems odd to respond to the confirm message, BUT, as using strobe requires explicit hand off (similar to a token), in order for the initiator to send any commands it needs to be “passed back”.

It is easy to see that there are correct matching key operations on each side, and as the key operation ratchets the state, a separate ratchet operation is not needed as in the shared key state.

At this point, the new key/session becomes active.

Command Phase

Once the session has been established, the initiator will send commands of the following format:

Currently the only defined type for args, is a 4 byte integer in little endian format. Additional types may be added later.

The following commands are defined: TERMINATE: 1 -- no args: terminate the session, reply confirms WAITFOR: 2 -- args: (length): enqueues a waits for length seconds RUNFOR: 3 -- args: (chan, length): enqueues a turn on chan for length seconds PING: 4 -- args: (): No op, verify connection is functional SETUNSET: 5 -- args: (chan, val): sets chan to val ADV: 6 -- args: ([cnt]): Advances to the next cnt (default 1) command CLEAR: 7 -- args: (): Clears all future commands, but keeps current running

When the responder receives a command, it will process it, and send back a response w/ the same command byte as an acknowledgment.

Note that the commands WAITFOR and RUNFOR are enqueued on the run queue. If there is not a running enqueued command, it will be started, and only when the current enqueued command finishes will the next one be run.

The other commands are run immediately.

To clear any pending enqueued commands, and end the current command, a CLEAR command followed by an ADV command must be sent. This will clear out any scheduled commands, and then end the current command so that any future scheduled commands will be started, as opposed to waiting till the current scheduled command finishes.

Note: look at: https://monocypher.org/manual/advanced/elligator when doing ECDHE