Key providers
Every signing operation in the SDKs — records, JWTs, HTTP signatures, lifecycle events — goes through a KeyProvider, the interface that abstracts where private keys live. Two implementations ship with each SDK (a local file-backed store and an AWS KMS-backed provider), and you can implement your own for other KMSs or HSMs without touching the verification APIs.
All providers share one key lifecycle:
- Pending — freshly generated, unpublished; can sign only for proof-of-possession during rotation, and is hidden from key listings and JWKS.
- Active — the one signing key; always listed first.
- Retained — rotated out; published for verifying old signatures, can never sign again.
Local file-backed keys
Section titled “Local file-backed keys”The store is a JSON file (written mode 0600) holding the active, retained, and pending private JWKs. All three local providers support Ed25519 and ES256.
// Load, or generate + persist on first run.kp, err := dnsid.LoadOrCreateLocalKeyProvider("keys.json", dnsid.JoseAlgEdDSA)if err != nil { log.Fatal(err)}fmt.Println(kp.JWK()) // active public JWKfmt.Println(kp.ListKeyIds()) // active kid first, then retained
// Rotate: generate pending -> activate -> supersede the old key.pendingKid, _ := kp.GenerateKey(dnsid.JoseAlgEdDSA)_ = kp.Activate(pendingKid) // old active becomes retained_ = kp.Supersede(oldKid) // drop the retained keyimport { LocalKeyProvider } from '@dnsid-ai/sdk/node';
// Load, or generate + persist on first run.const kp = await LocalKeyProvider.load('keys.json', true);console.log(await kp.signingKey()); // active public JWKconsole.log(await kp.listKeyIds()); // active kid first, then retained
// Rotate: generate pending -> activate -> supersede the old key.const pendingKid = await kp.generateKey();await kp.activate(pendingKid); // old active becomes retainedawait kp.supersede(oldKid); // drop the retained key
// Read the CLI's key for a domain (~/.dnsid/<domain>/private.jwk):const cliKp = await LocalKeyProvider.fromDomain('agent.example');from dnsid import LocalKeyProvider
# Load, or generate + persist on first run.kp = LocalKeyProvider.load("keys.json", create_if_missing=True)print(kp.signing_key()) # active public JWKprint(kp.list_key_ids()) # active kid first, then retained
# Rotate: generate pending -> activate -> supersede the old key.pending_kid = kp.generate_key()kp.activate(pending_kid) # old active becomes retainedkp.supersede(old_kid) # drop the retained key
# Read the CLI's key for a domain (~/.dnsid/<domain>/private.jwk):cli_kp = LocalKeyProvider.from_domain("agent.example") # read-only viewIn Go, config.IdentityManagerFromDnsid loads the CLI’s identity and key together. For the key alone, call dnsid.NewLocalKeyProvider with the domain’s private.jwk path.
The public JWKS to host at /.well-known/jwks.json is the same construction in every language: one entry per listed key id — active and retained keys, never pending ones.
AWS KMS
Section titled “AWS KMS”Private key material never leaves KMS: the provider fetches and caches public keys as JWKs and delegates signing to kms:Sign. Supported algorithms are ECDSA_SHA_256 (JOSE ES256) and ED25519_SHA_512 (JOSE EdDSA); pass a key ARN, key ID, or alias — aliases resolve to canonical IDs on load. Minimum IAM: kms:GetPublicKey + kms:Sign (plus kms:CreateKey to generate, kms:ScheduleKeyDeletion for deletion on cleanup).
// Separate module: go get github.com/dnsid-ai/dnsid-go/key/awsimport ( "github.com/aws/aws-sdk-go-v2/service/kms" "github.com/aws/aws-sdk-go-v2/service/kms/types" awskms "github.com/dnsid-ai/dnsid-go/key/aws")
client := awskms.SDKClient{Client: kms.NewFromConfig(awsCfg)}provider, err := awskms.Load(ctx, client, awskms.Config{ State: awskms.State{ActiveKeyID: "alias/dnsid-current"}, Algorithm: types.SigningAlgorithmSpecEcdsaSha256, // the default})// Persist provider.State() after GenerateKey/Activate/Supersede.// npm install @dnsid-ai/key-aws @aws-sdk/client-kmsimport { KMSClient } from '@aws-sdk/client-kms';import { AwsKmsKeyProvider, AwsSdkKmsFacade } from '@dnsid-ai/key-aws';
const provider = await AwsKmsKeyProvider.load( new AwsSdkKmsFacade(new KMSClient({ region: 'us-east-1' })), { state: { activeKeyId: 'alias/dnsid-current' }, algorithm: 'ECDSA_SHA_256', // or 'ED25519_SHA_512'; required },);// Persist provider.stateSnapshot() after generateKey()/activate()/supersede().# pip install "dnsid[aws] @ git+https://github.com/dnsid-ai/dnsid-py.git"import boto3from dnsid import AwsKmsConfig, AwsKmsKeyProvider, AwsKmsKeyState, BotoKmsFacade
facade = BotoKmsFacade(boto3.client("kms", region_name="us-east-1"))provider = AwsKmsKeyProvider.load( facade, AwsKmsConfig( state=AwsKmsKeyState(active_key_id="alias/dnsid-current"), algorithm="ECDSA_SHA_256", # or "ED25519_SHA_512" ),)# Persist provider.state_snapshot() after generate_key()/activate()/supersede().Implement your own
Section titled “Implement your own”Any key store can back DNSid by implementing the provider surface — the in-repo KMS providers are the model for wrapping a cloud client behind a facade.
// Satisfy the dnsid.KeyProvider interface (8 methods). The in-repo// providers add a compile-time check worth copying:var _ dnsid.KeyProvider = (*MyKeyProvider)(nil)
// JWK(kid ...string) jwk.Key // public JWK; no kid = active// ListKeyIds() []string // active first, then retained// Sign(payload []byte) (*dnsid.KeySignature, error)// SignKey(kid string, payload []byte) (*dnsid.KeySignature, error)// GenerateKey(alg dnsid.JoseAlg) (string, error)// Activate(kid string) error// Supersede(kid string) error// Purge(kid string) errorimport type { KeyProvider } from '@dnsid-ai/sdk';
class MyKeyProvider implements KeyProvider { async signingKey() { /* active public JWK; kid must not contain '#' */ } async jwk(kid: string) { /* ... */ } async listKeyIds() { /* active first, then retained */ } async sign(payload: Uint8Array) { /* raw signature bytes */ } async signKey(kid: string, payload: Uint8Array) { /* ... */ } async generateKey() { /* returns new pending kid */ } async activate(kid: string) { /* ... */ } async supersede(kid: string) { /* ... */ }}from dnsid import JWKfrom dnsid.interfaces import KeyProvider
class MyKeyProvider(KeyProvider): def signing_key(self) -> JWK: ... # active public JWK; kid must not contain '#' def jwk(self, kid: str) -> JWK: ... def list_key_ids(self) -> list[str]: ... # active first, then retained def sign(self, payload: bytes) -> bytes: ... def generate_key(self) -> str: ... # returns new pending kid def activate(self, kid: str) -> None: ... def supersede(self, kid: str) -> None: ... # sign_key(kid, payload) is optional — only needed for rotation flows.API reference
Section titled “API reference”Full key-provider surface per language: Go and Go AWS KMS · TypeScript AWS KMS · Python.