2

I am currently trying to harden communication over a limited physical communication channel on an embedded system.

The Scenario

I think the system is rather typical for small embedded applications:

  • The embedded systems involved are small microcontrollers without any OS, which can be assumed to be in a secure physical environment. The connection channel is physically accessible to aggressors, they can interfere with messages as desired.

  • Different communication channels are possible: e.g. RS485 or RS232. There is a crude protocol which handles stuff like addressing and crude integrity (e.g. CRC16 for bitflips) on which we can build. Think of it as being a slow TCP.

  • Each communication partner knows a root certificate which can act as a CA and has an own unique certificate(+key) signed by it. Certificates are self signed, elliptic curve prime256v1

  • The mbedTLS library is available for these systems (with DH, AES, ..).

  • Most of the time there is no internet available.

  • The systems can keep time and can create sufficient random numbers

I want to reach privacy, authenticity and integrity.

How it might be solved

A rule in cryptographic protocols is to never implement them on your own. Unfortunately I did not find anything that matches this use case.

I also browsed through existing questions, but nothing provided the full solution i was searching for.

Finally in this answer implementing an own protocol is given as the last option.

This is why I came up with the solution described below - but I am not entirely sure about it. I oriented on this answer on how to authenticate a Diffie-Hellmann Key Exchange, this answer on how to check integrity and this thread giving me some ideas on how to implement such a protocol.

Protocol to secure communication

First thing to do is to randomly generate a symmetric key K. First thing sent is a handshake message. It has the following fields:

Field Description
DH The Diffie-Hellmann key exchange msg gK
Cert The certificate(-chain) of the sending device
MsgIdNonce A random 64-bit value
Signature A signature of the complete message using the private key of Cert

Both devices send (and receive) such a message.

When a message is received the following is done:

  1. Check if cert is signed by the expected CA (has to be in chain)
  2. Check if msg is properly signed with cert and its key

When these checks succeed the sent and received DH gK messages are used to calculate a symmetric key.

From this point on, applications can send messages encrypted with AES-CBC. As messages could be lost relatively often, IV is always in the message.

This is the message format:

Field Description Encrypted
IV Initialy Random IV for AES No
MsgIdNonce Handshake/Last MsgIdNonce + 1 or + 2 Yes
Application Data The data/commands to protect Yes
CMAC truncated to 64 Bit MAC for checking integrity Yes

For a message to be accepted it must

  1. Have the expected MsgIdNonce (1 or 2 above last received one) - preventing replay attacks, allowing to lose 1 message
  2. Have a valid CMAC - preventing attacks on integrity
  3. Be received max. T seconds after the last message - preventing delay attacks

If anything is not as expected, Handshake is sent again, then message is sent again with newly exchanged parameters.

Questions

Am I on the right path or should I try something completely different?

Does this provide my security goals or do I miss any obvious vulnerabilities here?

Can this work?

I hope you can help me and other embedded engineers facing similar problems!

mile4712
  • 23
  • 3

1 Answers1

3

A rule in cryptographic protocols is to never implement them on your own. Unfortunately I did not find anything that matches this use case.

