@openeudi/openid4vp 0.8.1 → 0.9.1
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 +349 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +105 -1
- package/dist/index.d.ts +105 -1
- package/dist/index.js +348 -25
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -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, SignedRequestBuildError, UnsupportedJweError, DecryptionFailedError, MissingDecryptionKeyError, MultipleCredentialsNotSupportedError;
|
|
22
|
+
var InvalidSignatureError, ExpiredCredentialError, UnsupportedFormatError, MalformedCredentialError, NonceValidationError, HaipValidationError, OpenID4VPError, TrustAnchorNotFoundError, CertificateChainError, RevokedCertificateError, RevocationCheckFailedError, LotlFetchError, LotlSignatureError, SignedRequestBuildError, UnsupportedJweError, DecryptionFailedError, MissingDecryptionKeyError, MissingVerifierEncryptionKeyError, MultipleCredentialsNotSupportedError;
|
|
23
23
|
var init_errors = __esm({
|
|
24
24
|
"src/errors.ts"() {
|
|
25
25
|
InvalidSignatureError = class extends Error {
|
|
@@ -126,10 +126,10 @@ var init_errors = __esm({
|
|
|
126
126
|
code = "unsupported_jwe";
|
|
127
127
|
alg;
|
|
128
128
|
enc;
|
|
129
|
-
constructor(alg,
|
|
130
|
-
super(`Unsupported JWE algorithms: alg=${alg}, enc=${
|
|
129
|
+
constructor(alg, enc3) {
|
|
130
|
+
super(`Unsupported JWE algorithms: alg=${alg}, enc=${enc3}`);
|
|
131
131
|
this.alg = alg;
|
|
132
|
-
this.enc =
|
|
132
|
+
this.enc = enc3;
|
|
133
133
|
}
|
|
134
134
|
};
|
|
135
135
|
DecryptionFailedError = class extends OpenID4VPError {
|
|
@@ -144,6 +144,12 @@ var init_errors = __esm({
|
|
|
144
144
|
super(message);
|
|
145
145
|
}
|
|
146
146
|
};
|
|
147
|
+
MissingVerifierEncryptionKeyError = class extends OpenID4VPError {
|
|
148
|
+
code = "missing_verifier_encryption_key";
|
|
149
|
+
constructor(message = "verifierEncryptionJwk is required to build the OID4VP 1.0 OpenID4VPHandover SessionTranscript for an encrypted response") {
|
|
150
|
+
super(message);
|
|
151
|
+
}
|
|
152
|
+
};
|
|
147
153
|
MultipleCredentialsNotSupportedError = class extends OpenID4VPError {
|
|
148
154
|
code = "multi_credential_unsupported";
|
|
149
155
|
entryCount;
|
|
@@ -313,9 +319,9 @@ var init_index_es = __esm({
|
|
|
313
319
|
static isBase64Url(data) {
|
|
314
320
|
return typeof data === STRING_TYPE && BASE64URL_REGEX.test(data);
|
|
315
321
|
}
|
|
316
|
-
static ToString(buffer,
|
|
322
|
+
static ToString(buffer, enc3 = "utf8") {
|
|
317
323
|
const buf = BufferSourceConverter.toUint8Array(buffer);
|
|
318
|
-
switch (
|
|
324
|
+
switch (enc3.toLowerCase()) {
|
|
319
325
|
case "utf8":
|
|
320
326
|
return this.ToUtf8String(buf);
|
|
321
327
|
case "binary":
|
|
@@ -332,14 +338,14 @@ var init_index_es = __esm({
|
|
|
332
338
|
case "utf16be":
|
|
333
339
|
return Utf16Converter.toString(buf);
|
|
334
340
|
default:
|
|
335
|
-
throw new Error(`Unknown type of encoding '${
|
|
341
|
+
throw new Error(`Unknown type of encoding '${enc3}'`);
|
|
336
342
|
}
|
|
337
343
|
}
|
|
338
|
-
static FromString(str,
|
|
344
|
+
static FromString(str, enc3 = "utf8") {
|
|
339
345
|
if (!str) {
|
|
340
346
|
return new ArrayBuffer(0);
|
|
341
347
|
}
|
|
342
|
-
switch (
|
|
348
|
+
switch (enc3.toLowerCase()) {
|
|
343
349
|
case "utf8":
|
|
344
350
|
return this.FromUtf8String(str);
|
|
345
351
|
case "binary":
|
|
@@ -356,7 +362,7 @@ var init_index_es = __esm({
|
|
|
356
362
|
case "utf16be":
|
|
357
363
|
return Utf16Converter.fromString(str);
|
|
358
364
|
default:
|
|
359
|
-
throw new Error(`Unknown type of encoding '${
|
|
365
|
+
throw new Error(`Unknown type of encoding '${enc3}'`);
|
|
360
366
|
}
|
|
361
367
|
}
|
|
362
368
|
static ToBase64(buffer) {
|
|
@@ -8170,8 +8176,8 @@ async function createSignedAuthorizationRequest(input, query) {
|
|
|
8170
8176
|
vp_formats_supported: input.vpFormatsSupported
|
|
8171
8177
|
};
|
|
8172
8178
|
if (responseMode === "direct_post.jwt" && input.encryptionKey) {
|
|
8173
|
-
const
|
|
8174
|
-
if (
|
|
8179
|
+
const enc3 = input.encryptionKey.supportedEncValues;
|
|
8180
|
+
if (enc3 !== void 0 && enc3.length === 0) {
|
|
8175
8181
|
throw new SignedRequestBuildError(
|
|
8176
8182
|
"empty_supported_enc_values",
|
|
8177
8183
|
"encryptionKey.supportedEncValues must not be empty (omit to use defaults [A128GCM, A256GCM])"
|
|
@@ -8179,7 +8185,7 @@ async function createSignedAuthorizationRequest(input, query) {
|
|
|
8179
8185
|
}
|
|
8180
8186
|
const jwk = { ...input.encryptionKey.publicJwk, use: "enc" };
|
|
8181
8187
|
clientMetadata.jwks = { keys: [jwk] };
|
|
8182
|
-
const encValues =
|
|
8188
|
+
const encValues = enc3 ?? [...DEFAULT_SUPPORTED_ENC_VALUES];
|
|
8183
8189
|
clientMetadata.encrypted_response_enc_values_supported = encValues;
|
|
8184
8190
|
clientMetadata.authorization_encrypted_response_alg = "ECDH-ES";
|
|
8185
8191
|
clientMetadata.authorization_encrypted_response_enc = encValues[0];
|
|
@@ -8453,6 +8459,131 @@ async function verifyCoseSign1(coseSign1, publicKey, allowedAlgorithms) {
|
|
|
8453
8459
|
}
|
|
8454
8460
|
}
|
|
8455
8461
|
|
|
8462
|
+
// src/crypto/device-auth.ts
|
|
8463
|
+
init_errors();
|
|
8464
|
+
var enc = new Encoder({ mapsAsObjects: false, useRecords: false, tagUint8Array: false });
|
|
8465
|
+
var HEADER_ALG2 = 1;
|
|
8466
|
+
var COSE_ALG_TO_NAME2 = {
|
|
8467
|
+
[-7]: "ES256",
|
|
8468
|
+
[-35]: "ES384",
|
|
8469
|
+
[-36]: "ES512"
|
|
8470
|
+
};
|
|
8471
|
+
var ALG_TO_HASH2 = { ES256: "SHA-256", ES384: "SHA-384", ES512: "SHA-512" };
|
|
8472
|
+
var COSE_KEY_KTY = 1;
|
|
8473
|
+
var COSE_KEY_CRV = -1;
|
|
8474
|
+
var COSE_KEY_X = -2;
|
|
8475
|
+
var COSE_KEY_Y = -3;
|
|
8476
|
+
var COSE_KTY_EC2 = 2;
|
|
8477
|
+
var COSE_CRV_TO_JWK = { 1: "P-256", 2: "P-384", 3: "P-521" };
|
|
8478
|
+
function concatBytes(...chunks) {
|
|
8479
|
+
let len = 0;
|
|
8480
|
+
for (const c of chunks) len += c.length;
|
|
8481
|
+
const out = new Uint8Array(len);
|
|
8482
|
+
let off = 0;
|
|
8483
|
+
for (const c of chunks) {
|
|
8484
|
+
out.set(c, off);
|
|
8485
|
+
off += c.length;
|
|
8486
|
+
}
|
|
8487
|
+
return out;
|
|
8488
|
+
}
|
|
8489
|
+
function bytesToBase64Url(bytes) {
|
|
8490
|
+
let bin = "";
|
|
8491
|
+
for (const b of bytes) bin += String.fromCharCode(b);
|
|
8492
|
+
return globalThis.btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
8493
|
+
}
|
|
8494
|
+
function encodeTag24(inner) {
|
|
8495
|
+
return concatBytes(new Uint8Array([216, 24]), new Uint8Array(enc.encode(inner)));
|
|
8496
|
+
}
|
|
8497
|
+
function buildDeviceAuthenticationBytes(params) {
|
|
8498
|
+
const deviceAuthentication = concatBytes(
|
|
8499
|
+
new Uint8Array([132]),
|
|
8500
|
+
// array(4)
|
|
8501
|
+
new Uint8Array(enc.encode("DeviceAuthentication")),
|
|
8502
|
+
params.sessionTranscript,
|
|
8503
|
+
new Uint8Array(enc.encode(params.docType)),
|
|
8504
|
+
encodeTag24(params.deviceNameSpacesInner)
|
|
8505
|
+
);
|
|
8506
|
+
return encodeTag24(deviceAuthentication);
|
|
8507
|
+
}
|
|
8508
|
+
function encodeDeviceSigStructure(protectedBytes, deviceAuthBytes) {
|
|
8509
|
+
const sig = ["Signature1", protectedBytes, new Uint8Array(0), deviceAuthBytes];
|
|
8510
|
+
return new Uint8Array(enc.encode(sig));
|
|
8511
|
+
}
|
|
8512
|
+
async function importDeviceKeyFromCose(coseKey) {
|
|
8513
|
+
if (!(coseKey instanceof Map)) {
|
|
8514
|
+
throw new MalformedCredentialError("MSO deviceKeyInfo.deviceKey is not a COSE_Key map");
|
|
8515
|
+
}
|
|
8516
|
+
const kty = coseKey.get(COSE_KEY_KTY);
|
|
8517
|
+
if (kty !== COSE_KTY_EC2) {
|
|
8518
|
+
throw new MalformedCredentialError(`Unsupported COSE key type for device key: ${String(kty)}`);
|
|
8519
|
+
}
|
|
8520
|
+
const crv = coseKey.get(COSE_KEY_CRV);
|
|
8521
|
+
const crvName = typeof crv === "number" ? COSE_CRV_TO_JWK[crv] : void 0;
|
|
8522
|
+
const x = coseKey.get(COSE_KEY_X);
|
|
8523
|
+
const y = coseKey.get(COSE_KEY_Y);
|
|
8524
|
+
if (!crvName || !(x instanceof Uint8Array) || !(y instanceof Uint8Array)) {
|
|
8525
|
+
throw new MalformedCredentialError("Malformed EC2 COSE_Key in MSO deviceKeyInfo");
|
|
8526
|
+
}
|
|
8527
|
+
return crypto.subtle.importKey(
|
|
8528
|
+
"jwk",
|
|
8529
|
+
{ kty: "EC", crv: crvName, x: bytesToBase64Url(x), y: bytesToBase64Url(y) },
|
|
8530
|
+
{ name: "ECDSA", namedCurve: crvName },
|
|
8531
|
+
false,
|
|
8532
|
+
["verify"]
|
|
8533
|
+
);
|
|
8534
|
+
}
|
|
8535
|
+
async function verifyDeviceAuth(params) {
|
|
8536
|
+
const { deviceSigned, deviceKeyCose, docType, sessionTranscript, allowedAlgorithms } = params;
|
|
8537
|
+
const deviceAuth = deviceSigned.get("deviceAuth");
|
|
8538
|
+
if (!(deviceAuth instanceof Map)) {
|
|
8539
|
+
throw new MalformedCredentialError("deviceSigned.deviceAuth is missing or malformed");
|
|
8540
|
+
}
|
|
8541
|
+
if (deviceAuth.has("deviceMac") && !deviceAuth.has("deviceSignature")) {
|
|
8542
|
+
throw new MalformedCredentialError("mDOC DeviceMac authentication is not supported");
|
|
8543
|
+
}
|
|
8544
|
+
const deviceSignature = deviceAuth.get("deviceSignature");
|
|
8545
|
+
if (!Array.isArray(deviceSignature) || deviceSignature.length !== 4) {
|
|
8546
|
+
throw new MalformedCredentialError("deviceAuth.deviceSignature must be a COSE_Sign1 array");
|
|
8547
|
+
}
|
|
8548
|
+
const protectedBytes = deviceSignature[0];
|
|
8549
|
+
const signature = deviceSignature[3];
|
|
8550
|
+
if (!(protectedBytes instanceof Uint8Array) || !(signature instanceof Uint8Array)) {
|
|
8551
|
+
throw new MalformedCredentialError("DeviceSignature protected header / signature must be byte strings");
|
|
8552
|
+
}
|
|
8553
|
+
let alg;
|
|
8554
|
+
try {
|
|
8555
|
+
const ph = enc.decode(protectedBytes);
|
|
8556
|
+
const label = ph instanceof Map ? ph.get(HEADER_ALG2) : void 0;
|
|
8557
|
+
alg = typeof label === "number" ? COSE_ALG_TO_NAME2[label] : void 0;
|
|
8558
|
+
} catch {
|
|
8559
|
+
throw new MalformedCredentialError("DeviceSignature protected header is not decodable CBOR");
|
|
8560
|
+
}
|
|
8561
|
+
if (!alg || !allowedAlgorithms.includes(alg)) {
|
|
8562
|
+
throw new MalformedCredentialError(`DeviceSignature uses a disallowed algorithm: ${String(alg)}`);
|
|
8563
|
+
}
|
|
8564
|
+
const deviceNameSpacesInner = extractTag24Inner(deviceSigned.get("nameSpaces"));
|
|
8565
|
+
const deviceAuthBytes = buildDeviceAuthenticationBytes({ sessionTranscript, docType, deviceNameSpacesInner });
|
|
8566
|
+
const sigInput = encodeDeviceSigStructure(protectedBytes, deviceAuthBytes);
|
|
8567
|
+
const deviceKey = await importDeviceKeyFromCose(deviceKeyCose);
|
|
8568
|
+
const ok = await crypto.subtle.verify(
|
|
8569
|
+
{ name: "ECDSA", hash: ALG_TO_HASH2[alg] },
|
|
8570
|
+
deviceKey,
|
|
8571
|
+
new Uint8Array(signature),
|
|
8572
|
+
sigInput
|
|
8573
|
+
);
|
|
8574
|
+
if (!ok) {
|
|
8575
|
+
throw new MalformedCredentialError("mDOC device authentication (DeviceSignature) failed to verify");
|
|
8576
|
+
}
|
|
8577
|
+
}
|
|
8578
|
+
function extractTag24Inner(value) {
|
|
8579
|
+
if (value instanceof Uint8Array) return value;
|
|
8580
|
+
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.
|
|
8581
|
+
("tag" in value ? value.tag === 24 : true)) {
|
|
8582
|
+
return value.value;
|
|
8583
|
+
}
|
|
8584
|
+
throw new MalformedCredentialError("deviceSigned.nameSpaces must be tag-24 wrapped CBOR");
|
|
8585
|
+
}
|
|
8586
|
+
|
|
8456
8587
|
// src/crypto/digest.ts
|
|
8457
8588
|
init_errors();
|
|
8458
8589
|
var decoder2 = new Encoder({ mapsAsObjects: false, useRecords: false });
|
|
@@ -8585,6 +8716,14 @@ function decodeMso(payload) {
|
|
|
8585
8716
|
}
|
|
8586
8717
|
valueDigests.set(ns, perNs);
|
|
8587
8718
|
}
|
|
8719
|
+
let deviceKey;
|
|
8720
|
+
const dkiRaw = m.get("deviceKeyInfo");
|
|
8721
|
+
if (dkiRaw instanceof Map) {
|
|
8722
|
+
const dk = dkiRaw.get("deviceKey");
|
|
8723
|
+
if (dk instanceof Map) {
|
|
8724
|
+
deviceKey = dk;
|
|
8725
|
+
}
|
|
8726
|
+
}
|
|
8588
8727
|
const viRaw = m.get("validityInfo");
|
|
8589
8728
|
if (!(viRaw instanceof Map)) {
|
|
8590
8729
|
throw new MalformedCredentialError("MSO.validityInfo must be a Map");
|
|
@@ -8598,7 +8737,7 @@ function decodeMso(payload) {
|
|
|
8598
8737
|
if (vi.has("expectedUpdate")) {
|
|
8599
8738
|
validityInfo.expectedUpdate = decodeDateLike(vi.get("expectedUpdate"));
|
|
8600
8739
|
}
|
|
8601
|
-
return { version, digestAlgorithm, valueDigests, docType, validityInfo };
|
|
8740
|
+
return { version, digestAlgorithm, valueDigests, docType, deviceKey, validityInfo };
|
|
8602
8741
|
}
|
|
8603
8742
|
function validateMsoValidity(mso, now) {
|
|
8604
8743
|
const { signed, validFrom, validUntil } = mso.validityInfo;
|
|
@@ -8648,15 +8787,54 @@ ${lines.join("\n")}
|
|
|
8648
8787
|
function extractCountryHintFromCert(_certDer) {
|
|
8649
8788
|
return "";
|
|
8650
8789
|
}
|
|
8790
|
+
function cborByteStringHeader(length) {
|
|
8791
|
+
if (length < 24) return Uint8Array.of(64 | length);
|
|
8792
|
+
if (length < 256) return Uint8Array.of(88, length);
|
|
8793
|
+
if (length < 65536) return Uint8Array.of(89, length >> 8 & 255, length & 255);
|
|
8794
|
+
if (length < 4294967296) {
|
|
8795
|
+
return Uint8Array.of(
|
|
8796
|
+
90,
|
|
8797
|
+
length >>> 24 & 255,
|
|
8798
|
+
length >>> 16 & 255,
|
|
8799
|
+
length >>> 8 & 255,
|
|
8800
|
+
length & 255
|
|
8801
|
+
);
|
|
8802
|
+
}
|
|
8803
|
+
const hi = Math.floor(length / 4294967296);
|
|
8804
|
+
const lo = length >>> 0;
|
|
8805
|
+
return Uint8Array.of(
|
|
8806
|
+
91,
|
|
8807
|
+
hi >>> 24 & 255,
|
|
8808
|
+
hi >>> 16 & 255,
|
|
8809
|
+
hi >>> 8 & 255,
|
|
8810
|
+
hi & 255,
|
|
8811
|
+
lo >>> 24 & 255,
|
|
8812
|
+
lo >>> 16 & 255,
|
|
8813
|
+
lo >>> 8 & 255,
|
|
8814
|
+
lo & 255
|
|
8815
|
+
);
|
|
8816
|
+
}
|
|
8817
|
+
function wrapIssuerSignedItemBytes(inner) {
|
|
8818
|
+
const header = cborByteStringHeader(inner.length);
|
|
8819
|
+
const out = new Uint8Array(2 + header.length + inner.length);
|
|
8820
|
+
out[0] = 216;
|
|
8821
|
+
out[1] = 24;
|
|
8822
|
+
out.set(header, 2);
|
|
8823
|
+
out.set(inner, 2 + header.length);
|
|
8824
|
+
return out;
|
|
8825
|
+
}
|
|
8651
8826
|
function extractRawItemBytes(item) {
|
|
8652
8827
|
if (item instanceof Tag && item.tag === 24 && item.value instanceof Uint8Array) {
|
|
8653
|
-
return item.value;
|
|
8828
|
+
return wrapIssuerSignedItemBytes(item.value);
|
|
8654
8829
|
}
|
|
8655
8830
|
if (item instanceof Uint8Array) {
|
|
8656
|
-
|
|
8831
|
+
if (item.length >= 2 && item[0] === 216 && item[1] === 24) {
|
|
8832
|
+
return item;
|
|
8833
|
+
}
|
|
8834
|
+
return wrapIssuerSignedItemBytes(item);
|
|
8657
8835
|
}
|
|
8658
8836
|
if (item !== null && typeof item === "object" && !(item instanceof Tag) && !(item instanceof Map) && "value" in item && item.value instanceof Uint8Array) {
|
|
8659
|
-
return item.value;
|
|
8837
|
+
return wrapIssuerSignedItemBytes(item.value);
|
|
8660
8838
|
}
|
|
8661
8839
|
throw new MalformedCredentialError("IssuerSignedItem must be a tag-24 byte string");
|
|
8662
8840
|
}
|
|
@@ -8846,6 +9024,54 @@ var MdocParser = class {
|
|
|
8846
9024
|
nameSpaces.set(ns, items.map(extractRawItemBytes));
|
|
8847
9025
|
}
|
|
8848
9026
|
await verifyAllDigests(nameSpaces, mso);
|
|
9027
|
+
const deviceSigned = document.get("deviceSigned");
|
|
9028
|
+
if (!(deviceSigned instanceof Map)) {
|
|
9029
|
+
return {
|
|
9030
|
+
valid: false,
|
|
9031
|
+
format: this.format,
|
|
9032
|
+
claims: {},
|
|
9033
|
+
issuer: { certificate: issuerCertBytes, country: "" },
|
|
9034
|
+
error: "Missing deviceSigned \u2014 mDOC device authentication is required"
|
|
9035
|
+
};
|
|
9036
|
+
}
|
|
9037
|
+
if (!(options.mdocSessionTranscript instanceof Uint8Array) || options.mdocSessionTranscript.length === 0) {
|
|
9038
|
+
return {
|
|
9039
|
+
valid: false,
|
|
9040
|
+
format: this.format,
|
|
9041
|
+
claims: {},
|
|
9042
|
+
issuer: { certificate: issuerCertBytes, country: "" },
|
|
9043
|
+
error: "mdocSessionTranscript is required to verify mDOC device authentication"
|
|
9044
|
+
};
|
|
9045
|
+
}
|
|
9046
|
+
if (!mso.deviceKey) {
|
|
9047
|
+
return {
|
|
9048
|
+
valid: false,
|
|
9049
|
+
format: this.format,
|
|
9050
|
+
claims: {},
|
|
9051
|
+
issuer: { certificate: issuerCertBytes, country: "" },
|
|
9052
|
+
error: "MSO is missing deviceKeyInfo.deviceKey; cannot verify device authentication"
|
|
9053
|
+
};
|
|
9054
|
+
}
|
|
9055
|
+
try {
|
|
9056
|
+
await verifyDeviceAuth({
|
|
9057
|
+
deviceSigned,
|
|
9058
|
+
deviceKeyCose: mso.deviceKey,
|
|
9059
|
+
docType: mso.docType,
|
|
9060
|
+
sessionTranscript: options.mdocSessionTranscript,
|
|
9061
|
+
allowedAlgorithms
|
|
9062
|
+
});
|
|
9063
|
+
} catch (err) {
|
|
9064
|
+
if (err instanceof MalformedCredentialError) {
|
|
9065
|
+
return {
|
|
9066
|
+
valid: false,
|
|
9067
|
+
format: this.format,
|
|
9068
|
+
claims: {},
|
|
9069
|
+
issuer: { certificate: issuerCertBytes, country: "" },
|
|
9070
|
+
error: err.message
|
|
9071
|
+
};
|
|
9072
|
+
}
|
|
9073
|
+
throw err;
|
|
9074
|
+
}
|
|
8849
9075
|
const { flat: claims, namespaced: namespacedClaims } = mapRawToClaims(nameSpaces);
|
|
8850
9076
|
const issuer = {
|
|
8851
9077
|
certificate: issuerCertBytes,
|
|
@@ -9118,11 +9344,21 @@ var SdJwtParser = class {
|
|
|
9118
9344
|
return invalidResult("Disclosure hash mismatch");
|
|
9119
9345
|
}
|
|
9120
9346
|
}
|
|
9347
|
+
const cnf = payload["cnf"];
|
|
9348
|
+
const isHolderBound = !!cnf && typeof cnf === "object" && !!cnf["jwk"];
|
|
9349
|
+
const keyBindingRequired = isHolderBound || options.requireKeyBinding === true;
|
|
9350
|
+
if (keyBindingRequired && !parts.kbJwt) {
|
|
9351
|
+
return invalidResult(
|
|
9352
|
+
"Key binding JWT is required for holder-bound credentials but was not presented"
|
|
9353
|
+
);
|
|
9354
|
+
}
|
|
9121
9355
|
if (parts.kbJwt) {
|
|
9122
|
-
|
|
9123
|
-
if (!cnf || typeof cnf !== "object" || !cnf["jwk"]) {
|
|
9356
|
+
if (!isHolderBound) {
|
|
9124
9357
|
throw new MalformedCredentialError("Missing cnf.jwk in issuer JWT for key binding");
|
|
9125
9358
|
}
|
|
9359
|
+
if (typeof options.nonce !== "string" || options.nonce.length === 0) {
|
|
9360
|
+
return invalidResult("A nonce is required to verify the key binding JWT");
|
|
9361
|
+
}
|
|
9126
9362
|
let holderKey;
|
|
9127
9363
|
try {
|
|
9128
9364
|
const kbHeader = decodeProtectedHeader(parts.kbJwt);
|
|
@@ -9240,9 +9476,9 @@ async function decryptAuthorizationResponse(jwe, privateKey) {
|
|
|
9240
9476
|
});
|
|
9241
9477
|
}
|
|
9242
9478
|
const alg = typeof header.alg === "string" ? header.alg : "";
|
|
9243
|
-
const
|
|
9244
|
-
if (alg !== SUPPORTED_ALG || !SUPPORTED_ENC.has(
|
|
9245
|
-
throw new UnsupportedJweError(alg,
|
|
9479
|
+
const enc3 = typeof header.enc === "string" ? header.enc : "";
|
|
9480
|
+
if (alg !== SUPPORTED_ALG || !SUPPORTED_ENC.has(enc3)) {
|
|
9481
|
+
throw new UnsupportedJweError(alg, enc3);
|
|
9246
9482
|
}
|
|
9247
9483
|
let plaintext;
|
|
9248
9484
|
try {
|
|
@@ -9260,6 +9496,32 @@ async function decryptAuthorizationResponse(jwe, privateKey) {
|
|
|
9260
9496
|
});
|
|
9261
9497
|
}
|
|
9262
9498
|
}
|
|
9499
|
+
var enc2 = new Encoder({ mapsAsObjects: false, useRecords: false, tagUint8Array: false });
|
|
9500
|
+
async function sha256(bytes) {
|
|
9501
|
+
return new Uint8Array(await crypto.subtle.digest("SHA-256", bytes));
|
|
9502
|
+
}
|
|
9503
|
+
async function buildOid4vpSessionTranscript(params) {
|
|
9504
|
+
const { clientId, responseUri, nonce, mdocGeneratedNonce } = params;
|
|
9505
|
+
const clientIdHash = await sha256(new Uint8Array(enc2.encode([clientId, mdocGeneratedNonce])));
|
|
9506
|
+
const responseUriHash = await sha256(new Uint8Array(enc2.encode([responseUri, mdocGeneratedNonce])));
|
|
9507
|
+
return new Uint8Array(enc2.encode([null, null, [clientIdHash, responseUriHash, nonce]]));
|
|
9508
|
+
}
|
|
9509
|
+
async function jwkSha256Thumbprint(jwk) {
|
|
9510
|
+
if (jwk.kty !== "EC") {
|
|
9511
|
+
throw new Error(`Unsupported JWK kty for thumbprint: ${jwk.kty}`);
|
|
9512
|
+
}
|
|
9513
|
+
if (!jwk.crv || !jwk.x || !jwk.y) {
|
|
9514
|
+
throw new Error("EC JWK for thumbprint is missing a required member (crv, x, or y)");
|
|
9515
|
+
}
|
|
9516
|
+
const canonical = `{"crv":"${jwk.crv}","kty":"EC","x":"${jwk.x}","y":"${jwk.y}"}`;
|
|
9517
|
+
return sha256(new TextEncoder().encode(canonical));
|
|
9518
|
+
}
|
|
9519
|
+
async function buildOpenID4VPHandoverSessionTranscript(params) {
|
|
9520
|
+
const { clientId, nonce, responseUri, verifierEncryptionJwk } = params;
|
|
9521
|
+
const thumb = verifierEncryptionJwk ? await jwkSha256Thumbprint(verifierEncryptionJwk) : null;
|
|
9522
|
+
const infoHash = await sha256(new Uint8Array(enc2.encode([clientId, nonce, thumb, responseUri])));
|
|
9523
|
+
return new Uint8Array(enc2.encode([null, null, ["OpenID4VPHandover", infoHash]]));
|
|
9524
|
+
}
|
|
9263
9525
|
|
|
9264
9526
|
// src/verify.ts
|
|
9265
9527
|
init_errors();
|
|
@@ -9297,6 +9559,42 @@ async function verifyPresentation(vpToken, query, options) {
|
|
|
9297
9559
|
function isEncryptedResponse(x) {
|
|
9298
9560
|
return typeof x === "object" && x !== null && "response" in x && typeof x.response === "string" && Object.keys(x).length === 1;
|
|
9299
9561
|
}
|
|
9562
|
+
var BASE64URL_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
9563
|
+
function base64UrlToBytes(input) {
|
|
9564
|
+
const cleaned = input.replace(/=+$/, "");
|
|
9565
|
+
const byteLength = Math.floor(cleaned.length * 3 / 4);
|
|
9566
|
+
const bytes = new Uint8Array(byteLength);
|
|
9567
|
+
let byteIndex = 0;
|
|
9568
|
+
for (let i = 0; i < cleaned.length; i += 4) {
|
|
9569
|
+
const a = BASE64URL_CHARS.indexOf(cleaned[i]);
|
|
9570
|
+
const b = i + 1 < cleaned.length ? BASE64URL_CHARS.indexOf(cleaned[i + 1]) : 0;
|
|
9571
|
+
const c = i + 2 < cleaned.length ? BASE64URL_CHARS.indexOf(cleaned[i + 2]) : 0;
|
|
9572
|
+
const d = i + 3 < cleaned.length ? BASE64URL_CHARS.indexOf(cleaned[i + 3]) : 0;
|
|
9573
|
+
if (a === -1 || b === -1 || c === -1 || d === -1) {
|
|
9574
|
+
throw new TypeError("Invalid base64url character in vp_token/apu");
|
|
9575
|
+
}
|
|
9576
|
+
bytes[byteIndex++] = a << 2 | b >> 4;
|
|
9577
|
+
if (byteIndex < byteLength) bytes[byteIndex++] = (b & 15) << 4 | c >> 2;
|
|
9578
|
+
if (byteIndex < byteLength) bytes[byteIndex++] = (c & 3) << 6 | d;
|
|
9579
|
+
}
|
|
9580
|
+
return bytes;
|
|
9581
|
+
}
|
|
9582
|
+
function extractMdocGeneratedNonce(jwe) {
|
|
9583
|
+
let apu;
|
|
9584
|
+
try {
|
|
9585
|
+
apu = decodeProtectedHeader(jwe).apu;
|
|
9586
|
+
} catch {
|
|
9587
|
+
return void 0;
|
|
9588
|
+
}
|
|
9589
|
+
if (typeof apu !== "string" || apu.length === 0) {
|
|
9590
|
+
return void 0;
|
|
9591
|
+
}
|
|
9592
|
+
try {
|
|
9593
|
+
return new TextDecoder().decode(base64UrlToBytes(apu));
|
|
9594
|
+
} catch {
|
|
9595
|
+
return void 0;
|
|
9596
|
+
}
|
|
9597
|
+
}
|
|
9300
9598
|
function isAuthorizationResponse(x) {
|
|
9301
9599
|
return typeof x === "object" && x !== null && "vp_token" in x && typeof x.vp_token === "object" && x.vp_token !== null;
|
|
9302
9600
|
}
|
|
@@ -9305,11 +9603,35 @@ async function verifyAuthorizationResponse(envelope, query, options) {
|
|
|
9305
9603
|
if (!options.decryptionKey) {
|
|
9306
9604
|
throw new MissingDecryptionKeyError();
|
|
9307
9605
|
}
|
|
9606
|
+
const nextOptions = { ...options };
|
|
9607
|
+
if (options.mdocSessionTranscript === void 0 && options.clientId !== void 0 && options.responseUri !== void 0 && options.nonce !== void 0) {
|
|
9608
|
+
if (options.sessionTranscriptProfile === "openid4vp-1.0") {
|
|
9609
|
+
if (options.verifierEncryptionJwk === void 0) {
|
|
9610
|
+
throw new MissingVerifierEncryptionKeyError();
|
|
9611
|
+
}
|
|
9612
|
+
nextOptions.mdocSessionTranscript = await buildOpenID4VPHandoverSessionTranscript({
|
|
9613
|
+
clientId: options.clientId,
|
|
9614
|
+
nonce: options.nonce,
|
|
9615
|
+
responseUri: options.responseUri,
|
|
9616
|
+
verifierEncryptionJwk: options.verifierEncryptionJwk
|
|
9617
|
+
});
|
|
9618
|
+
} else {
|
|
9619
|
+
const mdocGeneratedNonce = extractMdocGeneratedNonce(envelope.response);
|
|
9620
|
+
if (mdocGeneratedNonce !== void 0) {
|
|
9621
|
+
nextOptions.mdocSessionTranscript = await buildOid4vpSessionTranscript({
|
|
9622
|
+
clientId: options.clientId,
|
|
9623
|
+
responseUri: options.responseUri,
|
|
9624
|
+
nonce: options.nonce,
|
|
9625
|
+
mdocGeneratedNonce
|
|
9626
|
+
});
|
|
9627
|
+
}
|
|
9628
|
+
}
|
|
9629
|
+
}
|
|
9308
9630
|
const decrypted = await decryptAuthorizationResponse(
|
|
9309
9631
|
envelope.response,
|
|
9310
9632
|
options.decryptionKey
|
|
9311
9633
|
);
|
|
9312
|
-
return verifyAuthorizationResponse(decrypted, query,
|
|
9634
|
+
return verifyAuthorizationResponse(decrypted, query, nextOptions);
|
|
9313
9635
|
}
|
|
9314
9636
|
if (!isAuthorizationResponse(envelope)) {
|
|
9315
9637
|
throw new TypeError("envelope must contain vp_token as a JSON object per OpenID4VP 1.0 \xA78.1");
|
|
@@ -9334,7 +9656,8 @@ async function verifyAuthorizationResponse(envelope, query, options) {
|
|
|
9334
9656
|
throw new MultipleCredentialsNotSupportedError(queryIds.length, presentationCount);
|
|
9335
9657
|
}
|
|
9336
9658
|
const presentation = vpToken[queryIds[0]][0];
|
|
9337
|
-
|
|
9659
|
+
const decodedPresentation = query.credentials[0]?.format === "mso_mdoc" && typeof presentation === "string" ? base64UrlToBytes(presentation) : presentation;
|
|
9660
|
+
return verifyPresentation(decodedPresentation, query, options);
|
|
9338
9661
|
}
|
|
9339
9662
|
|
|
9340
9663
|
// src/index.ts
|
|
@@ -10017,6 +10340,6 @@ asn1js/build/index.es.js:
|
|
|
10017
10340
|
*)
|
|
10018
10341
|
*/
|
|
10019
10342
|
|
|
10020
|
-
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 };
|
|
10343
|
+
export { CertificateChainError, CompositeTrustStore, DecryptionFailedError, ExpiredCredentialError, HAIP_DOCTYPE_NAMESPACES, HaipValidationError, InMemoryCache, InvalidSignatureError, LotlFetchError, LotlSignatureError, LotlTrustStore, MalformedCredentialError, MdocParser, MissingDecryptionKeyError, MissingVerifierEncryptionKeyError, MultipleCredentialsNotSupportedError, NonceValidationError, OpenID4VPError, RevocationCheckFailedError, RevokedCertificateError, SdJwtParser, SignedRequestBuildError, StaticTrustStore, TrustAnchorNotFoundError, UnsupportedFormatError, UnsupportedJweError, VERSION, buildHaipQuery, buildOid4vpSessionTranscript, buildOpenID4VPHandoverSessionTranscript, createAuthorizationRequest, createSignedAuthorizationRequest, decryptAuthorizationResponse, isHaipQuery, parsePresentation, validateHaipQuery, verifyAuthorizationResponse, verifyPresentation };
|
|
10021
10344
|
//# sourceMappingURL=index.js.map
|
|
10022
10345
|
//# sourceMappingURL=index.js.map
|