@openeudi/openid4vp 0.6.0 → 0.8.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.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,138 @@ 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
+ const encValues = enc ?? [...DEFAULT_SUPPORTED_ENC_VALUES];
8142
+ clientMetadata.encrypted_response_enc_values_supported = encValues;
8143
+ clientMetadata.authorization_encrypted_response_alg = "ECDH-ES";
8144
+ clientMetadata.authorization_encrypted_response_enc = encValues[0];
8145
+ }
8146
+ const payload = {
8147
+ iss: clientId,
8148
+ aud: "https://self-issued.me/v2",
8149
+ response_type: "vp_token",
8150
+ response_mode: responseMode,
8151
+ client_id: clientId,
8152
+ response_uri: input.responseUri,
8153
+ nonce: input.nonce,
8154
+ state,
8155
+ dcql_query: query,
8156
+ client_metadata: clientMetadata,
8157
+ iat: now,
8158
+ exp: now + 120
8159
+ };
8160
+ const x5c = input.certificateChain.map((der) => bytesToBase64(der));
8161
+ const jws = await new SignJWT(payload).setProtectedHeader({ typ: "oauth-authz-req+jwt", alg: signingAlg, x5c }).sign(input.signer.privateKey);
8162
+ const uriParams = new URLSearchParams({
8163
+ client_id: clientId,
8164
+ request_uri: input.requestUri
8165
+ });
8166
+ return {
8167
+ uri: `openid4vp://authorize?${uriParams.toString()}`,
8168
+ requestObject: jws,
8169
+ dcqlQuery: query,
8170
+ nonce: input.nonce,
8171
+ state
8172
+ };
8173
+ }
8174
+ function extractDnsNames(cert) {
8175
+ const SAN_OID = "2.5.29.17";
8176
+ const ext = cert.extensions.find((e) => e.type === SAN_OID);
8177
+ if (!ext) return [];
8178
+ const names = ext.names;
8179
+ const items = names?.items ?? [];
8180
+ return items.filter((e) => e.type === "dns").map((e) => e.value);
8181
+ }
8182
+ function bytesEqual(a, b) {
8183
+ if (a.length !== b.length) return false;
8184
+ for (let i = 0; i < a.length; i++) {
8185
+ if (a[i] !== b[i]) return false;
8186
+ }
8187
+ return true;
8188
+ }
8189
+ function bytesToBase64(bytes) {
8190
+ let s = "";
8191
+ for (let i = 0; i < bytes.length; i++) s += String.fromCharCode(bytes[i]);
8192
+ return btoa(s);
8193
+ }
8194
+ function toArrayBuffer(bytes) {
8195
+ const out = new ArrayBuffer(bytes.byteLength);
8196
+ new Uint8Array(out).set(bytes);
8197
+ return out;
8198
+ }
8199
+
8027
8200
  // src/haip.ts
8028
8201
  init_errors();
