# Verify · attribute · govern

**Six copy-paste recipes give every 5G network function an identity it can prove, an operator you can name behind a rotating peer, and an egress you can govern to one call. Each one runs today against the keyless surface anyone can reach.**

They share one primitive: a routable IPv6 `/128` that an NF *derives* from the SBA mTLS key it already holds. That's the same key whose certificate SAN already carries `urn:uuid:<nfInstanceId>` (3GPP TS 33.310 §6.1.3c). It is DNSSEC-signed and [DANE-pinned](/docs/dane) to that same certificate, so any operator, IPX or regulator can verify it and nobody can forge it, without your private CA in the path. Everything below is built from shipped parts: the NF `/128`, keyless [verify](/docs/verify), the attribution graph, egress governance, and one-call [revoke](/docs/control-plane). Where a step is a pattern you assemble rather than a single command, or a connector still on the roadmap, it says so in plain words.

> **What's shipped, stated honestly.** The NF `/128` derivation (public key + `device_id` = the `nfInstanceId`), the keyless [verify](/docs/verify) / RDAP surface, the attribution graph over the public API, egress governance (`policy`/`firewall`/`budget`), `lookups`, and one-call [revoke](/docs/control-plane) are **live**. Two caveats these recipes are written around: there is no first-class `--nf-instance` CLI flag yet, so NF provisioning is shown via the control-plane API (which *is* live); and the STIX · TAXII *export* connector named at the end is roadmap; the **Splunk**, Microsoft Sentinel and OpenCTI connectors ship today. Whisper anchors the **DNS / IP / transport boundary**: it is a *second, independent* DNS-anchored layer beside your mandatory SBI mTLS + OAuth2, never a replacement for them, the operator PKI, the NRF, or the SS7/Diameter signaling plane. Each recipe names exactly where it stops.

## The shared primitive: the address *is* the NF

Every 5G NF (AMF, SMF, UPF, PCF, UDM, AUSF, NSSF, or a SEPP) is an HTTP/2 + JSON microservice on the SBI that registers a **NFProfile** with the **NRF** (TS 29.510). That profile already carries the NF's `nfInstanceId` (an RFC 4122 UUID), its `nfType`, and at least one of `fqdn` / `ipv4Addresses` / `ipv6Addresses`. Mutual TLS is mandatory on the SBI, and the certificate is the identity anchor. But it's signed only by the **operator-private CA**, so no roaming partner, IPX or regulator can independently check it. Whisper takes only the **public** SubjectPublicKeyInfo (SPKI) of that same SBA key and, with the `nfInstanceId` as the domain separator, *deterministically derives* a `/128` under `2a04:2a01::/32` (`AS219419`). The private key never leaves the NF.

The derivation has four properties that make it useful as an identity rather than just an address:

- **Deterministic & idempotent**: the same key + `nfInstanceId` always yields the same `/128`. Re-provisioning a redeployed pod for the same NF instance returns the same address; there is no registry to keep in sync with the NRF.
- **Tenant-bound & fleet-unlinkable**: the derivation folds in your operator tenant, so the same NF instance under two operators yields two *unrelated* `/128`s. No one can link an instance across the PLMN boundary by suffix, and there is no enumeration oracle.
- **Forge-proof**: the `/128` is [DANE-EE `3 1 1`](/docs/dane)-pinned to that NF's own SBA certificate and has DNSSEC-signed reverse DNS. A rogue NF with a different key derives a different address and can't present the pinned key.
- **Revocable across operators**: one control-plane call tears the `/128`, its PTR, and its DANE pin down everywhere at DNS-TTL speed. That beats one CRL per operator that peers may never fetch.

Because the domain separator is the `nfInstanceId`, the **UUID alone yields nothing**: you cannot go `nfInstanceId` → `/128` without the key, there is no enumerable directory, and RDAP and reverse-DNS return the registry object, never the NF's internal whereabouts. The `nfInstanceId` is the public fingerprint that flows through every NRF registration; the `/128` is its cryptographic counterpart. Once an NF holds one, every check in the six recipes below is **keyless**: no account, no API key, just DNS and TLS any counterparty already has.

