LogoLogo
1.0.0
1.0.0
  • Introduction
  • High level architecture
  • Public key infrastructure
  • Terminology
  • Tutorials
    • Quickstart
    • Production Setup with NervesKey
  • Nerves Hub
    • Setup
      • Add NervesHub to your project
      • Connecting to your environment
      • Firmware signing keys
      • Products
      • Devices
      • Firmware
      • Deployments
    • Command-line tools
    • Managing organizations and products
    • Device management
    • HTTP API
    • Device WebSocket
  • Nerves Key
    • Introduction
    • NervesKey for Raspberry Pi
    • Private keys and certificates
    • General NervesKey storage
    • Provisioning in Elixir
    • Nerves integration
    • NervesHubLink integration
    • MQTT integration
Powered by GitBook
On this page
  • Selecting the authentication method
  • Device Certificate method
  • JITP method (Just-in-Time Provisioning)
  • Install the CLI
  • Create a Nerves project (or use an existing one)
  • Add new dependencies
  • Configuration
  • Build and deploy
  • Provision NervesKey
  • Managing the Signer Certificate and private key
  • Your serial number
  • The board name
  • Performing the provisioning
  • Set up your NervesHub product
  • Add Signer CA cert
  • Register device on NervesHub
  • Device shows up in NervesHub
  • Firmware signing
  • Create a firmware update
  • Upload firmware
  • Create a deployment
  • Confirm that the device updates
  • Sending more updates

Was this helpful?

  1. Tutorials

Production Setup with NervesKey

PreviousQuickstartNextSetup

Last updated 3 days ago

Was this helpful?

This tutorial will get a thorough setup with the easy to use but reasonably secure NervesKey hardware (Microchip ATECC608-series) to provide mTLS using device certificates for authentication against your NervesHub instance. This is not the simplest or fastest way to try NervesHub, for that, try the. It is not a massive undertaking either, so don't worry.

This guide is largely also applicable if using the LocalCert authentication method which is simply less secure but relevant if your hardware lacks a secure peripheral. It can also be applied for other HSM (Hardware Security Module) type devices but the tooling and support will vary. The NervesKey gets to be the example, but anything that offers an OpenSSL PKCS11 engine implementation should be possible to use.

A fair number of devices ship with an ATECC608 on them already but if you need a breakout for your prototyping you can and possibly a few other vendors.

We will:

  • Choose an authentication method

  • Create a Nerves project

  • Set up a NervesHub Product for our chosen method

  • Deploy a device

  • Create and sign firmware

  • Deploy a firmware update

  • Relax!

Selecting the authentication method

There are a variety of ways to use NervesHub's device authentication process. This guide focuses on the device certificate-based approaches. The covers the Shared Secret method.

This is the most explanation that will need to happen during this tutorial as you need to make a choice about your needs.

Device Certificate method

This is the recommended approach. It is secure, explicit and controlled.

This relies on knowing the individual device's public key/certificate and uploading those to NervesHub as part of manufacture or provisioning of the devices. This works well with the Microchip TrustNGo parts as well that come pre-provisioned. You create the device on NervesHub before it comes online and you add the device certificate information to the device in NervesHub. As part of the mTLS exchange we look up that the device exists and has a matching certificate in NervesHub. You essentially create an exact allow-list.

We still recommend uploading the Signer Certificate (aka. public key) as a Certificate Authority in NervesHub since that allows correlating which devices run certificates signed by which CA. Because the Device Certificate is uploaded before the device ever connects the CA is not strictly needed and this method would also work in a situation where you for some reason do not control the signing key.

JITP method (Just-in-Time Provisioning)

This is not the recommended approach but it has special use-cases.

JITP will not provision your NervesKey. It refers to provisioning the device onto NervesHub. A JITP setup requires the Signer Certificate to be uploaded as a Certificate Authority in NervesHub. It also requires enabling that key to be used specifically for JITP on a particular product.

