Whisper · Docs
Telecom

Network-function identity

Bind the nfInstanceId a 5G network function already carries in its certificate to a routable IPv6 /128 derived from the NF's own public key, so the address is the NF: forge-proof, tenant-private, DNSSEC-anchored, DANE-pinned, publicly verifiable across operators, and revocable at DNS-TTL in one call. It's the cross-operator off-switch its operator-private CA never had.

This is the spine of the telecom vertical, and it is shipped and live. Everything else (the NF-impersonation cure, the NRF / SEPP / CAMARA / O-RAN integrations, the NIS2 and 5G-Toolbox evidence) builds on the one idea below: an NF's network address stops being a disposable label that any reachable session can present and becomes a cryptographic fact only that function's key can stand behind. The remarkable part is that 3GPP already binds a genuinely good key-based name into every NF's TLS certificate: the nfInstanceId in its subjectAltName. It then locks that name under an operator-private CA that no roaming partner, IPX or regulator can check, and whose revocation never crosses an operator boundary. Whisper keeps that property and gives it the two things it lacks.

Two tiers, per Postel's Law. With no API key, anyone (a roaming partner, an IPX, a peer operator, a regulator) can verify an NF'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 its egress, pull its logs, watch who's enumerating it, and revoke it. Verification never needs an account; the control plane does.

The nfInstanceId: a key-bound name, in a private root

Start with what the 5G core already has, because it is better than most people realize. Every network function on the service-based interface (SBI: an AMF, SMF, UPF, PCF, UDM, AUSF, or a border SEPP) is an HTTP/2 + JSON microservice that registers an NFProfile with the NRF (3GPP TS 29.510). That profile is keyed by the nfInstanceId: an RFC 4122 UUID. And that UUID is not a bare label floating in a database. Under the SBA certificate profile, TS 33.310 §6.1.3c mandates that the NF's TLS certificate carry, in its subjectAltName, a URI-ID equal to urn:uuid:<nfInstanceId> (ECDSA recommended). In other words, the standard already binds the NF's key material to a globally-unique, structured name inside a certificate the NF presents on every mutual-TLS handshake. That is the same instinct Whisper is built on, and it is a real head start.

The trouble is not the name. The trouble is the walls around it. Under TS 33.501, mutual TLS is mandatory on the SBI and the certificate is the identity anchor. But "TLS certificates shall be signed by the CA in the operator domain the entity belongs to," enrolled via CMPv2. The trust chain terminates at an operator-private root; there is no public or global anchor. Cross-operator trust, needed for roaming, means bilaterally cross-certifying each operator's Root CA, an N² PKI exchange mediated through IPX intermediaries. And revocation is a per-operator CRL/OCSP that does not cross the boundary where it matters most. So the nfInstanceId is a good key-bound name that is nonetheless boxed in on four sides at once:

The NF cert identity already has The NF cert identity still lacks
Key-bound identity: urn:uuid:<nfInstanceId> in the cert SAN (TS 33.310 §6.1.3c), the NFProfile primary key and the OAuth2 client_id Not publicly verifiable: trust ends at the operator-private CA; no roaming partner, IPX, regulator or peer operator can independently check it without cross-certification
Globally unique & structured: a UUID that flows through every NRF registration and discovery (TS 29.510) Not a routable identity: the UUID names a service, not a network endpoint that survives NAT, roaming and IPX to be reached and authorized against
Mandated: mutual TLS on the SBI, the certificate as the identity anchor (TS 33.501 §13) Rides an unsigned name layer: NF/NRF discovery resolves through DNS, but 3GPP mandates no DNSSEC/DANE: spoof the DNS and you redirect to a rogue NRF or a forged token-issuer URL mTLS never sees
Bound to a key only the NF holds: the private key sealed on the function Non-revocable across operators: CRL/OCSP is per-operator; de-peering a compromised roaming identity is slow, manual and commercial

Read the right-hand column as a to-do list. Whisper does exactly those four things, without touching the left-hand column, without re-keying a single NF, and without standing up a new CA.

The cure: a routable, revocable /128

Whisper keeps the nfInstanceId's one good property (identity bound to the NF's own key) and adds the two the private root withholds: public verifiability and cross-operator revocation at DNS-TTL. It does this by giving the function a real, routable IPv6 /128 out of 2a04:2a01::/32 (announced by AS219419) that is a deterministic function of the NF's public key, the same SBA mTLS key whose certificate SAN already carries the UUID, with the nfInstanceId as the domain separator. That address then anchors into DNS so any operator can prove it and one call can retire it, and it drops straight into NFProfile.ipv6Addresses with no NRF API change.

