Docs menu

Pilot Devices

How to set up a small run of pilot units so each one keeps connecting for as long as its connection exists. Each unit holds its own key, and it makes a short-lived pass from that key and renews it itself. Nothing on the unit expires, and nobody has to come back to it.

Pilot grade. A key that firmware holds in ordinary storage is fine for prototypes and pilots. Production and medical units should hold their identity in hardware, a SIM or a secure element, so the key cannot be read out of the unit. That path is coming; until then, keep pilot units few and known.

What a unit holds

A unit holdsA unit never holds
Its own key-based connection string, written once at flashingA token copied from Connection Info
The MQTT host, username and topic from Connection Info (they do not change)A key shared with another unit, or an OpenX API key
A correct clock, set from the networkA pass it made earlier, written to storage

The key-based connection string has this shape:

HostName=<hub>.azure-devices.net;DeviceId=<device id>;SharedAccessKey=<device key>

One key per unit

Today each unit is its own data connection, so each unit gets its own key:

  1. Add a Connection for the unit, named after it (for example its serial number), and Deploy. Deploy registers the device in the hub, and the hub gives it its own random key.
  2. Open the connection's Inspector -> Connection Info and copy Device ConnStr (primary).
  3. Write that string to the unit once, when you flash it. Keep the only other copy in your secret store, never in source control, a spreadsheet or a log.
  4. Check the unit: its data shows in the connection's Live Stream.

The key comes from the hub. It is never worked out from the unit's printed ID or serial number, and no two units share one, so one unit's key opens nothing else. The unit keeps that key for years: it is never re-flashed to keep connecting. Keep Device ConnStr (secondary) for the day a key is changed (Live Devices).

Coming: enrolling units in a device provisioning service, so a unit registers itself when it first powers on instead of being added one connection at a time. It is not available yet.

If the unit runs an Azure IoT SDK

Give the SDK the key-based connection string and you are done: the SDK makes the pass, renews it and reconnects by itself. Keep the clock right (below). The next section is for firmware without an SDK.

Mint the pass from the key

Firmware without an SDK makes its pass (a shared access signature) from the key each time it connects, in memory:

  1. Take the SharedAccessKey value and base64-decode it to bytes.
  2. The resource is <hub>.azure-devices.net/devices/<device id>. Lowercase it, then URL-encode it.
  3. The expiry is a Unix time in seconds: now plus the pass's lifetime (24 hours, below).
  4. The string to sign is the encoded resource, a newline, and the expiry.
  5. Sign it with HMAC-SHA256 using the decoded key, base64-encode the result, then URL-encode it.
  6. The pass is:
SharedAccessSignature sr=<encoded resource>&sig=<encoded signature>&se=<expiry>

Connect over MQTT on port 8883 with TLS, with the DeviceID as the client ID, the MQTT Username from Connection Info, and the pass as the password. Publish to the MQTT Telemetry Topic. AMQP and HTTPS take the same pass.

The same steps in TypeScript, as a reference to check firmware against:

async function mintPass(host: string, deviceId: string, keyB64: string, lifetimeSeconds: number) {
  const resource = encodeURIComponent(`${host}/devices/${deviceId}`.toLowerCase());
  const expiry = Math.floor(Date.now() / 1000) + lifetimeSeconds;
  const keyBytes = Uint8Array.from(atob(keyB64), (c) => c.charCodeAt(0));
  const key = await crypto.subtle.importKey(
    'raw',
    keyBytes,
    { name: 'HMAC', hash: 'SHA-256' },
    false,
    ['sign'],
  );
  const signed = await crypto.subtle.sign(
    'HMAC',
    key,
    new TextEncoder().encode(`${resource}\n${expiry}`),
  );
  const sig = encodeURIComponent(btoa(String.fromCharCode(...new Uint8Array(signed))));

  return `SharedAccessSignature sr=${resource}&sig=${sig}&se=${expiry}`;
}

Never write a pass to storage. Make a new one at each connect and keep only the key.

Keep the clock right

The hub checks the pass's expiry against its own clock. A unit whose clock is behind makes a pass the hub reads as already expired, and the connection is refused; a clock that is ahead makes a pass that lasts longer than you meant. Set the clock from the network (NTP, or the cellular network's time) before the first pass after every boot. A unit with no battery-backed clock always starts wrong, so it syncs first.

Renew once a day

Make each pass last 24 hours, and make a new one before the old one runs out.

  • MQTT: the hub closes the connection when the pass expires, so a renewal is a disconnect and a reconnect with the new pass: about one reconnect a day.
  • AMQP: the pass is refreshed in place, and the connection stays open.
  • A unit that sleeps and wakes makes a fresh pass at each wake and does not need to count the day.

This is not a 24-hour connection. The unit keeps its key for years and renews its own pass for as long as it runs; nobody re-flashes it. A pass that leaks works for at most a day.

Testing tokens

Connection Info also shows ready-made device tokens (the MQTT, AMQP and HTTP passwords). Each lasts seven days unless a longer device token is chosen (30 to 365 days, for people with the right to deploy), and testing tokens are made from the inspector. Use one to try a connection from a laptop or a test client. Never put one in a unit: a unit that stores a token stops connecting when the token expires, and nobody can renew it from outside.

After the pilot

Per-unit keys are the pilot step. Production and medical units move to an identity held in hardware, a SIM or a secure element, enrolled with a device provisioning service; this guide will cover that when it ships.

On this page