@originator-profile/verify 0.4.0 → 0.5.0-beta.2
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 +298 -254
- package/dist/index.d.cts +53 -135
- package/dist/index.d.mts +53 -135
- package/dist/index.mjs +297 -247
- package/package.json +9 -10
package/dist/index.cjs
CHANGED
|
@@ -4,6 +4,7 @@ var securingMechanism = require('@originator-profile/securing-mechanism');
|
|
|
4
4
|
var sign = require('@originator-profile/sign');
|
|
5
5
|
var cryptography = require('@originator-profile/cryptography');
|
|
6
6
|
var model = require('@originator-profile/model');
|
|
7
|
+
var zod = require('zod');
|
|
7
8
|
|
|
8
9
|
class CaInvalid extends Error {
|
|
9
10
|
constructor(message, result) {
|
|
@@ -26,34 +27,6 @@ class CaVerifyFailed extends Error {
|
|
|
26
27
|
code = CaVerifyFailed.code;
|
|
27
28
|
}
|
|
28
29
|
|
|
29
|
-
function verifyAllowedOrigin(origin, allowedOrigins) {
|
|
30
|
-
if (origin === "null") {
|
|
31
|
-
return false;
|
|
32
|
-
}
|
|
33
|
-
return [allowedOrigins].flat().includes(origin);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
async function importURLPatternPolyfill() {
|
|
37
|
-
if (typeof URLPattern === "undefined") {
|
|
38
|
-
await Promise.resolve().then(function () { return require('./index-D-j8gXz_.cjs'); });
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
function ReplaceEncode(url) {
|
|
42
|
-
return url.replace(/(%[0-9a-f]{2}?)+/g, function(match) {
|
|
43
|
-
return match.toUpperCase();
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
async function verifyAllowedUrl(url, allowedUrl) {
|
|
47
|
-
await importURLPatternPolyfill();
|
|
48
|
-
return [allowedUrl].flat().some((value) => {
|
|
49
|
-
if (!value) {
|
|
50
|
-
return false;
|
|
51
|
-
}
|
|
52
|
-
const pattern = new URLPattern(ReplaceEncode(value));
|
|
53
|
-
return pattern.test(ReplaceEncode(url));
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
|
|
57
30
|
const supportedHashAlgorithms = {
|
|
58
31
|
/** SHA-256 hash algorithm */
|
|
59
32
|
sha256: "SHA-256",
|
|
@@ -328,12 +301,62 @@ async function createIntegrityMetadataSet(hashAlgorithms, data, options = {
|
|
|
328
301
|
return new IntegrityMetadataSet(set, options);
|
|
329
302
|
}
|
|
330
303
|
|
|
304
|
+
const WARN_SUFFIX = `This will become an error after 2027. See: https://docs.originator-profile.org/en/opb/context/#the-image-datatype`;
|
|
331
305
|
async function verifyDigestSri(content, fetcher = fetch) {
|
|
332
306
|
const integrity = new IntegrityMetadataSet(content.digestSRI);
|
|
333
307
|
const alg = integrity.strongestHashAlgorithms.filter(Boolean);
|
|
334
308
|
if (alg.length === 0) return false;
|
|
335
|
-
|
|
336
|
-
|
|
309
|
+
try {
|
|
310
|
+
const result = await sign.createDigestSri(alg[0], content, fetcher);
|
|
311
|
+
return "digestSRI" in result && integrity.match(result.digestSRI);
|
|
312
|
+
} catch (error) {
|
|
313
|
+
console.error(
|
|
314
|
+
"Failed to access content for digestSRI verification:",
|
|
315
|
+
error
|
|
316
|
+
);
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
async function verifyImageDigestSri(value, fetcher = fetch) {
|
|
321
|
+
if (!value) return;
|
|
322
|
+
if (!value.digestSRI) {
|
|
323
|
+
console.warn(`digestSRI is missing. ${WARN_SUFFIX}`);
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
const valid = await verifyDigestSri(
|
|
327
|
+
{ id: value.id, digestSRI: value.digestSRI },
|
|
328
|
+
fetcher
|
|
329
|
+
);
|
|
330
|
+
if (!valid) {
|
|
331
|
+
console.warn(`digestSRI verification failed. ${WARN_SUFFIX}`);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
class IntegrityFetchFailed extends Error {
|
|
336
|
+
static get code() {
|
|
337
|
+
return "ERR_INTEGRITY_FETCH_FAILED";
|
|
338
|
+
}
|
|
339
|
+
code = IntegrityFetchFailed.code;
|
|
340
|
+
ok = false;
|
|
341
|
+
/** 取得結果 */
|
|
342
|
+
result;
|
|
343
|
+
constructor(message, result) {
|
|
344
|
+
super(message);
|
|
345
|
+
this.result = result;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
class IntegrityVerificationFailed extends Error {
|
|
349
|
+
static get code() {
|
|
350
|
+
return "ERR_INTEGRITY_VERIFICATION_FAILED";
|
|
351
|
+
}
|
|
352
|
+
code = IntegrityVerificationFailed.code;
|
|
353
|
+
ok = false;
|
|
354
|
+
/** 取得結果 */
|
|
355
|
+
result;
|
|
356
|
+
constructor(message, result) {
|
|
357
|
+
super(message);
|
|
358
|
+
this.result = result;
|
|
359
|
+
}
|
|
337
360
|
}
|
|
338
361
|
|
|
339
362
|
class IntegrityVerifier {
|
|
@@ -382,7 +405,47 @@ async function verifyIntegrity(content, doc = document, fetcher = fetch) {
|
|
|
382
405
|
(content2) => contentFetcher(content2, fetcher),
|
|
383
406
|
elementSelector
|
|
384
407
|
);
|
|
385
|
-
|
|
408
|
+
try {
|
|
409
|
+
return await integrityVerifier.verify(content, doc);
|
|
410
|
+
} catch (e) {
|
|
411
|
+
if (e instanceof sign.FetchFailed) {
|
|
412
|
+
return new IntegrityFetchFailed("Verify integrity failed", e.error);
|
|
413
|
+
}
|
|
414
|
+
return new IntegrityVerificationFailed("Verify integrity failed", e);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function verifyAllowedOrigin(origin, allowedOrigins) {
|
|
419
|
+
if (origin === "null") {
|
|
420
|
+
return false;
|
|
421
|
+
}
|
|
422
|
+
return [allowedOrigins].flat().includes(origin);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
async function importURLPatternPolyfill() {
|
|
426
|
+
if (typeof URLPattern === "undefined") {
|
|
427
|
+
await Promise.resolve().then(function () { return require('./index-D-j8gXz_.cjs'); });
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
function ReplaceEncode(url) {
|
|
431
|
+
return url.replace(/(%[0-9a-f]{2}?)+/g, function(match) {
|
|
432
|
+
return match.toUpperCase();
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
async function verifyAllowedUrl(url, allowedUrl) {
|
|
436
|
+
await importURLPatternPolyfill();
|
|
437
|
+
return [allowedUrl].flat().some((value) => {
|
|
438
|
+
if (!value) {
|
|
439
|
+
return false;
|
|
440
|
+
}
|
|
441
|
+
try {
|
|
442
|
+
const pattern = new URLPattern(ReplaceEncode(value));
|
|
443
|
+
return pattern.test(ReplaceEncode(url));
|
|
444
|
+
} catch (e) {
|
|
445
|
+
console.error(`Invalid URLPattern: ${value} (url: ${url})`);
|
|
446
|
+
}
|
|
447
|
+
return false;
|
|
448
|
+
});
|
|
386
449
|
}
|
|
387
450
|
|
|
388
451
|
async function checkUrlAndOrigin(result, url) {
|
|
@@ -408,6 +471,32 @@ async function checkUrlAndOrigin(result, url) {
|
|
|
408
471
|
}
|
|
409
472
|
return result;
|
|
410
473
|
}
|
|
474
|
+
function checkIntegrityResults(integrityResults, urlResult) {
|
|
475
|
+
const fetchFailedResults = integrityResults.filter(
|
|
476
|
+
(r) => r.verifyResult instanceof Error && r.verifyResult.code === IntegrityFetchFailed.code
|
|
477
|
+
);
|
|
478
|
+
if (fetchFailedResults.length > 0) {
|
|
479
|
+
const failedIntegritiesMessage = fetchFailedResults.map((result) => {
|
|
480
|
+
return `target[${result.index}] Expected: ${result.expectedIntegrity}`;
|
|
481
|
+
}).join(", ");
|
|
482
|
+
return new CaVerifyFailed(
|
|
483
|
+
`Content Attestation Target integrity fetch failed for element(s): ${failedIntegritiesMessage}`,
|
|
484
|
+
urlResult
|
|
485
|
+
);
|
|
486
|
+
}
|
|
487
|
+
const verificationFailedResults = integrityResults.filter(
|
|
488
|
+
(r) => r.verifyResult instanceof Error && r.verifyResult.code === IntegrityVerificationFailed.code
|
|
489
|
+
);
|
|
490
|
+
if (verificationFailedResults.length > 0) {
|
|
491
|
+
const failedIntegritiesMessage = verificationFailedResults.map((result) => {
|
|
492
|
+
return `target[${result.index}] Expected: ${result.expectedIntegrity}`;
|
|
493
|
+
}).join(", ");
|
|
494
|
+
return new CaVerifyFailed(
|
|
495
|
+
`Content Attestation Target integrity verification failed for element(s): ${failedIntegritiesMessage}`,
|
|
496
|
+
urlResult
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
411
500
|
function CaVerifier(ca, keys, issuer, url, verifyIntegrity$1 = verifyIntegrity, validator) {
|
|
412
501
|
const verifyCa = securingMechanism.JwtVcVerifier(keys, issuer, validator);
|
|
413
502
|
return async () => {
|
|
@@ -422,6 +511,9 @@ function CaVerifier(ca, keys, issuer, url, verifyIntegrity$1 = verifyIntegrity,
|
|
|
422
511
|
if (urlResult instanceof Error) {
|
|
423
512
|
return urlResult;
|
|
424
513
|
}
|
|
514
|
+
await verifyImageDigestSri(
|
|
515
|
+
urlResult.doc.credentialSubject.image
|
|
516
|
+
);
|
|
425
517
|
if (urlResult.doc.target) {
|
|
426
518
|
if (urlResult.doc.target.length === 0) {
|
|
427
519
|
return new CaInvalid("Target is empty", urlResult);
|
|
@@ -433,12 +525,19 @@ function CaVerifier(ca, keys, issuer, url, verifyIntegrity$1 = verifyIntegrity,
|
|
|
433
525
|
expectedIntegrity: t.integrity
|
|
434
526
|
}))
|
|
435
527
|
);
|
|
436
|
-
const
|
|
528
|
+
const error = checkIntegrityResults(integrityResults, urlResult);
|
|
529
|
+
if (error) {
|
|
530
|
+
return error;
|
|
531
|
+
}
|
|
532
|
+
const failedIndices = integrityResults.filter(
|
|
533
|
+
(result2) => !(result2.verifyResult instanceof Error) && !result2.verifyResult.valid
|
|
534
|
+
).map((result2) => result2.index);
|
|
437
535
|
if (failedIndices.length > 0) {
|
|
438
536
|
const failedIntegritiesMessage = failedIndices.map((integrityResultIndex) => {
|
|
439
537
|
const integrityResult = integrityResults[integrityResultIndex];
|
|
440
538
|
if (integrityResult) {
|
|
441
|
-
const
|
|
539
|
+
const verifyResult = integrityResult.verifyResult;
|
|
540
|
+
const calculatedIntegrities = verifyResult.failedIntegrities.join();
|
|
442
541
|
return `target[${integrityResultIndex}] Expected: ${integrityResult.expectedIntegrity}, Calculated: ${calculatedIntegrities}`;
|
|
443
542
|
}
|
|
444
543
|
return void 0;
|
|
@@ -514,91 +613,6 @@ async function verifyCas(cas, verifiedOps, url, verifyIntegrity, validator) {
|
|
|
514
613
|
return resultCas;
|
|
515
614
|
}
|
|
516
615
|
|
|
517
|
-
class ProfileGenericError extends Error {
|
|
518
|
-
static get code() {
|
|
519
|
-
return "ERR_PROFILE_GENERIC";
|
|
520
|
-
}
|
|
521
|
-
code = ProfileGenericError.code;
|
|
522
|
-
}
|
|
523
|
-
class ProfileClaimsValidationFailed extends ProfileGenericError {
|
|
524
|
-
static get code() {
|
|
525
|
-
return "ERR_PROFILE_CLAIMS_VALIDATION_FAILED";
|
|
526
|
-
}
|
|
527
|
-
code = ProfileClaimsValidationFailed.code;
|
|
528
|
-
/** 復号結果 */
|
|
529
|
-
result;
|
|
530
|
-
constructor(message, result) {
|
|
531
|
-
super(message);
|
|
532
|
-
this.result = result;
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
class ProfileTokenVerifyFailed extends ProfileGenericError {
|
|
536
|
-
static get code() {
|
|
537
|
-
return "ERR_PROFILE_TOKEN_VERIFY_FAILED";
|
|
538
|
-
}
|
|
539
|
-
code = ProfileTokenVerifyFailed.code;
|
|
540
|
-
/** 検証結果 */
|
|
541
|
-
result;
|
|
542
|
-
constructor(message, result) {
|
|
543
|
-
super(message);
|
|
544
|
-
this.result = result;
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
class ProfileBodyExtractFailed extends ProfileGenericError {
|
|
548
|
-
static get code() {
|
|
549
|
-
return "ERR_PROFILE_BODY_EXTRACT_FAILED";
|
|
550
|
-
}
|
|
551
|
-
code = ProfileBodyExtractFailed.code;
|
|
552
|
-
}
|
|
553
|
-
class ProfileBodyVerifyFailed extends ProfileGenericError {
|
|
554
|
-
static get code() {
|
|
555
|
-
return "ERR_PROFILE_BODY_VERIFY_FAILED";
|
|
556
|
-
}
|
|
557
|
-
code = ProfileBodyVerifyFailed.code;
|
|
558
|
-
/** 検証結果 */
|
|
559
|
-
result;
|
|
560
|
-
constructor(message, result) {
|
|
561
|
-
super(message);
|
|
562
|
-
this.result = result;
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
class ProfilesResolveFailed extends ProfileGenericError {
|
|
566
|
-
static get code() {
|
|
567
|
-
return "ERR_PROFILES_RESOLVE_FAILED";
|
|
568
|
-
}
|
|
569
|
-
code = ProfilesResolveFailed.code;
|
|
570
|
-
/** 検証結果 */
|
|
571
|
-
result;
|
|
572
|
-
constructor(message, result) {
|
|
573
|
-
super(message);
|
|
574
|
-
this.result = result;
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
class ProfilesVerifyFailed extends ProfileGenericError {
|
|
578
|
-
static get code() {
|
|
579
|
-
return "ERR_PROFILES_VERIFY_FAILED";
|
|
580
|
-
}
|
|
581
|
-
code = ProfilesVerifyFailed.code;
|
|
582
|
-
/** 検証結果 */
|
|
583
|
-
result;
|
|
584
|
-
constructor(message, result) {
|
|
585
|
-
super(message);
|
|
586
|
-
this.result = result;
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
class CertificationSystemValidationFailed extends ProfileGenericError {
|
|
590
|
-
static get code() {
|
|
591
|
-
return "ERR_CERTIFICATION_SYSTEM_VALIDATION_FAILED";
|
|
592
|
-
}
|
|
593
|
-
code = CertificationSystemValidationFailed.code;
|
|
594
|
-
/** 検証結果 */
|
|
595
|
-
result;
|
|
596
|
-
constructor(message, result) {
|
|
597
|
-
super(message);
|
|
598
|
-
this.result = result;
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
|
|
602
616
|
var REMOVE = "remove";
|
|
603
617
|
var REPLACE = "replace";
|
|
604
618
|
var ADD = "add";
|
|
@@ -887,8 +901,44 @@ class OpVerifyFailed extends Error {
|
|
|
887
901
|
}
|
|
888
902
|
code = OpVerifyFailed.code;
|
|
889
903
|
}
|
|
904
|
+
class CertificateExpired extends Error {
|
|
905
|
+
constructor(message, result) {
|
|
906
|
+
super(message);
|
|
907
|
+
this.result = result;
|
|
908
|
+
}
|
|
909
|
+
static get code() {
|
|
910
|
+
return "ERR_CERTIFICATE_EXPIRED";
|
|
911
|
+
}
|
|
912
|
+
code = CertificateExpired.code;
|
|
913
|
+
}
|
|
890
914
|
|
|
891
915
|
const isEveryDecodedPa = (annotations) => annotations.every((annotation) => "doc" in annotation);
|
|
916
|
+
const isEveryDecodedWmp = (media) => media.every((m) => "doc" in m);
|
|
917
|
+
const validateDecodedOp = (core, annotations, media, resultOp) => {
|
|
918
|
+
if (annotations && !isEveryDecodedPa(annotations)) {
|
|
919
|
+
return new OpInvalid("Profile Annotation decode failed", resultOp);
|
|
920
|
+
}
|
|
921
|
+
if (media && !isEveryDecodedWmp(media)) {
|
|
922
|
+
return new OpInvalid("Web Media Profile decode failed", resultOp);
|
|
923
|
+
}
|
|
924
|
+
if (media && media.some(
|
|
925
|
+
(m) => core.doc.credentialSubject.id !== m.doc.credentialSubject.id
|
|
926
|
+
)) {
|
|
927
|
+
return new OpInvalid(
|
|
928
|
+
"Subject mismatch between Core Profile and Web Media Profile",
|
|
929
|
+
resultOp
|
|
930
|
+
);
|
|
931
|
+
}
|
|
932
|
+
if (annotations && annotations.some(
|
|
933
|
+
(annotation) => core.doc.credentialSubject.id !== annotation.doc.credentialSubject.id
|
|
934
|
+
)) {
|
|
935
|
+
return new OpInvalid(
|
|
936
|
+
"Subject mismatch between Core Profile and Profile Annotation",
|
|
937
|
+
resultOp
|
|
938
|
+
);
|
|
939
|
+
}
|
|
940
|
+
return { type: "valid", annotations, media };
|
|
941
|
+
};
|
|
892
942
|
const isDecodedOps = (ops) => ops.every((op) => !(op instanceof OpInvalid));
|
|
893
943
|
function decodeOps(ops) {
|
|
894
944
|
const decodeCp = securingMechanism.JwtVcDecoder();
|
|
@@ -897,32 +947,22 @@ function decodeOps(ops) {
|
|
|
897
947
|
const resultOps = ops.map((op) => {
|
|
898
948
|
const core = decodeCp(op.core);
|
|
899
949
|
const annotations = op.annotations ? op.annotations.map(decodePa) : void 0;
|
|
900
|
-
const
|
|
950
|
+
const mediaInput = op.media;
|
|
951
|
+
const mediaArray = mediaInput ? Array.isArray(mediaInput) ? mediaInput : [mediaInput] : void 0;
|
|
952
|
+
const media = mediaArray ? mediaArray.map(decodeWmp) : void 0;
|
|
901
953
|
const resultOp = { core, annotations, media };
|
|
902
954
|
if (core instanceof Error) {
|
|
903
955
|
return new OpInvalid("Core Profile decode failed", resultOp);
|
|
904
956
|
}
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
if (media instanceof Error) {
|
|
909
|
-
return new OpInvalid("Web Media Profile decode failed", resultOp);
|
|
910
|
-
}
|
|
911
|
-
if (media && core.doc.credentialSubject.id !== media.doc.credentialSubject.id) {
|
|
912
|
-
return new OpInvalid(
|
|
913
|
-
"Subject mismatch between Core Profile and Web Media Profile",
|
|
914
|
-
resultOp
|
|
915
|
-
);
|
|
916
|
-
}
|
|
917
|
-
if (annotations && annotations.some(
|
|
918
|
-
(annotation) => core.doc.credentialSubject.id !== annotation.doc.credentialSubject.id
|
|
919
|
-
)) {
|
|
920
|
-
return new OpInvalid(
|
|
921
|
-
"Subject mismatch between Core Profile and Profile Annotation",
|
|
922
|
-
resultOp
|
|
923
|
-
);
|
|
957
|
+
const validated = validateDecodedOp(core, annotations, media, resultOp);
|
|
958
|
+
if (validated instanceof OpInvalid) {
|
|
959
|
+
return validated;
|
|
924
960
|
}
|
|
925
|
-
return
|
|
961
|
+
return {
|
|
962
|
+
core,
|
|
963
|
+
annotations: validated.annotations,
|
|
964
|
+
media: validated.media
|
|
965
|
+
};
|
|
926
966
|
});
|
|
927
967
|
if (!isDecodedOps(resultOps)) {
|
|
928
968
|
return new OpsInvalid("Invalid Originator Profile Set", resultOps);
|
|
@@ -939,29 +979,59 @@ function OpVerifier(paOrWmpIssuerKeys, vc, validator) {
|
|
|
939
979
|
const cpKeys = cryptography.LocalKeys(jwks);
|
|
940
980
|
return securingMechanism.JwtVcVerifier(cpKeys, issuer, validator);
|
|
941
981
|
}
|
|
982
|
+
function validateCertificateExpiry(verifiedVc) {
|
|
983
|
+
const now = /* @__PURE__ */ new Date();
|
|
984
|
+
const validFrom = verifiedVc.doc.validFrom ? new Date(verifiedVc.doc.validFrom) : null;
|
|
985
|
+
const validUntil = verifiedVc.doc.validUntil ? new Date(verifiedVc.doc.validUntil) : null;
|
|
986
|
+
if (validFrom && now < validFrom) {
|
|
987
|
+
return new CertificateExpired("Certificate not yet valid", verifiedVc);
|
|
988
|
+
}
|
|
989
|
+
if (validUntil && now > validUntil) {
|
|
990
|
+
return new CertificateExpired("Certificate expired", verifiedVc);
|
|
991
|
+
}
|
|
992
|
+
return verifiedVc;
|
|
993
|
+
}
|
|
942
994
|
async function verifyAnnotations(paIssuerKeys, annotations, validator) {
|
|
943
995
|
if (!annotations) return;
|
|
944
996
|
return await Promise.all(
|
|
945
|
-
annotations.map((annotation) => {
|
|
997
|
+
annotations.map(async (annotation) => {
|
|
946
998
|
const verify = OpVerifier(
|
|
947
999
|
paIssuerKeys,
|
|
948
1000
|
annotation,
|
|
949
|
-
validator?.(
|
|
950
|
-
|
|
951
|
-
|
|
1001
|
+
validator?.(zod.z.union([model.Certificate, model.JapaneseExistenceCertificate]))
|
|
1002
|
+
);
|
|
1003
|
+
const result = await verify(annotation.source);
|
|
1004
|
+
if (result instanceof Error) {
|
|
1005
|
+
return result;
|
|
1006
|
+
}
|
|
1007
|
+
const valid = validateCertificateExpiry(result);
|
|
1008
|
+
if (valid instanceof CertificateExpired) {
|
|
1009
|
+
return valid;
|
|
1010
|
+
}
|
|
1011
|
+
await verifyImageDigestSri(
|
|
1012
|
+
valid.doc.credentialSubject.image
|
|
952
1013
|
);
|
|
953
|
-
return
|
|
1014
|
+
return valid;
|
|
954
1015
|
})
|
|
955
1016
|
);
|
|
956
1017
|
}
|
|
957
1018
|
async function verifyMedia(wmpIssuerKeys, media, validator) {
|
|
958
1019
|
if (!media) return;
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
1020
|
+
return await Promise.all(
|
|
1021
|
+
media.map(async (m) => {
|
|
1022
|
+
const verify = OpVerifier(
|
|
1023
|
+
wmpIssuerKeys,
|
|
1024
|
+
m,
|
|
1025
|
+
validator?.(model.WebMediaProfile)
|
|
1026
|
+
);
|
|
1027
|
+
const result = await verify(m.source);
|
|
1028
|
+
if (result instanceof Error) {
|
|
1029
|
+
return result;
|
|
1030
|
+
}
|
|
1031
|
+
await verifyImageDigestSri(result.doc.credentialSubject.logo);
|
|
1032
|
+
return result;
|
|
1033
|
+
})
|
|
963
1034
|
);
|
|
964
|
-
return await verify(media.source);
|
|
965
1035
|
}
|
|
966
1036
|
const isVerifiedOps = (ops) => ops.every((op) => !(op instanceof OpVerifyFailed));
|
|
967
1037
|
function OpsVerifier(ops, keys, issuer, validator) {
|
|
@@ -995,7 +1065,7 @@ function OpsVerifier(ops, keys, issuer, validator) {
|
|
|
995
1065
|
resultOp
|
|
996
1066
|
);
|
|
997
1067
|
}
|
|
998
|
-
if (media instanceof Error) {
|
|
1068
|
+
if (media && media.some((m) => m instanceof Error)) {
|
|
999
1069
|
return new OpVerifyFailed(
|
|
1000
1070
|
"Web Media Profile verify failed",
|
|
1001
1071
|
resultOp
|
|
@@ -1036,146 +1106,120 @@ class SiteProfileVerifyFailed extends Error {
|
|
|
1036
1106
|
code = SiteProfileVerifyFailed.code;
|
|
1037
1107
|
}
|
|
1038
1108
|
|
|
1109
|
+
const decodeWebsiteProfiles = (sp, opsVerified) => {
|
|
1110
|
+
const wspSources = sp.sites || (sp.credential ? [sp.credential] : []);
|
|
1111
|
+
if (wspSources.length === 0) {
|
|
1112
|
+
return new SiteProfileInvalid("No Website Profile found", {
|
|
1113
|
+
originators: opsVerified,
|
|
1114
|
+
sites: []
|
|
1115
|
+
});
|
|
1116
|
+
}
|
|
1117
|
+
const decodeWsp = securingMechanism.JwtVcDecoder();
|
|
1118
|
+
const decodedWsps = wspSources.map(decodeWsp);
|
|
1119
|
+
const decodeErrors = decodedWsps.filter((wsp) => wsp instanceof Error);
|
|
1120
|
+
if (decodeErrors.length > 0) {
|
|
1121
|
+
return new SiteProfileInvalid("Website Profile invalid", {
|
|
1122
|
+
originators: opsVerified,
|
|
1123
|
+
sites: decodeErrors
|
|
1124
|
+
});
|
|
1125
|
+
}
|
|
1126
|
+
return {
|
|
1127
|
+
decodedWsps,
|
|
1128
|
+
wspSources
|
|
1129
|
+
};
|
|
1130
|
+
};
|
|
1039
1131
|
function SpVerifier(sp, keys, issuer, origin, verifyOrigin = true, validator) {
|
|
1040
1132
|
async function verify() {
|
|
1041
1133
|
const verifyOps = OpsVerifier(sp.originators, keys, issuer, validator);
|
|
1042
1134
|
const opsVerified = await verifyOps();
|
|
1043
1135
|
if (opsVerified instanceof OpsInvalid) {
|
|
1044
1136
|
return new SiteProfileInvalid("Originator Profile Set invalid", {
|
|
1045
|
-
originators: opsVerified
|
|
1137
|
+
originators: opsVerified,
|
|
1138
|
+
sites: []
|
|
1046
1139
|
});
|
|
1047
1140
|
}
|
|
1048
1141
|
if (opsVerified instanceof OpsVerifyFailed) {
|
|
1049
1142
|
return new SiteProfileVerifyFailed(
|
|
1050
1143
|
"Originator Profile Set verify failed",
|
|
1051
|
-
{ originators: opsVerified }
|
|
1144
|
+
{ originators: opsVerified, sites: [] }
|
|
1052
1145
|
);
|
|
1053
1146
|
}
|
|
1054
|
-
const
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
return new SiteProfileInvalid("Website Profile invalid", {
|
|
1058
|
-
originators: opsVerified,
|
|
1059
|
-
credential: decodedWsp
|
|
1060
|
-
});
|
|
1147
|
+
const decoded = decodeWebsiteProfiles(sp, opsVerified);
|
|
1148
|
+
if (decoded instanceof SiteProfileInvalid) {
|
|
1149
|
+
return decoded;
|
|
1061
1150
|
}
|
|
1062
|
-
const
|
|
1063
|
-
const
|
|
1064
|
-
(
|
|
1151
|
+
const { decodedWsps, wspSources } = decoded;
|
|
1152
|
+
const verifiedWsps = await Promise.all(
|
|
1153
|
+
decodedWsps.map(async (decodedWsp, index) => {
|
|
1154
|
+
if (decodedWsp instanceof Error) {
|
|
1155
|
+
return decodedWsp;
|
|
1156
|
+
}
|
|
1157
|
+
const wspIssuer = decodedWsp.doc.issuer;
|
|
1158
|
+
const cp = opsVerified.find(
|
|
1159
|
+
(op) => op.core.doc.credentialSubject.id === wspIssuer
|
|
1160
|
+
);
|
|
1161
|
+
if (!cp) {
|
|
1162
|
+
return new CoreProfileNotFound(
|
|
1163
|
+
`Missing Core Profile (${wspIssuer})`,
|
|
1164
|
+
decodedWsp
|
|
1165
|
+
);
|
|
1166
|
+
}
|
|
1167
|
+
const verifyWsp = securingMechanism.JwtVcVerifier(
|
|
1168
|
+
cryptography.LocalKeys(cp.core.doc.credentialSubject.jwks),
|
|
1169
|
+
cp.core.doc.credentialSubject.id,
|
|
1170
|
+
validator?.(model.WebsiteProfile)
|
|
1171
|
+
);
|
|
1172
|
+
const verified = await verifyWsp(wspSources[index]);
|
|
1173
|
+
if (verified instanceof Error) {
|
|
1174
|
+
return verified;
|
|
1175
|
+
}
|
|
1176
|
+
if (verifyOrigin) {
|
|
1177
|
+
const allowedOrigin = "allowedOrigin" in verified.doc.credentialSubject ? verified.doc.credentialSubject.allowedOrigin : verified.doc.credentialSubject.url;
|
|
1178
|
+
if (!verifyAllowedOrigin(origin, allowedOrigin)) {
|
|
1179
|
+
return new Error("Origin not allowed");
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
await verifyImageDigestSri(verified.doc.credentialSubject.image);
|
|
1183
|
+
return verified;
|
|
1184
|
+
})
|
|
1065
1185
|
);
|
|
1066
|
-
|
|
1186
|
+
const hasCoreProfileNotFound = verifiedWsps.some(
|
|
1187
|
+
(wsp) => wsp instanceof CoreProfileNotFound
|
|
1188
|
+
);
|
|
1189
|
+
if (hasCoreProfileNotFound) {
|
|
1067
1190
|
return new SiteProfileInvalid("Appropriate Core Profile not found", {
|
|
1068
1191
|
originators: opsVerified,
|
|
1069
|
-
|
|
1070
|
-
`Missing Core Profile (${wspIssuer})`,
|
|
1071
|
-
decodedWsp
|
|
1072
|
-
)
|
|
1192
|
+
sites: verifiedWsps
|
|
1073
1193
|
});
|
|
1074
1194
|
}
|
|
1075
|
-
const
|
|
1076
|
-
|
|
1077
|
-
cp.core.doc.credentialSubject.id,
|
|
1078
|
-
validator?.(model.WebsiteProfile)
|
|
1079
|
-
);
|
|
1080
|
-
const credential = await verifyWsp(sp.credential);
|
|
1081
|
-
if (credential instanceof Error) {
|
|
1195
|
+
const hasError = verifiedWsps.some((wsp) => wsp instanceof Error);
|
|
1196
|
+
if (hasError) {
|
|
1082
1197
|
return new SiteProfileVerifyFailed("Website Profile verify failed", {
|
|
1083
1198
|
originators: opsVerified,
|
|
1084
|
-
|
|
1199
|
+
sites: verifiedWsps
|
|
1085
1200
|
});
|
|
1086
1201
|
}
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
originators: opsVerified,
|
|
1092
|
-
credential
|
|
1093
|
-
});
|
|
1094
|
-
}
|
|
1095
|
-
}
|
|
1096
|
-
return { originators: opsVerified, credential };
|
|
1202
|
+
return {
|
|
1203
|
+
originators: opsVerified,
|
|
1204
|
+
sites: verifiedWsps
|
|
1205
|
+
};
|
|
1097
1206
|
}
|
|
1098
1207
|
return verify;
|
|
1099
1208
|
}
|
|
1100
1209
|
|
|
1101
|
-
var objectTypeof = typeOf;
|
|
1102
|
-
function typeOf(obj) {
|
|
1103
|
-
if (obj === null) {
|
|
1104
|
-
return "null";
|
|
1105
|
-
}
|
|
1106
|
-
if (obj !== Object(obj)) {
|
|
1107
|
-
return typeof obj;
|
|
1108
|
-
}
|
|
1109
|
-
var result = {}.toString.call(obj).slice(8, -1).toLowerCase();
|
|
1110
|
-
return result.indexOf("function") > -1 ? "function" : result;
|
|
1111
|
-
}
|
|
1112
|
-
|
|
1113
|
-
function CertificationSystemValidator() {
|
|
1114
|
-
return function validate(payload) {
|
|
1115
|
-
if (objectTypeof(payload) !== "object") {
|
|
1116
|
-
return new CertificationSystemValidationFailed("should be an object", {
|
|
1117
|
-
payload
|
|
1118
|
-
});
|
|
1119
|
-
}
|
|
1120
|
-
const keys = Object.keys(payload);
|
|
1121
|
-
const entries = Object.entries(payload);
|
|
1122
|
-
if (!model.CertificationSystem.required.every((k) => keys.includes(k))) {
|
|
1123
|
-
return new CertificationSystemValidationFailed(
|
|
1124
|
-
"should be contain required properties",
|
|
1125
|
-
{ payload }
|
|
1126
|
-
);
|
|
1127
|
-
}
|
|
1128
|
-
for (const entry of entries) {
|
|
1129
|
-
const [key, value] = entry;
|
|
1130
|
-
const propertySchema = model.CertificationSystem.properties[key];
|
|
1131
|
-
if (objectTypeof(propertySchema) !== "object") {
|
|
1132
|
-
return new CertificationSystemValidationFailed(
|
|
1133
|
-
"should not contain additional properties",
|
|
1134
|
-
{ payload }
|
|
1135
|
-
);
|
|
1136
|
-
}
|
|
1137
|
-
if ("const" in propertySchema && value !== propertySchema.const)
|
|
1138
|
-
return new CertificationSystemValidationFailed(
|
|
1139
|
-
`should be contain value of '${value}' in '${key}' property`,
|
|
1140
|
-
{ payload }
|
|
1141
|
-
);
|
|
1142
|
-
if ("type" in propertySchema && typeof value !== propertySchema.type)
|
|
1143
|
-
return new CertificationSystemValidationFailed(
|
|
1144
|
-
`should be contain ${propertySchema.type} value in '${key}' property`,
|
|
1145
|
-
{ payload }
|
|
1146
|
-
);
|
|
1147
|
-
}
|
|
1148
|
-
return true;
|
|
1149
|
-
};
|
|
1150
|
-
}
|
|
1151
|
-
function validateCertificationSystem(payload) {
|
|
1152
|
-
const validator = CertificationSystemValidator();
|
|
1153
|
-
const result = validator(payload);
|
|
1154
|
-
if (result !== true) {
|
|
1155
|
-
return result;
|
|
1156
|
-
}
|
|
1157
|
-
return payload;
|
|
1158
|
-
}
|
|
1159
|
-
|
|
1160
1210
|
exports.CaInvalid = CaInvalid;
|
|
1161
1211
|
exports.CaVerifier = CaVerifier;
|
|
1162
1212
|
exports.CaVerifyFailed = CaVerifyFailed;
|
|
1163
1213
|
exports.CasVerifyFailed = CasVerifyFailed;
|
|
1164
|
-
exports.
|
|
1165
|
-
exports.CertificationSystemValidator = CertificationSystemValidator;
|
|
1214
|
+
exports.CertificateExpired = CertificateExpired;
|
|
1166
1215
|
exports.CoreProfileNotFound = CoreProfileNotFound;
|
|
1216
|
+
exports.IntegrityFetchFailed = IntegrityFetchFailed;
|
|
1217
|
+
exports.IntegrityVerificationFailed = IntegrityVerificationFailed;
|
|
1167
1218
|
exports.OpInvalid = OpInvalid;
|
|
1168
1219
|
exports.OpVerifyFailed = OpVerifyFailed;
|
|
1169
1220
|
exports.OpsInvalid = OpsInvalid;
|
|
1170
1221
|
exports.OpsVerifier = OpsVerifier;
|
|
1171
1222
|
exports.OpsVerifyFailed = OpsVerifyFailed;
|
|
1172
|
-
exports.ProfileBodyExtractFailed = ProfileBodyExtractFailed;
|
|
1173
|
-
exports.ProfileBodyVerifyFailed = ProfileBodyVerifyFailed;
|
|
1174
|
-
exports.ProfileClaimsValidationFailed = ProfileClaimsValidationFailed;
|
|
1175
|
-
exports.ProfileGenericError = ProfileGenericError;
|
|
1176
|
-
exports.ProfileTokenVerifyFailed = ProfileTokenVerifyFailed;
|
|
1177
|
-
exports.ProfilesResolveFailed = ProfilesResolveFailed;
|
|
1178
|
-
exports.ProfilesVerifyFailed = ProfilesVerifyFailed;
|
|
1179
1223
|
exports.SiteProfileInvalid = SiteProfileInvalid;
|
|
1180
1224
|
exports.SiteProfileVerifyFailed = SiteProfileVerifyFailed;
|
|
1181
1225
|
exports.SpVerifier = SpVerifier;
|
|
@@ -1192,10 +1236,10 @@ exports.getTupledKeys = getTupledKeys;
|
|
|
1192
1236
|
exports.normalizeCasItem = normalizeCasItem;
|
|
1193
1237
|
exports.opId = opId;
|
|
1194
1238
|
exports.patch = patch;
|
|
1195
|
-
exports.validateCertificationSystem = validateCertificationSystem;
|
|
1196
1239
|
exports.verifyAllowedOrigin = verifyAllowedOrigin;
|
|
1197
1240
|
exports.verifyCas = verifyCas;
|
|
1198
1241
|
exports.verifyDigestSri = verifyDigestSri;
|
|
1242
|
+
exports.verifyImageDigestSri = verifyImageDigestSri;
|
|
1199
1243
|
exports.verifyIntegrity = verifyIntegrity;
|
|
1200
1244
|
exports.wmp = wmp;
|
|
1201
1245
|
exports.wsp = wsp;
|