Skip to content

Keys

RSA and EC keys with the same versioning + soft-delete skeleton as secrets, plus cryptographic operations backed by real Go crypto — signatures verify against the JWK the API returns, not stubs. Software-protected only (no HSM).

Method + pathPurpose
POST /keys/{name}/createcreate — {kty, key_size?/crv?, key_ops?, attributes?, tags?} → key bundle
PUT /keys/{name}import a caller-supplied JWK ({key:{kty,…private members}, attributes?, tags?})
GET /keys/{name} | /keys/{name}/{version}get the public JWK
PATCH /keys/{name}/{version} | /keys/{name}update key_ops/attributes/tags (versioned or latest)
GET /keys | /keys/{name}/versionslist (paged)
DELETE /keys/{name}soft-delete
POST /keys/{name}/backup · POST /keys/restoreopaque backup blob (all versions) → restore into an empty name
GET | PUT /keys/{name}/rotationpolicyrotation policy (round-tripped; unset returns the disabled-rotation default)
POST /keys/{name}/{version}/releaseSecure Key Release → {value} (a signed JWS carrying the released public JWK)
GET/DELETE /deletedkeys/{name}, GET /deletedkeys, POST /deletedkeys/{name}/recoverdeleted-key lifecycle
POST /rng{count} (1–128) → {value} cryptographically-random base64url bytes

Release exercises the SDK’s ReleaseKey path. The response value is a genuine three-part JWS — a fresh signing key is generated per call and its public JWK rides in the header, so the token self-verifies. Real HSM/enclave attestation is out of scope (there is no secure enclave to attest), so any enabled key is releasable; the emulator emulates the shape and call path, not a hardware trust boundary.

Import reconstructs a real key from the JWK’s private members (RSA n/e/d/p/q, EC crv/x/y/d); the material is validated (RSA CRT check, EC on-curve check) and a subsequent sign verifies against the returned public JWK — the same interop guarantee as generated keys.

Versioned and unversioned; wire values are base64url. The caller hashes (Key Vault signs a digest), matching AKV semantics.

Method + pathAlgorithms
POST /keys/{name}/{version}/sign | /verifyRS256/384/512, PS256/384/512, ES256/384/512
POST /keys/{name}/{version}/encrypt | /decryptRSA1_5, RSA-OAEP, RSA-OAEP-256
POST /keys/{name}/{version}/wrapKey | /unwrapKeyRSA-OAEP, RSA-OAEP-256, RSA1_5
  • RSA — key sizes 2048 / 3072 / 4096.
  • EC — curves P-256 / P-384 / P-521.

RSA-HSM / EC-HSM kty values are accepted and normalized to their software equivalents (there is no HSM). The private key never leaves the store; every response derives the public JWK (n/e for RSA, crv/x/y for EC).

A signature the emulator produces verifies against the public JWK it returned — proven both through the SDK’s Verify and by independent reconstruction of the public key in the CI e2e (RSA via n/e, EC via the raw r‖s encoding Azure emits). A tampered signature fails; a disabled key 403s on crypto ops.

kc, _ := azkeys.NewClient(vaultURL, cred, opts)
key, _ := kc.CreateKey(ctx, "signer", azkeys.CreateKeyParameters{
Kty: to.Ptr(azkeys.KeyTypeRSA), KeySize: to.Ptr(int32(2048)),
}, nil)
digest := sha256.Sum256([]byte("attest me"))
sig, _ := kc.Sign(ctx, "signer", "", azkeys.SignParameters{
Algorithm: to.Ptr(azkeys.SignatureAlgorithmRS256), Value: digest[:],
}, nil)
// Verifies through the SDK — and locally against key.Key (n, e).
ok, _ := kc.Verify(ctx, "signer", "", azkeys.VerifyParameters{
Algorithm: to.Ptr(azkeys.SignatureAlgorithmRS256),
Digest: digest[:], Signature: sig.Result,
}, nil) // *ok.Value == true

Verified end to end against the real azkeys SDK in CI.