5.6 KiB
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"
!!! warning "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.
!!! note "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
.
!!! note "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 & IP Address")
spk["private key"]
ssi["sign csr"]
end
spk -.-> skc["server key & cert"]
ca_cred --> ssi
spk --> scsr
con["country, org<br>& 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 & 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
::: amqtt.contrib.cert.UserAuthCertPlugin.Config options: show_source: false heading_level: 4 extra: class_style: "simple"
Key and Certificate Generation
::: mkdocs-typer2 :module: amqtt.scripts.ca_creds :name: ca_creds
::: mkdocs-typer2 :module: amqtt.scripts.server_creds :name: server_creds
::: mkdocs-typer2 :module: amqtt.scripts.device_creds :name: device_creds