@openeudi/openid4vp 0.8.1 → 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/README.md +91 -30
- package/dist/index.cjs +336 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +101 -1
- package/dist/index.d.ts +101 -1
- package/dist/index.js +335 -24
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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,
|
|
151
|
-
super(`Unsupported JWE algorithms: alg=${alg}, enc=${
|
|
150
|
+
constructor(alg, enc3) {
|
|
151
|
+
super(`Unsupported JWE algorithms: alg=${alg}, enc=${enc3}`);
|
|
152
152
|
this.alg = alg;
|
|
153
|
-
this.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,
|
|
337
|
+
static ToString(buffer, enc3 = "utf8") {
|
|
338
338
|
const buf = BufferSourceConverter.toUint8Array(buffer);
|
|
339
|
-
switch (
|
|
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 '${
|
|
356
|
+
throw new Error(`Unknown type of encoding '${enc3}'`);
|
|
357
357
|
}
|
|
358
358
|
}
|
|
359
|
-
static FromString(str,
|
|
359
|
+
static FromString(str, enc3 = "utf8") {
|
|
360
360
|
if (!str) {
|
|
361
361
|
return new ArrayBuffer(0);
|
|
362
362
|
}
|
|
363
|
-
switch (
|
|
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 '${
|
|
380
|
+
throw new Error(`Unknown type of encoding '${enc3}'`);
|
|
381
381
|
}
|
|
382
382
|
}
|
|
383
383
|
static ToBase64(buffer) {
|
|
@@ -8191,8 +8191,8 @@ async function createSignedAuthorizationRequest(input, query) {
|
|
|
8191
8191
|
vp_formats_supported: input.vpFormatsSupported
|
|
8192
8192
|
};
|
|
8193
8193
|
if (responseMode === "direct_post.jwt" && input.encryptionKey) {
|
|
8194
|
-
const
|
|
8195
|
-
if (
|
|
8194
|
+
const enc3 = input.encryptionKey.supportedEncValues;
|
|
8195
|
+
if (enc3 !== void 0 && enc3.length === 0) {
|
|
8196
8196
|
throw new exports.SignedRequestBuildError(
|
|
8197
8197
|
"empty_supported_enc_values",
|
|
8198
8198
|
"encryptionKey.supportedEncValues must not be empty (omit to use defaults [A128GCM, A256GCM])"
|
|
@@ -8200,7 +8200,7 @@ async function createSignedAuthorizationRequest(input, query) {
|
|
|
8200
8200
|
}
|
|
8201
8201
|
const jwk = { ...input.encryptionKey.publicJwk, use: "enc" };
|
|
8202
8202
|
clientMetadata.jwks = { keys: [jwk] };
|
|
8203
|
-
const encValues =
|
|
8203
|
+
const encValues = enc3 ?? [...DEFAULT_SUPPORTED_ENC_VALUES];
|
|
8204
8204
|
clientMetadata.encrypted_response_enc_values_supported = encValues;
|
|
8205
8205
|
clientMetadata.authorization_encrypted_response_alg = "ECDH-ES";
|
|
8206
8206
|
clientMetadata.authorization_encrypted_response_enc = encValues[0];
|
|
@@ -8474,6 +8474,131 @@ async function verifyCoseSign1(coseSign1, publicKey, allowedAlgorithms) {
|
|
|
8474
8474
|
}
|
|
8475
8475
|
}
|
|
8476
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
|
+
|
|
8477
8602
|
// src/crypto/digest.ts
|
|
8478
8603
|
init_errors();
|
|
8479
8604
|
var decoder2 = new cborX.Encoder({ mapsAsObjects: false, useRecords: false });
|
|
@@ -8606,6 +8731,14 @@ function decodeMso(payload) {
|
|
|
8606
8731
|
}
|
|
8607
8732
|
valueDigests.set(ns, perNs);
|
|
8608
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
|
+
}
|
|
8609
8742
|
const viRaw = m.get("validityInfo");
|
|
8610
8743
|
if (!(viRaw instanceof Map)) {
|
|
8611
8744
|
throw new exports.MalformedCredentialError("MSO.validityInfo must be a Map");
|
|
@@ -8619,7 +8752,7 @@ function decodeMso(payload) {
|
|
|
8619
8752
|
if (vi.has("expectedUpdate")) {
|
|
8620
8753
|
validityInfo.expectedUpdate = decodeDateLike(vi.get("expectedUpdate"));
|
|
8621
8754
|
}
|
|
8622
|
-
return { version, digestAlgorithm, valueDigests, docType, validityInfo };
|
|
8755
|
+
return { version, digestAlgorithm, valueDigests, docType, deviceKey, validityInfo };
|
|
8623
8756
|
}
|
|
8624
8757
|
function validateMsoValidity(mso, now) {
|
|
8625
8758
|
const { signed, validFrom, validUntil } = mso.validityInfo;
|
|
@@ -8669,15 +8802,54 @@ ${lines.join("\n")}
|
|
|
8669
8802
|
function extractCountryHintFromCert(_certDer) {
|
|
8670
8803
|
return "";
|
|
8671
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
|
+
}
|
|
8672
8841
|
function extractRawItemBytes(item) {
|
|
8673
8842
|
if (item instanceof cborX.Tag && item.tag === 24 && item.value instanceof Uint8Array) {
|
|
8674
|
-
return item.value;
|
|
8843
|
+
return wrapIssuerSignedItemBytes(item.value);
|
|
8675
8844
|
}
|
|
8676
8845
|
if (item instanceof Uint8Array) {
|
|
8677
|
-
|
|
8846
|
+
if (item.length >= 2 && item[0] === 216 && item[1] === 24) {
|
|
8847
|
+
return item;
|
|
8848
|
+
}
|
|
8849
|
+
return wrapIssuerSignedItemBytes(item);
|
|
8678
8850
|
}
|
|
8679
8851
|
if (item !== null && typeof item === "object" && !(item instanceof cborX.Tag) && !(item instanceof Map) && "value" in item && item.value instanceof Uint8Array) {
|
|
8680
|
-
return item.value;
|
|
8852
|
+
return wrapIssuerSignedItemBytes(item.value);
|
|
8681
8853
|
}
|
|
8682
8854
|
throw new exports.MalformedCredentialError("IssuerSignedItem must be a tag-24 byte string");
|
|
8683
8855
|
}
|
|
@@ -8867,6 +9039,54 @@ var MdocParser = class {
|
|
|
8867
9039
|
nameSpaces.set(ns, items.map(extractRawItemBytes));
|
|
8868
9040
|
}
|
|
8869
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
|
+
}
|
|
8870
9090
|
const { flat: claims, namespaced: namespacedClaims } = mapRawToClaims(nameSpaces);
|
|
8871
9091
|
const issuer = {
|
|
8872
9092
|
certificate: issuerCertBytes,
|
|
@@ -9139,11 +9359,21 @@ var SdJwtParser = class {
|
|
|
9139
9359
|
return invalidResult("Disclosure hash mismatch");
|
|
9140
9360
|
}
|
|
9141
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
|
+
}
|
|
9142
9370
|
if (parts.kbJwt) {
|
|
9143
|
-
|
|
9144
|
-
if (!cnf || typeof cnf !== "object" || !cnf["jwk"]) {
|
|
9371
|
+
if (!isHolderBound) {
|
|
9145
9372
|
throw new exports.MalformedCredentialError("Missing cnf.jwk in issuer JWT for key binding");
|
|
9146
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
|
+
}
|
|
9147
9377
|
let holderKey;
|
|
9148
9378
|
try {
|
|
9149
9379
|
const kbHeader = jose.decodeProtectedHeader(parts.kbJwt);
|
|
@@ -9261,9 +9491,9 @@ async function decryptAuthorizationResponse(jwe, privateKey) {
|
|
|
9261
9491
|
});
|
|
9262
9492
|
}
|
|
9263
9493
|
const alg = typeof header.alg === "string" ? header.alg : "";
|
|
9264
|
-
const
|
|
9265
|
-
if (alg !== SUPPORTED_ALG || !SUPPORTED_ENC.has(
|
|
9266
|
-
throw new exports.UnsupportedJweError(alg,
|
|
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);
|
|
9267
9497
|
}
|
|
9268
9498
|
let plaintext;
|
|
9269
9499
|
try {
|
|
@@ -9281,6 +9511,29 @@ async function decryptAuthorizationResponse(jwe, privateKey) {
|
|
|
9281
9511
|
});
|
|
9282
9512
|
}
|
|
9283
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
|
+
}
|
|
9284
9537
|
|
|
9285
9538
|
// src/verify.ts
|
|
9286
9539
|
init_errors();
|
|
@@ -9318,6 +9571,42 @@ async function verifyPresentation(vpToken, query, options) {
|
|
|
9318
9571
|
function isEncryptedResponse(x) {
|
|
9319
9572
|
return typeof x === "object" && x !== null && "response" in x && typeof x.response === "string" && Object.keys(x).length === 1;
|
|
9320
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
|
+
}
|
|
9321
9610
|
function isAuthorizationResponse(x) {
|
|
9322
9611
|
return typeof x === "object" && x !== null && "vp_token" in x && typeof x.vp_token === "object" && x.vp_token !== null;
|
|
9323
9612
|
}
|
|
@@ -9326,11 +9615,32 @@ async function verifyAuthorizationResponse(envelope, query, options) {
|
|
|
9326
9615
|
if (!options.decryptionKey) {
|
|
9327
9616
|
throw new exports.MissingDecryptionKeyError();
|
|
9328
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
|
+
}
|
|
9329
9639
|
const decrypted = await decryptAuthorizationResponse(
|
|
9330
9640
|
envelope.response,
|
|
9331
9641
|
options.decryptionKey
|
|
9332
9642
|
);
|
|
9333
|
-
return verifyAuthorizationResponse(decrypted, query,
|
|
9643
|
+
return verifyAuthorizationResponse(decrypted, query, nextOptions);
|
|
9334
9644
|
}
|
|
9335
9645
|
if (!isAuthorizationResponse(envelope)) {
|
|
9336
9646
|
throw new TypeError("envelope must contain vp_token as a JSON object per OpenID4VP 1.0 \xA78.1");
|
|
@@ -9355,7 +9665,8 @@ async function verifyAuthorizationResponse(envelope, query, options) {
|
|
|
9355
9665
|
throw new exports.MultipleCredentialsNotSupportedError(queryIds.length, presentationCount);
|
|
9356
9666
|
}
|
|
9357
9667
|
const presentation = vpToken[queryIds[0]][0];
|
|
9358
|
-
|
|
9668
|
+
const decodedPresentation = query.credentials[0]?.format === "mso_mdoc" && typeof presentation === "string" ? base64UrlToBytes(presentation) : presentation;
|
|
9669
|
+
return verifyPresentation(decodedPresentation, query, options);
|
|
9359
9670
|
}
|
|
9360
9671
|
|
|
9361
9672
|
// src/index.ts
|
|
@@ -10055,6 +10366,8 @@ exports.SdJwtParser = SdJwtParser;
|
|
|
10055
10366
|
exports.StaticTrustStore = StaticTrustStore;
|
|
10056
10367
|
exports.VERSION = VERSION;
|
|
10057
10368
|
exports.buildHaipQuery = buildHaipQuery;
|
|
10369
|
+
exports.buildOid4vpSessionTranscript = buildOid4vpSessionTranscript;
|
|
10370
|
+
exports.buildOpenID4VPHandoverSessionTranscript = buildOpenID4VPHandoverSessionTranscript;
|
|
10058
10371
|
exports.createAuthorizationRequest = createAuthorizationRequest;
|
|
10059
10372
|
exports.createSignedAuthorizationRequest = createSignedAuthorizationRequest;
|
|
10060
10373
|
exports.decryptAuthorizationResponse = decryptAuthorizationResponse;
|