# Asset & PLC identity

**Bind the OPC UA `ApplicationUri` an asset already carries to a routable IPv6 `/128` derived from the device's own public key. The address _is_ the asset: forge-proof, tenant-private, DNSSEC-anchored, DANE-pinned, and revocable worldwide in one call. That's the cross-org off-switch its local TrustList never had. And it gives the identity-less legacy floor (a bare Modbus PLC behind a gateway) a verifiable network identity for the first time.**

This is the spine of the OT vertical, and it is **shipped and live**. Everything else (the OT-exposure cure, the platform integrations, the IEC 62443 and EU CRA evidence) builds on the one idea below: an asset's network address stops being a label a foothold inherits by owning an IP, and becomes a cryptographic fact only that device's silicon can stand behind. The striking part: OPC UA _already_ computes a genuinely good key-bound name for every application instance, the `ApplicationUri`, cryptographically welded into the certificate. It just traps that name in a local, per-site trust store that no one outside the plant can validate and no one can revoke across an org boundary. Whisper keeps that property and gives it the two things it lacks.

> **Two tiers, per Postel's Law.** With **no API key**, anyone (an asset owner, an integrator, an OEM certifier, an auditor) can _verify_ an asset's identity from stock tools (`dig`, `curl`, RDAP), because the identity is public by design. With **your key** you _provision_ and _govern_: mint the `/128`, source-bind and constrain its egress to its MUD-declared peers, pull its logs, watch who's enumerating it, and revoke it. Verification never needs an account; the control plane does.

## The `ApplicationUri`: a key-bound name, in a local cage

Start with what OPC UA already has, because it is better than most people realize. Every application instance is named by its **`ApplicationUri`**, an `ApplicationDescription` member that OPC UA Part 4 defines as the _"globally unique identifier for the application instance."_ It is an identity string, not a locator (the `EndpointUrl`, `opc.tcp://host:4840`, is the locator, discovered separately via `GetEndpoints` / `FindServers`). And it is not a value the operator is free to spoof at will: Part 6 welds it into the certificate. The **ApplicationInstanceCertificate** SAN _"shall include a uniformResourceIdentifier equal to the applicationUri"_, exactly one URI. That binding is enforced on the wire: at `OpenSecureChannel` / `CreateSession`, a certificate whose SAN URI does not match the presented `ApplicationUri` is rejected with **`BadCertificateUriInvalid`**, and the session fails. In other words, the standard already binds a globally-unique name into the asset's key material, the same instinct Whisper is built on. That is a real head start.

The trouble is not the name. The trouble is the walls around it. OPC UA's trust model (Part 2) is deliberately **local**: trust is a per-site **TrustList** / private, operator-run CA, and the spec _explicitly discourages_ commercial or public CAs. The reasoning: a public CA would let the CA, not the operator, decide which applications are trusted. Self-signed deployments have no CA and no CRL at all. Revocation is a local CRL or TrustList edit; it is invisible one hop outside the site. The Part 12 GDS / `CertificateManager` can push onboarding, renewal, TrustList updates and revocation, but every one of those actions is scoped to the local trust domain.

> Per the OPC UA security model: trust is anchored in a _local TrustList / private per-site CA_; using a _commercial or public CA is explicitly discouraged_, and a self-signed deployment has _"no CA or CRL."_ Revocation is a local edit, _invisible across an organizational boundary._

So the `ApplicationUri` is a good key-bound name that is nonetheless boxed in on four sides at once:

| The `ApplicationUri` already has | The `ApplicationUri` still lacks |
|---|---|
| **Key-bound identity**: welded into the certificate SAN; a mismatch fails the session (`BadCertificateUriInvalid`) | **Not routable**: an application-layer URN, never a network endpoint you can reach or authorize against on the IP fabric |
| **Globally unique**: a canonical identifier that flows through every OPC UA discovery exchange | **Not publicly verifiable**: trust lives in a private, per-site TrustList; commercial CAs are discouraged; useless one org-hop away |
| **Standards-mandated**: the default identity of every OPC UA application instance | **Allow-listed by hand**: the peer certificate is trusted by a local TrustList edit, per site, per integrator |
| **Bound to a key only the device holds** | **Non-revocable cross-org**: revocation is a local CRL/TrustList change no one outside the site can see |

Read the right-hand column as a to-do list. Whisper does exactly those four things, without touching the left-hand column and without re-flashing the asset.

