# Platform integrations

**Whisper sits beside the OT stack you already run and anchors exactly one thing: a routable, verifiable identity at the asset/IP boundary. That's the layer where a reachable socket on a flat network looks identical to the real controller.**

Everything below is a **proposed** integration pattern: our design, not a vendor endorsement. Every one is written the same way: *Whisper complements the mechanism you have; it never replaces it.* We do not touch Modbus, DNP3 or PROFINET on the wire, we do not sit in a PLC's command path, and we never join your OPC UA `TrustList` or terminate a `SecureChannel`. Those live on the fieldbus and inside the session, and they are already someone's job. Whisper's job is the network identity of whatever speaks across a *conduit*: a PLC, an RTU, an IED, an HMI, an engineering workstation, a historian, or a vendor's remote-access endpoint. It's expressed as an IPv6 `/128` that DNSSEC, DANE and RDAP can prove and one call can revoke. No operator is named here as a breach victim; every pattern is described at the class level.

> **One spine under all six.** Each integration below reuses the same shipped primitive: a deterministic `/128` derived from a device's *public* key plus its native identifier (an OPC UA `ApplicationUri`, an 802.1AR IDevID serial, or a bare nameplate serial), passed as `device_id` ([Asset & PLC identity](/docs/industries/ot/asset-identity)), published with a DANE pin and an RDAP record, verifiable by anyone with no account. The integrations differ only in *where in your stack* that identity is bound and checked.

## Anchor the boundary, not the bus

An OT environment carries several trust mechanisms, each correct for its own layer. The mistake is to imagine a new identity system has to displace one of them. It doesn't. Whisper is deliberately confined to the one place none of them cover: the moment a network endpoint claims to be a particular asset (a controller, a workstation, a sanctioned data consumer) and the receiver has only a reachable IP, a VLAN membership, or a private-CA certificate to go on.

| Layer | Owner / mechanism | Whisper's role |
|---|---|---|
| In-plant fieldbus (Modbus / DNP3 / PROFINET) | link trust; DNP3-SA pre-shared keys where present | **none**: never on the wire or in the command path |
| OPC UA session security | app-instance cert + local TrustList (GDS / CertificateManager) | **complements**: publishes the `ApplicationUri`↔cert binding; TrustList stays |
| Device onboarding | 802.1AR IDevID + BRSKI voucher | **complements**: derives the `/128` from the IDevID key; private half sealed |
| Asset ↔ network (IP) | VLAN + firewall: trust = IP + topology | **anchors here**: a publicly verifiable `/128` the asset *is*, provable without your CA |

*Diagram: the four OT trust layers stacked by owner. The fieldbus (Modbus / DNP3 / PROFINET) trusts the link and Whisper touches none of it; OPC UA session security uses an app-instance cert + local TrustList and Whisper only publishes the `ApplicationUri`↔cert binding; onboarding uses an 802.1AR IDevID + BRSKI voucher and Whisper derives the `/128` from the IDevID key; and the asset↔network IP boundary trusts an IP + a VLAN today, the one layer Whisper anchors, with a publicly verifiable `/128` provable without your CA, and never the fieldbus, the `SecureChannel`, or the PLC's command path.*

Read the last row as the whole thesis: at the IP boundary, identity today is a *claim*, a source address on a segment. Whisper turns it into a *network fact*: the address itself, forward-confirmed in DNS, pinned in DANE, registered in RDAP, and revocable in one call.

## OPC UA: publish the `ApplicationUri`↔certificate binding

OPC UA already does key-derived naming. Every application instance carries a globally-unique `ApplicationUri`, and OPC UA Part 6 §6.2.2 requires the application-instance certificate SAN to hold *exactly one* `uniformResourceIdentifier` equal to it. The stack enforces that binding at `OpenSecureChannel` / `CreateSession`: present a certificate whose URI ≠ the `ApplicationUri` and the session fails with `BadCertificateUriInvalid`. So the identity↔key binding is already cryptographic; it is just trapped in a *local*, per-site `TrustList`. OPC UA Part 2 §8.1.2 explicitly discourages public CAs so that no external party decides which applications are trusted, which is precisely why a peer operator, an integrator, or a regulator cannot verify your asset without being enrolled in your `TrustList`; revocation is a local CRL edit no one outside can see.

