Implementing CoAP The Secure Way, Part I: Fundamentals
The Constrained Application Protocol (CoAP) is a RESTful protocol that has many similarities to HTTP (as well as big differences). It is a lightweight protocol designed for Machine-to-Machine (M2M) communications within Internet of Things (IoT) applications, with a compact format suitable for constrained devices and lossy networks. It’s used in IoT apps and devices, including smart lightbulbs, building automation systems, radiation detectors, and automobiles. It can be used to send and receive sensor updates, and it can also be used to push firmware updates to devices. CoAP is deployed in many ways, with different architectures changing the security boundaries and attack surface. Many implementations of the protocol interpret the RFCs in their own way, leading to some potentially unexpected or undesired behavior.
In this two-part series, we will cover the fundamentals of CoAP, popular technologies and frameworks, and security lessons for software engineers and security testers. For testing our examples, we will use the Copper add-on for Firefox as well as coap-client from the libcoap project. To install libcoap within Terminal, just type brew install libcoap. If you don’t have Homebrew, get it here. In the next post, we will release a set of open-source tools for learning how to build secure CoAP applications as well as performing testing for security issues.
How It Works
Getting started with CoAP requires understanding what the protocol offers and how it is commonly used. Compared to web protocols, CoAP is very decentralized, allowing sensors and nodes to link with and publish to each other. The default port for coap:// is 5683/udp and the default for coaps:// is 5684/udp. It supports multicast transmissions, allowing for efficient resource discovery and updates. Since CoAP uses UDP rather than TCP, it is a smaller packet and more lightweight in comparison to HTTP. Headers are considerably smaller, and the protocol supports splitting larger payloads through multiple requests known as a Blockwise transfer. However, CoAP can be used in conjunction with other protocols including TCP, SMS, and any protocol you’re willing to write the code to extend it to work with. This side-by-side comparison of HTTP over TCP and CoAP over UDP illustrates the lightweight representation of CoAP:
CoAP can be used in several different ways. You can talk CoAP directly from a mobile device or browser to an IoT device. Or, you can utilize servers and proxies along the way that perform caching and protocol translation as well as handle security duties such as authentication and access control.
Here is a really useful cheat sheet for CoAP which will come in handy as you work with the protocol more. The CoAP message format looks like this:
Image from https://tools.ietf.org/html/rfc7252
Let’s examine each piece of the CoAP message.
- Version - Protocol version, currently at 1
Type - Message type can be one of four types
- Confirmable (CON) - Must be acknowledged by the receiver with an ACK packet.
- Non-Confirmable (NON) - “Fire and forget” messages that do not require acknowledgement
- Acknowledgement (ACK) - Acknowledge a confirmable message
- Reset (RST) - Reject a confirmable or remove an observer
- Token Length - Specifies the length of the token as 0 to 8 bytes (see Token field)
- Code - Response code analogous to HTTP response codes, can be a success message, client error, or server error
- Message ID - Identifier for each message sent, which in most implementations are likely sequentially assigned; not very effective for security purposes
Token - Used between the client and server for differentiating between concurrent requests
- If spoofed, could allow for malicious resource modification or cause denial of service conditions by removing subscriptions
- Entropy might be important
- Options - Can be set to include one or more options, including a subset of what’s available via HTTP headers
- Payload - Body of message or specific format, if any
CoAP uses URIs intended to map directly to HTTP. It also supports four methods that correspond with their HTTP counterparts in a RESTful way:
The CoAP response code conventions are also similar to HTTP, where a 2.xx response code indicates success, a 4.xx response code indicates a client error, and a 5.xx response code indicates a server error. For the examples in this post, we will use the Copper add-on for Firefox as well as custom client code written for Californium and Contiki + Erbium.
When operating as a proxy or server, mapping between CoAP and HTTP often occurs, including CoAP-to-HTTP and HTTP-to-CoAP proxies. There are well-defined mappings between CoAP and HTTP, although there are definitely implementation dragons to be found if you dig deep enough into the frameworks and libraries out there today. Similar to HTTP, CoAP is a plaintext protocol by default. Communications security requires the presence of an additional encrypted protocol as a wrapper, just like the relationship between HTTP and TLS. As CoAP is a UDP protocol rather than a TCP protocol, TLS isn’t used by default. Instead, encryption is most commonly accomplished using Datagram Transport Layer Security (DTLS) and occasionally with IPSec. We’ll take a deeper look at DTLS implementations later in this article. An example of an implementation over TCP can be found in the Spark protocol, using CoAP over TCP.
Observers are based on the well-known Observer pattern; they allow for a client to receive updates when a subject’s state changes. This reduces the need for constant polling but also introduces additional engineering overhead into the protocol when considering sleeping nodes and unreliable networks. Below we can see an example of this in action, where the light sensor provides updates to the light as they are observed.
A client becomes an Observer by setting the Observer option within a request. The client receives updates until one of the following conditions are met: a Confirmable message is unacknowledged, the client sends an explicit removal message, or the Max-Age of the last notification is exceeded.
Multicast Group Communications
CoAP supports group communications and multicast. This allows for a single request to be transmitted to several interested recipients, in a one-to-many relationship.
Previously, DTLS was not multicast-friendly and was not compatible. However, there have been standard drafts to address these shortcomings. This includes key management, encryption, and authentication.
Resource discovery allows for resources hosted on CoAP or HTTP servers to be discovered by sending a GET request to /.well-known/core. This will disclose a list of all known resources within the resource directory. Filtered GET requests enable query string parameters, allowing for more complex expressions and patterns to be used.
An example of a basic query looks like this:
An example of a more complex query looks like this:
When we take a closer look at filters and resource discovery, we’ll also look at some potential abuse cases as well.
Come back soon for our next post where we will dig deeper into code examples, secure development pointers, and security testing tips for your IoT applications using CoAP.