Device authentication & mTLS

The two ways devices authenticate to NervesHub (shared secrets and X.509 certificates), when to use each, and where keys live (NervesKey, TPM, file).

Written By Josh Kalderimis

Last updated About 2 months ago

Every device proves who it is before NervesHub lets it connect. There are two families of authentication: shared secrets and certificates (mTLS). This page explains how each works, when to choose one over the other, and where a device's private key can live.

The two options at a glance

Shared secret

Certificate (mTLS)

Identity

A product key + secret shared by devices

A unique X.509 certificate per device

Crypto

HMAC token at connection time

Mutual TLS with a private key that never leaves the device

Onboarding

Paste two values into config

Provision a cert/key per device

Per-device revocation

No (revoke the whole product secret)

Yes (per device)

Best for

Hobby projects, R&D, prototypes

Production fleets

Setup effort

Minimal

Higher (provisioning + a Signer CA)

You can start with a shared secret to get moving and switch to certificates before you ship at scale.

Shared secrets

A shared secret is a product key and product secret you generate in a product's settings. Devices include them in their nerves_hub_link config, and the client uses HMAC to generate an authentication token when it opens the websocket.

config :nerves_hub_link,
  host: "devices.nervescloud.com",
  shared_secret: [
    product_key: "<product_key>",
    product_secret: "<product_secret>"
  ]

Use shared secrets when:

  • You're prototyping or in early R&D and want the fastest path to a connected device.

  • You don't yet need per-device identity or revocation.

  • Your devices don't have secure-element hardware.

Be aware:

  • Every device using that secret shares one credential. If it leaks, you rotate the product secret and reconfigure devices.

  • There's no per-device revocation. To remove one device you can delete it, but the secret itself is fleet-wide.

Shared secret auth must be enabled on the server (DEVICE_SHARED_SECRETS_ENABLED when self-hosting).

Certificates (mTLS)

For production, each device gets its own X.509 device certificate and a matching private key. The device presents the certificate over mutual TLS, and it proves possession of the private key. Because the key never leaves the device, this is strong, per-device identity.

Device certificates are signed by a Signer certificate (a Certificate Authority, or CA) that you create and control. This is a self-signed CA registered to your NervesHub organization, not a public CA like Let's Encrypt. Keep its private key secret: it's what lets you mint devices that NervesHub will trust.

How NervesHub validates a device

When a device connects over TLS, NervesHub checks its certificate roughly like this:

  1. It fingerprints the certificate. If that exact certificate is already on file (and not expired), the device is allowed.

  2. If the certificate is new but its public key is known, NervesHub confirms the device ID matches and that a registered Signer CA in the same organization signed it, then records the rotated certificate. (Two devices may never share a public key.)

  3. If neither is known, NervesHub reads the device ID (the certificate's common name), verifies a registered Signer CA signed it, validates the certificate chain, and looks the device up in that organization.

Think of it as a split password: NervesHub holds the public side, and the device proves it holds the private side. The Signer CA is what lets NervesHub trust a certificate it hasn't seen before.

Two ways devices get registered

  • Pre-registered (Device Certificate method): you upload each device's certificate to NervesHub ahead of time, creating an explicit allow-list. Recommended.

  • JITP (Just-In-Time Provisioning): devices holding a certificate signed by a trusted Signer CA are registered automatically on first connect. Useful when pre-registration isn't practical, but it gives you less control.

Both are walked through in Production setup with NervesKey.

Use certificates when:

  • You're going to production or manufacturing at any real scale.

  • You need per-device identity, rotation, and revocation.

  • You want the private key protected by secure hardware.

Where the private key lives

With certificate auth, the biggest security question is where the device's private key is stored. Options, from most to least protected:

Option

Key storage

Notes

NervesKey

Secure element (ATECC608)

Key generated on-chip, never extractable. Recommended for production.

TPM

Trusted Platform Module

Hardware-backed key storage via the tpm library.

File (LocalCertKey)

Cert and key on the filesystem

No secure hardware needed; least protected.

Any hardware that exposes an OpenSSL engine can work in principle; NervesKey and TPM are the supported, documented paths.

NervesKey

NervesKey uses a Microchip ATECC608 secure element. The private key is generated inside the chip during provisioning and can never be read out, so even full access to the device's filesystem doesn't expose it. If you add nerves_key as a dependency, NervesHubLink reads the certificate and key from the chip automatically:

# mix.exs
{:nerves_key, "~> 1.2"}
# config/target.exs 
# (nerves_key present, so this is all you need)
config :nerves_hub_link, 
  host: "devices.nervescloud.com"

It defaults to I2C bus 1 and the :primary certificate pair (the :primary pair is one-time configurable; :aux can be updated later). Override if needed:

config :nerves_hub_link, :nerves_key,
  certificate_pair: :aux,
  i2c_bus: 0

TPM

If your device has a Trusted Platform Module and you use the tpm library, NervesHubLink can read the key and certificate from the module and set up TLS for you:

# mix.exs
{:tpm, "~> 0.2.0"}
# config/target.exs
config :nerves_hub_link, 
  host: "devices.nervescloud.com"

Defaults: it loads the tpm_tis_spi kernel module, reads the private key from /data/.ssh/nerves_hub_link_key, and reads the certificate from memory address 0x1000001. Override any of these:

config :nerves_hub_link, :tpm,
  probe_name: "tpm_tis_i2c",
  key_path: "/data/.ssh/nerves_hub_link/key",
  certificate_address: "0x1000002"

File-based (LocalCertKey)

Without a secure element, you can store the certificate and key on the filesystem. This is the least protected option, but it needs no special hardware:

config :nerves_hub_link,
  host: "devices.nervescloud.com",
  configurator: NervesHubLink.Configurator.LocalCertKey

Defaults are /data/nerves_hub/cert.pem and /data/nerves_hub/key.pem. Point elsewhere with ssl: [certfile: ..., keyfile: ...].

Choosing

  • Prototyping or a hobby project? Start with a shared secret. It's two config values and you're connected.

  • Shipping to production? Use certificates, and store the key in NervesKey (or TPM) so it can't be extracted. Fall back to file-based storage only if the hardware has no secure element.

Related