When devices that hold a device certificate matching that CA connect we will trust the information they provide to provision them onto the platform. This approach is not or primary recommendation as the Device Certificate method is more explicit and gives you as a manufacturer of the device more control. The JITP approach has worse consequences if the Signer private key gets out.

JITP can work in unusual situations where the Device Certificate method will not work at all, so it is an option. This mostly applies where complex key infrastructure is in place.

Install the CLI

brew install nerves-hub/tap/nh

The second easiest is via curl:ing a shell script.

curl --proto '=https' --tlsv1.2 -fsSL https://raw.githubusercontent.com/nerves-hub/nerves_hub_cli/master/install.sh | sh

To set your NervesHub instance to use with the CLI, use this command, replacing the specific URL with your instance:

nh config set uri "https://manage.nervescloud.com/"

You need to be authorized with the NervesHub instance, you get that via:

nh user auth

You can test it out with:

nh device list --org my-org --product my_project

Then to avoid setting those org and product flags all the time you can set env vars. This means a tool like direnv can be helpful to manage per-project env vars:

export NERVES_HUB_ORG="my-org"
export NERVES_HUB_PRODUCT="my_project"

Create a Nerves project (or use an existing one)

mix nerves.new my_project
export MIX_TARGET=rpi4

After running that your Nerves-related mix commands will know which system you are targeting.

Add new dependencies

In your Nerves project find mix.exs and in the function called deps add:

{:nerves_hub_link, "~> 2.7"},
{:nerves_key, "~> 1.2"}

Now run:

mix deps.get

Configuration

Your config/target.exs is for configuration that applies to your target device. Since we added the nerves_key library NervesHubLink will attempt to use a NervesKey with the :primary key slot and default I2C bus.

config :nerves_hub_link,
  # Replace this with your instance device endpoint if hosting your own
  host: "devices.nervescloud.com",
  # Enable the very nice remote console
  remote_iex: true,
  # If you want to adjust config or be explicit you can uncomment these
  # configurator: NervesHubLink.Configurator.NervesKey
  # certificate_pair: :primary,
  # certificate_pair: :aux,
  # i2c_bus: 0

You can add this to your config/dev.exs and config/test.exs to stop NervesHubLink from connecting in development or test runs:

config :nerves_hub_link, connect: false

Some systems will be able to do networking using USB gadget mode which is great works. It doesn't always work and is sometimes not practical. It also won't provide Internet access. Which we need for NervesHub. Ethernet will work right out of the box if you have it. Otherwise Wi-Fi becomes your best option. Again, in config/target.exs you can set up your wlan0:

config :vintage_net,
  config: [
    {"usb0", %{type: VintageNetDirect}},
    {"eth0",
     %{
       type: VintageNetEthernet,
       ipv4: %{method: :dhcp}
     }},
    # Typically you only need to change the wlan0 config
    {"wlan0",
      %{
        type: VintageNetWiFi,
        vintage_net_wifi: %{
          networks: [
            %{
              key_mgmt: :wpa_psk,
              ssid: "your-network-name",
              psk: "your-network-password",
            }
          ]
        },
        ipv4: %{method: :dhcp},
      }
    }
  ]

Build and deploy

Remember to ensure you have MIX_TARGET set to the appropriate target and that you have run mix deps.get for the target. You can now build the firmware:

mix firmware

Typically then you'll use and SD card reader to burn the image onto an SD card. When working with Compute Modules and other non-SD devices there is some other process to get flashing done. This tutorial doesn't cover that, consult your system documentation:

mix burn

Then insert the SD card into the device and power it up.

It should transmit the nerves.local mDNS hostname that you can then SSH into.

ssh nerves.local

You need to ensure you have a way of SSH:ing into the device for the next step.

Provision NervesKey

Managing the Signer Certificate and private key

We will be generating important cryptographic secrets during this tutorial. The Signer Certificate private key is what lets you create hardware devices that you can verify cryptographically as yours. Put it in a secure place intended for secret management. Limit how many people have access to it. The devices you test this with will be permanently linked to that key. No take-backs.