Have you considered DTLS? That tries to address the same problem (except for time stamps; that could be added by inserting the time stamp in the datagram), and already has things thought through. Another possibility would be IPsec; however DTLS looks closer to addressing the problem you have (unless the data traffic you're protecting is IP traffic).

That said, here are some thoughts on the protocol you outlined:

  • What happens if two sequential messages are dropped (e.g. received incorrectly)? Will the two sides realize what happened, or will they proceed to drop all subsequent messages (because of the 'nonce has to increment no more than 2' rule)?

  • Presuming they attempt a rekey, how is this coordinated? If one side thinks they haven't rekeyed yet, and the other side thinks they are, what will happen to messages encrypted with the old key? We find it generally useful to include a 'which key this is encrypted under' tag with the ciphertext (in DTLS, this is the 'epoch').

  • Can your communication channel reorder messages? I suspect it can't in your case, but if it can, you should think about how that should be handled.

  • What happens if someone injects a previous (valid) handshake message? How will that confuse things?

  • In the data encryption path, you use CBC encryption and CMAC integrity. While this is a workable solution, this is prone to various padding attacks if they aren't integrated correctly. Current fashion is to use an AEAD mode (such as GCM or Chacha20/Poly1305) which does both.

  • Is the encrypted traffic unidirectional or bidirectional? If traffic goes both ways, do you use the same set of keys to protect both sets of traffic?

  • "If anything is not as expected, Handshake is sent again, then message is sent again with newly exchanged parameters." - how does that work? If the receiver decided it didn't like the message, how does the sender know to resend it?

poncho
  • 147,019
  • 11
  • 229
  • 360
  • I will take a look at DTLS. Up to now I only thought of TLS, but that did not seem to work with my transport. Mbedtls makes it easy to change the transport to UART/RS485, I will have to evaluate how this works out in practice. – mile4712 Jan 05 '21 at 08:01
  • If I can't use it for some reason I will work your questions in and leave it here. Here are my first thoughts:

    Seems like i was trying to build a AEAD mode myself! - I would change that to GCM.

    The channel is bidirectional, and I would use the keys for both directions. MsgIdNonce is valid only for 1 Direction.

    – mile4712 Jan 05 '21 at 08:01
  • Applications have to handle losing messages every once in a while - so two sequential messages dropped should resend a handshake and new messages with old key would be lost. As you noted, receivers can't retrigger a resend - so that message would be lost as well.

    For rekeying I would send a Handshake whenever I received one - and lose messages with the old key.

    Injecting an old handshake message would result in broken communication (MsgIdNonce would not fit for proper messages), which would result in a new Handshake.

    You are right, this channel cannot reorder messages.

    – mile4712 Jan 05 '21 at 08:02
  • @mile4712: if you do use GCM with the same key in both directions, you should take some thought as to how to make sure that both sides don't send a message with the same nonce; it can be easy (e.g. one side uses even nonces, the other side uses odd ones), however it should be done. On the other hand, GCM doesn't need randomness for its nonces; you can use a simple counter to generate them – poncho Jan 05 '21 at 17:51
  • 1
    @mile4712: the other thing to consider (which I forgot to mention) is "what happens if both sides send out initial handshake messages at about the same time? Is there a potential race condition there that can confuse things?" Depending on how you structure things, it might be benign; however you should think this through – poncho Jan 05 '21 at 17:54
  • DTLS 1.2 is able to handle "message drops", it doesn't require new handshakes for that. This is done by sending a explicit record sequence number. – Achim Kraus Jan 07 '21 at 08:40
  • @poncho:

    I thought that I would just use a random nonce for each direction, so they are not equal. I thought of replacing it with a counter, but I am not quite sure on how to do that without introducing different roles for the communication partners - at the moment I do not need any roles.

    I don't think that both messages sent simoultaneosly are a problem if the implementation sychronizes outgoing and incoming handshakes. So from a conceptional point of view it should work.

    – mile4712 Jan 11 '21 at 15:28
  • @AchimKraus: In my understanding this is similar to my MsgIdNonce being allowed to be 1 or 2 above the last one. Allowing a range [1,n] above the last one would allow to handle up to (n -1) message drops without a new handshake. – mile4712 Jan 11 '21 at 15:32
  • The record seqn doesn't limit the drops. The gap may be larger. Only "delays" and "reorders" must fit in a "window", e.g. if one message get's delayed on its route for a couple of seconds but other messages afterwards makes it to the endpoint, then that window came into play. e.g. if the size is 32, then the delay message will be either still received, if not more than 32 other messages are received, or dropped, because it's out of that window. – Achim Kraus Jan 11 '21 at 18:55
  • @mile4712: using a counter for both directions does not mandate different roles; you could use the convention that (for example) the party with the smaller DH value $g^k$ use even nonces, while the one with the larger DH value uses the odd ones. – poncho Jan 11 '21 at 20:21
  • @AchimKraus If I understand you right, I could achieve that without changing the message structure by allowing the MsgIdNonce to be in a specific window. – mile4712 Jan 13 '21 at 10:54
  • @poncho That is a good idea, I will work that in! – mile4712 Jan 13 '21 at 10:55
  • @mile4712 I only know DTLS 1.2 in deep. I'm not really aware of your plans/ideas. In my personal opinion, it doesn't payoff to invent something new, without prior understanding, what is already there. So, I only try to explain, what DTLS is offering, not if this ispossible with your proposal. Hope, I don't created a too wrong impression, – Achim Kraus Jan 13 '21 at 14:24
  • @AchimKraus Ok I misunderstood that. I am indeed learning a lot from this discussion and reading into DTLS. I am currently working out things, first of all I will evaluate DTLS on my system. Getting this running would be the best solution I think. I am currently looking into a solution where I can make it role-less, like discussed above. In my application it is undefined who initiates the connection. If this for some reason does not work out, I will switch to the protocol discussed here. – mile4712 Jan 13 '21 at 16:30
  • @mile4712 "role-less" is in DTLS sometimes called "roll-exchange". Unfortunately, the most libraries, which are derived from TLS (like mbedtls), don't support that. AFAIK, only Eclipse/tinyDTLS (C) and Eclipse/Californium (Java) have that implemented. I'm also not sure, for what the CA/x509 is used? Maybe RFC7250 offers with RPK a different starting point. That mainly uses the public-key itself. – Achim Kraus Jan 13 '21 at 19:04
  • "role-exchange" not "roll ..." sorry for the typo. – Achim Kraus Jan 14 '21 at 10:58
  • @AchimKraus I can't switch the library, but maybe I can decide on the roles automatically without using a DTLS role-exchange. There are some application specifics that could allow to do that.

    I will look into RFC7250 in practice, at first sight it seems like it really could relieve the slow transport channel when doing the handshake!

    – mile4712 Jan 14 '21 at 14:16