already in the NF certificate public · verifiable · revocable: what Whisper adds NF SBA key urn:uuid:nfInstanceId · TS 33.310 AMF · SMF · UPF · SEPP · CMPv2 private key sealed on-NF public key + nfInstanceId /128 2a04:2a01:5e0::a3f → NFProfile.ipv6Addresses DNSSEC + DANE-EE 3 1 1 A name any operator can verify whisper verify --trustless RDAP-registered · DNSSEC to the IANA root no operator-private CA in the trust path op:'revoke' → gone across operators at DNS-TTL
The NF certificate already carries urn:uuid:<nfInstanceId> in its SAN (TS 33.310 §6.1.3c): 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 the cross-operator off-switch that per-operator CRL/OCSP never delivered. One leaf key per identity; never a shared root.

Because the address is derived from a key only that NF holds, you cannot present an NF identity whose key you don't have. "One reachable NRF → speak as any NF" becomes physically impossible; a stolen bearer off the wrong address is inert; and every impersonation is a DNSSEC/DANE inconsistency any counterparty catches for free, inside the core or across the N32 roaming border. That is the whole point of moving trust from a claim the caller carries 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 NF already has. Three things go in:

Input What it is Where it lives
NF public key the SubjectPublicKeyInfo (SPKI) of the NF's SBA key: the public half of the very certificate whose SAN carries urn:uuid:<nfInstanceId> (TS 33.310); ECDSA in practice the public half is submitted; the private key never leaves the NF
nfInstanceId the RFC 4122 UUID the NF already registers with the NRF, used here as the domain separator (device_id = nfInstanceId) submitted with the request; the public index
nfService / replica selector (optional) a per-service or per-pod domain separator, so one instance can hold many addressable identities: a distinct /128 per nfService or per scaled-out replica optional; omit it for a single per-instance 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 operator:

# inputs -> a stable, forge-proof interface identifier
derive( NF public key,  nfInstanceId [, service/replica],  your PLMN )  -->  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:

The moment the address is derived it is published as a full identity, atomically: an AAAA, a forward-confirmed PTR in ip6.arpa, and a DANE-EE TLSA 3 1 1 record that pins the NF's leaf key directly, all DNSSEC-signed to the IANA root and registered in RDAP. That TLSA pin is what turns "the address is derived from a key" into "the address is provable against that key by anyone." See DANE & TLSA for the byte-for-byte record and DNSSEC for the chain it hangs from.

The private key never moves. The NF submits only its public SPKI: the same public half of the SBA mTLS key it already presents on every handshake. The server derives a public address from public inputs plus a server-side secret; it never sees, holds, or derives the NF's private key. The function proves ownership later by presenting its own key against the DANE pin. This is a second, DNS-anchored layer. It does not replace the mandatory mTLS + OAuth2 the SBI already runs.

Pin the SBA cert you already present

The derived /128 gives the NF a publicly verifiable address. There is a second, complementary move that is arguably the strongest thing on this page, because it closes a gap mutual TLS alone never covered: DANE-pin the very certificate the NF already presents.

3GPP mandates mTLS and OAuth2 on the SBI, but it mandates no DNSSEC/DANE on the name layer that NF and NRF discovery ride on. That name layer is DNS, and it is unsigned. The consequence, flagged in SA3 and academic analysis of the 5G SBA, is concrete: spoof the DNS and you redirect a consumer NF to a rogue NRF, or forge the token-issuer URL so a victim requests an OAuth2 access token from an attacker-controlled endpoint. An N32-c spoof at the roaming border works the same way. Mutual TLS authenticates the handshake it eventually reaches; it does nothing to guarantee you reached the right endpoint in the first place.

Publish a DANE TLSA record over DNSSEC that pins the NF's (or the NRF's) existing certificate, and the name → address → expected-cert binding becomes cryptographic and hijack-resistant, all the way to the IANA root. A relying party resolves the name, validates the DNSSEC chain, and checks that the certificate the handshake presents matches the pin, without any operator-private CA in its trust path, and without cross-certifying anyone. You keep the same urn:uuid:<nfInstanceId> identity; you simply make its certificate publicly checkable and its resolution un-spoofable.

The two moves are complementary. The derived /128 gives the NF a publicly verifiable, revocable address; a DANE-pin of the existing SBA cert makes its existing certificate publicly verifiable and its DNS resolution un-spoofable. Both anchor in the public DNSSEC root, closing the DNS-spoofing / rogue-NRF / forged-issuer vector that TS 33.501's mandated mTLS leaves open. This is a EU 5G Toolbox TM02 move, turning on a defence-in-depth control that is strongest at the trust boundaries: NF/NRF discovery, the N32 roaming border, and NEF exposure.

Provision an NF identity

