@openeudi/openid4vp 0.6.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/dist/index.d.cts CHANGED
@@ -15,9 +15,49 @@ interface AuthorizationRequest {
15
15
  nonce: string;
16
16
  state: string;
17
17
  }
18
+ interface SignedAuthorizationRequestInput {
19
+ hostname: string;
20
+ requestUri: string;
21
+ responseUri: string;
22
+ nonce: string;
23
+ state?: string;
24
+ responseMode?: 'direct_post' | 'direct_post.jwt';
25
+ signer: CryptoKeyPair;
26
+ signingAlgorithm?: 'ES256' | 'ES384' | 'RS256';
27
+ certificateChain: Uint8Array[];
28
+ encryptionKey?: {
29
+ publicJwk: JsonWebKey;
30
+ supportedEncValues?: string[];
31
+ };
32
+ vpFormatsSupported: Record<string, unknown>;
33
+ }
34
+ interface SignedAuthorizationRequest {
35
+ uri: string;
36
+ requestObject: string;
37
+ dcqlQuery: DcqlQuery;
38
+ nonce: string;
39
+ state: string;
40
+ }
41
+ interface AuthorizationResponse {
42
+ vp_token: Record<string, Array<string | object>>;
43
+ state?: string;
44
+ [key: string]: unknown;
45
+ }
18
46
 
19
47
  declare function createAuthorizationRequest(input: AuthorizationRequestInput, query: DcqlQuery): AuthorizationRequest;
20
48
 
