# Introduction

{% hint style="warning" %}
We're busy adding and updating the NervesHub documentation to make it easier to use NervesHub. In particular, the NervesHub web user interface is being reorganized so we're only lightly documenting it now. If something doesn't make sense, please let us know via a GitHub issue.
{% endhint %}

NervesHub is an open-source firmware update server that works well with Nerves-based devices.

NervesHub provides many of the features that you'd expect in a firmware update server. Fundamentally, devices connect to the server either by polling at a pre-configured interval or by joining a long-lived [Phoenix channel](https://hexdocs.pm/phoenix/channels.html). If a firmware update is available, NervesHub provides a URL to the device and the device can update immediately or when convenient.

NervesHub leverages best practices and libraries from the Elixir community to provide a service that scales from hobbyist use to large scale industrial deployments. Since all components are open-source, there is no lock in. Extensive regression tests enable you to extend NervesHub with confidence if necessary. Of course, we hope that you will contribute improvements back.

Security is a primary concern of NervesHub:

* Cryptographic signatures on firmware updates provide end-to-end authentication
* Client-side and server-side SSL provide link authentication and encryption
* Access control within organizations to firmware updates

NervesHub provides the tools for safe remote updates. While it's not possible for the firmware update server alone to ensure that firmware updates are always safe, NervesHub does provide lightweight verification of firmware and detects misbehaving devices.


# High level architecture

The following diagram shows the primary NervesHub components.

![NervesHub architecture](/files/-LfLhwC8Rahs5casg1bC)

The NervesHub server provides web and API endpoints for most interactions. The actual firmware storage and delivery resides on an object storage service such as AWS S3.

