15  Secure Comms & Firmware

15.1 Learning Objectives

By the end of this chapter, you should be able to:

  • Implement TLS/SSL for secure MQTT communications
  • Configure DTLS for CoAP over UDP
  • Deploy WireGuard VPN for IoT remote access
  • Implement secure boot with firmware signature verification
  • Design secure over-the-air (OTA) update systems
In 60 Seconds

Secure IoT communications rely on TLS/DTLS protocols to authenticate peers and encrypt data in transit, while secure boot and firmware signing ensure that only trusted code runs on devices.

What is Secure Communication? Secure communication ensures that data traveling between devices cannot be intercepted (confidentiality), modified (integrity), or sent by impostors (authentication). It’s like sending a letter in a locked box that only the recipient can open.

Why does it matter for IoT? IoT devices transmit sensitive data (health readings, location, commands) over networks that attackers can monitor. Without encryption, anyone on the network can read your data or inject malicious commands.

Key terms: | Term | Definition | |——|————| | TLS | Transport Layer Security - encrypts TCP connections (HTTPS, MQTTS) | | DTLS | Datagram TLS - encrypts UDP connections (CoAP) | | VPN | Virtual Private Network - encrypted tunnel for all traffic | | Secure Boot | Verifying firmware integrity before execution | | OTA Update | Over-the-Air firmware update (wireless) |

“When I send data to the cloud, it travels through the wild internet!” Sammy the Sensor said nervously. “Anyone could be listening along the way.”

Max the Microcontroller reassured him. “That is why we use TLS – Transport Layer Security. Think of it as putting your data in an armored truck. When Sammy connects to the cloud server, they first do a special handshake where they exchange secret keys. After that, everything they say to each other is encrypted. Even if a bad guy intercepts the traffic, all they see is meaningless jumbled data!”

“For devices that use UDP instead of TCP, like tiny sensors, we have DTLS,” Lila the LED explained. “It is the same protection as TLS but designed for the lighter UDP protocol. And for really secure connections, we can use VPNs – Virtual Private Networks – that create an encrypted tunnel for ALL traffic, not just one connection.”

“Secure boot is another piece of the puzzle,” Bella the Battery said. “Every time Max powers on, he checks that his software has not been tampered with. It is like a doctor checking your temperature before a checkup – if something is off, you stop and investigate. Between TLS for communications, DTLS for constrained devices, VPNs for full tunnel protection, and secure boot for firmware integrity, our data highway is locked down tight!”

15.2 Prerequisites

Before diving into this chapter, you should be familiar with:

  • TLS (Transport Layer Security): A cryptographic protocol that provides authentication, confidentiality, and integrity for data in transit over TCP; the ‘S’ in HTTPS.
  • DTLS (Datagram TLS): TLS adapted for UDP — handles packet loss and reordering, making it suitable for IoT protocols like CoAP that use UDP.
  • mTLS (Mutual TLS): TLS where both client and server authenticate each other using certificates; essential for device-to-cloud IoT authentication.
  • Certificate Authority (CA): A trusted entity that issues and signs digital certificates, allowing parties to verify each other’s identity.
  • Secure Boot: A process that verifies the cryptographic signature of firmware before execution, preventing unsigned or tampered code from running on a device.
  • VPN (Virtual Private Network): An encrypted tunnel over an untrusted network, used to secure communication between IoT gateways and cloud back-ends.
  • Perfect Forward Secrecy (PFS): A property of key exchange where session keys are ephemeral — compromise of the long-term private key does not expose past session data.

15.3 How It Works: TLS Handshake for MQTT

TLS establishes an encrypted channel through a multi-step handshake before any application data is sent:

Step 1: Client Hello (ESP32 to Broker)

  • Client announces supported TLS versions (TLS 1.2, 1.3)
  • Lists cipher suites: TLS_AES_128_GCM_SHA256, TLS_CHACHA20_POLY1305_SHA256
  • Sends random nonce (32 bytes) for key derivation

Step 2: Server Hello (Broker to ESP32)

  • Selects TLS version and cipher suite
  • Sends server’s X.509 certificate (contains public key)
  • Sends random nonce (32 bytes)

Step 3: Certificate Verification (ESP32 validates broker)

  • Extract server’s public key from certificate
  • Verify certificate signature using CA’s public key (pre-loaded on ESP32)
  • Check certificate validity period (not expired)
  • Verify hostname matches (broker.example.com)
  • If any check fails: Abort connection, prevent man-in-the-middle attack

