Introduces a CA/PKI module so field devices can authenticate to Mosquitto over TLS (8883) with per-device client certificates (CN = serial number) instead of a shared password, with matching Devices/MQTT-Certs UI. Adds live transmitter position tracking alongside logged points, an MQTTS transport option in the simulator for exercising the real cert-auth path, and Swagger API docs at /api/docs. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
5.9 KiB
There is nothing fundamentally wrong with the approach, provided the device’s private key never leaves the device. The main complication is that the phone—not the BLE device—is establishing the MQTT/TLS connection.
The key distinction
The certificate itself is public. Authentication occurs because the client proves possession of the corresponding private key by signing part of the TLS handshake. In TLS 1.3, this happens in the CertificateVerify step. (IETF Datatracker)
Therefore, simply reading the certificate over BLE and giving it to the phone is insufficient. You need one of these architectures.
1. Device acts as a remote TLS signer
The phone performs the MQTT/TLS connection but delegates private-key operations to the BLE device:
MQTT broker
↕ TLS
Phone MQTT/TLS client
↕ BLE signing request
Embedded device/private key
During the TLS handshake:
- Phone sends the device certificate.
- TLS library generates the
CertificateVerifydata. - Phone sends the digest/signing request over BLE.
- Device signs it using its private key.
- Device returns only the signature.
- Phone completes mutual TLS.
This is cryptographically valid and is similar to using a secure element, smart card, TPM, or hardware-backed keystore. Hardware-backed key systems are specifically designed so applications can use a key without extracting it. (Android Open Source Project)
The difficulty is implementation: your Android/iOS MQTT and TLS libraries must support an external or asynchronous private-key signer. Many high-level MQTT libraries expect a conventional PrivateKey object and do not readily support a BLE-backed signing operation.
2. Device signs an application-level authentication challenge
This is usually easier and often cleaner:
Phone → server: Request device-authentication challenge
Server → phone: Random nonce
Phone → BLE device: Sign nonce + context
Device → phone: Signature
Phone → server: Certificate + signature
Server → phone: Short-lived MQTT credential
Phone → MQTT broker: Connect using short-lived credential
The short-lived MQTT credential could be:
- A JWT or similar access token
- A temporary username/password
- A temporary MQTT client certificate
- A session credential issued by your backend
I would generally favor this architecture. It lets your backend authenticate the device certificate while the phone uses an ordinary MQTT client library.
Include at least this in the signed data:
protocol_version
device_serial_number
server_nonce
intended_server/audience
timestamp or expiration
requested permissions
phone/app session identifier
That prevents the signature from being reused for another server, session, or purpose.
Principal security concerns
Do not export the private key
Sending both the certificate and private key to the phone eliminates much of the value of provisioning a unique device credential. A compromised or rooted phone could copy the identity permanently.
Only send signatures from the device.
Secure the BLE connection
BLE should use authenticated LE Secure Connections, preferably Numeric Comparison, Passkey Entry, or an equivalent product-specific enrollment process. “Just Works” pairing encrypts traffic but does not provide meaningful protection against an active man-in-the-middle during pairing. (Nordic Developer Academy)
For stronger protection, add application-layer security and mutual device/app authentication rather than relying solely on BLE pairing.
Avoid creating an unrestricted signing oracle
Do not expose a generic BLE command such as:
SIGN arbitrary_bytes
A malicious phone application could use the device to sign unrelated material.
Instead expose a narrowly scoped operation:
AUTHENTICATE_MQTT(challenge, broker_id, expiration)
The firmware should:
- Validate the request format
- Require an approved broker or backend identifier
- Apply a domain-separation prefix such as
MYPRODUCT-MQTT-AUTH-V1 - Reject stale challenges
- Rate-limit attempts
- Optionally require that the phone was previously provisioned or bonded
Broker authorization remains necessary
A valid certificate should identify the device, but it should not automatically grant access to every MQTT topic. Use broker ACLs tied to the authenticated device identity, for example:
devices/DEVICE123/telemetry publish
devices/DEVICE123/commands subscribe
MQTT itself recommends using authentication, authorization, and secure communication mechanisms for sensitive systems. (OASIS Open)
My recommendation
For a phone acting as a passive BLE-to-cloud data ferry, I would use:
Permanent private key: embedded device only
Device certificate: embedded device, readable by phone
Device authentication: signed server challenge over BLE
Phone MQTT credential: short-lived token or temporary certificate
MQTT authorization: device-specific topic ACL
BLE protection: authenticated pairing plus app-level binding
That preserves the device’s permanent identity, works with normal phone MQTT libraries, supports credential expiration and revocation, and prevents the permanent private key from ever reaching the phone.
Direct BLE-backed TLS signing is also sound, but it is substantially harder to integrate and may tie you to a particular TLS/MQTT implementation.