Exactly how you manage this key during manufacturing and production of devices is a real challenge that is hard to give a single answer for. Put some thought into it, handle it with care.

You generate your Signer certificate and private key using a mix task that makes sure it matches the ATECC Compressed Certificate Definition:

mix nerves_key.signer create my_board_prod_signer_1

This produces two files:

  • my_board_prod_signer_1.cert - You can be sloppy with this one.

  • my_board_prod_signer_1.key - This is the secret one to be careful with.

Your serial number

The NervesKey will store a serial number of your own design as a manufacturer serial number. You are a manufacturer now, enjoy it. It is up to you to ensure uniqueness and have a satisfying and useful scheme for your product.

The board name

You also get to name the board, aka. the product. This is nice and informational. It is not used by NervesHub.

Performing the provisioning

We upload the cert and key to the device using sftp. It may work over scp but the Erlang SSH subsystem and nerves_ssh can be a bit particular so be mindful of that if you experiment. It really doesn't like Cyberduck for some reason.

$ sftp nerves.local
Connected to nerves.local.
sftp> cd /tmp
sftp> put my_board_prod_signer_1.*
Uploading my_board_prod_signer_1.cert to /tmp/my_board_prod_signer_1.cert
my_board_prod_signer_1.cert                                              100%  636    78.3KB/s   00:00
Uploading my_board_prod_signer_1.key to /tmp/my_board_prod_signer_1.key
my_board_prod_signer_1.key                                               100%  228    78.3KB/s   00:00
sftp> exit

Now we get to the fun part. Burning permanent unchangeable information into the hardware. If you are building a production device. Have multiple ATECC chips to work with during experimentation. You can screw up the chip if you make a mistake here.

Next we ssh nerves.local to get the IEx prompt:

cert_name="my_board_prod_signer_1"
manufacturer_sn = "MB000001"
board_name = "my_board"

signer_cert = File.read!("/tmp/#{cert_name}.cert") |> X509.Certificate.from_pem!;true
signer_key = File.read!("/tmp/#{cert_name}.key") |> X509.PrivateKey.from_pem!();true

{:ok, i2c} = ATECC508A.Transport.I2C.init([])
provision_info = %NervesKey.ProvisioningInfo{manufacturer_sn: manufacturer_sn, board_name: board_name}

# Double-check what you typed above before running this
NervesKey.provision(i2c, provision_info, signer_cert, signer_key)

To verify that you NervesKey is provisioned you can run the following and get your public key/device certificate:

{:ok, i2c} = ATECC508A.Transport.I2C.init([])
true = NervesKey.provisioned?(i2c)
cert = NervesKey.device_cert(i2c)
X509.Certificate.to_pem(cert) |> IO.puts()

Grab that and put it in MB000001.cert on your local machine. This is a public key and so not particularly sensitive.

Set up your NervesHub product

You should already have an organization in your name. Or you can create a separate one. Selecting the org should take you to the Products view. Hit the button for creating a new Product. We can use the defaults but we want to add a name. There is a convenience in using the same name for this as your Nerves project so let's use "my_project".

Add Signer CA cert

On the Organisation view you will find a section called Certificates. You can add your CA cert there. This is mandatory for the JITP method and strongly recommended for the Device Certificate method. Easiest is to upload using the CLI:

nh cacert register my_board_prod_signer_1.cert

JITP method: If using JITP there is a checkbox for enabling Just In Time Provisioning. The important part is to then select your Product from the dropdown menu.

Register device on NervesHub

If using JITP you can skip this step as that is what JITP will do for you.

Otherwise you want to create the device on NervesHub and attach the certificate so it can be allow-listed for connecting later on.

nh device create

It will prompt you for additional details. I will assume you enter MB000001 for the serial number. There are flags for automatically providing the serial and so on if you want to script it later.

With that created, we can then import the certificate from before:

nh device cert import MB000001 MB000001.cert