Step 4: Key Exchange (Establish shared secret)

  • ESP32 generates ephemeral ECDH key pair
  • Sends public key to server
  • Server generates ephemeral ECDH key pair, sends public key to client
  • Both compute shared secret (pre-master secret) using ECDH
  • Derive session keys from: client_nonce + server_nonce + shared_secret

Step 5: Finished Messages (Verify handshake integrity)

  • Client sends HMAC of entire handshake encrypted with session key
  • Server sends HMAC of entire handshake encrypted with session key
  • Both verify HMACs match – handshake complete

Step 6: Application Data (MQTT over encrypted channel)

  • All MQTT messages encrypted with AES-128-GCM using session keys
  • Each message has authentication tag preventing tampering
  • Session keys discarded after disconnect (forward secrecy)

Mutual TLS (mTLS) Addition: After Step 3, server requests client certificate:

  • ESP32 sends its device certificate (unique per device)
  • Server verifies client certificate against CA
  • Both sides authenticated (server proves identity to client, client proves identity to server)

Performance Impact on ESP32:

  • Handshake time: 200-500ms (one-time per connection)
  • Encryption overhead: 1-3ms per MQTT message
  • RAM usage: ~40KB for TLS buffers
  • CPU overhead: ~5% for AES-128-GCM (hardware accelerated on ESP32)

Attack Scenario Prevented: Without certificate verification (setInsecure()), attacker performs ARP spoofing on local network, intercepts connection to broker.example.com, presents self-signed certificate. Device accepts it and sends all data to attacker’s fake broker. With proper verification, device rejects untrusted certificate and aborts connection.

15.4 TLS/SSL for MQTT

MQTT is the most common IoT messaging protocol. Securing it with TLS prevents eavesdropping and ensures only authorized devices connect.

Why MQTT Security Matters: Attack Scenario

MQTT UTF-8 validation attack diagram showing a malicious publisher sending invalid UTF-8 data with retain=True and QoS=2 to an MQTT broker (central). A subscriber without UTF-8 validation accepts the message and sends ACK, enabling continued delivery. A second subscriber with UTF-8 validation rejects the message, causing the broker to repeatedly retry delivery (shown as multiple arrows), leading to denial of service through message queue exhaustion.

MQTT protocol attack scenario
Figure 15.1: MQTT Attack: A malicious publisher exploits UTF-8 validation inconsistencies to cause denial of service.

Without TLS encryption and proper input validation, attackers can:

  • Inject malformed messages that crash subscribers
  • Exploit QoS 2 retry mechanisms to amplify attacks
  • Eavesdrop on sensitive sensor data transmitted in cleartext
  • Spoof device identities without certificate authentication

15.4.1 MQTT over TLS Implementation (ESP32)

// MQTT over TLS (MQTTS) on ESP32
#include <WiFiClientSecure.h>
#include <PubSubClient.h>

WiFiClientSecure espClient;
PubSubClient mqtt(espClient);

void setup() {
  WiFi.begin(ssid, password);

  // Configure TLS with server CA + optional mutual TLS
  espClient.setCACert(ca_cert);
  espClient.setCertificate(client_cert);  // mutual TLS (optional)
  espClient.setPrivateKey(client_key);

  mqtt.setServer("broker.example.com", 8883);  // MQTTS port
  mqtt.setCallback(messageCallback);
}

void reconnect() {
  while (!mqtt.connected()) {
    if (mqtt.connect("ESP32Client", mqtt_user, mqtt_pass)) {
      mqtt.subscribe("sensors/#");
    } else {
      delay(5000);  // retry
    }
  }
}

15.4.2 MQTT Security Levels

Level Configuration Security Use Case
None Port 1883, no auth None Development only
Password Port 1883, username/password Low Internal networks
TLS Port 8883, server cert Medium General production
mTLS Port 8883, server + client certs High Enterprise, critical systems
Try It: TLS Handshake Step Explorer

Step through each phase of a TLS 1.3 handshake to see what data is exchanged, what cryptographic operations happen, and what attacks each step prevents.

15.5 DTLS for CoAP

CoAP uses UDP, so TLS cannot be used directly. DTLS (Datagram TLS) provides equivalent security for UDP-based protocols.

15.5.1 DTLS Implementation

// CoAP over DTLS
#include <coap-simple.h>
#include <mbedtls/ssl.h>

mbedtls_ssl_context ssl;
mbedtls_ssl_config conf;

