# Embedded recipes

**Three copy-paste recipes put a forge-proof, owner-revocable network identity under an OTA pipeline, a fleet gateway, and a genuine part. Each one runs today on shipped primitives, and each names exactly where it stops.**

They share one primitive: a routable IPv6 `/128` that a device *derives* from a key it already holds, DNSSEC-signed and [DANE-pinned](/docs/dane) so anyone can verify it and nobody can copy it.

> **What's shipped, stated honestly.** The device `/128` derivation, the keyless [verify](/docs/verify) / RDAP surface, WireGuard egress + policy + logs on Linux-class devices, and one-call [revoke](/docs/control-plane) are **live**. These recipes run from a Linux-class device, gateway, or workstation today; the keyless HTTPS checks additionally run from any embedded client with a TLS stack (ESP32-class and up). The MCU-native SDKs (C, ESP-IDF, Arduino, Zephyr, Yocto, pico-sdk) and the packaged MQTT-DANCE / manufacture-line recipes are **roadmap**, phase-2 deliverables: nothing below depends on them, and nothing below presents them as installable. Whisper anchors the **device/cloud IP boundary**: not secure boot, not the RTOS, not a radio protocol's own security layer.

## The shared primitive: an identity a device derives, not one you flash into it

A device already carries a hardware key from the factory: the 802.1AR IDevID key in its secure element or OTP. Whisper takes only its **public** SPKI and, optionally with a canonical device identifier as the domain separator, *deterministically derives* a `/128` under `2a04:2a01::/32` (`AS219419`). The private key never leaves the device.

- **Deterministic & idempotent:** the same key + identifier always yields the same `/128`.
- **Tenant-bound & fleet-unlinkable:** an outsider deriving under a different tenant gets a *different* address; no enumeration oracle.
- **Forge-proof:** DANE-EE `3 1 1`-pinned to that device's key with DNSSEC-signed reverse DNS.
- **Revocable worldwide, by the owner:** one call tears the `/128`, its PTR, and its DANE pin down everywhere at DNS-TTL speed.

```sh
# Provision a device's identity from the key it already holds (idempotent).
# The identifier is optional; today it rides the live device_id argument
# (17-character canonical form; a free-form serial/EUI-64 argument is roadmap):
curl -s https://graph.whisper.security/api/query \
  -H "X-API-Key: whisper_live_xxx" \
  -H "content-type: application/json" \
  --data @- <<'JSON' | jq .
{"query":"CALL whisper.agents({op:'connect', args:{tier:'wireguard', identity_public_key:'<base64 SPKI of the device key>', device_id:'GW00427A310B55EF2'}}) YIELD op, ok, status, result, error RETURN op, ok, status, result, error"}
JSON

# For a specific sub-module or part, add ecu_serial as the component separator:
#   args:{tier:'wireguard', identity_public_key:'…', device_id:'GW00427A310B55EF2', ecu_serial:'MOD-7F31'}
```

Once provisioned, every check in the three recipes below is **keyless**: no account, no API key, just DNS and TLS.

## Recipe 1: Verify the OTA/update server, from the device

Before a device fetches an image, it should know it is talking to the genuine update host. With the update endpoint carrying a Whisper identity, that check is a DANE pin comparison, and the keyless HTTPS form runs on any client with a TLS stack, ESP32-class and up, today.

> **Boundary.** This complements your firmware image signing; it does **not** replace it. Signed images answer "is this image genuine"; this recipe answers "is this server genuine, before I even fetch." Keep both.

```sh
# On a Linux-class device (or your workstation), the strong trustless form:
whisper verify --trustless ota.demo.agents.whisper.online
dnssec   pass   DNSSEC-root   AAAA, PTR and TLSA(3 1 1) all DNSSEC-validated to the IANA root
dane     pass   DNSSEC-root   served leaf SPKI-SHA256 == TLSA pin

# The same check with stock tools:
dig +short TLSA _443._tcp.ota.demo.agents.whisper.online
openssl s_client -connect ota.demo.agents.whisper.online:443 </dev/null 2>/dev/null \
  | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der \
  | openssl dgst -sha256        # must equal the TLSA pin above

# The keyless HTTPS verdict, runnable from any TLS-capable embedded client today:
curl -s https://whisper.online/verify-identity/ota.demo.agents.whisper.online | jq .
```

An MITM'd or spoofed host answering on that name fails immediately: it cannot present the pinned key, and the pin it fails against is DNSSEC-signed, so the failure is provable, not a heuristic.

## Recipe 2: A governed /128 for a fleet gateway (shipped, end to end)

The concentrator that fronts your fleet is a Linux computer, which means the *full* product works on it today: a derived identity, routed egress that sources from it, default-deny policy, and per-identity logs.

> **Boundary.** Node↔gateway auth stays with the local bus (OTAA, Modbus, BLE). This recipe anchors the gateway↔cloud IP boundary: the only place those nodes touch the internet.