## Recipe 1 · Bind an NF to the `nfInstanceId` it already carries

> **Shipped & live.** Deriving an NF `/128` from the SBA key it already holds, keyed by its `nfInstanceId`, is in production today. Provision one with the control-plane call below, then drop the address straight into `NFProfile.ipv6Addresses`. No NRF API change, no re-keying, no new CA.

Pass the NF's base64 SPKI as `identity_public_key` and its `nfInstanceId` as `device_id`. That UUID is the exact one already in the cert SAN (`urn:uuid:<nfInstanceId>`, TS 33.310). Re-running with the same key + UUID returns the *same* `/128`:

```bash
# Bind an NF: device_id = its nfInstanceId (the urn:uuid already in its cert SAN).
# Idempotent: same key + nfInstanceId returns the SAME /128, no NRF registry to sync.
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:'"'"'connect'"'"', args:{tier:'"'"'wireguard'"'"', identity_public_key:'"'"'<base64 SPKI of the NF SBA key>'"'"', device_id:'"'"'3f2504e0-4f89-11d3-9a0c-0305e82c3301'"'"'}}) YIELD op, ok, status, result, error RETURN op, ok, status, result, error"}' | jq .
```

```
# response
{ "op": "connect", "ok": true, "status": "created",
  "result": {
    "address": "2a04:2a01:5e0::a3f",
    "fqdn":    "nf-amf-3f2504e0.sbi.example-plmn.whisper.online",
    "wireguard": { /* peer, keys, allowed-ips: source-bind the NF's egress to its /128 */ }
  } }
```

Confirm the address and its forward-confirmed name, then place the `/128` into the NFProfile you PUT to the NRF. It lives in the field that was always there for it:

```
# The address is the NF. Forward-confirmed reverse DNS names it:
dig +short -x 2a04:2a01:5e0::a3f
nf-amf-3f2504e0.sbi.example-plmn.whisper.online.

# Drop it into the NFProfile you register with the NRF (TS 29.510). No API change:
#   "nfInstanceId":  "3f2504e0-4f89-11d3-9a0c-0305e82c3301"
#   "ipv6Addresses": ["2a04:2a01:5e0::a3f"]
```

*Figure: The NF certificate already carries `urn:uuid:<nfInstanceId>` in its SAN (TS 33.310), a good key-bound identity trapped in an operator-private CA. Whisper binds the same UUID to a routable, publicly verifiable `/128` and gives it a cross-operator off-switch. One leaf key per identity; never a shared root.*

> The call is **liberal in what it accepts, strict in what it commits**: the same NF key with a *different* `nfInstanceId` on your tenant is a clear `409`, not a silent overwrite; a non-string `device_id` is a `400` that tells you exactly what was wrong, never an opaque 500. The server only ever derives a public **address**; it never sees or reconstructs the NF's private key. Nothing changes in the NRF, the SBI handshake, or your operator CA. This is a second identity *on top* of the one you already run.

## Recipe 2 · Prove an NF's identity, keyless

Mutual TLS proves the NF at the SBI handshake: *to a counterparty that already trusts your operator CA.* Across the PLMN boundary, through an IPX, or to a regulator, no one can chain your private-CA cert to a public anchor. The `/128` closes that: any party re-derives and checks the identity trustlessly against the IANA DNSSEC root, with no Whisper API and no operator CA trusted as an authority.

> **Boundary.** This **complements** the mandatory SBI mTLS + OAuth2 (TS 33.501 §13); it does **not** replace them and does **not** issue NF certificates. It adds the *publicly*-verifiable check (the one a roaming partner, an IPX hub, or an auditor can run without bilateral cross-certification) at exactly the multi-party boundary the operator-private PKI can't cross.

The CLI walks the full proof chain in one line. It's the same check either side of a peering runs against the other:

```
# Trustless: re-derived against the IANA DNSSEC root; no operator CA trusted as an authority.
whisper verify --trustless nf-amf-3f2504e0.sbi.example-plmn.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 NF's own SBA cert)
ledger   pass   DNSSEC-root   transparency-log entry present, signature verifies
CRYPTOGRAPHICALLY PROVEN, trust anchor: DNSSEC root (IANA) + DANE-EE, operator CA NOT trusted
```

