@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.cjs
CHANGED
|
@@ -40,7 +40,7 @@ var __export = (target, all) => {
|
|
|
40
40
|
};
|
|
41
41
|
|
|
42
42
|
// src/errors.ts
|
|
43
|
-
exports.InvalidSignatureError = void 0; exports.ExpiredCredentialError = void 0; exports.UnsupportedFormatError = void 0; exports.MalformedCredentialError = void 0; exports.NonceValidationError = void 0; exports.HaipValidationError = void 0; exports.OpenID4VPError = void 0; exports.TrustAnchorNotFoundError = void 0; exports.CertificateChainError = void 0; exports.RevokedCertificateError = void 0; exports.RevocationCheckFailedError = void 0; exports.LotlFetchError = void 0; exports.LotlSignatureError = void 0; exports.SignedRequestBuildError = void 0; exports.UnsupportedJweError = void 0; exports.DecryptionFailedError = void 0; exports.MissingDecryptionKeyError = void 0; exports.MultipleCredentialsNotSupportedError = void 0;
|
|
43
|
+
exports.InvalidSignatureError = void 0; exports.ExpiredCredentialError = void 0; exports.UnsupportedFormatError = void 0; exports.MalformedCredentialError = void 0; exports.NonceValidationError = void 0; exports.HaipValidationError = void 0; exports.OpenID4VPError = void 0; exports.TrustAnchorNotFoundError = void 0; exports.CertificateChainError = void 0; exports.RevokedCertificateError = void 0; exports.RevocationCheckFailedError = void 0; exports.LotlFetchError = void 0; exports.LotlSignatureError = void 0; exports.SignedRequestBuildError = void 0; exports.UnsupportedJweError = void 0; exports.DecryptionFailedError = void 0; exports.MissingDecryptionKeyError = void 0; exports.MissingVerifierEncryptionKeyError = void 0; exports.MultipleCredentialsNotSupportedError = void 0;
|
|
44
44
|
var init_errors = __esm({
|
|
45
45
|
"src/errors.ts"() {
|
|
46
46
|
exports.InvalidSignatureError = class extends Error {
|
|
@@ -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 {
|
|
@@ -165,6 +165,12 @@ var init_errors = __esm({
|
|
|
165
165
|
super(message);
|
|
166
166
|
}
|
|
167
167
|
};
|
|
168
|
+
exports.MissingVerifierEncryptionKeyError = class extends exports.OpenID4VPError {
|
|
169
|
+
code = "missing_verifier_encryption_key";
|
|
170
|
+
constructor(message = "verifierEncryptionJwk is required to build the OID4VP 1.0 OpenID4VPHandover SessionTranscript for an encrypted response") {
|
|
171
|
+
super(message);
|
|
172
|
+
}
|
|
173
|
+
};
|
|
168
174
|
exports.MultipleCredentialsNotSupportedError = class extends exports.OpenID4VPError {
|
|
169
175
|
code = "multi_credential_unsupported";
|
|
170
176
|
entryCount;
|
|
@@ -334,9 +340,9 @@ var init_index_es = __esm({
|
|
|
334
340
|
static isBase64Url(data) {
|
|
335
341
|
return typeof data === STRING_TYPE && BASE64URL_REGEX.test(data);
|
|
336
342
|
}
|
|
337
|
-
static ToString(buffer,
|
|
343
|
+
static ToString(buffer, enc3 = "utf8") {
|
|
338
344
|
const buf = BufferSourceConverter.toUint8Array(buffer);
|
|
339
|
-
switch (
|
|
345
|
+
switch (enc3.toLowerCase()) {
|
|
340
346
|
case "utf8":
|
|
341
347
|
return this.ToUtf8String(buf);
|
|
342
348
|
case "binary":
|
|
@@ -353,14 +359,14 @@ var init_index_es = __esm({
|
|
|
353
359
|
case "utf16be":
|
|
354
360
|
return Utf16Converter.toString(buf);
|
|
355
361
|
default:
|
|
356
|
-
throw new Error(`Unknown type of encoding '${
|
|
362
|
+
throw new Error(`Unknown type of encoding '${enc3}'`);
|
|
357
363
|
}
|
|
358
364
|
}
|
|
359
|
-
static FromString(str,
|
|
365
|
+
static FromString(str, enc3 = "utf8") {
|
|
360
366
|
if (!str) {
|
|
361
367
|
return new ArrayBuffer(0);
|
|
362
368
|
}
|
|
363
|
-
switch (
|
|
369
|
+
switch (enc3.toLowerCase()) {
|
|
364
370
|
case "utf8":
|
|
365
371
|
return this.FromUtf8String(str);
|
|
366
372
|
case "binary":
|
|
@@ -377,7 +383,7 @@ var init_index_es = __esm({
|
|
|
377
383
|
case "utf16be":
|
|
378
384
|
return Utf16Converter.fromString(str);
|
|
379
385
|
default:
|
|
380
|
-
throw new Error(`Unknown type of encoding '${
|
|
386
|
+
throw new Error(`Unknown type of encoding '${enc3}'`);
|
|
381
387
|
}
|
|
382
388
|
}
|
|
383
389
|
static ToBase64(buffer) {
|
|
@@ -8191,8 +8197,8 @@ async function createSignedAuthorizationRequest(input, query) {
|
|
|
8191
8197
|
vp_formats_supported: input.vpFormatsSupported
|
|
8192
8198
|
};
|
|
8193
8199
|
if (responseMode === "direct_post.jwt" && input.encryptionKey) {
|
|
8194
|
-
const
|
|
8195
|
-
if (
|
|
8200
|
+
const enc3 = input.encryptionKey.supportedEncValues;
|
|
8201
|
+
if (enc3 !== void 0 && enc3.length === 0) {
|
|
8196
8202
|
throw new exports.SignedRequestBuildError(
|
|
8197
8203
|
"empty_supported_enc_values",
|
|
8198
8204
|
"encryptionKey.supportedEncValues must not be empty (omit to use defaults [A128GCM, A256GCM])"
|
|
@@ -8200,7 +8206,7 @@ async function createSignedAuthorizationRequest(input, query) {
|
|
|
8200
8206
|
}
|
|
8201
8207
|
const jwk = { ...input.encryptionKey.publicJwk, use: "enc" };
|
|
8202
8208
|
clientMetadata.jwks = { keys: [jwk] };
|
|
8203
|
-
const encValues =
|
|
8209
|
+
const encValues = enc3 ?? [...DEFAULT_SUPPORTED_ENC_VALUES];
|
|
8204
8210
|
clientMetadata.encrypted_response_enc_values_supported = encValues;
|
|
8205
8211
|
clientMetadata.authorization_encrypted_response_alg = "ECDH-ES";
|
|
8206
8212
|
clientMetadata.authorization_encrypted_response_enc = encValues[0];
|
|
@@ -8474,6 +8480,131 @@ async function verifyCoseSign1(coseSign1, publicKey, allowedAlgorithms) {
|
|
|
8474
8480
|
}
|
|
8475
8481
|
}
|
|
8476
8482
|
|
|
8483
|
+
// src/crypto/device-auth.ts
|
|
8484
|
+
init_errors();
|
|
8485
|
+
var enc = new cborX.Encoder({ mapsAsObjects: false, useRecords: false, tagUint8Array: false });
|
|
8486
|
+
var HEADER_ALG2 = 1;
|
|
8487
|
+
var COSE_ALG_TO_NAME2 = {
|
|
8488
|
+
[-7]: "ES256",
|
|
8489
|
+
[-35]: "ES384",
|
|
8490
|
+
[-36]: "ES512"
|
|
8491
|
+
};
|
|
8492
|
+
var ALG_TO_HASH2 = { ES256: "SHA-256", ES384: "SHA-384", ES512: "SHA-512" };
|
|
8493
|
+
var COSE_KEY_KTY = 1;
|
|
8494
|
+
var COSE_KEY_CRV = -1;
|
|
8495
|
+
var COSE_KEY_X = -2;
|
|
8496
|
+
var COSE_KEY_Y = -3;
|
|
8497
|
+
var COSE_KTY_EC2 = 2;
|
|
8498
|
+
var COSE_CRV_TO_JWK = { 1: "P-256", 2: "P-384", 3: "P-521" };
|
|
8499
|
+
function concatBytes(...chunks) {
|
|
8500
|
+
let len = 0;
|
|
8501
|
+
for (const c of chunks) len += c.length;
|
|
8502
|
+
const out = new Uint8Array(len);
|
|
8503
|
+
let off = 0;
|
|
8504
|
+
for (const c of chunks) {
|
|
8505
|
+
out.set(c, off);
|
|
8506
|
+
off += c.length;
|
|
8507
|
+
}
|
|
8508
|
+
return out;
|
|
8509
|
+
}
|
|
8510
|
+
function bytesToBase64Url(bytes) {
|
|
8511
|
+
let bin = "";
|
|
8512
|
+
for (const b of bytes) bin += String.fromCharCode(b);
|
|
8513
|
+
return globalThis.btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
8514
|
+
}
|
|
8515
|
+
function encodeTag24(inner) {
|
|
8516
|
+
return concatBytes(new Uint8Array([216, 24]), new Uint8Array(enc.encode(inner)));
|
|
8517
|
+
}
|
|
8518
|
+
function buildDeviceAuthenticationBytes(params) {
|
|
8519
|
+
const deviceAuthentication = concatBytes(
|
|
8520
|
+
new Uint8Array([132]),
|
|
8521
|
+
// array(4)
|
|
8522
|
+
new Uint8Array(enc.encode("DeviceAuthentication")),
|
|
8523
|
+
params.sessionTranscript,
|
|
8524
|
+
new Uint8Array(enc.encode(params.docType)),
|
|
8525
|
+
encodeTag24(params.deviceNameSpacesInner)
|
|
8526
|
+
);
|
|
8527
|
+
return encodeTag24(deviceAuthentication);
|
|
8528
|
+
}
|
|
8529
|
+
function encodeDeviceSigStructure(protectedBytes, deviceAuthBytes) {
|
|
8530
|
+
const sig = ["Signature1", protectedBytes, new Uint8Array(0), deviceAuthBytes];
|
|
8531
|
+
return new Uint8Array(enc.encode(sig));
|
|
8532
|
+
}
|
|
8533
|
+
async function importDeviceKeyFromCose(coseKey) {
|
|
8534
|
+
if (!(coseKey instanceof Map)) {
|
|
8535
|
+
throw new exports.MalformedCredentialError("MSO deviceKeyInfo.deviceKey is not a COSE_Key map");
|
|
8536
|
+
}
|
|
8537
|
+
const kty = coseKey.get(COSE_KEY_KTY);
|
|
8538
|
+
if (kty !== COSE_KTY_EC2) {
|
|
8539
|
+
throw new exports.MalformedCredentialError(`Unsupported COSE key type for device key: ${String(kty)}`);
|
|
8540
|
+
}
|
|
8541
|
+
const crv = coseKey.get(COSE_KEY_CRV);
|
|
8542
|
+
const crvName = typeof crv === "number" ? COSE_CRV_TO_JWK[crv] : void 0;
|
|
8543
|
+
const x = coseKey.get(COSE_KEY_X);
|
|
8544
|
+
const y = coseKey.get(COSE_KEY_Y);
|
|
8545
|
+
if (!crvName || !(x instanceof Uint8Array) || !(y instanceof Uint8Array)) {
|
|
8546
|
+
throw new exports.MalformedCredentialError("Malformed EC2 COSE_Key in MSO deviceKeyInfo");
|
|
8547
|
+
}
|
|
8548
|
+
return crypto.subtle.importKey(
|
|
8549
|
+
"jwk",
|
|
8550
|
+
{ kty: "EC", crv: crvName, x: bytesToBase64Url(x), y: bytesToBase64Url(y) },
|
|
8551
|
+
{ name: "ECDSA", namedCurve: crvName },
|
|
8552
|
+
false,
|
|
8553
|
+
["verify"]
|
|
8554
|
+
);
|
|
8555
|
+
}
|
|
8556
|
+
async function verifyDeviceAuth(params) {
|
|
8557
|
+
const { deviceSigned, deviceKeyCose, docType, sessionTranscript, allowedAlgorithms } = params;
|
|
8558
|
+
const deviceAuth = deviceSigned.get("deviceAuth");
|
|
8559
|
+
if (!(deviceAuth instanceof Map)) {
|
|
8560
|
+
throw new exports.MalformedCredentialError("deviceSigned.deviceAuth is missing or malformed");
|
|
8561
|
+
}
|
|
8562
|
+
if (deviceAuth.has("deviceMac") && !deviceAuth.has("deviceSignature")) {
|
|
8563
|
+
throw new exports.MalformedCredentialError("mDOC DeviceMac authentication is not supported");
|
|
8564
|
+
}
|
|
8565
|
+
const deviceSignature = deviceAuth.get("deviceSignature");
|
|
8566
|
+
if (!Array.isArray(deviceSignature) || deviceSignature.length !== 4) {
|
|
8567
|
+
throw new exports.MalformedCredentialError("deviceAuth.deviceSignature must be a COSE_Sign1 array");
|
|
8568
|
+
}
|
|
8569
|
+
const protectedBytes = deviceSignature[0];
|
|
8570
|
+
const signature = deviceSignature[3];
|
|
8571
|
+
if (!(protectedBytes instanceof Uint8Array) || !(signature instanceof Uint8Array)) {
|
|
8572
|
+
throw new exports.MalformedCredentialError("DeviceSignature protected header / signature must be byte strings");
|
|
8573
|
+
}
|
|
8574
|
+
let alg;
|
|
8575
|
+
try {
|
|
8576
|
+
const ph = enc.decode(protectedBytes);
|
|
8577
|
+
const label = ph instanceof Map ? ph.get(HEADER_ALG2) : void 0;
|
|
8578
|
+
alg = typeof label === "number" ? COSE_ALG_TO_NAME2[label] : void 0;
|
|
8579
|
+
} catch {
|
|
8580
|
+
throw new exports.MalformedCredentialError("DeviceSignature protected header is not decodable CBOR");
|
|
8581
|
+
}
|
|
8582
|
+
if (!alg || !allowedAlgorithms.includes(alg)) {
|
|
8583
|
+
throw new exports.MalformedCredentialError(`DeviceSignature uses a disallowed algorithm: ${String(alg)}`);
|
|
8584
|
+
}
|
|
8585
|
+
const deviceNameSpacesInner = extractTag24Inner(deviceSigned.get("nameSpaces"));
|
|
8586
|
+
const deviceAuthBytes = buildDeviceAuthenticationBytes({ sessionTranscript, docType, deviceNameSpacesInner });
|
|
8587
|
+
const sigInput = encodeDeviceSigStructure(protectedBytes, deviceAuthBytes);
|
|
8588
|
+
const deviceKey = await importDeviceKeyFromCose(deviceKeyCose);
|
|
8589
|
+
const ok = await crypto.subtle.verify(
|
|
8590
|
+
{ name: "ECDSA", hash: ALG_TO_HASH2[alg] },
|
|
8591
|
+
deviceKey,
|
|
8592
|
+
new Uint8Array(signature),
|
|
8593
|
+
sigInput
|
|
8594
|
+
);
|
|
8595
|
+
if (!ok) {
|
|
8596
|
+
throw new exports.MalformedCredentialError("mDOC device authentication (DeviceSignature) failed to verify");
|
|
8597
|
+
}
|
|
8598
|
+
}
|
|
8599
|
+
function extractTag24Inner(value) {
|
|
8600
|
+
if (value instanceof Uint8Array) return value;
|
|
8601
|
+
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.
|
|
8602
|
+
("tag" in value ? value.tag === 24 : true)) {
|
|
8603
|
+
return value.value;
|
|
8604
|
+
}
|
|
8605
|
+
throw new exports.MalformedCredentialError("deviceSigned.nameSpaces must be tag-24 wrapped CBOR");
|
|
8606
|
+
}
|
|
8607
|
+
|
|
8477
8608
|
// src/crypto/digest.ts
|
|
8478
8609
|
init_errors();
|
|
8479
8610
|
var decoder2 = new cborX.Encoder({ mapsAsObjects: false, useRecords: false });
|
|
@@ -8606,6 +8737,14 @@ function decodeMso(payload) {
|
|
|
8606
8737
|
}
|
|
8607
8738
|
valueDigests.set(ns, perNs);
|
|
8608
8739
|
}
|
|
8740
|
+
let deviceKey;
|
|
8741
|
+
const dkiRaw = m.get("deviceKeyInfo");
|
|
8742
|
+
if (dkiRaw instanceof Map) {
|
|
8743
|
+
const dk = dkiRaw.get("deviceKey");
|
|
8744
|
+
if (dk instanceof Map) {
|
|
8745
|
+
deviceKey = dk;
|
|
8746
|
+
}
|
|
8747
|
+
}
|
|
8609
8748
|
const viRaw = m.get("validityInfo");
|
|
8610
8749
|
if (!(viRaw instanceof Map)) {
|
|
8611
8750
|
throw new exports.MalformedCredentialError("MSO.validityInfo must be a Map");
|
|
@@ -8619,7 +8758,7 @@ function decodeMso(payload) {
|
|
|
8619
8758
|
if (vi.has("expectedUpdate")) {
|
|
8620
8759
|
validityInfo.expectedUpdate = decodeDateLike(vi.get("expectedUpdate"));
|
|
8621
8760
|
}
|
|
8622
|
-
return { version, digestAlgorithm, valueDigests, docType, validityInfo };
|
|
8761
|
+
return { version, digestAlgorithm, valueDigests, docType, deviceKey, validityInfo };
|
|
8623
8762
|
}
|
|
8624
8763
|
function validateMsoValidity(mso, now) {
|
|
8625
8764
|
const { signed, validFrom, validUntil } = mso.validityInfo;
|
|
@@ -8669,15 +8808,54 @@ ${lines.join("\n")}
|
|
|
8669
8808
|
function extractCountryHintFromCert(_certDer) {
|
|
8670
8809
|
return "";
|
|
8671
8810
|
}
|
|
8811
|
+
function cborByteStringHeader(length) {
|
|
8812
|
+
if (length < 24) return Uint8Array.of(64 | length);
|
|
8813
|
+
if (length < 256) return Uint8Array.of(88, length);
|
|
8814
|
+
if (length < 65536) return Uint8Array.of(89, length >> 8 & 255, length & 255);
|
|
8815
|
+
if (length < 4294967296) {
|
|
8816
|
+
return Uint8Array.of(
|
|
8817
|
+
90,
|
|
8818
|
+
length >>> 24 & 255,
|
|
8819
|
+
length >>> 16 & 255,
|
|
8820
|
+
length >>> 8 & 255,
|
|
8821
|
+
length & 255
|
|
8822
|
+
);
|
|
8823
|
+
}
|
|
8824
|
+
const hi = Math.floor(length / 4294967296);
|
|
8825
|
+
const lo = length >>> 0;
|
|
8826
|
+
return Uint8Array.of(
|
|
8827
|
+
91,
|
|
8828
|
+
hi >>> 24 & 255,
|
|
8829
|
+
hi >>> 16 & 255,
|
|
8830
|
+
hi >>> 8 & 255,
|
|
8831
|
+
hi & 255,
|
|
8832
|
+
lo >>> 24 & 255,
|
|
8833
|
+
lo >>> 16 & 255,
|
|
8834
|
+
lo >>> 8 & 255,
|
|
8835
|
+
lo & 255
|
|
8836
|
+
);
|
|
8837
|
+
}
|
|
8838
|
+
function wrapIssuerSignedItemBytes(inner) {
|
|
8839
|
+
const header = cborByteStringHeader(inner.length);
|
|
8840
|
+
const out = new Uint8Array(2 + header.length + inner.length);
|
|
8841
|
+
out[0] = 216;
|
|
8842
|
+
out[1] = 24;
|
|
8843
|
+
out.set(header, 2);
|
|
8844
|
+
out.set(inner, 2 + header.length);
|
|
8845
|
+
return out;
|
|
8846
|
+
}
|
|
8672
8847
|
function extractRawItemBytes(item) {
|
|
8673
8848
|
if (item instanceof cborX.Tag && item.tag === 24 && item.value instanceof Uint8Array) {
|
|
8674
|
-
return item.value;
|
|
8849
|
+
return wrapIssuerSignedItemBytes(item.value);
|
|
8675
8850
|
}
|
|
8676
8851
|
if (item instanceof Uint8Array) {
|
|
8677
|
-
|
|
8852
|
+
if (item.length >= 2 && item[0] === 216 && item[1] === 24) {
|
|
8853
|
+
return item;
|
|
8854
|
+
}
|
|
8855
|
+
return wrapIssuerSignedItemBytes(item);
|
|
8678
8856
|
}
|
|
8679
8857
|
if (item !== null && typeof item === "object" && !(item instanceof cborX.Tag) && !(item instanceof Map) && "value" in item && item.value instanceof Uint8Array) {
|
|
8680
|
-
return item.value;
|
|
8858
|
+
return wrapIssuerSignedItemBytes(item.value);
|
|
8681
8859
|
}
|
|
8682
8860
|
throw new exports.MalformedCredentialError("IssuerSignedItem must be a tag-24 byte string");
|
|
8683
8861
|
}
|
|
@@ -8867,6 +9045,54 @@ var MdocParser = class {
|
|
|
8867
9045
|
nameSpaces.set(ns, items.map(extractRawItemBytes));
|
|
8868
9046
|
}
|
|
8869
9047
|
await verifyAllDigests(nameSpaces, mso);
|
|
9048
|
+
const deviceSigned = document.get("deviceSigned");
|
|
9049
|
+
if (!(deviceSigned instanceof Map)) {
|
|
9050
|
+
return {
|
|
9051
|
+
valid: false,
|
|
9052
|
+
format: this.format,
|
|
9053
|
+
claims: {},
|
|
9054
|
+
issuer: { certificate: issuerCertBytes, country: "" },
|
|
9055
|
+
error: "Missing deviceSigned \u2014 mDOC device authentication is required"
|
|
9056
|
+
};
|
|
9057
|
+
}
|
|
9058
|
+
if (!(options.mdocSessionTranscript instanceof Uint8Array) || options.mdocSessionTranscript.length === 0) {
|
|
9059
|
+
return {
|
|
9060
|
+
valid: false,
|
|
9061
|
+
format: this.format,
|
|
9062
|
+
claims: {},
|
|
9063
|
+
issuer: { certificate: issuerCertBytes, country: "" },
|
|
9064
|
+
error: "mdocSessionTranscript is required to verify mDOC device authentication"
|
|
9065
|
+
};
|
|
9066
|
+
}
|
|
9067
|
+
if (!mso.deviceKey) {
|
|
9068
|
+
return {
|
|
9069
|
+
valid: false,
|
|
9070
|
+
format: this.format,
|
|
9071
|
+
claims: {},
|
|
9072
|
+
issuer: { certificate: issuerCertBytes, country: "" },
|
|
9073
|
+
error: "MSO is missing deviceKeyInfo.deviceKey; cannot verify device authentication"
|
|
9074
|
+
};
|
|
9075
|
+
}
|
|
9076
|
+
try {
|
|
9077
|
+
await verifyDeviceAuth({
|
|
9078
|
+
deviceSigned,
|
|
9079
|
+
deviceKeyCose: mso.deviceKey,
|
|
9080
|
+
docType: mso.docType,
|
|
9081
|
+
sessionTranscript: options.mdocSessionTranscript,
|
|
9082
|
+
allowedAlgorithms
|
|
9083
|
+
});
|
|
9084
|
+
} catch (err) {
|
|
9085
|
+
if (err instanceof exports.MalformedCredentialError) {
|
|
9086
|
+
return {
|
|
9087
|
+
valid: false,
|
|
9088
|
+
format: this.format,
|
|
9089
|
+
claims: {},
|
|
9090
|
+
issuer: { certificate: issuerCertBytes, country: "" },
|
|
9091
|
+
error: err.message
|
|
9092
|
+
};
|
|
9093
|
+
}
|
|
9094
|
+
throw err;
|
|
9095
|
+
}
|
|
8870
9096
|
const { flat: claims, namespaced: namespacedClaims } = mapRawToClaims(nameSpaces);
|
|
8871
9097
|
const issuer = {
|
|
8872
9098
|
certificate: issuerCertBytes,
|
|
@@ -9139,11 +9365,21 @@ var SdJwtParser = class {
|
|
|
9139
9365
|
return invalidResult("Disclosure hash mismatch");
|
|
9140
9366
|
}
|
|
9141
9367
|
}
|
|
9368
|
+
const cnf = payload["cnf"];
|
|
9369
|
+
const isHolderBound = !!cnf && typeof cnf === "object" && !!cnf["jwk"];
|
|
9370
|
+
const keyBindingRequired = isHolderBound || options.requireKeyBinding === true;
|
|
9371
|
+
if (keyBindingRequired && !parts.kbJwt) {
|
|
9372
|
+
return invalidResult(
|
|
9373
|
+
"Key binding JWT is required for holder-bound credentials but was not presented"
|
|
9374
|
+
);
|
|
9375
|
+
}
|
|
9142
9376
|
if (parts.kbJwt) {
|
|
9143
|
-
|
|
9144
|
-
if (!cnf || typeof cnf !== "object" || !cnf["jwk"]) {
|
|
9377
|
+
if (!isHolderBound) {
|
|
9145
9378
|
throw new exports.MalformedCredentialError("Missing cnf.jwk in issuer JWT for key binding");
|
|
9146
9379
|
}
|
|
9380
|
+
if (typeof options.nonce !== "string" || options.nonce.length === 0) {
|
|
9381
|
+
return invalidResult("A nonce is required to verify the key binding JWT");
|
|
9382
|
+
}
|
|
9147
9383
|
let holderKey;
|
|
9148
9384
|
try {
|
|
9149
9385
|
const kbHeader = jose.decodeProtectedHeader(parts.kbJwt);
|
|
@@ -9261,9 +9497,9 @@ async function decryptAuthorizationResponse(jwe, privateKey) {
|
|
|
9261
9497
|
});
|
|
9262
9498
|
}
|
|
9263
9499
|
const alg = typeof header.alg === "string" ? header.alg : "";
|
|
9264
|
-
const
|
|
9265
|
-
if (alg !== SUPPORTED_ALG || !SUPPORTED_ENC.has(
|
|
9266
|
-
throw new exports.UnsupportedJweError(alg,
|
|
9500
|
+
const enc3 = typeof header.enc === "string" ? header.enc : "";
|
|
9501
|
+
if (alg !== SUPPORTED_ALG || !SUPPORTED_ENC.has(enc3)) {
|
|
9502
|
+
throw new exports.UnsupportedJweError(alg, enc3);
|
|
9267
9503
|
}
|
|
9268
9504
|
let plaintext;
|
|
9269
9505
|
try {
|
|
@@ -9281,6 +9517,32 @@ async function decryptAuthorizationResponse(jwe, privateKey) {
|
|
|
9281
9517
|
});
|
|
9282
9518
|
}
|
|
9283
9519
|
}
|
|
9520
|
+
var enc2 = new cborX.Encoder({ mapsAsObjects: false, useRecords: false, tagUint8Array: false });
|
|
9521
|
+
async function sha256(bytes) {
|
|
9522
|
+
return new Uint8Array(await crypto.subtle.digest("SHA-256", bytes));
|
|
9523
|
+
}
|
|
9524
|
+
async function buildOid4vpSessionTranscript(params) {
|
|
9525
|
+
const { clientId, responseUri, nonce, mdocGeneratedNonce } = params;
|
|
9526
|
+
const clientIdHash = await sha256(new Uint8Array(enc2.encode([clientId, mdocGeneratedNonce])));
|
|
9527
|
+
const responseUriHash = await sha256(new Uint8Array(enc2.encode([responseUri, mdocGeneratedNonce])));
|
|
9528
|
+
return new Uint8Array(enc2.encode([null, null, [clientIdHash, responseUriHash, nonce]]));
|
|
9529
|
+
}
|
|
9530
|
+
async function jwkSha256Thumbprint(jwk) {
|
|
9531
|
+
if (jwk.kty !== "EC") {
|
|
9532
|
+
throw new Error(`Unsupported JWK kty for thumbprint: ${jwk.kty}`);
|
|
9533
|
+
}
|
|
9534
|
+
if (!jwk.crv || !jwk.x || !jwk.y) {
|
|
9535
|
+
throw new Error("EC JWK for thumbprint is missing a required member (crv, x, or y)");
|
|
9536
|
+
}
|
|
9537
|
+
const canonical = `{"crv":"${jwk.crv}","kty":"EC","x":"${jwk.x}","y":"${jwk.y}"}`;
|
|
9538
|
+
return sha256(new TextEncoder().encode(canonical));
|
|
9539
|
+
}
|
|
9540
|
+
async function buildOpenID4VPHandoverSessionTranscript(params) {
|
|
9541
|
+
const { clientId, nonce, responseUri, verifierEncryptionJwk } = params;
|
|
9542
|
+
const thumb = verifierEncryptionJwk ? await jwkSha256Thumbprint(verifierEncryptionJwk) : null;
|
|
9543
|
+
const infoHash = await sha256(new Uint8Array(enc2.encode([clientId, nonce, thumb, responseUri])));
|
|
9544
|
+
return new Uint8Array(enc2.encode([null, null, ["OpenID4VPHandover", infoHash]]));
|
|
9545
|
+
}
|
|
9284
9546
|
|
|
9285
9547
|
// src/verify.ts
|
|
9286
9548
|
init_errors();
|
|
@@ -9318,6 +9580,42 @@ async function verifyPresentation(vpToken, query, options) {
|
|
|
9318
9580
|
function isEncryptedResponse(x) {
|
|
9319
9581
|
return typeof x === "object" && x !== null && "response" in x && typeof x.response === "string" && Object.keys(x).length === 1;
|
|
9320
9582
|
}
|
|
9583
|
+
var BASE64URL_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
9584
|
+
function base64UrlToBytes(input) {
|
|
9585
|
+
const cleaned = input.replace(/=+$/, "");
|
|
9586
|
+
const byteLength = Math.floor(cleaned.length * 3 / 4);
|
|
9587
|
+
const bytes = new Uint8Array(byteLength);
|
|
9588
|
+
let byteIndex = 0;
|
|
9589
|
+
for (let i = 0; i < cleaned.length; i += 4) {
|
|
9590
|
+
const a = BASE64URL_CHARS.indexOf(cleaned[i]);
|
|
9591
|
+
const b = i + 1 < cleaned.length ? BASE64URL_CHARS.indexOf(cleaned[i + 1]) : 0;
|
|
9592
|
+
const c = i + 2 < cleaned.length ? BASE64URL_CHARS.indexOf(cleaned[i + 2]) : 0;
|
|
9593
|
+
const d = i + 3 < cleaned.length ? BASE64URL_CHARS.indexOf(cleaned[i + 3]) : 0;
|
|
9594
|
+
if (a === -1 || b === -1 || c === -1 || d === -1) {
|
|
9595
|
+
throw new TypeError("Invalid base64url character in vp_token/apu");
|
|
9596
|
+
}
|
|
9597
|
+
bytes[byteIndex++] = a << 2 | b >> 4;
|
|
9598
|
+
if (byteIndex < byteLength) bytes[byteIndex++] = (b & 15) << 4 | c >> 2;
|
|
9599
|
+
if (byteIndex < byteLength) bytes[byteIndex++] = (c & 3) << 6 | d;
|
|
9600
|
+
}
|
|
9601
|
+
return bytes;
|
|
9602
|
+
}
|
|
9603
|
+
function extractMdocGeneratedNonce(jwe) {
|
|
9604
|
+
let apu;
|
|
9605
|
+
try {
|
|
9606
|
+
apu = jose.decodeProtectedHeader(jwe).apu;
|
|
9607
|
+
} catch {
|
|
9608
|
+
return void 0;
|
|
9609
|
+
}
|
|
9610
|
+
if (typeof apu !== "string" || apu.length === 0) {
|
|
9611
|
+
return void 0;
|
|
9612
|
+
}
|
|
9613
|
+
try {
|
|
9614
|
+
return new TextDecoder().decode(base64UrlToBytes(apu));
|
|
9615
|
+
} catch {
|
|
9616
|
+
return void 0;
|
|
9617
|
+
}
|
|
9618
|
+
}
|
|
9321
9619
|
function isAuthorizationResponse(x) {
|
|
9322
9620
|
return typeof x === "object" && x !== null && "vp_token" in x && typeof x.vp_token === "object" && x.vp_token !== null;
|
|
9323
9621
|
}
|
|
@@ -9326,11 +9624,35 @@ async function verifyAuthorizationResponse(envelope, query, options) {
|
|
|
9326
9624
|
if (!options.decryptionKey) {
|
|
9327
9625
|
throw new exports.MissingDecryptionKeyError();
|
|
9328
9626
|
}
|
|
9627
|
+
const nextOptions = { ...options };
|
|
9628
|
+
if (options.mdocSessionTranscript === void 0 && options.clientId !== void 0 && options.responseUri !== void 0 && options.nonce !== void 0) {
|
|
9629
|
+
if (options.sessionTranscriptProfile === "openid4vp-1.0") {
|
|
9630
|
+
if (options.verifierEncryptionJwk === void 0) {
|
|
9631
|
+
throw new exports.MissingVerifierEncryptionKeyError();
|
|
9632
|
+
}
|
|
9633
|
+
nextOptions.mdocSessionTranscript = await buildOpenID4VPHandoverSessionTranscript({
|
|
9634
|
+
clientId: options.clientId,
|
|
9635
|
+
nonce: options.nonce,
|
|
9636
|
+
responseUri: options.responseUri,
|
|
9637
|
+
verifierEncryptionJwk: options.verifierEncryptionJwk
|
|
9638
|
+
});
|
|
9639
|
+
} else {
|
|
9640
|
+
const mdocGeneratedNonce = extractMdocGeneratedNonce(envelope.response);
|
|
9641
|
+
if (mdocGeneratedNonce !== void 0) {
|
|
9642
|
+
nextOptions.mdocSessionTranscript = await buildOid4vpSessionTranscript({
|
|
9643
|
+
clientId: options.clientId,
|
|
9644
|
+
responseUri: options.responseUri,
|
|
9645
|
+
nonce: options.nonce,
|
|
9646
|
+
mdocGeneratedNonce
|
|
9647
|
+
});
|
|
9648
|
+
}
|
|
9649
|
+
}
|
|
9650
|
+
}
|
|
9329
9651
|
const decrypted = await decryptAuthorizationResponse(
|
|
9330
9652
|
envelope.response,
|
|
9331
9653
|
options.decryptionKey
|
|
9332
9654
|
);
|
|
9333
|
-
return verifyAuthorizationResponse(decrypted, query,
|
|
9655
|
+
return verifyAuthorizationResponse(decrypted, query, nextOptions);
|
|
9334
9656
|
}
|
|
9335
9657
|
if (!isAuthorizationResponse(envelope)) {
|
|
9336
9658
|
throw new TypeError("envelope must contain vp_token as a JSON object per OpenID4VP 1.0 \xA78.1");
|
|
@@ -9355,7 +9677,8 @@ async function verifyAuthorizationResponse(envelope, query, options) {
|
|
|
9355
9677
|
throw new exports.MultipleCredentialsNotSupportedError(queryIds.length, presentationCount);
|
|
9356
9678
|
}
|
|
9357
9679
|
const presentation = vpToken[queryIds[0]][0];
|
|
9358
|
-
|
|
9680
|
+
const decodedPresentation = query.credentials[0]?.format === "mso_mdoc" && typeof presentation === "string" ? base64UrlToBytes(presentation) : presentation;
|
|
9681
|
+
return verifyPresentation(decodedPresentation, query, options);
|
|
9359
9682
|
}
|
|
9360
9683
|
|
|
9361
9684
|
// src/index.ts
|
|
@@ -10055,6 +10378,8 @@ exports.SdJwtParser = SdJwtParser;
|
|
|
10055
10378
|
exports.StaticTrustStore = StaticTrustStore;
|
|
10056
10379
|
exports.VERSION = VERSION;
|
|
10057
10380
|
exports.buildHaipQuery = buildHaipQuery;
|
|
10381
|
+
exports.buildOid4vpSessionTranscript = buildOid4vpSessionTranscript;
|
|
10382
|
+
exports.buildOpenID4VPHandoverSessionTranscript = buildOpenID4VPHandoverSessionTranscript;
|
|
10058
10383
|
exports.createAuthorizationRequest = createAuthorizationRequest;
|
|
10059
10384
|
exports.createSignedAuthorizationRequest = createSignedAuthorizationRequest;
|
|
10060
10385
|
exports.decryptAuthorizationResponse = decryptAuthorizationResponse;
|