# The NF-impersonation cure

The 5G core is a flat, all-IP HTTP/2 microservice mesh. OAuth2 authorization to the registry every function trusts, the **NRF**, is *optional by spec* (TS 33.501 §13). So **reachability becomes authorization**: any NF that can reach the NRF can pull any other NF's profile and speak as it, or deregister a live NF for a clean, stealthy DoS.

Whisper closes it by making the address *be* the NF: a routable IPv6 `/128` derived from the key already behind the NF's `urn:uuid:<nfInstanceId>` certificate, DNSSEC-anchored and DANE-pinned, that no peer can forge and one call can revoke worldwide. A stolen token with no NF key behind it authenticates to nothing. This page walks the root cause, the reframe, the exact live calls (keyless to verify, one keyed call to provision), the attribution companion, and where it honestly does and does not bite. It is **additive**: it never replaces the mutual TLS and OAuth2/NRF the SBA already mandates.

## Reachability is authorization

It is not a breach. The Service-Based Architecture is used exactly as it was designed: a web of microservices whose trust in one another was *asserted*, never proven, and weaponized by a party who was never authenticated.

Every NF (`AMF`, `SMF`, `UPF`, `PCF`, `UDM`, `AUSF`, `NSSF`) is an HTTP/2 + JSON service on the `SBI`. Each registers an `NFProfile` with the NRF (`TS 29.510`) and discovers its peers there. The SBA inherits the whole web/API threat model, and its most valuable object sits, by the spec's own allowance, effectively open: **token-based authorization for the NF↔NRF exchange is not required** (`TS 33.501` §13.3.1.3). Where an operator leaves it off, or a token is over-scoped, reaching the NRF is the same as being allowed to use it.

> The root cause has a name in the API world: **BOLA** (Broken Object-Level Authorization, OWASP API1:2023). The credential authenticates a *claim* ("I am an authorized caller") but never proves *which function* is on the other end. So a leaked JWS access token is indistinguishable from the real one, and the source IP that might have narrowed it down is disposable. Inside the SBA the object is the `NFProfile`; the same shape reappears on the northbound edge as CAMARA / Open Gateway APIs.

From a reachable NRF the moves are ordinary: read any NF's profile and **impersonate** it; register a rogue profile; or **deregister** a live NF's profile: a stealthy denial of service that leaves the target simply undiscoverable. None of it is caught by identity, because an NF's identity is a **private-PKI TLS certificate no counterparty can independently verify**. `TS 33.310` §6.1.3c binds `nfInstanceId` ↔ key ↔ endpoint in that certificate's `subjectAltName` (a URI-ID, `urn:uuid:<nfInstanceId>`), a genuinely good key-derived name, but it is signed by the operator's private CA only. No roaming partner, no IPX, no regulator, and no other operator can chain it to a public anchor without bilateral cross-certification. And the DNS layer that NF discovery rides on isn't signed at all: spoof it and you redirect an NF to a *rogue NRF* or a forged token-issuer URL.

Two properties make the abuse invisible at the network layer. A legitimate NF is *one endpoint to the functions it serves*; the impersonator is *one credential to a whole mesh it doesn't own*. And the egress hops across clouds, an IPX, or a residential-proxy swarm, so a core SOC correlates a fresh, meaningless *last IP* and nothing else. The endgame is not the intrusion. It is *unattributed persistence*: an action bound to no verifiable, revocable identity, with no cross-operator proof of *who* and no fast federated kill-switch, only slow manual de-peering. Nation-state-class interconnect campaigns have stayed unattributed and un-evicted for years on exactly this vacuum. We cite that only class-level, as proof the problem is real and unsolved, never as something an identity layer alone would have stopped.

## The reframe: the address is the NF

Detection will always be a step behind a credential that is genuinely valid. You can tune signaling firewalls and anomaly models forever and the impersonator still looks exactly like a peer: to your core, it is one. The strictly-stronger move is to change what the SBI trusts.

