How the emulator’s surface maps to real Key Vault (as documented at
learn.microsoft.com/azure/key-vault),
and — the point of this table — whether real work happens or just the API shape.
The design bet is that the durable, testable surface is protocol + real
cryptography + identity, and those are done for real: real RS256 token
validation, real RSA/EC keys doing real signing and encryption, real X.509
issuance. What is left out is the infrastructure around the vault — ARM, the
HSM, private networking — which no localhost process can honestly provide.
“Real via our own wire-protocol implementation.” A row is 🟢 Real not
only when real cryptography does the work, but also when the emulator itself
implements Key Vault’s wire protocol and the logic behind it — so a real,
unmodified SDK gets byte- and behaviour-identical responses. The Entra
challenge handshake, the object model and the error envelope are all in this
category.
Tokenless request returns the real challenge — Bearer authorization="…", resource="https://vault.azure.net" with AKV code AKV10000; unmodified azidentity walks it
Signature verified before any claim is read; issuer + audience (string or array) + exp/nbf with 60s skew, on the emulator’s controllable clock; JWKS cached by kid, refetched once on miss
🟢 Real
Principal derivation (oid → sub; idtyp=app → service principal)
Full
🟢 Real
Multiple trusted issuers
KV_ENTRA_ISSUER accepts a comma-separated list; each issuer validates against its own JWKS, and the verifying key is bound to the token’s iss
Stored and returned; retrieval stays permissive — exactly real Key Vault’s behaviour (expired secrets still read; consuming code decides)
🟢 Real
Backup / restore
An opaque sealed blob (AEAD under an emulator-held key), restorable only by the same emulator instance — the honest analog of the same-subscription/geography rule
Public JWK exposure (private material never leaves)
Full — private PKCS#8 stays in the store
🟢 Real
RSA-HSM / EC-HSM key types
Accepted, then silently normalised to software keys — no HSM exists
🟡 Emulated
Secure Key Release (/release)
exportable is enforced (a non-exportable key refuses release, as real KV) and release_policy is stored and returned; the JWS is genuinely signed — but no attestation (there is no enclave to attest)
🟡 Emulated
Key rotation policy (get/set)
Stored, round-tripped, and acting: a Rotate trigger’s timeAfterCreate rotates lazily on the emulator clock; expiryTime drives the new version’s exp
🟢 Real
nbf / exp enforced for cryptographic use
Crypto with an expired or not-yet-valid key returns 403, as real Key Vault refuses; reads stay permissive
🟢 Real
Rotate key (POST /keys/{name}/rotate)
Real: a new version with fresh material of the same type and size; key_ops and tags carry over
🟢 Real
key_ops enforcement
Enforced — an operation outside the key’s key_ops gets 403 Forbidden, and the JWK’s key_ops drives SDK-local refusal too
🟢 Real
oct / oct-HSM symmetric keys (and their AES algorithms)
Refused with the real error — vaults hold RSA/EC only; symmetric keys require Managed HSM, which is out of scope below
🟢 Real
BYOK (KEK-wrapped import)
Real: the .byok transfer blob’s CKM_RSA_AES_KEY_WRAP is genuinely undone — RSA-OAEP(SHA-1) to the vault-held KEK, then AES-KWP (RFC 5649) — and possession is proven by signature. The KEK is software-held, per the HSM normalisation above
🟢 Real
Key backup / restore
An opaque sealed blob (AEAD, emulator-held key) — private material never rides in a readable blob
Linked key + secret materialised under the same name
Full on create, as real Key Vault
🟢 Real
Certificate signing request (PKCS#10) for a named issuer
Real CSR; the operation reports inProgress with the CSR bytes
🟢 Real
Merge a signed chain
Real — and the leaf’s public key is verified to match the pending key before merge (400 otherwise)
🟢 Real
Issuance by a real CA
The emulator generates the key and a real CSR and merges the chain your CA signs — real X.509 only when you attach that CA
🟠 BYO-engine
Delete cascade to the linked key/secret
Deletion, recovery and purge carry the linked key and secret, as the three-views-of-one-object model requires
🟢 Real
Issuers / contacts
The issuer registry drives issuance: a named issuer must be registered before it can issue (Unknown = external-CSR escape hatch), as real KV requires; contacts round-trip. Live CA integration is the BYO-engine row above
🟢 Real
Certificate backup / restore
An opaque sealed blob (AEAD, emulator-held key)
🟢 Real
Cancel / delete a certificate operation
Cancel marks an in-progress operation cancelled (merge is then refused); delete removes the operation until the next create restores it
🟢 Real
Create operation LRO shape (inProgress on create → completed on poll)
As real Key Vault reports it — the shape the SDK pollers depend on
Real enforced state machine, retention validated 7–90 days — every transition and refusal is genuine logic, nothing pretended
🟢 Real
Retention window expiry
Genuinely clock-driven: an object past purgeAt purges lazily on observation — indistinguishable from a background job to any caller, and deterministic on the controllable clock
🟢 Real
Name reuse while soft-deleted → 409 Conflict
Enforced
🟢 Real
Purge protection / non-purgeable recoveryLevel
-purge-protection (or /_emulator/purge-protection): purge returns 403 Forbidden and recoveryLevel reports Recoverable while enabled
Always rendered canonically regardless of the listen address
🟢 Real
TLS with a cert covering *.vault.azure.net
Real self-signed material, persisted so fingerprints are stable
🟢 Real
Paging (maxresults, nextLink)
Full, capped at 25
🟢 Real
Key Vault error envelope + x-ms-request-id
On every response
🟢 Real
api-version validation
Required and validated: the 7.x line and the date-based versions current SDKs send (e.g. 2025-07-01) are accepted; anything else gets the real 400 envelope. Behaviour is not version-differentiated
A per-principal operation allowlist (POST /_emulator/permissions, ops named {type}/{op}, optional :{object} scope, * wildcard); empty = full access
🟡 Emulated
RBAC data-plane roles (Key Vault Secrets User, …)
The real built-in roles by name (POST /_emulator/rbac), expanded to their documented data-action sets, with object-level scopes (scope: "/keys/{name}") as data-plane RBAC supports — assignment via the control surface, not ARM
🟡 Emulated
Access policies (the classic vault access-policy document)
The real document shape (objectId + permissions:{secrets,keys,certificates}, incl. all) accepted at POST /_emulator/access-policy and enforced; unknown permission names refused
🟡 Emulated
Emulator-only (no Key Vault equivalent — these exist for testing)
The Go SDK tests are the real oracle: they reconstruct an *rsa.PublicKey from
the returned JWK and verify an SDK-produced signature outside the SDK, with a
tampered-signature negative alongside.
Scope boundary: the vault, not the infrastructure around it