@openeudi/openid4vp 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +93 -1
- package/dist/index.cjs +371 -89
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +103 -2
- package/dist/index.d.ts +103 -2
- package/dist/index.js +369 -90
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -116,12 +116,104 @@ if (result.valid) {
|
|
|
116
116
|
} else {
|
|
117
117
|
console.warn("mismatch reasons:", result.match.unmatched);
|
|
118
118
|
// each entry: { queryId, reason, detail? }
|
|
119
|
-
// reason ∈ { format_mismatch, vct_mismatch, doctype_mismatch, missing_claims, trusted_authority_mismatch, no_credential_found }
|
|
119
|
+
// reason ∈ { format_mismatch, vct_mismatch, doctype_mismatch, missing_claims, value_mismatch, trusted_authority_mismatch, no_credential_found /* only when the candidate list is empty */ }
|
|
120
120
|
}
|
|
121
121
|
```
|
|
122
122
|
|
|
123
123
|
Mismatches return `valid: false` — they do not throw. Only crypto/structural failures (malformed VP tokens, invalid signatures, expired credentials) and malformed DCQL queries throw exceptions.
|
|
124
124
|
|
|
125
|
+
> **Privacy — diagnostics are verifier-internal.** `match.unmatched[].reason` and `detail` (including `value_mismatch`) are intended for verifier-side logging, debugging, and admin UIs. OpenID4VP §11 warns that per-claim verification outcomes can reveal wallet contents to observers. Do NOT echo these diagnostics into the OpenID4VP wire response sent back to the wallet, into end-user-visible error messages that another party could correlate, or into public analytics/third-party logs. The protocol's own error codes are the public interface; these fields are your internal instrumentation.
|
|
126
|
+
|
|
127
|
+
## Signed authorization requests (x509_san_dns)
|
|
128
|
+
|
|
129
|
+
For flows that require a signed request object (JAR) per OpenID4VP 1.0 §5.10, use `createSignedAuthorizationRequest`:
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
import { createSignedAuthorizationRequest } from "@openeudi/openid4vp";
|
|
133
|
+
|
|
134
|
+
const req = await createSignedAuthorizationRequest({
|
|
135
|
+
hostname: "verifier.example.com",
|
|
136
|
+
requestUri: "https://verifier.example.com/request.jwt",
|
|
137
|
+
responseUri: "https://verifier.example.com/response",
|
|
138
|
+
nonce,
|
|
139
|
+
signer: verifierKeyPair, // CryptoKeyPair with public+private
|
|
140
|
+
certificateChain: [leafCertDer], // DER-encoded, leaf SAN DNSName must equal hostname
|
|
141
|
+
encryptionKey: {
|
|
142
|
+
publicJwk: encryptionPublicJwk, // must include alg, e.g. "ECDH-ES"
|
|
143
|
+
},
|
|
144
|
+
vpFormatsSupported: {
|
|
145
|
+
"dc+sd-jwt": { "sd-jwt_alg_values": ["ES256"] },
|
|
146
|
+
},
|
|
147
|
+
}, dcqlQuery);
|
|
148
|
+
|
|
149
|
+
// req.uri — the short URI to hand to the wallet
|
|
150
|
+
// req.requestObject — the JWS the verifier must host at requestUri
|
|
151
|
+
// (Content-Type: application/oauth-authz-req+jwt)
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
The caller hosts `req.requestObject` at `requestUri` (the library does not host HTTP). The library verifies that the signing key's public SPKI matches the leaf certificate's public key — an attempt to sign with a mismatched key fails with `SignedRequestBuildError: signing_key_cert_mismatch`.
|
|
155
|
+
|
|
156
|
+
## Authorization responses (direct_post and direct_post.jwt)
|
|
157
|
+
|
|
158
|
+
Wallets POST the Authorization Response to your `responseUri`. The library is stateless — you MUST compare the envelope's `state` against the value you issued before treating the response as trustworthy. The recommended pattern differs slightly between the unencrypted and encrypted modes.
|
|
159
|
+
|
|
160
|
+
### Unencrypted (`direct_post`)
|
|
161
|
+
|
|
162
|
+
The envelope arrives as form-encoded JSON; parse it, check `state`, then verify:
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
import { verifyAuthorizationResponse } from "@openeudi/openid4vp";
|
|
166
|
+
|
|
167
|
+
const envelope = parsedVpTokenObject; // { vp_token, state, ... }
|
|
168
|
+
|
|
169
|
+
if (envelope.state !== submittedState) {
|
|
170
|
+
throw new Error("state mismatch — possible CSRF / replay");
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const result = await verifyAuthorizationResponse(envelope, dcqlQuery, {
|
|
174
|
+
trustedCertificates: [issuerCertDer],
|
|
175
|
+
nonce,
|
|
176
|
+
});
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Encrypted (`direct_post.jwt`)
|
|
180
|
+
|
|
181
|
+
The wallet wraps the envelope in a JWE. Decrypt explicitly so you can check `state` against the decrypted envelope **before** verification runs:
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
import {
|
|
185
|
+
decryptAuthorizationResponse,
|
|
186
|
+
verifyAuthorizationResponse,
|
|
187
|
+
} from "@openeudi/openid4vp";
|
|
188
|
+
|
|
189
|
+
const decrypted = await decryptAuthorizationResponse(
|
|
190
|
+
form.get("response"), // the JWE string
|
|
191
|
+
verifierEncryptionPrivateKey,
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
if (decrypted.state !== submittedState) {
|
|
195
|
+
throw new Error("state mismatch — possible CSRF / replay");
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const result = await verifyAuthorizationResponse(decrypted, dcqlQuery, {
|
|
199
|
+
trustedCertificates: [issuerCertDer],
|
|
200
|
+
nonce,
|
|
201
|
+
});
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
`verifyAuthorizationResponse` also accepts the JWE directly via `{ response: jwe }` together with `options.decryptionKey` — but that path makes the `state` check easy to skip, since the caller never holds the decrypted envelope. Prefer the explicit two-step pattern above.
|
|
205
|
+
|
|
206
|
+
`verifyAuthorizationResponse` accepts the OpenID4VP 1.0 §8.1 envelope shape: `vp_token` is always an object keyed by DCQL credential query id, with arrays of presentations. This release supports **single-credential single-presentation only** — multi-credential queries or multi-presentation arrays throw `MultipleCredentialsNotSupportedError`.
|
|
207
|
+
|
|
208
|
+
### Supported JWE algorithms
|
|
209
|
+
|
|
210
|
+
`direct_post.jwt` decryption supports:
|
|
211
|
+
|
|
212
|
+
- `alg`: `ECDH-ES` (driven by the encryption JWK's `alg` parameter)
|
|
213
|
+
- `enc`: `A128GCM`, `A256GCM` (HAIP requires both)
|
|
214
|
+
|
|
215
|
+
Other algorithms throw `UnsupportedJweError`.
|
|
216
|
+
|
|
125
217
|
### ParseOptions / VerifyOptions
|
|
126
218
|
|
|
127
219
|
Both `parsePresentation` and `verifyPresentation` accept:
|