Secure decoupled messaging with DANE and the TLSA resource record

Decoupled and Secure

© Photo by Dayne Topkin on Unsplash.com

© Photo by Dayne Topkin on Unsplash.com

Article from Issue 244/2021
Author(s):

Decoupled application design gets in the way of secure communication, but a little known feature of DNS can provide message security.

Traditional security mechanisms like Transport Layer Security (TLS) provide the ability to authenticate both sides of a direct session between two parties, and to encrypt the traffic passing over the authenticated session. For applications that fit into the footprint of the client/server architecture, TLS is a fine solution for authentication and encryption.

However, as applications become more sophisticated, client/server applications are often challenged to maintain availability with a large number of clients. Middleware layers often serve as a means for providing more graceful scaling. The practice of adding layers to the application stack connecting communicating parties is called decoupling. Decoupled applications – applications that may contain components like message queues or brokers between the message sender and receiver – have been around for many years. Decoupled designs are now employed for building massive IoT applications, like smart cities and facilities automation.

Message brokers and other middleware components offer many advantages, but they also add some complications. One problem is that a message broker prevents the sender and receiver from establishing a direct session that can be secured with TLS. If you don't have a direct connection, how do you encrypt the data and also authenticate both sides of a session?

This article describes a standards-based solution to the message security problem in a decoupled application.

Message Security in Decoupled Apps

The challenge with authentication and encryption across decoupled applications is that there is no direct session to secure between the sender and receiver. You need to apply security to the messages themselves to ensure that they arrive unparsed and unchanged.

Digital identity, in its most simple form, is a method by which an entity can prove ownership of its name. The usefulness of a digital identity is directly tied to how widely recognized the identity is, and how effectively the identity resists abuse by impersonation. Digital certificates are used to bind a name to a public key, but different certificate authorities might create certificates for the same name, if the right controls are not in place. A name is only guaranteed to be unique within the scope of a single certificate authority (CA). In other words, the CA's namespace is the boundary of protection against naming collisions.

The Domain Name System (DNS) is most commonly used to associate a name (www.example.com) with an IP address. The list of DNS standards for representing other types of information is quite long. For this project, I will use a record format called TLSA, which is defined by the DNS-based Authentication of Named Entities (DANE) standard [1]. According to RFC 6698, DANE enables "administrators of domain names to specify the keys used in that domain's TLS servers." DANE places the control of which public keys can be associated with specific TLS-protected services in the hands of the administrator of the DNS zone. By binding the DNS name to a certificate, DANE mitigates naming collisions across CAs, greatly increasing the level of difficulty involved in website impersonation. Only the certificate found at the server's name in DNS may be used to authenticate the server side of the connection.

A key component of DANE is the TLSA DNS record format. (See the box entitled "DNS Resource Records.") The TLSA record is a multifunction tool, enabling the user to present information about public keys in a variety of different ways. The original purpose of the TLSA record was to publish constraints around how a public key can be associated with a name in DNS. For instance, a TLSA record can specify that only a PKI-validated certificate containing a specific public key may be used to authenticate a server operating on a specific port. This is known as the service certificate constraint mode of the TLSA record. Using the TLSA record in this way allows the application owner to specify a separate certificate for service authentication for each TLS-secured port on a server. I will be using the TLSA record for something a little different: certificate discovery, to enable message-based authentication and encryption.

DNS Resource Records

This article assumes that you already have some familiarity with DNS. But for a little refresher, DNS doles out bits of information in the form of resource records. The classic record type is the A record, which returns a 32-bit IPv4 address for the specified DNS name, but DNS supports dozens of other record types. For instance, the MX record maps the domain name to a mail transfer agent and the CNAME record provides an alias "canonical name" for the specified domain. This flexible system allows developers to extend DNS by simply adding new resource record types.

The dig command-line tool is a quick and easy way to interact with DNS. To query DNS for the TLSA record defined by the DANE standard, use the following command:

dig -t TLSA ${IDENTITY_NAME}

replacing ${IDENTITY_NAME} with the DNS name where you expect to find a certificate. This command will come in handy later, when you configure your device's identity in DNS.

Encrypted and Authenticated Messaging System