Whisper has one primitive: **the address is the identity**. A routable IPv6 `/128` out of `2a04:2a01::/32` (announced by **AS219419**), deterministically derived from a key, DNSSEC-signed to the IANA root, DANE-EE pinned (`3 1 1`), and RDAP-registered, re-derivable and verifiable by anyone with `dig`.

Point it at the NF. Whisper derives each function's `/128` from the key it *already holds*: the public `SubjectPublicKeyInfo` of the mTLS key behind the `urn:uuid:<nfInstanceId>` SAN in its SBA certificate, with the **nfInstanceId as the domain separator** (you pass it as `device_id`). The private key never leaves the NF; only its public SPKI is an input, and the `/128` is a one-way function of that public half and the UUID. The derivation is deterministic and **tenant-bound**: the same NF key and nfInstanceId always yield the same `/128`, and the same vendor NF under two operators yields two unrelated `/128`s that no outsider can link. The SBI can then authorize on the NF's *pinned identity*, not a stealable token: a request either proves it is the function it claims to be, before a single detection rule runs, or it has no authority at all.

```
NF mTLS key                 /128                        a name any peer can verify
urn:uuid · SBA cert SAN  ─▶ 2a04:2a01:3f::a1        ─▶  whisper verify --trustless
public SPKI only            into NFProfile.ipv6Addresses    our API not in the trust path
(private key stays in NF)   DNSSEC + DANE-EE 3 1 1          op:revoke → gone at DNS-TTL
```

The `nfInstanceId` UUID is already bound to the NF's key in its SBA cert: a good key-derived name trapped in a private, cross-operator-unverifiable root. Whisper binds it to a routable, publicly verifiable `/128` and gives it the cross-operator revoke private CRLs and OCSP never had. Same UUID, same address field, same cert, anchored in the public DNSSEC root.

> **The nfInstanceId is the public index; the `/128` is its cryptographic counterpart.** The `nfInstanceId` is a structured UUID flowing through every NRF registration and discovery; useful for interoperability, but not a secret. The `/128` derives from the in-NF key *salted* by that UUID, so the UUID alone yields nothing. You cannot go `nfInstanceId` → `/128` without the key, there is no enumerable directory, and RDAP / reverse DNS return the registry object, never the NF's whereabouts.

## What changes

Nothing here is a new detection rule. Each row is an abuse technique that stops being *possible*, not one you catch after the fact.

| The abuse today | Why it dies under identity |
| --- | --- |
| **Reach the NRF → become any NF** | Reachability is no longer authorization. A peer that reaches the NRF still cannot present an NF identity whose key it doesn't hold; every forgery is a DNSSEC / DANE inconsistency any counterparty catches with stock tools: `dig -x` names an identity whose `AAAA` and TLSA don't agree. |
| **Rotate egress across clouds / IPX / proxies** | Identity is not the source IP. The *last IP* was never the credential, so rotating it across AWS, GCP, Azure, an IPX hub, or a residential swarm changes nothing about whether the caller can prove the NF. |
| **Replay a stolen JWS token / over-scoped grant** | The bearer has no NF key behind it. A state-changing call terminates mutually-authenticated to the target NF's `/128` (whose DANE pin any counterparty checks against the public root), so a valid-looking token that can't prove the `nfInstanceId` never had authority. BOLA loses its leverage: reaching the NRF no longer reaches any NF. |
| **Deregister a live NF (stealth DoS)** | A deregister request that can't prove the target NF's identity is just noise. The verifiable, DNSSEC-anchored name and its DANE pin exist independently of the NRF record an attacker would try to pull. |
| **Blast radius on compromise** | One `revoke` tears down the `/128`, its PTR, and its DANE pin worldwide at DNS-TTL speed, *across operator boundaries*. No per-operator CRL you hope every peer fetched, no bilateral PKI exchange. Compromise one NF and you have compromised *that NF*, not the mesh. |

## Provision an NF identity