## The cure: a routable, revocable `/128`

Whisper keeps the `ApplicationUri`'s one good property (_identity bound to the device's own key_) and adds the two the local TrustList withholds: **public, cross-org verifiability** and **DNS-TTL, cross-org revocation**. It does this by giving the asset a real, routable IPv6 `/128` out of `2a04:2a01::/32` (announced by **AS219419**) that is a deterministic function of the device's _public_ key with its `ApplicationUri` (or a bare serial, or an IEEE [802.1AR IDevID](https://1.ieee802.org/security/802-1ar/)) as the domain separator, then anchoring that address into DNS so anyone can prove it and one call can retire it.

```
        already in the asset                    public · cross-org verifiable & revocable: what Whisper adds

  Asset key                 ─public key + URI─▶  /128                ─DNSSEC + DANE-EE 3 1 1─▶  A name anyone can verify
  ApplicationUri welded in cert SAN             2a04:2a01:7a3::c1a                             whisper verify --trustless
  OPC UA cert · 802.1AR IDevID · TPM/SE         routable · tenant-bound                        RDAP-registered · DNSSEC to the IANA root
  private key sealed on-device                                                                 (our own API is not in the trust path)
                                                                                               └─▶ op:'revoke' → gone worldwide at DNS-TTL
```

OPC UA already welds a globally-unique `ApplicationUri` into the asset's certificate: a good key-bound name, trapped in a local, non-revocable-cross-org TrustList. Whisper binds it to a routable, publicly verifiable `/128` and gives it the cross-org off-switch the local trust store never had.

Because the address is _derived from_ a key only that device holds, you cannot present an asset identity whose key you don't have. A foothold that owns an IP no longer inherits the asset's identity. A spoofed session off the wrong address is inert. Every forgery is a DNSSEC/DANE inconsistency any verifier catches for free. That is the whole point of moving trust from _reachability on a flat segment_ to the _machine_ on the other end.

## How the derivation works

The `/128` is not drawn from a pool and written into a database. It is _computed_, the same way on every node, from inputs the asset already has. Three things go in:

| Input | What it is | Where it lives |
|---|---|---|
| **Device public key** | the `SubjectPublicKeyInfo` (SPKI) of the asset's key: the public half of its OPC UA ApplicationInstanceCertificate, an 802.1AR IDevID, a TPM, or a secure element | the **public** half is submitted; the private key **never leaves the device** |
| **Device id** | the native identifier the asset already carries, used here as the domain separator: the OPC UA `ApplicationUri`, an IDevID serial, or a bare asset/PLC serial | submitted with the request; the public index |
| **Component serial** _(optional)_ | a per-component domain separator, so one asset can hold many addressable identities (the PLC CPU, a comms module, a specific OPC UA server instance) | optional; omit it for a single per-asset address |

Those inputs are combined by a one-way derivation, with a Whisper-held secret mixed in, into a stable, unguessable interface identifier scoped to your tenant:

```
# inputs -> a stable, forge-proof interface identifier
derive( device public key,  ApplicationUri [, component serial],  your tenant )  -->  64 uniform bits

# the /64 prefix is your tenant block; the low 64 bits are the derived id
/128 = < your tenant /64 prefix > : < derived interface id >
```

Four properties fall straight out of that derivation, and each one is load-bearing:

- **Deterministic.** The same `(key, ApplicationUri[, serial])` yields a byte-identical `/128` every time, on every server: exactly one candidate, never a random retry. A reconnecting asset re-derives its own address; both of our authoritative nodes mint the identical identity with zero replication between them.
- **Forge-proof.** The address is a function of a key only the device holds. An attacker with the `ApplicationUri` and even the device's _public_ key still cannot become that asset: the server-side secret and the DANE pin (below) are the parts they can never produce.
- **Tenant-bound & unlinkable across sites.** Your tenant's own `/64` is folded into the derivation. The same key + `ApplicationUri` under a _different_ tenant produces a _different_ address, so an outsider cannot derive or enumerate an asset's address in a fleet they don't control, and a unit is not linkable across sites by address suffix. The `ApplicationUri` flows through every discovery exchange and is _not_ a secret, yet **the id alone yields nothing**: you cannot go from `ApplicationUri` to `/128` without the key, there is no enumerable directory, and RDAP / reverse-DNS return the registry object, never the asset's whereabouts.
- **Liberal in, strict out.** The identifier is accepted generously (whitespace stripped, canonicalized) then validated; a bare serial is accepted where an asset has no `ApplicationUri`. A malformed identifier fails closed with a clear message, never a silent wrong address.