To demonstrate these concepts in a real-life scenario, I will show you how to configure DNS to let users locate certificates via the TLSA record. Using DNS for discovering certificates allows me to distribute the public key so that whoever receives the messages can validate the sender, and anyone with the public key can send encrypted messages to the device. For the message transport between devices, I will use the free MQTT message broker service provided by HiveMQ.com. (See the box entitled "MQTT.")

MQTT

Message Queuing Telemetry Transport, or MQTT [2], is a message brokering protocol maintained by OASIS, an open standards group. MQTT is lightweight and very widely used in IoT applications. MQTT is especially useful for protecting an application from surges of information that may occur in large IoT applications, which would otherwise require the central processing service to scale dramatically.

This example is provided as a proof of concept. Adapt and expand as needed for your own network, and, as always, be aware of the need to conform to security policies for you own organization.

For this test, you'll need:

  • At least one Raspberry Pi 3 or 4
  • Internet connectivity for the devices
  • A 16GB microSD card
  • A way to get the application image onto the Pi's microSD card (USB microSD adapter)
  • An account with Balena.io, a cloud platform for supporting IoT projects (I will use the Balena.io account for managing the code on the devices)
  • An account with a DNS hosting provider, or your own DNS server (make sure you choose a DNS project that can present TLSA records)

It is also possible to use docker-compose for this experiment instead of Balena.io and a Raspberry Pi – see the instructions in the code repository's README.md file.

The first step is to navigate to https://github.com/ValiMail/dane-message-security-mqtt and click on Deploy to Balena. This will allow you to create a new application in your Balena account, and it will start the build for the application code.

Download the Balena image for the device. Then install Balena Etcher and use it to copy the image to the microSD card. Next, put the microSD card in the Pi and plug in power and network cables. The Pi will automatically register with the Balena service and download the application.

Understanding the Message Application

Before I go on (and while you wait for Balena to build and ship the application to the device), I'll briefly describe the theory of operation for this messaging application. The application runs three services (as configured in docker-compose.yml): messaging_sender, messaging_receiver, and maintenance. As you might have guessed, the messaging_sender service is responsible for signing, encrypting, and transmitting messages. The messaging_receiver service is responsible for retrieving, decrypting, and verifying messages. The maintenance service is used for managing your keys and certificates on the device.

This is a decoupled application, so the messaging_sender service does not directly connect to the recipient device's message_receiver service. Instead, the devices use HiveMQ's free message broker service to get messages from one device to the other. As an added benefit, the use of a public message broker allows you to avoid firewall port forwarding – the message broker acts as a server for the purpose of establishing a connection, and the message broker's publishers and subscribers are clients of the message broker.

When the messaging_receiver service starts and detects a usable configuration, it connects to the message broker as a subscriber. On inspecting applications/messaging_receiver/src/application.py, you may notice that there are queues in place to act as buffers between the distinct steps of message processing. Although this could have been written in a more synchronous manner, this approach was taken to improve the readability of the code and more closely represent how a subscriber might use buffering or queueing to handle a dynamic volume of events.

When a message arrives via MQTT, the message is placed in a queue containing encrypted messages, named ENCRYPTED_MESSAGES. A thread monitors the ENCRYPTED_MESSAGES queue for new messages. When a new encrypted message appears, the thread attempts to use the device's own private key to decrypt it. The mechanics of this process are handled in the dane_jwe_jws library [3]. If the message is successfully decrypted, it is then placed in the DECRYPTED_MESSAGES queue, where the thread that performs authentication picks it up.

The process of authenticating the message is largely handled in the dane_jwe_jws library. The JWS [4] message format contains a signature-protected field named x5u. The x5u field is used to convey the URI where the message sender's x.509 certificate is located. When the receiver's message_is_authentic() function is called, the underlying dane_jwe_jws library extracts the contents of the x5u field and uses the DNS URI contained therein to retrieve the sender's certificate from DNS. If the message authenticates against the public key contained in the certificate, the decrypted message is placed in the AUTHENTICATED_MESSAGES queue. The message printer picks up the authenticated messages and writes them to stdout, along with the authenticated sender's ID.

