Post

#22 Guarantee Forward Secrecy

The default configuration of an ESP32 project does not guarantee perfect forward secrecy(PFS) of your TLS communication. In this post we’ll cover what forward secrecy is, why you should care about it, and how to modify your ESP32 project to guarantee it.

Before we proceed, it’s important to note that none of what you are about to read matters if your projects doesn’t use TLS. If you are connecting to services over HTTP instead of HTTPS, please fix that first and then come back to level up even further.

What is Forward Secrecy?

Forward secrecy, also known as perfect forward secrecy(PFS), involves the secret key exchange portion of a TLS connection. Without it, a bad actor is able to do the following:

  1. Record all of your raw, encrypted device traffic
  2. Find a way to compromise your private key
  3. Decrypt ALL previously recorded traffic as well as new traffic

With forward secrecy, if a bad actor gets your private key, they are only able to decrypt future traffic. All historical traffic is safe.

Forward secrecy protects past sessions against future compromises of keys or passwords.

Why Do I Care?

It is well known that bad actors, including governments are harvesting vast amounts of network traffic. Many people think they are safe because their devices use TLS. So those bad actors shouldn’t be able to decrypt and see that data. That is true…today. But what about in 2, 5, or 10 years from now? What happens if that bad actor is recording all of that encrypted data and then, in the future, is able to compromise your private key. This used to seem impossible but with quantum computers the job will be fairly trivial in the not-too-distant future. It’s even possible today using simple social engineering attacks. This is known as a harvest now, decrypt later attack.

This is especially important if your embedded project is transmitting sensitive information.

The Default Config in IDF

Out-of-the-box, ESP32 projects do not guarantee forward secrecy. Don’t panic, you’re probably still just fine. When your ESP32 connects to a server using TLS it sends a list of supported cipher suites in the order it prefers to use them. The server has it’s own list of supported cipher suites. The two generally agree to use the first match found between them. The cipher suite list is typically sorted from most secure to least secure (this is how MbedTLS does it in case you were curious).

As of this writing, using the latest stable v5.3 branch of IDF with accompanying MbedTLS version 3.6.0, using esp_http_client_perform sends the following cipher suite list to the server.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_256_CCM",
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8",
"TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384",
"TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_CCM",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8",
"TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256",
"TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256",
"TLS_RSA_WITH_AES_256_GCM_SHA384",
"TLS_RSA_WITH_AES_256_CCM",
"TLS_RSA_WITH_AES_256_CBC_SHA256",
"TLS_RSA_WITH_AES_256_CBC_SHA",
"TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384",
"TLS_ECDH_RSA_WITH_AES_256_CBC_SHA",
"TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384",
"TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA",
"TLS_RSA_WITH_AES_256_CCM_8",
"TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384",
"TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384",
"TLS_RSA_WITH_ARIA_256_GCM_SHA384",
"TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384",
"TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384",
"TLS_RSA_WITH_ARIA_256_CBC_SHA384",
"TLS_RSA_WITH_AES_128_GCM_SHA256",
"TLS_RSA_WITH_AES_128_CCM",
"TLS_RSA_WITH_AES_128_CBC_SHA256",
"TLS_RSA_WITH_AES_128_CBC_SHA",
"TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDH_RSA_WITH_AES_128_CBC_SHA",
"TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA",
"TLS_RSA_WITH_AES_128_CCM_8",
"TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256",
"TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256",
"TLS_RSA_WITH_ARIA_128_GCM_SHA256",
"TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256",
"TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256",
"TLS_RSA_WITH_ARIA_128_CBC_SHA256",
"TLS_EMPTY_RENEGOTIATION_INFO_SCSV"

We can easily get this data by having our ESP32 HTTPS client hit the endpoint https://howsmyssl.com/a/check and inspect the JSON result. In this case, if the server supports it, the connection will use the TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 cipher suite.

Using a tool called CipherSuite, we can get some information about that cipher suite. Simply navigate to https://ciphersuite.info/cs/TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384/.

