@openeudi/openid4vp 0.8.0 → 0.9.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.cjs CHANGED
@@ -147,10 +147,10 @@ var init_errors = __esm({
147
147
  code = "unsupported_jwe";
148
148
  alg;
149
149
  enc;
150
- constructor(alg, enc) {
151
- super(`Unsupported JWE algorithms: alg=${alg}, enc=${enc}`);
150
+ constructor(alg, enc3) {
151
+ super(`Unsupported JWE algorithms: alg=${alg}, enc=${enc3}`);
152
152
  this.alg = alg;
153
- this.enc = enc;
153
+ this.enc = enc3;
154
154
  }
155
155
  };
156
156
  exports.DecryptionFailedError = class extends exports.OpenID4VPError {
@@ -334,9 +334,9 @@ var init_index_es = __esm({
334
334
  static isBase64Url(data) {
335
335
  return typeof data === STRING_TYPE && BASE64URL_REGEX.test(data);
336
336
  }
337
- static ToString(buffer, enc = "utf8") {
337
+ static ToString(buffer, enc3 = "utf8") {
338
338
  const buf = BufferSourceConverter.toUint8Array(buffer);
339
- switch (enc.toLowerCase()) {
339
+ switch (enc3.toLowerCase()) {
340
340
  case "utf8":
341
341
  return this.ToUtf8String(buf);
342
342
  case "binary":
@@ -353,14 +353,14 @@ var init_index_es = __esm({
353
353
  case "utf16be":
354
354
  return Utf16Converter.toString(buf);
355
355
  default:
356
- throw new Error(`Unknown type of encoding '${enc}'`);
356
+ throw new Error(`Unknown type of encoding '${enc3}'`);
357
357
  }
358
358
  }
359
- static FromString(str, enc = "utf8") {
359
+ static FromString(str, enc3 = "utf8") {
360
360
  if (!str) {
361
361
  return new ArrayBuffer(0);
362
362
  }
363
- switch (enc.toLowerCase()) {
363
+ switch (enc3.toLowerCase()) {
364
364
  case "utf8":
365
365
  return this.FromUtf8String(str);
366
366
  case "binary":
@@ -377,7 +377,7 @@ var init_index_es = __esm({
377
377
  case "utf16be":
378
378
  return Utf16Converter.fromString(str);
379
379
  default:
380
- throw new Error(`Unknown type of encoding '${enc}'`);
380
+ throw new Error(`Unknown type of encoding '${enc3}'`);
381
381
  }
382
382
  }
383
383
  static ToBase64(buffer) {
@@ -6382,6 +6382,33 @@ var init_es20152 = __esm({
6382
6382
  init_validity();
6383
6383
  }
6384
6384
  });
6385
+ function getSkiHex(cert) {
6386
+ const ext = cert.getExtension(x509.SubjectKeyIdentifierExtension);
6387
+ return ext ? ext.keyId.toLowerCase() : null;
6388
+ }
6389
+ function certificatesEqual(a, b) {
6390
+ const ab = new Uint8Array(a.rawData);
6391
+ const bb = new Uint8Array(b.rawData);
6392
+ if (ab.length !== bb.length) return false;
6393
+ for (let i = 0; i < ab.length; i++) {
6394
+ if (ab[i] !== bb[i]) return false;
6395
+ }
6396
+ return true;
6397
+ }
6398
+ function getSkiBytes(cert) {
6399
+ const ext = cert.getExtension(x509.SubjectKeyIdentifierExtension);
6400
+ if (!ext) return null;
6401
+ const hex = ext.keyId;
6402
+ const out = new Uint8Array(hex.length / 2);
6403
+ for (let i = 0; i < out.length; i++) {
6404
+ out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
6405
+ }
6406
+ return out;
6407
+ }
6408
+ var init_x509_utils = __esm({
6409
+ "src/trust/x509-utils.ts"() {
6410
+ }
6411
+ });
6385
6412
  function mapX509AlgoToJwaName(cert) {
6386
6413
  const alg = cert.signatureAlgorithm;
6387
6414
  const name = alg?.name ?? "";
@@ -6391,8 +6418,11 @@ function mapX509AlgoToJwaName(cert) {
6391
6418
  if (name === "ECDSA" && hash === "SHA-512") return "ES512";
6392
6419
  if (name === "Ed25519" || name === "EdDSA") return "EdDSA";
6393
6420
  if (name === "RSASSA-PKCS1-v1_5" && hash === "SHA-256") return "RS256";
6421
+ if (name === "RSASSA-PKCS1-v1_5" && hash === "SHA-384") return "RS384";
6422
+ if (name === "RSASSA-PKCS1-v1_5" && hash === "SHA-512") return "RS512";
6394
6423
  if (name === "RSA-PSS" && hash === "SHA-256") return "PS256";
6395
6424
  if (name === "RSA-PSS" && hash === "SHA-384") return "PS384";
6425
+ if (name === "RSA-PSS" && hash === "SHA-512") return "PS512";
6396
6426
  return `${name}-${hash}`;
6397
6427
  }
6398
6428
  function collectLeafNames(leaf) {
@@ -6485,8 +6515,20 @@ var init_ChainBuilder = __esm({
6485
6515
  init_es2015();
6486
6516
  init_es20152();
6487
6517
  init_errors();
6518
+ init_x509_utils();
6488
6519
  NAME_CONSTRAINTS_OID = "2.5.29.30";
6489
- DEFAULT_ALGORITHMS = ["ES256", "ES384", "EdDSA", "RS256", "PS256"];
6520
+ DEFAULT_ALGORITHMS = [
6521
+ "ES256",
6522
+ "ES384",
6523
+ "ES512",
6524
+ "EdDSA",
6525
+ "RS256",
6526
+ "RS384",
6527
+ "RS512",
6528
+ "PS256",
6529
+ "PS384",
6530
+ "PS512"
6531
+ ];
6490
6532
  ChainBuilder = class {
6491
6533
  constructor(opts = {}) {
6492
6534
  this.opts = opts;
@@ -6545,6 +6587,12 @@ var init_ChainBuilder = __esm({
6545
6587
  current = issuer;
6546
6588
  nonLeafDepth += 1;
6547
6589
  }
6590
+ if (!certificatesEqual(current, anchor)) {
6591
+ throw new exports.CertificateChainError(
6592
+ `certificate ${current.subject} shares the trust anchor's Subject DN but is not the anchor (no signature closure)`,
6593
+ { reason: "signature" }
6594
+ );
6595
+ }
6548
6596
  this.checkNameConstraints(leaf, chain.slice(1));
6549
6597
  return chain;
6550
6598
  }
@@ -7785,7 +7833,10 @@ var init_RevocationChecker = __esm({
7785
7833
  if (hints.ocspUrl) {
7786
7834
  try {
7787
7835
  return await this.ocsp.checkCached(subjectCert, issuerCert, hints.ocspUrl, now);
7788
- } catch {
7836
+ } catch (err) {
7837
+ console.warn(
7838
+ `[openid4vp] OCSP check failed for ${subjectCert.subject} via ${hints.ocspUrl}, falling back to CRL: ${err.message}`
7839
+ );
7789
7840
  }
7790
7841
  }
7791
7842
  for (const url of hints.crlUrls) {
@@ -7813,6 +7864,9 @@ var init_RevocationChecker = __esm({
7813
7864
  return { status: "good", source: "crl", checkedAt: now };
7814
7865
  } catch (err) {
7815
7866
  if (err instanceof exports.RevocationCheckFailedError) throw err;
7867
+ console.warn(
7868
+ `[openid4vp] CRL check failed for ${subjectCert.subject} via ${url}: ${err.message}`
7869
+ );
7816
7870
  continue;
7817
7871
  }
7818
7872
  }
@@ -7831,24 +7885,6 @@ var init_RevocationChecker = __esm({
7831
7885
  };
7832
7886
  }
7833
7887
  });
7834
- function getSkiHex(cert) {
7835
- const ext = cert.getExtension(x509.SubjectKeyIdentifierExtension);
7836
- return ext ? ext.keyId.toLowerCase() : null;
7837
- }
7838
- function getSkiBytes(cert) {
7839
- const ext = cert.getExtension(x509.SubjectKeyIdentifierExtension);
7840
- if (!ext) return null;
7841
- const hex = ext.keyId;
7842
- const out = new Uint8Array(hex.length / 2);
7843
- for (let i = 0; i < out.length; i++) {
7844
- out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
7845
- }
7846
- return out;
7847
- }
7848
- var init_x509_utils = __esm({
7849
- "src/trust/x509-utils.ts"() {
7850
- }
7851
- });
7852
7888
 
7853
7889
  // src/trust/lotl-loa-mapping.ts
7854
7890
  function mapLoA(uris) {
@@ -8030,8 +8066,13 @@ var init_TrustEvaluator = __esm({
8030
8066
  );
8031
8067
  const terminusCert = chain[chain.length - 1];
8032
8068
  const anchor = anchors.find(
8033
- (a) => a.certificate.serialNumber === terminusCert.serialNumber
8034
- ) ?? anchors[0];
8069
+ (a) => certificatesEqual(a.certificate, terminusCert)
8070
+ );
8071
+ if (!anchor) {
8072
+ throw new exports.TrustAnchorNotFoundError(
8073
+ `chain terminus ${terminusCert.subject} does not match any trust anchor certificate`
8074
+ );
8075
+ }
8035
8076
  const issuerForRevocation = chain.length > 1 ? chain[1] : terminusCert;
8036
8077
  const revocation = await this.revocationChecker.check(
8037
8078
  leaf,
@@ -8150,8 +8191,8 @@ async function createSignedAuthorizationRequest(input, query) {
8150
8191
  vp_formats_supported: input.vpFormatsSupported
8151
8192
  };
8152
8193
  if (responseMode === "direct_post.jwt" && input.encryptionKey) {
8153
- const enc = input.encryptionKey.supportedEncValues;
8154
- if (enc !== void 0 && enc.length === 0) {
8194
+ const enc3 = input.encryptionKey.supportedEncValues;
8195
+ if (enc3 !== void 0 && enc3.length === 0) {
8155
8196
  throw new exports.SignedRequestBuildError(
8156
8197
  "empty_supported_enc_values",
8157
8198
  "encryptionKey.supportedEncValues must not be empty (omit to use defaults [A128GCM, A256GCM])"
@@ -8159,7 +8200,7 @@ async function createSignedAuthorizationRequest(input, query) {
8159
8200
  }
8160
8201
  const jwk = { ...input.encryptionKey.publicJwk, use: "enc" };
8161
8202
  clientMetadata.jwks = { keys: [jwk] };
8162
- const encValues = enc ?? [...DEFAULT_SUPPORTED_ENC_VALUES];
8203
+ const encValues = enc3 ?? [...DEFAULT_SUPPORTED_ENC_VALUES];
8163
8204
  clientMetadata.encrypted_response_enc_values_supported = encValues;
8164
8205
  clientMetadata.authorization_encrypted_response_alg = "ECDH-ES";
8165
8206
  clientMetadata.authorization_encrypted_response_enc = encValues[0];
@@ -8335,7 +8376,7 @@ function isHaipQuery(query) {
8335
8376
 
8336
8377
  // src/crypto/cose-sign1.ts
8337
8378
  init_errors();
8338
- var decoder = new cborX.Encoder({ mapsAsObjects: false, useRecords: false });
8379
+ var decoder = new cborX.Encoder({ mapsAsObjects: false, useRecords: false, tagUint8Array: false });
8339
8380
  var HEADER_ALG = 1;
8340
8381
  var HEADER_X5CHAIN = 33;
8341
8382
  var COSE_ALG_TO_NAME = {
@@ -8433,6 +8474,131 @@ async function verifyCoseSign1(coseSign1, publicKey, allowedAlgorithms) {
8433
8474
  }
8434
8475
  }
8435
8476
 
8477
+ // src/crypto/device-auth.ts
8478
+ init_errors();
8479
+ var enc = new cborX.Encoder({ mapsAsObjects: false, useRecords: false, tagUint8Array: false });
8480
+ var HEADER_ALG2 = 1;
8481
+ var COSE_ALG_TO_NAME2 = {
8482
+ [-7]: "ES256",
8483
+ [-35]: "ES384",
8484
+ [-36]: "ES512"
8485
+ };
8486
+ var ALG_TO_HASH2 = { ES256: "SHA-256", ES384: "SHA-384", ES512: "SHA-512" };
8487
+ var COSE_KEY_KTY = 1;
8488
+ var COSE_KEY_CRV = -1;
8489
+ var COSE_KEY_X = -2;
8490
+ var COSE_KEY_Y = -3;
8491
+ var COSE_KTY_EC2 = 2;
8492
+ var COSE_CRV_TO_JWK = { 1: "P-256", 2: "P-384", 3: "P-521" };
8493
+ function concatBytes(...chunks) {
8494
+ let len = 0;
8495
+ for (const c of chunks) len += c.length;
8496
+ const out = new Uint8Array(len);
8497
+ let off = 0;
8498
+ for (const c of chunks) {
8499
+ out.set(c, off);
8500
+ off += c.length;
8501
+ }
8502
+ return out;
8503
+ }
8504
+ function bytesToBase64Url(bytes) {
8505
+ let bin = "";
8506
+ for (const b of bytes) bin += String.fromCharCode(b);
8507
+ return globalThis.btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
8508
+ }
8509
+ function encodeTag24(inner) {
8510
+ return concatBytes(new Uint8Array([216, 24]), new Uint8Array(enc.encode(inner)));
8511
+ }
8512
+ function buildDeviceAuthenticationBytes(params) {
8513
+ const deviceAuthentication = concatBytes(
8514
+ new Uint8Array([132]),
8515
+ // array(4)
8516
+ new Uint8Array(enc.encode("DeviceAuthentication")),
8517
+ params.sessionTranscript,
8518
+ new Uint8Array(enc.encode(params.docType)),
8519
+ encodeTag24(params.deviceNameSpacesInner)
8520
+ );
8521
+ return encodeTag24(deviceAuthentication);
8522
+ }
8523
+ function encodeDeviceSigStructure(protectedBytes, deviceAuthBytes) {
8524
+ const sig = ["Signature1", protectedBytes, new Uint8Array(0), deviceAuthBytes];
8525
+ return new Uint8Array(enc.encode(sig));
8526
+ }
8527
+ async function importDeviceKeyFromCose(coseKey) {
8528
+ if (!(coseKey instanceof Map)) {
8529
+ throw new exports.MalformedCredentialError("MSO deviceKeyInfo.deviceKey is not a COSE_Key map");
8530
+ }
8531
+ const kty = coseKey.get(COSE_KEY_KTY);
8532
+ if (kty !== COSE_KTY_EC2) {
8533
+ throw new exports.MalformedCredentialError(`Unsupported COSE key type for device key: ${String(kty)}`);
8534
+ }
8535
+ const crv = coseKey.get(COSE_KEY_CRV);
8536
+ const crvName = typeof crv === "number" ? COSE_CRV_TO_JWK[crv] : void 0;
8537
+ const x = coseKey.get(COSE_KEY_X);
8538
+ const y = coseKey.get(COSE_KEY_Y);
8539
+ if (!crvName || !(x instanceof Uint8Array) || !(y instanceof Uint8Array)) {
8540
+ throw new exports.MalformedCredentialError("Malformed EC2 COSE_Key in MSO deviceKeyInfo");
8541
+ }
8542
+ return crypto.subtle.importKey(
8543
+ "jwk",
8544
+ { kty: "EC", crv: crvName, x: bytesToBase64Url(x), y: bytesToBase64Url(y) },
8545
+ { name: "ECDSA", namedCurve: crvName },
8546
+ false,
8547
+ ["verify"]
8548
+ );
8549
+ }
8550
+ async function verifyDeviceAuth(params) {
8551
+ const { deviceSigned, deviceKeyCose, docType, sessionTranscript, allowedAlgorithms } = params;
8552
+ const deviceAuth = deviceSigned.get("deviceAuth");
8553
+ if (!(deviceAuth instanceof Map)) {
8554
+ throw new exports.MalformedCredentialError("deviceSigned.deviceAuth is missing or malformed");
8555
+ }
8556
+ if (deviceAuth.has("deviceMac") && !deviceAuth.has("deviceSignature")) {
8557
+ throw new exports.MalformedCredentialError("mDOC DeviceMac authentication is not supported");
8558
+ }
8559
+ const deviceSignature = deviceAuth.get("deviceSignature");
8560
+ if (!Array.isArray(deviceSignature) || deviceSignature.length !== 4) {
8561
+ throw new exports.MalformedCredentialError("deviceAuth.deviceSignature must be a COSE_Sign1 array");
8562
+ }
8563
+ const protectedBytes = deviceSignature[0];
8564
+ const signature = deviceSignature[3];
8565
+ if (!(protectedBytes instanceof Uint8Array) || !(signature instanceof Uint8Array)) {
8566
+ throw new exports.MalformedCredentialError("DeviceSignature protected header / signature must be byte strings");
8567
+ }
8568
+ let alg;
8569
+ try {
8570
+ const ph = enc.decode(protectedBytes);
8571
+ const label = ph instanceof Map ? ph.get(HEADER_ALG2) : void 0;
8572
+ alg = typeof label === "number" ? COSE_ALG_TO_NAME2[label] : void 0;
8573
+ } catch {
8574
+ throw new exports.MalformedCredentialError("DeviceSignature protected header is not decodable CBOR");
8575
+ }
8576
+ if (!alg || !allowedAlgorithms.includes(alg)) {
8577
+ throw new exports.MalformedCredentialError(`DeviceSignature uses a disallowed algorithm: ${String(alg)}`);
8578
+ }
8579
+ const deviceNameSpacesInner = extractTag24Inner(deviceSigned.get("nameSpaces"));
8580
+ const deviceAuthBytes = buildDeviceAuthenticationBytes({ sessionTranscript, docType, deviceNameSpacesInner });
8581
+ const sigInput = encodeDeviceSigStructure(protectedBytes, deviceAuthBytes);
8582
+ const deviceKey = await importDeviceKeyFromCose(deviceKeyCose);
8583
+ const ok = await crypto.subtle.verify(
8584
+ { name: "ECDSA", hash: ALG_TO_HASH2[alg] },
8585
+ deviceKey,
8586
+ new Uint8Array(signature),
8587
+ sigInput
8588
+ );
8589
+ if (!ok) {
8590
+ throw new exports.MalformedCredentialError("mDOC device authentication (DeviceSignature) failed to verify");
8591
+ }
8592
+ }
8593
+ function extractTag24Inner(value) {
8594
+ if (value instanceof Uint8Array) return value;
8595
+ if (value !== null && typeof value === "object" && "value" in value && value.value instanceof Uint8Array && // Tag(24,bytes) and EmbeddedCbor both expose .value; both are fine here.
8596
+ ("tag" in value ? value.tag === 24 : true)) {
8597
+ return value.value;
8598
+ }
8599
+ throw new exports.MalformedCredentialError("deviceSigned.nameSpaces must be tag-24 wrapped CBOR");
8600
+ }
8601
+
8436
8602
  // src/crypto/digest.ts
8437
8603
  init_errors();
8438
8604
  var decoder2 = new cborX.Encoder({ mapsAsObjects: false, useRecords: false });
@@ -8565,6 +8731,14 @@ function decodeMso(payload) {
8565
8731
  }
8566
8732
  valueDigests.set(ns, perNs);
8567
8733
  }
8734
+ let deviceKey;
8735
+ const dkiRaw = m.get("deviceKeyInfo");
8736
+ if (dkiRaw instanceof Map) {
8737
+ const dk = dkiRaw.get("deviceKey");
8738
+ if (dk instanceof Map) {
8739
+ deviceKey = dk;
8740
+ }
8741
+ }
8568
8742
  const viRaw = m.get("validityInfo");
8569
8743
  if (!(viRaw instanceof Map)) {
8570
8744
  throw new exports.MalformedCredentialError("MSO.validityInfo must be a Map");
@@ -8578,7 +8752,7 @@ function decodeMso(payload) {
8578
8752
  if (vi.has("expectedUpdate")) {
8579
8753
  validityInfo.expectedUpdate = decodeDateLike(vi.get("expectedUpdate"));
8580
8754
  }
8581
- return { version, digestAlgorithm, valueDigests, docType, validityInfo };
8755
+ return { version, digestAlgorithm, valueDigests, docType, deviceKey, validityInfo };
8582
8756
  }
8583
8757
  function validateMsoValidity(mso, now) {
8584
8758
  const { signed, validFrom, validUntil } = mso.validityInfo;
@@ -8628,15 +8802,54 @@ ${lines.join("\n")}
8628
8802
  function extractCountryHintFromCert(_certDer) {
8629
8803
  return "";
8630
8804
  }
8805
+ function cborByteStringHeader(length) {
8806
+ if (length < 24) return Uint8Array.of(64 | length);
8807
+ if (length < 256) return Uint8Array.of(88, length);
8808
+ if (length < 65536) return Uint8Array.of(89, length >> 8 & 255, length & 255);
8809
+ if (length < 4294967296) {
8810
+ return Uint8Array.of(
8811
+ 90,
8812
+ length >>> 24 & 255,
8813
+ length >>> 16 & 255,
8814
+ length >>> 8 & 255,
8815
+ length & 255
8816
+ );
8817
+ }
8818
+ const hi = Math.floor(length / 4294967296);
8819
+ const lo = length >>> 0;
8820
+ return Uint8Array.of(
8821
+ 91,
8822
+ hi >>> 24 & 255,
8823
+ hi >>> 16 & 255,
8824
+ hi >>> 8 & 255,
8825
+ hi & 255,
8826
+ lo >>> 24 & 255,
8827
+ lo >>> 16 & 255,
8828
+ lo >>> 8 & 255,
8829
+ lo & 255
8830
+ );
8831
+ }
8832
+ function wrapIssuerSignedItemBytes(inner) {
8833
+ const header = cborByteStringHeader(inner.length);
8834
+ const out = new Uint8Array(2 + header.length + inner.length);
8835
+ out[0] = 216;
8836
+ out[1] = 24;
8837
+ out.set(header, 2);
8838
+ out.set(inner, 2 + header.length);
8839
+ return out;
8840
+ }
8631
8841
  function extractRawItemBytes(item) {
8632
8842
  if (item instanceof cborX.Tag && item.tag === 24 && item.value instanceof Uint8Array) {
8633
- return item.value;
8843
+ return wrapIssuerSignedItemBytes(item.value);
8634
8844
  }
8635
8845
  if (item instanceof Uint8Array) {
8636
- return item;
8846
+ if (item.length >= 2 && item[0] === 216 && item[1] === 24) {
8847
+ return item;
8848
+ }
8849
+ return wrapIssuerSignedItemBytes(item);
8637
8850
  }
8638
8851
  if (item !== null && typeof item === "object" && !(item instanceof cborX.Tag) && !(item instanceof Map) && "value" in item && item.value instanceof Uint8Array) {
8639
- return item.value;
8852
+ return wrapIssuerSignedItemBytes(item.value);
8640
8853
  }
8641
8854
  throw new exports.MalformedCredentialError("IssuerSignedItem must be a tag-24 byte string");
8642
8855
  }
@@ -8826,6 +9039,54 @@ var MdocParser = class {
8826
9039
  nameSpaces.set(ns, items.map(extractRawItemBytes));
8827
9040
  }
8828
9041
  await verifyAllDigests(nameSpaces, mso);
9042
+ const deviceSigned = document.get("deviceSigned");
9043
+ if (!(deviceSigned instanceof Map)) {
9044
+ return {
9045
+ valid: false,
9046
+ format: this.format,
9047
+ claims: {},
9048
+ issuer: { certificate: issuerCertBytes, country: "" },
9049
+ error: "Missing deviceSigned \u2014 mDOC device authentication is required"
9050
+ };
9051
+ }
9052
+ if (!(options.mdocSessionTranscript instanceof Uint8Array) || options.mdocSessionTranscript.length === 0) {
9053
+ return {
9054
+ valid: false,
9055
+ format: this.format,
9056
+ claims: {},
9057
+ issuer: { certificate: issuerCertBytes, country: "" },
9058
+ error: "mdocSessionTranscript is required to verify mDOC device authentication"
9059
+ };
9060
+ }
9061
+ if (!mso.deviceKey) {
9062
+ return {
9063
+ valid: false,
9064
+ format: this.format,
9065
+ claims: {},
9066
+ issuer: { certificate: issuerCertBytes, country: "" },
9067
+ error: "MSO is missing deviceKeyInfo.deviceKey; cannot verify device authentication"
9068
+ };
9069
+ }
9070
+ try {
9071
+ await verifyDeviceAuth({
9072
+ deviceSigned,
9073
+ deviceKeyCose: mso.deviceKey,
9074
+ docType: mso.docType,
9075
+ sessionTranscript: options.mdocSessionTranscript,
9076
+ allowedAlgorithms
9077
+ });
9078
+ } catch (err) {
9079
+ if (err instanceof exports.MalformedCredentialError) {
9080
+ return {
9081
+ valid: false,
9082
+ format: this.format,
9083
+ claims: {},
9084
+ issuer: { certificate: issuerCertBytes, country: "" },
9085
+ error: err.message
9086
+ };
9087
+ }
9088
+ throw err;
9089
+ }
8829
9090
  const { flat: claims, namespaced: namespacedClaims } = mapRawToClaims(nameSpaces);
8830
9091
  const issuer = {
8831
9092
  certificate: issuerCertBytes,
@@ -9098,11 +9359,21 @@ var SdJwtParser = class {
9098
9359
  return invalidResult("Disclosure hash mismatch");
9099
9360
  }
9100
9361
  }
9362
+ const cnf = payload["cnf"];
9363
+ const isHolderBound = !!cnf && typeof cnf === "object" && !!cnf["jwk"];
9364
+ const keyBindingRequired = isHolderBound || options.requireKeyBinding === true;
9365
+ if (keyBindingRequired && !parts.kbJwt) {
9366
+ return invalidResult(
9367
+ "Key binding JWT is required for holder-bound credentials but was not presented"
9368
+ );
9369
+ }
9101
9370
  if (parts.kbJwt) {
9102
- const cnf = payload["cnf"];
9103
- if (!cnf || typeof cnf !== "object" || !cnf["jwk"]) {
9371
+ if (!isHolderBound) {
9104
9372
  throw new exports.MalformedCredentialError("Missing cnf.jwk in issuer JWT for key binding");
9105
9373
  }
9374
+ if (typeof options.nonce !== "string" || options.nonce.length === 0) {
9375
+ return invalidResult("A nonce is required to verify the key binding JWT");
9376
+ }
9106
9377
  let holderKey;
9107
9378
  try {
9108
9379
  const kbHeader = jose.decodeProtectedHeader(parts.kbJwt);
@@ -9220,9 +9491,9 @@ async function decryptAuthorizationResponse(jwe, privateKey) {
9220
9491
  });
9221
9492
  }
9222
9493
  const alg = typeof header.alg === "string" ? header.alg : "";
9223
- const enc = typeof header.enc === "string" ? header.enc : "";
9224
- if (alg !== SUPPORTED_ALG || !SUPPORTED_ENC.has(enc)) {
9225
- throw new exports.UnsupportedJweError(alg, enc);
9494
+ const enc3 = typeof header.enc === "string" ? header.enc : "";
9495
+ if (alg !== SUPPORTED_ALG || !SUPPORTED_ENC.has(enc3)) {
9496
+ throw new exports.UnsupportedJweError(alg, enc3);
9226
9497
  }
9227
9498
  let plaintext;
9228
9499
  try {
@@ -9240,6 +9511,29 @@ async function decryptAuthorizationResponse(jwe, privateKey) {
9240
9511
  });
9241
9512
  }
9242
9513
  }
9514
+ var enc2 = new cborX.Encoder({ mapsAsObjects: false, useRecords: false, tagUint8Array: false });
9515
+ async function sha256(bytes) {
9516
+ return new Uint8Array(await crypto.subtle.digest("SHA-256", bytes));
9517
+ }
9518
+ async function buildOid4vpSessionTranscript(params) {
9519
+ const { clientId, responseUri, nonce, mdocGeneratedNonce } = params;
9520
+ const clientIdHash = await sha256(new Uint8Array(enc2.encode([clientId, mdocGeneratedNonce])));
9521
+ const responseUriHash = await sha256(new Uint8Array(enc2.encode([responseUri, mdocGeneratedNonce])));
9522
+ return new Uint8Array(enc2.encode([null, null, [clientIdHash, responseUriHash, nonce]]));
9523
+ }
9524
+ async function jwkSha256Thumbprint(jwk) {
9525
+ if (jwk.kty !== "EC") {
9526
+ throw new Error(`Unsupported JWK kty for thumbprint: ${jwk.kty}`);
9527
+ }
9528
+ const canonical = `{"crv":"${jwk.crv}","kty":"EC","x":"${jwk.x}","y":"${jwk.y}"}`;
9529
+ return sha256(new TextEncoder().encode(canonical));
9530
+ }
9531
+ async function buildOpenID4VPHandoverSessionTranscript(params) {
9532
+ const { clientId, nonce, responseUri, verifierEncryptionJwk } = params;
9533
+ const thumb = verifierEncryptionJwk ? await jwkSha256Thumbprint(verifierEncryptionJwk) : null;
9534
+ const infoHash = await sha256(new Uint8Array(enc2.encode([clientId, nonce, thumb, responseUri])));
9535
+ return new Uint8Array(enc2.encode([null, null, ["OpenID4VPHandover", infoHash]]));
9536
+ }
9243
9537
 
9244
9538
  // src/verify.ts
9245
9539
  init_errors();
@@ -9277,6 +9571,42 @@ async function verifyPresentation(vpToken, query, options) {
9277
9571
  function isEncryptedResponse(x) {
9278
9572
  return typeof x === "object" && x !== null && "response" in x && typeof x.response === "string" && Object.keys(x).length === 1;
9279
9573
  }
9574
+ var BASE64URL_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
9575
+ function base64UrlToBytes(input) {
9576
+ const cleaned = input.replace(/=+$/, "");
9577
+ const byteLength = Math.floor(cleaned.length * 3 / 4);
9578
+ const bytes = new Uint8Array(byteLength);
9579
+ let byteIndex = 0;
9580
+ for (let i = 0; i < cleaned.length; i += 4) {
9581
+ const a = BASE64URL_CHARS.indexOf(cleaned[i]);
9582
+ const b = i + 1 < cleaned.length ? BASE64URL_CHARS.indexOf(cleaned[i + 1]) : 0;
9583
+ const c = i + 2 < cleaned.length ? BASE64URL_CHARS.indexOf(cleaned[i + 2]) : 0;
9584
+ const d = i + 3 < cleaned.length ? BASE64URL_CHARS.indexOf(cleaned[i + 3]) : 0;
9585
+ if (a === -1 || b === -1 || c === -1 || d === -1) {
9586
+ throw new TypeError("Invalid base64url character in vp_token/apu");
9587
+ }
9588
+ bytes[byteIndex++] = a << 2 | b >> 4;
9589
+ if (byteIndex < byteLength) bytes[byteIndex++] = (b & 15) << 4 | c >> 2;
9590
+ if (byteIndex < byteLength) bytes[byteIndex++] = (c & 3) << 6 | d;
9591
+ }
9592
+ return bytes;
9593
+ }
9594
+ function extractMdocGeneratedNonce(jwe) {
9595
+ let apu;
9596
+ try {
9597
+ apu = jose.decodeProtectedHeader(jwe).apu;
9598
+ } catch {
9599
+ return void 0;
9600
+ }
9601
+ if (typeof apu !== "string" || apu.length === 0) {
9602
+ return void 0;
9603
+ }
9604
+ try {
9605
+ return new TextDecoder().decode(base64UrlToBytes(apu));
9606
+ } catch {
9607
+ return void 0;
9608
+ }
9609
+ }
9280
9610
  function isAuthorizationResponse(x) {
9281
9611
  return typeof x === "object" && x !== null && "vp_token" in x && typeof x.vp_token === "object" && x.vp_token !== null;
9282
9612
  }
@@ -9285,11 +9615,32 @@ async function verifyAuthorizationResponse(envelope, query, options) {
9285
9615
  if (!options.decryptionKey) {
9286
9616
  throw new exports.MissingDecryptionKeyError();
9287
9617
  }
9618
+ const nextOptions = { ...options };
9619
+ if (options.mdocSessionTranscript === void 0 && options.clientId !== void 0 && options.responseUri !== void 0 && options.nonce !== void 0) {
9620
+ if (options.sessionTranscriptProfile === "openid4vp-1.0") {
9621
+ nextOptions.mdocSessionTranscript = await buildOpenID4VPHandoverSessionTranscript({
9622
+ clientId: options.clientId,
9623
+ nonce: options.nonce,
9624
+ responseUri: options.responseUri,
9625
+ verifierEncryptionJwk: options.verifierEncryptionJwk
9626
+ });
9627
+ } else {
9628
+ const mdocGeneratedNonce = extractMdocGeneratedNonce(envelope.response);
9629
+ if (mdocGeneratedNonce !== void 0) {
9630
+ nextOptions.mdocSessionTranscript = await buildOid4vpSessionTranscript({
9631
+ clientId: options.clientId,
9632
+ responseUri: options.responseUri,
9633
+ nonce: options.nonce,
9634
+ mdocGeneratedNonce
9635
+ });
9636
+ }
9637
+ }
9638
+ }
9288
9639
  const decrypted = await decryptAuthorizationResponse(
9289
9640
  envelope.response,
9290
9641
  options.decryptionKey
9291
9642
  );
9292
- return verifyAuthorizationResponse(decrypted, query, options);
9643
+ return verifyAuthorizationResponse(decrypted, query, nextOptions);
9293
9644
  }
9294
9645
  if (!isAuthorizationResponse(envelope)) {
9295
9646
  throw new TypeError("envelope must contain vp_token as a JSON object per OpenID4VP 1.0 \xA78.1");
@@ -9314,7 +9665,8 @@ async function verifyAuthorizationResponse(envelope, query, options) {
9314
9665
  throw new exports.MultipleCredentialsNotSupportedError(queryIds.length, presentationCount);
9315
9666
  }
9316
9667
  const presentation = vpToken[queryIds[0]][0];
9317
- return verifyPresentation(presentation, query, options);
9668
+ const decodedPresentation = query.credentials[0]?.format === "mso_mdoc" && typeof presentation === "string" ? base64UrlToBytes(presentation) : presentation;
9669
+ return verifyPresentation(decodedPresentation, query, options);
9318
9670
  }
9319
9671
 
9320
9672
  // src/index.ts
@@ -10014,6 +10366,8 @@ exports.SdJwtParser = SdJwtParser;
10014
10366
  exports.StaticTrustStore = StaticTrustStore;
10015
10367
  exports.VERSION = VERSION;
10016
10368
  exports.buildHaipQuery = buildHaipQuery;
10369
+ exports.buildOid4vpSessionTranscript = buildOid4vpSessionTranscript;
10370
+ exports.buildOpenID4VPHandoverSessionTranscript = buildOpenID4VPHandoverSessionTranscript;
10017
10371
  exports.createAuthorizationRequest = createAuthorizationRequest;
10018
10372
  exports.createSignedAuthorizationRequest = createSignedAuthorizationRequest;
10019
10373
  exports.decryptAuthorizationResponse = decryptAuthorizationResponse;