The moment the address is derived it is published as a full identity, atomically: an `AAAA`, a forward-confirmed `PTR`, and a **DANE-EE `TLSA 3 1 1`** record that pins the device's leaf key directly, all DNSSEC-signed to the IANA root and registered in [RDAP](/docs/rdap). That TLSA pin is what turns "the address is derived from a key" into "the address is _provable_ against that key by anyone, in any org." See [DANE & TLSA](/docs/dane) for the byte-for-byte record and [DNSSEC](/docs/dnssec) for the chain it hangs from.

> **The private key never moves.** The device submits only its public SPKI, the same public half of the OPC UA / 802.1AR / TPM / secure-element key it already holds. The server derives a public _address_ from public inputs plus a server-side secret; it never sees, holds, or derives the asset's private key. The asset proves ownership later by presenting its own key against the DANE pin.

**You can pin the certificate you already speak.** If your OPC UA server already terminates its ApplicationInstanceCertificate, you can publish a [DANE](/docs/dane) `TLSA` record over DNSSEC that pins _that_ endpoint certificate, turning the private, single-TrustList trust into something any relying party can validate against the IANA root, cross-org, with _no commercial CA_ and no change to the device. This is exactly the gap OPC UA's own model leaves open: it discourages public CAs, so nothing off-site can validate the cert. A DANE pin closes it without contradicting the standard. Whisper's derived `/128` and a DANE-pin of your existing OPC UA cert are complementary: one gives the asset a publicly verifiable _address_, the other makes its existing _certificate_ publicly verifiable.

## Even a bare PLC gets a verifiable identity

Not every asset is an OPC UA server. A large part of any plant is the **identity-less legacy floor** (Modbus/TCP, EtherNet/IP CIP, PROFINET, base DNP3), protocols insecure by design: no TLS, no authentication, plaintext and spoofable, obeying any station that can reach them on a flat segment. You cannot re-flash a controller into having a key it was never built with. This is precisely the case NIST **SP 800-82r3** anticipates: where a device cannot do per-device identity itself, apply _compensating controls_ (segmentation, protocol-aware firewalls, session logging). An overlay identity layer is the sanctioned pattern, not a workaround.

So put the identity at the edge. A protocol-aware gateway in front of the legacy asset holds the derived `/128` and source-binds the asset's northbound traffic to it. For the first time, a dumb Modbus PLC has a verifiable network identity: an `AAAA`, a `PTR`, a DANE pin, an RDAP object, that a remote integrator or the plant's own SOC can resolve, verify cross-org, attribute, and revoke. What it does _not_ do is add authentication to the Modbus wire; Whisper anchors strictly at the IP/transport boundary.

```
             the identity-less legacy floor gets a first verifiable identity

  ┌ flat OT segment · IP-based trust ┐
  │  Legacy PLC / RTU                │  ─local link─▶  Whisper edge gateway  ─DNSSEC+DANE─▶  Verifiable identity
  │  Modbus/TCP:502 · DNP3 · PROFINET│    unchanged    holds the asset's /128                AAAA · PTR · DANE-EE 3 1 1
  │  no auth · plaintext             │                 source-binds egress · default-deny    RDAP-registered
  │  obeys any reachable IP          │                 NIST 800-82r3 compensating control     attributable · cross-org-revocable
  └──────────────────────────────────┘

  Whisper anchors at the IP / transport boundary. It adds no authentication to the Modbus wire itself.
  The last-inch protocol write must still be enforced in-path (at the PLC, a broker-gateway, or the EWS).
```

A protocol-aware gateway carries the derived `/128` for an asset that could never hold a key of its own, turning a bare Modbus/DNP3 controller into something remotely verifiable, attributable, and cross-org-revocable. This is the compensating-control overlay NIST SP 800-82r3 prescribes, honest about where it stops: it does not authenticate the field protocol on the wire.

## Provision an asset identity

Provisioning is one control-plane call: `whisper.agents` with `op:'connect'`, `tier:'wireguard'`, the device's public SPKI, and the `ApplicationUri` passed as `device_id`. It returns the deterministic `/128` and a ready WireGuard configuration so the asset's traffic sources _from_ its own identity. The endpoint is `POST https://graph.whisper.security/api/query`, authed with an `X-API-Key` header; no key ever travels in the body.