void setup_dtls() {
  mbedtls_ssl_init(&ssl);
  mbedtls_ssl_config_init(&conf);

  // Configure DTLS (datagram mode)
  mbedtls_ssl_config_defaults(&conf,
                               MBEDTLS_SSL_IS_CLIENT,
                               MBEDTLS_SSL_TRANSPORT_DATAGRAM,
                               MBEDTLS_SSL_PRESET_DEFAULT);

  // Set certificates
  mbedtls_ssl_conf_ca_chain(&conf, &cacert, NULL);
  mbedtls_ssl_conf_own_cert(&conf, &clicert, &pkey);

  mbedtls_ssl_setup(&ssl, &conf);
}

void sendCoapMessage(String payload) {
  // Encrypt and send with DTLS
  mbedtls_ssl_write(&ssl, (uint8_t*)payload.c_str(),
                    payload.length());

  // DTLS handles encryption, integrity, and replay protection
}

15.5.2 TLS vs DTLS Comparison

Feature TLS (TCP) DTLS (UDP)
Transport TCP (reliable) UDP (unreliable)
Connection Stateful Connectionless
Handshake Simpler Handles packet loss, reordering
Protocols HTTPS, MQTTS CoAP, VoIP, gaming
IoT Suitability Good Excellent for constrained
Try It: Secure Protocol Selector

Configure your IoT scenario and see which security protocol (TLS, DTLS, or VPN) is the best fit based on transport type, latency needs, and resource constraints.

Knowledge Check: TLS vs DTLS

Question: Why can TLS not be used directly over UDP for CoAP communications?

A. TLS encryption algorithms do not support UDP packets B. TLS relies on TCP’s reliable, ordered delivery which UDP does not provide C. UDP is inherently secure and does not need TLS D. TLS is only designed for web browsers

Click to reveal answer

Answer: B – TLS relies on TCP’s reliable, ordered delivery which UDP does not provide

TLS assumes packets arrive in order and without loss (guaranteed by TCP). UDP provides no such guarantees, so TLS handshake messages and encrypted records could arrive out of order or be lost entirely. DTLS adds its own retransmission timers and sequence numbers to handle this, making it the correct choice for UDP-based protocols like CoAP.

15.6 VPN for Remote Access

VPNs create encrypted tunnels for all traffic, enabling secure remote access to IoT networks.

15.6.1 WireGuard VPN Configuration

# WireGuard VPN configuration for IoT gateway
import subprocess

def setup_wireguard():
    config = """
[Interface]
PrivateKey = <device_private_key>
Address = 10.0.0.2/24
DNS = 10.0.0.1

[Peer]
PublicKey = <server_public_key>
Endpoint = vpn.example.com:51820
AllowedIPs = 10.0.0.0/24
PersistentKeepalive = 25
"""

    with open('/etc/wireguard/wg0.conf', 'w') as f:
        f.write(config)

    # Start VPN
    subprocess.run(['wg-quick', 'up', 'wg0'])

# All IoT traffic now encrypted through VPN

15.6.2 Worked Example: WireGuard for Distributed Agriculture

Scenario: A precision agriculture company operates 20 remote farms with IoT sensors. Each farm has intermittent 4G connectivity. Implement secure VPN using WireGuard.

Solution:

  1. Architecture: Hub-and-spoke topology with central server

    • Central VPN server: 10.200.0.1
    • Farm 1 subnet: 10.200.1.0/24
    • Farm 2 subnet: 10.200.2.0/24
    • Technician pool: 10.200.100.0/24
  2. Central server configuration:

    [Interface]
    PrivateKey = <server_private_key>
    Address = 10.200.0.1/16
    ListenPort = 51820
    
    # Farm 1 gateway (entire farm subnet behind gateway)
    [Peer]
    PublicKey = <farm1_public_key>
    AllowedIPs = 10.200.1.0/24
    PersistentKeepalive = 25
    
    # Technician 1 (single IP)
    [Peer]
    PublicKey = <tech1_public_key>
    AllowedIPs = 10.200.100.101/32
  3. Access control rules:

    # Technicians can reach farm gateways
    iptables -A FORWARD -s 10.200.100.0/24 -p tcp --dport 22 -j ACCEPT
    
    # Farms can only reach central MQTT broker
    iptables -A FORWARD -s 10.200.1.0/20 -d 10.200.0.1 -p tcp --dport 8883 -j ACCEPT
    
    # Block inter-farm traffic
    iptables -A FORWARD -s 10.200.1.0/24 -d 10.200.2.0/24 -j DROP

Result:

  • Encryption: Curve25519 key exchange (256-bit keys, ~128-bit security level) with ChaCha20-Poly1305
  • Latency overhead: 2-5ms
  • Isolation: Farms cannot communicate directly
  • Scalability: 200+ concurrent peers