This will upload your certificate and associate it with your device on NervesHub. For a manufactured batch you'd typically do this based on a CSV file of provisioned devices or something to that effect.

Device shows up in NervesHub

Given a bit of time and if it has Ethernet or Wi-Fi it should reach NervesHub and show up in the Devices list of your product.

If it fails to show up, SSH to the device and run RingLogger.next to see why it fails. You can also use NervesHubLink.reconnect() to trigger a reconnect.

If you used the JITP method the device won't have existed before and should have been automatically created. From then on it will have a device certificate and be allow-listed just like under the Device Certificate method.

Firmware signing

To update devices with new firmware the firmware must be cryptographically signed. We create a signing key like this:

nh key create my-key

It will ask you for a password and then produce a public key while saving the password-protected key in a special directory. It will also upload the public key to NervesHub as a firmware signing key attached to your organization. Keys can also be added on your Organization in the web UI by visiting Signing Keys.

This is the key that allows people to put new firmware on your device and it should be treated with a lot of care. Put it in the same type of secret management you use for the Signer CA key we created earlier

Create a firmware update

Let's modify the project. We don't have to do real work on it, we can just grabmix.exs and bump the version number. Then we build it:

mix firmware

Then you can run this to sign it:

nh firmware sign "./_build/${MIX_TARGET}_dev/nerves/images/my_project.fw" --key my-key

It will prompt you for the password then your .fw file should be fully signed.

Upload firmware

Again we use the CLI:

nh firmware publish "./_build/${MIX_TARGET}_dev/nerves/images/my_project.fw"

It should ask for confirmation and then show a progress bar for the upload. You can also perform an upload by going to your Product in the web UI and visiting the Firmwares section.

Create a deployment

Your firmware has a UUID that is occasionally useful. We can get it via fwup which is the tool that does all the interesting stuff with firmware:

fwup -i "./_build/${MIX_TARGET}_dev/nerves/images/my_project.fw" -m --metadata-key meta-uuid

Then we can use this UUID for setting up a deployment, or again, we can do this from the web UI.

nh deployment create --name "My deployment" --firmware "UUID_GOES_HERE" --version "" --tag "main"

The deployment is not active by default. To turn it on run:

nh deployment update "My deployment" state on

Confirm that the device updates

The device should be automatically added to the Deployment we just created since it wasn't already associated to one. And it should then be selected for receiving the new update.

If the update doesn't happen or you don't want to wait, you can hit the Reconnect button to force the device to reconnect to NervesHub or you can manually add the Deployment or even manually send the firmware. All from the web UI in the Device detail view.

Sending more updates

Future versions do not need as many commands:

mix firmware
export FW_PATH="./_build/${MIX_TARGET}_dev/nerves/images/my_project.fw"
nh firmware sign $FW_PATH --key my-key
nh firmware publish $FW_PATH --deploy "My deployment"

And of course you can build out your own script around this if you like.

Congratulations! Your Nerves device can now enjoy the splendor of NervesHub.

Check in on your device and see if it is reporting Health data, maybe a geo-location and try the console.

The easiest way to install is via .

More details and alternative installation methods are available in .

Assuming you've you should be able to run the following command:

This gets you a Nerves project including a bunch of the by default. We will pretend that you are using a Raspberry Pi 4 for this guide.

Ensure the device you have has a NervesKey/ATECC608 peripheral attached on I2C. There are a lot of things you might want to know about the NervesKey and provide a lot of detail. Here we focus on using it. First we have to provision it, this means adding the necessary information, locking in the config of the device and generating a private key inside the device that will never see the light of day.

Now log in to the web UI of your NervesHub instances. For NervesCloud this is.

Assuming your device shows up at this point we move on. If you can't get it working, consult of the Elixir Forum and feel free to ask for help.

quickstart
get one from Adafruit
quickstart
Homebrew
the repo
installed Nerves
supported Nerves systems
the docs
manage.nervescloud.com
the Nerves section