No CLI required. The same fact answers over one keyless HTTPS call, and again with nothing but `dig`, the tool already in every ops toolbox:

```
# The public verify endpoint. Evidence chain in JSON, no account:
curl -s https://whisper.online/verify-identity/2a04:2a01:5e0::a3f
{"is_whisper_agent":true,"dane_ok":true,"jws_ok":true,
 "evidence":{"address":"2a04:2a01:5e0::a3f",
   "ptr":"…","forward_aaaa":"2a04:2a01:5e0::a3f"}}

# Because the reverse is forward-confirmed (PTR ⇄ AAAA), dig alone proves the name:
dig +short -x 2a04:2a01:5e0::a3f
nf-amf-3f2504e0.sbi.example-plmn.whisper.online.
```

That is a mutual, forge-proof peer check at the layer the private PKI leaves open across operators: neither side depends on a token that could be replayed from another host, and neither has to trust a CA the other one chose.

## Recipe 3 · DANE-pin your SBA endpoint to public DNSSEC

This is the least-contestable recipe on the page. 3GPP mandates mTLS and OAuth2, but **not** DNSSEC or DANE on the SBA name layer that NF and NRF discovery ride on. That layer is unsigned. Spoof DNS and you redirect a consumer NF to a **rogue NRF**, or forge the token-issuer URL, and mutual TLS never sees it, because the client was steered to the attacker's name before the handshake began. A DNSSEC-signed zone plus a DANE `TLSA` record pinning the *same* certificate your NRF already presents makes the name → address → expected-cert binding cryptographic and hijack-resistant.