49
+ /**
50
+ * Build a signed authorization request (JAR) per OpenID4VP 1.0 §5.10 / RFC 9101.
51
+ *
52
+ * Validates that the signing key is bound to the leaf certificate and that the
53
+ * leaf cert's SAN DNSName equals the declared hostname. Emits a short URI
54
+ * carrying only `client_id` + `request_uri`, plus the JWS string the caller
55
+ * must host at `requestUri` with Content-Type `application/oauth-authz-req+jwt`.
56
+ *
57
+ * Client Identifier Prefix is always `x509_san_dns` — other prefixes deferred.
58
+ */
59
+ declare function createSignedAuthorizationRequest(input: SignedAuthorizationRequestInput, query: DcqlQuery): Promise<SignedAuthorizationRequest>;
60
+
21
61
  interface HaipQueryInput {
22
62
  credentialId: string;
23
63
  format: 'dc+sd-jwt' | 'mso_mdoc';
@@ -293,6 +333,12 @@ interface VerifyResult {
293
333
  submission: DcqlSubmission | null;
294
334
  valid: boolean;
295
335
  }
336
+ interface EncryptedResponse {
337
+ response: string;
338
+ }
339
+ type VerifyAuthorizationResponseOptions = VerifyOptions & {
340
+ decryptionKey?: CryptoKey;
341
+ };
296
342
 
297
343
  /**
298
344
  * Parses a VP token, then matches it against a DCQL query.
@@ -306,6 +352,34 @@ interface VerifyResult {
306
352
  * @param options parse + verify options (nonce, trusted issuers, etc.)
307
353
  */
308
354
  declare function verifyPresentation(vpToken: unknown, query: DcqlQuery, options: VerifyOptions): Promise<VerifyResult>;
355
+ /**
356
+ * Verify an OpenID4VP 1.0 §8.1 Authorization Response envelope.
357
+ *
358
+ * Accepts either the unencrypted envelope (object-keyed `vp_token`) or a
359
+ * JWE-wrapped envelope `{ response: '<JWE>' }` for response_mode =
360
+ * direct_post.jwt. When encrypted, decrypts with `options.decryptionKey`
361
+ * first. For this release only single-credential single-presentation is
362
+ * supported — multi-credential envelopes throw
363
+ * {@link MultipleCredentialsNotSupportedError}. Otherwise delegates the
364
+ * extracted single presentation to the existing {@link verifyPresentation}.
365
+ *
366
+ * Callers MUST compare the envelope's `state` against the value they issued
367
+ * themselves — library is stateless and does not track state.
368
+ */
369
+ declare function verifyAuthorizationResponse(envelope: AuthorizationResponse | EncryptedResponse, query: DcqlQuery, options: VerifyAuthorizationResponseOptions): Promise<VerifyResult>;
370
+
371
+ /**
372
+ * Decrypt a JWE-wrapped OpenID4VP Authorization Response (response_mode =
373
+ * direct_post.jwt). Returns the inner §8.1 envelope.
374
+ *
375
+ * Supported JWE algorithms (others throw UnsupportedJweError):
376
+ * - alg: ECDH-ES
377
+ * - enc: A128GCM, A256GCM (HAIP requires both)
378
+ *
379
+ * Cryptographic failures (wrong key, tampered ciphertext) throw
380
+ * DecryptionFailedError.
381
+ */
382
+ declare function decryptAuthorizationResponse(jwe: string, privateKey: CryptoKey): Promise<AuthorizationResponse>;
309
383
 
310
384
  /**
311
385
  * SD-JWT VC credential parser.
@@ -416,6 +490,33 @@ declare class LotlFetchError extends OpenID4VPError {
416
490
  declare class LotlSignatureError extends OpenID4VPError {
417
491
  readonly code: "lotl_signature_invalid";
418
492
  }
493
+ type SignedRequestBuildErrorCode = 'empty_cert_chain' | 'hostname_cert_mismatch' | 'signing_key_cert_mismatch' | 'missing_encryption_jwk' | 'missing_encryption_alg' | 'missing_vp_formats' | 'unsupported_signing_alg' | 'empty_supported_enc_values';
494
+ declare class SignedRequestBuildError extends OpenID4VPError {
495
+ readonly code: SignedRequestBuildErrorCode;
496
+ constructor(code: SignedRequestBuildErrorCode, message: string);
497
+ }
498
+ declare class UnsupportedJweError extends OpenID4VPError {
499
+ readonly code: "unsupported_jwe";
500
+ readonly alg: string;
501
+ readonly enc: string;
502
+ constructor(alg: string, enc: string);
503
+ }
504
+ declare class DecryptionFailedError extends OpenID4VPError {
505
+ readonly code: "decryption_failed";
506
+ constructor(message?: string, options?: {
507
+ cause?: Error;
508
+ });
509
+ }
510
+ declare class MissingDecryptionKeyError extends OpenID4VPError {
511
+ readonly code: "missing_decryption_key";
512
+ constructor(message?: string);
513
+ }
514
+ declare class MultipleCredentialsNotSupportedError extends OpenID4VPError {
515
+ readonly code: "multi_credential_unsupported";
516
+ readonly entryCount: number;
517
+ readonly presentationCount: number;
518
+ constructor(entryCount: number, presentationCount: number);
519
+ }
419
520
 
420
521
  interface LotlTrustStoreOptions {
421
522
  /** Override the bundled signing anchors. Empty is rejected. */
@@ -454,6 +555,6 @@ declare class LotlTrustStore implements TrustStore {
454
555
  private refresh;
455
556
  }
456
557
 
457
- declare const VERSION = "0.5.0";
558
+ declare const VERSION = "0.7.0";
458
559
 
459
- export { type AuthorizationRequest, type AuthorizationRequestInput, type Cache, CertificateChainError, type ChainErrorReason, CompositeTrustStore, type CredentialClaims, type CredentialFormat, ExpiredCredentialError, type Fetcher, HAIP_DOCTYPE_NAMESPACES, type HaipQueryInput, type HaipValidationCode, HaipValidationError, type ICredentialParser, InMemoryCache, InvalidSignatureError, type IssuerInfo, type LotlAnchorMetadata, LotlFetchError, LotlSignatureError, LotlTrustStore, type LotlTrustStoreOptions, MalformedCredentialError, MdocParser, NonceValidationError, OpenID4VPError, type ParseOptions, type PresentationResult, RevocationCheckFailedError, RevokedCertificateError, SdJwtParser, StaticTrustStore, type TrustAnchor, TrustAnchorNotFoundError, type TrustStore, type TrustStoreHint, type TrustStoreInput, UnsupportedFormatError, VERSION, type VerifyOptions, type VerifyResult, buildHaipQuery, createAuthorizationRequest, isHaipQuery, parsePresentation, validateHaipQuery, verifyPresentation };
560
+ export { type AuthorizationRequest, type AuthorizationRequestInput, type AuthorizationResponse, type Cache, CertificateChainError, type ChainErrorReason, CompositeTrustStore, type CredentialClaims, type CredentialFormat, DecryptionFailedError, type EncryptedResponse, ExpiredCredentialError, type Fetcher, HAIP_DOCTYPE_NAMESPACES, type HaipQueryInput, type HaipValidationCode, HaipValidationError, type ICredentialParser, InMemoryCache, InvalidSignatureError, type IssuerInfo, type LotlAnchorMetadata, LotlFetchError, LotlSignatureError, LotlTrustStore, type LotlTrustStoreOptions, MalformedCredentialError, MdocParser, MissingDecryptionKeyError, MultipleCredentialsNotSupportedError, NonceValidationError, OpenID4VPError, type ParseOptions, type PresentationResult, RevocationCheckFailedError, RevokedCertificateError, SdJwtParser, type SignedAuthorizationRequest, type SignedAuthorizationRequestInput, SignedRequestBuildError, type SignedRequestBuildErrorCode, StaticTrustStore, type TrustAnchor, TrustAnchorNotFoundError, type TrustStore, type TrustStoreHint, type TrustStoreInput, UnsupportedFormatError, UnsupportedJweError, VERSION, type VerifyAuthorizationResponseOptions, type VerifyOptions, type VerifyResult, buildHaipQuery, createAuthorizationRequest, createSignedAuthorizationRequest, decryptAuthorizationResponse, isHaipQuery, parsePresentation, validateHaipQuery, verifyAuthorizationResponse, verifyPresentation };
package/dist/index.d.ts CHANGED
@@ -15,9 +15,49 @@ interface AuthorizationRequest {
15
15
  nonce: string;
16
16
  state: string;
17
17
  }
18
+ interface SignedAuthorizationRequestInput {
19
+ hostname: string;
20
+ requestUri: string;
21
+ responseUri: string;
22
+ nonce: string;
23
+ state?: string;
24
+ responseMode?: 'direct_post' | 'direct_post.jwt';
25
+ signer: CryptoKeyPair;
26
+ signingAlgorithm?: 'ES256' | 'ES384' | 'RS256';
27
+ certificateChain: Uint8Array[];
28
+ encryptionKey?: {
29
+ publicJwk: JsonWebKey;
30
+ supportedEncValues?: string[];
31
+ };
32
+ vpFormatsSupported: Record<string, unknown>;
33
+ }
34
+ interface SignedAuthorizationRequest {
35
+ uri: string;
36
+ requestObject: string;
37
+ dcqlQuery: DcqlQuery;
38
+ nonce: string;
39
+ state: string;
40
+ }
41
+ interface AuthorizationResponse {
42
+ vp_token: Record<string, Array<string | object>>;
43
+ state?: string;
44
+ [key: string]: unknown;
45
+ }
18
46
 
19
47
  declare function createAuthorizationRequest(input: AuthorizationRequestInput, query: DcqlQuery): AuthorizationRequest;
20
48
 
49
+ /**
50
+ * Build a signed authorization request (JAR) per OpenID4VP 1.0 §5.10 / RFC 9101.
51
+ *
52
+ * Validates that the signing key is bound to the leaf certificate and that the
53
+ * leaf cert's SAN DNSName equals the declared hostname. Emits a short URI
54
+ * carrying only `client_id` + `request_uri`, plus the JWS string the caller
55
+ * must host at `requestUri` with Content-Type `application/oauth-authz-req+jwt`.
56
+ *
57
+ * Client Identifier Prefix is always `x509_san_dns` — other prefixes deferred.
58
+ */
59
+ declare function createSignedAuthorizationRequest(input: SignedAuthorizationRequestInput, query: DcqlQuery): Promise<SignedAuthorizationRequest>;
60
+
21
61
  interface HaipQueryInput {
22
62
  credentialId: string;
23
63
  format: 'dc+sd-jwt' | 'mso_mdoc';
@@ -293,6 +333,12 @@ interface VerifyResult {
293
333
  submission: DcqlSubmission | null;
294
334
  valid: boolean;
295
335
  }
336
+ interface EncryptedResponse {
337
+ response: string;
338
+ }
339
+ type VerifyAuthorizationResponseOptions = VerifyOptions & {
340
+ decryptionKey?: CryptoKey;
341
+ };
296
342
 
297
343
  /**
298
344
  * Parses a VP token, then matches it against a DCQL query.
@@ -306,6 +352,34 @@ interface VerifyResult {
306
352
  * @param options parse + verify options (nonce, trusted issuers, etc.)
307
353
  */
308
354
  declare function verifyPresentation(vpToken: unknown, query: DcqlQuery, options: VerifyOptions): Promise<VerifyResult>;
355
+ /**
356
+ * Verify an OpenID4VP 1.0 §8.1 Authorization Response envelope.
357
+ *
358
+ * Accepts either the unencrypted envelope (object-keyed `vp_token`) or a
359
+ * JWE-wrapped envelope `{ response: '<JWE>' }` for response_mode =
360
+ * direct_post.jwt. When encrypted, decrypts with `options.decryptionKey`
361
+ * first. For this release only single-credential single-presentation is
362
+ * supported — multi-credential envelopes throw
363
+ * {@link MultipleCredentialsNotSupportedError}. Otherwise delegates the
364
+ * extracted single presentation to the existing {@link verifyPresentation}.
365
+ *
366
+ * Callers MUST compare the envelope's `state` against the value they issued
367
+ * themselves — library is stateless and does not track state.
368
+ */
369
+ declare function verifyAuthorizationResponse(envelope: AuthorizationResponse | EncryptedResponse, query: DcqlQuery, options: VerifyAuthorizationResponseOptions): Promise<VerifyResult>;
370
+
371
+ /**
372
+ * Decrypt a JWE-wrapped OpenID4VP Authorization Response (response_mode =
373
+ * direct_post.jwt). Returns the inner §8.1 envelope.
374
+ *
375
+ * Supported JWE algorithms (others throw UnsupportedJweError):
376
+ * - alg: ECDH-ES
377
+ * - enc: A128GCM, A256GCM (HAIP requires both)
378
+ *
379
+ * Cryptographic failures (wrong key, tampered ciphertext) throw
380
+ * DecryptionFailedError.
381
+ */
382
+ declare function decryptAuthorizationResponse(jwe: string, privateKey: CryptoKey): Promise<AuthorizationResponse>;
309
383
 
310
384
  /**
311
385
  * SD-JWT VC credential parser.
@@ -416,6 +490,33 @@ declare class LotlFetchError extends OpenID4VPError {
416
490
  declare class LotlSignatureError extends OpenID4VPError {
417
491
  readonly code: "lotl_signature_invalid";
418
492
  }
493
+ type SignedRequestBuildErrorCode = 'empty_cert_chain' | 'hostname_cert_mismatch' | 'signing_key_cert_mismatch' | 'missing_encryption_jwk' | 'missing_encryption_alg' | 'missing_vp_formats' | 'unsupported_signing_alg' | 'empty_supported_enc_values';
494
+ declare class SignedRequestBuildError extends OpenID4VPError {
495
+ readonly code: SignedRequestBuildErrorCode;
496
+ constructor(code: SignedRequestBuildErrorCode, message: string);
497
+ }
498
+ declare class UnsupportedJweError extends OpenID4VPError {
499
+ readonly code: "unsupported_jwe";
500
+ readonly alg: string;
501
+ readonly enc: string;
502
+ constructor(alg: string, enc: string);
503
+ }
504
+ declare class DecryptionFailedError extends OpenID4VPError {
505
+ readonly code: "decryption_failed";
506
+ constructor(message?: string, options?: {
507
+ cause?: Error;
508
+ });
509
+ }
510
+ declare class MissingDecryptionKeyError extends OpenID4VPError {
511
+ readonly code: "missing_decryption_key";
512
+ constructor(message?: string);
513
+ }
514
+ declare class MultipleCredentialsNotSupportedError extends OpenID4VPError {
515
+ readonly code: "multi_credential_unsupported";
516
+ readonly entryCount: number;
517
+ readonly presentationCount: number;
518
+ constructor(entryCount: number, presentationCount: number);
519
+ }
419
520
 
420
521
  interface LotlTrustStoreOptions {
421
522
  /** Override the bundled signing anchors. Empty is rejected. */
@@ -454,6 +555,6 @@ declare class LotlTrustStore implements TrustStore {
454
555
  private refresh;
455
556
  }
456
557
 
457
- declare const VERSION = "0.5.0";
558
+ declare const VERSION = "0.7.0";
458
559
 
459
- export { type AuthorizationRequest, type AuthorizationRequestInput, type Cache, CertificateChainError, type ChainErrorReason, CompositeTrustStore, type CredentialClaims, type CredentialFormat, ExpiredCredentialError, type Fetcher, HAIP_DOCTYPE_NAMESPACES, type HaipQueryInput, type HaipValidationCode, HaipValidationError, type ICredentialParser, InMemoryCache, InvalidSignatureError, type IssuerInfo, type LotlAnchorMetadata, LotlFetchError, LotlSignatureError, LotlTrustStore, type LotlTrustStoreOptions, MalformedCredentialError, MdocParser, NonceValidationError, OpenID4VPError, type ParseOptions, type PresentationResult, RevocationCheckFailedError, RevokedCertificateError, SdJwtParser, StaticTrustStore, type TrustAnchor, TrustAnchorNotFoundError, type TrustStore, type TrustStoreHint, type TrustStoreInput, UnsupportedFormatError, VERSION, type VerifyOptions, type VerifyResult, buildHaipQuery, createAuthorizationRequest, isHaipQuery, parsePresentation, validateHaipQuery, verifyPresentation };
560
+ export { type AuthorizationRequest, type AuthorizationRequestInput, type AuthorizationResponse, type Cache, CertificateChainError, type ChainErrorReason, CompositeTrustStore, type CredentialClaims, type CredentialFormat, DecryptionFailedError, type EncryptedResponse, ExpiredCredentialError, type Fetcher, HAIP_DOCTYPE_NAMESPACES, type HaipQueryInput, type HaipValidationCode, HaipValidationError, type ICredentialParser, InMemoryCache, InvalidSignatureError, type IssuerInfo, type LotlAnchorMetadata, LotlFetchError, LotlSignatureError, LotlTrustStore, type LotlTrustStoreOptions, MalformedCredentialError, MdocParser, MissingDecryptionKeyError, MultipleCredentialsNotSupportedError, NonceValidationError, OpenID4VPError, type ParseOptions, type PresentationResult, RevocationCheckFailedError, RevokedCertificateError, SdJwtParser, type SignedAuthorizationRequest, type SignedAuthorizationRequestInput, SignedRequestBuildError, type SignedRequestBuildErrorCode, StaticTrustStore, type TrustAnchor, TrustAnchorNotFoundError, type TrustStore, type TrustStoreHint, type TrustStoreInput, UnsupportedFormatError, UnsupportedJweError, VERSION, type VerifyAuthorizationResponseOptions, type VerifyOptions, type VerifyResult, buildHaipQuery, createAuthorizationRequest, createSignedAuthorizationRequest, decryptAuthorizationResponse, isHaipQuery, parsePresentation, validateHaipQuery, verifyAuthorizationResponse, verifyPresentation };
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { X509Certificate, SubjectKeyIdentifierExtension, X509Certificates, BasicConstraintsExtension, KeyUsagesExtension, KeyUsageFlags as KeyUsageFlags$1, AuthorityKeyIdentifierExtension, SubjectAlternativeNameExtension, ExtendedKeyUsageExtension, Name as Name$1 } from '@peculiar/x509';
2
2
  import { v4 } from 'uuid';
3
+ import { SignJWT, importX509, decodeProtectedHeader, jwtVerify, importJWK, compactDecrypt } from 'jose';
3
4
  import { Encoder, Tag } from 'cbor-x';
4
- import { importX509, decodeProtectedHeader, jwtVerify, importJWK } from 'jose';
5
5
  import { splitSdJwt, decodeSdJwt, getClaims } from '@sd-jwt/decode';
6
6
  import { matchQuery, buildSubmission } from '@openeudi/dcql';
7
7
  export { DcqlMatchError, DcqlValidationError } from '@openeudi/dcql';
@@ -19,7 +19,7 @@ var __export = (target, all) => {
19
19
  };
20
20
 
21
21
  // src/errors.ts
22
- var InvalidSignatureError, ExpiredCredentialError, UnsupportedFormatError, MalformedCredentialError, NonceValidationError, HaipValidationError, OpenID4VPError, TrustAnchorNotFoundError, CertificateChainError, RevokedCertificateError, RevocationCheckFailedError, LotlFetchError, LotlSignatureError;
22
+ var InvalidSignatureError, ExpiredCredentialError, UnsupportedFormatError, MalformedCredentialError, NonceValidationError, HaipValidationError, OpenID4VPError, TrustAnchorNotFoundError, CertificateChainError, RevokedCertificateError, RevocationCheckFailedError, LotlFetchError, LotlSignatureError, SignedRequestBuildError, UnsupportedJweError, DecryptionFailedError, MissingDecryptionKeyError, MultipleCredentialsNotSupportedError;
23
23
  var init_errors = __esm({
24
24
  "src/errors.ts"() {
25
25
  InvalidSignatureError = class extends Error {
@@ -115,6 +115,47 @@ var init_errors = __esm({
115
115
  LotlSignatureError = class extends OpenID4VPError {
116
116
  code = "lotl_signature_invalid";
117
117
  };
118
+ SignedRequestBuildError = class extends OpenID4VPError {
119
+ code;
120
+ constructor(code, message) {
121
+ super(message);
122
+ this.code = code;
123
+ }
124
+ };
125
+ UnsupportedJweError = class extends OpenID4VPError {
126
+ code = "unsupported_jwe";
127
+ alg;
128
+ enc;
129
+ constructor(alg, enc) {
130
+ super(`Unsupported JWE algorithms: alg=${alg}, enc=${enc}`);
131
+ this.alg = alg;
132
+ this.enc = enc;
133
+ }
134
+ };
135
+ DecryptionFailedError = class extends OpenID4VPError {
136
+ code = "decryption_failed";
137
+ constructor(message = "Failed to decrypt authorization response", options) {
138
+ super(message, options);
139
+ }
140
+ };
141
+ MissingDecryptionKeyError = class extends OpenID4VPError {
142
+ code = "missing_decryption_key";
143
+ constructor(message = "decryptionKey is required to verify a direct_post.jwt response") {
144
+ super(message);
145
+ }
146
+ };
147
+ MultipleCredentialsNotSupportedError = class extends OpenID4VPError {
148
+ code = "multi_credential_unsupported";
149
+ entryCount;
150
+ presentationCount;
151
+ constructor(entryCount, presentationCount) {
152
+ super(
153
+ `Multi-credential presentations are not yet supported: ${entryCount} queryId entries, ${presentationCount} presentations`
154
+ );
155
+ this.entryCount = entryCount;
156
+ this.presentationCount = presentationCount;
157
+ }
158
+ };
118
159
  }
119
160
  });
120
161
 
@@ -8024,6 +8065,135 @@ function createAuthorizationRequest(input, query) {
8024
8065
  };
8025
8066
  }
8026
8067
 
8068
+ // src/signed-authorization.ts
8069
+ init_errors();
8070
+ var DEFAULT_SUPPORTED_ENC_VALUES = ["A128GCM", "A256GCM"];
8071
+ var SUPPORTED_SIGNING_ALGS = /* @__PURE__ */ new Set(["ES256", "ES384", "RS256"]);
8072
+ async function createSignedAuthorizationRequest(input, query) {
8073
+ if (input.certificateChain.length === 0) {
8074
+ throw new SignedRequestBuildError(
8075
+ "empty_cert_chain",
8076
+ "certificateChain must contain at least the leaf certificate"
8077
+ );
8078
+ }
8079
+ const signingAlg = input.signingAlgorithm ?? "ES256";
8080
+ if (!SUPPORTED_SIGNING_ALGS.has(signingAlg)) {
8081
+ throw new SignedRequestBuildError(
8082
+ "unsupported_signing_alg",
8083
+ `signingAlgorithm ${signingAlg} is not supported (expected ES256/ES384/RS256)`
8084
+ );
8085
+ }
8086
+ if (!input.vpFormatsSupported || Object.keys(input.vpFormatsSupported).length === 0) {
8087
+ throw new SignedRequestBuildError(
8088
+ "missing_vp_formats",
8089
+ "vpFormatsSupported is required and must be non-empty"
8090
+ );
8091
+ }
8092
+ const leafCert = new X509Certificate(toArrayBuffer(input.certificateChain[0]));
8093
+ const sanDnsNames = extractDnsNames(leafCert);
8094
+ if (!sanDnsNames.includes(input.hostname)) {
8095
+ throw new SignedRequestBuildError(
8096
+ "hostname_cert_mismatch",
8097
+ `leaf cert SAN DNSName values [${sanDnsNames.join(", ")}] do not include hostname "${input.hostname}"`
8098
+ );
8099
+ }
8100
+ const signerPublicSpki = new Uint8Array(
8101
+ await crypto.subtle.exportKey("spki", input.signer.publicKey)
8102
+ );
8103
+ const leafSpki = new Uint8Array(leafCert.publicKey.rawData);
8104
+ if (!bytesEqual(signerPublicSpki, leafSpki)) {
8105
+ throw new SignedRequestBuildError(
8106
+ "signing_key_cert_mismatch",
8107
+ "signer.publicKey SPKI does not match leaf certificate SPKI"
8108
+ );
8109
+ }
8110
+ const responseMode = input.responseMode ?? "direct_post.jwt";
8111
+ if (responseMode === "direct_post.jwt") {
8112
+ if (!input.encryptionKey?.publicJwk) {
8113
+ throw new SignedRequestBuildError(
8114
+ "missing_encryption_jwk",
8115
+ "direct_post.jwt response mode requires encryptionKey.publicJwk"
8116
+ );
8117
+ }
8118
+ if (!input.encryptionKey.publicJwk.alg) {
8119
+ throw new SignedRequestBuildError(
8120
+ "missing_encryption_alg",
8121
+ "encryptionKey.publicJwk must carry alg (OpenID4VP 1.0 \xA78.3)"
8122
+ );
8123
+ }
8124
+ }
8125
+ const state = input.state ?? v4();
8126
+ const clientId = `x509_san_dns:${input.hostname}`;
8127
+ const now = Math.floor(Date.now() / 1e3);
8128
+ const clientMetadata = {
8129
+ vp_formats_supported: input.vpFormatsSupported
8130
+ };
8131
+ if (responseMode === "direct_post.jwt" && input.encryptionKey) {
8132
+ const enc = input.encryptionKey.supportedEncValues;
8133
+ if (enc !== void 0 && enc.length === 0) {
8134
+ throw new SignedRequestBuildError(
8135
+ "empty_supported_enc_values",
8136
+ "encryptionKey.supportedEncValues must not be empty (omit to use defaults [A128GCM, A256GCM])"
8137
+ );
8138
+ }
8139
+ const jwk = { ...input.encryptionKey.publicJwk, use: "enc" };
8140
+ clientMetadata.jwks = { keys: [jwk] };
8141
+ clientMetadata.encrypted_response_enc_values_supported = enc ?? [...DEFAULT_SUPPORTED_ENC_VALUES];
8142
+ }
8143
+ const payload = {
8144
+ iss: clientId,
8145
+ aud: "https://self-issued.me/v2",
8146
+ response_type: "vp_token",
8147
+ response_mode: responseMode,
8148
+ client_id: clientId,
8149
+ response_uri: input.responseUri,
8150
+ nonce: input.nonce,
8151
+ state,
8152
+ dcql_query: query,
8153
+ client_metadata: clientMetadata,
8154
+ iat: now,
8155
+ exp: now + 120
8156
+ };
8157
+ const x5c = input.certificateChain.map((der) => bytesToBase64(der));
8158
+ const jws = await new SignJWT(payload).setProtectedHeader({ typ: "oauth-authz-req+jwt", alg: signingAlg, x5c }).sign(input.signer.privateKey);
8159
+ const uriParams = new URLSearchParams({
8160
+ client_id: clientId,
8161
+ request_uri: input.requestUri
8162
+ });
8163
+ return {
8164
+ uri: `openid4vp://authorize?${uriParams.toString()}`,
8165
+ requestObject: jws,
8166
+ dcqlQuery: query,
8167
+ nonce: input.nonce,
8168
+ state
8169
+ };
8170
+ }
8171
+ function extractDnsNames(cert) {
8172
+ const SAN_OID = "2.5.29.17";
8173
+ const ext = cert.extensions.find((e) => e.type === SAN_OID);
8174
+ if (!ext) return [];
8175
+ const names = ext.names;
8176
+ const items = names?.items ?? [];
8177
+ return items.filter((e) => e.type === "dns").map((e) => e.value);
8178
+ }
8179
+ function bytesEqual(a, b) {
8180
+ if (a.length !== b.length) return false;
8181
+ for (let i = 0; i < a.length; i++) {
8182
+ if (a[i] !== b[i]) return false;
8183
+ }
8184
+ return true;
8185
+ }
8186
+ function bytesToBase64(bytes) {
8187
+ let s = "";
8188
+ for (let i = 0; i < bytes.length; i++) s += String.fromCharCode(bytes[i]);
8189
+ return btoa(s);
8190
+ }
8191
+ function toArrayBuffer(bytes) {
8192
+ const out = new ArrayBuffer(bytes.byteLength);
8193
+ new Uint8Array(out).set(bytes);
8194
+ return out;
8195
+ }
8196
+
8027
8197
  // src/haip.ts
8028
8198
  init_errors();
8029
8199
  var HAIP_DOCTYPE_NAMESPACES = {
@@ -8250,7 +8420,7 @@ async function computeItemDigest(itemBytes, digestAlgorithm) {
8250
8420
  const buf = await crypto.subtle.digest(digestAlgorithm, new Uint8Array(itemBytes));
8251
8421
  return new Uint8Array(buf);
8252
8422
  }
8253
- function bytesEqual(a, b) {
8423
+ function bytesEqual2(a, b) {
8254
8424
  if (a.length !== b.length) return false;
8255
8425
  for (let i = 0; i < a.length; i++) {
8256
8426
  if (a[i] !== b[i]) return false;
@@ -8296,7 +8466,7 @@ async function verifyAllDigests(nameSpaces, mso) {
8296
8466
  if (!expectedDigest) {
8297
8467
  throw new MalformedCredentialError(`digestID ${digestID} missing from MSO valueDigests[${ns}]`);
8298
8468
  }
8299
- if (!bytesEqual(hash, expectedDigest)) {
8469
+ if (!bytesEqual2(hash, expectedDigest)) {
8300
8470
  throw new MalformedCredentialError(`digest mismatch for ns=${ns} digestID=${digestID}`);
8301
8471
  }
8302
8472
  }
@@ -8415,7 +8585,7 @@ init_errors();
8415
8585
  var decoder4 = new Encoder({ mapsAsObjects: false, useRecords: false });
8416
8586
  var encoder = new Encoder({ mapsAsObjects: false, useRecords: false });
8417
8587
  var DEFAULT_ALLOWED_ALGORITHMS = ["ES256", "ES384", "ES512"];
8418
- function bytesEqual2(a, b) {
8588
+ function bytesEqual3(a, b) {
8419
8589
  if (a.length !== b.length) return false;
8420
8590
  for (let i = 0; i < a.length; i++) {
8421
8591
  if (a[i] !== b[i]) return false;
@@ -8551,8 +8721,8 @@ var MdocParser = class {
8551
8721
  cache: options.cache,
8552
8722
  clockSkewTolerance: options.clockSkewTolerance
8553
8723
  });
8554
- const { X509Certificate: X509Certificate8 } = await import('@peculiar/x509');
8555
- const leaf = new X509Certificate8(issuerCertBytes);
8724
+ const { X509Certificate: X509Certificate9 } = await import('@peculiar/x509');
8725
+ const leaf = new X509Certificate9(issuerCertBytes);
8556
8726
  trustResult = await evaluator.evaluate(leaf);
8557
8727
  } else {
8558
8728
  if (options.trustedCertificates.length === 0) {
@@ -8562,7 +8732,7 @@ var MdocParser = class {
8562
8732
  );
8563
8733
  }
8564
8734
  } else {
8565
- const trusted = options.trustedCertificates.some((t) => bytesEqual2(t, issuerCertBytes));
8735
+ const trusted = options.trustedCertificates.some((t) => bytesEqual3(t, issuerCertBytes));
8566
8736
  if (!trusted) {
8567
8737
  return {
8568
8738
  valid: false,
@@ -8677,7 +8847,7 @@ function base64ToBytes(base64) {
8677
8847
  }
8678
8848
  return bytes;
8679
8849
  }
8680
- function bytesEqual3(a, b) {
8850
+ function bytesEqual4(a, b) {
8681
8851
  if (a.length !== b.length) return false;
8682
8852
  for (let i = 0; i < a.length; i++) {
8683
8853
  if (a[i] !== b[i]) return false;
@@ -8800,8 +8970,8 @@ var SdJwtParser = class {
8800
8970
  cache: options.cache,
8801
8971
  clockSkewTolerance: options.clockSkewTolerance
8802
8972
  });
8803
- const { X509Certificate: X509Certificate8 } = await import('@peculiar/x509');
8804
- const leaf = new X509Certificate8(issuerCertBytes);
8973
+ const { X509Certificate: X509Certificate9 } = await import('@peculiar/x509');
8974
+ const leaf = new X509Certificate9(issuerCertBytes);
8805
8975
  trustResult = await evaluator.evaluate(leaf);
8806
8976
  } else {
8807
8977
  if (options.trustedCertificates.length === 0) {
@@ -8811,7 +8981,7 @@ var SdJwtParser = class {
8811
8981
  );
8812
8982
  }
8813
8983
  } else {
8814
- const isTrusted = options.trustedCertificates.some((trusted) => bytesEqual3(trusted, issuerCertBytes));
8984
+ const isTrusted = options.trustedCertificates.some((trusted) => bytesEqual4(trusted, issuerCertBytes));
8815
8985
  if (!isTrusted) {
8816
8986
  return invalidResult("Issuer certificate is not trusted");
8817
8987
  }
@@ -8908,6 +9078,44 @@ async function parsePresentation(vpToken, options) {
8908
9078
  error: "Unsupported credential format"
8909
9079
  };
8910
9080
  }
9081
+
9082
+ // src/decrypt-response.ts
9083
+ init_errors();
9084
+ var SUPPORTED_ALG = "ECDH-ES";
9085
+ var SUPPORTED_ENC = /* @__PURE__ */ new Set(["A128GCM", "A256GCM"]);
9086
+ async function decryptAuthorizationResponse(jwe, privateKey) {
9087
+ let header;
9088
+ try {
9089
+ header = decodeProtectedHeader(jwe);
9090
+ } catch (err) {
9091
+ throw new DecryptionFailedError("Malformed JWE header", {
9092
+ cause: err
9093
+ });
9094
+ }
9095
+ const alg = typeof header.alg === "string" ? header.alg : "";
9096
+ const enc = typeof header.enc === "string" ? header.enc : "";
9097
+ if (alg !== SUPPORTED_ALG || !SUPPORTED_ENC.has(enc)) {
9098
+ throw new UnsupportedJweError(alg, enc);
9099
+ }
9100
+ let plaintext;
9101
+ try {
9102
+ const result = await compactDecrypt(jwe, privateKey);
9103
+ plaintext = result.plaintext;
9104
+ } catch (err) {
9105
+ throw new DecryptionFailedError(void 0, { cause: err });
9106
+ }
9107
+ try {
9108
+ const text = new TextDecoder().decode(plaintext);
9109
+ return JSON.parse(text);
9110
+ } catch (err) {
9111
+ throw new DecryptionFailedError("Decrypted plaintext is not valid JSON", {
9112
+ cause: err
9113
+ });
9114
+ }
9115
+ }
9116
+
9117
+ // src/verify.ts
9118
+ init_errors();
8911
9119
  var INTERNAL_TO_DCQL_FORMAT = {
8912
9120
  "sd-jwt-vc": "dc+sd-jwt",
8913
9121
  mdoc: "mso_mdoc"
@@ -8939,6 +9147,48 @@ async function verifyPresentation(vpToken, query, options) {
8939
9147
  valid: parsed.valid && match.satisfied
8940
9148
  };
8941
9149
  }
9150
+ function isEncryptedResponse(x) {
9151
+ return typeof x === "object" && x !== null && "response" in x && typeof x.response === "string" && Object.keys(x).length === 1;
9152
+ }
9153
+ function isAuthorizationResponse(x) {
9154
+ return typeof x === "object" && x !== null && "vp_token" in x && typeof x.vp_token === "object" && x.vp_token !== null;
9155
+ }
9156
+ async function verifyAuthorizationResponse(envelope, query, options) {
9157
+ if (isEncryptedResponse(envelope)) {
9158
+ if (!options.decryptionKey) {
9159
+ throw new MissingDecryptionKeyError();
9160
+ }
9161
+ const decrypted = await decryptAuthorizationResponse(
9162
+ envelope.response,
9163
+ options.decryptionKey
9164
+ );
9165
+ return verifyAuthorizationResponse(decrypted, query, options);
9166
+ }
9167
+ if (!isAuthorizationResponse(envelope)) {
9168
+ throw new TypeError("envelope must contain vp_token as a JSON object per OpenID4VP 1.0 \xA78.1");
9169
+ }
9170
+ const vpToken = envelope.vp_token;
9171
+ const queryIds = Object.keys(vpToken);
9172
+ if (queryIds.length === 0) {
9173
+ throw new TypeError("vp_token cannot be empty per OpenID4VP 1.0 \xA78.1");
9174
+ }
9175
+ for (const id of queryIds) {
9176
+ if (!Array.isArray(vpToken[id])) {
9177
+ throw new TypeError(
9178
+ `vp_token entry "${id}" must be an array of presentations per OpenID4VP 1.0 \xA78.1`
9179
+ );
9180
+ }
9181
+ }
9182
+ const presentationCount = queryIds.reduce(
9183
+ (sum, id) => sum + vpToken[id].length,
9184
+ 0
9185
+ );
9186
+ if (queryIds.length > 1 || presentationCount > 1) {
9187
+ throw new MultipleCredentialsNotSupportedError(queryIds.length, presentationCount);
9188
+ }
9189
+ const presentation = vpToken[queryIds[0]][0];
9190
+ return verifyPresentation(presentation, query, options);
9191
+ }
8942
9192
 
8943
9193
  // src/index.ts
8944
9194
  init_errors();
@@ -9550,7 +9800,7 @@ function equalBytes(a, b) {
9550
9800
 
9551
9801
  // src/index.ts
9552
9802
  init_errors();
9553
- var VERSION = "0.5.0";
9803
+ var VERSION = "0.7.0";
9554
9804
  /*! Bundled license information:
9555
9805
 
9556
9806
  pvtsutils/build/index.es.js:
@@ -9620,6 +9870,6 @@ asn1js/build/index.es.js:
9620
9870
  *)
9621
9871
  */
9622
9872
 
9623
- export { CertificateChainError, CompositeTrustStore, ExpiredCredentialError, HAIP_DOCTYPE_NAMESPACES, HaipValidationError, InMemoryCache, InvalidSignatureError, LotlFetchError, LotlSignatureError, LotlTrustStore, MalformedCredentialError, MdocParser, NonceValidationError, OpenID4VPError, RevocationCheckFailedError, RevokedCertificateError, SdJwtParser, StaticTrustStore, TrustAnchorNotFoundError, UnsupportedFormatError, VERSION, buildHaipQuery, createAuthorizationRequest, isHaipQuery, parsePresentation, validateHaipQuery, verifyPresentation };
9873
+ export { CertificateChainError, CompositeTrustStore, DecryptionFailedError, ExpiredCredentialError, HAIP_DOCTYPE_NAMESPACES, HaipValidationError, InMemoryCache, InvalidSignatureError, LotlFetchError, LotlSignatureError, LotlTrustStore, MalformedCredentialError, MdocParser, MissingDecryptionKeyError, MultipleCredentialsNotSupportedError, NonceValidationError, OpenID4VPError, RevocationCheckFailedError, RevokedCertificateError, SdJwtParser, SignedRequestBuildError, StaticTrustStore, TrustAnchorNotFoundError, UnsupportedFormatError, UnsupportedJweError, VERSION, buildHaipQuery, createAuthorizationRequest, createSignedAuthorizationRequest, decryptAuthorizationResponse, isHaipQuery, parsePresentation, validateHaipQuery, verifyAuthorizationResponse, verifyPresentation };
9624
9874
  //# sourceMappingURL=index.js.map
9625
9875
  //# sourceMappingURL=index.js.map