Provisioning is one control-plane call: whisper.agents with op:'connect', tier:'wireguard', the NF's public SPKI, and the nfInstanceId passed as device_id. It returns the deterministic /128 and a ready WireGuard configuration so the NF's SBI 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 NF SBA key>',
  device_id:           '3f2504e0-4f89-11d3-9a0c-0305e82c3301'   // the nfInstanceId (UUID)
  // ecu_serial: 'nudm-r2'   // optional: a distinct /128 per nfService or replica
}}) 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:

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:'3f2504e0-4f89-11d3-9a0c-0305e82c3301'}}) 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 NF holds its own key, no private key is ever returned, only the public identity and the config that binds egress to it:

{
  "op": "connect", "ok": true, "status": 200,
  "result": {
    "tier":               "wireguard",
    "address":            "2a04:2a01:5e0::a3f",                        // the deterministic /128
    "fqdn":               "nf-amf-3f2504e0.sbi.<plmn>.agents.whisper.online",
    "nfprofile_hint":     "add to NFProfile.ipv6Addresses, no NRF API change",
    "server_public_key":  "…",
    "endpoint":           "…:51820",
    "dns":                "2a04:2a01:0:53::53",
    "wireguard_config":   "[Interface]\nAddress = 2a04:2a01:5e0::a3f/128\n…"
  },
  "error": null
}

Add the returned address to the NF's NFProfile.ipv6Addresses (the field already exists in TS 29.510, so there is no NRF API change and no re-registration ceremony), and drop the wireguard_config onto the function (or feed it to wireproxy for a no-root, userspace tunnel) so every packet it sends sources from its own /128. A consumer NF, a peer SEPP, or a regulator authorizes on that address: a forge-proof, attributable, revocable network identity, instead of a bearer anyone could carry. For the full transport mechanics and the SOCKS5 / AnyIP alternatives, see Connect & egress; for every other op on this same endpoint, the Control plane reference.

The whisper CLI ships create --register, verify --trustless, policy, logs, and kill --revoke. A dedicated --nf-instance-id flag is on the roadmap, not shipped. Provision NFs 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 the nfInstanceId, an NFProfile identity, or an O-RAN component identifier, whatever native identity the function carries.

Idempotent, with honest errors

Because the address is derived, provisioning is naturally idempotent, and the failure modes are clear rather than surprising. It's Postel all the way down, the same shape the NRF's own ProblemDetails uses:

