Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/mdoc-conformance-checks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@credo-ts/core': patch
---

Update `@owf/mdoc`, `@owf/cose` and `@owf/token-status-list`, which add ISO/IEC 18013-5 conformance checks to mdoc verification:

- Device signed elements must be authorized by the `keyAuthorizations` in the MSO (9.1.3.4). Creating a device response with `deviceNameSpaces` the mdoc does not authorize now throws. `Mdoc.sign` accepts a new `keyAuthorizations` option to authorize namespaces or individual data elements.
- A CWT status list referenced by an mdoc must have an expiration time (12.3.6.3), and its `sub` must equal the URI it is referenced by. Pass `expiresAt` when creating a CWT token status list for mdocs.
- A device response with a status other than `0` must not contain documents, and a document must not contain the same element identifier twice in a namespace.
29 changes: 29 additions & 0 deletions .changeset/mdoc-dc-api-annex-c.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: the other changelog entries don't do line wrapping, this one does.

'@credo-ts/drizzle-storage': patch
'@credo-ts/askar': patch
'@credo-ts/core': patch
'@credo-ts/node': patch
---

Add support for the ISO/IEC TS 18013-7:2025 Annex C (`org-iso-mdoc`) Digital Credentials API.

- KMS: HPKE (RFC 9180) key agreement algorithms `HPKE-0`, `HPKE-3` and `HPKE-7`, following the
naming of draft-ietf-jose-hpke-encrypt. These are integrated-encryption algorithms, so
`encryption`/`decryption` must be omitted and `encrypt` returns an `encapsulatedKey`. Implemented
in the askar backend (the recipient private key stays inside askar; only the Diffie-Hellman output
leaves it) and in the node backend.
- Mdoc module: `createDcApiVerificationSession` / `verifyDcApiResponse` for verifiers and
`resolveDcApiRequest` / `createDcApiResponse` for wallets, backed by a new
`MdocVerificationSessionRecord`.
- `verifyDcApiResponse` matches the response against the device request of the session and throws
a `MdocDeviceRequestNotSatisfiedError` when a doc request is not satisfied. By default every
requested element must be disclosed and issuer signed; pass `deviceRequestElements` to
`createDcApiVerificationSession` to mark elements as optional or as device signed. The match is
returned as `deviceRequestMatch`.
- Reader authentication on an incoming request is resolved through the same trust layers as
credential verification: the certificates passed to `resolveDcApiRequest`, then the global
`getTrustedIssuersForVerification` callback (with the new `mdocReaderAuth` verification type,
called per doc request), then the deprecated `getTrustedCertificatesForVerification` callback,
then the statically configured trusted certificates. Resolving a reader authenticated request
throws when none of these are configured. Return the leaf certificate from the callback to trust
a reader on the certificate it presented itself.
7 changes: 7 additions & 0 deletions .changeset/response-encryption-supported-key-types.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@credo-ts/openid4vc": patch
"@credo-ts/askar": patch
"@credo-ts/node": patch
---

Only pick a response encryption key from `client_metadata.jwks` that the key management backends of the agent can actually perform the `ECDH-ES` key agreement with. Previously the first recognized `enc` key was used, which failed later on if e.g. a verifier included a `P-521` key and the configured KMS backend (such as Askar) does not support that curve. The Askar and Node key management backends now also take the curve of the external public key into account in `isOperationSupported`.
2 changes: 2 additions & 0 deletions packages/askar/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@
},
"dependencies": {
"@credo-ts/core": "workspace:*",
"@panva/hpke-noble": "catalog:",
"class-transformer": "catalog:",
"class-validator": "catalog:",
"hpke": "catalog:",
"rxjs": "catalog:",
"tsyringe": "catalog:"
},
Expand Down
180 changes: 142 additions & 38 deletions packages/askar/src/kms/AskarKeyManagementService.ts

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { readFileSync } from 'node:fs'
import path from 'node:path'
import { InjectionSymbols, JsonEncoder, Kms, TypedArrayEncoder } from '@credo-ts/core'
import { AskarError, askar } from '@openwallet-foundation/askar-shared'
import { askar } from '@openwallet-foundation/askar-nodejs'
import { AskarError } from '@openwallet-foundation/askar-shared'
import { getAgentConfig, getAgentContext } from '../../../../core/tests'
import { NodeFileSystem } from '../../../../node/src/NodeFileSystem'
import { AskarModuleConfig, AskarMultiWalletDatabaseScheme } from '../../AskarModuleConfig'
Expand Down Expand Up @@ -970,6 +971,32 @@ describe('AskarKeyManagementService', () => {
})
})

describe('isOperationSupported', () => {
it('returns false for key agreement with a curve that is not supported by askar', () => {
expect(
service.isOperationSupported(agentContext, {
operation: 'encrypt',
encryption: { algorithm: 'A256GCM' },
keyAgreement: {
algorithm: 'ECDH-ES',
externalPublicJwk: { kty: 'EC', crv: 'P-521', x: '', y: '' },
},
})
).toBe(false)

expect(
service.isOperationSupported(agentContext, {
operation: 'encrypt',
encryption: { algorithm: 'A256GCM' },
keyAgreement: {
algorithm: 'ECDH-ES',
externalPublicJwk: { kty: 'EC', crv: 'P-256', x: '', y: '' },
},
})
).toBe(true)
})
})

describe('encrypt', () => {
it('throws error if key is not found', async () => {
await expect(
Expand Down
Loading
Loading