```
CALL whisper.agents({op:'connect', args:{
  tier:                'wireguard',
  identity_public_key: '<base64 SubjectPublicKeyInfo of the asset key>',
  device_id:           'urn:acme-plant.example:ACME:PLC:S71500:00A1B2C3'   // the OPC UA ApplicationUri
  // ecu_serial: 'CPU-1516-3PN'   // optional: a distinct /128 per component
}}) YIELD op, ok, status, result, error
   RETURN op, ok, status, result, error
```

**With stock tools:** just `curl`, no Whisper software. A quoted heredoc keeps the Cypher single-quotes intact so it pastes and runs as-is:

```sh
curl -s https://graph.whisper.security/api/query \
  -H "X-API-Key: whisper_live_xxx" \
  -H "content-type: application/json" \
  -d @- <<'JSON' | jq .
{"query":"CALL whisper.agents({op:'connect', args:{tier:'wireguard', identity_public_key:'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...<SPKI>...', device_id:'urn:acme-plant.example:ACME:PLC:S71500:00A1B2C3'}}) YIELD op, ok, status, result, error RETURN op, ok, status, result, error"}
JSON
```

The response is the standard envelope; `result` carries the derived address and the transport. Because the device holds its own key, no private key is ever returned. Only the public identity and the config that binds egress to it come back:

```json
{
  "op": "connect", "ok": true, "status": 200,
  "result": {
    "tier":               "wireguard",
    "address":            "2a04:2a01:7a3::c1a",                     // the deterministic /128
    "fqdn":               "asset-9f4c2a1b.plc.<tenant>.agents.whisper.online",
    "server_public_key":  "…",
    "endpoint":           "…:51820",
    "dns":                "2a04:2a01:0:53::53",
    "wireguard_config":   "[Interface]\nAddress = 2a04:2a01:7a3::c1a/128\n…"
  },
  "error": null
}
```

Drop the returned `wireguard_config` onto the asset's comms module or its protocol-aware gateway (or feed it to `wireproxy` for a no-root, userspace tunnel) and every packet it sends now sources from its own `/128`. The backend (a SCADA head-end, an MES, a cloud historian) authorizes on that address, a forge-proof, attributable, revocable network identity, instead of a shared credential or bare reachability. For the full transport mechanics and the SOCKS5 / AnyIP alternatives, see [Connect & egress](/docs/connect); for every other op on this same endpoint, the [Control plane](/docs/control-plane) reference.

> The `whisper` CLI ships `create --register`, `verify --trustless`, `policy`, `logs`, and `kill --revoke`. A dedicated `--application-uri` (or `--mud`) flag is **on the roadmap, not shipped**. Provision assets today via the control-plane call above, which is live. When the flag lands it will be a thin wrapper over exactly this call. The `device_id` argument is generic on purpose: pass an `ApplicationUri`, an 802.1AR IDevID serial, or a bare PLC serial, whatever native identifier the asset carries.

## Idempotent, with honest errors

Because the address is _derived_, provisioning is naturally idempotent, and the failure modes are clear rather than surprising: Postel all the way down.

| You send | You get |
|---|---|
| the **same** key + `ApplicationUri` again (same tenant) | the **same** `/128`: a re-derivation, not a new allocation |
| the same key with a **different** `device_id` (same tenant) | `409`: the reused identity is never silently re-pinned to a mismatched address |
| a **non-string** `device_id` (or `ecu_serial`) | `400` with a helpful `detail`, never an opaque `500` |
| `device_id` without `identity_public_key` | `400`: an asset derives its address from its _own_ key |

## Verify: keyless, no account

The identity half is public on purpose: anyone (an asset owner allow-listing a sanctioned integrator, an OEM certifier gathering CRA evidence, an MSSP reconciling a remote-maintenance session, a suspicious peer across a 62443 conduit) can prove an asset's `/128` without a Whisper account and without trusting Whisper's word. Four independent checks, all from tools already on the machine:

```sh
# 1. Forward-confirmed reverse DNS: the address names the asset, the name resolves back
dig -x 2a04:2a01:7a3::c1a +short
asset-9f4c2a1b.plc.<tenant>.agents.whisper.online.

# 2. The keyless verdict endpoint (takes an address or an FQDN; ?ip=<target> also accepted)
curl -s https://whisper.online/verify-identity/2a04:2a01:7a3::c1a | jq .
{
  "is_whisper_agent": true,
  "dane_ok": true,
  "jws_ok": true,
  "evidence": { "aaaa": "...", "ptr": "...", "tlsa": "3 1 1 b653a4ef…fcb82d1d" }
}

# 3. The registry record: RDAP, IP-anchored to the /128
curl -s https://whisper.online/ip/2a04:2a01:7a3::c1a | jq '.handle, .parentHandle'

# 4. The full chain re-derived on YOUR machine, against the IANA root: Whisper NOT in the trust path
whisper verify --trustless asset-9f4c2a1b.plc.<tenant>.agents.whisper.online
```

A target that isn't a Whisper identity gets a clean `200 {"is_whisper_agent": false}`. A negative verdict is a successful answer, not an error; only genuinely malformed input draws a `400`, never a `500`. `--trustless` is the strong form: it validates DNSSEC from the root _in-process_, on your resolver, so the proof holds even for a party across an org boundary that won't take Whisper's word for anything, the exact case OPC UA's local TrustList cannot serve. The full keyless walk lives in [Verify an agent](/docs/verify).

## Revoke: worldwide, in one call

A compromised or decommissioned asset is one `revoke` away from having no network identity anywhere. The call tears down the `/128`, its `PTR`, and its DANE pin across both authoritative servers, and the change propagates at DNS-TTL speed:

```sh
CALL whisper.agents({op:'revoke', args:{agent:'2a04:2a01:7a3::c1a'}})

# prove it: zero Whisper software, the same stock tools that proved it existed:
dig -x 2a04:2a01:7a3::c1a +short                       # -> nothing
curl -s https://whisper.online/verify-identity/2a04:2a01:7a3::c1a
# -> {"is_whisper_agent": false, ...}
```

Revocation isn't a database flag you have to trust; it's provable the same way the identity was. The reverse lookup goes empty and the keyless verdict flips to `false` for everyone, everywhere, at once. This is exactly what a local OPC UA TrustList _cannot_ do: a CRL edit there is invisible one org-hop away, and a shared credential rotation reaches only the callers you can still reach. Compromise one asset and you've compromised _that asset_, not the site, and you can cut it, or a whole compromised model, off across every organization that verifies it, in a single call.

## The lifecycle, end to end

An asset identity is not a one-shot registration; it tracks the device through its whole service life. Because every `/128` is _derived_, each stage is a plain control-plane call, and each mint or revoke is written to a public, tamper-evident log. Nothing is issued in the dark.

```
 Factory IDevID ──▶ Commissioning ──▶ Re-flash / ──▶ Incident revoke ──▶ Decommission /   time ▶
 born on-device     /128 + egress     module swap     cut off worldwide    resale
 ApplicationUri set op:'connect'      re-key → new/128 op:'revoke'          retire → re-register
      │                 │                  │                │                    │
   (no ledger)          └────────┬─────────┴───────┬────────┴─────────┬─────────┘
                                 ▼                  ▼                  ▼
      ┌───────────────────────────────────────────────────────────────────────────────┐
      │  public Merkle transparency log · every mint & revoke                           │
      │  Ed25519-signed · Bitcoin-anchored                                              │
      └───────────────────────────────────────────────────────────────────────────────┘
```

One identity, tracked from the factory IDevID to decommission: each stage a plain `whisper.agents` call. A re-flash or module swap re-keys to a new `/128` and revokes the old; every mint and revoke is written to the public log.

| Stage | What happens | Call |
|---|---|---|
| **Factory IDevID** | The asset is born with its key; its `ApplicationUri` is welded into the resulting certificate. Nothing is minted yet. | _native, no Whisper call_ |
| **Commissioning** | At commissioning, derive the `/128` and source-bind egress; the head-end now authorizes the address, not bare reachability. | `op:'connect'` |
| **Re-flash / module swap** | A firmware re-key or a replaced comms module carries a new key, so it re-keys to a fresh `/128`; the old address is revoked in the same breath. | `op:'connect'` + `op:'revoke'` |
| **Incident revoke** | A compromised unit, or a whole compromised model, is cut off worldwide at DNS-TTL; verify flips to `false` everywhere. | `op:'revoke'` |
| **Decommission / resale** | An asset transfer or an end-of-life retire is one revoke and a re-register to the new owner; `op:'erase'` renders the log leaf's meaning unrecoverable while its proofs stay valid. | `op:'revoke'` (+ `op:'erase'`) |