The integration is to publish that same binding under the *public* DNSSEC chain, adding nothing to OPC UA: pin the app-instance certificate's leaf key as a DANE-EE `TLSA 3 1 1` under a DNSSEC-signed name for the asset. Now the `ApplicationUri`↔certificate binding OPC UA enforces privately becomes **publicly, cross-org verifiable**: no commercial CA, no change to your endpoints. The GDS / CertificateManager keeps doing onboarding, renewal, TrustList and CRL exactly as today.

```bash
# Pin the OPC UA endpoint's leaf key in DNSSEC-signed DNS. The binary port is 4840.
# No shared CA: a peer verifies the exact leaf, not a bundle you both have to trust.
dig +dnssec TLSA _4840._tcp.opcua-plc7.asset.<tenant>.agents.whisper.online
;; flags: qr rd ra ad          ← ad = DNSSEC-authenticated
;; _4840._tcp....agents.whisper.online. 300 IN TLSA 3 1 1 b653a4ef…fcb82d1d

# …and the whole binding (DNSSEC + DANE + RDAP) checked with no account, no TrustList.
whisper verify --trustless opcua-plc7.asset.<tenant>.agents.whisper.online
✓ DANE-EE (TLSA 3 1 1) leaf matches the asset's OPC UA application-instance cert key
✓ DNSSEC chain valid to the IANA root · RDAP: AS219419 · 2a04:2a01::/32
```

> **Complements the GDS / local TrustList, does not replace it.** Whisper never joins your TrustList, never terminates a `SecureChannel`, and issues no application-instance certificate. It publishes the `ApplicationUri`↔key binding you *already* enforce, so anyone can confirm it from the DNSSEC root. See [DANE & TLSA](/docs/dane) for the byte-for-byte record.

## MUD (RFC 8520): the device's declaration, made enforceable