A secure cipher suite A secure cipher suite

We can see that this cipher suite is recommended and, under the Key Exchange section, we see it is labeled PFS meaning it supports perfect forward secrecy. Perfect! No problems here. This is true for the top 24 cipher suites which means almost all connections to services will have PFS. But what if the server doesn’t support that cipher suite? What if it doesn’t support those at the top of the list we sent? What if the first one the server supports is TLS_RSA_WITH_AES_256_GCM_SHA384?

We can get the information on that cipher suite by using the following URL pattern on CipherSuite:

1
https://ciphersuite.info/cs/[CIPHER SUITE IDENTIFIER]

Here is what we get for https://ciphersuite.info/cs/TLS_RSA_WITH_AES_256_GCM_SHA384/:

A weak cipher suite A weak cipher suite

This cipher suite is flagged as “weak” security-wise because the Key Exchange doesn’t support PFS. It gives us a warning of “Non-ephemeral Key Exchange”. So if the server your device connects to prefers this cipher suite your ESP32 will say “Hey, that works for me. Let’s do this!” and you’ve just opened yourself up to a harvest now, decrypt later attack.😞

As a rule, all cipher suites that use the RSA or ECDH key exchange algorithms do NOT provide PFS. All cipher suites that use the ECDHE key exchange algorithm do provide PFS.

Guarantee PFS in Your Project

The question you might ask is “Why does it allow for that by default?” The short answer is compatibility. The more cipher suites your device supports, the more likely it is to successfully connect to the remote server. This is a balance between compatibility and security.

That said, guaranteeing PFS is pretty trivial in practice. All we need to do is remove the key exchange methods that don’t provide PFS. This is done under the Component config->mbedTLS->TLS Key Exchange Methods section in menuconfig. Here are the settings you can add to your sdkconfig.defaults file.

1
2
3
4
CONFIG_MBEDTLS_KEY_EXCHANGE_RSA_PSK=n
CONFIG_MBEDTLS_KEY_EXCHANGE_RSA=n
CONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA=n
CONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_RSA=n

Rebuilding our project we now get the following supported cipher suites:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_256_CCM",
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8",
"TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384",
"TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_CCM",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8",
"TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256",
"TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256",
"TLS_EMPTY_RENEGOTIATION_INFO_SCSV"

If you check each of these using CipherSuite you will find they all have the PFS label. That’s because the ECDHE key exchange algorithm ensures forward secrecy.

You will notice some of these are still listed by CipherSuite as “weak”. This is because some of them use the cipher block chaining (CBC) encryption algorithm which has a known security vulnerability. You can remove these using certain TLS config settings but that is a topic for another post. The good new is, MbedTLS is planning to remove all CBC cipher suites in a future release. That said, all of them guarantee perfect forward secrecy.

Test Test Test

This new configuration guarantees perfect forward secrecy in your TLS connections but it does NOT guarantee your device will still connect to all of the services it did before. With these settings we have drastically reduced the list of supported cipher suites. You should test every single service your device needs to connect to and ensure it still does so without issue.

If your device no longer connects to certain services it needs you have two options.

  1. Find a different service that supports more secure cipher suites
  2. Re-enable some of the cipher suites to get it to connect again and accept that you no longer have PFS

Production Pointers

  • Guarantee perfect forward secrecy by modifying the client list of cipher suites
  • Ensure your devices only connect to services that support PFS
  • After making any cipher suite changes, ensure your device can still connect to all necessary services

Summary

Forward secrecy is critical to the Secure and Deterministic Pillars of Production. Security is obvious here as it prevents a malicious actor from recovering old data. But it’s also deterministic if you follow the suggestions above to guarantee your device won’t fall back to a key exchange method that doesn’t provide PFS.

Join the community and get the weekly Production ESP32 newsletter. Concise, actionable content right in your inbox.

© Kevin Sidwar

Comments powered by Disqus.