**Nothing issued in the dark.** Every mint and every revoke along that timeline lands in a public, append-only [Merkle transparency log](/docs/transparency) (RFC 6962 `tlog-tiles` with signed-note checkpoints), Ed25519-signed and anchored to Bitcoin via OpenTimestamps: an auditable, non-repudiable issuance-and-revocation trail your PSIRT and your regulator can replay against an EU CRA reporting obligation or an IEC 62443 audit. _Honest status:_ the log is tamper-evident, signed, and Bitcoin-anchored today, but it is **not yet independently witnessed**. Our two authoritative nodes co-signing is availability, not independence. The log already speaks the C2SP `tlog-witness` protocol, so any external witness can co-sign; that step is on the roadmap, and we label it as such rather than overclaim.

## Govern a live asset: MUD egress, budget, lookups

Revocation is the kill-switch. In between, the same control plane governs exactly what a _live_ asset may reach, caps it, and tells you who is looking at it. This is where OT has a second native socket worth honoring: **MUD** (RFC 8520). A device already _declares_ its intended peers: a `from-device-policy` and `to-device-policy` of ACLs with abstractions like `my-controller`, `manufacturer`, and `local-networks`, emitted via a DHCP option, an LLDP TLV, or an X.509 extension. The weakness of plain MUD is that it is fetched from a manufacturer domain and enforced by a _local_ MUD manager against spoofable name abstractions. Whisper enforces that same declared intent as **default-deny egress keyed to a cryptographically verifiable `/128`**: a genuine strengthening, and an identity-keyed conduit per asset in IEC 62443-3-3 terms.

```sh
# enforce the asset's MUD manifest as default-deny egress, keyed to its verifiable /128
# "I talk only to my controller, my manufacturer's update service, and the local subnet"
whisper policy set --default deny \
  --allow scada.acme-plant.example,updates.acme.example,10.20.0.0/16

# per-asset firewall (allow/deny by host, cidr, or port) + a daily budget with a kill-switch
CALL whisper.agents({op:'firewall', args:{agent:'2a04:2a01:7a3::c1a',
     deny:['0.0.0.0/0'], allow:['scada.acme-plant.example:4840','updates.acme.example:443']}})
CALL whisper.agents({op:'budget',   args:{agent:'2a04:2a01:7a3::c1a', max_mb_per_day:20}})
```

That contains the exact surfaces the 2023-25 OT wave abused: the IT→OT convergence bridge and remote-access sprawl. A governed asset cannot beacon to a C2 host or pivot laterally, because everything but its declared peers is denied by default, at asset granularity, keyed to identity rather than to a VLAN a foothold already sits inside.

**See who's enumerating your plant, before the command lands.** An identity you can prove is also an identity you can _watch_. Because every asset's name resolves through Whisper's own authoritative DNS and RDAP, the owner can ask who looked: a reconnaissance tripwire a local TrustList never gave you. `op:'lookups'` returns who resolved or RDAP-queried an asset's identity, so you catch an attacker _enumerating_ the asset inventory in recon, not in the post-mortem:

```sh
# who has been resolving / RDAP-querying this asset's identity, and when
CALL whisper.agents({op:'lookups', args:{agent:'2a04:2a01:7a3::c1a', window:'24h'}})

# the same reverse-observability view, keyless, per address
curl -s https://whisper.online/ip/2a04:2a01:7a3::c1a/lookups | jq .
# -> who resolved this asset's PTR/AAAA/TLSA and hit its RDAP object
```