15.7 Secure Boot and Firmware Verification

Secure boot ensures that only authenticated firmware runs on a device, preventing attackers from installing malware.

Code signing process diagram showing the workflow for creating digital signatures. Executable source code is passed through a SHA-256 hash algorithm to produce a one-way hash (digest). This digest is then encrypted with the code signer's private key to create a digital signature. The final signed code package contains three components: the original executable code, the digital signature, and the code signer's certificate. A separate private key input feeds into the encryption step.
Figure 15.2: Code-signing process for firmware integrity

The code-signing process shown above is the foundation of secure OTA updates (Section 15.8). The manufacturer signs firmware with their private key, and the device verifies the signature using the corresponding public key embedded in the secure boot chain.

15.7.1 Secure Boot Implementation (ESP32)

// ESP32 Secure Boot
#include "esp_secure_boot.h"
#include "esp_flash_encrypt.h"

void setup() {
  Serial.begin(115200);

  // Check if secure boot is enabled
  if (esp_secure_boot_enabled()) {
    Serial.println("Secure boot ENABLED");
    Serial.println("  Firmware signature verified at boot");
  } else {
    Serial.println("WARNING: Secure boot DISABLED");
    Serial.println("  Device vulnerable to firmware attacks");
  }

  // Check flash encryption
  if (esp_flash_encryption_enabled()) {
    Serial.println("Flash encryption ENABLED");
    Serial.println("  Firmware encrypted in flash");
  } else {
    Serial.println("WARNING: Flash encryption DISABLED");
  }
}

15.7.2 Secure Boot Chain of Trust

Secure boot chain of trust diagram showing four sequential stages connected by arrows: ROM (Root of Trust, Immutable) verifies the Bootloader (1st Stage, Signature Verified), which verifies the OS Kernel (2nd Stage, Hash Verified), which verifies the Application Firmware (Integrity Checked). A caption at the bottom states that each stage cryptographically verifies the next before execution, and if any stage fails, boot halts.
Figure 15.3: Secure boot chain of trust from ROM to firmware

Key Insight: The chain of trust starts with immutable Boot ROM (hardware). Each stage verifies the next before execution. If ANY verification fails, the device refuses to boot.

Try It: Secure Boot Chain Simulator

Simulate the secure boot verification process. Toggle each stage to be legitimate or tampered, and observe how the chain of trust detects and halts at the first compromised stage.

15.7.3 Hardware Trojan Countermeasures

Hardware trojans are malicious modifications to integrated circuits that bypass software security.

Hardware Trojan countermeasures taxonomy organized as a tree with three main branches: Trojan Detection Approaches (split into Destructive and Non-Destructive, with Non-Destructive further divided into Logic Testing and Side-channel Analysis, plus IP Trust Verification at pre-silicon and post-silicon stages), Design for Security (with sub-branches for Prevent Insertion and Facilitate Detection), and Run-time Monitoring (continuous integrity verification during operation).

Countermeasures against Hardware Trojans
Figure 15.4: Hardware Trojan Countermeasures: Detection, prevention, and monitoring approaches
Strategy Description IoT Application
Side-Channel Analysis Monitor power/EM emissions for anomalies Detecting trojans in deployed sensors
Logic Testing Exhaustive testing of rare input combinations Pre-deployment validation
Design Obfuscation Hide circuit design intent Chip manufacturing security
Run-time Monitoring Continuous behavioral analysis Detecting activated trojans

15.8 Secure OTA Updates

Over-the-air updates must verify authenticity before installation to prevent malicious firmware injection.

15.8.1 OTA with Signature Verification

// Secure OTA with signature verification (ESP32)
#include <Update.h>
#include <HTTPClient.h>
#include "mbedtls/md.h"
#include "mbedtls/pk.h"

// Manufacturer's public key (embedded at build time)
const char* manufacturer_public_key = "-----BEGIN PUBLIC KEY-----\n...";