> **Boundary.** DANE pins the cert *out of band*; it does **not** revoke the operator TLS certificate (that stays the operator CA's job) and it does **not** replace mTLS. It is a second, independent DNS-anchored check that closes the DNS-spoofing / rogue-NRF / forged-issuer vector TS 33.501 leaves open, plus a faster kill-switch. It maps to **EU 5G Toolbox TM02** (turn on optional security controls) and hardens the N32-c spoofing vector **GSMA FS.36** names.

Publish the pin: the SHA-256 of the NRF endpoint certificate's SubjectPublicKeyInfo, as a DANE-EE `3 1 1` record in the DNSSEC-signed zone:

```
# The DNSSEC-signed TLSA pin for the NRF's SBI endpoint (selector 1 = SPKI, match 1 = SHA-256):
dig +short TLSA _443._tcp.nrf.sbi.example-plmn.whisper.online
3 1 1 b653a4ef…fcb82d1d
```

Now any consumer NF checks the NRF it was pointed at against that pin *before* it trusts a discovery answer or a token issuer. A rogue NRF answering on the name cannot present the pinned key, so the check fails and the redirect is caught:

```
# On the consumer NF, before trusting the NRF answer: does the live cert match the pin?
openssl s_client -connect nrf.sbi.example-plmn.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; a rogue NRF can't

# Or let the CLI do the whole DNSSEC + DANE walk for the NRF endpoint in one line:
whisper verify --trustless nrf.sbi.example-plmn.whisper.online
dane   pass   DNSSEC-root   served leaf SPKI-SHA256 == TLSA pin
```

Pin the SEPP the same way and the check extends to the N32 border: the home operator verifies a peer SEPP against a public anchor instead of trusting the SEPP's own assertion. It's the modern-stack analogue of the same move.

## Recipe 4 · Back-trace the operator behind a suspicious peer

Identity stops the next impersonation; the graph names the operator behind a host already in your logs. That attribution survives rotation because it fingerprints the operator and the tooling, not the ephemeral egress IP. Rate-limit an IPX-hosted peer and a fresh IP appears; the egress hops across an IPX hub, clouds, and a residential-proxy swarm, so all your NF SOC ever logs is a meaningless *last IP*. The graph collapses it.

> **Boundary, stated plainly.** This is an **additive feed** into the signaling firewalls (SS7 / Diameter / GTP) and the SEPP you already run; Whisper does **not** do signaling inspection or packet-core protection. And it is honest about its reach: attribution names the operator behind a rotating egress; it does **not**, by itself, evict a below-identity-layer implant or stolen management credential. Use it as the who/where accelerator for incident handling, not a claim to have stopped a nation-state-class campaign.

Pass the suspicious address to `whisper.identify` over the public graph API with your key. There is no `whisper identify` CLI verb; the read-only Cypher call below *is* the interface:

```
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(\"185.90.x.x\")"}' | jq .
  operator:  <fingerprinted> · seen across an IPX hub, AWS, GCP and Azure
  residential swarm collapsed by JA4: same tooling, 37 exit IPs → 1 operator
```

*Figure: Attribution survives rotation because it tracks the infrastructure and the tooling, not the ephemeral egress IP. The read-only verbs (`identify`, `origins`, `walk`, `variants`, `history`) each return a reproducible, replayable JSON evidence chain your NF SOC, PSIRT, and a regulator can replay.*

The finding accelerates the who/where forensics a **NIS2 Art.23** report (24h / 72h / 1 month) demands, and it feeds straight into the signaling firewall and SEPP you already operate. It's enrichment, not a new console to babysit.

## Recipe 5 · Govern NF egress: default-deny, allow the peers, cap, kill

An identity you can prove is also one you can *govern*. Because the NF's `/128` resolves through Whisper's own graph-first resolver and its egress is source-bound to that address, you can enforce **default-deny** per NF: allow only the peers it legitimately talks to, cap its traffic, and cut it off in one call. This is anti-lateral-movement micro-segmentation at the network layer, the move **NSA/CISA ESF 5G Cloud** Parts II and III and the **CISA ZTMM** Identity + Visibility pillars ask for.

> **Boundary.** This rides existing DNS / IPv6 and adds **no inline packet chokepoint** in the core. It is not a GTP-U / SCTP firewall (those incumbents keep their ✓). The DANE/verify plane is built to **fail open**: a Whisper outage never bricks an NF; checks degrade to your existing anchors. It complements the 5G-core firewalls; it does not sit in the user-plane path.

```
# Default-deny the NF, then allow only its legitimate SBI peers, by name or subdomain.
whisper policy set --default deny \
  --allow nrf.sbi.example-plmn.whisper.online,smf.sbi.example-plmn.whisper.online,sepp.sbi.example-plmn.whisper.online

# The same as a per-NF firewall over the control plane: allow/deny by host, cidr or port:
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:'"'"'firewall'"'"', args:{agent:'"'"'2a04:2a01:5e0::a3f'"'"', default:'"'"'deny'"'"', allow:['"'"'nrf.sbi.example-plmn.whisper.online'"'"','"'"'smf.sbi.example-plmn.whisper.online'"'"']}})"}'

# Cap the NF's egress and arm the kill-switch:
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:'"'"'budget'"'"', args:{agent:'"'"'2a04:2a01:5e0::a3f'"'"', max_egress_gb:50, on_exceed:'"'"'kill'"'"'}})"}'
```

*Figure: Default-deny per NF, allow only the SBI peers it legitimately reaches, cap it with `op:budget`, and cut it off with `op:revoke`. That's a network-layer least-privilege perimeter around each `/128`, additive and availability-safe.*

When an NF is compromised, or resold, decommissioned, or simply rotated out, one call tears down the `/128`, its PTR, and its DANE pin everywhere at once, bounded only by DNS TTL. No CRL to distribute, no bilateral PKI exchange:

```
# Kill an NF identity across operators. Every keyless check above starts returning
# "not a Whisper identity" within the DNS TTL, no coordination, no distribution list.
whisper kill --revoke 2a04:2a01:5e0::a3f
```

> **Nothing issued in the dark.** Every provision and every revoke lands in a public, append-only [RFC 6962 Merkle transparency log](/docs/transparency), Ed25519-signed and anchored to Bitcoin via OpenTimestamps: an auditable, non-repudiable issuance/revocation trail for a **NIS2 Art.23** report or an SCAS review. *Honest status:* tamper-evident and Bitcoin-anchored today, but **not yet independently witnessed** (it already speaks the C2SP `tlog-witness` protocol, so an external witness can co-sign). And you can bind each SCAS / incident log line to the NF's forge-proof `/128` with [signed outputs](/docs/sign-outputs), so a report attributes every logged action to the exact NF instance, not a claim.

## Recipe 6 · See who queried your NF identity

Enumeration is Stage 1 of the impersonation kill chain: before anyone speaks *as* your AMF, they resolve and probe it. Because every NF identity resolves through Whisper's own authoritative DNS and RDAP, the owner sees exactly who looked: a reconnaissance tripwire the operator-private NRF never gave you. This is the reverse of `op:logs` (the NF's *own* outbound): `op:lookups` answers *who checked this NF*.