Paired with `op:'logs'` (the asset's own outbound activity) you have both halves of the picture: what an asset reaches out to, and who is reaching in to look at it. That's the per-`/128` egress record that maps to CRA Annex I 2(l) and CISA CPG monitoring. And because each OPC UA data change, historian record, or telemetry stream can be [signed under](/docs/sign-outputs) the asset's forge-proof `/128`, the integrator, the regulator, and your own process-data lineage can trust the reading came from the real asset: non-repudiable process data, not a spoofed feed. The full policy surface is in [Egress governance](/docs/egress-governance).

## Attribution: name whoever already scanned you

Identity stops the _next_ forgery. To name whoever _already_ scanned or scraped your plant, across rotating clouds and residential proxies, the same API key opens the read-only attribution graph on the same endpoint. `whisper.identify` takes an address and returns the operator behind it, stitched across the Amazon → Google → Azure hops that a raw last-IP loses:

```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(\"203.0.113.45\")"}' | jq .
# operator fingerprinted across AWS / GCP / Azure; residential swarm collapsed by JA4
```

The read-only Cypher surface (`identify`, `origins`, `walk`, `variants`, `history`) runs over the same `POST https://graph.whisper.security/api/query` with your key, and each verb returns a reproducible, replayable JSON evidence chain your OT SOC, your PSIRT, and a regulator can replay. There is no `whisper identify` CLI subcommand. This is the API call, and it is live. Full verbs and shapes in [Graph & cognition](/docs/graph-api).

## Where this fits, and where it doesn't

Whisper anchors the asset's identity at the **device↔network IP boundary**: the endpoint a SCADA head-end, MES, cloud historian, or remote-maintenance session authorizes over IP. It is additive: it complements the roots of trust you already run and does not try to replace them, and it deliberately stops at the field bus.

- **IEEE 802.1AR IDevID / TPM / secure element (and BRSKI onboarding).** The device's birth key is the _input_ to the derivation. Whisper adds a routable, DNSSEC/DANE-provable name on top of the silicon root of trust, cross-org revocable independent of the manufacturer PKI. It complements that root; it does not replace it.
- **OPC UA GDS / `CertificateManager` (Part 12).** The GDS manages your _local_ TrustList, CRLs, onboarding and renewal beautifully, inside the site. Whisper adds the layer the local model explicitly omits: a publicly verifiable, DANE-anchored identity a party in _another_ organization can validate against the IANA root, and revocation they can see. And you can DANE-pin the same ApplicationInstanceCertificate the GDS issued.
- **MUD / RFC 8520.** The asset declares its intended egress; Whisper enforces that intent against verifiable `/128`s rather than spoofable manufacturer-domain abstractions, and can DANE-pin the MUD URL itself: the identity-keyed enforcement plain MUD lacks. A strengthening, not a replacement.
- **Cloud device registries with a private CA (AWS IoT Core / IoT SiteWise class).** There the cloud is the CA and mutual TLS rides X.509. Whisper adds an _out-of-tenancy_ identity the backend can allow-list and any third party can verify against DANE without cross-signing. It complements the registry CA; it does not replace it.
- **Where Whisper does _not_ go.** Not on the Modbus/DNP3/PROFINET wire (it adds no authentication to the field protocol), not inside the IEC 61850 GOOSE/MMS substation bus, and not the in-plant command path once an attacker already holds a segment foothold. Whisper changes _who_ may reach and speak to the asset over IP, and makes it attributable and cross-org-revocable: the doorway, not the field bus. Candid scope, per NIST SP 800-82r3: the last-inch insecure-protocol write must still be enforced in-path, at the PLC, a protocol-aware broker-gateway, or the EWS.

For mapping these identities to **IEC 62443-4-2 CR 1.2** and 62443-3-3 zones-and-conduits evidence, EU **CRA** Annex I 2(d)/2(j)/2(l), NIST SP 800-82r3, CISA CPG 2.0, and TSA / NIS2 obligations, see [IEC 62443 · EU CRA · TSA](/docs/ot-compliance). The Splunk, Microsoft Sentinel and OpenCTI connectors are shipped; STIX 2.1 over TAXII + ISAC machine-readable export is on the roadmap and labelled as such there. Whisper is additive throughout: it evidences the identity, attack-surface, and logging clauses; the asset still performs its own authentication handshake, and SBOM, host audit, and vulnerability remediation stay with your existing stack. No specific operator or vendor is named, endorsed, or implicated as a breach victim anywhere in these docs; the abuse patterns cited are the public, cross-industry ones.

## Next

- [OT-exposure cure](/docs/ot-exposure-cure): this identity, applied to the exact internet-exposed-ICS / flat-network / no-attribution problem it was built for
- [OPC UA · MUD · 62443](/docs/ot-integrations): dropping the `/128` into OPC UA, 802.1AR/BRSKI, a MUD manifest, and cloud device registries
- [DANE & TLSA](/docs/dane): the byte-for-byte record that makes the address provable against the asset's key

---

← [OT overview](/docs) · [OT-exposure cure →](/docs/ot-exposure-cure)