bool performSecureOTA(String firmwareUrl, String signatureUrl) {
  // 1. Download signature
  HTTPClient http;
  http.begin(signatureUrl);
  String signature = http.getString();
  http.end();

  // 2. Download firmware, computing SHA-256 hash in-stream
  http.begin(firmwareUrl);
  WiFiClient *stream = http.getStreamPtr();
  int remaining = http.getSize();

  mbedtls_md_context_t ctx;
  mbedtls_md_init(&ctx);
  mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 0);
  mbedtls_md_starts(&ctx);

  uint8_t buffer[1024];
  while (remaining > 0) {
    int c = stream->readBytes(buffer, min((size_t)remaining, sizeof(buffer)));
    mbedtls_md_update(&ctx, buffer, c);
    remaining -= c;
  }
  uint8_t hash[32];
  mbedtls_md_finish(&ctx, hash);

  // 3. Verify RSA signature against hash
  mbedtls_pk_context pk;
  mbedtls_pk_init(&pk);
  mbedtls_pk_parse_public_key(&pk, (const uint8_t*)manufacturer_public_key,
                               strlen(manufacturer_public_key) + 1);
  int ret = mbedtls_pk_verify(&pk, MBEDTLS_MD_SHA256, hash, 32,
                               signature_bytes, signature_len);
  if (ret != 0) return false;  // REJECT tampered firmware

  // 4. Install verified firmware ...
  return true;
}

15.8.2 OTA Security Checklist

Security Measure Purpose Implementation
Digital signature Verify firmware authenticity RSA-2048 or ECDSA P-256
HTTPS download Protect during transit TLS 1.2+ with certificate pinning
Version check Prevent downgrade attacks Reject older versions
Rollback partition Recovery from failed update Dual-partition A/B scheme
Staged rollout Limit blast radius Update 1% of devices first
Try It: OTA Update Security Checker

Configure an OTA update scenario to see which security checks pass or fail. Toggle different protections on and off to understand how each layer contributes to firmware integrity.

Knowledge Check: Secure Boot

Question: An IoT gateway needs to securely boot and load firmware. An attacker replaces the application firmware with malware. What prevents the malware from running?

A. Boot ROM loads bootloader without verification B. Bootloader checks firmware file size before loading C. Bootloader verifies firmware RSA signature against manufacturer’s public key D. Firmware runs in sandbox that limits malware damage

Click to reveal answer

Answer: C - Bootloader verifies firmware RSA signature against manufacturer’s public key

Why?

  • The chain of trust: Boot ROM (immutable) verifies bootloader signature, then bootloader verifies firmware signature
  • Attackers cannot forge signatures without the manufacturer’s private key
  • Even if they replace firmware, signature verification fails and device refuses to boot
  • File size checks are easily spoofed; sandboxing helps but doesn’t prevent execution

Scenario: Agricultural IoT company deploys 1,000 soil moisture sensors across 50 farms using LoRaWAN. Network server is cloud-hosted, but customer demands that raw sensor data never be visible to the cloud provider.

Requirements:

  • Sensor data encrypted end-to-end (device to customer application)
  • LoRaWAN network server cannot read sensor values
  • Bandwidth: 51-byte max MACPayload at LoRaWAN SF10 (EU868)
  • Battery life: 5 years on 2x AA batteries

Solution Architecture:

Layer Protocol Key Type Purpose
E1: Link LoRaWAN AES-128 Network session key Protect RF transmission
E2: App Custom AES-128-GCM Per-device key Prevent network server reading
E3: Transport TLS 1.3 ECDHE Secure network server to customer app

Payload Calculation:

Max MACPayload at SF10/EU868: 51 bytes
MACPayload = FHDR (7 bytes) + FPort (1 byte) + FRMPayload
Available application payload (FRMPayload): 51 - 7 - 1 = 43 bytes

Sensor data (E2 plaintext):
- Device ID: 4 bytes
- Timestamp: 4 bytes
- Moisture reading: 2 bytes
- Temperature: 2 bytes
- Battery voltage: 2 bytes
Total plaintext: 14 bytes

E2 encryption overhead:
- AES-GCM nonce: 12 bytes
- AES-GCM auth tag: 16 bytes
- Encrypted payload: 14 bytes
Total E2 packet: 42 bytes

42 bytes <= 43 bytes max FRMPayload
Result: Fits within LoRaWAN constraints with 1 byte to spare

Energy Analysis:

Per message (sent hourly):

  • LoRaWAN transmission: 45 mA x 1.5s = 67.5 mAs
  • AES-128-GCM encryption: 8 mA x 0.02s = 0.16 mAs
  • Total per message: 67.66 mAs

Annual energy:

  • Messages per year: 24 x 365 = 8,760
  • Transmission + encryption: 8,760 x 67.66 = 592,702 mAs = 164.6 mAh
  • Sleep/quiescent overhead (MCU sleep, sensor probe, RTC): ~760 mAh/year

Battery capacity: 2 x 2,400 mAh = 4,800 mAh Battery life: 4,800 / (164.6 + 760) = 5.2 years

Note: The sleep/quiescent current (~87 uA average) dominates battery consumption, not the transmission energy. This is typical for agricultural sensors with continuous soil moisture probe power draw.

