Skip to content

Authentication Using Signed Certificates¤

Using client-specific certificates, signed by a common authority (even if self-signed) provides a highly secure way of authenticating mqtt clients. Often used with IoT devices where a unique certificate can be initialized on initial provisioning.

With so many options, X509 certificates can be daunting to create with openssl. Included are command line utilities to generate a root self-signed certificate and then the proper broker and device certificates with the correct X509 attributes to enable authenticity.

Quick start¤

Generate a self-signed root credentials and server credentials:

$ ca_creds --country US --state NY --locality NY --org-name "My Org's Name" --cn "my.domain.name"
$ server_creds --country US --org-name "My Org's Name" --cn "my.domain.name"

Security of private keys

Your root credential private key and your server key should never be shared with anyone. The certificates -- specifically the root CA certificate -- is completely safe to share and will need to be shared along with device credentials when using a self-signed CA.

Include in your server config:

listeners:
  ssl-mqtt:
    bind: "127.0.0.1:8883"
    ssl: true
    certfile: server.crt
    keyfile: server.key
    cafile: ca.crt
plugins:
  amqtt.contrib.cert.CertificateAuthPlugin:
    uri_domain: my.domain.name

Generate a device's credentials:

$ device_creds --country US --org-name "My Org's Name" --device-id myUniqueDeviceId --uri my.domain.name

And use these to initialize the MQTTClient:

import asyncio
from amqtt.client import MQTTClient

client_config = {
    'keyfile': 'myUniqueDeviceId.key',
    'certfile': 'myUniqueDeviceId.crt',
    'broker': {
        'cafile': 'ca.crt'
    }
}

async def main():
    client = MQTTClient(config=client_config)
    await client.connect("mqtts://my.domain.name:8883")
    # publish messages or subscribe to receive 

asyncio.run(main())

Background¤

Often used for IoT devices, this method provides the most secure form of identification. A root certificate, often referenced as a CA certificate -- either issued by a known authority (such as LetsEncrypt) or a self-signed certificate) is used to sign a private key and certificate for the server. Each device/client also gets a unique private key and certificate signed by the same CA certificate; also included in the device certificate is a 'SAN' or SubjectAlternativeName which is the device's unique identifier.

Since both server and device certificates are signed by the same CA certificate, the client can verify the server's authenticity; and the server can verify the client's authenticity. And since the device's certificate contains a x509 SAN, the server (with this plugin) can identify the device securely.

URI and Client ID configuration

uri_domain configuration must be set to the same uri used to generate the device credentials

when a device is connecting with private key and certificate, the client_id must match the device id used to generate the device credentials.

Available ore three scripts to help with the key generation and certificate signing: ca_creds, server_creds and device_creds.

Configuring broker & client for using Self-signed root CA

If using self-signed root credentials, the cafile configuration for both broker and client need to be configured with cafile set to the ca.crt.

Root & Certificate Credentials¤

The process for generating a server's private key and certificate is only done once. If you have a private key & certificate -- such as one from verifying your webserver's domain with LetsEncrypt -- that you want to use, pass them to the server_creds cli. If you'd like to use a self-signed certificate, generate your own CA by running the ca_creds cli (make sure your client is configured with check_hostname as False).

---
config:
  theme: redux
---
flowchart LR
 subgraph ca_cred["ca_cred #40;cli#41; or other CA"]
    ca["ca key & cert"]
 end

 subgraph server_cred["server_cred fl°°40¶ßclifl°°41¶ß"]
        scsr("certificate signing<br>request fl°°40¶ßCSRfl°°41¶ß with<br>SAN of DNS &amp; IP Address")
        spk["private key"]
        ssi["sign csr"]
  end


    spk -.-> skc["server key & cert"]
    ca_cred --> ssi
    spk --> scsr
    con["country, org<br>&amp; common name"] --> scsr
    scsr --> ssi
    ssi --> skc

Device credentials¤

For each device, create a device id to generate a device-specific private key and certificate using the device_creds cli. Use the same CA as was used for the server (above) so the client & server recognize each other.

---
config:
  theme: redux
---
flowchart LR
 subgraph ca_cred["ca_cred #40;cli#41; or other CA"]
    ca["ca key & cert"]
 end
 subgraph device_cred["device_cred fl°°40¶ßclifl°°41¶ß"]
        ccsr("certificate signing<br>request fl°°40¶ßCSRfl°°41¶ß with<br>SAN of URI &amp; DNS")
        cpk["private key"]
        csi["sign csr"]
  end
    cpk --> ccsr
    csi --> ckc[device key & cert]
    cpk -.-> ckc
    ccsr --> csi
    ca_cred --> csi

    con["country, org<br/>common name<br/>& device id"] --> ccsr

Configuration¤

Config dataclass ¤

Configuration for the CertificateAuthPlugin.

uri_domain instance-attribute ¤
uri_domain: str

The domain that is expected as part of the device certificate's spiffe (e.g. test.amqtt.io)

Key and Certificate Generation¤

ca_creds¤

Generate a self-signed key and certificate to be used as the root CA, with a key size of 2048 and a 1-year expiration.

Usage:

console $ ca_creds [OPTIONS]

Options:

  • --country TEXT: x509 'country_name' attribute [required]
  • --state TEXT: x509 'state_or_province_name' attribute [required]
  • --locality TEXT: x509 'locality_name' attribute [required]
  • --org-name TEXT: x509 'organization_name' attribute [required]
  • --cn TEXT: x509 'common_name' attribute [required]
  • --output-dir TEXT: output directory [default: /home/docs/checkouts/readthedocs.org/user_builds/amqtt/checkouts/latest]
  • --help: Show this message and exit.

server_creds¤

Generate a key and certificate for the broker in pem format, signed by the provided CA credentials. With a key size of 2048 and a 1-year expiration.

Usage:

console $ server_creds [OPTIONS]

Options:

  • --country TEXT: x509 'country_name' attribute [required]
  • --org-name TEXT: x509 'organization_name' attribute [required]
  • --cn TEXT: x509 'common_name' attribute [required]
  • --output-dir TEXT: output directory [default: /home/docs/checkouts/readthedocs.org/user_builds/amqtt/checkouts/latest]
  • --ca-key TEXT: server key output filename. [default: ca.key]
  • --ca-crt TEXT: server cert output filename. [default: ca.crt]
  • --help: Show this message and exit.

device_creds¤

Generate a key and certificate for each device in pem format, signed by the provided CA credentials. With a key size of 2048 and a 1-year expiration.

Usage:

console $ device_creds [OPTIONS]

Options:

  • --country TEXT: x509 'country_name' attribute [required]
  • --org-name TEXT: x509 'organization_name' attribute [required]
  • --device-id TEXT: device id for the SAN [required]
  • --uri TEXT: domain name for device SAN [required]
  • --output-dir TEXT: output directory [default: /home/docs/checkouts/readthedocs.org/user_builds/amqtt/checkouts/latest]
  • --ca-key TEXT: root key filename used for signing. [default: ca.key]
  • --ca-crt TEXT: root cert filename used for signing. [default: ca.crt]
  • --help: Show this message and exit.