Skip to content

TLS onboarding (owner verification)

Device tokens can carry an owner verification mode (OVMode) — an extra security layer on top of the auto-join token. With OVMode set to TLS, a device that provisions itself with the token is not trusted just for knowing the token: it must also prove, over a mutual-TLS connection, that it holds a private key belonging to a certificate chain you registered up front (the root of trust). Until it does, the device is marked as ownership_unverified and its API access is restricted.

This protects against the main weakness of plain token provisioning: an auto-join token is a shared secret baked into images at the factory, so it can leak. With TLS verification enabled, a leaked token alone is not enough to onboard a rogue device into your account.

The protocol

The whole flow at a glance — the owner registers a trust anchor once, then every device proves itself against it on first boot:

Owner (Hub UI)DevicePantahub API1 · Create device token with root of trust (OVMode = TLS)2 · Register with auto-join tokendevice assigned · ownership_unverified3 · /auth/login — device-id + creds.secretshort-lived token, restricted to validation4 · /devices/…/ownership/validate over mutual TLSserver verifies the client certificateagainst the token's root of truststatus: completed5 · /auth/login againnew token with full accessnormal API access with device credentials

1. Owner registers a root of trust

When creating a device token (/devices/tokens/new), enable Owner verification and select the TLS mode. Paste your certificate chain, Base64 encoded, as the root of trust. The chain is stored with the token; every device joining through this token inherits an ovmode extension referencing it.

New device token form with owner verification enabled and TLS mode selected

Other modes exist (claim, manual, default) — this page covers tls. For where TLS verification sits among the onboarding routes, see How onboarding works.

2. Device provisions with the token

The device registers on first boot using the token (Pantahub-Devices-Auto-Token-V1 header, see Auto-join tokens). It is auto-assigned to your account as usual, but because the token has OVMode enabled, the device record carries ownership_unverified: true and a pending verification status.

3. Device logs in — with restricted access

The device authenticates against /auth/login with its device identity (prn:::devices:/<device-id>) and the creds.secret Pantavisor stored during registration. The token issued before verification is short-lived and deliberately scoped down: the only call it permits is the ownership validation endpoint. Any other API call fails, and the device stays in a "syncing" state until verification succeeds.

4. Device proves key possession over mutual TLS

The device calls the validation endpoint,

/devices/{device-id}/ownership/validate

presenting its client certificate and private key as the TLS client identity (plus the restricted bearer token from step 3). The server validates the presented certificate against the token's root of trust.

5. Verification completes

On success the response reports status: completed and the device's ownership_unverified flag is cleared. The device simply authenticates again at /auth/login — this time it receives a normal token with full access, and from here on it operates like any other device. Nothing else is required by the protocol; anything beyond the re-login (recording state locally, rebooting) is up to the device's own software.

Security properties

  • Two independent factors. Onboarding requires both the auto-join token and possession of a private key chained to your root of trust. Leaking either one alone is not sufficient.
  • Short-lived, restricted pre-verification token. Until validation succeeds, the device only ever holds a short-lived token that can reach nothing but the validation endpoint, so an unverified device cannot read or write anything else in your account.
  • Owner-controlled trust anchor. You choose and upload the certificate chain; Pantahub never sees the device's private key — it only verifies the TLS handshake against the chain.
  • Keep the private key in hardware. The device's private key should be generated inside — and never leave — a hardware-backed keystore. Common options are a TPM 2.0 (discrete chip or firmware TPM such as Intel PTT or AMD fTPM), a secure element like the Microchip ATECC608 or NXP EdgeLock SE050, an SoC keystore behind Arm TrustZone/OP-TEE, or SoC crypto units such as NXP CAAM on i.MX. Exposed through PKCS#11 (e.g. tpm2-pkcs11) or an OpenSSL provider, the TLS stack performs the handshake while the key stays in hardware. If none of that is available, at minimum keep file-based keys on a Pantavisor encrypted disk — and never commit real keys to a repository. Rotating means issuing a new chain, updating the token's root of trust, and rolling new keys to devices.

Storing the key on an encrypted disk