Key Management:

Manufacturing:

  1. Generate unique 128-bit AES key per device
  2. Provision key in device secure flash
  3. Escrow customer’s public key in device
  4. Device generates ephemeral ECDH key pair

Field Operation:

  1. Device encrypts sensor data with AES-GCM (E2)
  2. LoRaWAN encrypts E2 packet with network session key (E1)
  3. Network server decrypts E1, forwards E2 blob to customer app
  4. Customer app decrypts E2 using provisioned device key

Security Properties Achieved:

Threat Mitigation Layer
RF eavesdropping LoRaWAN AES-128 E1
Network server compromise End-to-end AES-GCM E2
Data tampering AES-GCM authentication tag E2
Key distribution Pre-provisioned at manufacturing E2
Transport security TLS 1.3 with ECDHE E3

Result: Customer receives sensor data encrypted at the device level. Even if the LoRaWAN network provider is compromised, raw sensor data remains protected. Bandwidth overhead (28 bytes for E2 encryption) fits within LoRaWAN limits, and encryption energy consumption is negligible (<1% of transmission energy).

Use this framework to select the appropriate secure communication approach:

Criteria TLS (TCP) DTLS (UDP) VPN (WireGuard)
Protocol compatibility HTTPS, MQTTS, WebSocket CoAP, QUIC Any IP traffic
Latency requirement <500ms acceptable <100ms required <50ms required
Packet loss tolerance Poor (TCP retransmits) Excellent Excellent
Setup complexity Medium Medium Low
Per-connection overhead ~5KB ~1KB ~300 bytes (peer state)
Battery impact Medium Low Very low
Firewall traversal Easy (port 443) Moderate (custom ports) Excellent (UDP hole punching)

Decision Tree:

Is your application protocol TCP-based (HTTP, MQTT)?
+-- YES --> Use TLS 1.3
|  +-- Configure cipher suite: TLS_AES_128_GCM_SHA256
|
+-- NO --> Does your protocol use UDP?
   +-- YES --> Use DTLS 1.3
   |  +-- Example: CoAP with DTLS (RFC 7252)
   |
   +-- NO --> Do you need to secure ALL traffic?
      +-- YES --> Use VPN (WireGuard)
      |  +-- Encrypts entire IP layer
      |
      +-- NO --> Application-layer encryption
         +-- Use E2/E3 with AES-GCM

Real-World Example Decision:

Smart meter deployment:

  • Protocol: CoAP (UDP-based)
  • Network: Cellular NB-IoT (high latency, packet loss)
  • Data: Meter readings every 15 minutes
  • Constraint: 200ms maximum latency for demand response

Analysis:

Option Pros Cons Verdict
TLS Industry standard Requires TCP (overhead) Rejected
DTLS Designed for UDP, lightweight Certificate management Selected
VPN Secures all traffic Battery drain, always-on Rejected
App-layer Ultra-lightweight No standard key exchange Rejected

Final choice: DTLS 1.3 with PSK (pre-shared keys) mode to avoid certificate overhead. Handshake completes in 1-RTT, meeting latency requirement.

Common Mistake: Disabling Certificate Validation

The Mistake: Developers disable TLS certificate validation to “fix” connection errors during development, then forget to re-enable it in production.

Code example (WRONG):

// ESP32 - DON'T DO THIS
WiFiClientSecure espClient;
espClient.setInsecure(); // Disables certificate validation

mqtt.setClient(espClient);
mqtt.connect("broker.example.com", 8883);

Why this is catastrophic:

With certificate validation disabled, your device will accept TLS connections from anyone - including attackers performing man-in-the-middle attacks.

Attack scenario:

  1. Device connects to “broker.example.com:8883”
  2. Attacker intercepts connection (ARP spoofing, DNS hijack, rogue Wi-Fi AP)
  3. Attacker presents self-signed certificate
  4. Device accepts it (validation disabled!) and establishes “secure” connection
  5. Attacker now has full access to device commands and data

The fix (CORRECT):

// Option 1: Pin specific CA certificate (recommended for ESP32)
const char* broker_cert = \
"-----BEGIN CERTIFICATE-----\n" \
"MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQsF...\n" \
"-----END CERTIFICATE-----\n";

espClient.setCACert(broker_cert);

// Option 2: Use system CA bundle (ESP32)
espClient.setCACertBundle(ca_bundle_start);

Why developers make this mistake:

Reason Reality Solution
“Certificate errors during testing” Usually hostname mismatch Use correct hostname in test environment
“Don’t know how to get CA cert” CA certs are public Download from broker provider website
“Certificate expired” Test environment issue Update test broker certificates
“Works without it” Only against non-malicious network Enable validation ALWAYS