While NervesHub could operate with many firmware update file formats in principle, it currently expects files using the format provided by [fwup](https://github.com/fhunleth/fwup). The fwup tool provides the low level packaging and application of firmware images. Firmware files are ZIP-formated files with metadata.

User interaction with NervesHub is through the web interface and via a CLI provided by an extension to Elixir's mix build tool. The CLI tools and their underlying User API endpoint are useful for automating common tasks.

Devices connect to NervesHub via a separate API endpoint that exposes only the functionality needed for firmware updates. For Nerves-based devices, the [nerves\_hub](https://github.com/nerves-hub/nerves_hub) library manages the interactions with the NervesHub server and invokes fwup to apply updates.

The final component is the NervesHub Certificate Authority (CA). This component issues the client-side SSL certificates for the User API. Devices also use client-side SSL, but those certificates are issued and managed by you, the user of NervesHub.


# Public key infrastructure

NervesHub uses [asymmetric cryptography](https://en.wikipedia.org/wiki/Public-key_cryptography) for authenticating both sides of the connections to NervesHub and for end-to-end validation of firmware images. As with any use of asymmetric cryptography, a public key infrastructure (PKI) is required for establishing trust. The PKI for NervesHub is mostly managed by organizations using the service. Choices in NervesHub are intentionally compatible with other IoT services such as AWS IoT so that devices need only be provisioned with one private key rather than having a private key per service. There is no requirement that prevents devices from being issued many private keys.

## Client-side SSL for devices

Before use, each device must be provisioned with a signed X.509 certificate and its associated private key. When devices connect to NervesHub they'll validate NervesHub's certificate. Likewise, NervesHub validates these client certificates.

Device certificates are signed by a Device CA certificate owned by you. You will register one or more Device CA certificates with NervesHub. The private keys associated with these certificates are never seen by NervesHub. If necessary, you can secure them in hardware security modules (HSMs).

When a device connects for the first time, the device must present both its public certificate and the Device CA Certificate that signed it. Assuming that NervesHub knows the Device CA Certificate and that the Device Certificate has been signed properly, it will allow the device to connect. The Device Certificate is recorded on NervesHub to avoid verifying the chain of trust in the future. This also enables scenarios where Device CA Certificates have short validity time periods or are deleted once all devices signed by it have all connected.

The NervesHub CLI tools provide convenience functions for creating certificates. It's also possible to use openssl or the Elixir [x509 library](https://hex.pm/packages/x509) directly.

Care should be taken when handling the Device CA and device private keys. Compromised private keys can be used to access firmware updates and likely cause other mischief. Consider integrating a hardware storage chip into your device if it does not natively support secure storage. See [NervesKey](/1.0.0/nerves-key/getting-started) for one solution.

## Firmware update signatures

NervesHub requires cryptographic signatures on all firmware files. As opposed to the X.509 certificates used earlier, this functionality is provided by [libsodium's public key support](https://download.libsodium.org/doc/public-key_cryptography/public-key_signatures). The fwup commandline utility invokes libsodium to create key pairs, sign firmware update files, and verify signatures.

The public keys used to sign firmware update files must be uploaded to NervesHub and accessible to the device's firmware. By default, the NervesHub tooling embedded public keys in the device firmware. Both NervesHub and devices validate firmware signatures. The device validation is the important step since it ensures that the firmware that you generated is identical to the firmware being installed.

NervesHub requires users to register firmware public keys with it. This is used to catch mistakes that would otherwise waste time and bandwidth with deploying firmware that could never install correctly.

Users should protect firmware signing keys. It is possible to register multiple firmware public keys on NervesHub and on devices. This allows more convenient setups where development devices can accept both "production" and "debug" firmware. Devices in the field, though, could have only the "production" public keys and therefore reject accidental deploys of "debug" firmware. Of course, NervesHub supports more mechanisms to group devices so relying on firmware signing keys can be considered a last resort.


# Terminology

This section defines important words used throughout the NervesHub documentation.

## Auxiliary Device Certificate

An auxiliary device certificate is a secondary device certificate stored on a NervesKey to support development use cases.

## Device

A device is a client of NervesHub that receives firmware updates

## Device Certificate

A device certificate is an X.509 certificate that identifies a device. Each device certificate contains a public key, the device's serial number, validity and other information. NervesHub uses device certificates to authenticate connections from devices.

## Device CA Certificate

A device CA certificate is an X.509 certificate used by device manufacturers to cryptographically sign device certificates. NervesHub uses device CA certificates to validate device certificates.

## Deployment

A deployment specifies a firmware and a match condition on devices. NervesHub notifies all devices matching the condition to download the new firmware.

## Firmware

Firmware refers to the file that's uploaded to NervesHub, stored, and sent to devices. The most common case is that the file contains the software that runs on the device, but this is not a requirement. NervesHub supports firmware created by the [fwup](https://github.com/fhunleth/fwup) utility.

## Firmware signing keys

Firmware signing keys refer to the raw or base64-encoded public and private keys used to sign and authenticate firmware files. NervesHub and devices hold public keys and verify firmware files before processing them. Firmware signatures provide an end-to-end assurance that the firmware installed on a device has not been modified by NervesHub or anyone else.

## Firmware UUID

This is a globally unique firmware identifier created from the contents of the firmware. Two firmware with the same UUID install the bit-for-bit identical software on devices. Likewise firmware with different UUIDs have some difference even if their metadata reports their version numbers to be the same.

## NervesKey

A NervesKey is a hardware module that protects a device's private key and is used to authenticate a device with NervesHub.

## Product

Products organize related devices, firmware, and deployments. Organizations and users can have one or more products. Devices, firmware, and deployments only belong to one product at a time.


# Quickstart

This tutorial will get you up and running with a device on your NervesHub\
instance with minimal fuss. It uses the simplest approach. We offer[a separate guide](/1.0.0/tutorials/index-1) for production deployments with security hardware in\
place.

We will:

* Create a Nerves project
* Set up a NervesHub product for Shared Secret auth
* Deploy a device
* Create and sign firmware
* Deploy a firmware update
* Rejoice!

## Create a Nerves project (or use an existing one)

Assuming you've [installed Nerves](https://hexdocs.pm/nerves/installation.html)\
you should be able to run the following command:

```sh
mix nerves.new my_project
```

This gets you a Nerves project including a bunch of the [supported Nerves\
systems](https://hexdocs.pm/nerves/supported-targets.html#supported-targets-and-systems)\
by default. We will pretend that you are using a Raspberry Pi 4 for this guide.

```sh
export MIX_TARGET=rpi4
```

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

## Set up your NervesHub product

Now log in to the web UI of your NervesHub instances. For NervesCloud this is[manage.nervescloud.com](https://manage.nervescloud.com).

You should already have an organization in your name. Selecting it 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"`.

Select your Product, go to the Settings of the Product and find the section for\
Shared Secret authentication. Hit the button for creating a new Shared Secret.

## Add new dependencies

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

```elixir
{:nerves_hub_link, "~> 2.7"},
```

Now run:

```sh
mix deps.get
```

## Configuration

Your `config/target.exs` is for configuration that applies to your target device. Here you can grab those Shared Secret credentials you added to your product and add them:

```elixir
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,
  shared_secret: [
    product_key: "YourProductKey",
    product_secret: "YourProductSecret",
  ]
```

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

```elixir
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:

```sh
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:

```sh
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.

## 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.

Sometimes it can take a while before the device clock is updated and picked up\
by the Erlang runtime. In that case you may see connection errors as the\
Shared Secret cryptography requires the clock to be something reasonable.

Assuming your device shows up at this point we move on. If you can't get it\
working, consult [the Nerves section](https://elixirforum.com/c/nerves-forum)\
of the Elixir Forum and feel free to ask for help.

## Install the CLI

The easiest way to install is via [Homebrew](https://brew.sh/).

```
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
```

More details and alternative installation methods are available in [the repo](https://github.com/nerves-hub/nerves_hub_cli).

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

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

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

```sh
nh user auth
```

You can test it out with:

```sh
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:

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

## Firmware signing

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

```sh
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.

## Create a firmware update

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

```sh
mix firmware
```

Then you can run this to sign it:

```sh
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:

```sh
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:

```sh
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.

```sh
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:

```sh
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:

```sh
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 delights of NervesHub.

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


# Production Setup with NervesKey

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[quickstart](/1.0.0/tutorials/index). 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[get one from Adafruit](https://www.adafruit.com/product/4314) 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[quickstart](/1.0.0/tutorials/index) 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

The easiest way to install is via [Homebrew](https://brew.sh/).

```
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
```

More details and alternative installation methods are available in [the repo](https://github.com/nerves-hub/nerves_hub_cli).

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

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

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

```sh
nh user auth
```

You can test it out with:

```sh
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)

Assuming you've [installed Nerves](https://hexdocs.pm/nerves/installation.html)\
you should be able to run the following command:

```sh
mix nerves.new my_project
```

This gets you a Nerves project including a bunch of the [supported Nerves\
systems](https://hexdocs.pm/nerves/supported-targets.html#supported-targets-and-systems)\
by default. We will pretend that you are using a Raspberry Pi 4 for this guide.

```sh
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:

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

Now run:

```sh
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.

```elixir
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:

```elixir
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:

```sh
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:

```sh
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.

```sh
ssh nerves.local
```

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

## Provision NervesKey

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 [the\
docs](https://hexdocs.pm/nerves_key/readme.html) 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.

### 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:

```sh
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.

```sh
$ 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:

```elixir
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:

```elixir
{: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

Now log in to the web UI of your NervesHub instances. For NervesCloud this is[manage.nervescloud.com](https://manage.nervescloud.com).

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:

```sh
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.

```sh
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:

```sh
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.

Assuming your device shows up at this point we move on. If you can't get it\
working, consult [the Nerves section](https://elixirforum.com/c/nerves-forum)\
of the Elixir Forum and feel free to ask for help.

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:

```sh
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 grab`mix.exs` and bump the version number. Then we build it:

```sh
mix firmware
```

Then you can run this to sign it:

```sh
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:

```sh
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:

```sh
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.

```sh
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:

```sh
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:

```sh
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.


# Setup

## Getting Started

The following sections will walk you through updating your Nerves project to work with your NervesHub server.

Many of the following steps may feel manual, but they can and are automated by NervesHub users to set up automatic firmware updates from CI and to manufacture large numbers of devices.


# Add NervesHub to your project

The first step is to add [`nerves_hub_link`](https://github.com/nerves-hub/nerves_hub_link) to your target dependencies in your project's `mix.exs`. Since NervesHub uses SSL certificates, the time must be set on the device or certificate validity checks will fail. If you're not already setting the time, add [`nerves_time`](https://hex.pm/packages/nerves_time) to your dependencies. For example:

```elixir
  defp deps(target) do
    [
      {:nerves_runtime, "~> 0.9"},
      {:nerves_hub_link, "~> 2.0"},
      {:nerves_hub_cli, "2.0.0-dev"},
      {:nerves_time, "~> 0.2"},
      ...
    ] ++ system(target)
  end
```

Next, update your `config.exs` so that the [`nerves_hub_link`](https://github.com/nerves-hub/nerves_hub_link) library can help provision devices. Do this by adding `provisioning: :nerves_hub_link` to the `:nerves, :firmware` option like this:

```elixir
config :nerves, :firmware,
  provisioning: :nerves_hub_link
```

Adding this library will start a long-lived websocket connection with NervesHub for communicating device updates.

## Connection Setup

See the [`NervesKey: NervesHubLink Integration`](/1.0.0/nerves-key/integration-with-nerveshublink#integration-with-your-nerves-project) section for connection setup if you are using NervesKey

If you aren't using NervesKey, you can also provide your own options to use for the NervesHub socket connection via the `:socket`and `:ssl` keys, which are forwarded on to `slipstream` when creating the socket connection.

Any [valid Erlang ssl socket option](http://erlang.org/doc/man/ssl.html#TLS/DTLS%20OPTION%20DESCRIPTIONS%20-%20COMMON%20for%20SERVER%20and%20CLIENT) can go in the `:ssl` key.

```elixir
config :nerves_hub_link,
  socket: [
    json_library: Jason,
    heartbeat_interval: 45_000
  ],
  ssl: [
    cert: "some_cert_der",
    keyfile: "path/to/keyfile"
  ]
```

**Note:** The Application config is evaluated at compile time. Extra caution is needed when using file paths in the config as they might be referencing host paths instead of device paths. Another option would be to configure at runtime (see [Runtime Configuration](#runtime-configuration))

### Using your own Signer CA

In some cases you may opt to use your own device signer CA when creating device certificates. If you do, there a few things needed for a successful connection:

1. Your signer CA must be uploaded to NervesHub. You can do this a couple ways

* From the web UI at `https://www.nerves-hub.org/settings/{YOUR_ORG}/certificates/new`
* Via CLI with `mix nerves_hub.ca_certificate register /path/to/signer-ca.pem`

2. The signer CA must be included in the connection request alongside the NervesHub intermediate CAs. If you're using [`NervesKey`](https://github.com/nerves-hub/nerves_key), then this is typically handled for you. If managing certificates more manually, it is usually best to read at runtime and appened to the CA list. Here's a basically example of what that step might look like:

```elixir
signer_cert =
  File.read!("/path/to/signer.pem")
  |> NervesHubLink.Certificate.pem_to_der()

cacerts = [signer_cert | NervesHubLink.Certificate.ca_certs()]
```

## Runtime Configuration

Some cases require that connection configuration happens at runtime like selectively choosing which cert/key to use based on device, or reading a file stored on device which isn't available during compilation.

Whatever the reason, `NervesHubLink` also supports runtime configuration via the [`NervesHubLink.Configurator`](https://github.com/nerves-hub/nerves_hub_link/blob/master/lib/nerves_hub_link/configurator.ex) behavior. This is called during application startup to build the configuration that is to be used for the connection. When implementing the behavior, you'll receive the initial default config read in from the application environment and you can modify it however you need.

For example:

```elixir
defmodule MyApp.Configurator do
  @behaviour NervesHubLink.Configurator

  @impl true
  def build(config) do
    ssl = [certfile: "/root/ssl/cert.pem", keyfile: "/root/ssl/key.pem"]
    %{config | ssl: ssl}
  end
end
```

Then you specify which configurator `NervesHubLink` should use in `config.exs`:

```elixir
config :nerves_hub_link, configurator: MyApp.Configurator
```

## Client Setup

By default NervesHubLink applies updates as soon as it knows about them from the NervesHubLink server and doesn't give warning before rebooting. Optionally, you can override this behavior if desired by implementing the [`NervesHubLink.Client`](https://hexdocs.pm/nerves_hub_link/NervesHubLink.Client.html) behaviour.

This let's devices hook into the decision making process and monitor the update's progress.

### Example

```elixir
defmodule MyApp.NervesHubLinkClient do
  @behaviour NervesHubLink.Client
  # May return:
  #  * `:apply` - apply the action immediately
  #  * `:ignore` - don't apply the action, don't ask again.
  #  * `{:reschedule, timeout_in_milliseconds}` - call this function again later.
  @impl NervesHubLink.Client
  def update_available(data) do
    if SomeInternalAPI.is_now_a_good_time_to_update?(data) do
      :apply
    else
      {:reschedule, 60_000}
    end
  end
end
```

To have NervesHubLink invoke it, add the following to your `config.exs`:

```elixir
config :nerves_hub_link, client: MyApp.NervesHubLinkClient
```


# Connecting to your environment

To configure NervesHubLink to talk to your hosted server, the following configuration must be set:

```elixir
# The Server CA Certificates need to be provided.
ca_certs = "/path/to/my-nerves-hub.org/ca_certs"

config :nerves_hub_link,
  device_api_host: "device.my-nerves-hub.org",
  device_api_sni: 'device.my-nerves-hub.org',
  device_api_port: 443,
  ca_certs: ca_certs

config :nerves_hub_cli, 
  host: "my-nerves-hub.org",
  port: 443,
  server_name_indication: 'my-nerves-hub.org',
  ca_certs: ca_certs
```


# Firmware signing keys

NervesHub requires cryptographic signatures on all managed firmware. Devices receiving firmware from NervesHub validate signatures. Since firmware is signed before uploading to NervesHub, NervesHub or any service NervesHub uses cannot modify it.

Firmware authentication uses [Ed25519 digital signatures](https://en.wikipedia.org/wiki/EdDSA#Ed25519). You need to create at least one public/private key pair and copy the public key part to NervesHub and to devices. NervesHub tooling helps with both. A typical setup has multiple signing keys to support key rotation and "development" keys that are not as protected.

Start by creating a `devkey` firmware signing key pair:

```bash
mix nerves_hub.key create devkey --org my-organisation-name
```

On success, you'll see the public key. You can confirm using the NervesHub web interface that the public key exists. Private keys are never sent to the NervesHub server. NervesHub requires valid signatures from known keys on all firmware it distributes. Since this command uploads the public key to NervesHub you are now ready to sign firmware with it.

```bash
mix nerves_hub.firmware sign myfirmware.fw --key devkey
```

On authenticating with NervesHub a device will download the available public keys to enable validation of firmware signatures.

## Locking a device to keys (optional)

The next step is to make sure that the public key is embedded into the firmware image. This is optional. The device will then use these keys to verify the firmware it receives from a NervesHub server before applying the update. It will ignore other keys available on that NervesHub organisation. This protects the device against anyone tampering with the firmware image between when it was signed by you and when it is installed.

All firmware signing public keys need to be added to your `config.exs`.

```elixir
config :nerves_hub_link,
  fwup_public_keys: [
    # devkey
    "bM/O9+ykZhCWx8uZVgx0sU3f0JJX7mqnAVU9VGeuHr4="
  ]
```

The `nerves_hub_link` dependency converts key names to public keys at compile time. If you haven't compiled your project yet, run `mix firmware` now. If you have compiled it, `mix` won't know to recompile `nerves_hub_link` due to the configuration change. Force it to recompile by running:

```bash
mix deps.compile nerves_hub_link --force
mix firmware
```

While not shown here, you can export keys for safe storage. Additionally, key creation and firmware signing can be done outside of the `mix` tooling. The only part that is required is that the firmware signing public keys be added to your `config.exs` and to the NervesHub server.


# Products

A NervesHub product groups devices that run the same kind of firmware. All devices and firmware images have a product. NervesHub provides finer grain mechanisms for grouping devices, but a product is needed to get started.

By default, NervesHub uses the `:app` name in your `mix.exs` for the product name. If you would like it to use a different name, add a `:name` field to your `Mix.Project.config()`. For example, NervesHub would use "My Example" instead of "example" for the following project:

```elixir
  def project do
    [
      app: :example,
      name: "My Example"
    ]
  end
```

For the remainder of this document, though, we will not use the `:name` field and simply use the product name `example`.

Create a new product on NervesHub by running:

```bash
mix nerves_hub.product create
```


# Devices

## Initializing

In this example we will create a device with a hardware identifier `1234`. The device will also be tagged with `qa` so we can target it in our deployment group. Device certificates are required for a device to establish a connection with the NervesHub server.

```bash
$ mix nerves_hub.device create

NervesHub organization: nerveshub
identifier: 1234
description: test-1234
tags: qa
Local user password:
Device 1234 created
Finished
```

If your device has an ATECCx08A module or NervesKey that has been provisioned by a CA/signer certificate known to NervesHub, the device is ready to go. If not using a hardware module to protect the device's private key, create and register a certificate and key pair manually by running:

{% code overflow="wrap" %}

```
mix nerves_hub.device cert create 1234 --signer-key key.pem --signer-cert cert.pem
```

{% endcode %}

NervesHub certificates and hardware identifiers are persisted to the firmware when the firmware is burned to the SD card. To make this process easier, you can call `nerves_hub.device burn IDENTIFIER`. In this example, we are going to burn the firmware and certificates for device `1234` that we created.

```bash
mix nerves_hub.device burn 1234
```

Your device will now connect to NervesHub when it boots and establishes an network connection.


# Firmware

## Publishing

Uploading firmware to NervesHub is called publishing. To publish firmware start by calling:

```bash
mix firmware
```

Firmware can only be published if has been signed. You can sign the firmware by running.

```bash
mix nerves_hub.firmware sign --key devkey
```

Firmware can also be signed while publishing:

```bash
mix nerves_hub.firmware publish --key devkey
```

## Delta Updates

With Delta updates, NervesHub can compare two binary files and only update the pieces of the firmware that have changed. This can reduce the size and cost of OTA updates by up to 95%. There are a few requirements to use Delta Updates:

* You will need to enable delta updates, on a per product basis, on the product settings page in your [nerves-hub.org](https://nerves-hub.org) account.
* `fwup >= 1.6.0` - If your nerves system is based on `nerves_system_br >= 1.11.2` then this condition is met. If not, you may need to upgrade your nerves system.
* `nerves_hub_link >= 0.9.1`
* Delta update compatible nerves system
  * If needed, you can find instructions in [Nerves: Experimental Features](https://hexdocs.pm/nerves/experimental-features.html#firmware-patches) for how to make the necessary changes in your system to support firmware patches.
  * see [Nerves: Customizing Systems](https://hexdocs.pm/nerves/customizing-systems.html) for instructions on creating your own Nerves system if needed.


# Deployments

## Creating

Deployments associate firmware images to devices. NervesHub won't send firmware to a device until you create a deployment. First find the UUID of the firmware. You can list the firmware on NervesHub by calling:

```bash
mix nerves_hub.firmware list

Firmwares:
------------
  product:      example
  version:      0.3.0
  platform:     rpi3
  architecture: arm
  uuid:         1cbecdbb-aa7d-5aee-4ba2-864d518417df
```

In this example we will create a new deployment for our test group using firmware `1cbecdbb-aa7d-5aee-4ba2-864d518417df`.

```bash
mix nerves_hub.deployment create

NervesHub organization: nerveshub
Deployment name: qa_deployment
firmware uuid: 1cbecdbb-aa7d-5aee-4ba2-864d518417df
version condition:
tags: qa
Local user password:
Deployment test created
```

Here we create a new deployment called `qa_deployment`. In the conditions of this deployment we left the `version condition` unspecified and the `tags` set to only `qa`. This means that in order for a device to qualify for an update, it needs to have at least the tags `[qa]` and the device can be coming from any version.

At this point we can try to update the connected device.

Start by bumping the application version number from `0.1.0` to `0.1.1`. Then, create new firmware:

```bash
mix firmware
```

We can publish, sign, and deploy firmware in a single command now.

```bash
mix nerves_hub.firmware publish --key devkey --deploy qa_deployment
```

## Conditionally applying updates

It's not always appropriate to apply a firmware update immediately. Custom logic can be added to the device by implementing the [`NervesHubLink.Client`](https://hexdocs.pm/nerves_hub_link/NervesHubLink.Client.html) behaviour and telling the NervesHubLink OTP application about it.

Here's an example implementation:

```elixir
defmodule MyApp.NervesHubClient do
   @behaviour NervesHubLink.Client

   # May return:
   #  * `:apply` - apply the action immediately
   #  * `:ignore` - don't apply the action, don't ask again.
   #  * `{:reschedule, timeout_in_milliseconds}` - call this function again later.

   @impl NervesHubLink.Client
   def update_available(data) do
    if SomeInternalAPI.is_now_a_good_time_to_update?(data) do
      :apply
    else
      {:reschedule, 60_000}
    end
   end
end
```

To have NervesHubLink invoke it, update your `config.exs` as follows:

```elixir
config :nerves_hub_link, client: MyApp.NervesHubClient
```


# Command-line tools

Introducing the NervesHub command-line tools aka. the CLI.

## Installation

The easiest way to install is via [Homebrew](https://brew.sh/).

```
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
```

More details and alternative installation methods are available in [the repo](https://github.com/nerves-hub/nerves_hub_cli).

## Setup

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

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

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

```sh
nh user auth
```

If you want to pass fewer arguments on later calls we can recommend setting up some environment variables in your shell.\
Usually that means `~/.bashrc` or `~/.zshrc`. The most commonly needed is the org:

```sh
NERVES_HUB_ORG="my-org-name-here"
```

## Try it out

You can start with the help function which gives you a lot of your options:

```sh
nh
```

To list devices for example:

```sh
nh device list --org my-org --product my-product
```

This should get you started with the CLI.


# Managing organizations and products

TBD. This section is intended to talk about organizations, products, and permissions.


# Device management

TBD. This section talks about the various device management features of NervesHub and how to enable and use them:

1. Device health and tweaking thresholds
2. Enabling and using remote IEx consoles
3. Remote reboot


# HTTP API

This document describes the NervesHub web API endpoints.

[You can also view the latest OpenAPI Spec on Github.](https://github.com/nerves-hub/nerves_hub_web/blob/main/api-spec.yml)

{% openapi src="<https://raw.githubusercontent.com/nerves-hub/nerves_hub_web/main/api-spec.yml>" path="/devices/{id}" method="get" %}
<https://raw.githubusercontent.com/nerves-hub/nerves_hub_web/main/api-spec.yml>
{% endopenapi %}

{% openapi src="<https://raw.githubusercontent.com/nerves-hub/nerves_hub_web/main/api-spec.yml>" path="/orgs/{org}/keys" method="get" %}
<https://raw.githubusercontent.com/nerves-hub/nerves_hub_web/main/api-spec.yml>
{% endopenapi %}

{% openapi src="<https://raw.githubusercontent.com/nerves-hub/nerves_hub_web/main/api-spec.yml>" path="/orgs/{org}/keys" method="post" %}
<https://raw.githubusercontent.com/nerves-hub/nerves_hub_web/main/api-spec.yml>
{% endopenapi %}

{% openapi src="<https://raw.githubusercontent.com/nerves-hub/nerves_hub_web/main/api-spec.yml>" path="/users/me" method="get" %}
<https://raw.githubusercontent.com/nerves-hub/nerves_hub_web/main/api-spec.yml>
{% endopenapi %}


# Device WebSocket

NervesHub exposes a WebSocket interface which utilizes [Phoenix channels](https://hexdocs.pm/phoenix/channels.html) for long lived connections. NervesHub uses SSL peer verification so the device's certificate and applicable CA certificates must be included in the connection request. The device's SSL certificate is also used to determine organization and serial number.

The connection URI is `wss://{host}/socket/websocket`. Once connected, you can then join any of the supported channel topics to start sending and receiving messages with NervesHub.

## Message Structure

NervesHub utilizes the Phoenix message structure for all WebSocket communications. In its raw form, the message is a simple list expected to be structured as `[join_ref, ref, topic, event, payload]` . (See the [`Phoenix.Socket.Message` documetation ](https://hexdocs.pm/phoenix/Phoenix.Socket.Message.html)for more info on what each part of the message means)

```javascript
# Example messages
["join_123", "ref-453", "some_topic", "update", "some_payload"]
[null, "another-ref", "diff_topic", "response", {"key": "val"}]
[null, null, "topic3", "wat", [1, 2, 3, 4]]
```

## Joining a Channel

To communicate with NervesHub, you must join a channel on a supported topic once the websocket has been connected. This requires sending a message with the `phx_join` event to the desired topic:

```javascript
['arbitrary_join_ref', 'ref1', 'devices', 'phx_join', {}]
```

## Supported Topics

NervesHub currently supports the following channel topics:

* `device` - The main topic a device should join for receiving updates and other device specific events. There is a required payload of the `device_api_version` when connecting, e.g. `{'device_api_version': '2.0.0'}`. This is used to instruct NervesHub what messages it can send to the device.
* `console` - topic for the device to send IO requests to and from NervesHub for supporting remote console interaction. For Nerves, this is the topic used from remote IEx sessions.

## Server Events

The supported events in messages coming from the server (NervesHub) to the client:

### **device**

* `update`
  * Specifies that an update is available for the device
  * Payload fields:
    * `update_available` - Boolean stating update availability
    * `deployment_id` - ID of the deployment triggering the update
    * `firmware_url` - URL where the firmware file can be downloaded. **Note**: this has a default TTL of 10 minutes. Using the URL after that time will fail and a new update request will need to be sent for a new URL
    * `firmware_meta` - Contains a map of the various metadata elements for the firmware
      * `uuid`
      * `architecture`
      * `platform`
      * `product`
      * `version`
      * `author`
      * `description`
      * `vcs_identifier`
      * `misc`

```javascript
[
  null,
  "some-ref-1",
  "devices",
  "update",
  {
    "update_available": true,
    "deployment_id": 12,
    "firmware_url": "https://some-url.com",
    "firmware_meta": {
      "uuid": "12345-6789-0129435",
      "architecture": "arm",
      "platform": "rpi0",
      "product": "MyProduct",
      "version": "1.1.10",
      "author": "Ron Swanson",
      "description": "baconator 3000",
      "vcs_identifier": "some_version_control_sha",
      "misc": "random data"
    }
  }
]
```

* `reboot`
  * Request that device reboot. Typlically used for troubleshooting purposes
  * Payload is not used and can be ignored

```javascript
[null, "some-ref-1", "devices", "reboot", {}]
```

* `phx_err`
  * Error case, such as a channel process crashing, or when attempting to join an already joined channel
  * Payload fields:
    * `reason` - text of failure reason

```javascript
[null, "some-ref-1", "devices", "phx_err", {"reason": "some reason"}]
```

* `phx_close`
  * Channel was gracefully closed
  * Payload fields:
    * tbd

```javascript
[null, "some-ref-1", "devices", "phx_close", {}]
```

## Client Events

The supported event messages coming from the client to server (NervesHub)

### device

* `rebooting`
  * Tells the server that the device is rebooting
  * Payload is ignored server-side

```javascript
[null, "some-ref-1", "devices", "rebooting", {}]
```

* `fwup_progress`
  * Progress update during a firmware update
  * Payload fields:
    * `value` - percentage of update progress

```javascript
[
  null,
  "some-ref-1",
  "devices",
  "fwup_progress",
  {"value": 42}
]
```

* `status_update`
  * Current status of the device as it relates to a firmware update
  * Payload fields:
    * `status` - One of the supported statuses for a device
      * `idle` - waiting for an update
      * `fwup_error` - error occurred in the fwup process
      * `update_failed`
      * `update_rescheduled`
      * `unknown` - generic status for unhandled states

```javascript
[
  null,
  "some-ref-1",
  "devices",
  "status_update",
  {"state": "update_rescheduled"}
]
```


# Introduction

A NervesKey is a cryptographic hardware module that is configured to work with NervesHub. Its primary responsibility is to secure the private key used for authentication. It can also store useful information such as device serial numbers, board name, and important settings.

The following are options for adding a NervesKey to your device:

1. If you're making a custom board, add a Microchip [ATECC508A](https://octopart.com/search?q=atecc508a) or [ATECC608A](https://octopart.com/search?q=atecc608a) to an available I2C bus
2. Obtain a [NervesKey](https://www.tindie.com/products/troodonsw/nerveskey/) and solder it to the GPIO connector on a Raspberry Pi
3. Contact us for information about USB NervesKey

The NervesKey configuration of the ATECC508A/ATECC608A is documented in the [nerves\_key project's README.md](https://github.com/nerves-hub/nerves_key#atecc508a-configuration). The cryptographic modules are quite capable devices. If the NervesKey configuration does not meet your needs, refer to the module datasheets and use the [nerves\_key configuration](https://github.com/nerves-hub/nerves_key#atecc508a-configuration) as a helpful example.

{% hint style="danger" %}
Except for a few cases, NervesKeys come unprogrammed. Some fields in the NervesKey can only be programmed once.
{% endhint %}


# NervesKey for Raspberry Pi

Raspberry Pis are popular boards for prototyping and low volume deployments. Without additional hardware, the only place to store private keys is on the MicroSD card. Since the Raspberry Pi does not have a secure bootloader, the keys either need to be stored in the clear or obfuscated somehow using information in the firmware image or elsewhere on the PI. Neither option is ideal.

The NervesKey for Raspberry Pi is a tiny circuit board with an ATECC608A that can be mounted underneath the Raspberry Pi's GPIO header:

![NervesKey installed on a Raspberry Pi](/files/-LcJ3ryvpoIQMGUMa8Jn)

This location doesn't interfere with Raspberry Pi hats and still fits in many cases. If you would like a less permanent option, you can add a header to the NervesKey and attach it to the top of the Raspberry Pi or even add header pins for insertion in a breadboard. We recommend soldering it to the bottom, though, since NervesKeys are typically associated with one device. The keys can also be removed using solder wick.

## Mounting the NervesKey

If you're using a Raspberry Pi Zero or Zero W that does not have header pins, solder those in first. If you're planning on mounting the NervesKey to the bottom of the board, try to use a little less solder on the right-most pins. Refer to the picture above for where the NervesKey is mounted. By using less solder, you'll be able to mount the NervesKey flatter on the bottom of the board.

{% hint style="info" %}
In an attempt to mount the NervesKey as flat as possible, we used solder wick to remove solder off the header pins on a few Raspberry Pis. We've decided that it wasn't worth it. The NervesKey mounts close enough to flat on the boards we have anyway. If you soldered header pins in and put a particularly large amount of solder on the pins, you may need to remove some to mount the board.
{% endhint %}

The NervesKey connects to the I2C and power pins on the GPIO connector. If you match your work to the picture, you'll be fine.

After you've soldered the NervesKey, go to the [Provisioning](broken://pages/-MHDArXH4T_PRPWdBLsY) section for programming it.

## More information and schematics

If you're already designing a custom board that attaches to the Raspberry Pi's GPIO header, it is likely going to be more convenient and less expensive to add an ATECC508A or ATECC608A to your board. See the [schematics](https://github.com/nerves-hub/nerves_key/blob/master/hw/assets/TSW19001_NERVESKEY_X1_SCH.PDF) for the connections.


# Private keys and certificates

Each NervesKey holds one ECC P256 private key. The provisioning process creates the private key and it never leaves the module. The ATECC508A and ATECC608A modules used on the NervesKeys have countermeasures to make extracting the private keys impractical. The associated public key can always be retrieved from the NervesKey.

The X.509 standards define an encoding for keys and their associated metadata. A key and its metadata are referred to as a certificate. Certificates can then be cryptographically signed by other keys to establish a chain of trust. X.509 certificates have two common encodings, PEM and DER. DER is a binary encoding and PEM is a text encoding.

The NervesKey holds information for up to four certificates:

1. A primary device certificate
2. The certificate that signed the primary device certificate
3. An auxiliary device certificate
4. The certificate that signed the auxiliary device certificate

The provisioning procedure requires a signing certificate and creates the primary device certificate. Both certificates are recorded to the NervesKey. The auxiliary certificates are similar in function, but can be filled in later and rewritten.

The X.509 certificate that signs the device certificate is also called a Device Certificate Authority certificate (Device CA certificate) and must be uploaded to NervesHub for the device to be allowed to connect over TLS.

All certificates have validity periods. Validity periods establish the dates where a device can be used. It is crucial to have these set appropriately or TLS connections will not work. Deciding how long certificates are valid requires some thought. Device CA certificates are maintained outside of the NervesKey and may need to be kept in less than ideal conditions. NervesHub only requires a valid Device CA certificate the first time that a device connects (it saves trusted device certificates).

{% hint style="warning" %}
Nerves devices often start with their clocks set to January 1st, 1970. X.509 certificates will fail validation unless the clock is updated. The [`nerves_time`](https://hex.pm/packages/nerves_time) library is one option for synchronize the clock.
{% endhint %}

Since the NervesKey protects the private key, device certificates typically have long validity periods. The Device CA certificates have much shorter validity periods and can be removed from NervesHub once all devices have connected once.

Since the NervesKey has limited storage, the contents of the certificates must be kept small. Additional compromises were made in the granularity of validity dates and other fields. The NervesKey software follows the [ATECC Compressed Certificate Specification](http://ww1.microchip.com/downloads/en/AppNotes/Atmel-8974-CryptoAuth-ATECC-Compressed-Certificate-Definition-ApplicationNote.pdf) for encoding and storing certificates. Unfortunately, the compressed certificate constraints make it difficult to generate certificates with programs like `openssl`. The `nerves_key` library has helper functions that create certificates using the `x509` library and Erlang's `public_key` application. Note that the X.509 certificates are still completely valid and usable by services that require them. If a third party service cannot be made to work with the constraints imposed by the NervesKey, you can still create an X.509 certificate for their use with the public key from the NervesKey. However, you cannot store that X.509 certificate in the NervesKey.


# General NervesKey storage

NervesKeys provide a small amount of non-key storage. This includes

* Manufacturer serial number assignment
* Board name and revision
* Device settings

The manufacturer serial number is used to identify devices associated with the NervesKey to NervesHub. This is the serial number that might be printed on a label on the outside of the device. Manufacturer serial numbers are written to one-time-programmable memory on the NervesKey and can never be changed.

{% hint style="warning" %}
NervesHub requires all devices within an organization to have unique serial numbers. If you do not want to maintain a list of serial numbers, we recommend that you use the default provided by the NervesKey provisioning software. It is guaranteed to be globally unique.
{% endhint %}

Storage for a board name and hardware revision is also available. Like the manufacturer serial number, it is written to one-time-programmable memory. Setting these fields is only available programmatically via the `nerves_key` library. The board name is set to "NervesKey" by default. Most users don't need to set this field and its not displayed or used internally by any NervesHub components.

Finally, each NervesKey provides 524 bytes of user storage for settings. This can be handy for storing important information settings that you don't want to lose if someone replaces the MicroSD card on your device. This could hold WiFi settings, for example. Another use is to store calibration data that's determined at manufacturing time. See [`NervesKey.put_settings/2`](https://hexdocs.pm/nerves_key/NervesKey.html#put_settings/2) and [`NervesKey.get_settings/1`](https://hexdocs.pm/nerves_key/NervesKey.html#get_settings/1) for more information.

See the NervesKey [ATECC508a Configuration](https://github.com/nerves-hub/nerves_key#atecc508a-configuration) for the low level storage of all of the above.


# Provisioning in Elixir

The quickstart firmware is useful for getting started with NervesKeys. This section describes one way of provisioning NervesKeys programmatically. More sophisticated and automated ways are certainly possible.

## Prerequisites

To provision a NervesKey, make sure that you have the following:

1. A Device CA certificate and its private certificate (this is also referred to

   as a signing certificate)
2. A serial number for your device
3. A name for the device

The signing certificate and serial number are very important. After the provisioning process, they are locked down and cannot be changed without replacing the NervesKey. The device name is purely informational unless you choose to use it in your software.

NervesKeys support an auxillary set of certificates that identify the device. These are writable after the provisioning process. Since they're writable, they can be provisioned and updated at any time. As such, they're not programmed in the first-time provisioning process.

### Creating a Device CA certificate

If you have not already created a Device CA certificate, you will need to create one now. For simplicity, the private key is handled in the clear. Depending on your manufacturing or provisioning environment, you may want to store the private key in a hardware security module (HSM) or keep it encrypted.

As mentioned earlier, the NervesKey requires that the Device CA certificate adhere to the [ATECC Compressed Certificate Definition](https://www.microchip.com/wwwAppNotes/AppNotes.aspx?appnote=en591852) constraints so that it can be stored. By far the easiest way to create compliant certificates is to use the `nerves_key` `mix` commandline helpers. If you don't already have an Elixir Project that has `nerves_key` as a dependency, create one now, and run `mix deps.get` to pull down the helpers. Then run the following commands:

```bash
$ mix nerves_key.signer create nerveskey_prod_signer1
Created signing cert, nerveskey_prod_signer1.cert and private key, nerveskey_prod_signer1.key.

Please store nerveskey_prod_signer1.key in a safe place.

nerveskey_prod_signer1.cert is ready to be uploaded to the servers that need
to authenticate devices signed by the private key.
```

There is no magic in the compressible certificates. They're just limited in what they can contain. You can inspect them with `openssl x509 -in nerveskey_prod_signer1.cert -text`.

Check with your IoT service on how the signer certificate is used. If it's only used for first-time device registration, then the signer certificate may not need a long expiration time. You may also be interested in creating more than one signing certificate if you have more than one manufacturing facility.

### Manufacturer serial numbers

The NervesKey saves the manufacturing serial number in the one-time programmable memory on the ATECC508A/608A and also in the device's X.509 certificate. The device's X.509 certificate is signed, so cloud servers can trust the manufacturer serial number.

At this point, you're the manufacturer. Decide how you'd like your serial numbers to look. Whatever you pick, it must fit in 16-bytes. Representing the serial number is commonly done in ASCII. If you don't want to deal with this, do what we do (Base32-encode a globally unique identify burned into the ATECC508A/608A). `NervesKey.default_info/1` will create this default for you:

```elixir
iex> {:ok, i2c} = ATECC508A.Transport.I2C.init([])
{:ok, {ATECC508A.Transport.I2C, {#Reference<0.879310498.269090821.27261>, 96}}}
iex> NervesKey.default_info(i2c)
%NervesKey.ProvisioningInfo{
  board_name: "NervesKey",
  manufacturer_sn: "AER245UNQOY4T3Q"
}
```

## Provisioning

Now that you have a Device CA certificate, the Device CA's private key, and a manufacturer serial number, you can provision a NervesKey or the standalone ATECC508A/608A. Usually there's some custom manufacturing software that performs this step. We'll provision at the iex prompt.

Use `sftp` to copy the Device CA certificate and private key to your device. We'll put them `/tmp` so that they disappear on reboot:

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

Next, go to the IEx prompt on the device and run the following:

```elixir
# Customize these or use `NervesKey.default_info/1` for defaults
cert_name="nerveskey_prod_signer1"

# These lines should be copy/paste
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.default_info(i2c)

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

If the last line returns `:ok` after about 2 seconds, then celebrate. You successfully programmed a NervesKey. You can't program it again. If you try, you'll get an error.

## Provisioning an auxiliary certificate

Auxiliary certificates are provisioned very similarly to the first time device programming. You will need to copy the Device CA certificate and private key to the device as before. This is likely a different certificate and private key, though.

The following assumes that you have already run the first time provisioning steps above.

```elixir
# Customize these
cert_name="nerveskey_prod_signer1"

# These lines should be copy/paste
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([])
NervesKey.provision_aux_certificates(i2c, signer_cert, signer_key)
```

See the `NervesKey.clear_aux_certificates/1` function for erasing the auxiliary certificate.

## Settings

The `NervesKey.put_settings/2` and `NervesKey.get_settings/1` APIs let you store and retrieve a map. Since the storage is limited and relatively slow, this is intended for settings that rarely change or may be tightly coupled with certificates already being stored in the NervesKey.

Internally, `NervesKey` calls `:erlang.term_to_binary` to convert the map to raw bytes and then writes it to the ATECC508A/608A's persistent storage. Not that the keys in the map take up space as well.


# Nerves integration

Nerves can use the serial number programmed into the NervesKey for the hostname. This can come in handy for finding devices on the network since the hostname can be reported via DHCP or broadcast using mDNS.

{% hint style="info" %}
The DHCP client for Nerves reports hostnames to the DHCP server. Some DHCP servers like dnsmasq-based ones register those names with a colocated DNS server so that requests of that name return the IP address of the Nerves-based device. Many DHCP servers do not do this, so mDNS is the only option for easily finding the device.
{% endhint %}

In Nerves, the `boardid` program reads serial numbers off of boards. It has options to support many different kinds of hardware. To have it read the serial number off the NervesKey, add a `rootfs_overlay/etc/boardid.conf` file to your Nerves project. This will override the `boardid.conf` distributed with your Nerves system (assuming that you're using an official Nerves system). The contents should be:

```
# /etc/boardid.conf

-b nerves_key -f /dev/i2c-1
```

If your NervesKey is attached via a different I2C bus, update the `-f` option accordingly.

If this doesn't work, you can test `boardid` at the IEx prompt using the Toolshed `cmd` helper (or `System.cmd/3` if not using Toolshed) by running:

```elixir
iex> cmd("boardid -b nerves_key -f /dev/i2c-1 -b force -f failed")
abc123
```

The `-b force -f failed` part ensures that `boardid` doesn't fall back to try to find a serial number based on options in a `/etc/boardid.conf` file. Run `boardid --help` for more information.


# NervesHubLink integration

This section walks you through integrating a device with a NervesKey with NervesHub.

## Device registration

Devices must be registered with NervesHub via the commandline tools, web user interface or via an API integration first to authenticate with a NervesKey. The only exception is if you are using the JITP feature in which case registration is automated.

The command-line tools are the easiest for this. Follow [these instructions](/1.0.0/nerves-hub/cli-tools) to get them installed.

You will need the device's manufacturing serial number for this step. If you don't have it `NervesKey.manufacturer_sn/1` can retrieve it. From the CLI, run `mix nerves_hub.device create`. Fill in the manufacturing serial number when it asks for an identifier. Fill in the `description` and `tags` fields as you'd like:

```
$ nh device create
NervesHub server: api.nerves-hub.org:443
NervesHub organization: your_organization
identifier: 123456789ABCDE
description: My favorite device
tags: qa
Local NervesHub user password:
Device 123456789ABCDE created
```

{% hint style="info" %}
When registering devices in bulk, we've found that there's usually a convenient hook in the manufacturing process to call `mix nerves_hub.device create` with commandline parameters.
{% endhint %}

If you are not using the Signer CA approach you also want to upload a certificate per device. This can be done using:

```
nh device cert import 123456789ABCDE device-cert-file-path-goes-here
```

## Integration with your Nerves project

You can tell `nerves_hub_link` to read those certificates and key from the chip and assign the SSL options for you by enabling add it as a dependency in your `mix.exs`:

```elixir
def deps() do
  [
    {:nerves_hub_link, "~> 2.7"},
    {:nerves_key, "~> 1.2"}
  ]
end
```

NervesKey will default to using i2c bus 1 and `:primary` cerificate pair.

NervesHubLink will use the NervesKey library by default if it is available. NervesHubLink itself needs to be configured a bit:

```elixir
config :nerves_hub_link,
  # point this at the device endpoint of your instance
  host: "devices.nervescloud.com",
  # if you want the remote console
  remote_iex: true,
```


# MQTT integration

This section provides guidance for using the NervesKey with popular MQTT brokers.

NervesKeys aren't only for use with NervesHub. If a service allows users to provide their own certificate authority, it should be possible to also use the NervesKey. Nearly all services use TLS or DTLS so these instructions provide guidance for using the [Erlang/OTP SSL application](http://erlang.org/doc/apps/ssl/users_guide.html). The following diagram shows the other compoents that are involved with establishing and authenticating MQTT connections over TLS:

![Libraries supporting MQTT connections using the NervesKey](/files/-LfWMoH0VAPQetjmU7fG)

In particular, the NervesKey integration with Erlang makes use of the [Engine API](http://erlang.org/doc/apps/crypto/engine_keys.html) to redirect private key operations to the cryptographic module. While `nerves_key_pkcs11` provides a PKCS#11 implementation, be aware that it is minimal and only supports the operations necessary for establishing TLS connections. Programs using the NervesKey should still use the `nerves_key` library for provisioning and everything else.

To use the Erlang/OTP SSL application's Engine API, you will need to do the following:

1. Initialize the engine to start the `nerves_key_pkcs11` integration
2. Replace the locations where you would have suppliced a private key with an Elixir map that has a reference to the engine and other information

The `nerves_key_pkcs11`library has a helper functions for both tasks.

The following example code fragment shows how to start the Tortoise MQTT client API:

```
{:ok, engine} = NervesKey.PKCS11.load_engine()
{:ok, i2c} = ATECC508A.Transport.I2C.init([])

signer_cert = X509.Certificate.to_der(NervesKey.signer_cert(i2c, :primary))
cert = X509.Certificate.to_der(NervesKey.device_cert(i2c, :primary))
key = NervesKey.PKCS11.private_key(engine, {:i2c, 1})
cacerts = [signer_cert | aws_root_certs()]

Tortoise.Supervisor.start_child(
  client_id: manufacturing_serial_number(),
  handler: {MQTTHandler, []},
  server: {
    Tortoise.Transport.SSL,
    verify: :verify_peer,
    host: mqtt_host(),
    port: mqtt_port(),
    alpn_advertised_protocols: alpn_advertised_protocols(),
    server_name_indication: server_name_indication(),
    cert: cert,
    key: key,
    cacerts: cacerts,
    versions: [:"tlsv1.2"]
  },
  subscriptions: topics()
 )
```


# Introduction

{% hint style="warning" %}
We're busy adding and updating the NervesHub documentation to make it easier to use NervesHub. In particular, the NervesHub web user interface is being reorganized so we're only lightly documenting it now. If something doesn't make sense, please let us know via a GitHub issue.
{% endhint %}

NervesHub is an open-source firmware update server that works well with Nerves-based devices.

NervesHub provides many of the features that you'd expect in a firmware update server. Fundamentally, devices connect to the server either by polling at a pre-configured interval or by joining a long-lived [Phoenix channel](https://hexdocs.pm/phoenix/channels.html). If a firmware update is available, NervesHub provides a URL to the device and the device can update immediately or when convenient.

NervesHub leverages best practices and libraries from the Elixir community to provide a service that scales from hobbyist use to large scale industrial deployments. Since all components are open-source, there is no lock in. Extensive regression tests enable you to extend NervesHub with confidence if necessary. Of course, we hope that you will contribute improvements back.

Security is a primary concern of NervesHub:

* Cryptographic signatures on firmware updates provide end-to-end authentication
* Client-side and server-side SSL provide link authentication and encryption
* Access control within organizations to firmware updates

NervesHub provides the tools for safe remote updates. While it's not possible for the firmware update server alone to ensure that firmware updates are always safe, NervesHub does provide lightweight verification of firmware and detects misbehaving devices.


# High level architecture

The following diagram shows the primary NervesHub components.

![NervesHub architecture](/files/-LfLhwC8Rahs5casg1bC)

The NervesHub server provides web and API endpoints for most interactions. The actual firmware storage and delivery resides on an object storage service such as AWS S3.

While NervesHub could operate with many firmware update file formats in principle, it currently expects files using the format provided by [fwup](https://github.com/fhunleth/fwup). The fwup tool provides the low level packaging and application of firmware images. Firmware files are ZIP-formated files with metadata.

User interaction with NervesHub is through the web interface and via a CLI provided by an extension to Elixir's mix build tool. The CLI tools and their underlying User API endpoint are useful for automating common tasks.

Devices connect to NervesHub via a separate API endpoint that exposes only the functionality needed for firmware updates. For Nerves-based devices, the [nerves\_hub](https://github.com/nerves-hub/nerves_hub) library manages the interactions with the NervesHub server and invokes fwup to apply updates.

The final component is the NervesHub Certificate Authority (CA). This component issues the client-side SSL certificates for the User API. Devices also use client-side SSL, but those certificates are issued and managed by you, the user of NervesHub.


# Public key infrastructure

NervesHub uses [asymmetric cryptography](https://en.wikipedia.org/wiki/Public-key_cryptography) for authenticating both sides of the connections to NervesHub and for end-to-end validation of firmware images. As with any use of asymmetric cryptography, a public key infrastructure (PKI) is required for establishing trust. The PKI for NervesHub is mostly managed by organizations using the service. Choices in NervesHub are intentionally compatible with other IoT services such as AWS IoT so that devices need only be provisioned with one private key rather than having a private key per service. There is no requirement that prevents devices from being issued many private keys.

## Client-side SSL for devices

Before use, each device must be provisioned with a signed X.509 certificate and its associated private key. When devices connect to NervesHub they'll validate NervesHub's certificate. Likewise, NervesHub validates these client certificates.

Device certificates are signed by a Device CA certificate owned by you. You will register one or more Device CA certificates with NervesHub. The private keys associated with these certificates are never seen by NervesHub. If necessary, you can secure them in hardware security modules (HSMs).

When a device connects for the first time, the device must present both its public certificate and the Device CA Certificate that signed it. Assuming that NervesHub knows the Device CA Certificate and that the Device Certificate has been signed properly, it will allow the device to connect. The Device Certificate is recorded on NervesHub to avoid verifying the chain of trust in the future. This also enables scenarios where Device CA Certificates have short validity time periods or are deleted once all devices signed by it have all connected.

The NervesHub CLI tools provide convenience functions for creating certificates. It's also possible to use openssl or the Elixir [x509 library](https://hex.pm/packages/x509) directly.

Care should be taken when handling the Device CA and device private keys. Compromised private keys can be used to access firmware updates and likely cause other mischief. Consider integrating a hardware storage chip into your device if it does not natively support secure storage. See [NervesKey](/nerves-key/getting-started) for one solution.

## Firmware update signatures

NervesHub requires cryptographic signatures on all firmware files. As opposed to the X.509 certificates used earlier, this functionality is provided by [libsodium's public key support](https://download.libsodium.org/doc/public-key_cryptography/public-key_signatures). The fwup commandline utility invokes libsodium to create key pairs, sign firmware update files, and verify signatures.

The public keys used to sign firmware update files must be uploaded to NervesHub and accessible to the device's firmware. By default, the NervesHub tooling embedded public keys in the device firmware. Both NervesHub and devices validate firmware signatures. The device validation is the important step since it ensures that the firmware that you generated is identical to the firmware being installed.

NervesHub requires users to register firmware public keys with it. This is used to catch mistakes that would otherwise waste time and bandwidth with deploying firmware that could never install correctly.

Users should protect firmware signing keys. It is possible to register multiple firmware public keys on NervesHub and on devices. This allows more convenient setups where development devices can accept both "production" and "debug" firmware. Devices in the field, though, could have only the "production" public keys and therefore reject accidental deploys of "debug" firmware. Of course, NervesHub supports more mechanisms to group devices so relying on firmware signing keys can be considered a last resort.


# Terminology

This section defines important words used throughout the NervesHub documentation.

## Auxiliary Device Certificate

An auxiliary device certificate is a secondary device certificate stored on a NervesKey to support development use cases.

## Device

A device is a client of NervesHub that receives firmware updates

## Device Certificate

A device certificate is an X.509 certificate that identifies a device. Each device certificate contains a public key, the device's serial number, validity and other information. NervesHub uses device certificates to authenticate connections from devices.

## Device CA Certificate

A device CA certificate is an X.509 certificate used by device manufacturers to cryptographically sign device certificates. NervesHub uses device CA certificates to validate device certificates.

## Deployment

A deployment specifies a firmware and a match condition on devices. NervesHub notifies all devices matching the condition to download the new firmware.

## Firmware

Firmware refers to the file that's uploaded to NervesHub, stored, and sent to devices. The most common case is that the file contains the software that runs on the device, but this is not a requirement. NervesHub supports firmware created by the [fwup](https://github.com/fhunleth/fwup) utility.

## Firmware signing keys

Firmware signing keys refer to the raw or base64-encoded public and private keys used to sign and authenticate firmware files. NervesHub and devices hold public keys and verify firmware files before processing them. Firmware signatures provide an end-to-end assurance that the firmware installed on a device has not been modified by NervesHub or anyone else.

## Firmware UUID

This is a globally unique firmware identifier created from the contents of the firmware. Two firmware with the same UUID install the bit-for-bit identical software on devices. Likewise firmware with different UUIDs have some difference even if their metadata reports their version numbers to be the same.

## NervesKey

A NervesKey is a hardware module that protects a device's private key and is used to authenticate a device with NervesHub.

## Product

Products organize related devices, firmware, and deployments. Organizations and users can have one or more products. Devices, firmware, and deployments only belong to one product at a time.


# Quickstart

This tutorial will get you up and running with a device on your NervesHub\
instance with minimal fuss. It uses the simplest approach. We offer[a separate guide](/tutorials/index-1) for production deployments with security hardware in\
place.

We will:

* Create a Nerves project
* Set up a NervesHub product for Shared Secret auth
* Deploy a device
* Create and sign firmware
* Deploy a firmware update
* Rejoice!

## Create a Nerves project (or use an existing one)

Assuming you've [installed Nerves](https://hexdocs.pm/nerves/installation.html)\
you should be able to run the following command:

```sh
mix nerves.new my_project
```

This gets you a Nerves project including a bunch of the [supported Nerves\
systems](https://hexdocs.pm/nerves/supported-targets.html#supported-targets-and-systems)\
by default. We will pretend that you are using a Raspberry Pi 4 for this guide.

```sh
export MIX_TARGET=rpi4
```

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

## Set up your NervesHub product

Now log in to the web UI of your NervesHub instances. For NervesCloud this is[manage.nervescloud.com](https://manage.nervescloud.com).

You should already have an organization in your name. Selecting it 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"`.

Select your Product, go to the Settings of the Product and find the section for\
Shared Secret authentication. Hit the button for creating a new Shared Secret.

## Add new dependencies

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

```elixir
{:nerves_hub_link, "~> 2.7"},
```

Now run:

```sh
mix deps.get
```

## Configuration

Your `config/target.exs` is for configuration that applies to your target device. Here you can grab those Shared Secret credentials you added to your product and add them:

```elixir
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,
  shared_secret: [
    product_key: "YourProductKey",
    product_secret: "YourProductSecret",
  ]
```

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

```elixir
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:

```sh
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:

```sh
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.

## 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.

Sometimes it can take a while before the device clock is updated and picked up\
by the Erlang runtime. In that case you may see connection errors as the\
Shared Secret cryptography requires the clock to be something reasonable.

Assuming your device shows up at this point we move on. If you can't get it\
working, consult [the Nerves section](https://elixirforum.com/c/nerves-forum)\
of the Elixir Forum and feel free to ask for help.

## Install the CLI

The easiest way to install is via [Homebrew](https://brew.sh/).

```
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
```

More details and alternative installation methods are available in [the repo](https://github.com/nerves-hub/nerves_hub_cli).

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

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

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

```sh
nh user auth
```

You can test it out with:

```sh
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:

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

## Firmware signing

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

```sh
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.

## Create a firmware update

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

```sh
mix firmware
```

Then you can run this to sign it:

```sh
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:

```sh
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:

```sh
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.

```sh
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:

```sh
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:

```sh
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 delights of NervesHub.

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


# Production Setup with NervesKey

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[quickstart](/tutorials/index). 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[get one from Adafruit](https://www.adafruit.com/product/4314) 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[quickstart](/tutorials/index) 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

The easiest way to install is via [Homebrew](https://brew.sh/).

```
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
```

More details and alternative installation methods are available in [the repo](https://github.com/nerves-hub/nerves_hub_cli).

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

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

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

```sh
nh user auth
```

You can test it out with:

```sh
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)

Assuming you've [installed Nerves](https://hexdocs.pm/nerves/installation.html)\
you should be able to run the following command:

```sh
mix nerves.new my_project
```

This gets you a Nerves project including a bunch of the [supported Nerves\
systems](https://hexdocs.pm/nerves/supported-targets.html#supported-targets-and-systems)\
by default. We will pretend that you are using a Raspberry Pi 4 for this guide.

```sh
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:

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

Now run:

```sh
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.

```elixir
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:

```elixir
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:

```sh
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:

```sh
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.

```sh
ssh nerves.local
```

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

## Provision NervesKey

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 [the\
docs](https://hexdocs.pm/nerves_key/readme.html) 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.

### 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:

```sh
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.

```sh
$ 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:

```elixir
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:

```elixir
{: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

Now log in to the web UI of your NervesHub instances. For NervesCloud this is[manage.nervescloud.com](https://manage.nervescloud.com).

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:

```sh
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.

```sh
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:

```sh
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.

Assuming your device shows up at this point we move on. If you can't get it\
working, consult [the Nerves section](https://elixirforum.com/c/nerves-forum)\
of the Elixir Forum and feel free to ask for help.

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:

```sh
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 grab`mix.exs` and bump the version number. Then we build it:

```sh
mix firmware
```

Then you can run this to sign it:

```sh
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:

```sh
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:

```sh
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.

```sh
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:

```sh
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:

```sh
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.


# Setup

## Getting Started

The following sections will walk you through updating your Nerves project to work with your NervesHub server.

Many of the following steps may feel manual, but they can and are automated by NervesHub users to set up automatic firmware updates from CI and to manufacture large numbers of devices.


# Add NervesHub to your project

The first step is to add [`nerves_hub_link`](https://github.com/nerves-hub/nerves_hub_link) to your target dependencies in your project's `mix.exs`. Since NervesHub uses SSL certificates, the time must be set on the device or certificate validity checks will fail. If you're not already setting the time, add [`nerves_time`](https://hex.pm/packages/nerves_time) to your dependencies. For example:

```elixir
  defp deps(target) do
    [
      {:nerves_runtime, "~> 0.9"},
      {:nerves_hub_link, "~> 2.0"},
      {:nerves_hub_cli, "2.0.0-dev"},
      {:nerves_time, "~> 0.2"},
      ...
    ] ++ system(target)
  end
```

Next, update your `config.exs` so that the [`nerves_hub_link`](https://github.com/nerves-hub/nerves_hub_link) library can help provision devices. Do this by adding `provisioning: :nerves_hub_link` to the `:nerves, :firmware` option like this:

```elixir
config :nerves, :firmware,
  provisioning: :nerves_hub_link
```

Adding this library will start a long-lived websocket connection with NervesHub for communicating device updates.

## Connection Setup

See the [`NervesKey: NervesHubLink Integration`](/nerves-key/integration-with-nerveshublink#integration-with-your-nerves-project) section for connection setup if you are using NervesKey

If you aren't using NervesKey, you can also provide your own options to use for the NervesHub socket connection via the `:socket`and `:ssl` keys, which are forwarded on to `slipstream` when creating the socket connection.

Any [valid Erlang ssl socket option](http://erlang.org/doc/man/ssl.html#TLS/DTLS%20OPTION%20DESCRIPTIONS%20-%20COMMON%20for%20SERVER%20and%20CLIENT) can go in the `:ssl` key.

```elixir
config :nerves_hub_link,
  socket: [
    json_library: Jason,
    heartbeat_interval: 45_000
  ],
  ssl: [
    cert: "some_cert_der",
    keyfile: "path/to/keyfile"
  ]
```

**Note:** The Application config is evaluated at compile time. Extra caution is needed when using file paths in the config as they might be referencing host paths instead of device paths. Another option would be to configure at runtime (see [Runtime Configuration](#runtime-configuration))

### Using your own Signer CA

In some cases you may opt to use your own device signer CA when creating device certificates. If you do, there a few things needed for a successful connection:

1. Your signer CA must be uploaded to NervesHub. You can do this a couple ways

* From the web UI at `https://www.nerves-hub.org/settings/{YOUR_ORG}/certificates/new`
* Via CLI with `mix nerves_hub.ca_certificate register /path/to/signer-ca.pem`

2. The signer CA must be included in the connection request alongside the NervesHub intermediate CAs. If you're using [`NervesKey`](https://github.com/nerves-hub/nerves_key), then this is typically handled for you. If managing certificates more manually, it is usually best to read at runtime and appened to the CA list. Here's a basically example of what that step might look like:

```elixir
signer_cert =
  File.read!("/path/to/signer.pem")
  |> NervesHubLink.Certificate.pem_to_der()

cacerts = [signer_cert | NervesHubLink.Certificate.ca_certs()]
```

## Runtime Configuration

Some cases require that connection configuration happens at runtime like selectively choosing which cert/key to use based on device, or reading a file stored on device which isn't available during compilation.

Whatever the reason, `NervesHubLink` also supports runtime configuration via the [`NervesHubLink.Configurator`](https://github.com/nerves-hub/nerves_hub_link/blob/master/lib/nerves_hub_link/configurator.ex) behavior. This is called during application startup to build the configuration that is to be used for the connection. When implementing the behavior, you'll receive the initial default config read in from the application environment and you can modify it however you need.

For example:

```elixir
defmodule MyApp.Configurator do
  @behaviour NervesHubLink.Configurator

  @impl true
  def build(config) do
    ssl = [certfile: "/root/ssl/cert.pem", keyfile: "/root/ssl/key.pem"]
    %{config | ssl: ssl}
  end
end
```

Then you specify which configurator `NervesHubLink` should use in `config.exs`:

```elixir
config :nerves_hub_link, configurator: MyApp.Configurator
```

## Client Setup

By default NervesHubLink applies updates as soon as it knows about them from the NervesHubLink server and doesn't give warning before rebooting. Optionally, you can override this behavior if desired by implementing the [`NervesHubLink.Client`](https://hexdocs.pm/nerves_hub_link/NervesHubLink.Client.html) behaviour.

This let's devices hook into the decision making process and monitor the update's progress.

### Example

```elixir
defmodule MyApp.NervesHubLinkClient do
  @behaviour NervesHubLink.Client
  # May return:
  #  * `:apply` - apply the action immediately
  #  * `:ignore` - don't apply the action, don't ask again.
  #  * `{:reschedule, timeout_in_milliseconds}` - call this function again later.
  @impl NervesHubLink.Client
  def update_available(data) do
    if SomeInternalAPI.is_now_a_good_time_to_update?(data) do
      :apply
    else
      {:reschedule, 60_000}
    end
  end
end
```

To have NervesHubLink invoke it, add the following to your `config.exs`:

```elixir
config :nerves_hub_link, client: MyApp.NervesHubLinkClient
```


# Connecting to your environment

To configure NervesHubLink to talk to your hosted server, the following configuration must be set:

```elixir
# The Server CA Certificates need to be provided.
ca_certs = "/path/to/my-nerves-hub.org/ca_certs"

config :nerves_hub_link,
  device_api_host: "device.my-nerves-hub.org",
  device_api_sni: 'device.my-nerves-hub.org',
  device_api_port: 443,
  ca_certs: ca_certs

config :nerves_hub_cli, 
  host: "my-nerves-hub.org",
  port: 443,
  server_name_indication: 'my-nerves-hub.org',
  ca_certs: ca_certs
```


# Firmware signing keys

NervesHub requires cryptographic signatures on all managed firmware. Devices receiving firmware from NervesHub validate signatures. Since firmware is signed before uploading to NervesHub, NervesHub or any service NervesHub uses cannot modify it.

Firmware authentication uses [Ed25519 digital signatures](https://en.wikipedia.org/wiki/EdDSA#Ed25519). You need to create at least one public/private key pair and copy the public key part to NervesHub and to devices. NervesHub tooling helps with both. A typical setup has multiple signing keys to support key rotation and "development" keys that are not as protected.

Start by creating a `devkey` firmware signing key pair:

```bash
mix nerves_hub.key create devkey --org my-organisation-name
```

On success, you'll see the public key. You can confirm using the NervesHub web interface that the public key exists. Private keys are never sent to the NervesHub server. NervesHub requires valid signatures from known keys on all firmware it distributes. Since this command uploads the public key to NervesHub you are now ready to sign firmware with it.

```bash
mix nerves_hub.firmware sign myfirmware.fw --key devkey
```

On authenticating with NervesHub a device will download the available public keys to enable validation of firmware signatures.

## Locking a device to keys (optional)

The next step is to make sure that the public key is embedded into the firmware image. This is optional. The device will then use these keys to verify the firmware it receives from a NervesHub server before applying the update. It will ignore other keys available on that NervesHub organisation. This protects the device against anyone tampering with the firmware image between when it was signed by you and when it is installed.

All firmware signing public keys need to be added to your `config.exs`.

```elixir
config :nerves_hub_link,
  fwup_public_keys: [
    # devkey
    "bM/O9+ykZhCWx8uZVgx0sU3f0JJX7mqnAVU9VGeuHr4="
  ]
```

The `nerves_hub_link` dependency converts key names to public keys at compile time. If you haven't compiled your project yet, run `mix firmware` now. If you have compiled it, `mix` won't know to recompile `nerves_hub_link` due to the configuration change. Force it to recompile by running:

```bash
mix deps.compile nerves_hub_link --force
mix firmware
```

While not shown here, you can export keys for safe storage. Additionally, key creation and firmware signing can be done outside of the `mix` tooling. The only part that is required is that the firmware signing public keys be added to your `config.exs` and to the NervesHub server.


# Products

A NervesHub product groups devices that run the same kind of firmware. All devices and firmware images have a product. NervesHub provides finer grain mechanisms for grouping devices, but a product is needed to get started.

By default, NervesHub uses the `:app` name in your `mix.exs` for the product name. If you would like it to use a different name, add a `:name` field to your `Mix.Project.config()`. For example, NervesHub would use "My Example" instead of "example" for the following project:

```elixir
  def project do
    [
      app: :example,
      name: "My Example"
    ]
  end
```

For the remainder of this document, though, we will not use the `:name` field and simply use the product name `example`.

Create a new product on NervesHub by running:

```bash
mix nerves_hub.product create
```


# Devices

## Initializing

In this example we will create a device with a hardware identifier `1234`. The device will also be tagged with `qa` so we can target it in our deployment group. Device certificates are required for a device to establish a connection with the NervesHub server.

```bash
$ mix nerves_hub.device create

NervesHub organization: nerveshub
identifier: 1234
description: test-1234
tags: qa
Local user password:
Device 1234 created
Finished
```

If your device has an ATECCx08A module or NervesKey that has been provisioned by a CA/signer certificate known to NervesHub, the device is ready to go. If not using a hardware module to protect the device's private key, create and register a certificate and key pair manually by running:

{% code overflow="wrap" %}

```
mix nerves_hub.device cert create 1234 --signer-key key.pem --signer-cert cert.pem
```

{% endcode %}

NervesHub certificates and hardware identifiers are persisted to the firmware when the firmware is burned to the SD card. To make this process easier, you can call `nerves_hub.device burn IDENTIFIER`. In this example, we are going to burn the firmware and certificates for device `1234` that we created.

```bash
mix nerves_hub.device burn 1234
```

Your device will now connect to NervesHub when it boots and establishes an network connection.


# Firmware

## Publishing

Uploading firmware to NervesHub is called publishing. To publish firmware start by calling:

```bash
mix firmware
```

Firmware can only be published if has been signed. You can sign the firmware by running.

```bash
mix nerves_hub.firmware sign --key devkey
```

Firmware can also be signed while publishing:

```bash
mix nerves_hub.firmware publish --key devkey
```

## Delta Updates

With Delta updates, NervesHub can compare two binary files and only update the pieces of the firmware that have changed. This can reduce the size and cost of OTA updates by up to 95%. There are a few requirements to use Delta Updates:

* You will need to enable delta updates, on a per product basis, on the product settings page in your [nerves-hub.org](https://nerves-hub.org) account.
* `fwup >= 1.6.0` - If your nerves system is based on `nerves_system_br >= 1.11.2` then this condition is met. If not, you may need to upgrade your nerves system.
* `nerves_hub_link >= 0.9.1`
* Delta update compatible nerves system
  * If needed, you can find instructions in [Nerves: Experimental Features](https://hexdocs.pm/nerves/experimental-features.html#firmware-patches) for how to make the necessary changes in your system to support firmware patches.
  * see [Nerves: Customizing Systems](https://hexdocs.pm/nerves/customizing-systems.html) for instructions on creating your own Nerves system if needed.


# Deployments

## Creating

Deployments associate firmware images to devices. NervesHub won't send firmware to a device until you create a deployment. First find the UUID of the firmware. You can list the firmware on NervesHub by calling:

```bash
mix nerves_hub.firmware list

Firmwares:
------------
  product:      example
  version:      0.3.0
  platform:     rpi3
  architecture: arm
  uuid:         1cbecdbb-aa7d-5aee-4ba2-864d518417df
```

In this example we will create a new deployment for our test group using firmware `1cbecdbb-aa7d-5aee-4ba2-864d518417df`.

```bash
mix nerves_hub.deployment create

NervesHub organization: nerveshub
Deployment name: qa_deployment
firmware uuid: 1cbecdbb-aa7d-5aee-4ba2-864d518417df
version condition:
tags: qa
Local user password:
Deployment test created
```

Here we create a new deployment called `qa_deployment`. In the conditions of this deployment we left the `version condition` unspecified and the `tags` set to only `qa`. This means that in order for a device to qualify for an update, it needs to have at least the tags `[qa]` and the device can be coming from any version.

At this point we can try to update the connected device.

Start by bumping the application version number from `0.1.0` to `0.1.1`. Then, create new firmware:

```bash
mix firmware
```

We can publish, sign, and deploy firmware in a single command now.

```bash
mix nerves_hub.firmware publish --key devkey --deploy qa_deployment
```

## Conditionally applying updates

It's not always appropriate to apply a firmware update immediately. Custom logic can be added to the device by implementing the [`NervesHubLink.Client`](https://hexdocs.pm/nerves_hub_link/NervesHubLink.Client.html) behaviour and telling the NervesHubLink OTP application about it.

Here's an example implementation:

```elixir
defmodule MyApp.NervesHubClient do
   @behaviour NervesHubLink.Client

   # May return:
   #  * `:apply` - apply the action immediately
   #  * `:ignore` - don't apply the action, don't ask again.
   #  * `{:reschedule, timeout_in_milliseconds}` - call this function again later.

   @impl NervesHubLink.Client
   def update_available(data) do
    if SomeInternalAPI.is_now_a_good_time_to_update?(data) do
      :apply
    else
      {:reschedule, 60_000}
    end
   end
end
```

To have NervesHubLink invoke it, update your `config.exs` as follows:

```elixir
config :nerves_hub_link, client: MyApp.NervesHubClient
```


# Command-line tools

Introducing the NervesHub command-line tools aka. the CLI.

## Installation

The easiest way to install is via [Homebrew](https://brew.sh/).

```
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
```

More details and alternative installation methods are available in [the repo](https://github.com/nerves-hub/nerves_hub_cli).

## Setup

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

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

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

```sh
nh user auth
```

If you want to pass fewer arguments on later calls we can recommend setting up some environment variables in your shell.\
Usually that means `~/.bashrc` or `~/.zshrc`. The most commonly needed is the org:

```sh
NERVES_HUB_ORG="my-org-name-here"
```

## Try it out

You can start with the help function which gives you a lot of your options:

```sh
nh
```

To list devices for example:

```sh
nh device list --org my-org --product my-product
```

This should get you started with the CLI.


# Managing organizations and products

TBD. This section is intended to talk about organizations, products, and permissions.


# Device management

TBD. This section talks about the various device management features of NervesHub and how to enable and use them:

1. Device health and tweaking thresholds
2. Enabling and using remote IEx consoles
3. Remote reboot


# HTTP API

This document describes the NervesHub web API endpoints.

[You can also view the latest OpenAPI Spec on Github.](https://github.com/nerves-hub/nerves_hub_web/blob/main/api-spec.yml)

{% openapi src="<https://raw.githubusercontent.com/nerves-hub/nerves_hub_web/main/api-spec.yml>" path="/devices/{id}" method="get" %}
<https://raw.githubusercontent.com/nerves-hub/nerves_hub_web/main/api-spec.yml>
{% endopenapi %}

{% openapi src="<https://raw.githubusercontent.com/nerves-hub/nerves_hub_web/main/api-spec.yml>" path="/orgs/{org}/keys" method="get" %}
<https://raw.githubusercontent.com/nerves-hub/nerves_hub_web/main/api-spec.yml>
{% endopenapi %}

{% openapi src="<https://raw.githubusercontent.com/nerves-hub/nerves_hub_web/main/api-spec.yml>" path="/orgs/{org}/keys" method="post" %}
<https://raw.githubusercontent.com/nerves-hub/nerves_hub_web/main/api-spec.yml>
{% endopenapi %}

{% openapi src="<https://raw.githubusercontent.com/nerves-hub/nerves_hub_web/main/api-spec.yml>" path="/users/me" method="get" %}
<https://raw.githubusercontent.com/nerves-hub/nerves_hub_web/main/api-spec.yml>
{% endopenapi %}


# Device WebSocket

NervesHub exposes a WebSocket interface which utilizes [Phoenix channels](https://hexdocs.pm/phoenix/channels.html) for long lived connections. NervesHub uses SSL peer verification so the device's certificate and applicable CA certificates must be included in the connection request. The device's SSL certificate is also used to determine organization and serial number.

The connection URI is `wss://{host}/socket/websocket`. Once connected, you can then join any of the supported channel topics to start sending and receiving messages with NervesHub.

## Message Structure

NervesHub utilizes the Phoenix message structure for all WebSocket communications. In its raw form, the message is a simple list expected to be structured as `[join_ref, ref, topic, event, payload]` . (See the [`Phoenix.Socket.Message` documetation ](https://hexdocs.pm/phoenix/Phoenix.Socket.Message.html)for more info on what each part of the message means)

```javascript
# Example messages
["join_123", "ref-453", "some_topic", "update", "some_payload"]
[null, "another-ref", "diff_topic", "response", {"key": "val"}]
[null, null, "topic3", "wat", [1, 2, 3, 4]]
```

## Joining a Channel

To communicate with NervesHub, you must join a channel on a supported topic once the websocket has been connected. This requires sending a message with the `phx_join` event to the desired topic:

```javascript
['arbitrary_join_ref', 'ref1', 'devices', 'phx_join', {}]
```

## Supported Topics

NervesHub currently supports the following channel topics:

* `device` - The main topic a device should join for receiving updates and other device specific events. There is a required payload of the `device_api_version` when connecting, e.g. `{'device_api_version': '2.0.0'}`. This is used to instruct NervesHub what messages it can send to the device.
* `console` - topic for the device to send IO requests to and from NervesHub for supporting remote console interaction. For Nerves, this is the topic used from remote IEx sessions.

## Server Events

The supported events in messages coming from the server (NervesHub) to the client:

### **device**

* `update`
  * Specifies that an update is available for the device
  * Payload fields:
    * `update_available` - Boolean stating update availability
    * `deployment_id` - ID of the deployment triggering the update
    * `firmware_url` - URL where the firmware file can be downloaded. **Note**: this has a default TTL of 10 minutes. Using the URL after that time will fail and a new update request will need to be sent for a new URL
    * `firmware_meta` - Contains a map of the various metadata elements for the firmware
      * `uuid`
      * `architecture`
      * `platform`
      * `product`
      * `version`
      * `author`
      * `description`
      * `vcs_identifier`
      * `misc`

```javascript
[
  null,
  "some-ref-1",
  "devices",
  "update",
  {
    "update_available": true,
    "deployment_id": 12,
    "firmware_url": "https://some-url.com",
    "firmware_meta": {
      "uuid": "12345-6789-0129435",
      "architecture": "arm",
      "platform": "rpi0",
      "product": "MyProduct",
      "version": "1.1.10",
      "author": "Ron Swanson",
      "description": "baconator 3000",
      "vcs_identifier": "some_version_control_sha",
      "misc": "random data"
    }
  }
]
```

* `reboot`
  * Request that device reboot. Typlically used for troubleshooting purposes
  * Payload is not used and can be ignored

```javascript
[null, "some-ref-1", "devices", "reboot", {}]
```

* `phx_err`
  * Error case, such as a channel process crashing, or when attempting to join an already joined channel
  * Payload fields:
    * `reason` - text of failure reason

```javascript
[null, "some-ref-1", "devices", "phx_err", {"reason": "some reason"}]
```

* `phx_close`
  * Channel was gracefully closed
  * Payload fields:
    * tbd

```javascript
[null, "some-ref-1", "devices", "phx_close", {}]
```

## Client Events

The supported event messages coming from the client to server (NervesHub)

### device

* `rebooting`
  * Tells the server that the device is rebooting
  * Payload is ignored server-side

```javascript
[null, "some-ref-1", "devices", "rebooting", {}]
```

* `fwup_progress`
  * Progress update during a firmware update
  * Payload fields:
    * `value` - percentage of update progress

```javascript
[
  null,
  "some-ref-1",
  "devices",
  "fwup_progress",
  {"value": 42}
]
```

* `status_update`
  * Current status of the device as it relates to a firmware update
  * Payload fields:
    * `status` - One of the supported statuses for a device
      * `idle` - waiting for an update
      * `fwup_error` - error occurred in the fwup process
      * `update_failed`
      * `update_rescheduled`
      * `unknown` - generic status for unhandled states

```javascript
[
  null,
  "some-ref-1",
  "devices",
  "status_update",
  {"state": "update_rescheduled"}
]
```


# Introduction

A NervesKey is a cryptographic hardware module that is configured to work with NervesHub. Its primary responsibility is to secure the private key used for authentication. It can also store useful information such as device serial numbers, board name, and important settings.

The following are options for adding a NervesKey to your device:

1. If you're making a custom board, add a Microchip [ATECC508A](https://octopart.com/search?q=atecc508a) or [ATECC608A](https://octopart.com/search?q=atecc608a) to an available I2C bus
2. Obtain a [NervesKey](https://www.tindie.com/products/troodonsw/nerveskey/) and solder it to the GPIO connector on a Raspberry Pi
3. Contact us for information about USB NervesKey

The NervesKey configuration of the ATECC508A/ATECC608A is documented in the [nerves\_key project's README.md](https://github.com/nerves-hub/nerves_key#atecc508a-configuration). The cryptographic modules are quite capable devices. If the NervesKey configuration does not meet your needs, refer to the module datasheets and use the [nerves\_key configuration](https://github.com/nerves-hub/nerves_key#atecc508a-configuration) as a helpful example.

{% hint style="danger" %}
Except for a few cases, NervesKeys come unprogrammed. Some fields in the NervesKey can only be programmed once.
{% endhint %}


# NervesKey for Raspberry Pi

Raspberry Pis are popular boards for prototyping and low volume deployments. Without additional hardware, the only place to store private keys is on the MicroSD card. Since the Raspberry Pi does not have a secure bootloader, the keys either need to be stored in the clear or obfuscated somehow using information in the firmware image or elsewhere on the PI. Neither option is ideal.

The NervesKey for Raspberry Pi is a tiny circuit board with an ATECC608A that can be mounted underneath the Raspberry Pi's GPIO header:

![NervesKey installed on a Raspberry Pi](/files/-LcJ3ryvpoIQMGUMa8Jn)

This location doesn't interfere with Raspberry Pi hats and still fits in many cases. If you would like a less permanent option, you can add a header to the NervesKey and attach it to the top of the Raspberry Pi or even add header pins for insertion in a breadboard. We recommend soldering it to the bottom, though, since NervesKeys are typically associated with one device. The keys can also be removed using solder wick.

## Mounting the NervesKey

If you're using a Raspberry Pi Zero or Zero W that does not have header pins, solder those in first. If you're planning on mounting the NervesKey to the bottom of the board, try to use a little less solder on the right-most pins. Refer to the picture above for where the NervesKey is mounted. By using less solder, you'll be able to mount the NervesKey flatter on the bottom of the board.

{% hint style="info" %}
In an attempt to mount the NervesKey as flat as possible, we used solder wick to remove solder off the header pins on a few Raspberry Pis. We've decided that it wasn't worth it. The NervesKey mounts close enough to flat on the boards we have anyway. If you soldered header pins in and put a particularly large amount of solder on the pins, you may need to remove some to mount the board.
{% endhint %}

The NervesKey connects to the I2C and power pins on the GPIO connector. If you match your work to the picture, you'll be fine.

After you've soldered the NervesKey, go to the [Provisioning](broken://pages/-MHDArXH4T_PRPWdBLsY) section for programming it.

## More information and schematics

If you're already designing a custom board that attaches to the Raspberry Pi's GPIO header, it is likely going to be more convenient and less expensive to add an ATECC508A or ATECC608A to your board. See the [schematics](https://github.com/nerves-hub/nerves_key/blob/master/hw/assets/TSW19001_NERVESKEY_X1_SCH.PDF) for the connections.


# Private keys and certificates

Each NervesKey holds one ECC P256 private key. The provisioning process creates the private key and it never leaves the module. The ATECC508A and ATECC608A modules used on the NervesKeys have countermeasures to make extracting the private keys impractical. The associated public key can always be retrieved from the NervesKey.

The X.509 standards define an encoding for keys and their associated metadata. A key and its metadata are referred to as a certificate. Certificates can then be cryptographically signed by other keys to establish a chain of trust. X.509 certificates have two common encodings, PEM and DER. DER is a binary encoding and PEM is a text encoding.

The NervesKey holds information for up to four certificates:

1. A primary device certificate
2. The certificate that signed the primary device certificate
3. An auxiliary device certificate
4. The certificate that signed the auxiliary device certificate

The provisioning procedure requires a signing certificate and creates the primary device certificate. Both certificates are recorded to the NervesKey. The auxiliary certificates are similar in function, but can be filled in later and rewritten.

The X.509 certificate that signs the device certificate is also called a Device Certificate Authority certificate (Device CA certificate) and must be uploaded to NervesHub for the device to be allowed to connect over TLS.

All certificates have validity periods. Validity periods establish the dates where a device can be used. It is crucial to have these set appropriately or TLS connections will not work. Deciding how long certificates are valid requires some thought. Device CA certificates are maintained outside of the NervesKey and may need to be kept in less than ideal conditions. NervesHub only requires a valid Device CA certificate the first time that a device connects (it saves trusted device certificates).

{% hint style="warning" %}
Nerves devices often start with their clocks set to January 1st, 1970. X.509 certificates will fail validation unless the clock is updated. The [`nerves_time`](https://hex.pm/packages/nerves_time) library is one option for synchronize the clock.
{% endhint %}

Since the NervesKey protects the private key, device certificates typically have long validity periods. The Device CA certificates have much shorter validity periods and can be removed from NervesHub once all devices have connected once.

Since the NervesKey has limited storage, the contents of the certificates must be kept small. Additional compromises were made in the granularity of validity dates and other fields. The NervesKey software follows the [ATECC Compressed Certificate Specification](http://ww1.microchip.com/downloads/en/AppNotes/Atmel-8974-CryptoAuth-ATECC-Compressed-Certificate-Definition-ApplicationNote.pdf) for encoding and storing certificates. Unfortunately, the compressed certificate constraints make it difficult to generate certificates with programs like `openssl`. The `nerves_key` library has helper functions that create certificates using the `x509` library and Erlang's `public_key` application. Note that the X.509 certificates are still completely valid and usable by services that require them. If a third party service cannot be made to work with the constraints imposed by the NervesKey, you can still create an X.509 certificate for their use with the public key from the NervesKey. However, you cannot store that X.509 certificate in the NervesKey.


# General NervesKey storage

NervesKeys provide a small amount of non-key storage. This includes

* Manufacturer serial number assignment
* Board name and revision
* Device settings

The manufacturer serial number is used to identify devices associated with the NervesKey to NervesHub. This is the serial number that might be printed on a label on the outside of the device. Manufacturer serial numbers are written to one-time-programmable memory on the NervesKey and can never be changed.

{% hint style="warning" %}
NervesHub requires all devices within an organization to have unique serial numbers. If you do not want to maintain a list of serial numbers, we recommend that you use the default provided by the NervesKey provisioning software. It is guaranteed to be globally unique.
{% endhint %}

Storage for a board name and hardware revision is also available. Like the manufacturer serial number, it is written to one-time-programmable memory. Setting these fields is only available programmatically via the `nerves_key` library. The board name is set to "NervesKey" by default. Most users don't need to set this field and its not displayed or used internally by any NervesHub components.

Finally, each NervesKey provides 524 bytes of user storage for settings. This can be handy for storing important information settings that you don't want to lose if someone replaces the MicroSD card on your device. This could hold WiFi settings, for example. Another use is to store calibration data that's determined at manufacturing time. See [`NervesKey.put_settings/2`](https://hexdocs.pm/nerves_key/NervesKey.html#put_settings/2) and [`NervesKey.get_settings/1`](https://hexdocs.pm/nerves_key/NervesKey.html#get_settings/1) for more information.

See the NervesKey [ATECC508a Configuration](https://github.com/nerves-hub/nerves_key#atecc508a-configuration) for the low level storage of all of the above.


# Provisioning in Elixir

The quickstart firmware is useful for getting started with NervesKeys. This section describes one way of provisioning NervesKeys programmatically. More sophisticated and automated ways are certainly possible.

## Prerequisites

To provision a NervesKey, make sure that you have the following:

1. A Device CA certificate and its private certificate (this is also referred to

   as a signing certificate)
2. A serial number for your device
3. A name for the device

The signing certificate and serial number are very important. After the provisioning process, they are locked down and cannot be changed without replacing the NervesKey. The device name is purely informational unless you choose to use it in your software.

NervesKeys support an auxillary set of certificates that identify the device. These are writable after the provisioning process. Since they're writable, they can be provisioned and updated at any time. As such, they're not programmed in the first-time provisioning process.

### Creating a Device CA certificate

If you have not already created a Device CA certificate, you will need to create one now. For simplicity, the private key is handled in the clear. Depending on your manufacturing or provisioning environment, you may want to store the private key in a hardware security module (HSM) or keep it encrypted.

As mentioned earlier, the NervesKey requires that the Device CA certificate adhere to the [ATECC Compressed Certificate Definition](https://www.microchip.com/wwwAppNotes/AppNotes.aspx?appnote=en591852) constraints so that it can be stored. By far the easiest way to create compliant certificates is to use the `nerves_key` `mix` commandline helpers. If you don't already have an Elixir Project that has `nerves_key` as a dependency, create one now, and run `mix deps.get` to pull down the helpers. Then run the following commands:

```bash
$ mix nerves_key.signer create nerveskey_prod_signer1
Created signing cert, nerveskey_prod_signer1.cert and private key, nerveskey_prod_signer1.key.

Please store nerveskey_prod_signer1.key in a safe place.

nerveskey_prod_signer1.cert is ready to be uploaded to the servers that need
to authenticate devices signed by the private key.
```

There is no magic in the compressible certificates. They're just limited in what they can contain. You can inspect them with `openssl x509 -in nerveskey_prod_signer1.cert -text`.

Check with your IoT service on how the signer certificate is used. If it's only used for first-time device registration, then the signer certificate may not need a long expiration time. You may also be interested in creating more than one signing certificate if you have more than one manufacturing facility.

### Manufacturer serial numbers

The NervesKey saves the manufacturing serial number in the one-time programmable memory on the ATECC508A/608A and also in the device's X.509 certificate. The device's X.509 certificate is signed, so cloud servers can trust the manufacturer serial number.

At this point, you're the manufacturer. Decide how you'd like your serial numbers to look. Whatever you pick, it must fit in 16-bytes. Representing the serial number is commonly done in ASCII. If you don't want to deal with this, do what we do (Base32-encode a globally unique identify burned into the ATECC508A/608A). `NervesKey.default_info/1` will create this default for you:

```elixir
iex> {:ok, i2c} = ATECC508A.Transport.I2C.init([])
{:ok, {ATECC508A.Transport.I2C, {#Reference<0.879310498.269090821.27261>, 96}}}
iex> NervesKey.default_info(i2c)
%NervesKey.ProvisioningInfo{
  board_name: "NervesKey",
  manufacturer_sn: "AER245UNQOY4T3Q"
}
```

## Provisioning

Now that you have a Device CA certificate, the Device CA's private key, and a manufacturer serial number, you can provision a NervesKey or the standalone ATECC508A/608A. Usually there's some custom manufacturing software that performs this step. We'll provision at the iex prompt.

Use `sftp` to copy the Device CA certificate and private key to your device. We'll put them `/tmp` so that they disappear on reboot:

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

Next, go to the IEx prompt on the device and run the following:

```elixir
# Customize these or use `NervesKey.default_info/1` for defaults
cert_name="nerveskey_prod_signer1"

# These lines should be copy/paste
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.default_info(i2c)

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

If the last line returns `:ok` after about 2 seconds, then celebrate. You successfully programmed a NervesKey. You can't program it again. If you try, you'll get an error.

## Provisioning an auxiliary certificate

Auxiliary certificates are provisioned very similarly to the first time device programming. You will need to copy the Device CA certificate and private key to the device as before. This is likely a different certificate and private key, though.

The following assumes that you have already run the first time provisioning steps above.

```elixir
# Customize these
cert_name="nerveskey_prod_signer1"

# These lines should be copy/paste
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([])
NervesKey.provision_aux_certificates(i2c, signer_cert, signer_key)
```

See the `NervesKey.clear_aux_certificates/1` function for erasing the auxiliary certificate.

## Settings

The `NervesKey.put_settings/2` and `NervesKey.get_settings/1` APIs let you store and retrieve a map. Since the storage is limited and relatively slow, this is intended for settings that rarely change or may be tightly coupled with certificates already being stored in the NervesKey.

Internally, `NervesKey` calls `:erlang.term_to_binary` to convert the map to raw bytes and then writes it to the ATECC508A/608A's persistent storage. Not that the keys in the map take up space as well.


# Nerves integration

Nerves can use the serial number programmed into the NervesKey for the hostname. This can come in handy for finding devices on the network since the hostname can be reported via DHCP or broadcast using mDNS.

{% hint style="info" %}
The DHCP client for Nerves reports hostnames to the DHCP server. Some DHCP servers like dnsmasq-based ones register those names with a colocated DNS server so that requests of that name return the IP address of the Nerves-based device. Many DHCP servers do not do this, so mDNS is the only option for easily finding the device.
{% endhint %}

In Nerves, the `boardid` program reads serial numbers off of boards. It has options to support many different kinds of hardware. To have it read the serial number off the NervesKey, add a `rootfs_overlay/etc/boardid.conf` file to your Nerves project. This will override the `boardid.conf` distributed with your Nerves system (assuming that you're using an official Nerves system). The contents should be:

```
# /etc/boardid.conf

-b nerves_key -f /dev/i2c-1
```

If your NervesKey is attached via a different I2C bus, update the `-f` option accordingly.

If this doesn't work, you can test `boardid` at the IEx prompt using the Toolshed `cmd` helper (or `System.cmd/3` if not using Toolshed) by running:

```elixir
iex> cmd("boardid -b nerves_key -f /dev/i2c-1 -b force -f failed")
abc123
```

The `-b force -f failed` part ensures that `boardid` doesn't fall back to try to find a serial number based on options in a `/etc/boardid.conf` file. Run `boardid --help` for more information.


# NervesHubLink integration

This section walks you through integrating a device with a NervesKey with NervesHub.

## Device registration

Devices must be registered with NervesHub via the commandline tools, web user interface or via an API integration first to authenticate with a NervesKey. The only exception is if you are using the JITP feature in which case registration is automated.

The command-line tools are the easiest for this. Follow [these instructions](/nerves-hub/cli-tools) to get them installed.

You will need the device's manufacturing serial number for this step. If you don't have it `NervesKey.manufacturer_sn/1` can retrieve it. From the CLI, run `mix nerves_hub.device create`. Fill in the manufacturing serial number when it asks for an identifier. Fill in the `description` and `tags` fields as you'd like:

```
$ nh device create
NervesHub server: api.nerves-hub.org:443
NervesHub organization: your_organization
identifier: 123456789ABCDE
description: My favorite device
tags: qa
Local NervesHub user password:
Device 123456789ABCDE created
```

{% hint style="info" %}
When registering devices in bulk, we've found that there's usually a convenient hook in the manufacturing process to call `mix nerves_hub.device create` with commandline parameters.
{% endhint %}

If you are not using the Signer CA approach you also want to upload a certificate per device. This can be done using:

```
nh device cert import 123456789ABCDE device-cert-file-path-goes-here
```

## Integration with your Nerves project

You can tell `nerves_hub_link` to read those certificates and key from the chip and assign the SSL options for you by enabling add it as a dependency in your `mix.exs`:

```elixir
def deps() do
  [
    {:nerves_hub_link, "~> 2.7"},
    {:nerves_key, "~> 1.2"}
  ]
end
```

NervesKey will default to using i2c bus 1 and `:primary` cerificate pair.

NervesHubLink will use the NervesKey library by default if it is available. NervesHubLink itself needs to be configured a bit:

```elixir
config :nerves_hub_link,
  # point this at the device endpoint of your instance
  host: "devices.nervescloud.com",
  # if you want the remote console
  remote_iex: true,
```


# MQTT integration

This section provides guidance for using the NervesKey with popular MQTT brokers.

NervesKeys aren't only for use with NervesHub. If a service allows users to provide their own certificate authority, it should be possible to also use the NervesKey. Nearly all services use TLS or DTLS so these instructions provide guidance for using the [Erlang/OTP SSL application](http://erlang.org/doc/apps/ssl/users_guide.html). The following diagram shows the other compoents that are involved with establishing and authenticating MQTT connections over TLS:

![Libraries supporting MQTT connections using the NervesKey](/files/-LfWMoH0VAPQetjmU7fG)

In particular, the NervesKey integration with Erlang makes use of the [Engine API](http://erlang.org/doc/apps/crypto/engine_keys.html) to redirect private key operations to the cryptographic module. While `nerves_key_pkcs11` provides a PKCS#11 implementation, be aware that it is minimal and only supports the operations necessary for establishing TLS connections. Programs using the NervesKey should still use the `nerves_key` library for provisioning and everything else.

To use the Erlang/OTP SSL application's Engine API, you will need to do the following:

1. Initialize the engine to start the `nerves_key_pkcs11` integration
2. Replace the locations where you would have suppliced a private key with an Elixir map that has a reference to the engine and other information

The `nerves_key_pkcs11`library has a helper functions for both tasks.

The following example code fragment shows how to start the Tortoise MQTT client API:

```
{:ok, engine} = NervesKey.PKCS11.load_engine()
{:ok, i2c} = ATECC508A.Transport.I2C.init([])

signer_cert = X509.Certificate.to_der(NervesKey.signer_cert(i2c, :primary))
cert = X509.Certificate.to_der(NervesKey.device_cert(i2c, :primary))
key = NervesKey.PKCS11.private_key(engine, {:i2c, 1})
cacerts = [signer_cert | aws_root_certs()]

Tortoise.Supervisor.start_child(
  client_id: manufacturing_serial_number(),
  handler: {MQTTHandler, []},
  server: {
    Tortoise.Transport.SSL,
    verify: :verify_peer,
    host: mqtt_host(),
    port: mqtt_port(),
    alpn_advertised_protocols: alpn_advertised_protocols(),
    server_name_indication: server_name_indication(),
    cert: cert,
    key: key,
    cacerts: cacerts,
    versions: [:"tlsv1.2"]
  },
  subscriptions: topics()
 )
```


