identity-kit 0.2.2

DID, key management and verifiable credential primitives
Documentation

banner

QA Coverage OpenSSF Scorecard crates.io docs.rs License

Collection of primitives for SSI libraries and applications: decentralized identifiers (DIDs), key management behind a pluggable key store, and JWT-based verifiable credentials.

Add it to a project with cargo add identity-kit. The API reference is on docs.rs.


Features

  • ✅ Did, DidUrl and DidMethod parsing and validation per W3C DID Core
  • ✅ DidDocument data model and DID Resolution types
  • ✅ JWK and compact JWS (RFC 7515, RFC 7517) with EdDSA, ECDSA and RSA verification
  • ✅ KeyStore trait designed for remote signers, with in-memory and file-based backends
  • ✅ PKCS#11 key store backend, tested against SoftHSM2
  • ✅ Identity trait for the signing party: the identifier, kid and signer for a negotiated identifier kind and algorithm, with KeyStoreIdentity over any key store
  • ✅ DidResolver and DidProducer traits with a Resolver registry, document cache and DID URL dereferencing
  • ✅ KeyReference and KeyResolver: a token's key found from a DID URL kid, a #fragment relative to a DID iss, a jwk header, cnf, or the JWT VC issuer metadata of an HTTPS iss
  • ✅ did:key, did:jwk and did:web methods, with a hardened HTTPS client for did:web
  • ✅ Verifiable credentials and presentations as JWTs: the VCDM 1.1 JWT profile, VCDM 2.0 with VC-JOSE-COSE envelopes, and validators over any DID method
  • ✅ SD-JWT (RFC 9901), SD-JWT VC and VCDM 2.0 credentials secured with SD-JWT (vc+sd-jwt): issuing with selective disclosure, presenting with key binding, validating over any DID method, the Token Status List and type metadata
  • ✅ DIF Well Known DID Configuration: the DID configuration resource, its domain linkage credentials and their validation against an origin
  • ✅ DIF Linked Verifiable Presentation: LinkedVerifiablePresentation services, presentations signed for publishing, and fetching and validating what a DID document links

✅ implemented, 📋 planned. The full checklist, with Cargo features, tracking issues and links to every specification, is in docs/FEATURES.md. The architecture is described in docs/DESIGN.md.

Cargo features

All features are additive; the lower layer (did, jose, keys, identity, resolver) is always compiled.

Feature Default Enables
did-key ✅ The did:key method (methods::key)
did-jwk ✅ The did:jwk method (methods::jwk)
did-web ✅ The did:web method (methods::web) over any HttpClient
reqwest ✅ The hardened default HttpClient for did:web (reqwest with rustls); implies did-web
local-network Development and tests only: ReqwestClient::builder with opt-ins to reach loopback and private addresses over http; implies reqwest
secp256k1 ✅ ES256K signatures and secp256k1 keys, via k256
rsa RS256 and PS256 signature verification with keys of 2048 bits or more, via rsa
software ✅ SoftwareKeyStore: private keys in a JWK set file
in-memory InMemoryKeyStore: keys in memory, optionally derived from a seed
pkcs11 Pkcs11KeyStore: keys in a PKCS#11 module (an HSM, SoftHSM2), via cryptoki and tokio's blocking pool
vc ✅ Verifiable credentials and presentations as JWTs (vc); pulls in chrono
sd-jwt ✅ SD-JWT, SD-JWT VC and vc+sd-jwt credentials (sd_jwt_vc); implies vc, pulls in miniz_oxide
domain-linkage ✅ DIF Well Known DID Configuration (domain_linkage); implies vc and did-web
linked-vp ✅ DIF Linked Verifiable Presentation (linked_vp); implies vc and did-web
jwks resolver::JwksResolver: the keys of an HTTPS issuer from its JWT VC issuer metadata; implies did-web
utoipa OpenAPI schemas (utoipa::ToSchema) for DidDocument, VerificationMethod, Service, Jwk, DomainLinkageConfiguration, ClaimPath and TypeMetadata
test-vectors keys::testsuite, the behavioural suite every key store must pass, and vectors, the specification vectors