Real-world incident:

A smart home product shipped with setInsecure() enabled. Attackers created fake MQTT brokers at coffee shops. When devices connected to Wi-Fi, they sent data to attacker’s fake broker. Over 50,000 devices compromised before firmware patch deployed.

Cost: $12M in emergency firmware updates, legal settlements, and brand damage.

The rule: NEVER disable certificate validation in production. If you face certificate errors, fix the root cause – don’t disable security.

Concept Relationships
Concept Related To Relationship Type
TLS (Transport Layer) TCP, MQTT, HTTPS Secures - Reliable connection-oriented protocols
DTLS (Datagram TLS) UDP, CoAP Adapts - TLS concepts to unreliable connectionless protocols
VPN (WireGuard) IP Layer Encryption Tunnels - All network traffic through encrypted channel
Secure Boot Chain of Trust, ROM Establishes - Hardware root of trust validates firmware signatures
OTA Updates Code Signing, Versioning Protects - Firmware integrity during wireless updates
Certificate Pinning Man-in-the-Middle Defense Prevents - Acceptance of fraudulent certificates even if CA compromised
See Also

Foundation Concepts:

Related Security Topics:

Practical Applications:

Build a TLS certificate validator to understand the chain of trust verification process.

Exercise Steps:

  1. Load server certificate (leaf cert) from connection
  2. Extract issuer information (which CA signed this cert)
  3. Load CA certificate (root or intermediate)
  4. Verify signature: hash(server_cert) matches signature decrypted with CA public key
  5. Check validity period (not_before <= current_time <= not_after)
  6. Verify hostname matches (cert’s CN or SAN matches broker.example.com)

Starter Code (Python with cryptography library):

from cryptography import x509
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from datetime import datetime, timezone

def verify_certificate(server_cert_pem, ca_cert_pem, hostname):
    # Load certificates
    server_cert = x509.load_pem_x509_certificate(server_cert_pem.encode())
    ca_cert = x509.load_pem_x509_certificate(ca_cert_pem.encode())

    # Check 1: Verify signature (server cert signed by CA)
    try:
        ca_public_key = ca_cert.public_key()
        ca_public_key.verify(
            server_cert.signature,
            server_cert.tbs_certificate_bytes,
            padding.PKCS1v15(),
            server_cert.signature_hash_algorithm
        )
        print("Signature valid (cert signed by CA)")
    except Exception as e:
        print(f"Signature invalid: {e}")
        return False

    # Check 2: Verify validity period
    now = datetime.now(timezone.utc)
    if server_cert.not_valid_before_utc <= now <= server_cert.not_valid_after_utc:
        print(f"Validity period OK (expires {server_cert.not_valid_after_utc})")
    else:
        print("Certificate expired or not yet valid")
        return False

    # Check 3: Verify hostname (CN or SAN)
    # Exercise: Extract CN from subject or SAN extension
    # Exercise: Compare with hostname parameter
    print(f"Hostname matches: {hostname}")

    return True

# Test with real certificate
server_cert = """-----BEGIN CERTIFICATE-----..."""
ca_cert = """-----BEGIN CERTIFICATE-----..."""
verify_certificate(server_cert, ca_cert, "broker.example.com")

What to Observe:

  • Signature verification uses CA’s PUBLIC key (anyone can verify)
  • Signing uses CA’s PRIVATE key (only CA can sign)
  • Expired certificates rejected even if signature valid
  • Hostname mismatch detected (prevents wrong-domain attacks)
  • Chain of trust: root CA -> intermediate CA -> server cert

Extension: Implement full chain validation for 3-level hierarchy (root -> intermediate -> leaf)

TLS 1.3 derives session keys using HKDF (HMAC-based Key Derivation Function), defined in RFC 5869:

\[\text{HKDF-Extract}(\text{salt}, \text{IKM}) = HMAC\text{-}SHA256(\text{salt}, \text{IKM})\]

\[\text{HKDF-Expand}(\text{PRK}, \text{info}, L) = \text{first } L \text{ bytes of } T\]

where \(T = T_1 \parallel T_2 \parallel \ldots\) and \(T_i = HMAC\text{-}SHA256(\text{PRK}, T_{i-1} \parallel \text{info} \parallel i)\).

TLS 1.3 Key Schedule:

\[\text{Handshake Secret} = \text{HKDF-Extract}(\text{derived salt}, \text{ECDH shared secret})\]

\[\text{Master Secret} = \text{HKDF-Extract}(\text{derived salt}, 0)\]

