
[](https://github.com/impierce/identity-kit/actions/workflows/qa.yml)
[](https://codecov.io/gh/impierce/identity-kit)
[](https://scorecard.dev/viewer/?uri=github.com/impierce/identity-kit)
[](https://crates.io/crates/identity-kit)
[](https://docs.rs/identity-kit)
[](https://github.com/impierce/identity-kit/blob/HEAD/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](https://docs.rs/identity-kit).
---
## Features
- ✅ `Did`, `DidUrl` and `DidMethod` parsing and validation per [W3C DID Core](https://www.w3.org/TR/did-core/)
- ✅ `DidDocument` data model and [DID Resolution](https://www.w3.org/TR/did-resolution/) types
- ✅ JWK and compact JWS ([RFC 7515](https://www.rfc-editor.org/rfc/rfc7515), [RFC 7517](https://www.rfc-editor.org/rfc/rfc7517)) 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](https://www.w3.org/TR/vc-data-model/) JWT profile, [VCDM 2.0](https://www.w3.org/TR/vc-data-model-2.0/) with [VC-JOSE-COSE](https://www.w3.org/TR/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, and type metadata
- ✅ The IETF Token Status List for JWT and SD-JWT credentials alike: issuing and signing status lists, and checking the `status` claim
- ✅ 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`](https://github.com/impierce/identity-kit/blob/HEAD/docs/FEATURES.md).
The architecture is described in [`docs/DESIGN.md`](https://github.com/impierce/identity-kit/blob/HEAD/docs/DESIGN.md).
## Cargo features
All features are additive; the lower layer (`did`, `jose`, `keys`, `identity`, `resolver`) is always compiled.
| `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` |
| `status-list` | ✅ | The IETF Token Status List for JWT and SD-JWT credentials (`status_list`); implies `vc`, pulls in `miniz_oxide` |
| `sd-jwt` | ✅ | SD-JWT, SD-JWT VC and `vc+sd-jwt` credentials (`sd_jwt_vc`); implies `vc` and `status-list` |
| `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`, `JwsAlgorithm`, `Credential`, `Presentation`, `CredentialV2`, `PresentationV2`, `EnvelopedVc`, `EnvelopedVp`, `DomainLinkageConfiguration`, `ClaimPath`, `TypeMetadata` and `StatusType` |
| `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.
| `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](https://github.com/impierce/identity-kit/blob/HEAD/docs/adr/0001-rsa-verification-only.md); 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`](https://github.com/impierce/identity-kit/blob/HEAD/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.
```rust
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`](https://github.com/impierce/identity-kit/blob/HEAD/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.
```rust
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:
```rust
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](https://github.com/impierce/identity-kit/issues).
See [CONTRIBUTING.md](https://github.com/impierce/identity-kit/blob/HEAD/CONTRIBUTING.md) for how to set up, test and submit changes.
The parsers are fuzzed continuously with `cargo-fuzz`; [`fuzz/README.md`](https://github.com/impierce/identity-kit/blob/HEAD/fuzz/README.md) explains the targets, their seeds and how to run them.
To report a vulnerability, see [SECURITY.md](https://github.com/impierce/identity-kit/blob/HEAD/SECURITY.md).
## Release
Releases are automated with [release-plz](https://release-plz.dev/) and follow [Cargo's SemVer rules](https://doc.rust-lang.org/cargo/reference/semver.html).
Every push to `main` updates a release pull request with the next version and changelog, derived from the [Conventional Commits](https://www.conventionalcommits.org/) since the last release.
Merging it publishes to [crates.io](https://crates.io/crates/identity-kit) and creates the tag and GitHub release.
Versions and `CHANGELOG.md` are never edited by hand.
See [`docs/RELEASE.md`](https://github.com/impierce/identity-kit/blob/HEAD/docs/RELEASE.md) for the versioning rules, changelog filtering and the credential setup.