> **Shipped & live.** Deriving an NF `/128` from the key it already holds, with its `nfInstanceId` passed as `device_id`, is in production today. Provision one with the control-plane call below and verify it from the DNSSEC root with tools already on your machine.

Provisioning is one control-plane call to `POST https://graph.whisper.security/api/query` with your `X-API-Key` header. You pass the NF's **public** key material (the base64 `SubjectPublicKeyInfo` of the mTLS key behind its SBA cert) and its `nfInstanceId` as `device_id`; you get back the deterministic `/128` (drop it into the `NFProfile`'s `ipv6Addresses`) and a WireGuard config to source the NF's traffic from that address.

### The call

```
CALL whisper.agents({op:'connect', args:{
  tier:'wireguard',
  identity_public_key:'<base64 SPKI of the NF mTLS key>',   # the public half behind the urn:uuid SBA cert
  device_id:'3f2504e0-4f89-11d3-9a0c-0305e82c3301'          # the nfInstanceId (RFC 4122 UUID)
}}) YIELD op, ok, status, result, error
  RETURN op, ok, status, result, error
```

**Over stock tools**: a heredoc keeps the single-quoted Cypher literals intact, so this runs as-is:

```bash
# the public key only: the private key never leaves the NF's HSM / secure store
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:'3f2504e0-4f89-11d3-9a0c-0305e82c3301'}}) YIELD op, ok, status, result, error RETURN op, ok, status, result, error"}
JSON
```

### The response

```
# result carries the deterministic identity plus a ready-to-apply tunnel
address        2a04:2a01:3f::a1
fqdn           003f0000000000a1.<tenant>.agents.whisper.online
ptr            003f0000000000a1.<tenant>.agents.whisper.online
state          active                       # DNSSEC + DANE-EE (3 1 1) live at provision time
wireguard_config   [Interface] …            # source the NF's egress from its own /128
```