identity_kit::prelude gathers the common types of the lower layer and identity_kit::prelude::credentials those of the upper layer, for glob import.

Key stores

Private keys live behind the KeyStore trait: opaque key ids, one algorithm per key, signing as an async operation that may leave the process. The crate ships vendor-neutral backends; a cloud KMS or HSM backend implements the trait from its own crate.

Backend Feature Keys live in Generates Deletes Signs with
InMemoryKeyStore in-memory Process memory, zeroized on drop ✅ ✅ EdDSA, ES256, ES256K (secp256k1)
SoftwareKeyStore software A private JWK set file, permission-checked, atomically rewritten ✅ ✅ EdDSA, ES256, ES256K (secp256k1)
Pkcs11KeyStore pkcs11 A PKCS#11 token: an HSM, a smart card, SoftHSM2 ✅ unless write-protected ✅ unless write-protected What the module offers: EdDSA, ES256, ES256K (secp256k1), RS256 or PS256

Neither software backend signs with RSA: see ADR 0001; the PKCS#11 backend signs RSA in the module. The full contract, with the error vocabulary and the algorithm matrix, is in docs/key-stores.md.

Extending

Adding a DID method

A method is a type that implements DidResolver (the Read operation) and, when it can create documents from keys, DidProducer. No enum lists the methods: a Resolver dispatches on the DID's method name to whatever was registered.

use identity_kit::prelude::*;

struct DidExample;

#[async_trait::async_trait]
impl DidResolver for DidExample {
    fn method(&self) -> &'static str {
        "example"
    }

    async fn resolve(
        &self,
        did: &Did,
        options: &ResolutionOptions,
    ) -> Result<ResolutionResult, ResolutionError> {
        // Fetch or derive the document, then wrap it with its metadata.
        todo!()
    }
}

let resolver = Resolver::builder().with(DidExample).build();

DidProducer adds capabilities() (whether documents can be updated, where they are hosted, how many keys fit) and produce(keys, params), which builds a document for a set of public keys. Keep the method in a module of its own behind a Cargo feature, test it against its specification's vectors, and add a row to docs/FEATURES.md.

Adding a key store backend

A backend implements KeyStore: capabilities, public_jwk, sign and exists, plus generate and delete when the backend supports them. Signatures come back in their JWS form (64 bytes for EdDSA, raw r || s for ECDSA), and the capabilities must be truthful: every algorithm listed signs, and generate succeeds only when advertised.

use identity_kit::prelude::*;

struct MyKms { /* client, credentials */ }

#[async_trait::async_trait]
impl KeyStore for MyKms {
    fn capabilities(&self) -> KeyStoreCapabilities {
        let mut capabilities = KeyStoreCapabilities::new([JwsAlgorithm::ES256]);
        capabilities.remote = true; // signing leaves the process
        capabilities
    }

    async fn public_jwk(&self, key: &KeyId) -> Result<Jwk, KeyStoreError> { todo!() }

    async fn sign(&self, key: &KeyId, alg: JwsAlgorithm, data: &[u8]) -> Result<Vec<u8>, KeyStoreError> { todo!() }

    async fn exists(&self, key: &KeyId) -> Result<bool, KeyStoreError> { todo!() }
}

The behavioural suite checks the contract against a live backend; run it in the backend's own tests with the test-vectors feature enabled:

identity_kit::keys::testsuite::run(&store).await;                                   // a backend that generates keys
identity_kit::keys::testsuite::run_with_keys(&store, &[(key_id, JwsAlgorithm::ES256)]).await; // provisioned keys

Development

Report bugs and propose features in GitHub issues. See CONTRIBUTING.md for how to set up, test and submit changes. The parsers are fuzzed continuously with cargo-fuzz; fuzz/README.md explains the targets, their seeds and how to run them. To report a vulnerability, see SECURITY.md.

Release

Releases are automated with release-plz and follow Cargo's SemVer rules. Every push to main updates a release pull request with the next version and changelog, derived from the Conventional Commits since the last release. Merging it publishes to crates.io and creates the tag and GitHub release. Versions and CHANGELOG.md are never edited by hand. See docs/RELEASE.md for the versioning rules, changelog filtering and the credential setup.