The messaging_sender service is far simpler than the messaging_receiver service. When a message is sent, the send_message.py script (Listing 1) creates a JWS object. The JWS object contains the user's message and the enclosed x5u field is populated with the DNS URI for the sender's identity, so that the recipient can retrieve the sender's certificate from the DNS record. The send_message.py script then uses the device's private key to sign the JWS object. As with the messaging_receiver service, the details of the JWS and JWE object construction, signing, and encrypting are handled in the dane_jwe_jws library.

Listing 1

send_message.py

01         #!/usr/bin/env python3
02 """Send encrypted messages over MQTT."""
03 import argparse
04 import os
05 import sys
06
07 from dane_jwe_jws.authentication import Authentication
08 from dane_jwe_jws.encryption import Encryption
09 from dane_discovery.exceptions import TLSAError
10 import paho.mqtt.publish as publish
11
12 from idlib import Bootstrap
13
14
15 def main():
16     """Wrap all."""
17     parser = argparse.ArgumentParser()
18     parser.add_argument("recipient", help="Recipient DNS name")
19     parser.add_argument("message", help="Message for recipient")
20     args = parser.parse_args()
21     env_config = get_config()
22     try:
23         payload = sign_and_encrypt(env_config["identity_name"], env_config["crypto_path"],
24                                    env_config["app_uid"], args.message, args.recipient)
25     except TLSAError as err:
26         print("Trouble retrieving certificate from DNS: {}".format(err))
27         sys.exit(2)
28     topic_name = args.recipient
29     publish.single(topic_name, payload, hostname=env_config["mqtt_host"],
30                    port=int(env_config["mqtt_port"]))
31
32
33 def sign_and_encrypt(source_name, crypto_path, app_uid, message, recipient):
34     """Return a signed and encrypted JSON object."""
35     crypto = Bootstrap(source_name, crypto_path, app_uid)
36     signed = Authentication.sign(message, crypto.get_path_for_pki_asset("key"), source_name)
37     return Encryption.encrypt(signed, recipient)
38
39
40 def get_config():
41     """Get config from environment variables."""
42     var_names = ["identity_name", "crypto_path", "mqtt_host",
43                  "mqtt_port", "app_uid"]
44     config = {}
45     for x in var_names:
46         config[x] = os.getenv(x.upper())
47     for k, v in config.items():
48         if v is None:
49             print("Missing essential configuration env var: {}".format(k.upper()))
50     if None in config.values():
51         sys.exit(1)
52     return config
53
54 if __name__ == "__main__":
55     main()
56
57 # copyright 2021 GitHub, Inc.

Once a signed object is generated, the send_message.py script uses DNS to locate the intended recipient's certificate. The public key is extracted from the certificate and used to generate an encrypted JWE object. Finally, the send_message.py script connects to the message broker as a publisher and publishes the JWE object (which contains the signed JWS object) using the recipient device's DNS name as the topic. Once the message is sent to the broker, the send_message.py script disconnects from the broker and exits.

At this point, the device should be running the proof-of-concept code, which was built by Balena.

Buy this article as PDF

Express-Checkout as PDF
Price $2.95
(incl. VAT)

Buy Linux Magazine

SINGLE ISSUES
 
SUBSCRIPTIONS
 
TABLET & SMARTPHONE APPS
Get it on Google Play

US / Canada

Get it on Google Play

UK / Australia

Related content

  • The State of Email

    Email encryption is not that difficult – and it is more important now than ever before. We take a look at some important tools and trends in email encryption.

  • Workspace: Digital Signatures

    We'll show you the free and easy way to set up digital signatures for office documents and email.

  • Thunderbird Security

    Thunderbird offers several options for secure email, and the GnuPG-based Enigmail encryption add-on provides an additional layer of protection.

  • Encrypting Email

    The leading email applications include new features for helping users secure and authenticate their mail messages, but each tool has a different approach to handling tasks such as signing and encryption. This article describes how to add encryption and digital signatures to the Thunderbird, Kmail, and Evolution mail clients.

  • The sys admin's daily grind: Let's Encrypt wildcards

    The pleasure of owning a nice domain like sensorenresidenz.de is clouded by the requirement of an X.509 certificate for every subdomain that the admin wants or has. Columnist Charly can help boost the webmaster's spirits.

comments powered by Disqus
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs
Subscribe to our ADMIN Newsletters

Support Our Work

Linux Magazine content is made possible with support from readers like you. Please consider contributing when you’ve found an article to be beneficial.

Learn More

News