When the TLS stack needs the key as a file (as the example container does), Pantavisor's disks feature can provide an encrypted dm-crypt volume whose encryption key is itself sealed by hardware. Disks are declared in the device state's device.json and come in several types:

  • dm-crypt-caam — the volume key is protected by the NXP CAAM crypto module. With mode: mainline it uses the Linux trusted-key subsystem, so the plaintext key never leaves the kernel; mode: nxp uses NXP's tagged key blobs.
  • dm-crypt-dcp — same idea for NXP DCP platforms (i.MX6ULL class), also with nxp and mainline modes.
  • dm-crypt-versatile — software-only fallback with a plaintext key file. It protects the data at rest (e.g. against copying the storage offline) but not against an attacker with root on the device — prefer the hardware-backed types where the silicon supports them.

A minimal setup: declare the disk once,

json
{
    "disks": [
        {
            "name": "dm-secrets",
            "type": "dm-crypt-caam",
            "mode": "mainline",
            "path": "-v2 /storage/dm-crypt-files/secrets/caam.img,2,key"
        }
    ]
}

then have the onboarding container mount its key directory from that disk via the storage section of its run.json:

json
"storage": {
    "pv-tlsownership--var-tlsownership": {
        "disk": "dm-secrets",
        "persistence": "permanent"
    }
}

Pantavisor creates, formats and mounts the encrypted volume on demand the first time it is referenced, so /var/tlsownership — where the container expects cert.pem and key.pem — lives on encrypted storage from the first boot. See the Pantavisor disks documentation for the full type reference, dual-disk key migration, and mount recovery details.

Example container: pv-tlsownership

pv-tlsownership is a small Alpine-based Pantavisor container (curl + jq + one shell script) that runs the device side of this protocol. On startup it:

  1. Checks the ovmode_status device-meta key via the pv-ctrl socket — if already completed, it just idles.
  2. Reads the device ID from /pantavisor/device-id and the API host from /pantavisor/pantahub-host, and pulls creds.secret from the Pantavisor config (retrying until Pantavisor has populated them).
  3. Logs in at /auth/login as the device to obtain the restricted token.
  4. Calls /devices/<id>/ownership/validate with --cert/--key, i.e. as a TLS client using your certificate and key.
  5. On status: completed, sets ovmode_status=completed in device-meta and reboots the device.

Note that step 5 is this container's own bookkeeping, not part of the protocol: the device-meta flag lets the script skip validation on later boots, and the reboot restarts the stack cleanly in the verified state. Protocol-wise all that's needed after validation is to log in again for a full-access token.

Also, the container reads the certificate and key from plain files for simplicity. In production the private key should live in a hardware keystore (TPM 2.0, secure element, TrustZone/OP-TEE — see Security properties) and be reached through PKCS#11 rather than shipped as a file — or, at minimum, the key files should sit on a Pantavisor encrypted disk.

Configuration

The script reads its inputs from these locations (overridable via flags or environment):

SettingDefaultOverride
Client certificate/var/tlsownership/cert.pem-c / CERT_PATH
Private key/var/tlsownership/key.pem-k / KEY_PATH
Device ID file/pantavisor/device-id-d
Pantahub host file/pantavisor/pantahub-host-p
pv-ctrl socket/pantavisor/pv-ctrl-s

Using it on your devices

  1. Create a device token with owner verification mode TLS and your Base64-encoded chain as root of trust (see Tokens):

    Owner Verification Mode section: TLS mode with a Base64 encoded certificate chain as root of trust

  2. Add the pv-tlsownership container to your device state. Prebuilt Pantavisor packages exist for amd64, arm32v6, arm32v7 and arm64v8; replace the placeholder cert.pem and key.pem under the package's _config/pv-tlsownership/var/tlsownership/ with the certificate and key matching the chain you registered on the token.

  3. Provision the device with the token and boot it. The container performs the validation on first boot and reboots the device once verified; from then on the device operates with full access.

Devices that fail validation stay visible in your registry but remain unverified and restricted — a useful signal that a token may be circulating outside your factory flow.