This is the sharpest OT fit. Under [**MUD (RFC 8520)**](https://www.rfc-editor.org/rfc/rfc8520) a device already *declares* what it should talk to: a manufacturer-signed manifest of `from-device-policy` and `to-device-policy` ACLs, emitted over DHCP (options `161`/`112`), an LLDP vendor TLV (type `127`), or the `id-pe-mud-url` X.509 extension co-located with an 802.1AR IDevID. MUD's structural weakness: the manifest is only a *hint*, fetched from the manufacturer's domain and enforced by a *local* MUD manager at the nearest switch. A compromised device still reaches anything the local admin didn't block, and the binding is a spoofable URL, not a verifiable identity.

The integration binds that declaration to the asset's verifiable `/128` and *enforces* it as **default-deny egress governance** where traffic actually leaves: cross-org, and checkable by anyone. Egress is source-bound to the asset's `/128`, so the manufacturer's declaration of intent becomes a cryptographic, enforced default rather than a suggestion at the nearest hop.

```bash
# The device declares its egress in MUD; Whisper enforces it as default-deny at the /128.
# This PLC may reach ONLY its historian, its controller, and the vendor OTA endpoint.
whisper policy set --agent 2a04:2a01:7a2::4840 \
  --default deny --allow historian.example-plant.example:4840,controller.line2.local,ota.vendor.example

# Per-asset firewall + a traffic budget + kill-switch: keyed to a verifiable identity, not a VLAN tag.
CALL whisper.agents({op:'firewall', args:{agent:'2a04:2a01:7a2::4840', deny:['0.0.0.0/0','::/0'], allow:['historian.example-plant.example:4840']}})
CALL whisper.agents({op:'budget',   args:{agent:'2a04:2a01:7a2::4840', max_mb_per_day:50}})
```

**The honest edge.** Egress governance changes *who may reach and speak to* an asset, and constrains what a governed asset may reach. It contains C2, exfil and lateral movement across the convergence bridge and the remote-access surface. It does *not* add authentication to Modbus, DNP3 or PROFINET on the wire, and it does not stop a purely-internal write by an attacker who already holds an OT-segment foothold: closing that last inch needs identity enforced in the command path, at the PLC, a protocol-aware broker-gateway, or the EWS. We say so before your assessor does.

> **Complements MUD, does not replace it.** The manufacturer keeps authoring the manifest; Whisper pins it to a globally-verifiable identity and enforces it where traffic egresses. The full policy surface is on [Egress governance](/docs/egress-governance); a runnable enforce-a-manifest recipe is on [MUD egress · verify · attribute](/docs/industries/ot/ot-recipes).

## IEC 62443 zones & conduits: a conduit at the asset, not the VLAN

IEC 62443-3-3 makes segmentation mandatory (SR 5.1) and organises the plant into *zones* joined by *conduits* (the cross-trust channels between them, exactly where a portable, verifiable identity matters); 62443-4-2 CR 1.2 mandates unique device identification & authentication for every component. A per-`/128` egress allow-list *is* a conduit at asset granularity (a micro-segment at the asset, not the VLAN), keyed to a cryptographically verifiable identity rather than an IP or a VLAN tag. Compromise one asset and the blast radius is that asset, not the flat segment.

*Diagram: the same six assets (PLC, RTU, IED, HMI, EWS, gateway), two segmentation models. On the left, a flat VLAN zone meshes them into one trust blast radius, so a single foothold that owns an IP inherits the whole segment. Applying `op:firewall` + `op:policy` produces the right side: each asset is wrapped in its own `/128` conduit that reaches only its own allow-list, a micro-segment per asset, keyed to a verifiable identity, additive to your 62443 zones.*

```
# A conduit at the asset: this IED reaches ONLY its historian (OPC UA :4840) and controller (DNP3 :20000).
CALL whisper.agents({op:'firewall', args:{agent:'2a04:2a01:7a2::4840',
  deny:['0.0.0.0/0','::/0'],
  allow:['historian.example-plant.example:4840','controller.line2.local:20000']}})
```

And nothing is issued in the dark: every mint and every revoke lands in a public, append-only [Merkle transparency log](/docs/transparency) (Ed25519-signed and anchored to Bitcoin via OpenTimestamps) that you and your assessor can replay as a non-repudiable issuance/revocation trail. (Honest status: tamper-evident and Bitcoin-anchored today; independent third-party witnessing is the next step, and the log already speaks the witness protocol.)

> **Complements your 62443 zones & conduits, does not replace them.** Whisper supplies the unique network identifier (the CR 1.2 identity half) and the identity-keyed conduit (SR 5.1), additive to your SL program. The asset still does its own mutual-auth handshake; we make identity globally verifiable and revocable, we do not claim end-to-end CR 1.2 authentication. The full mapping is on [IEC 62443 · EU CRA · TSA](/docs/industries/ot/ot-compliance).

## IEEE 802.1AR IDevID & BRSKI: the birth certificate, onto the network

This is the shipped spine the other five build on. An [802.1AR IDevID](https://www.rfc-editor.org/rfc/rfc8995) (or a TPM or in-vehicle-grade secure element) is a device's hardware birth certificate: a key generated on the silicon whose private half never leaves it. **BRSKI (RFC 8995)** turns that IDevID into an operator-issued LDevID through a `pledge → registrar → MASA voucher` ([RFC 8366](https://www.rfc-editor.org/rfc/rfc8366)) exchange. Whisper derives the asset's `/128` from that device's *public* `SubjectPublicKeyInfo` plus its serial (or `ApplicationUri`), bridging the identity plane onto the network plane at the same onboarding moment. The private key stays sealed; only the public SPKI is ever an input.

The derivation is deterministic and tenant-bound: the same key and identifier always yield the same `/128`, the mapping is unlinkable to outsiders so a fleet can't be enumerated from its addresses, and the result is DNSSEC-anchored, DANE-EE `3 1 1` pinned, and RDAP-registered the instant it's minted.

```
# Provision the /128 for an asset from the key it already holds at onboarding + its native id.
# identity_public_key is the device's PUBLIC SPKI (base64): the IDevID / TPM key.
CALL whisper.agents({op:'connect', args:{
  tier:'wireguard',
  identity_public_key:'<base64 SPKI of the IDevID / TPM key>',
  device_id:'urn:example-plant:line2:PLC7:server'   // ApplicationUri, IDevID serial or nameplate serial
}}) YIELD op, ok, status, result, error
RETURN op, ok, status, result, error
```

Send it with stock tools. The control plane is one HTTP endpoint, and the heredoc keeps the single-quoted Cypher literals intact so it runs as-is:

```bash
curl -s https://graph.whisper.security/api/query \
  -H "X-API-Key: whisper_live_xxx" \
  -H 'content-type: application/json' \
  --data @- <<'JSON'
{"query":"CALL whisper.agents({op:'connect', args:{tier:'wireguard', identity_public_key:'<base64 SPKI>', device_id:'urn:example-plant:line2:PLC7:server'}}) YIELD op, ok, status, result, error RETURN op, ok, status, result, error"}
JSON
{ "ok": true, "status": "created",
  "result": { "address": "2a04:2a01:7a2::4840",
    "fqdn": "opcua-plc7.asset.<tenant>.agents.whisper.online",
    "wireguard": { /* peer, keys, allowed-ips */ } } }
```

The behaviour is honest and predictable, which is the point:

| Re-provision with… | Result |
|---|---|
| the *same* key + `device_id` | `200`: the **same** `/128` returns (idempotent) |
| a *different* `device_id` on the same tenant's key | `409`: the identity is already bound; no silent rebind |
| a non-string `device_id` | `400`: a clear input error, never an opaque 500 |

Tear it down worldwide at DNS-TTL speed with one call. The `/128`, its PTR, and its DANE pin all go together:

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

> **Complements the IDevID / BRSKI, does not replace it.** The birth certificate stays exactly where it was born: the private key never leaves the secure element, because Whisper only ever consumes the *public* SPKI. The full derivation, tenant-unlinkability property and DANE mechanics are on [Asset & PLC identity](/docs/industries/ot/asset-identity).

## OT visibility platforms: consume the inventory, feed the SOC

The OT-visibility platform class (passive discovery, deep OT-protocol inventory, and process-aware anomaly detection) is excellent at exactly what Whisper deliberately does *not* do. These platforms are allies, not rivals. There are two clean handoffs, and neither asks you to move a single sensor.

- **Consume their asset inventory as the `device_id` input.** Take an asset a visibility platform has already discovered (its `ApplicationUri`, IDevID serial, or nameplate serial) and provision its `/128`. An inferred, appliance-scoped inventory entry becomes a publicly verifiable, DNSSEC/DANE-anchored, RDAP-registered identity anyone outside your SOC can check.
- **Feed attribution back to the SOC.** Behavioral OT monitoring is superb inside the plant, but it stops at the Purdue boundary and the firewall. The attribution graph names the *operator* behind a rotating remote-access or cloud egress. It's a reproducible, replayable evidence chain your SOC, your PSIRT, and a regulator can replay.

And `op:lookups` is a reconnaissance tripwire the platforms can't give you: because every asset's name resolves through Whisper's own authoritative DNS and RDAP, you can ask *who* resolved or RDAP-queried an asset's identity (someone enumerating your plant) before the write lands, not in the post-mortem afterward.

```bash
# Consume an asset from your visibility platform's inventory → give it a verifiable /128.
# device_id = whatever native id the inventory already carries (ApplicationUri / IDevID serial / nameplate).

# Feed attribution back to the SOC: name the operator behind a rotating remote-access egress.
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(\"45.83.x.x\")"}' | jq .
# → operator clustered across clouds by shared ASN / hosting / cert lineage; remote-access pool collapsed by JA4

# Who has been enumerating this asset's identity: resolving its PTR/AAAA/TLSA or hitting its RDAP object?
CALL whisper.agents({op:'lookups', args:{agent:'2a04:2a01:7a2::4840', window:'24h'}})
```

> **Complements OT-visibility platforms, does not replace them.** Whisper does no OT-protocol visibility, no asset discovery, and no anomaly detection. That ground stays theirs. We consume their inventory as an identity input and feed cross-org attribution back to their SOC. More on the attribution verbs is in [Graph & cognition](/docs/graph-api).

## The SIEM feed: Splunk, Sentinel &amp; OpenCTI today; STIX/TAXII next

Identity, egress and attribution evidence should land where your OT SOC already looks. The **Splunk** connector is **shipped & live**: a signed, replayable JSON evidence line mapped to CEF / ECS fields, so an asset's own outbound activity (`op:logs`) and who queried its identity (`op:lookups`) both stream into your existing dashboards and correlation searches.

The **Microsoft Sentinel** connector and **OpenCTI** integration ship today. **STIX 2.1 over TAXII** is **on the roadmap**, together with sector-ISAC (E-ISAC / WaterISAC) machine-readable export. We label these honestly so you can plan against them, not around them.

```
# Shipped & live: the Splunk connector maps a signed, replayable evidence line → CEF / ECS.
# The evidence is the same whether it feeds a dashboard, a correlation search, or an audit.
{ "identity": "2a04:2a01:7a2::4840", "event": "egress.deny",
  "dst": "scan.example:502", "policy": "default-deny", "sig": "ed25519:…" }
# → cef: CEF:0|Whisper|OT|1|egress.deny|Egress denied|5|src=2a04:2a01:7a2::4840 dst=scan.example dpt=502
```

## Shipped today vs proposed

Honesty about what runs matters more here than anywhere. The *identity primitive and its control plane* under every integration are live and provable right now; the *integration guides* themselves are proposed patterns for wiring that primitive into each platform.

| Building block | Status |
|---|---|
| Deterministic `/128` from a device's public key + `device_id` (`ApplicationUri` / IDevID serial / nameplate) | **shipped, live** |
| Provision via the control plane (`op:'connect'`, WireGuard tier) | **shipped, live** |
| Keyless verify: `whisper verify --trustless`, `/verify-identity`, `dig -x`, RDAP | **shipped, live** |
| MUD-style egress governance: `op:policy` / `op:firewall` / `op:budget` | **shipped, live** |
| Revoke: `/128` + PTR + DANE torn down at DNS-TTL speed | **shipped, live** |
| `op:lookups` (who queried this identity) + the Merkle transparency log | **shipped**: log is tamper-evident + Bitcoin-anchored, *not yet independently witnessed* |
| Attribution graph over the public API (`CALL whisper.identify(…)`) | **shipped, live** |
| The **Splunk** connector (signed JSON → CEF / ECS) | **shipped, live** |
| A first-class typed `--application-uri` / `--mud` CLI flag | **proposed**: provision via the control-plane call today |
| STIX 2.1 / TAXII · sector-ISAC export | **proposed / roadmap** |
| The six integration *guides* on this page (OPC UA, MUD, 62443, 802.1AR/BRSKI, OT-visibility, SIEM) | **proposed**: our design, not a vendor endorsement |

> The `--application-uri` / `--mud` CLI flags are not shipped yet. Asset provisioning today goes through the control-plane call in the 802.1AR section above, which *is* live. The shipped CLI verbs are `whisper verify --trustless`, `whisper create --register`, `whisper kill --revoke`, `whisper policy`, and `whisper logs`. See [CLI & one-command](/docs/cli).

## Next

- [Asset & PLC identity](/docs/industries/ot/asset-identity): the shipped derivation these integrations all build on
- [OT-exposure cure](/docs/industries/ot/ot-exposure-cure): why a reachable socket on a flat network is the whole exploit
- [IEC 62443 · EU CRA · TSA](/docs/industries/ot/ot-compliance): where the identity, egress and attribution evidence lands
- [MUD egress · verify · attribute](/docs/industries/ot/ot-recipes): runnable recipes end to end