8029
8202
  var HAIP_DOCTYPE_NAMESPACES = {
@@ -8227,7 +8400,7 @@ async function verifyCoseSign1(coseSign1, publicKey, allowedAlgorithms) {
8227
8400
  throw new MalformedCredentialError(`no hash mapping for alg ${coseSign1.alg}`);
8228
8401
  }
8229
8402
  const sigStructure = ["Signature1", coseSign1.protectedHeaderBytes, new Uint8Array(0), coseSign1.payload];
8230
- const sigInput = decoder.encode(sigStructure);
8403
+ const sigInput = new Uint8Array(decoder.encode(sigStructure));
8231
8404
  const ok = await crypto.subtle.verify(
8232
8405
  { name: "ECDSA", hash: hashAlg },
8233
8406
  publicKey,
@@ -8250,7 +8423,7 @@ async function computeItemDigest(itemBytes, digestAlgorithm) {
8250
8423
  const buf = await crypto.subtle.digest(digestAlgorithm, new Uint8Array(itemBytes));
8251
8424
  return new Uint8Array(buf);
8252
8425
  }
8253
- function bytesEqual(a, b) {
8426
+ function bytesEqual2(a, b) {
8254
8427
  if (a.length !== b.length) return false;
8255
8428
  for (let i = 0; i < a.length; i++) {
8256
8429
  if (a[i] !== b[i]) return false;
@@ -8296,7 +8469,7 @@ async function verifyAllDigests(nameSpaces, mso) {
8296
8469
  if (!expectedDigest) {
8297
8470
  throw new MalformedCredentialError(`digestID ${digestID} missing from MSO valueDigests[${ns}]`);
8298
8471
  }
8299
- if (!bytesEqual(hash, expectedDigest)) {
8472
+ if (!bytesEqual2(hash, expectedDigest)) {
8300
8473
  throw new MalformedCredentialError(`digest mismatch for ns=${ns} digestID=${digestID}`);
8301
8474
  }
8302
8475
  }
@@ -8415,7 +8588,7 @@ init_errors();
8415
8588
  var decoder4 = new Encoder({ mapsAsObjects: false, useRecords: false });
8416
8589
  var encoder = new Encoder({ mapsAsObjects: false, useRecords: false });
8417
8590
  var DEFAULT_ALLOWED_ALGORITHMS = ["ES256", "ES384", "ES512"];
8418
- function bytesEqual2(a, b) {
8591
+ function bytesEqual3(a, b) {
8419
8592
  if (a.length !== b.length) return false;
8420
8593
  for (let i = 0; i < a.length; i++) {
8421
8594
  if (a[i] !== b[i]) return false;
@@ -8551,8 +8724,8 @@ var MdocParser = class {
8551
8724
  cache: options.cache,
8552
8725
  clockSkewTolerance: options.clockSkewTolerance
8553
8726
  });
8554
- const { X509Certificate: X509Certificate8 } = await import('@peculiar/x509');
8555
- const leaf = new X509Certificate8(issuerCertBytes);
8727
+ const { X509Certificate: X509Certificate9 } = await import('@peculiar/x509');
8728
+ const leaf = new X509Certificate9(issuerCertBytes);
8556
8729
  trustResult = await evaluator.evaluate(leaf);
8557
8730
  } else {
8558
8731
  if (options.trustedCertificates.length === 0) {
@@ -8562,7 +8735,7 @@ var MdocParser = class {
8562
8735
  );
8563
8736
  }
8564
8737
  } else {
8565
- const trusted = options.trustedCertificates.some((t) => bytesEqual2(t, issuerCertBytes));
8738
+ const trusted = options.trustedCertificates.some((t) => bytesEqual3(t, issuerCertBytes));
8566
8739
  if (!trusted) {
8567
8740
  return {
8568
8741
  valid: false,
@@ -8677,7 +8850,7 @@ function base64ToBytes(base64) {
8677
8850
  }
8678
8851
  return bytes;
8679
8852
  }
8680
- function bytesEqual3(a, b) {
8853
+ function bytesEqual4(a, b) {
8681
8854
  if (a.length !== b.length) return false;
8682
8855
  for (let i = 0; i < a.length; i++) {
8683
8856
  if (a[i] !== b[i]) return false;
@@ -8766,32 +8939,74 @@ var SdJwtParser = class {
8766
8939
  return invalidResult(`Unsupported algorithm: ${alg}`);
8767
8940
  }
8768
8941
  const x5cArray = header.x5c;
8769
- if (!Array.isArray(x5cArray) || x5cArray.length === 0 || typeof x5cArray[0] !== "string") {
8770
- throw new MalformedCredentialError("Missing or invalid x5c in JWT header");
8771
- }
8942
+ const hasX5c = Array.isArray(x5cArray) && x5cArray.length > 0 && typeof x5cArray[0] === "string";
8772
8943
  let issuerKey;
8773
8944
  let issuerCertBytes;
8774
- try {
8775
- issuerKey = await importX509(derToPem2(x5cArray[0]), alg);
8776
- issuerCertBytes = base64ToBytes(x5cArray[0]);
8777
- } catch (err) {
8778
- if (err instanceof MalformedCredentialError) throw err;
8779
- throw new MalformedCredentialError("Failed to import issuer certificate");
8945
+ if (hasX5c) {
8946
+ try {
8947
+ issuerKey = await importX509(derToPem2(x5cArray[0]), alg);
8948
+ issuerCertBytes = base64ToBytes(x5cArray[0]);
8949
+ } catch (err) {
8950
+ if (err instanceof MalformedCredentialError) throw err;
8951
+ throw new MalformedCredentialError("Failed to import issuer certificate");
8952
+ }
8953
+ } else if (options.trustedIssuerJwks && options.trustedIssuerJwks.length > 0) {
8954
+ issuerKey = void 0;
8955
+ } else {
8956
+ throw new MalformedCredentialError("Missing or invalid x5c in JWT header");
8780
8957
  }
8781
8958
  let payload;
8782
- try {
8783
- const result2 = await jwtVerify(parts.jwt, issuerKey, { algorithms: allowedAlgorithms });
8784
- payload = result2.payload;
8785
- } catch {
8786
- return invalidResult("Issuer JWT signature verification failed");
8959
+ if (issuerKey) {
8960
+ try {
8961
+ const result2 = await jwtVerify(parts.jwt, issuerKey, { algorithms: allowedAlgorithms });
8962
+ payload = result2.payload;
8963
+ } catch {
8964
+ return invalidResult("Issuer JWT signature verification failed");
8965
+ }
8966
+ } else {
8967
+ const jwks = options.trustedIssuerJwks;
8968
+ const headerKid = header["kid"];
8969
+ let candidates;
8970
+ if (typeof headerKid === "string") {
8971
+ const matched = jwks.find((k) => k["kid"] === headerKid);
8972
+ if (!matched) {
8973
+ return invalidResult(
8974
+ `No matching JWK found in trustedIssuerJwks for kid=${headerKid}`
8975
+ );
8976
+ }
8977
+ candidates = [matched];
8978
+ } else {
8979
+ candidates = jwks;
8980
+ }
8981
+ let verifiedPayload;
8982
+ for (const jwk of candidates) {
8983
+ let candidateKey;
8984
+ try {
8985
+ candidateKey = await importJWK(jwk, alg);
8986
+ } catch {
8987
+ continue;
8988
+ }
8989
+ try {
8990
+ const result2 = await jwtVerify(parts.jwt, candidateKey, {
8991
+ algorithms: allowedAlgorithms
8992
+ });
8993
+ verifiedPayload = result2.payload;
8994
+ break;
8995
+ } catch {
8996
+ }
8997
+ }
8998
+ if (!verifiedPayload) {
8999
+ return invalidResult("Issuer JWT signature verification failed");
9000
+ }
9001
+ payload = verifiedPayload;
8787
9002
  }
8788
9003
  const issuerString = typeof payload["iss"] === "string" ? payload["iss"] : "";
8789
9004
  const issuerInfo = {
8790
- certificate: issuerCertBytes,
9005
+ certificate: issuerCertBytes ?? new Uint8Array(),
8791
9006
  country: extractCountryHint(issuerString)
8792
9007
  };
8793
9008
  let trustResult;
8794
- if (options.trustStore) {
9009
+ if (issuerCertBytes === void 0) ; else if (options.trustStore) {
8795
9010
  const { TrustEvaluator: TrustEvaluator2 } = await Promise.resolve().then(() => (init_TrustEvaluator(), TrustEvaluator_exports));
8796
9011
  const evaluator = new TrustEvaluator2({
8797
9012
  trustStore: options.trustStore,
@@ -8800,8 +9015,8 @@ var SdJwtParser = class {
8800
9015
  cache: options.cache,
8801
9016
  clockSkewTolerance: options.clockSkewTolerance
8802
9017
  });
8803
- const { X509Certificate: X509Certificate8 } = await import('@peculiar/x509');
8804
- const leaf = new X509Certificate8(issuerCertBytes);
9018
+ const { X509Certificate: X509Certificate9 } = await import('@peculiar/x509');
9019
+ const leaf = new X509Certificate9(issuerCertBytes);
8805
9020
  trustResult = await evaluator.evaluate(leaf);
8806
9021
  } else {
8807
9022
  if (options.trustedCertificates.length === 0) {
@@ -8811,23 +9026,55 @@ var SdJwtParser = class {
8811
9026
  );
8812
9027
  }
8813
9028
  } else {
8814
- const isTrusted = options.trustedCertificates.some((trusted) => bytesEqual3(trusted, issuerCertBytes));
9029
+ const isTrusted = options.trustedCertificates.some(
9030
+ (trusted) => bytesEqual4(trusted, issuerCertBytes)
9031
+ );
8815
9032
  if (!isTrusted) {
8816
9033
  return invalidResult("Issuer certificate is not trusted");
8817
9034
  }
8818
9035
  }
8819
9036
  }
8820
- const sdArray = payload["_sd"];
8821
9037
  if (parts.disclosures.length > 0) {
8822
- if (!Array.isArray(sdArray) || sdArray.length === 0) {
8823
- return invalidResult("Disclosures present but no _sd array in issuer JWT");
8824
- }
9038
+ const decodedDisclosures = [];
8825
9039
  for (const disclosure of parts.disclosures) {
9040
+ let parsed;
9041
+ try {
9042
+ parsed = JSON.parse(new TextDecoder().decode(base64ToBytes(disclosure)));
9043
+ } catch {
9044
+ return invalidResult("Disclosure is not valid base64url-encoded JSON");
9045
+ }
9046
+ if (!Array.isArray(parsed) || parsed.length !== 2 && parsed.length !== 3) {
9047
+ return invalidResult("Disclosure must be a 2- or 3-element array");
9048
+ }
8826
9049
  const hashBytes = await sha256Hasher(disclosure);
8827
- const hashB64url = bytesToBase64url(hashBytes);
8828
- if (!sdArray.includes(hashB64url)) {
8829
- return invalidResult("Disclosure hash mismatch");
9050
+ decodedDisclosures.push({
9051
+ raw: disclosure,
9052
+ hash: bytesToBase64url(hashBytes),
9053
+ length: parsed.length,
9054
+ value: parsed.length === 3 ? parsed[2] : parsed[1]
9055
+ });
9056
+ }
9057
+ const seed = collectAnchorHashes(payload);
9058
+ const kvHashes = seed.kvHashes;
9059
+ const arrayElemHashes = seed.arrayElemHashes;
9060
+ const remaining = [...decodedDisclosures];
9061
+ for (; ; ) {
9062
+ const anchored = [];
9063
+ for (const d of remaining) {
9064
+ const valid = d.length === 3 ? kvHashes.has(d.hash) : arrayElemHashes.has(d.hash);
9065
+ if (valid) anchored.push(d);
8830
9066
  }
9067
+ if (anchored.length === 0) break;
9068
+ for (const a of anchored) {
9069
+ const sub = collectAnchorHashes(a.value);
9070
+ for (const h of sub.kvHashes) kvHashes.add(h);
9071
+ for (const h of sub.arrayElemHashes) arrayElemHashes.add(h);
9072
+ const idx = remaining.indexOf(a);
9073
+ if (idx >= 0) remaining.splice(idx, 1);
9074
+ }
9075
+ }
9076
+ if (remaining.length > 0) {
9077
+ return invalidResult("Disclosure hash mismatch");
8831
9078
  }
8832
9079
  }
8833
9080
  if (parts.kbJwt) {
@@ -8891,6 +9138,35 @@ var SdJwtParser = class {
8891
9138
  return result;
8892
9139
  }
8893
9140
  };
9141
+ function collectAnchorHashes(node) {
9142
+ const kvHashes = /* @__PURE__ */ new Set();
9143
+ const arrayElemHashes = /* @__PURE__ */ new Set();
9144
+ const visit = (value) => {
9145
+ if (Array.isArray(value)) {
9146
+ for (const item of value) {
9147
+ if (item && typeof item === "object" && !Array.isArray(item)) {
9148
+ const obj = item;
9149
+ const keys = Object.keys(obj);
9150
+ if (keys.length === 1 && keys[0] === "..." && typeof obj["..."] === "string") {
9151
+ arrayElemHashes.add(obj["..."]);
9152
+ continue;
9153
+ }
9154
+ }
9155
+ visit(item);
9156
+ }
9157
+ } else if (value && typeof value === "object") {
9158
+ for (const [k, v] of Object.entries(value)) {
9159
+ if (k === "_sd" && Array.isArray(v)) {
9160
+ for (const h of v) if (typeof h === "string") kvHashes.add(h);
9161
+ continue;
9162
+ }
9163
+ visit(v);
9164
+ }
9165
+ }
9166
+ };
9167
+ visit(node);
9168
+ return { kvHashes, arrayElemHashes };
9169
+ }
8894
9170
 
8895
9171
  // src/presentation.ts
8896
9172
  var parsers = [new SdJwtParser(), new MdocParser()];
@@ -8908,6 +9184,44 @@ async function parsePresentation(vpToken, options) {
8908
9184
  error: "Unsupported credential format"
8909
9185
  };
8910
9186
  }
9187
+
9188
+ // src/decrypt-response.ts
9189
+ init_errors();
9190
+ var SUPPORTED_ALG = "ECDH-ES";
9191
+ var SUPPORTED_ENC = /* @__PURE__ */ new Set(["A128GCM", "A256GCM"]);
9192
+ async function decryptAuthorizationResponse(jwe, privateKey) {
9193
+ let header;
9194
+ try {
9195
+ header = decodeProtectedHeader(jwe);
9196
+ } catch (err) {
9197
+ throw new DecryptionFailedError("Malformed JWE header", {
9198
+ cause: err
9199
+ });
9200
+ }
9201
+ const alg = typeof header.alg === "string" ? header.alg : "";
9202
+ const enc = typeof header.enc === "string" ? header.enc : "";
9203
+ if (alg !== SUPPORTED_ALG || !SUPPORTED_ENC.has(enc)) {
9204
+ throw new UnsupportedJweError(alg, enc);
9205
+ }
9206
+ let plaintext;
9207
+ try {
9208
+ const result = await compactDecrypt(jwe, privateKey);
9209
+ plaintext = result.plaintext;
9210
+ } catch (err) {
9211
+ throw new DecryptionFailedError(void 0, { cause: err });
9212
+ }
9213
+ try {
9214
+ const text = new TextDecoder().decode(plaintext);
9215
+ return JSON.parse(text);
9216
+ } catch (err) {
9217
+ throw new DecryptionFailedError("Decrypted plaintext is not valid JSON", {
9218
+ cause: err
9219
+ });
9220
+ }
9221
+ }
9222
+
9223
+ // src/verify.ts
9224
+ init_errors();
8911
9225
  var INTERNAL_TO_DCQL_FORMAT = {
8912
9226
  "sd-jwt-vc": "dc+sd-jwt",
8913
9227
  mdoc: "mso_mdoc"
@@ -8939,6 +9253,48 @@ async function verifyPresentation(vpToken, query, options) {
8939
9253
  valid: parsed.valid && match.satisfied
8940
9254
  };
8941
9255
  }
9256
+ function isEncryptedResponse(x) {
9257
+ return typeof x === "object" && x !== null && "response" in x && typeof x.response === "string" && Object.keys(x).length === 1;
9258
+ }
9259
+ function isAuthorizationResponse(x) {
9260
+ return typeof x === "object" && x !== null && "vp_token" in x && typeof x.vp_token === "object" && x.vp_token !== null;
9261
+ }
9262
+ async function verifyAuthorizationResponse(envelope, query, options) {
9263
+ if (isEncryptedResponse(envelope)) {
9264
+ if (!options.decryptionKey) {
9265
+ throw new MissingDecryptionKeyError();
9266
+ }
9267
+ const decrypted = await decryptAuthorizationResponse(
9268
+ envelope.response,
9269
+ options.decryptionKey
9270
+ );
9271
+ return verifyAuthorizationResponse(decrypted, query, options);
9272
+ }
9273
+ if (!isAuthorizationResponse(envelope)) {
9274
+ throw new TypeError("envelope must contain vp_token as a JSON object per OpenID4VP 1.0 \xA78.1");
9275
+ }
9276
+ const vpToken = envelope.vp_token;
9277
+ const queryIds = Object.keys(vpToken);
9278
+ if (queryIds.length === 0) {
9279
+ throw new TypeError("vp_token cannot be empty per OpenID4VP 1.0 \xA78.1");
9280
+ }
9281
+ for (const id of queryIds) {
9282
+ if (!Array.isArray(vpToken[id])) {
9283
+ throw new TypeError(
9284
+ `vp_token entry "${id}" must be an array of presentations per OpenID4VP 1.0 \xA78.1`
9285
+ );
9286
+ }
9287
+ }
9288
+ const presentationCount = queryIds.reduce(
9289
+ (sum, id) => sum + vpToken[id].length,
9290
+ 0
9291
+ );
9292
+ if (queryIds.length > 1 || presentationCount > 1) {
9293
+ throw new MultipleCredentialsNotSupportedError(queryIds.length, presentationCount);
9294
+ }
9295
+ const presentation = vpToken[queryIds[0]][0];
9296
+ return verifyPresentation(presentation, query, options);
9297
+ }
8942
9298
 
8943
9299
  // src/index.ts
8944
9300
  init_errors();
@@ -9550,7 +9906,7 @@ function equalBytes(a, b) {
9550
9906
 
9551
9907
  // src/index.ts
9552
9908
  init_errors();
9553
- var VERSION = "0.5.0";
9909
+ var VERSION = "0.8.0";
9554
9910
  /*! Bundled license information:
9555
9911
 
9556
9912
  pvtsutils/build/index.es.js:
@@ -9620,6 +9976,6 @@ asn1js/build/index.es.js:
9620
9976
  *)
9621
9977
  */
9622
9978
 
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 };
9979
+ 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
9980
  //# sourceMappingURL=index.js.map
9625
9981
  //# sourceMappingURL=index.js.map