```sh
# 1. Provision (above) returns a wireguard_config. Bring it up on the gateway:
wg-quick up ./whisper-gw.conf        # or feed it to wireproxy for a no-root userspace tunnel

# 2. Every packet now sources from the gateway's own /128. Prove it from anywhere:
dig +short -x 2a04:2a01:e5a7:41c9:04d0:c85f:3a1b:77e2
04d0c85f3a1b77e2.demo.agents.whisper.online.

# 3. Default-deny egress: the gateway reaches its platform and nothing else:
whisper policy --agent 2a04:2a01:e5a7:41c9:04d0:c85f:3a1b:77e2 \
  --default deny --allow telemetry.example-maker.com,ota.example-maker.com

# 4. What did it actually reach last night? Per-identity, one keyed call:
CALL whisper.agents({op:'logs', args:{agent:'<the gateway /128>', kind:'dns', from:'-24h'}})
```

A hijacked gateway can't quietly call home to a new owner (default-deny blocks it, and the attempt is logged per-identity), and the whole segment behind it inherits an attributable, revocable front door.

## Recipe 3: The anti-counterfeit module

A genuine module or smart consumable holds a secure-element key from the factory; a counterfeit doesn't hold the one that was *registered*. Bind the genuine key to a Whisper `/128` at manufacture (the `ecu_serial` component separator gives each part its own identity):

> **Boundary.** This complements the secure element already in the part; it does **not** replace the chip. It makes the chip's identity *network-verifiable* and *instantly revocable*.

```sh
# Genuine part: a registered, DANE-anchored identity. It proves itself:
curl -s https://whisper.online/verify-identity/2a04:2a01:e5a7:41c9:04d0:c85f:3a1b:77e2
{"is_whisper_agent": true, "dane_ok": true, "jws_ok": true, …}

# Counterfeit: no registered /128 for its key, no DANE pin it can present:
curl -s https://whisper.online/verify-identity/2a04:2a01::1
{"is_whisper_agent": false, "detail": "no Whisper agent identity anchors this address"}

# A distributor or service centre confirms genuineness with RDAP, no vendor call:
curl -s https://whisper.online/ip/2a04:2a01:e5a7:41c9:04d0:c85f:3a1b:77e2 | jq
```

## One `revoke`, blacklisted worldwide, thrown by the owner

```sh
curl -s https://graph.whisper.security/api/query \
  -H "X-API-Key: whisper_live_xxx" \
  -H "content-type: application/json" \
  -d '{"query":"CALL whisper.agents({op:'"'"'revoke'"'"', args:{agent:'"'"'2a04:2a01:e5a7:41c9:04d0:c85f:3a1b:77e2'"'"'}})"}'

# With the CLI, the same teardown is one verb:
whisper kill --revoke 2a04:2a01:e5a7:41c9:04d0:c85f:3a1b:77e2
```

After that, Recipe 3's genuine-part check flips to `is_whisper_agent:false` for that address everywhere: revocation and counterfeit-detection are, deliberately, the *same* mechanism. On a legitimate resale or RMA, revoke and re-register under the new owner.

## Attribution: which fleet, which destinations (keyed)

```sh
curl -s https://graph.whisper.security/api/query \
  -H "X-API-Key: whisper_live_xxx" \
  -H "content-type: application/json" \
  -d '{"query":"CALL whisper.identify(\"2a04:2a01:e5a7:41c9:04d0:c85f:3a1b:77e2\")"}' | jq .
```

This is the graph API, not a CLI subcommand: there is no `whisper identify` verb; the call above *is* the interface.

## Where each recipe stops (and what's roadmap)

| Recipe | Complements | Does *not* touch / replace |
|---|---|---|
| Verify the OTA server | your firmware image signing | image formats, signing keys, the update orchestration itself |
| Governed gateway /128 | the local bus's own auth (OTAA, Modbus, BLE) | node↔gateway security; the radio stack |
| Anti-counterfeit module | the secure element in the part | the chip itself; any bus-level pairing |

> **Roadmap, clearly labelled.** The MCU-native SDKs (C, ESP-IDF, Arduino, Zephyr, Yocto, OpenWrt/Buildroot, pico-sdk), the manufacture-line provisioning recipe, and the MQTT-DANCE broker recipe are phase-2 deliverables: proposed, not yet installable, and each will be proven with a full end-to-end test (provision a real device → run it → verify the routable /128 with `dig -x`/RDAP → prove the egress) before its guide appears here. Streaming evidence into a SIEM ships today for **Splunk**, **Microsoft Sentinel** and **OpenCTI**; **STIX/TAXII** is roadmap. Nothing on this list is required for the three recipes; they run on shipped primitives alone.

## Next

- [Device & IDevID identity](/docs/device-identity): the full derivation the `/128` above comes from
- [Passwordless device auth](/docs/device-secret-cure): the DANCE handshake these identities are built to run
- [DANE & TLSA](/docs/dane): the `3 1 1` pin every check here rests on, byte for byte
- [Embedded compliance](/docs/embedded-compliance): where these recipes map into CRA, 62443 and EN 303 645 evidence