```
# The recon tripwire: who has been resolving / RDAP-querying this NF's identity?
# A spike in PTR/TLSA/RDAP lookups from an unexpected origin = someone enumerating
# before they impersonate. Early warning, not a post-mortem. No key required:
curl -s https://whisper.online/ip/2a04:2a01:5e0::a3f/lookups
{ "address":"2a04:2a01:5e0::a3f",
  "lookups":[ {"kind":"PTR","count":128,"first":"…","last":"…"},
              {"kind":"TLSA","count":91,"…":"…"},
              {"kind":"rdap","count":17,"…":"…"} ] }

# With your key, the same over the control plane: per NF, alongside op:logs:
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:'"'"'lookups'"'"', args:{agent:'"'"'2a04:2a01:5e0::a3f'"'"'}})"}' | jq .
```

Pair the tripwire with Recipe 4: an unexpected surge of lookups against your NRF's or a slice's NFs, then `whisper.identify` on the querying source, turns "someone is casing the core" from a hunch you find in an incident review into a named operator you can act on before the impersonation lands.

## Where each recipe stops, and what's roadmap

Honest scoping, one row per recipe. Whisper anchors the DNS / IP / transport boundary; the SBI handshake, the operator PKI, the NRF, and the signaling plane stay exactly where they are.

| Recipe | Complements | Does *not* touch / replace |
|---|---|---|
| 1 · Bind an NF identity | the `urn:uuid:nfInstanceId` cert (TS 33.310) + mandatory SBI mTLS + OAuth2 | the NRF API (no change), the operator CA (still issues the cert), the SBI handshake |
| 2 · Keyless NF verify | SBI mTLS across the PLMN / IPX boundary | the mandatory mTLS + OAuth2 at the handshake; issues no NF certs |
| 3 · DANE-pin the SBA endpoint | TS 33.501 mTLS · EU 5G Toolbox TM02 · GSMA FS.36 (N32-c) | operator TLS-cert revocation (still the operator CA's job) |
| 4 · Back-trace on the graph | signaling firewalls (SS7 / Diameter / GTP) + SEPP / N32 | signaling / packet-core inspection; not a claim to evict a below-identity implant |
| 5 · Govern NF egress | 5G-core firewalls · NSA/CISA ESF micro-segmentation | inline GTP-U / SCTP inspection; adds no user-plane chokepoint |
| 6 · Lookups tripwire | NRF access logs · SCAS TS 33.117 logging | (a keyless read of your own identity's query record) |

> **Roadmap, clearly labelled.** Streaming this evidence into a SIEM ships today for **Splunk**, **Microsoft Sentinel** and **OpenCTI** (signed, replayable JSON → CEF / ECS fields). A **STIX 2.1 over TAXII** feed and a per-sector machine-readable export for sharing revocation and attribution evidence are proposed, not yet available. And the first-class typed `--nf-instance` CLI flag is roadmap. Provision NFs via the control-plane API shown above, which is live. Nothing on this list is required for the six recipes; they run on shipped primitives alone.

## Next

- [NF identity](/docs/industries/telecom/nf-identity): the full derivation the `/128` above comes from, SBA key + `nfInstanceId` to address
- [DANE & TLSA](/docs/dane): the `3 1 1` pin every check here rests on, byte for byte
- [Verify an agent](/docs/verify): the full keyless proof chain `whisper verify --trustless` walks
- [NESAS · NIS2 · 5G Toolbox](/docs/industries/telecom/telecom-compliance): where these recipes map into audit and incident-reporting evidence