The NF now has a name any peer can prove and an address it egresses from. Reverse DNS resolves the `/128` to that identity, a TLSA record pins the leaf key (*the same key already behind the NF's `urn:uuid` cert*), and RDAP registers the object under `2a04:2a01::/32`. The full seven-proof chain, published atomically with the allocation, and it needs **no NRF API change and no new CA**.

### Idempotency and errors

The call is deterministic and honest about conflicts: conservative in what it emits, liberal in what it accepts. It answers a bad body with `ProblemDetails`, exactly as the NRF's own OpenAPI would.

| You send | You get |
| --- | --- |
| The **same** NF key + nfInstanceId again | The **same** `/128`: idempotent, safe to retry, safe to run on every NF start-up. No duplicate identities, no churn in the `NFProfile`. |
| The same NF key with a **different nfInstanceId** on your tenant | `409 Conflict`: a key binds to exactly one nfInstanceId. The `error.detail` tells you which UUID it is already bound to. |
| A **non-string** `device_id` (a number, an array, null) | `400` with an actionable `detail`, never an opaque `500`. Send the nfInstanceId as a string. |

> **Shipped vs roadmap.** The generic `device_id` domain separator is live: pass your `nfInstanceId` there today. A first-class typed `--nf-instance-id` CLI/API argument is **on the roadmap**; provision through the control-plane call above, which runs as-is. On the CLI, `whisper create --register` mints a generic identity and `whisper verify` / `whisper policy` / `whisper logs` / `whisper kill --revoke` drive the rest.

### Revoke, worldwide

A scale-in, a software upgrade that re-keys, a decommission, a changed roaming partner, or a compromised NF is one call. It's provable with the same stock tools that proved the identity existed, no Whisper software required.

```bash
# the control-plane op…
CALL whisper.agents({op:'revoke', args:{agent:'2a04:2a01:3f::a1'}})
# …or the CLI
whisper kill --revoke 2a04:2a01:3f::a1

# now prove it, worldwide, at DNS-TTL speed, across operator boundaries:
dig -x 2a04:2a01:3f::a1 +short                       # -> nothing
curl -s https://whisper.online/verify-identity/2a04:2a01:3f::a1
# -> {"is_whisper_agent": false, ...}
```

This kills the `/128` and its egress authorization faster than any CRL or OCSP could propagate: the federated kill-switch private PKIs never had. It does *not* revoke the operator's TLS certificate; that stays your CA's job. Sell it as an *additional*, faster kill-switch, not a replacement.

## Verify it: keyless, no account

The identity is public by design, so anyone (a roaming partner, an IPX, a regulator, your PSIRT, another operator) can check an NF without your key and without taking Whisper's word for it. This is the keyless half of the two-tier surface: verify with no key, provision and govern with your key.

```bash
# no key, no account: re-derive and verify the NF's identity, trustless to the IANA root
whisper verify --trustless 2a04:2a01:3f::a1

# or with only curl: the keyless full-chain verdict
curl -s https://whisper.online/verify-identity/2a04:2a01:3f::a1
# { "is_whisper_agent": true, "dane_ok": true, "jws_ok": true, "evidence": { ... } }

# the address IS the NF: forward-confirmed reverse DNS names it
dig -x 2a04:2a01:3f::a1 +short
# 003f0000000000a1.<tenant>.agents.whisper.online.

# the registry object for the /128: RDAP, typed JSON
curl -s https://whisper.online/ip/2a04:2a01:3f::a1 | jq '.handle, .parentHandle'
# "2A04:2A01:3F::A1/128"
# "2A04:2A01::/32"
```

The `--trustless` flag is the point: nothing there calls back to Whisper's own API as an authority. The CLI re-derives the DNSSEC chain to the IANA root, on your machine, with your resolver. This is what a private NRF and an operator-private CA cannot give you: a **home operator verifies a visiting peer against the public root, not the IPX's word**.

**At the roaming border: same peer, two ways to believe it.** The *old way* trusts a chain of assertions through a semi-trusted IPX hub at the `N32-f` / PRINS boundary, which reads and rewrites whitelisted JSON. The *new way* resolves the peer's `/128` and verifies its DANE-EE `3 1 1` pin at the public DNSSEC root, independent of any private CA and any IPX. Mutual TLS proves the peer only inside its own operator's private CA; Whisper lets the home operator verify the same identity (the `urn:uuid` key, now a DANE-pinned `/128`) against the public root without cross-certifying a single CA. It hardens the `N32-c` spoofing vector GSMA FS.36 names, and it complements PRINS and the SEPP; it does not replace them.

## Govern, observe, attest

An identity you can prove is also one you can *watch* and *fence*. Because every NF's name resolves through Whisper's own authoritative DNS and RDAP, the owner sees exactly who looked, governs precisely what each function may reach, and gets a non-repudiable record of every mint and revoke. All of the following are shipped control-plane ops on the same keyed endpoint.

```bash
# default-deny egress: an AMF may reach the NRF and SCP, nothing else
whisper policy set --default deny \
  --allow nrf.5gc.example-mno.net,scp.5gc.example-mno.net

# op:firewall: allow/deny by ip, cidr, host, or port; op:budget caps and kills
CALL whisper.agents({op:'firewall', args:{agent:'2a04:2a01:3f::a1',
  deny:['0.0.0.0/0'], allow:['nrf.5gc.example-mno.net:443']}})
CALL whisper.agents({op:'budget',   args:{agent:'2a04:2a01:3f::a1', cap:'5GB/day'}})

# op:lookups: WHO resolved or RDAP-queried this NF's identity (a recon tripwire)
curl -s https://whisper.online/ip/2a04:2a01:3f::a1/lookups
# PTR / AAAA / TLSA + RDAP accesses against this NF's records:
# catch a peer enumerating your NFs IN RECON, not in the post-mortem
```

- **Egress governance (`op:policy` · `op:firewall` · `op:budget` · `op:revoke`).** A per-NF `/128` with default-deny is network-layer least privilege *between* NFs: the "isolate the workloads" control of **NSA/CISA ESF 5G Cloud** expressed as a primitive, not a policy binder. Cap a function's traffic, and cut a compromised one off worldwide in one call.
- **Reverse observability (`op:lookups`).** The owner-facing companion to `op:logs` (the NF's own outbound): `lookups` returns who *checked* this NF's identity: the early warning a private NRF never gave you, and a per-operator verification-analytics stream.
- **The transparency log: nothing issued in the dark.** Every mint and every revoke lands in a public, append-only RFC 6962 Merkle log with Ed25519-signed checkpoints, each root anchored to Bitcoin via OpenTimestamps, an auditable NF-identity lifecycle trail for NIS2 incident reporting and your regulator (`/checkpoint`, `/ledger`, and per-identity `/ip/<addr>/transparency`). *Honest status:* tamper-evident, Ed25519-signed, and Bitcoin-anchored today, but **not yet independently witnessed**. The log speaks the C2SP `tlog-witness` protocol so any external witness can co-sign. GDPR-compatible: salted opaque commitments with selective disclosure, so an erasure destroys the meaning while the proofs stay valid.
- **Attest the logs (`sign-outputs`).** Bind an NF's `SCAS` logs or telemetry to its `/128` so a PSIRT can prove *which exact instance* emitted them: non-repudiation on top of the identity.

The same address-is-identity primitive that governs a compromised NF also governs the AI agents your NOC, your OSS/BSS automation, and your CAMARA developer edge are about to run: per-agent `/128`, per-agent logs, default-deny egress, one `revoke`. From day one.

## Name what already got in

Identity stops the next impersonation. It does not name the operator behind the sessions already in your logs. Interconnect abuse is unattributed *by design*, so the same platform back-traces them, and the attribution *survives* the rotation because it fingerprints the operator's infrastructure and tooling, not the ephemeral egress IP.

Take a suspect egress IP straight from your core SOC logs and ask the graph who really operates it. This runs read-only over the same public graph API, with your key:

```bash
# who really operates a host, even behind a CDN, an IPX, or a cloud front
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(\"34.90.x.x\")"}'
# operator fingerprinted across AWS / GCP / Azure; residential swarm collapsed by JA4
```

The read-only verbs (`identify`, `origins`, `walk`, `variants`, `history`) run over that one endpoint against a live internet-infrastructure graph of fused BGP, DNS, WHOIS, TLS, hosting, and threat intelligence. Cloud and IPX rotation collapses through `origins` and `walk`, which cluster shared ASN, hosting, and certificate lineage into one infrastructure genealogy; where a subscriber IP gives an infra graph nothing to grab, a `JA4/JA3` client fingerprint travels with the *tooling* and collapses a residential-proxy swarm to one operator. You can also express interconnect abuse directly as read-only Cypher (a question rather than a signature), such as *"one source touching N distinct NF identities in a window"*:

```cypher
# schema illustrative: the interconnect question, not a stored signature
MATCH (src)-[t:TOUCHED]->(n:NfIdentity)
WHERE t.window = "15m"
WITH src, count(DISTINCT n) AS nfs
WHERE nfs > 50
RETURN src, nfs ORDER BY nfs DESC
# -> 1 source → 2,187 distinct NF identities / 15m; egress collapsed to one operator
```

Every answer is reproducible, replayable JSON: the cryptographic paper trail **NIS2 Art.23**'s 24-hour / 72-hour / one-month reporting clock needs, not a screenshot.

> These graph verbs are the **API surface**: you call the endpoint directly, as above. There is no `whisper identify` / `graph` CLI subcommand; the CLI covers the control plane (`create`, `verify`, `policy`, `logs`, `kill`), and the graph is the query API. See [Graph & cognition](/docs/graph-api).

## Where it fits: standards, SIEM, integrations

Whisper is additive. It rides *on top of* the anchors 3GPP mandates and the SIEM you already run; it replaces none of them. It never inspects an SS7, Diameter, or GTP message, never touches the 5G-AKA / UICC subscriber plane or the air interface. It anchors the IP / DNS / transport boundary, strongest at the trust boundaries where independent verification matters most: NF discovery and its DNS, the N32 roaming border, NEF / CAMARA exposure, and management.

**Compliance.** The strongest, least-contestable claim: **DNSSEC + DANE close the DNS-spoofing / rogue-NRF / forged-issuer gap** that `TS 33.501`'s mandated mTLS + OAuth2 leave open on the name layer. Beyond that: cryptographic attribution accelerates the who/where forensics **NIS2 Art.21(2)(b) / Art.23** demand; a per-NF `/128` micro-perimeter is the **NSA/CISA ESF 5G Cloud** anti-lateral-movement control and advances **CISA ZTMM**'s Identity + Visibility pillars; a DANE-pinned NF resolution is an **EU 5G Toolbox TM02** move; a DANE-pinned peer identity hardens the `N32-c` vector **GSMA FS.36** names; and a per-component `/128` serves **O-RAN WG11**'s Zero-Trust Architecture Identity pillar across a multi-vendor RAN. The full mapping lives in [NESAS · NIS2 · 5G Toolbox](/docs/telecom-compliance).

> **Honest caveats: say them out loud.** Whisper *never* replaces 3GPP mTLS + OAuth2/NRF (mandatory and primary; this is a second, independent, DNS-anchored layer). It is **not** a NESAS / SCAS certification control and **not** a CRA conformity route: a differentiator and PSIRT tool, not a shortcut through certification. It does **nothing** for FCC rip-and-replace. And its `revoke` kills the `/128` and the egress authorization faster than any CRL/OCSP, but revoking the operator's TLS cert stays the operator CA's job.

> **SIEM: shipped vs roadmap.** The **Splunk**, **Microsoft Sentinel** and **OpenCTI** connectors ship today; findings arrive as signed JSON mapped to CEF and ECS fields. **On the roadmap**, labelled as such: **STIX 2.1 over TAXII** export, and a per-sector machine-readable export for peer sharing.

**Integrations (proposed, not vendor-endorsed).** Whisper anchors the cloud, IP, and DNS boundary, never the SS7/Diameter/GTP message, the SEPP's in-line PRINS transform, or the air interface:

- **NRF / NFProfile (TS 29.510).** The `/128` drops into the `ipv6Addresses` field the NRF already stores, and the DANE TLSA pins the `urn:uuid` cert the NF already presents. Same UUID, same address field, same cert: publicly anchored, with no NRF API change. Complements the NRF; does not replace it.
- **SEPP / N32 (TS 29.573) · GSMA FS.36.** The same DANE pin anchors a SEPP's `N32-c` endpoint, letting the home operator verify a peer against the public root instead of the IPX's assertion. Complements PRINS and TLS; never inspects the roaming JSON.
- **CAMARA / GSMA Open Gateway.** The northbound developer edge inherits the full API threat model; a per-endpoint `/128` + DANE + a who-accessed-what trail gives each authorized party a verifiable, revocable identity. Complements the NEF; does not replace its OAuth2.
- **O-RAN WG11 (O1 / O2 / A1).** Multi-vendor disaggregation multiplies the CAs that must trust each other; a vendor-neutral, publicly verifiable `/128` per component serves the ZTA Identity pillar without every vendor chaining to one private CA. Complements CMPv2 cert management.

And it is built to **fail open**: a Whisper outage never drops a call. Checks degrade to the anchors you already run, and connectivity is preserved. Anycast on AS219419, no single node in the path.

## Next

- [NF identity](/docs/nf-identity): how the `/128` is derived from the NF's key and its `nfInstanceId`, in depth
- [Control plane](/docs/control-plane): the full `whisper.agents` op set the provisioning call belongs to
- [NESAS · NIS2 · 5G Toolbox](/docs/telecom-compliance): the clause-by-clause evidence mapping
- [Verify · attribute · govern](/docs/telecom-recipes): runnable recipes for the N32 border, CAMARA edge, and PSIRT attribution