You send You get
the same key + nfInstanceId again (same tenant) the same /128: a re-derivation, not a new allocation
the same key with a different nfInstanceId (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 NF derives its address from its own key

Verify: keyless, no account

The identity half is public on purpose. Anyone can prove an NF's /128 without a Whisper account and without trusting Whisper's word: a home operator (HPMN) checking a visited-network (VPMN) peer, an IPX, a regulator running a NIS2 forensic, or a suspicious counterparty. Four independent checks, all from tools already on the machine:

# 1. Forward-confirmed reverse DNS: the address names the NF, the name resolves back
dig -x 2a04:2a01:5e0::a3f +short
nf-amf-3f2504e0.sbi.<plmn>.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:5e0::a3f | 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:5e0::a3f | 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 nf-amf-3f2504e0.sbi.<plmn>.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 roaming partner or regulator that won't take Whisper's word, or the operator's, for anything. The full keyless walk lives in Verify an agent.

Revoke: across operators, in one call

A compromised or scaled-in NF 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: the same TTL a roaming peer's resolver already honours:

CALL whisper.agents({op:'revoke', args:{agent:'2a04:2a01:5e0::a3f'}})

# prove it: zero Whisper software, the same stock tools that proved it existed:
dig -x 2a04:2a01:5e0::a3f +short                       # -> nothing
curl -s https://whisper.online/verify-identity/2a04:2a01:5e0::a3f
# -> {"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 every operator, everywhere, at once. Contrast a per-operator CRL/OCSP that a roaming peer may never fetch, or a bearer rotation that reaches only the callers you can still reach. Compromise one NF and you've compromised that NF, not the mesh. The impersonate-any-NF failure mode is structurally removed.

Honest scope. This kills the /128 and its egress authorization faster than CRL/OCSP, and it kills them cross-operator. It does not revoke the operator's TLS certificate. That stays the operator CA's job under CMPv2. Sell it, and read it, as an additional kill-switch at the identity / network / egress layer, not a replacement for the PKI's own revocation.

The lifecycle, end to end

An NF identity is not a one-shot registration; it tracks the function through its whole service life: enrolment, in-life SBI, scale-out, incident, decommission. 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.

time → CMPv2 enrolment key born on-NF nfInstanceId minted NRF registration /128 → NFProfile.ipv6Addresses op:'connect' Scale-out replica new /128 per instance connect (per replica) Incident revoke cut off cross-operator, DNS-TTL op:'revoke' Scale-in / de-peer retire → re-register / erase revoke (+ erase) public Merkle transparency log every mint & revoke · Ed25519-signed · Bitcoin-anchored
One identity, tracked from CMPv2 enrolment to de-peer: each stage a plain whisper.agents call. A scale-out replica re-derives its own /128; an incident revoke cuts the function off across operators; every mint and revoke is written to the public log.
Stage What happens Call
CMPv2 enrolment The NF is born with its key; the operator CA issues its certificate with urn:uuid:<nfInstanceId> in the SAN. Nothing is minted with Whisper yet. native, no Whisper call
NRF registration At registration, derive the /128, source-bind SBI egress, and add the address to NFProfile.ipv6Addresses; peers now authorize the address, not a bearer. op:'connect'
Scale-out replica Each scaled-out instance carries its own key, so it re-derives a fresh /128 per replica; identity follows the instance, not the pool. op:'connect' (per replica)
Incident revoke A compromised NF (or a compromised roaming peer at the N32 border) is cut off across operators at DNS-TTL; verify flips to false everywhere. op:'revoke'
Scale-in / de-peer A scaled-in replica or a de-peered roaming identity is one revoke and, where required, an op:'erase' that 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 (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, and a natural fit for the incident-handling evidence NIS2 Art.23 and SCAS TS 33.117 logging expect. 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 NF: egress, budget, lookups

Revocation is the kill-switch. In between, the same control plane governs exactly what a live NF may reach, caps it, and tells you who is looking at it. Because egress is source-bound to the function's /128, policy is enforced by name and by address, default-deny: the per-NF micro-perimeter the NSA/CISA ESF 5G Cloud guidance (Parts II and III) asks for to prevent lateral movement:

# default-deny: this AMF may reach ONLY its peer SEPP and its OAM endpoint
whisper policy set --default deny --allow sepp.partner-plmn.example,oam.example-plmn.net

# per-NF firewall (allow/deny by host, cidr, or port) + a daily budget with a kill-switch
CALL whisper.agents({op:'firewall', args:{agent:'2a04:2a01:5e0::a3f',
     deny:['0.0.0.0/0'], allow:['sepp.partner-plmn.example:443','oam.example-plmn.net:443']}})
CALL whisper.agents({op:'budget',   args:{agent:'2a04:2a01:5e0::a3f', max_mb_per_day:500}})

See who's enumerating your NFs, before the impersonation lands. An identity you can prove is also an identity you can watch. Because every NF's name resolves through Whisper's own authoritative DNS and RDAP, the owner can ask who looked: a reconnaissance tripwire the operator-private NRF never gave you. op:'lookups' returns who resolved or RDAP-queried a function's identity, so you catch a rogue NF or a probing roaming peer enumerating the core in recon, not in the post-mortem:

# who has been resolving / RDAP-querying this NF's identity, and when
CALL whisper.agents({op:'lookups', args:{agent:'2a04:2a01:5e0::a3f', window:'24h'}})

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

Paired with op:'logs' (the NF's own outbound activity) you have both halves of the picture: what a function reaches out to, and who is reaching in to look at it. And because each SBI response, CDR or N32 message can be signed under the NF's forge-proof /128, a roaming partner, a regulator, and interconnect settlement can trust the record came from the real function: non-repudiable interconnect telemetry, not a spoofed feed. The full policy surface is in Egress governance.

Attribution: name whoever already probed you

Identity stops the next impersonation. To name whoever already probed the core or a roaming border, across rotating IPX hubs, cloud regions 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 IPX → AWS → Azure hops that a raw last-IP loses:

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.60.x.x\")"}' | jq .
# operator fingerprinted across IPX / AWS / 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 core SOC, your interconnect team, and a regulator can replay: the who/where forensics NIS2 Art.23 reporting turns on. There is no whisper identify CLI subcommand. This is the API call, and it is live. Full verbs and shapes in Graph & cognition. Honest boundary: attribution names the operator behind a rotating egress; it is not a claim we would have stopped a router implant or a stolen management credential. The class of state-linked interconnect campaigns reported across 80+ countries and 600+ organizations since at least 2019 persists in exactly the attribution-and-eviction vacuum this closes, which is the honest reason to close it.

Where this fits, and where it doesn't

Whisper anchors the NF's identity at the DNS / IP / transport boundary: the network endpoint a consumer NF, a peer SEPP, or a regulator authorizes. It is additive: it complements the roots of trust 3GPP mandates and does not try to replace them, and it deliberately stops outside the intra-SBI path where mTLS and the NRF already bind tightly.

See NESAS · NIS2 · 5G Toolbox for how these identities map to NIS2 Art.21/23, the EU 5G Toolbox TM02, NSA/CISA ESF 5G Cloud Parts II and III, CISA ZTMM Identity, and GSMA FS.36 evidence. The honest note there: this is not a NESAS/SCAS certification control but a defence-in-depth differentiator and a PSIRT attribution tool. The Splunk, Microsoft Sentinel and OpenCTI connectors are shipped; STIX 2.1 over TAXII export is on the roadmap and labelled as such there. 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