Working through an example:

Given: ESP32 establishes TLS 1.3 connection to MQTT broker. Both sides performed ECDH key exchange using X25519.

Client random: \(C = \text{0xa3f8b2c1...}\) (32 bytes)

Server random: \(S = \text{0x7d4e9a6f...}\) (32 bytes)

ECDH shared secret: \(K = \text{0x5b2c8f1a...}\) (32 bytes)

Step 1: Extract handshake secret using HKDF-Extract \[\text{salt} = \text{HKDF-Expand-Label}(\text{Early Secret}, \text{"derived"}, \text{""}, 32)\]

\[\text{HS} = HMAC\text{-}SHA256(\text{salt}, K)\]

\[\text{HS} = \text{0x8f3d1c9e...} \quad (32 \text{ bytes})\]

Step 2: Derive client handshake traffic secret \[\text{label} = \text{"c hs traffic"}\]

\[\text{context} = \text{SHA-256}(\text{ClientHello} \parallel \text{ServerHello})\]

\[\text{CHTS} = \text{HKDF-Expand-Label}(\text{HS}, \text{label}, \text{context}, 32)\]

Step 3: Derive client write key (AES-128-GCM = 16 bytes) \[K_{client} = \text{HKDF-Expand-Label}(\text{CHTS}, \text{"key"}, \text{""}, 16)\]

\[K_{client} = \text{0x2b7e1516...} \quad (16 \text{ bytes})\]

Step 4: Derive server application traffic secret and write key similarly

\[K_{server} = \text{0x8c6d5a3f...} \quad (16 \text{ bytes})\]

Result: Client encrypts with \(K_{client}\), server encrypts with \(K_{server}\). Keys are unique per connection (forward secrecy). Eavesdropper capturing handshake cannot derive keys without ECDH private keys.

In practice: If attacker records all TLS traffic today and later compromises the server’s long-term private key, they CANNOT decrypt past sessions because ephemeral ECDH keys were destroyed after handshake. This is forward secrecy – critical for long-lived IoT deployments where key compromise years later should not expose historical data.

15.9 Chapter Summary

Secure communications protect IoT data in transit using TLS for TCP-based protocols (MQTT) and DTLS for UDP-based protocols (CoAP). VPNs like WireGuard provide encrypted tunnels for remote access with low latency overhead suitable for constrained IoT networks.

Secure boot establishes a chain of trust from immutable hardware (Boot ROM) through bootloader to application firmware, with each stage cryptographically verifying the next before execution. OTA updates must verify digital signatures before installation to prevent malicious firmware injection, with rollback capabilities for recovery from failed updates.

15.10 Knowledge Check

Common Pitfalls

Mistake: Setting verify=False in TLS connections to avoid certificate management complexity. Why it happens: Self-signed certificates cause errors during development. Fix: Use a proper IoT PKI with device certificates issued by a trusted CA; implement certificate pinning for high-security devices.

Mistake: Using TLS over UDP-based protocols like CoAP, causing connection failures. Why it happens: Developers familiar with HTTP/TLS apply the same stack to all protocols. Fix: Use DTLS (Datagram TLS) for UDP-based IoT protocols; TLS requires TCP, DTLS handles packet loss and reordering natively.

Mistake: Accepting any firmware update without verifying its cryptographic signature. Why it happens: Adding signature verification increases OTA update complexity. Fix: Sign firmware images with ECDSA private key, verify with device-stored public key before flashing; use secure boot to verify the bootloader itself.

Mistake: Writing MQTT session credentials or JWT tokens to unprotected flash storage. Why it happens: Flash storage is the obvious place for persistent credentials. Fix: Encrypt credentials at rest using a device-unique key derived from hardware identifiers, or use a secure element for credential storage.

Mistake: Using a counter-based IV that resets to zero on device reboot. Why it happens: IV generation seems like an implementation detail. Fix: Use a combination of a monotonic counter stored in non-volatile memory plus a random nonce; IV reuse in GCM mode catastrophically breaks both confidentiality and authentication.

15.11 What’s Next

With secure communications and firmware integrity in place, the next chapter examines Monitoring and Detection where you’ll learn to implement intrusion detection systems, anomaly detection for IoT networks, and comprehensive security exercises.

Direction Chapter Focus
Prerequisites Cryptography for IoT TLS/DTLS cryptographic foundations
Prerequisites Authentication Methods Certificate-based authentication for mTLS
Next Monitoring and Detection Intrusion detection for IoT networks
Next Encryption Principles Deeper dive into symmetric and asymmetric basics