@gramota/oid4vp 0.1.0 → 0.2.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 +47 -7
- package/dist/cert.d.ts +72 -0
- package/dist/cert.d.ts.map +1 -0
- package/dist/cert.js +191 -0
- package/dist/cert.js.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/jar.d.ts +50 -0
- package/dist/jar.d.ts.map +1 -0
- package/dist/jar.js +77 -0
- package/dist/jar.js.map +1 -0
- package/dist/request.d.ts +48 -6
- package/dist/request.d.ts.map +1 -1
- package/dist/request.js +48 -6
- package/dist/request.js.map +1 -1
- package/dist/response.d.ts +5 -3
- package/dist/response.d.ts.map +1 -1
- package/dist/response.js +90 -40
- package/dist/response.js.map +1 -1
- package/dist/types.d.ts +54 -6
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +10 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @gramota/oid4vp
|
|
2
2
|
|
|
3
|
-
> OpenID for Verifiable Presentations
|
|
3
|
+
> OpenID for Verifiable Presentations Final 1.0 — authorization request/response wire format, RFC 9101 JAR signing, X.509 verifier-identity certs. Accepts both DCQL and DIF Presentation Exchange response shapes.
|
|
4
4
|
|
|
5
5
|
Part of [Gramota](https://github.com/gramota-org/gramota) — the TypeScript
|
|
6
6
|
SDK for the EU Digital Identity Wallet (EUDIW).
|
|
@@ -13,17 +13,57 @@ pnpm add @gramota/oid4vp
|
|
|
13
13
|
# or: yarn add @gramota/oid4vp
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
##
|
|
16
|
+
## Quick example — verifier emits a signed JAR
|
|
17
17
|
|
|
18
18
|
```ts
|
|
19
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
generateSigningCert,
|
|
21
|
+
signAuthorizationRequest,
|
|
22
|
+
buildAuthorizationRequestUrl,
|
|
23
|
+
} from "@gramota/oid4vp";
|
|
20
24
|
|
|
21
|
-
|
|
22
|
-
const
|
|
25
|
+
// Self-signed cert with SAN-DNS = the verifier's hostname
|
|
26
|
+
const cert = await generateSigningCert({ sanDns: "verifier.example" });
|
|
27
|
+
|
|
28
|
+
// Build the OID4VP request, sign as a JAR (RFC 9101) embedded in the URL
|
|
29
|
+
const request = {
|
|
30
|
+
response_type: "vp_token" as const,
|
|
31
|
+
client_id: `x509_san_dns:${cert.sanDns}`,
|
|
32
|
+
response_mode: "direct_post" as const,
|
|
33
|
+
response_uri: "https://verifier.example/oid4vp/response",
|
|
34
|
+
nonce: "n-12345",
|
|
35
|
+
state: "s-67890",
|
|
36
|
+
dcql_query: {
|
|
37
|
+
credentials: [
|
|
38
|
+
{ id: "pid", format: "dc+sd-jwt", meta: { vct_values: ["urn:eudi:pid:1"] } },
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
const jar = await signAuthorizationRequest({ request, cert });
|
|
43
|
+
const deepLink = `openid4vp://?client_id=${encodeURIComponent(request.client_id)}&request=${encodeURIComponent(jar)}`;
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Quick example — verifier parses the wallet's response
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
import { parseAuthorizationResponseFromParams } from "@gramota/oid4vp";
|
|
50
|
+
|
|
51
|
+
// req.body is { vp_token, presentation_submission?, state }
|
|
52
|
+
const response = parseAuthorizationResponseFromParams(req.body);
|
|
53
|
+
// vp_token can be a string (PEX, single), string[] (PEX, multi),
|
|
54
|
+
// or { [credentialId]: string } (DCQL, OID4VP Final 1.0)
|
|
23
55
|
```
|
|
24
56
|
|
|
25
|
-
|
|
26
|
-
|
|
57
|
+
## What's inside
|
|
58
|
+
|
|
59
|
+
- `buildAuthorizationRequestUrl` / `parseAuthorizationRequestUrl` — wire format (§5)
|
|
60
|
+
- `parseAuthorizationResponseFromParams` / `buildAuthorizationResponseBody` — wire format (§6); accepts both PEX and DCQL response shapes
|
|
61
|
+
- `generateSigningCert` — self-signed X.509 with SAN-DNS for `x509_san_dns` client_id_prefix
|
|
62
|
+
- `signAuthorizationRequest` — RFC 9101 JAR signing with cert in the JWS `x5c` header
|
|
63
|
+
- `signingCertToJwks` — convert the cert+key bundle into a JWK pair for `@gramota/issuer`
|
|
64
|
+
|
|
65
|
+
For the high-level Verifier API, see the
|
|
66
|
+
[main repo](https://github.com/gramota-org/gramota).
|
|
27
67
|
|
|
28
68
|
## License
|
|
29
69
|
|
package/dist/cert.d.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* X.509 self-signed cert generation for OID4VP verifiers.
|
|
3
|
+
*
|
|
4
|
+
* The EU reference wallet's `X509SanDns` client_id_prefix accepts
|
|
5
|
+
* Authorization Requests signed by a cert whose Subject Alternative Name
|
|
6
|
+
* matches the verifier's hostname. For local dev, mock environments, and
|
|
7
|
+
* pinned-trust deployments we generate a fresh ES256 keypair + a leaf
|
|
8
|
+
* cert with the right SAN. Production deployments swap this for an
|
|
9
|
+
* externally issued cert (ACME / corporate CA) — the {@link SigningCert}
|
|
10
|
+
* shape is the same, only the origin differs.
|
|
11
|
+
*
|
|
12
|
+
* The cert+key pair is consumed by:
|
|
13
|
+
* 1. `signAuthorizationRequest` — signs the JAR JWS, embedding the
|
|
14
|
+
* cert in the `x5c` header.
|
|
15
|
+
* 2. The wallet's TLS / trust-anchor store — it must trust this cert
|
|
16
|
+
* out-of-band (in production: via a CA the wallet already trusts;
|
|
17
|
+
* for emulators: via patching the wallet's trust list).
|
|
18
|
+
*/
|
|
19
|
+
import "reflect-metadata";
|
|
20
|
+
import type { JsonWebKey } from "@gramota/jose";
|
|
21
|
+
import { type SigningCert } from "./types.js";
|
|
22
|
+
export interface GenerateSigningCertOptions {
|
|
23
|
+
/** Primary DNS name for the cert's Subject Alternative Name. Must match
|
|
24
|
+
* the verifier's hostname (the part the wallet sees in the OID4VP
|
|
25
|
+
* `client_id` after the `x509_san_dns:` prefix). */
|
|
26
|
+
readonly sanDns: string;
|
|
27
|
+
/** Additional SAN-DNS entries — used for wildcards (e.g. `*.localtest.me`)
|
|
28
|
+
* so a single cert covers per-tenant subdomains, or for serving multiple
|
|
29
|
+
* verifier hostnames behind one identity. */
|
|
30
|
+
readonly extraSanDns?: readonly string[];
|
|
31
|
+
/** Cert subject CN — purely cosmetic, shown in cert viewers. Defaults
|
|
32
|
+
* to `sanDns`. */
|
|
33
|
+
readonly commonName?: string;
|
|
34
|
+
/** Org name for the cert subject. Defaults to `"Gramota Verifier"`. */
|
|
35
|
+
readonly organizationName?: string;
|
|
36
|
+
/** Validity in days. Default 365. */
|
|
37
|
+
readonly validDays?: number;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Generate an ES256 keypair + self-signed X.509 cert with the given
|
|
41
|
+
* SAN-DNS entries.
|
|
42
|
+
*
|
|
43
|
+
* The cert carries the standard verifier-identity extensions: serverAuth
|
|
44
|
+
* + clientAuth EKUs (so wallets that look for a TLS-style cert accept
|
|
45
|
+
* it), digitalSignature + keyEncipherment KU flags, and CA:false.
|
|
46
|
+
*
|
|
47
|
+
* Returns a {@link SigningCert} ready to feed into
|
|
48
|
+
* {@link signAuthorizationRequest}.
|
|
49
|
+
*
|
|
50
|
+
* @throws {@link Oid4vpError} with `oid4vp.cert_generation_failed` if
|
|
51
|
+
* the underlying crypto / X.509 generation fails.
|
|
52
|
+
*/
|
|
53
|
+
export declare function generateSigningCert(input: GenerateSigningCertOptions): Promise<SigningCert>;
|
|
54
|
+
/**
|
|
55
|
+
* Convert a {@link SigningCert} (PEM bundle) into the JWK pair that
|
|
56
|
+
* `@gramota/issuer` and other JOSE consumers expect.
|
|
57
|
+
*
|
|
58
|
+
* Unlike `generateSigningCert` this is a pure conversion — it doesn't
|
|
59
|
+
* touch crypto state or generate anything new. It just bridges the two
|
|
60
|
+
* representations of the same key material:
|
|
61
|
+
*
|
|
62
|
+
* - Input shape: PKCS#8 PEM (private) + X.509 PEM (public-via-cert)
|
|
63
|
+
* - Output shape: two RFC 7517 JWKs with `alg: "ES256"` set
|
|
64
|
+
*
|
|
65
|
+
* Throws {@link Oid4vpError} with `oid4vp.cert_generation_failed` on a
|
|
66
|
+
* malformed PEM or an unrecognised key type.
|
|
67
|
+
*/
|
|
68
|
+
export declare function signingCertToJwks(cert: SigningCert): Promise<{
|
|
69
|
+
publicJwk: JsonWebKey;
|
|
70
|
+
privateJwk: JsonWebKey;
|
|
71
|
+
}>;
|
|
72
|
+
//# sourceMappingURL=cert.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cert.d.ts","sourceRoot":"","sources":["../src/cert.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAMH,OAAO,kBAAkB,CAAC;AAI1B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAe,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAc3D,MAAM,WAAW,0BAA0B;IACzC;;wDAEoD;IACpD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB;;iDAE6C;IAC7C,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC;sBACkB;IAClB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,uEAAuE;IACvE,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,qCAAqC;IACrC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,0BAA0B,GAChC,OAAO,CAAC,WAAW,CAAC,CAyFtB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,WAAW,GAChB,OAAO,CAAC;IAAE,SAAS,EAAE,UAAU,CAAC;IAAC,UAAU,EAAE,UAAU,CAAA;CAAE,CAAC,CAmC5D"}
|
package/dist/cert.js
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* X.509 self-signed cert generation for OID4VP verifiers.
|
|
3
|
+
*
|
|
4
|
+
* The EU reference wallet's `X509SanDns` client_id_prefix accepts
|
|
5
|
+
* Authorization Requests signed by a cert whose Subject Alternative Name
|
|
6
|
+
* matches the verifier's hostname. For local dev, mock environments, and
|
|
7
|
+
* pinned-trust deployments we generate a fresh ES256 keypair + a leaf
|
|
8
|
+
* cert with the right SAN. Production deployments swap this for an
|
|
9
|
+
* externally issued cert (ACME / corporate CA) — the {@link SigningCert}
|
|
10
|
+
* shape is the same, only the origin differs.
|
|
11
|
+
*
|
|
12
|
+
* The cert+key pair is consumed by:
|
|
13
|
+
* 1. `signAuthorizationRequest` — signs the JAR JWS, embedding the
|
|
14
|
+
* cert in the `x5c` header.
|
|
15
|
+
* 2. The wallet's TLS / trust-anchor store — it must trust this cert
|
|
16
|
+
* out-of-band (in production: via a CA the wallet already trusts;
|
|
17
|
+
* for emulators: via patching the wallet's trust list).
|
|
18
|
+
*/
|
|
19
|
+
// `@peculiar/x509@2.x` uses tsyringe for dependency injection, which in
|
|
20
|
+
// turn requires the Reflect.metadata polyfill at module load time. We
|
|
21
|
+
// import it here so consumers don't have to. The package.json marks
|
|
22
|
+
// `dist/cert.js` as a side-effecting module so bundlers don't drop it.
|
|
23
|
+
import "reflect-metadata";
|
|
24
|
+
import * as x509 from "@peculiar/x509";
|
|
25
|
+
import { webcrypto } from "node:crypto";
|
|
26
|
+
import { exportJWK, importPKCS8, importX509 } from "jose";
|
|
27
|
+
import { Oid4vpError } from "./types.js";
|
|
28
|
+
// @peculiar/x509 needs a Web Crypto provider — Node's webcrypto suffices.
|
|
29
|
+
// Safe to call multiple times; no-op after first.
|
|
30
|
+
let cryptoProviderSet = false;
|
|
31
|
+
function ensureCryptoProvider() {
|
|
32
|
+
if (cryptoProviderSet)
|
|
33
|
+
return;
|
|
34
|
+
// @peculiar/x509's CryptoProvider type wants a DOM `Crypto`. Node's
|
|
35
|
+
// webcrypto is API-compatible — the cast is just to satisfy TS.
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
37
|
+
x509.cryptoProvider.set(webcrypto);
|
|
38
|
+
cryptoProviderSet = true;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Generate an ES256 keypair + self-signed X.509 cert with the given
|
|
42
|
+
* SAN-DNS entries.
|
|
43
|
+
*
|
|
44
|
+
* The cert carries the standard verifier-identity extensions: serverAuth
|
|
45
|
+
* + clientAuth EKUs (so wallets that look for a TLS-style cert accept
|
|
46
|
+
* it), digitalSignature + keyEncipherment KU flags, and CA:false.
|
|
47
|
+
*
|
|
48
|
+
* Returns a {@link SigningCert} ready to feed into
|
|
49
|
+
* {@link signAuthorizationRequest}.
|
|
50
|
+
*
|
|
51
|
+
* @throws {@link Oid4vpError} with `oid4vp.cert_generation_failed` if
|
|
52
|
+
* the underlying crypto / X.509 generation fails.
|
|
53
|
+
*/
|
|
54
|
+
export async function generateSigningCert(input) {
|
|
55
|
+
if (typeof input.sanDns !== "string" || input.sanDns.length === 0) {
|
|
56
|
+
throw new Oid4vpError("oid4vp.cert_generation_failed", "generateSigningCert: sanDns is required");
|
|
57
|
+
}
|
|
58
|
+
ensureCryptoProvider();
|
|
59
|
+
const validDays = input.validDays ?? 365;
|
|
60
|
+
const notBefore = new Date();
|
|
61
|
+
const notAfter = new Date(notBefore.getTime() + validDays * 24 * 60 * 60 * 1000);
|
|
62
|
+
// ES256 — the algorithm the EU wallet uses for KB-JWT and what most
|
|
63
|
+
// OID4VP implementations default to.
|
|
64
|
+
const algorithm = { name: "ECDSA", namedCurve: "P-256" };
|
|
65
|
+
const keys = await webcrypto.subtle.generateKey(algorithm, true, [
|
|
66
|
+
"sign",
|
|
67
|
+
"verify",
|
|
68
|
+
]);
|
|
69
|
+
const subjectName = buildSubjectDn({
|
|
70
|
+
commonName: input.commonName ?? input.sanDns,
|
|
71
|
+
organizationName: input.organizationName ?? "Gramota Verifier",
|
|
72
|
+
});
|
|
73
|
+
let cert;
|
|
74
|
+
try {
|
|
75
|
+
cert = await x509.X509CertificateGenerator.createSelfSigned({
|
|
76
|
+
serialNumber: randomSerial(),
|
|
77
|
+
name: subjectName,
|
|
78
|
+
notBefore,
|
|
79
|
+
notAfter,
|
|
80
|
+
signingAlgorithm: { name: "ECDSA", hash: "SHA-256" },
|
|
81
|
+
keys,
|
|
82
|
+
extensions: [
|
|
83
|
+
// Subject Alternative Name — what the wallet validates against
|
|
84
|
+
// `client_id` (OID4VP `x509_san_dns:<host>`) and what TLS clients
|
|
85
|
+
// match the host header against.
|
|
86
|
+
new x509.SubjectAlternativeNameExtension([
|
|
87
|
+
{ type: "dns", value: input.sanDns },
|
|
88
|
+
...(input.extraSanDns ?? []).map((value) => ({
|
|
89
|
+
type: "dns",
|
|
90
|
+
value,
|
|
91
|
+
})),
|
|
92
|
+
]),
|
|
93
|
+
// Standard verifier-identity usage flags.
|
|
94
|
+
new x509.KeyUsagesExtension(x509.KeyUsageFlags.digitalSignature | x509.KeyUsageFlags.keyEncipherment),
|
|
95
|
+
new x509.ExtendedKeyUsageExtension([
|
|
96
|
+
// serverAuth — wallets that expect a TLS-style cert accept this
|
|
97
|
+
"1.3.6.1.5.5.7.3.1",
|
|
98
|
+
// clientAuth — used by some wallets to bind the verifier identity
|
|
99
|
+
"1.3.6.1.5.5.7.3.2",
|
|
100
|
+
]),
|
|
101
|
+
// Leaf cert, not a CA.
|
|
102
|
+
new x509.BasicConstraintsExtension(false),
|
|
103
|
+
],
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
throw new Oid4vpError("oid4vp.cert_generation_failed", `generateSigningCert: X.509 generation failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
108
|
+
}
|
|
109
|
+
// Export private key as PKCS#8 PEM
|
|
110
|
+
const pkcs8 = await webcrypto.subtle.exportKey("pkcs8", keys.privateKey);
|
|
111
|
+
const privateKeyPem = derToPem(new Uint8Array(pkcs8), "PRIVATE KEY");
|
|
112
|
+
// Cert PEM
|
|
113
|
+
const certificatePem = cert.toString("pem");
|
|
114
|
+
// x5c header value: array of base64-encoded DER certs (no PEM wrappers).
|
|
115
|
+
const certDer = new Uint8Array(cert.rawData);
|
|
116
|
+
const x5c = [bytesToBase64(certDer)];
|
|
117
|
+
return {
|
|
118
|
+
privateKeyPem,
|
|
119
|
+
certificatePem,
|
|
120
|
+
x5c,
|
|
121
|
+
sanDns: input.sanDns,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Convert a {@link SigningCert} (PEM bundle) into the JWK pair that
|
|
126
|
+
* `@gramota/issuer` and other JOSE consumers expect.
|
|
127
|
+
*
|
|
128
|
+
* Unlike `generateSigningCert` this is a pure conversion — it doesn't
|
|
129
|
+
* touch crypto state or generate anything new. It just bridges the two
|
|
130
|
+
* representations of the same key material:
|
|
131
|
+
*
|
|
132
|
+
* - Input shape: PKCS#8 PEM (private) + X.509 PEM (public-via-cert)
|
|
133
|
+
* - Output shape: two RFC 7517 JWKs with `alg: "ES256"` set
|
|
134
|
+
*
|
|
135
|
+
* Throws {@link Oid4vpError} with `oid4vp.cert_generation_failed` on a
|
|
136
|
+
* malformed PEM or an unrecognised key type.
|
|
137
|
+
*/
|
|
138
|
+
export async function signingCertToJwks(cert) {
|
|
139
|
+
if (cert === null ||
|
|
140
|
+
typeof cert !== "object" ||
|
|
141
|
+
typeof cert.privateKeyPem !== "string" ||
|
|
142
|
+
typeof cert.certificatePem !== "string") {
|
|
143
|
+
throw new Oid4vpError("oid4vp.cert_generation_failed", "signingCertToJwks: cert must include privateKeyPem and certificatePem");
|
|
144
|
+
}
|
|
145
|
+
let privateJwk;
|
|
146
|
+
let publicJwk;
|
|
147
|
+
try {
|
|
148
|
+
const priv = await importPKCS8(cert.privateKeyPem, "ES256");
|
|
149
|
+
const pub = await importX509(cert.certificatePem, "ES256");
|
|
150
|
+
privateJwk = (await exportJWK(priv));
|
|
151
|
+
publicJwk = (await exportJWK(pub));
|
|
152
|
+
}
|
|
153
|
+
catch (err) {
|
|
154
|
+
throw new Oid4vpError("oid4vp.cert_generation_failed", `signingCertToJwks: failed to import key material: ${err instanceof Error ? err.message : String(err)}`);
|
|
155
|
+
}
|
|
156
|
+
// Annotate with `alg` so downstream consumers (e.g. @gramota/issuer)
|
|
157
|
+
// can pick a JWS algorithm without re-deriving it.
|
|
158
|
+
privateJwk.alg = "ES256";
|
|
159
|
+
publicJwk.alg = "ES256";
|
|
160
|
+
return { publicJwk, privateJwk };
|
|
161
|
+
}
|
|
162
|
+
// ---------------------------------------------------------------------------
|
|
163
|
+
// helpers
|
|
164
|
+
// ---------------------------------------------------------------------------
|
|
165
|
+
function buildSubjectDn(parts) {
|
|
166
|
+
return `CN=${escapeDn(parts.commonName)}, O=${escapeDn(parts.organizationName)}`;
|
|
167
|
+
}
|
|
168
|
+
function escapeDn(value) {
|
|
169
|
+
// Minimal RFC 4514 escaping — commas, equals, plus.
|
|
170
|
+
return value.replace(/([,=+])/g, "\\$1");
|
|
171
|
+
}
|
|
172
|
+
function randomSerial() {
|
|
173
|
+
// 20-byte hex string — RFC 5280 §4.1.2.2 says serials must be positive
|
|
174
|
+
// integers up to 20 octets.
|
|
175
|
+
const bytes = new Uint8Array(20);
|
|
176
|
+
webcrypto.getRandomValues(bytes);
|
|
177
|
+
// Ensure leading byte isn't zero so it's a positive integer.
|
|
178
|
+
if (bytes[0] === 0)
|
|
179
|
+
bytes[0] = 1;
|
|
180
|
+
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
181
|
+
}
|
|
182
|
+
function derToPem(der, label) {
|
|
183
|
+
const b64 = bytesToBase64(der);
|
|
184
|
+
// 64-char wrap per RFC 7468.
|
|
185
|
+
const wrapped = b64.replace(/(.{64})/g, "$1\n");
|
|
186
|
+
return `-----BEGIN ${label}-----\n${wrapped}\n-----END ${label}-----\n`;
|
|
187
|
+
}
|
|
188
|
+
function bytesToBase64(bytes) {
|
|
189
|
+
return Buffer.from(bytes).toString("base64");
|
|
190
|
+
}
|
|
191
|
+
//# sourceMappingURL=cert.js.map
|
package/dist/cert.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cert.js","sourceRoot":"","sources":["../src/cert.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { buildAuthorizationRequestUrl, parseAuthorizationRequestUrl, parseAuthorizationRequestSearchParams, } from "./request.js";
|
|
2
2
|
export { buildAuthorizationResponseBody, parseAuthorizationResponseBody, parseAuthorizationResponseFromParams, } from "./response.js";
|
|
3
|
-
export {
|
|
3
|
+
export { generateSigningCert, signingCertToJwks, type GenerateSigningCertOptions, } from "./cert.js";
|
|
4
|
+
export { signAuthorizationRequest, type SignAuthorizationRequestOptions, } from "./jar.js";
|
|
5
|
+
export { Oid4vpError, type AuthorizationRequest, type AuthorizationResponse, type ClientIdScheme, type Oid4vpErrorCode, type ResponseMode, type SigningCert, } from "./types.js";
|
|
4
6
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,4BAA4B,EAC5B,4BAA4B,EAC5B,qCAAqC,GACtC,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,8BAA8B,EAC9B,8BAA8B,EAC9B,oCAAoC,GACrC,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,WAAW,EACX,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,YAAY,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,4BAA4B,EAC5B,4BAA4B,EAC5B,qCAAqC,GACtC,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,8BAA8B,EAC9B,8BAA8B,EAC9B,oCAAoC,GACrC,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,KAAK,0BAA0B,GAChC,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,wBAAwB,EACxB,KAAK,+BAA+B,GACrC,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,WAAW,EACX,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,WAAW,GACjB,MAAM,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { buildAuthorizationRequestUrl, parseAuthorizationRequestUrl, parseAuthorizationRequestSearchParams, } from "./request.js";
|
|
2
2
|
export { buildAuthorizationResponseBody, parseAuthorizationResponseBody, parseAuthorizationResponseFromParams, } from "./response.js";
|
|
3
|
+
export { generateSigningCert, signingCertToJwks, } from "./cert.js";
|
|
4
|
+
export { signAuthorizationRequest, } from "./jar.js";
|
|
3
5
|
export { Oid4vpError, } from "./types.js";
|
|
4
6
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,4BAA4B,EAC5B,4BAA4B,EAC5B,qCAAqC,GACtC,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,8BAA8B,EAC9B,8BAA8B,EAC9B,oCAAoC,GACrC,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,WAAW,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,4BAA4B,EAC5B,4BAA4B,EAC5B,qCAAqC,GACtC,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,8BAA8B,EAC9B,8BAA8B,EAC9B,oCAAoC,GACrC,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,mBAAmB,EACnB,iBAAiB,GAElB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,wBAAwB,GAEzB,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,WAAW,GAOZ,MAAM,YAAY,CAAC"}
|
package/dist/jar.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RFC 9101 JAR (JWT-Secured Authorization Request) signing for OID4VP.
|
|
3
|
+
*
|
|
4
|
+
* verifier ─── builds AuthorizationRequest object ──►
|
|
5
|
+
* ─── wraps as compact-serialised JWS ──►
|
|
6
|
+
* ─── sets x5c header = [leaf cert DER] ──►
|
|
7
|
+
* wallet
|
|
8
|
+
*
|
|
9
|
+
* The wallet:
|
|
10
|
+
* 1. Fetches the JAR (inline `request=<jwt>` or via `request_uri=<url>`)
|
|
11
|
+
* 2. Sees `application/oauth-authz-req+jwt`, parses as a JWS
|
|
12
|
+
* 3. Reads the `x5c` header → leaf cert
|
|
13
|
+
* 4. Validates: cert chain trusted? SAN matches `client_id` value?
|
|
14
|
+
* 5. Verifies the JWS signature with the leaf cert's public key
|
|
15
|
+
* 6. Decodes the payload as the AuthorizationRequest, processes it
|
|
16
|
+
*
|
|
17
|
+
* For the EUDIW HAIP profile + the EU reference wallet's demo build,
|
|
18
|
+
* `x509_san_dns` (with this signed JAR shape) is the only `client_id_prefix`
|
|
19
|
+
* the wallet accepts. Production verifiers MUST sign their requests.
|
|
20
|
+
*/
|
|
21
|
+
import { type AuthorizationRequest, type SigningCert } from "./types.js";
|
|
22
|
+
export interface SignAuthorizationRequestOptions {
|
|
23
|
+
/** The verifier's Authorization Request to sign. Must already include
|
|
24
|
+
* the verifier-side fields (`client_id`, `nonce`, `response_uri`, etc).
|
|
25
|
+
* The signer doesn't validate semantic content — only wire-encodes. */
|
|
26
|
+
readonly request: AuthorizationRequest;
|
|
27
|
+
/** The verifier's signing material — produced by `generateSigningCert`
|
|
28
|
+
* or supplied externally. */
|
|
29
|
+
readonly cert: SigningCert;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Sign an OID4VP Authorization Request as a compact-serialised JWS,
|
|
33
|
+
* formatted per RFC 9101 + OID4VP §5.10.
|
|
34
|
+
*
|
|
35
|
+
* The JWS header carries:
|
|
36
|
+
* - `alg: "ES256"` (the only algorithm the EU wallet currently accepts
|
|
37
|
+
* for x509_san_dns prefix; future iterations can lift this)
|
|
38
|
+
* - `typ: "oauth-authz-req+jwt"` per RFC 9101
|
|
39
|
+
* - `x5c: [<base64-DER leaf cert>]` so the wallet doesn't need a
|
|
40
|
+
* separate key-discovery step
|
|
41
|
+
*
|
|
42
|
+
* The JWS payload IS the request object — claims at the top level, not
|
|
43
|
+
* wrapped in some envelope (RFC 9101 §10.2). Caller serves the result
|
|
44
|
+
* with content type `application/oauth-authz-req+jwt`.
|
|
45
|
+
*
|
|
46
|
+
* @throws {@link Oid4vpError} with `oid4vp.jar_signing_failed` if the
|
|
47
|
+
* private key is malformed or signing fails.
|
|
48
|
+
*/
|
|
49
|
+
export declare function signAuthorizationRequest(options: SignAuthorizationRequestOptions): Promise<string>;
|
|
50
|
+
//# sourceMappingURL=jar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jar.d.ts","sourceRoot":"","sources":["../src/jar.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAGH,OAAO,EAEL,KAAK,oBAAoB,EACzB,KAAK,WAAW,EACjB,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,+BAA+B;IAC9C;;2EAEuE;IACvE,QAAQ,CAAC,OAAO,EAAE,oBAAoB,CAAC;IACvC;iCAC6B;IAC7B,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,+BAA+B,GACvC,OAAO,CAAC,MAAM,CAAC,CAqDjB"}
|
package/dist/jar.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RFC 9101 JAR (JWT-Secured Authorization Request) signing for OID4VP.
|
|
3
|
+
*
|
|
4
|
+
* verifier ─── builds AuthorizationRequest object ──►
|
|
5
|
+
* ─── wraps as compact-serialised JWS ──►
|
|
6
|
+
* ─── sets x5c header = [leaf cert DER] ──►
|
|
7
|
+
* wallet
|
|
8
|
+
*
|
|
9
|
+
* The wallet:
|
|
10
|
+
* 1. Fetches the JAR (inline `request=<jwt>` or via `request_uri=<url>`)
|
|
11
|
+
* 2. Sees `application/oauth-authz-req+jwt`, parses as a JWS
|
|
12
|
+
* 3. Reads the `x5c` header → leaf cert
|
|
13
|
+
* 4. Validates: cert chain trusted? SAN matches `client_id` value?
|
|
14
|
+
* 5. Verifies the JWS signature with the leaf cert's public key
|
|
15
|
+
* 6. Decodes the payload as the AuthorizationRequest, processes it
|
|
16
|
+
*
|
|
17
|
+
* For the EUDIW HAIP profile + the EU reference wallet's demo build,
|
|
18
|
+
* `x509_san_dns` (with this signed JAR shape) is the only `client_id_prefix`
|
|
19
|
+
* the wallet accepts. Production verifiers MUST sign their requests.
|
|
20
|
+
*/
|
|
21
|
+
import { SignJWT, importPKCS8 } from "jose";
|
|
22
|
+
import { Oid4vpError, } from "./types.js";
|
|
23
|
+
/**
|
|
24
|
+
* Sign an OID4VP Authorization Request as a compact-serialised JWS,
|
|
25
|
+
* formatted per RFC 9101 + OID4VP §5.10.
|
|
26
|
+
*
|
|
27
|
+
* The JWS header carries:
|
|
28
|
+
* - `alg: "ES256"` (the only algorithm the EU wallet currently accepts
|
|
29
|
+
* for x509_san_dns prefix; future iterations can lift this)
|
|
30
|
+
* - `typ: "oauth-authz-req+jwt"` per RFC 9101
|
|
31
|
+
* - `x5c: [<base64-DER leaf cert>]` so the wallet doesn't need a
|
|
32
|
+
* separate key-discovery step
|
|
33
|
+
*
|
|
34
|
+
* The JWS payload IS the request object — claims at the top level, not
|
|
35
|
+
* wrapped in some envelope (RFC 9101 §10.2). Caller serves the result
|
|
36
|
+
* with content type `application/oauth-authz-req+jwt`.
|
|
37
|
+
*
|
|
38
|
+
* @throws {@link Oid4vpError} with `oid4vp.jar_signing_failed` if the
|
|
39
|
+
* private key is malformed or signing fails.
|
|
40
|
+
*/
|
|
41
|
+
export async function signAuthorizationRequest(options) {
|
|
42
|
+
if (options.cert === null ||
|
|
43
|
+
typeof options.cert !== "object" ||
|
|
44
|
+
typeof options.cert.privateKeyPem !== "string") {
|
|
45
|
+
throw new Oid4vpError("oid4vp.jar_signing_failed", "signAuthorizationRequest: cert.privateKeyPem is required");
|
|
46
|
+
}
|
|
47
|
+
if (!Array.isArray(options.cert.x5c) || options.cert.x5c.length === 0) {
|
|
48
|
+
throw new Oid4vpError("oid4vp.jar_signing_failed", "signAuthorizationRequest: cert.x5c must be a non-empty array");
|
|
49
|
+
}
|
|
50
|
+
let privateKey;
|
|
51
|
+
try {
|
|
52
|
+
privateKey = await importPKCS8(options.cert.privateKeyPem, "ES256");
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
throw new Oid4vpError("oid4vp.jar_signing_failed", `signAuthorizationRequest: failed to import private key: ${err instanceof Error ? err.message : String(err)}`);
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
// Spread into a fresh mutable object — `jose.SignJWT` wants
|
|
59
|
+
// `JWTPayload` (mutable, index-signature) while `AuthorizationRequest`
|
|
60
|
+
// is strict-readonly. The spread copies own enumerable props and
|
|
61
|
+
// produces a value of structurally-compatible type without an
|
|
62
|
+
// `as unknown as` double-cast.
|
|
63
|
+
const payload = { ...options.request };
|
|
64
|
+
const jwt = await new SignJWT(payload)
|
|
65
|
+
.setProtectedHeader({
|
|
66
|
+
alg: "ES256",
|
|
67
|
+
typ: "oauth-authz-req+jwt",
|
|
68
|
+
x5c: [...options.cert.x5c],
|
|
69
|
+
})
|
|
70
|
+
.sign(privateKey);
|
|
71
|
+
return jwt;
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
throw new Oid4vpError("oid4vp.jar_signing_failed", `signAuthorizationRequest: JWS signing failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=jar.js.map
|
package/dist/jar.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jar.js","sourceRoot":"","sources":["../src/jar.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,MAAM,CAAC;AAC5C,OAAO,EACL,WAAW,GAGZ,MAAM,YAAY,CAAC;AAYpB;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAC5C,OAAwC;IAExC,IACE,OAAO,CAAC,IAAI,KAAK,IAAI;QACrB,OAAO,OAAO,CAAC,IAAI,KAAK,QAAQ;QAChC,OAAO,OAAO,CAAC,IAAI,CAAC,aAAa,KAAK,QAAQ,EAC9C,CAAC;QACD,MAAM,IAAI,WAAW,CACnB,2BAA2B,EAC3B,0DAA0D,CAC3D,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtE,MAAM,IAAI,WAAW,CACnB,2BAA2B,EAC3B,8DAA8D,CAC/D,CAAC;IACJ,CAAC;IAED,IAAI,UAAU,CAAC;IACf,IAAI,CAAC;QACH,UAAU,GAAG,MAAM,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;IACtE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,WAAW,CACnB,2BAA2B,EAC3B,2DACE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CACjD,EAAE,CACH,CAAC;IACJ,CAAC;IAED,IAAI,CAAC;QACH,4DAA4D;QAC5D,uEAAuE;QACvE,iEAAiE;QACjE,8DAA8D;QAC9D,+BAA+B;QAC/B,MAAM,OAAO,GAA4B,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;QAChE,MAAM,GAAG,GAAG,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC;aACnC,kBAAkB,CAAC;YAClB,GAAG,EAAE,OAAO;YACZ,GAAG,EAAE,qBAAqB;YAC1B,GAAG,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC;SAC3B,CAAC;aACD,IAAI,CAAC,UAAU,CAAC,CAAC;QACpB,OAAO,GAAG,CAAC;IACb,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,WAAW,CACnB,2BAA2B,EAC3B,iDACE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CACjD,EAAE,CACH,CAAC;IACJ,CAAC;AACH,CAAC"}
|
package/dist/request.d.ts
CHANGED
|
@@ -3,16 +3,58 @@ import { type AuthorizationRequest } from "./types.js";
|
|
|
3
3
|
* Serialise an Authorization Request to a URL with query parameters per
|
|
4
4
|
* OID4VP §5.1 (parameter encoding rules).
|
|
5
5
|
*
|
|
6
|
-
* The base URL is typically `
|
|
7
|
-
*
|
|
8
|
-
* the query string.
|
|
6
|
+
* The base URL is typically a custom scheme like `openid4vp://` for the
|
|
7
|
+
* native wallet handoff, or `https://wallet.example.com/authorize` for
|
|
8
|
+
* web flows — both work, the function just appends the query string.
|
|
9
|
+
*
|
|
10
|
+
* Object-valued parameters (`presentation_definition`, `dcql_query`,
|
|
11
|
+
* `client_metadata`) are JSON-encoded into the query string per §5.1.
|
|
12
|
+
*
|
|
13
|
+
* For HAIP / EUDIW-compliant verifiers, this output is typically wrapped
|
|
14
|
+
* in a signed JAR (RFC 9101) via {@link signAuthorizationRequest} and
|
|
15
|
+
* delivered as `request=<jwt>` rather than as raw query params.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* const url = buildAuthorizationRequestUrl("openid4vp://", {
|
|
20
|
+
* response_type: "vp_token",
|
|
21
|
+
* client_id: "x509_san_dns:verifier.example",
|
|
22
|
+
* nonce: "n-12345",
|
|
23
|
+
* response_mode: "direct_post",
|
|
24
|
+
* response_uri: "https://verifier.example/oid4vp/response",
|
|
25
|
+
* dcql_query: { credentials: [{ id: "pid", format: "dc+sd-jwt", meta: {...} }] },
|
|
26
|
+
* });
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* @throws {@link Oid4vpError} `oid4vp.required_field_missing`,
|
|
30
|
+
* `oid4vp.unsupported_response_type`, `oid4vp.mutually_exclusive_fields`,
|
|
31
|
+
* `oid4vp.response_uri_required`, or `oid4vp.invalid_value_type`.
|
|
9
32
|
*/
|
|
10
33
|
export declare function buildAuthorizationRequestUrl(baseUrl: string, request: AuthorizationRequest): string;
|
|
11
|
-
/**
|
|
34
|
+
/**
|
|
35
|
+
* Parse an OID4VP Authorization Request from a URL string.
|
|
36
|
+
*
|
|
37
|
+
* Inverse of {@link buildAuthorizationRequestUrl}. Validates required
|
|
38
|
+
* fields and mutually-exclusive flag combinations; does NOT verify a
|
|
39
|
+
* signed JAR (use the wallet's JWS verification step for that, then
|
|
40
|
+
* call this function on the JAR's payload claims).
|
|
41
|
+
*
|
|
42
|
+
* @throws {@link Oid4vpError} with `oid4vp.invalid_url`,
|
|
43
|
+
* `oid4vp.required_field_missing`, or other field-validation codes.
|
|
44
|
+
*/
|
|
12
45
|
export declare function parseAuthorizationRequestUrl(rawUrl: string): AuthorizationRequest;
|
|
13
46
|
/**
|
|
14
|
-
* Parse
|
|
15
|
-
*
|
|
47
|
+
* Parse an OID4VP Authorization Request from URL query parameters.
|
|
48
|
+
*
|
|
49
|
+
* Useful when the verifier already has parsed query params from a web
|
|
50
|
+
* framework (`req.query` from Express, Hono, Fastify, ...). Accepts
|
|
51
|
+
* either a `URLSearchParams` instance or a plain `Record<string, string>`.
|
|
52
|
+
*
|
|
53
|
+
* Object-valued params (`presentation_definition`, `dcql_query`,
|
|
54
|
+
* `client_metadata`) are JSON-decoded; everything else is left as-is.
|
|
55
|
+
*
|
|
56
|
+
* @throws {@link Oid4vpError} with `oid4vp.invalid_json` if a JSON-valued
|
|
57
|
+
* param is malformed, plus the standard field-validation codes.
|
|
16
58
|
*/
|
|
17
59
|
export declare function parseAuthorizationRequestSearchParams(params: URLSearchParams | Record<string, string>): AuthorizationRequest;
|
|
18
60
|
//# sourceMappingURL=request.d.ts.map
|
package/dist/request.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request.d.ts","sourceRoot":"","sources":["../src/request.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAYpE
|
|
1
|
+
{"version":3,"file":"request.d.ts","sourceRoot":"","sources":["../src/request.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAYpE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,oBAAoB,GAC5B,MAAM,CAkBR;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,MAAM,GACb,oBAAoB,CAWtB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,qCAAqC,CACnD,MAAM,EAAE,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC/C,oBAAoB,CA0CtB"}
|
package/dist/request.js
CHANGED
|
@@ -11,9 +11,32 @@ const JSON_FIELDS = new Set([
|
|
|
11
11
|
* Serialise an Authorization Request to a URL with query parameters per
|
|
12
12
|
* OID4VP §5.1 (parameter encoding rules).
|
|
13
13
|
*
|
|
14
|
-
* The base URL is typically `
|
|
15
|
-
*
|
|
16
|
-
* the query string.
|
|
14
|
+
* The base URL is typically a custom scheme like `openid4vp://` for the
|
|
15
|
+
* native wallet handoff, or `https://wallet.example.com/authorize` for
|
|
16
|
+
* web flows — both work, the function just appends the query string.
|
|
17
|
+
*
|
|
18
|
+
* Object-valued parameters (`presentation_definition`, `dcql_query`,
|
|
19
|
+
* `client_metadata`) are JSON-encoded into the query string per §5.1.
|
|
20
|
+
*
|
|
21
|
+
* For HAIP / EUDIW-compliant verifiers, this output is typically wrapped
|
|
22
|
+
* in a signed JAR (RFC 9101) via {@link signAuthorizationRequest} and
|
|
23
|
+
* delivered as `request=<jwt>` rather than as raw query params.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* const url = buildAuthorizationRequestUrl("openid4vp://", {
|
|
28
|
+
* response_type: "vp_token",
|
|
29
|
+
* client_id: "x509_san_dns:verifier.example",
|
|
30
|
+
* nonce: "n-12345",
|
|
31
|
+
* response_mode: "direct_post",
|
|
32
|
+
* response_uri: "https://verifier.example/oid4vp/response",
|
|
33
|
+
* dcql_query: { credentials: [{ id: "pid", format: "dc+sd-jwt", meta: {...} }] },
|
|
34
|
+
* });
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* @throws {@link Oid4vpError} `oid4vp.required_field_missing`,
|
|
38
|
+
* `oid4vp.unsupported_response_type`, `oid4vp.mutually_exclusive_fields`,
|
|
39
|
+
* `oid4vp.response_uri_required`, or `oid4vp.invalid_value_type`.
|
|
17
40
|
*/
|
|
18
41
|
export function buildAuthorizationRequestUrl(baseUrl, request) {
|
|
19
42
|
validateRequest(request);
|
|
@@ -33,7 +56,17 @@ export function buildAuthorizationRequestUrl(baseUrl, request) {
|
|
|
33
56
|
}
|
|
34
57
|
return url.toString();
|
|
35
58
|
}
|
|
36
|
-
/**
|
|
59
|
+
/**
|
|
60
|
+
* Parse an OID4VP Authorization Request from a URL string.
|
|
61
|
+
*
|
|
62
|
+
* Inverse of {@link buildAuthorizationRequestUrl}. Validates required
|
|
63
|
+
* fields and mutually-exclusive flag combinations; does NOT verify a
|
|
64
|
+
* signed JAR (use the wallet's JWS verification step for that, then
|
|
65
|
+
* call this function on the JAR's payload claims).
|
|
66
|
+
*
|
|
67
|
+
* @throws {@link Oid4vpError} with `oid4vp.invalid_url`,
|
|
68
|
+
* `oid4vp.required_field_missing`, or other field-validation codes.
|
|
69
|
+
*/
|
|
37
70
|
export function parseAuthorizationRequestUrl(rawUrl) {
|
|
38
71
|
let url;
|
|
39
72
|
try {
|
|
@@ -45,8 +78,17 @@ export function parseAuthorizationRequestUrl(rawUrl) {
|
|
|
45
78
|
return parseAuthorizationRequestSearchParams(url.searchParams);
|
|
46
79
|
}
|
|
47
80
|
/**
|
|
48
|
-
* Parse
|
|
49
|
-
*
|
|
81
|
+
* Parse an OID4VP Authorization Request from URL query parameters.
|
|
82
|
+
*
|
|
83
|
+
* Useful when the verifier already has parsed query params from a web
|
|
84
|
+
* framework (`req.query` from Express, Hono, Fastify, ...). Accepts
|
|
85
|
+
* either a `URLSearchParams` instance or a plain `Record<string, string>`.
|
|
86
|
+
*
|
|
87
|
+
* Object-valued params (`presentation_definition`, `dcql_query`,
|
|
88
|
+
* `client_metadata`) are JSON-decoded; everything else is left as-is.
|
|
89
|
+
*
|
|
90
|
+
* @throws {@link Oid4vpError} with `oid4vp.invalid_json` if a JSON-valued
|
|
91
|
+
* param is malformed, plus the standard field-validation codes.
|
|
50
92
|
*/
|
|
51
93
|
export function parseAuthorizationRequestSearchParams(params) {
|
|
52
94
|
const get = (k) => params instanceof URLSearchParams ? params.get(k) ?? undefined : params[k];
|
package/dist/request.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request.js","sourceRoot":"","sources":["../src/request.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAA6B,MAAM,YAAY,CAAC;AAEpE,qCAAqC;AACrC,MAAM,eAAe,GAAG,CAAC,eAAe,EAAE,WAAW,EAAE,OAAO,CAAU,CAAC;AAEzE,sEAAsE;AACtE,MAAM,WAAW,GAAG,IAAI,GAAG,CAAS;IAClC,yBAAyB;IACzB,YAAY;IACZ,iBAAiB;CAClB,CAAC,CAAC;AAEH
|
|
1
|
+
{"version":3,"file":"request.js","sourceRoot":"","sources":["../src/request.ts"],"names":[],"mappings":"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"}
|
package/dist/response.d.ts
CHANGED
|
@@ -3,9 +3,11 @@ import { type AuthorizationResponse } from "./types.js";
|
|
|
3
3
|
* Serialise an Authorization Response to a URL-encoded form body, suitable
|
|
4
4
|
* for `direct_post` (POST application/x-www-form-urlencoded). Per OID4VP §6:
|
|
5
5
|
*
|
|
6
|
-
* - `vp_token` is the raw presentation string,
|
|
7
|
-
* multiple credentials are presented
|
|
8
|
-
*
|
|
6
|
+
* - `vp_token` is the raw presentation string, a JSON-encoded array if
|
|
7
|
+
* multiple credentials are presented (PEX), or a JSON-encoded object
|
|
8
|
+
* keyed by DCQL credential id (DCQL response form).
|
|
9
|
+
* - `presentation_submission` is JSON-encoded — only included for PEX
|
|
10
|
+
* responses; DCQL responses omit it.
|
|
9
11
|
* - `state` (optional) and `iss` (optional) pass through as strings.
|
|
10
12
|
*/
|
|
11
13
|
export declare function buildAuthorizationResponseBody(response: AuthorizationResponse): string;
|
package/dist/response.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"response.d.ts","sourceRoot":"","sources":["../src/response.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,qBAAqB,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"response.d.ts","sourceRoot":"","sources":["../src/response.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAErE;;;;;;;;;;GAUG;AACH,wBAAgB,8BAA8B,CAC5C,QAAQ,EAAE,qBAAqB,GAC9B,MAAM,CA+BR;AAED,2EAA2E;AAC3E,wBAAgB,8BAA8B,CAC5C,OAAO,EAAE,MAAM,GACd,qBAAqB,CAavB;AAED;+BAC+B;AAC/B,wBAAgB,oCAAoC,CAClD,MAAM,EAAE,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC/C,qBAAqB,CAsGvB"}
|
package/dist/response.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { Oid4vpError } from "./types.js";
|
|
2
|
-
/** Required fields per OID4VP §6.1. */
|
|
3
|
-
const REQUIRED_FIELDS = ["vp_token", "presentation_submission"];
|
|
4
2
|
/**
|
|
5
3
|
* Serialise an Authorization Response to a URL-encoded form body, suitable
|
|
6
4
|
* for `direct_post` (POST application/x-www-form-urlencoded). Per OID4VP §6:
|
|
7
5
|
*
|
|
8
|
-
* - `vp_token` is the raw presentation string,
|
|
9
|
-
* multiple credentials are presented
|
|
10
|
-
*
|
|
6
|
+
* - `vp_token` is the raw presentation string, a JSON-encoded array if
|
|
7
|
+
* multiple credentials are presented (PEX), or a JSON-encoded object
|
|
8
|
+
* keyed by DCQL credential id (DCQL response form).
|
|
9
|
+
* - `presentation_submission` is JSON-encoded — only included for PEX
|
|
10
|
+
* responses; DCQL responses omit it.
|
|
11
11
|
* - `state` (optional) and `iss` (optional) pass through as strings.
|
|
12
12
|
*/
|
|
13
13
|
export function buildAuthorizationResponseBody(response) {
|
|
@@ -19,10 +19,17 @@ export function buildAuthorizationResponseBody(response) {
|
|
|
19
19
|
else if (typeof response.vp_token === "string") {
|
|
20
20
|
body.set("vp_token", response.vp_token);
|
|
21
21
|
}
|
|
22
|
+
else if (response.vp_token !== null &&
|
|
23
|
+
typeof response.vp_token === "object") {
|
|
24
|
+
// DCQL response — JSON object keyed by credential id.
|
|
25
|
+
body.set("vp_token", JSON.stringify(response.vp_token));
|
|
26
|
+
}
|
|
22
27
|
else {
|
|
23
|
-
throw new Oid4vpError("oid4vp.invalid_value_type", "vp_token must be a string
|
|
28
|
+
throw new Oid4vpError("oid4vp.invalid_value_type", "vp_token must be a string, array of strings, or DCQL credential map");
|
|
29
|
+
}
|
|
30
|
+
if (response.presentation_submission !== undefined) {
|
|
31
|
+
body.set("presentation_submission", JSON.stringify(response.presentation_submission));
|
|
24
32
|
}
|
|
25
|
-
body.set("presentation_submission", JSON.stringify(response.presentation_submission));
|
|
26
33
|
if (response.state !== undefined)
|
|
27
34
|
body.set("state", response.state);
|
|
28
35
|
if (response.iss !== undefined)
|
|
@@ -49,39 +56,74 @@ export function parseAuthorizationResponseFromParams(params) {
|
|
|
49
56
|
if (vpRaw === undefined) {
|
|
50
57
|
throw new Oid4vpError("oid4vp.required_field_missing", "Authorization Response is missing required parameter: vp_token");
|
|
51
58
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
submission = JSON.parse(submissionRaw);
|
|
58
|
-
}
|
|
59
|
-
catch (err) {
|
|
60
|
-
throw new Oid4vpError("oid4vp.invalid_json", `presentation_submission is not valid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
61
|
-
}
|
|
62
|
-
if (submission === null ||
|
|
63
|
-
typeof submission !== "object" ||
|
|
64
|
-
Array.isArray(submission)) {
|
|
65
|
-
throw new Oid4vpError("oid4vp.malformed_submission", "presentation_submission must be a JSON object (DIF Presentation Exchange v2)");
|
|
66
|
-
}
|
|
67
|
-
// vp_token may be a string OR a JSON-encoded array of strings.
|
|
59
|
+
// vp_token may be:
|
|
60
|
+
// - a single SD-JWT-VC string (PEX, single credential)
|
|
61
|
+
// - a JSON-encoded string[] (PEX, multiple credentials)
|
|
62
|
+
// - a JSON-encoded object keyed by DCQL credential id (DCQL response,
|
|
63
|
+
// OID4VP Final 1.0 — what production EU wallets send)
|
|
68
64
|
let vp_token = vpRaw;
|
|
69
|
-
|
|
65
|
+
const trimmed = vpRaw.trim();
|
|
66
|
+
if (trimmed.startsWith("[") || trimmed.startsWith("{")) {
|
|
70
67
|
try {
|
|
71
68
|
const parsed = JSON.parse(vpRaw);
|
|
72
|
-
if (Array.isArray(parsed) &&
|
|
73
|
-
parsed.every((p) => typeof p === "string")) {
|
|
69
|
+
if (Array.isArray(parsed) && parsed.every((p) => typeof p === "string")) {
|
|
74
70
|
vp_token = parsed;
|
|
75
71
|
}
|
|
72
|
+
else if (parsed !== null &&
|
|
73
|
+
typeof parsed === "object" &&
|
|
74
|
+
!Array.isArray(parsed)) {
|
|
75
|
+
// DCQL response form — keys are credential ids, values are
|
|
76
|
+
// (single-credential) strings or arrays.
|
|
77
|
+
const obj = parsed;
|
|
78
|
+
const flat = {};
|
|
79
|
+
for (const [id, val] of Object.entries(obj)) {
|
|
80
|
+
if (typeof val === "string") {
|
|
81
|
+
flat[id] = val;
|
|
82
|
+
}
|
|
83
|
+
else if (Array.isArray(val) &&
|
|
84
|
+
val.length === 1 &&
|
|
85
|
+
typeof val[0] === "string") {
|
|
86
|
+
// Some wallets wrap each credential in a single-element array
|
|
87
|
+
// (anticipating multi-instance presentations). Flatten for our
|
|
88
|
+
// SD-JWT-VC verifier which expects a single string per id.
|
|
89
|
+
flat[id] = val[0];
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
throw new Oid4vpError("oid4vp.invalid_value_type", `vp_token[${id}] must be a string or single-element array of strings`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
vp_token = flat;
|
|
96
|
+
}
|
|
76
97
|
}
|
|
77
|
-
catch {
|
|
78
|
-
|
|
98
|
+
catch (err) {
|
|
99
|
+
if (err instanceof Oid4vpError)
|
|
100
|
+
throw err;
|
|
101
|
+
// Not a JSON array/object — treat as a literal string.
|
|
79
102
|
}
|
|
80
103
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
104
|
+
// presentation_submission is required for PEX responses (string or
|
|
105
|
+
// string[] vp_token), optional for DCQL responses (object vp_token).
|
|
106
|
+
const isDcqlResponse = typeof vp_token === "object" && !Array.isArray(vp_token);
|
|
107
|
+
let submission;
|
|
108
|
+
if (submissionRaw !== undefined) {
|
|
109
|
+
let parsed;
|
|
110
|
+
try {
|
|
111
|
+
parsed = JSON.parse(submissionRaw);
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
throw new Oid4vpError("oid4vp.invalid_json", `presentation_submission is not valid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
115
|
+
}
|
|
116
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
117
|
+
throw new Oid4vpError("oid4vp.malformed_submission", "presentation_submission must be a JSON object (DIF Presentation Exchange v2)");
|
|
118
|
+
}
|
|
119
|
+
submission = parsed;
|
|
120
|
+
}
|
|
121
|
+
else if (!isDcqlResponse) {
|
|
122
|
+
throw new Oid4vpError("oid4vp.required_field_missing", "Authorization Response is missing required parameter: presentation_submission");
|
|
123
|
+
}
|
|
124
|
+
const result = { vp_token };
|
|
125
|
+
if (submission !== undefined)
|
|
126
|
+
result.presentation_submission = submission;
|
|
85
127
|
const state = get("state");
|
|
86
128
|
if (state !== undefined)
|
|
87
129
|
result.state = state;
|
|
@@ -92,15 +134,23 @@ export function parseAuthorizationResponseFromParams(params) {
|
|
|
92
134
|
return result;
|
|
93
135
|
}
|
|
94
136
|
function validateResponse(resp) {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
137
|
+
if (resp.vp_token === undefined) {
|
|
138
|
+
throw new Oid4vpError("oid4vp.required_field_missing", "Authorization Response is missing required parameter: vp_token");
|
|
139
|
+
}
|
|
140
|
+
const isString = typeof resp.vp_token === "string";
|
|
141
|
+
const isStringArray = Array.isArray(resp.vp_token) &&
|
|
142
|
+
resp.vp_token.every((v) => typeof v === "string");
|
|
143
|
+
const isDcqlMap = !Array.isArray(resp.vp_token) &&
|
|
144
|
+
resp.vp_token !== null &&
|
|
145
|
+
typeof resp.vp_token === "object" &&
|
|
146
|
+
Object.values(resp.vp_token).every((v) => typeof v === "string");
|
|
147
|
+
if (!isString && !isStringArray && !isDcqlMap) {
|
|
148
|
+
throw new Oid4vpError("oid4vp.invalid_value_type", "vp_token must be a string, array of strings, or DCQL credential map");
|
|
99
149
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
throw new Oid4vpError("oid4vp.
|
|
150
|
+
// PEX path requires presentation_submission; DCQL path doesn't.
|
|
151
|
+
if ((isString || isStringArray) &&
|
|
152
|
+
resp.presentation_submission === undefined) {
|
|
153
|
+
throw new Oid4vpError("oid4vp.required_field_missing", "Authorization Response is missing required parameter: presentation_submission");
|
|
104
154
|
}
|
|
105
155
|
}
|
|
106
156
|
//# sourceMappingURL=response.js.map
|
package/dist/response.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"response.js","sourceRoot":"","sources":["../src/response.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAA8B,MAAM,YAAY,CAAC;AAErE
|
|
1
|
+
{"version":3,"file":"response.js","sourceRoot":"","sources":["../src/response.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -47,19 +47,67 @@ export interface AuthorizationRequest {
|
|
|
47
47
|
* is willing to accept. */
|
|
48
48
|
scope?: string;
|
|
49
49
|
}
|
|
50
|
-
/** OID4VP §6 — Authorization Response from wallet to verifier.
|
|
50
|
+
/** OID4VP §6 — Authorization Response from wallet to verifier.
|
|
51
|
+
*
|
|
52
|
+
* The wire shape depends on which query language the request used:
|
|
53
|
+
*
|
|
54
|
+
* - **Presentation Exchange (DIF PEX)**: `vp_token` is a string or
|
|
55
|
+
* string[], paired with `presentation_submission` mapping descriptors
|
|
56
|
+
* to vp_token positions.
|
|
57
|
+
* - **DCQL** (OID4VP Final 1.0): `vp_token` is a JSON OBJECT keyed by
|
|
58
|
+
* the DCQL credential `id` (e.g. `{"pid": "<sd-jwt-vc>"}`). No
|
|
59
|
+
* `presentation_submission` is sent — the keys ARE the mapping.
|
|
60
|
+
*
|
|
61
|
+
* Verifiers should accept whichever shape the wallet sends; production EU
|
|
62
|
+
* wallets (eudi-lib-android-wallet-ui 0.26+) use DCQL exclusively.
|
|
63
|
+
*/
|
|
51
64
|
export interface AuthorizationResponse {
|
|
52
|
-
/** The presentation(s)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
65
|
+
/** The presentation(s).
|
|
66
|
+
*
|
|
67
|
+
* - String / string[] form (PEX response).
|
|
68
|
+
* - Object form (DCQL response) — keys are the DCQL credential ids
|
|
69
|
+
* and values are the credential strings.
|
|
70
|
+
*/
|
|
71
|
+
vp_token: string | readonly string[] | Readonly<Record<string, string>>;
|
|
72
|
+
/** DIF Presentation Submission mapping descriptors → vp_token positions.
|
|
73
|
+
* Required for PEX responses; absent for DCQL responses. */
|
|
74
|
+
presentation_submission?: Readonly<Record<string, unknown>>;
|
|
56
75
|
/** Echoes the verifier's `state` from the request. */
|
|
57
76
|
state?: string;
|
|
58
77
|
/** OID4VP §6.4 — the wallet's identifier (e.g. issuer URL). */
|
|
59
78
|
iss?: string;
|
|
60
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* X.509 signing material for an OID4VP verifier.
|
|
82
|
+
*
|
|
83
|
+
* Bundles together the four artefacts a verifier needs to produce signed
|
|
84
|
+
* Authorization Requests (RFC 9101 JAR) and prove its identity to wallets
|
|
85
|
+
* via the `x509_san_dns` client_id_prefix:
|
|
86
|
+
*
|
|
87
|
+
* - `privateKeyPem` — PKCS#8 private key, used to sign the JAR
|
|
88
|
+
* - `certificatePem` — leaf cert, served when the wallet asks for proof
|
|
89
|
+
* - `x5c` — base64 DER cert(s) embedded in the JWS `x5c` header
|
|
90
|
+
* - `sanDns` — the SAN-DNS hostname the wallet matches against `client_id`
|
|
91
|
+
*
|
|
92
|
+
* Generation: see `generateSigningCert` for self-signed certs (local dev,
|
|
93
|
+
* pinned-trust-store deployments). Production typically uses an externally
|
|
94
|
+
* issued cert (ACME, corporate CA) — same shape, different origin.
|
|
95
|
+
*/
|
|
96
|
+
export interface SigningCert {
|
|
97
|
+
/** PEM-encoded PKCS#8 private key. */
|
|
98
|
+
readonly privateKeyPem: string;
|
|
99
|
+
/** PEM-encoded leaf certificate. */
|
|
100
|
+
readonly certificatePem: string;
|
|
101
|
+
/** Base64-encoded DER certs in chain order, suitable for the JWS `x5c`
|
|
102
|
+
* header per RFC 7515 §4.1.6. Length 1 for self-signed leaves. */
|
|
103
|
+
readonly x5c: readonly string[];
|
|
104
|
+
/** DNS name in the cert's Subject Alternative Name. The wallet
|
|
105
|
+
* compares this against the OID4VP `client_id` value (with the
|
|
106
|
+
* `x509_san_dns:` prefix stripped). */
|
|
107
|
+
readonly sanDns: string;
|
|
108
|
+
}
|
|
61
109
|
/** Stable codes for `Oid4vpError`. */
|
|
62
|
-
export type Oid4vpErrorCode = "oid4vp.invalid_url" | "oid4vp.required_field_missing" | "oid4vp.unsupported_response_type" | "oid4vp.mutually_exclusive_fields" | "oid4vp.response_uri_required" | "oid4vp.invalid_json" | "oid4vp.invalid_value_type" | "oid4vp.malformed_body" | "oid4vp.malformed_submission";
|
|
110
|
+
export type Oid4vpErrorCode = "oid4vp.invalid_url" | "oid4vp.required_field_missing" | "oid4vp.unsupported_response_type" | "oid4vp.mutually_exclusive_fields" | "oid4vp.response_uri_required" | "oid4vp.invalid_json" | "oid4vp.invalid_value_type" | "oid4vp.malformed_body" | "oid4vp.malformed_submission" | "oid4vp.cert_generation_failed" | "oid4vp.jar_signing_failed";
|
|
63
111
|
export declare class Oid4vpError extends Error {
|
|
64
112
|
readonly name = "Oid4vpError";
|
|
65
113
|
readonly code: Oid4vpErrorCode;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,0DAA0D;AAC1D,MAAM,MAAM,cAAc,GACtB,gBAAgB,GAChB,cAAc,GACd,OAAO,GACP,KAAK,GACL,cAAc,GACd,cAAc,GACd,sBAAsB,GACtB,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;AAElB,oEAAoE;AACpE,MAAM,MAAM,YAAY,GACpB,aAAa,GACb,iBAAiB,GACjB,UAAU,GACV,OAAO,CAAC;AAEZ;;;;2DAI2D;AAC3D,MAAM,WAAW,oBAAoB;IACnC,wCAAwC;IACxC,aAAa,EAAE,UAAU,CAAC;IAC1B,0EAA0E;IAC1E,SAAS,EAAE,MAAM,CAAC;IAClB;uEACmE;IACnE,gBAAgB,CAAC,EAAE,cAAc,CAAC;IAClC,wEAAwE;IACxE,aAAa,CAAC,EAAE,YAAY,CAAC;IAC7B,mEAAmE;IACnE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,uDAAuD;IACvD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,qEAAqE;IACrE,KAAK,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;mCAC+B;IAC/B,uBAAuB,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC5D,gFAAgF;IAChF,2BAA2B,CAAC,EAAE,MAAM,CAAC;IACrC;4DACwD;IACxD,UAAU,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC/C,8CAA8C;IAC9C,eAAe,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACpD;+BAC2B;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,0DAA0D;AAC1D,MAAM,MAAM,cAAc,GACtB,gBAAgB,GAChB,cAAc,GACd,OAAO,GACP,KAAK,GACL,cAAc,GACd,cAAc,GACd,sBAAsB,GACtB,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;AAElB,oEAAoE;AACpE,MAAM,MAAM,YAAY,GACpB,aAAa,GACb,iBAAiB,GACjB,UAAU,GACV,OAAO,CAAC;AAEZ;;;;2DAI2D;AAC3D,MAAM,WAAW,oBAAoB;IACnC,wCAAwC;IACxC,aAAa,EAAE,UAAU,CAAC;IAC1B,0EAA0E;IAC1E,SAAS,EAAE,MAAM,CAAC;IAClB;uEACmE;IACnE,gBAAgB,CAAC,EAAE,cAAc,CAAC;IAClC,wEAAwE;IACxE,aAAa,CAAC,EAAE,YAAY,CAAC;IAC7B,mEAAmE;IACnE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,uDAAuD;IACvD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,qEAAqE;IACrE,KAAK,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;mCAC+B;IAC/B,uBAAuB,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC5D,gFAAgF;IAChF,2BAA2B,CAAC,EAAE,MAAM,CAAC;IACrC;4DACwD;IACxD,UAAU,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC/C,8CAA8C;IAC9C,eAAe,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACpD;+BAC2B;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,qBAAqB;IACpC;;;;;OAKG;IACH,QAAQ,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACxE;gEAC4D;IAC5D,uBAAuB,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC5D,sDAAsD;IACtD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+DAA+D;IAC/D,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,WAAW;IAC1B,sCAAsC;IACtC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,oCAAoC;IACpC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC;sEACkE;IAClE,QAAQ,CAAC,GAAG,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC;;2CAEuC;IACvC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,sCAAsC;AACtC,MAAM,MAAM,eAAe,GACvB,oBAAoB,GACpB,+BAA+B,GAC/B,kCAAkC,GAClC,kCAAkC,GAClC,8BAA8B,GAC9B,qBAAqB,GACrB,2BAA2B,GAC3B,uBAAuB,GACvB,6BAA6B,GAC7B,+BAA+B,GAC/B,2BAA2B,CAAC;AAEhC,qBAAa,WAAY,SAAQ,KAAK;IACpC,SAAkB,IAAI,iBAAiB;IACvC,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;gBAG7B,IAAI,EAAE,eAAe,EACrB,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE;CAQhC"}
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAqIH,MAAM,OAAO,WAAY,SAAQ,KAAK;IAClB,IAAI,GAAG,aAAa,CAAC;IAC9B,IAAI,CAAkB;IAE/B,YACE,IAAqB,EACrB,OAAe,EACf,OAA6B;QAE7B,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,OAAO,EAAE,KAAK,KAAK,SAAS,EAAE,CAAC;YAChC,IAA4B,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;QACtD,CAAC;IACH,CAAC;CACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gramota/oid4vp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "OpenID for Verifiable Presentations transport — authorization request/response wire format for EUDIW.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -15,6 +15,15 @@
|
|
|
15
15
|
"files": [
|
|
16
16
|
"dist"
|
|
17
17
|
],
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@peculiar/x509": "^2.0.0",
|
|
20
|
+
"jose": "^5.9.6",
|
|
21
|
+
"reflect-metadata": "^0.2.0",
|
|
22
|
+
"@gramota/jose": "0.2.0"
|
|
23
|
+
},
|
|
24
|
+
"sideEffects": [
|
|
25
|
+
"./dist/cert.js"
|
|
26
|
+
],
|
|
18
27
|
"author": "Petromil Pavlov <petromilpavlov@gmail.com>",
|
|
19
28
|
"homepage": "https://github.com/gramota-org/gramota#readme",
|
|
20
29
|
"bugs": {
|
|
@@ -23,7 +32,6 @@
|
|
|
23
32
|
"engines": {
|
|
24
33
|
"node": ">=20"
|
|
25
34
|
},
|
|
26
|
-
"sideEffects": false,
|
|
27
35
|
"publishConfig": {
|
|
28
36
|
"access": "public",
|
|
29
37
|
"registry": "https://registry.npmjs.org/"
|