@originator-profile/verify 0.7.0-beta.4 → 0.7.0-beta.5
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.d.ts +419 -67
- package/dist/index.js +609 -268
- package/package.json +7 -7
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { JwtVcVerifier, VcValidateFailed, VcVerifyFailed, JwtVcDecoder } from '@originator-profile/securing-mechanism';
|
|
2
|
-
import { createDigestSri,
|
|
2
|
+
import { createDigestSri, selectByCssOrIntegrity, fetchExternalResource, selectByCss, fetchVisibleTextContent, fetchTextContent, fetchHtmlContent, FetchFailed } from '@originator-profile/sign';
|
|
3
3
|
import { LocalKeys } from '@originator-profile/cryptography';
|
|
4
4
|
import { ContentAttestation, JapaneseExistencePA, ProfileAnnotationIssuerRegistration, ProfileAnnotation, JapaneseExistenceCertificate, Certificate, WebMediaProfile, CoreProfile, WebsiteProfile } from '@originator-profile/model';
|
|
5
5
|
import { z } from 'zod';
|
|
@@ -299,6 +299,27 @@ async function createIntegrityMetadataSet(hashAlgorithms, data, options = {
|
|
|
299
299
|
return new IntegrityMetadataSet(set, options);
|
|
300
300
|
}
|
|
301
301
|
|
|
302
|
+
const REFERENCE = "https://docs.originator-profile.org/error-reference/";
|
|
303
|
+
const problemType = (code) => `${REFERENCE}${code}/`;
|
|
304
|
+
const ProblemType = {
|
|
305
|
+
/** 非推奨の Certificate を検出した */
|
|
306
|
+
CertificateDeprecated: problemType("WARN_CERTIFICATE_DEPRECATED"),
|
|
307
|
+
/** Content Attestation の allowedOrigin は非推奨 */
|
|
308
|
+
AllowedOriginDeprecated: problemType("WARN_ALLOWED_ORIGIN_DEPRECATED"),
|
|
309
|
+
/** Content Attestation の VisibleTextTargetIntegrity は非推奨 */
|
|
310
|
+
VisibleTextTargetIntegrityDeprecated: problemType(
|
|
311
|
+
"WARN_VISIBLE_TEXT_TARGET_INTEGRITY_DEPRECATED"
|
|
312
|
+
),
|
|
313
|
+
/** digestSRI が設定されていない */
|
|
314
|
+
DigestSriMissing: problemType("WARN_DIGEST_SRI_MISSING"),
|
|
315
|
+
/** digestSRI の検証に失敗した */
|
|
316
|
+
DigestSriInvalid: problemType("WARN_DIGEST_SRI_INVALID"),
|
|
317
|
+
/** Profile Annotation Issuer がその認証制度の発行を認可されていない */
|
|
318
|
+
PaIssuerNotRegistered: problemType("INFO_PA_ISSUER_NOT_REGISTERED"),
|
|
319
|
+
/** 種類が特定されていない通知 */
|
|
320
|
+
Unspecified: problemType("UNSPECIFIED")
|
|
321
|
+
};
|
|
322
|
+
|
|
302
323
|
const WARN_SUFFIX = `This will become an error after 2027. See: https://docs.originator-profile.org/en/opb/context/#the-image-datatype`;
|
|
303
324
|
async function verifyDigestSri(content, fetcher = fetch) {
|
|
304
325
|
const integrity = new IntegrityMetadataSet(content.digestSRI);
|
|
@@ -316,10 +337,16 @@ async function verifyDigestSri(content, fetcher = fetch) {
|
|
|
316
337
|
}
|
|
317
338
|
}
|
|
318
339
|
async function verifyImageDigestSri(value, options = {}) {
|
|
319
|
-
const { fetcher = fetch, logger = console } = options;
|
|
340
|
+
const { fetcher = fetch, logger = console, at } = options;
|
|
320
341
|
if (!value) return;
|
|
321
342
|
if (!value.digestSRI) {
|
|
322
|
-
|
|
343
|
+
const message = `digestSRI is missing. ${WARN_SUFFIX}`;
|
|
344
|
+
logger.warn(message, {
|
|
345
|
+
type: ProblemType.DigestSriMissing,
|
|
346
|
+
title: message,
|
|
347
|
+
detail: value.id,
|
|
348
|
+
...at && { pointer: at }
|
|
349
|
+
});
|
|
323
350
|
return;
|
|
324
351
|
}
|
|
325
352
|
const valid = await verifyDigestSri(
|
|
@@ -327,7 +354,13 @@ async function verifyImageDigestSri(value, options = {}) {
|
|
|
327
354
|
fetcher
|
|
328
355
|
);
|
|
329
356
|
if (!valid) {
|
|
330
|
-
|
|
357
|
+
const message = `digestSRI verification failed. ${WARN_SUFFIX}`;
|
|
358
|
+
logger.warn(message, {
|
|
359
|
+
type: ProblemType.DigestSriInvalid,
|
|
360
|
+
title: message,
|
|
361
|
+
detail: value.id,
|
|
362
|
+
...at && { pointer: at }
|
|
363
|
+
});
|
|
331
364
|
}
|
|
332
365
|
}
|
|
333
366
|
|
|
@@ -395,7 +428,7 @@ const TargetIntegrityAlgorithm = {
|
|
|
395
428
|
},
|
|
396
429
|
ExternalResourceTargetIntegrity: {
|
|
397
430
|
contentFetcher: fetchExternalResource,
|
|
398
|
-
elementSelector:
|
|
431
|
+
elementSelector: selectByCssOrIntegrity
|
|
399
432
|
}
|
|
400
433
|
};
|
|
401
434
|
async function verifyIntegrity(content, doc = document, fetcher = fetch) {
|
|
@@ -447,14 +480,42 @@ async function verifyAllowedUrl(url, allowedUrl) {
|
|
|
447
480
|
});
|
|
448
481
|
}
|
|
449
482
|
|
|
450
|
-
|
|
483
|
+
function warnAllowedOriginDeprecated(logger, subject, at) {
|
|
484
|
+
const message = "[OP Warning] allowedOrigin is deprecated in Content Attestation and will be removed after September 2026. Please use allowedUrl instead. See: https://docs.originator-profile.org/";
|
|
485
|
+
logger.warn(message, {
|
|
486
|
+
type: ProblemType.AllowedOriginDeprecated,
|
|
487
|
+
title: message,
|
|
488
|
+
detail: subject,
|
|
489
|
+
...at && { pointer: at }
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
function warnVisibleTextTargetIntegrityDeprecated(logger, subject, at) {
|
|
493
|
+
const message = `[OP Warning] VisibleTextTargetIntegrity is deprecated in Content Attestation and will be removed after October 2027. innerText-based rendering is platform-dependent and incompatible across browser implementations and cannot serve as a basis for integrity verification. See: https://docs.originator-profile.org/opb/content-integrity-descriptor/visible-text/`;
|
|
494
|
+
logger.warn(message, {
|
|
495
|
+
type: ProblemType.VisibleTextTargetIntegrityDeprecated,
|
|
496
|
+
title: message,
|
|
497
|
+
detail: subject,
|
|
498
|
+
...at && { pointer: at }
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
function warnDeprecatedTargets(result, logger, at) {
|
|
502
|
+
const hasVisibleTextTarget = result.doc.target?.some(
|
|
503
|
+
(t) => t.type === "VisibleTextTargetIntegrity"
|
|
504
|
+
);
|
|
505
|
+
if (hasVisibleTextTarget) {
|
|
506
|
+
warnVisibleTextTargetIntegrityDeprecated(
|
|
507
|
+
logger,
|
|
508
|
+
result.doc.credentialSubject.id,
|
|
509
|
+
at
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
async function checkUrlAndOrigin(result, url, logger, at) {
|
|
451
514
|
if (result.doc.allowedUrl && result.doc.allowedOrigin) {
|
|
452
515
|
return new CaInvalid("allowedUrl and allowedOrigin are exclusive", result);
|
|
453
516
|
}
|
|
454
517
|
if (result.doc.allowedOrigin) {
|
|
455
|
-
|
|
456
|
-
"[OP Warning] allowedOrigin is deprecated in Content Attestation and will be removed after September 2026. Please use allowedUrl instead. See: https://docs.originator-profile.org/"
|
|
457
|
-
);
|
|
518
|
+
warnAllowedOriginDeprecated(logger, result.doc.credentialSubject.id, at);
|
|
458
519
|
}
|
|
459
520
|
if (result.doc.allowedUrl && !await verifyAllowedUrl(url.toString(), result.doc.allowedUrl)) {
|
|
460
521
|
return new CaVerifyFailed(
|
|
@@ -496,7 +557,7 @@ function checkIntegrityResults(integrityResults, urlResult) {
|
|
|
496
557
|
);
|
|
497
558
|
}
|
|
498
559
|
}
|
|
499
|
-
function CaVerifier(ca, keys, issuer, url, verifyIntegrity$1 = verifyIntegrity, validator) {
|
|
560
|
+
function CaVerifier(ca, keys, issuer, url, verifyIntegrity$1 = verifyIntegrity, validator, logger = console, at) {
|
|
500
561
|
const verifyCa = JwtVcVerifier(keys, issuer, validator);
|
|
501
562
|
return async () => {
|
|
502
563
|
const result = await verifyCa(ca);
|
|
@@ -506,17 +567,19 @@ function CaVerifier(ca, keys, issuer, url, verifyIntegrity$1 = verifyIntegrity,
|
|
|
506
567
|
if (result instanceof VcVerifyFailed) {
|
|
507
568
|
return new CaVerifyFailed("Content Attestation verify failed", result);
|
|
508
569
|
}
|
|
509
|
-
const urlResult = await checkUrlAndOrigin(result, url);
|
|
570
|
+
const urlResult = await checkUrlAndOrigin(result, url, logger, at);
|
|
510
571
|
if (urlResult instanceof Error) {
|
|
511
572
|
return urlResult;
|
|
512
573
|
}
|
|
513
574
|
await verifyImageDigestSri(
|
|
514
|
-
urlResult.doc.credentialSubject.image
|
|
575
|
+
urlResult.doc.credentialSubject.image,
|
|
576
|
+
{ logger, ...at && { at } }
|
|
515
577
|
);
|
|
516
578
|
if (urlResult.doc.target) {
|
|
517
579
|
if (urlResult.doc.target.length === 0) {
|
|
518
580
|
return new CaInvalid("Target is empty", urlResult);
|
|
519
581
|
}
|
|
582
|
+
warnDeprecatedTargets(urlResult, logger, at);
|
|
520
583
|
const integrityResults = await Promise.all(
|
|
521
584
|
urlResult.doc.target.map(async (t, index) => ({
|
|
522
585
|
index,
|
|
@@ -569,10 +632,17 @@ function normalizeCasItem(ca) {
|
|
|
569
632
|
return { main: false, attestation: ca };
|
|
570
633
|
}
|
|
571
634
|
|
|
572
|
-
|
|
635
|
+
const append = (path, segments) => segments.reduce(
|
|
636
|
+
(acc, segment) => typeof segment === "number" ? `${acc}[${segment}]` : `${acc}.${segment}`,
|
|
637
|
+
path
|
|
638
|
+
);
|
|
639
|
+
const pointer = (...segments) => append("$", segments);
|
|
640
|
+
const childPointer = (base, ...segments) => append(base, segments);
|
|
641
|
+
|
|
642
|
+
async function verifyCas(cas, verifiedOps, url, verifyIntegrity, validator, logger, at) {
|
|
573
643
|
const decodeCa = JwtVcDecoder();
|
|
574
644
|
const resultCas = await Promise.all(
|
|
575
|
-
cas.map(async (ca) => {
|
|
645
|
+
cas.map(async (ca, index) => {
|
|
576
646
|
const { main, attestation: source } = normalizeCasItem(ca);
|
|
577
647
|
const decodedCa = decodeCa(source);
|
|
578
648
|
if (decodedCa instanceof Error) {
|
|
@@ -596,7 +666,9 @@ async function verifyCas(cas, verifiedOps, url, verifyIntegrity, validator) {
|
|
|
596
666
|
decodedCa.doc.issuer,
|
|
597
667
|
new URL(url),
|
|
598
668
|
verifyIntegrity,
|
|
599
|
-
validator?.(ContentAttestation)
|
|
669
|
+
validator?.(ContentAttestation),
|
|
670
|
+
logger,
|
|
671
|
+
at ? childPointer(at, "cas", index, "attestation") : void 0
|
|
600
672
|
);
|
|
601
673
|
return { main, attestation: await verify() };
|
|
602
674
|
})
|
|
@@ -612,244 +684,6 @@ async function verifyCas(cas, verifiedOps, url, verifyIntegrity, validator) {
|
|
|
612
684
|
return resultCas;
|
|
613
685
|
}
|
|
614
686
|
|
|
615
|
-
var REMOVE = "remove";
|
|
616
|
-
var REPLACE = "replace";
|
|
617
|
-
var ADD = "add";
|
|
618
|
-
var MOVE = "move";
|
|
619
|
-
function diffApply(obj, diff, pathConverter) {
|
|
620
|
-
if (!obj || typeof obj != "object") {
|
|
621
|
-
throw new Error("base object must be an object or an array");
|
|
622
|
-
}
|
|
623
|
-
if (!Array.isArray(diff)) {
|
|
624
|
-
throw new Error("diff must be an array");
|
|
625
|
-
}
|
|
626
|
-
var diffLength = diff.length;
|
|
627
|
-
for (var i = 0; i < diffLength; i++) {
|
|
628
|
-
var thisDiff = diff[i];
|
|
629
|
-
var subObject = obj;
|
|
630
|
-
var thisOp = thisDiff.op;
|
|
631
|
-
var thisPath = transformPath(pathConverter, thisDiff.path);
|
|
632
|
-
var thisFromPath = thisDiff.from && transformPath(pathConverter, thisDiff.from);
|
|
633
|
-
var toPath, toPathCopy, lastToProp, subToObject, valueToMove;
|
|
634
|
-
if (thisFromPath) {
|
|
635
|
-
toPath = thisPath;
|
|
636
|
-
thisPath = thisFromPath;
|
|
637
|
-
toPathCopy = toPath.slice();
|
|
638
|
-
lastToProp = toPathCopy.pop();
|
|
639
|
-
prototypeCheck(lastToProp);
|
|
640
|
-
if (lastToProp == null) {
|
|
641
|
-
return false;
|
|
642
|
-
}
|
|
643
|
-
var thisToProp;
|
|
644
|
-
while ((thisToProp = toPathCopy.shift()) != null) {
|
|
645
|
-
prototypeCheck(thisToProp);
|
|
646
|
-
if (!(thisToProp in subToObject)) {
|
|
647
|
-
subToObject[thisToProp] = {};
|
|
648
|
-
}
|
|
649
|
-
subToObject = subToObject[thisToProp];
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
var pathCopy = thisPath.slice();
|
|
653
|
-
var lastProp = pathCopy.pop();
|
|
654
|
-
prototypeCheck(lastProp);
|
|
655
|
-
if (lastProp == null) {
|
|
656
|
-
return false;
|
|
657
|
-
}
|
|
658
|
-
var thisProp;
|
|
659
|
-
while ((thisProp = pathCopy.shift()) != null) {
|
|
660
|
-
prototypeCheck(thisProp);
|
|
661
|
-
if (!(thisProp in subObject)) {
|
|
662
|
-
subObject[thisProp] = {};
|
|
663
|
-
}
|
|
664
|
-
subObject = subObject[thisProp];
|
|
665
|
-
}
|
|
666
|
-
if (thisOp === REMOVE || thisOp === REPLACE || thisOp === MOVE) {
|
|
667
|
-
var path = thisOp === MOVE ? thisDiff.from : thisDiff.path;
|
|
668
|
-
if (!subObject.hasOwnProperty(lastProp)) {
|
|
669
|
-
throw new Error(["expected to find property", path, "in object", obj].join(" "));
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
if (thisOp === REMOVE || thisOp === MOVE) {
|
|
673
|
-
if (thisOp === MOVE) {
|
|
674
|
-
valueToMove = subObject[lastProp];
|
|
675
|
-
}
|
|
676
|
-
Array.isArray(subObject) ? subObject.splice(lastProp, 1) : delete subObject[lastProp];
|
|
677
|
-
}
|
|
678
|
-
if (thisOp === REPLACE || thisOp === ADD) {
|
|
679
|
-
subObject[lastProp] = thisDiff.value;
|
|
680
|
-
}
|
|
681
|
-
if (thisOp === MOVE) {
|
|
682
|
-
subObject[lastToProp] = valueToMove;
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
return subObject;
|
|
686
|
-
}
|
|
687
|
-
function transformPath(pathConverter, thisPath) {
|
|
688
|
-
{
|
|
689
|
-
if (!Array.isArray(thisPath)) {
|
|
690
|
-
throw new Error([
|
|
691
|
-
"diff path",
|
|
692
|
-
thisPath,
|
|
693
|
-
"must be an array, consider supplying a path converter"
|
|
694
|
-
].join(" "));
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
|
-
return thisPath;
|
|
698
|
-
}
|
|
699
|
-
function prototypeCheck(prop) {
|
|
700
|
-
if (prop == "__proto__" || prop == "constructor" || prop == "prototype") {
|
|
701
|
-
throw new Error("setting of prototype values not supported");
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
const patch = (...args) => {
|
|
706
|
-
const [source, diff] = args;
|
|
707
|
-
const patched = structuredClone(source);
|
|
708
|
-
diffApply(patched, diff);
|
|
709
|
-
return patched;
|
|
710
|
-
};
|
|
711
|
-
const VerifyResultFactory = (issuedAt, expiredAt) => ({
|
|
712
|
-
create: (vc, jwt, verificationKey, validated = false) => {
|
|
713
|
-
const unverified = {
|
|
714
|
-
doc: vc,
|
|
715
|
-
issuedAt,
|
|
716
|
-
expiredAt,
|
|
717
|
-
algorithm: "ES256",
|
|
718
|
-
mediaType: "application/vc+jwt",
|
|
719
|
-
source: jwt
|
|
720
|
-
};
|
|
721
|
-
if (!verificationKey) return unverified;
|
|
722
|
-
return { ...unverified, verificationKey, validated };
|
|
723
|
-
}
|
|
724
|
-
});
|
|
725
|
-
const opId = {
|
|
726
|
-
/** CP 発行者 */
|
|
727
|
-
authority: "dns:cp-issuer.example.org",
|
|
728
|
-
/** PA 発行者 */
|
|
729
|
-
certifier: "dns:pa-issuer.example.org",
|
|
730
|
-
/** CA 発行者 */
|
|
731
|
-
originator: "dns:originator.example.org",
|
|
732
|
-
/** 無効な第三者 */
|
|
733
|
-
invalid: "dns:invalid.example.org"
|
|
734
|
-
};
|
|
735
|
-
const cp = {
|
|
736
|
-
"@context": [
|
|
737
|
-
"https://www.w3.org/ns/credentials/v2",
|
|
738
|
-
"https://originator-profile.org/ns/credentials/v1"
|
|
739
|
-
],
|
|
740
|
-
type: ["VerifiableCredential", "CoreProfile"],
|
|
741
|
-
issuer: opId.authority,
|
|
742
|
-
credentialSubject: {
|
|
743
|
-
id: opId.originator,
|
|
744
|
-
type: "Core",
|
|
745
|
-
jwks: {
|
|
746
|
-
keys: []
|
|
747
|
-
}
|
|
748
|
-
}
|
|
749
|
-
};
|
|
750
|
-
const certificate = {
|
|
751
|
-
"@context": [
|
|
752
|
-
"https://www.w3.org/ns/credentials/v2",
|
|
753
|
-
"https://originator-profile.org/ns/credentials/v1",
|
|
754
|
-
"https://originator-profile.org/ns/cip/v1",
|
|
755
|
-
{
|
|
756
|
-
"@language": "ja"
|
|
757
|
-
}
|
|
758
|
-
],
|
|
759
|
-
type: ["VerifiableCredential", "Certificate"],
|
|
760
|
-
issuer: opId.certifier,
|
|
761
|
-
credentialSubject: {
|
|
762
|
-
id: opId.originator,
|
|
763
|
-
type: "CertificateProperties",
|
|
764
|
-
description: "Example Certificate",
|
|
765
|
-
certificationSystem: {
|
|
766
|
-
id: "urn:uuid:de5d6e80-10a5-404f-b4d3-e9f0e6926a21",
|
|
767
|
-
type: "CertificationSystem",
|
|
768
|
-
name: "Example Certification System",
|
|
769
|
-
description: "Example Certification System Description"
|
|
770
|
-
}
|
|
771
|
-
}
|
|
772
|
-
};
|
|
773
|
-
const wmp = {
|
|
774
|
-
"@context": [
|
|
775
|
-
"https://www.w3.org/ns/credentials/v2",
|
|
776
|
-
"https://originator-profile.org/ns/credentials/v1",
|
|
777
|
-
"https://originator-profile.org/ns/cip/v1",
|
|
778
|
-
{
|
|
779
|
-
"@language": "ja"
|
|
780
|
-
}
|
|
781
|
-
],
|
|
782
|
-
type: ["VerifiableCredential", "WebMediaProfile"],
|
|
783
|
-
issuer: opId.authority,
|
|
784
|
-
credentialSubject: {
|
|
785
|
-
id: opId.originator,
|
|
786
|
-
type: "OnlineBusiness",
|
|
787
|
-
name: "Example OP Holder",
|
|
788
|
-
url: "https://op-originator.example.org/"
|
|
789
|
-
}
|
|
790
|
-
};
|
|
791
|
-
const wsp = {
|
|
792
|
-
"@context": [
|
|
793
|
-
"https://www.w3.org/ns/credentials/v2",
|
|
794
|
-
"https://originator-profile.org/ns/credentials/v1",
|
|
795
|
-
"https://originator-profile.org/ns/cip/v1",
|
|
796
|
-
{
|
|
797
|
-
"@language": "ja"
|
|
798
|
-
}
|
|
799
|
-
],
|
|
800
|
-
type: ["VerifiableCredential", "WebsiteProfile"],
|
|
801
|
-
issuer: opId.originator,
|
|
802
|
-
credentialSubject: {
|
|
803
|
-
id: "https://originator.example.org",
|
|
804
|
-
type: "WebSite",
|
|
805
|
-
name: "Example Website",
|
|
806
|
-
description: "Example Website Description",
|
|
807
|
-
allowedOrigin: ["https://originator.example.org"]
|
|
808
|
-
}
|
|
809
|
-
};
|
|
810
|
-
const caId = "urn:uuid:78550fa7-f846-4e0f-ad5c-8d34461cb95b";
|
|
811
|
-
const caUrl = new URL("https://www.example.org/articles/example");
|
|
812
|
-
const article = {
|
|
813
|
-
"@context": [
|
|
814
|
-
"https://www.w3.org/ns/credentials/v2",
|
|
815
|
-
"https://originator-profile.org/ns/credentials/v1",
|
|
816
|
-
"https://originator-profile.org/ns/cip/v1",
|
|
817
|
-
{ "@language": "ja" }
|
|
818
|
-
],
|
|
819
|
-
type: ["VerifiableCredential", "ContentAttestation"],
|
|
820
|
-
issuer: opId.originator,
|
|
821
|
-
target: [],
|
|
822
|
-
allowedUrl: ["https://www.example.org/articles*"],
|
|
823
|
-
credentialSubject: {
|
|
824
|
-
id: caId,
|
|
825
|
-
type: "Article",
|
|
826
|
-
headline: "\u30C6\u30B9\u30C8\u8A18\u4E8B",
|
|
827
|
-
description: "\u8A18\u4E8B\u306E\u8AAC\u660E"
|
|
828
|
-
}
|
|
829
|
-
};
|
|
830
|
-
|
|
831
|
-
function getMappedKeys(ops) {
|
|
832
|
-
const groupedOps = Object.groupBy(
|
|
833
|
-
ops,
|
|
834
|
-
(op) => op.core.doc.credentialSubject.id
|
|
835
|
-
);
|
|
836
|
-
const keyMap = {};
|
|
837
|
-
for (const [opId, ops2] of Object.entries(groupedOps)) {
|
|
838
|
-
if (!ops2) continue;
|
|
839
|
-
const kidSet = /* @__PURE__ */ new Set();
|
|
840
|
-
const keys = ops2.flatMap((op) => op.core.doc.credentialSubject.jwks.keys).filter(({ kid }) => !kidSet.has(kid) && kidSet.add(kid));
|
|
841
|
-
keyMap[opId] = { keys };
|
|
842
|
-
}
|
|
843
|
-
return keyMap;
|
|
844
|
-
}
|
|
845
|
-
function getTupledKeys(ops) {
|
|
846
|
-
const keyMap = getMappedKeys(ops);
|
|
847
|
-
const kidSet = /* @__PURE__ */ new Set();
|
|
848
|
-
const opId = Object.keys(keyMap);
|
|
849
|
-
const keys = Object.values(keyMap).flatMap((jwks) => jwks.keys).filter(({ kid }) => !kidSet.has(kid) && kidSet.add(kid));
|
|
850
|
-
return [opId.length === 1 ? opId[0] : opId, LocalKeys({ keys })];
|
|
851
|
-
}
|
|
852
|
-
|
|
853
687
|
class OpsInvalid extends Error {
|
|
854
688
|
constructor(message, result) {
|
|
855
689
|
super(message);
|
|
@@ -1029,16 +863,27 @@ function reportUnauthorizedAnnotation({
|
|
|
1029
863
|
const { doc } = annotation;
|
|
1030
864
|
const { issuer: profileAnnotationIssuer } = doc;
|
|
1031
865
|
const location = `OP[${opIndex}].PA[${paIndex}]`;
|
|
866
|
+
const at = pointer("originators", opIndex, "annotations", paIndex);
|
|
1032
867
|
if (!isProfileAnnotation(doc)) {
|
|
1033
|
-
|
|
868
|
+
const message = `Certificate is deprecated (${location})`;
|
|
869
|
+
logger.warn(message, {
|
|
870
|
+
type: ProblemType.CertificateDeprecated,
|
|
871
|
+
title: message,
|
|
872
|
+
pointer: at
|
|
873
|
+
});
|
|
1034
874
|
return;
|
|
1035
875
|
}
|
|
1036
876
|
const policyId = getAnnotationPolicy(doc)?.id;
|
|
1037
877
|
const isAllowed = policyId && policy.get(profileAnnotationIssuer)?.has(policyId);
|
|
1038
878
|
if (!isAllowed) {
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
879
|
+
const scheme = policyId ?? "unknown";
|
|
880
|
+
const message = `Profile Annotation Issuer is not registered for this annotation scheme (${location} issuer: ${profileAnnotationIssuer}, scheme: ${scheme})`;
|
|
881
|
+
logger.info(message, {
|
|
882
|
+
type: ProblemType.PaIssuerNotRegistered,
|
|
883
|
+
title: message,
|
|
884
|
+
detail: `issuer: ${profileAnnotationIssuer}, scheme: ${scheme}`,
|
|
885
|
+
pointer: at
|
|
886
|
+
});
|
|
1042
887
|
}
|
|
1043
888
|
}
|
|
1044
889
|
function verifyAnnotationIssuerRegistration(verifiedOps, paIssuerRegistrationIssuer, logger = console) {
|
|
@@ -1063,7 +908,29 @@ function verifyAnnotationIssuerRegistration(verifiedOps, paIssuerRegistrationIss
|
|
|
1063
908
|
return verifiedOps;
|
|
1064
909
|
}
|
|
1065
910
|
|
|
1066
|
-
function
|
|
911
|
+
function getMappedKeys(ops) {
|
|
912
|
+
const groupedOps = Object.groupBy(
|
|
913
|
+
ops,
|
|
914
|
+
(op) => op.core.doc.credentialSubject.id
|
|
915
|
+
);
|
|
916
|
+
const keyMap = {};
|
|
917
|
+
for (const [opId, ops2] of Object.entries(groupedOps)) {
|
|
918
|
+
if (!ops2) continue;
|
|
919
|
+
const kidSet = /* @__PURE__ */ new Set();
|
|
920
|
+
const keys = ops2.flatMap((op) => op.core.doc.credentialSubject.jwks.keys).filter(({ kid }) => !kidSet.has(kid) && kidSet.add(kid));
|
|
921
|
+
keyMap[opId] = { keys };
|
|
922
|
+
}
|
|
923
|
+
return keyMap;
|
|
924
|
+
}
|
|
925
|
+
function getTupledKeys(ops) {
|
|
926
|
+
const keyMap = getMappedKeys(ops);
|
|
927
|
+
const kidSet = /* @__PURE__ */ new Set();
|
|
928
|
+
const opId = Object.keys(keyMap);
|
|
929
|
+
const keys = Object.values(keyMap).flatMap((jwks) => jwks.keys).filter(({ kid }) => !kidSet.has(kid) && kidSet.add(kid));
|
|
930
|
+
return [opId.length === 1 ? opId[0] : opId, LocalKeys({ keys })];
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
function OpVerifier(paOrWmpIssuerKeys, vc, validator) {
|
|
1067
934
|
const issuer = vc.doc.issuer;
|
|
1068
935
|
const jwks = paOrWmpIssuerKeys[issuer];
|
|
1069
936
|
if (!jwks) {
|
|
@@ -1086,10 +953,10 @@ function validateCertificateExpiry(verifiedVc) {
|
|
|
1086
953
|
return verifiedVc;
|
|
1087
954
|
}
|
|
1088
955
|
async function verifyAnnotations(paIssuerKeys, annotations, options = {}) {
|
|
1089
|
-
const { validator, logger = console } = options;
|
|
956
|
+
const { validator, logger = console, at } = options;
|
|
1090
957
|
if (!annotations) return;
|
|
1091
958
|
return await Promise.all(
|
|
1092
|
-
annotations.map(async (annotation) => {
|
|
959
|
+
annotations.map(async (annotation, index) => {
|
|
1093
960
|
const verify = OpVerifier(
|
|
1094
961
|
paIssuerKeys,
|
|
1095
962
|
annotation,
|
|
@@ -1113,7 +980,8 @@ async function verifyAnnotations(paIssuerKeys, annotations, options = {}) {
|
|
|
1113
980
|
return valid;
|
|
1114
981
|
}
|
|
1115
982
|
await verifyImageDigestSri(valid.doc.credentialSubject.image, {
|
|
1116
|
-
logger
|
|
983
|
+
logger,
|
|
984
|
+
...at && { at: childPointer(at, "annotations", index) }
|
|
1117
985
|
});
|
|
1118
986
|
return valid;
|
|
1119
987
|
})
|
|
@@ -1121,10 +989,10 @@ async function verifyAnnotations(paIssuerKeys, annotations, options = {}) {
|
|
|
1121
989
|
}
|
|
1122
990
|
|
|
1123
991
|
async function verifyMedia(wmpIssuerKeys, media, options = {}) {
|
|
1124
|
-
const { validator, logger = console } = options;
|
|
992
|
+
const { validator, logger = console, at } = options;
|
|
1125
993
|
if (!media) return;
|
|
1126
994
|
return await Promise.all(
|
|
1127
|
-
media.map(async (m) => {
|
|
995
|
+
media.map(async (m, index) => {
|
|
1128
996
|
const verify = OpVerifier(
|
|
1129
997
|
wmpIssuerKeys,
|
|
1130
998
|
m,
|
|
@@ -1135,7 +1003,8 @@ async function verifyMedia(wmpIssuerKeys, media, options = {}) {
|
|
|
1135
1003
|
return result;
|
|
1136
1004
|
}
|
|
1137
1005
|
await verifyImageDigestSri(result.doc.credentialSubject.logo, {
|
|
1138
|
-
logger
|
|
1006
|
+
logger,
|
|
1007
|
+
...at && { at: childPointer(at, "media", index) }
|
|
1139
1008
|
});
|
|
1140
1009
|
return result;
|
|
1141
1010
|
})
|
|
@@ -1167,15 +1036,17 @@ function OpsVerifier(ops, keys, issuer, options = {}) {
|
|
|
1167
1036
|
const paOrWmpIssuerKeys = getMappedKeys(decoded);
|
|
1168
1037
|
const resultOps = await Promise.all(
|
|
1169
1038
|
decoded.map(async (op, opIndex) => {
|
|
1039
|
+
const at = pointer("originators", opIndex);
|
|
1170
1040
|
const core = await verifyCp(op.core.source);
|
|
1171
1041
|
const annotations = await verifyAnnotations(
|
|
1172
1042
|
paOrWmpIssuerKeys,
|
|
1173
1043
|
op.annotations,
|
|
1174
|
-
{ validator, logger }
|
|
1044
|
+
{ validator, logger, at }
|
|
1175
1045
|
);
|
|
1176
1046
|
const media = await verifyMedia(paOrWmpIssuerKeys, op.media, {
|
|
1177
1047
|
validator,
|
|
1178
|
-
logger
|
|
1048
|
+
logger,
|
|
1049
|
+
at
|
|
1179
1050
|
});
|
|
1180
1051
|
const resultOp = { core, annotations, media };
|
|
1181
1052
|
if (core instanceof Error) {
|
|
@@ -1216,6 +1087,436 @@ function OpsVerifier(ops, keys, issuer, options = {}) {
|
|
|
1216
1087
|
return verify;
|
|
1217
1088
|
}
|
|
1218
1089
|
|
|
1090
|
+
const unspecified = (title) => ({
|
|
1091
|
+
type: ProblemType.Unspecified,
|
|
1092
|
+
title
|
|
1093
|
+
});
|
|
1094
|
+
function collectProblems(delegate = console) {
|
|
1095
|
+
const warnings = [];
|
|
1096
|
+
const info = [];
|
|
1097
|
+
return {
|
|
1098
|
+
logger: {
|
|
1099
|
+
warn(message, details) {
|
|
1100
|
+
delegate.warn(message);
|
|
1101
|
+
warnings.push(details ?? unspecified(message));
|
|
1102
|
+
},
|
|
1103
|
+
info(message, details) {
|
|
1104
|
+
delegate.info(message);
|
|
1105
|
+
info.push(details ?? unspecified(message));
|
|
1106
|
+
}
|
|
1107
|
+
},
|
|
1108
|
+
warnings,
|
|
1109
|
+
info
|
|
1110
|
+
};
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
const hasCode = (error) => "code" in error && typeof error.code === "string";
|
|
1114
|
+
const isObject$1 = (value) => typeof value === "object" && value !== null;
|
|
1115
|
+
const innerErrorOf = (error) => {
|
|
1116
|
+
if (error.cause instanceof Error) return error.cause;
|
|
1117
|
+
const result = "result" in error ? error.result : void 0;
|
|
1118
|
+
return isObject$1(result) && result.error instanceof Error ? result.error : void 0;
|
|
1119
|
+
};
|
|
1120
|
+
function toProblemDetails(error, at) {
|
|
1121
|
+
const pointer = at ? { pointer: at } : {};
|
|
1122
|
+
if (!(error instanceof Error)) {
|
|
1123
|
+
return { type: ProblemType.Unspecified, title: String(error), ...pointer };
|
|
1124
|
+
}
|
|
1125
|
+
const type = hasCode(error) ? problemType(error.code) : ProblemType.Unspecified;
|
|
1126
|
+
const inner = innerErrorOf(error);
|
|
1127
|
+
return inner ? { type, title: error.message, detail: inner.message, ...pointer } : { type, title: error.message, ...pointer };
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
const createCollector = () => ({
|
|
1131
|
+
securingResults: [],
|
|
1132
|
+
errors: []
|
|
1133
|
+
});
|
|
1134
|
+
const isObject = (value) => typeof value === "object" && value !== null;
|
|
1135
|
+
const failureOf = (error) => {
|
|
1136
|
+
const result = "result" in error ? error.result : void 0;
|
|
1137
|
+
return isObject(result) ? result : void 0;
|
|
1138
|
+
};
|
|
1139
|
+
const asRecord = (value) => isObject(value) ? value : void 0;
|
|
1140
|
+
const asString = (value) => typeof value === "string" ? value : void 0;
|
|
1141
|
+
const asIsoString = (value) => value instanceof Date ? value.toISOString() : void 0;
|
|
1142
|
+
const asJwk = (value) => isObject(value) ? value : void 0;
|
|
1143
|
+
const compact = (value) => Object.fromEntries(
|
|
1144
|
+
Object.entries(value).filter(([, v]) => v !== void 0)
|
|
1145
|
+
);
|
|
1146
|
+
function toSecuringResult(vc, at, status) {
|
|
1147
|
+
const doc = asRecord(vc?.doc);
|
|
1148
|
+
return compact({
|
|
1149
|
+
pointer: at,
|
|
1150
|
+
status,
|
|
1151
|
+
source: asString(vc?.source),
|
|
1152
|
+
mediaType: asString(vc?.mediaType),
|
|
1153
|
+
algorithm: asString(vc?.algorithm),
|
|
1154
|
+
issuedAt: asIsoString(vc?.issuedAt),
|
|
1155
|
+
expiredAt: asIsoString(vc?.expiredAt),
|
|
1156
|
+
verificationKey: asJwk(vc?.verificationKey),
|
|
1157
|
+
controller: asString(doc?.issuer)
|
|
1158
|
+
});
|
|
1159
|
+
}
|
|
1160
|
+
function convertVc(value, at, collect) {
|
|
1161
|
+
if (value instanceof Error) {
|
|
1162
|
+
const failure = failureOf(value);
|
|
1163
|
+
collect.securingResults.push(toSecuringResult(failure, at, false));
|
|
1164
|
+
collect.errors.push(toProblemDetails(value, at));
|
|
1165
|
+
return failure?.doc ?? null;
|
|
1166
|
+
}
|
|
1167
|
+
if (!isObject(value)) return null;
|
|
1168
|
+
collect.securingResults.push(toSecuringResult(value, at, true));
|
|
1169
|
+
return value.doc ?? null;
|
|
1170
|
+
}
|
|
1171
|
+
function convertOp(op, at, collect) {
|
|
1172
|
+
if (op instanceof Error) {
|
|
1173
|
+
collect.errors.push(toProblemDetails(op, at));
|
|
1174
|
+
}
|
|
1175
|
+
const source = op instanceof Error ? failureOf(op) : op;
|
|
1176
|
+
if (!source) return { core: null };
|
|
1177
|
+
return {
|
|
1178
|
+
core: convertVc(
|
|
1179
|
+
source.core,
|
|
1180
|
+
childPointer(at, "core"),
|
|
1181
|
+
collect
|
|
1182
|
+
),
|
|
1183
|
+
...source.annotations && {
|
|
1184
|
+
annotations: source.annotations.map(
|
|
1185
|
+
(annotation, index) => convertVc(
|
|
1186
|
+
annotation,
|
|
1187
|
+
childPointer(at, "annotations", index),
|
|
1188
|
+
collect
|
|
1189
|
+
)
|
|
1190
|
+
)
|
|
1191
|
+
},
|
|
1192
|
+
...source.media && {
|
|
1193
|
+
media: source.media.map(
|
|
1194
|
+
(m, index) => convertVc(
|
|
1195
|
+
m,
|
|
1196
|
+
childPointer(at, "media", index),
|
|
1197
|
+
collect
|
|
1198
|
+
)
|
|
1199
|
+
)
|
|
1200
|
+
}
|
|
1201
|
+
};
|
|
1202
|
+
}
|
|
1203
|
+
function convertOps(ops, collect) {
|
|
1204
|
+
if (ops instanceof Error) {
|
|
1205
|
+
collect.errors.push(toProblemDetails(ops, pointer("originators")));
|
|
1206
|
+
}
|
|
1207
|
+
const list = ops instanceof Error ? failureOf(ops) : ops;
|
|
1208
|
+
if (!Array.isArray(list)) return [];
|
|
1209
|
+
return list.map(
|
|
1210
|
+
(op, index) => convertOp(op, pointer("originators", index), collect)
|
|
1211
|
+
);
|
|
1212
|
+
}
|
|
1213
|
+
function convertCas(cas, at, collect) {
|
|
1214
|
+
return cas.map(({ main, attestation }, index) => ({
|
|
1215
|
+
main,
|
|
1216
|
+
attestation: convertVc(
|
|
1217
|
+
attestation,
|
|
1218
|
+
childPointer(at, "cas", index, "attestation"),
|
|
1219
|
+
collect
|
|
1220
|
+
)
|
|
1221
|
+
}));
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
async function verifyDocuments(targets, options) {
|
|
1225
|
+
const { registry, websiteOriginators, validator, logger } = options;
|
|
1226
|
+
const { logger: collecting, warnings, info } = collectProblems(logger);
|
|
1227
|
+
const collect = createCollector();
|
|
1228
|
+
const opsVerifier = OpsVerifier(
|
|
1229
|
+
[
|
|
1230
|
+
...registry.ops,
|
|
1231
|
+
...websiteOriginators ?? [],
|
|
1232
|
+
...targets.flatMap((target) => target.ops)
|
|
1233
|
+
],
|
|
1234
|
+
registry.keys,
|
|
1235
|
+
registry.issuer,
|
|
1236
|
+
{ validator, logger: collecting }
|
|
1237
|
+
);
|
|
1238
|
+
const verifiedOps = await opsVerifier();
|
|
1239
|
+
if (verifiedOps instanceof OpsInvalid || verifiedOps instanceof OpsVerifyFailed) {
|
|
1240
|
+
return {
|
|
1241
|
+
status: false,
|
|
1242
|
+
outcome: { originators: convertOps(verifiedOps, collect), documents: [] },
|
|
1243
|
+
securingResults: collect.securingResults,
|
|
1244
|
+
warnings,
|
|
1245
|
+
info,
|
|
1246
|
+
errors: [toProblemDetails(verifiedOps), ...collect.errors]
|
|
1247
|
+
};
|
|
1248
|
+
}
|
|
1249
|
+
const results = await Promise.all(
|
|
1250
|
+
targets.map(async (target, index) => {
|
|
1251
|
+
const at = pointer("documents", index);
|
|
1252
|
+
return {
|
|
1253
|
+
target,
|
|
1254
|
+
at,
|
|
1255
|
+
cas: await verifyCas(
|
|
1256
|
+
target.cas,
|
|
1257
|
+
verifiedOps,
|
|
1258
|
+
target.url,
|
|
1259
|
+
target.verifyIntegrity,
|
|
1260
|
+
validator,
|
|
1261
|
+
collecting,
|
|
1262
|
+
at
|
|
1263
|
+
)
|
|
1264
|
+
};
|
|
1265
|
+
})
|
|
1266
|
+
);
|
|
1267
|
+
const outcome = {
|
|
1268
|
+
originators: convertOps(verifiedOps, collect),
|
|
1269
|
+
documents: results.map(({ target, at, cas }) => ({
|
|
1270
|
+
target,
|
|
1271
|
+
cas: convertCas(
|
|
1272
|
+
cas instanceof CasVerifyFailed ? cas.result : cas,
|
|
1273
|
+
at,
|
|
1274
|
+
collect
|
|
1275
|
+
)
|
|
1276
|
+
}))
|
|
1277
|
+
};
|
|
1278
|
+
const failed = results.find(({ cas }) => cas instanceof CasVerifyFailed);
|
|
1279
|
+
return failed ? {
|
|
1280
|
+
status: false,
|
|
1281
|
+
outcome,
|
|
1282
|
+
securingResults: collect.securingResults,
|
|
1283
|
+
warnings,
|
|
1284
|
+
info,
|
|
1285
|
+
errors: [toProblemDetails(failed.cas, failed.at), ...collect.errors]
|
|
1286
|
+
} : {
|
|
1287
|
+
status: true,
|
|
1288
|
+
outcome,
|
|
1289
|
+
securingResults: collect.securingResults,
|
|
1290
|
+
warnings,
|
|
1291
|
+
info
|
|
1292
|
+
};
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
var REMOVE = "remove";
|
|
1296
|
+
var REPLACE = "replace";
|
|
1297
|
+
var ADD = "add";
|
|
1298
|
+
var MOVE = "move";
|
|
1299
|
+
function diffApply(obj, diff, pathConverter) {
|
|
1300
|
+
if (!obj || typeof obj != "object") {
|
|
1301
|
+
throw new Error("base object must be an object or an array");
|
|
1302
|
+
}
|
|
1303
|
+
if (!Array.isArray(diff)) {
|
|
1304
|
+
throw new Error("diff must be an array");
|
|
1305
|
+
}
|
|
1306
|
+
var diffLength = diff.length;
|
|
1307
|
+
for (var i = 0; i < diffLength; i++) {
|
|
1308
|
+
var thisDiff = diff[i];
|
|
1309
|
+
var subObject = obj;
|
|
1310
|
+
var thisOp = thisDiff.op;
|
|
1311
|
+
var thisPath = transformPath(pathConverter, thisDiff.path);
|
|
1312
|
+
var thisFromPath = thisDiff.from && transformPath(pathConverter, thisDiff.from);
|
|
1313
|
+
var toPath, toPathCopy, lastToProp, subToObject, valueToMove;
|
|
1314
|
+
if (thisFromPath) {
|
|
1315
|
+
toPath = thisPath;
|
|
1316
|
+
thisPath = thisFromPath;
|
|
1317
|
+
toPathCopy = toPath.slice();
|
|
1318
|
+
lastToProp = toPathCopy.pop();
|
|
1319
|
+
prototypeCheck(lastToProp);
|
|
1320
|
+
if (lastToProp == null) {
|
|
1321
|
+
return false;
|
|
1322
|
+
}
|
|
1323
|
+
var thisToProp;
|
|
1324
|
+
while ((thisToProp = toPathCopy.shift()) != null) {
|
|
1325
|
+
prototypeCheck(thisToProp);
|
|
1326
|
+
if (!(thisToProp in subToObject)) {
|
|
1327
|
+
subToObject[thisToProp] = {};
|
|
1328
|
+
}
|
|
1329
|
+
subToObject = subToObject[thisToProp];
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
var pathCopy = thisPath.slice();
|
|
1333
|
+
var lastProp = pathCopy.pop();
|
|
1334
|
+
prototypeCheck(lastProp);
|
|
1335
|
+
if (lastProp == null) {
|
|
1336
|
+
return false;
|
|
1337
|
+
}
|
|
1338
|
+
var thisProp;
|
|
1339
|
+
while ((thisProp = pathCopy.shift()) != null) {
|
|
1340
|
+
prototypeCheck(thisProp);
|
|
1341
|
+
if (!(thisProp in subObject)) {
|
|
1342
|
+
subObject[thisProp] = {};
|
|
1343
|
+
}
|
|
1344
|
+
subObject = subObject[thisProp];
|
|
1345
|
+
}
|
|
1346
|
+
if (thisOp === REMOVE || thisOp === REPLACE || thisOp === MOVE) {
|
|
1347
|
+
var path = thisOp === MOVE ? thisDiff.from : thisDiff.path;
|
|
1348
|
+
if (!subObject.hasOwnProperty(lastProp)) {
|
|
1349
|
+
throw new Error(["expected to find property", path, "in object", obj].join(" "));
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
if (thisOp === REMOVE || thisOp === MOVE) {
|
|
1353
|
+
if (thisOp === MOVE) {
|
|
1354
|
+
valueToMove = subObject[lastProp];
|
|
1355
|
+
}
|
|
1356
|
+
Array.isArray(subObject) ? subObject.splice(lastProp, 1) : delete subObject[lastProp];
|
|
1357
|
+
}
|
|
1358
|
+
if (thisOp === REPLACE || thisOp === ADD) {
|
|
1359
|
+
subObject[lastProp] = thisDiff.value;
|
|
1360
|
+
}
|
|
1361
|
+
if (thisOp === MOVE) {
|
|
1362
|
+
subObject[lastToProp] = valueToMove;
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
return subObject;
|
|
1366
|
+
}
|
|
1367
|
+
function transformPath(pathConverter, thisPath) {
|
|
1368
|
+
{
|
|
1369
|
+
if (!Array.isArray(thisPath)) {
|
|
1370
|
+
throw new Error([
|
|
1371
|
+
"diff path",
|
|
1372
|
+
thisPath,
|
|
1373
|
+
"must be an array, consider supplying a path converter"
|
|
1374
|
+
].join(" "));
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
return thisPath;
|
|
1378
|
+
}
|
|
1379
|
+
function prototypeCheck(prop) {
|
|
1380
|
+
if (prop == "__proto__" || prop == "constructor" || prop == "prototype") {
|
|
1381
|
+
throw new Error("setting of prototype values not supported");
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
const patch = (...args) => {
|
|
1386
|
+
const [source, diff] = args;
|
|
1387
|
+
const patched = structuredClone(source);
|
|
1388
|
+
diffApply(patched, diff);
|
|
1389
|
+
return patched;
|
|
1390
|
+
};
|
|
1391
|
+
const VerifyResultFactory = (issuedAt, expiredAt) => ({
|
|
1392
|
+
create: (vc, jwt, verificationKey, validated = false) => {
|
|
1393
|
+
const unverified = {
|
|
1394
|
+
doc: vc,
|
|
1395
|
+
issuedAt,
|
|
1396
|
+
expiredAt,
|
|
1397
|
+
algorithm: "ES256",
|
|
1398
|
+
mediaType: "application/vc+jwt",
|
|
1399
|
+
source: jwt
|
|
1400
|
+
};
|
|
1401
|
+
if (!verificationKey) return unverified;
|
|
1402
|
+
return { ...unverified, verificationKey, validated };
|
|
1403
|
+
}
|
|
1404
|
+
});
|
|
1405
|
+
const opId = {
|
|
1406
|
+
/** CP 発行者 */
|
|
1407
|
+
authority: "dns:cp-issuer.example.org",
|
|
1408
|
+
/** PA 発行者 */
|
|
1409
|
+
certifier: "dns:pa-issuer.example.org",
|
|
1410
|
+
/** CA 発行者 */
|
|
1411
|
+
originator: "dns:originator.example.org",
|
|
1412
|
+
/** 無効な第三者 */
|
|
1413
|
+
invalid: "dns:invalid.example.org"
|
|
1414
|
+
};
|
|
1415
|
+
const cp = {
|
|
1416
|
+
"@context": [
|
|
1417
|
+
"https://www.w3.org/ns/credentials/v2",
|
|
1418
|
+
"https://originator-profile.org/ns/credentials/v1"
|
|
1419
|
+
],
|
|
1420
|
+
type: ["VerifiableCredential", "CoreProfile"],
|
|
1421
|
+
issuer: opId.authority,
|
|
1422
|
+
credentialSubject: {
|
|
1423
|
+
id: opId.originator,
|
|
1424
|
+
type: "Core",
|
|
1425
|
+
jwks: {
|
|
1426
|
+
keys: []
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
};
|
|
1430
|
+
const certificate = {
|
|
1431
|
+
"@context": [
|
|
1432
|
+
"https://www.w3.org/ns/credentials/v2",
|
|
1433
|
+
"https://originator-profile.org/ns/credentials/v1",
|
|
1434
|
+
"https://originator-profile.org/ns/cip/v1",
|
|
1435
|
+
{
|
|
1436
|
+
"@language": "ja"
|
|
1437
|
+
}
|
|
1438
|
+
],
|
|
1439
|
+
type: ["VerifiableCredential", "Certificate"],
|
|
1440
|
+
issuer: opId.certifier,
|
|
1441
|
+
credentialSubject: {
|
|
1442
|
+
id: opId.originator,
|
|
1443
|
+
type: "CertificateProperties",
|
|
1444
|
+
description: "Example Certificate",
|
|
1445
|
+
certificationSystem: {
|
|
1446
|
+
id: "urn:uuid:de5d6e80-10a5-404f-b4d3-e9f0e6926a21",
|
|
1447
|
+
type: "CertificationSystem",
|
|
1448
|
+
name: "Example Certification System",
|
|
1449
|
+
description: "Example Certification System Description"
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
};
|
|
1453
|
+
const wmp = {
|
|
1454
|
+
"@context": [
|
|
1455
|
+
"https://www.w3.org/ns/credentials/v2",
|
|
1456
|
+
"https://originator-profile.org/ns/credentials/v1",
|
|
1457
|
+
"https://originator-profile.org/ns/cip/v1",
|
|
1458
|
+
{
|
|
1459
|
+
"@language": "ja"
|
|
1460
|
+
}
|
|
1461
|
+
],
|
|
1462
|
+
type: ["VerifiableCredential", "WebMediaProfile"],
|
|
1463
|
+
issuer: opId.authority,
|
|
1464
|
+
credentialSubject: {
|
|
1465
|
+
id: opId.originator,
|
|
1466
|
+
type: "OnlineBusiness",
|
|
1467
|
+
name: "Example OP Holder",
|
|
1468
|
+
url: "https://op-originator.example.org/"
|
|
1469
|
+
}
|
|
1470
|
+
};
|
|
1471
|
+
const wsp = {
|
|
1472
|
+
"@context": [
|
|
1473
|
+
"https://www.w3.org/ns/credentials/v2",
|
|
1474
|
+
"https://originator-profile.org/ns/credentials/v1",
|
|
1475
|
+
"https://originator-profile.org/ns/cip/v1",
|
|
1476
|
+
{
|
|
1477
|
+
"@language": "ja"
|
|
1478
|
+
}
|
|
1479
|
+
],
|
|
1480
|
+
type: ["VerifiableCredential", "WebsiteProfile"],
|
|
1481
|
+
issuer: opId.originator,
|
|
1482
|
+
credentialSubject: {
|
|
1483
|
+
id: "https://originator.example.org",
|
|
1484
|
+
type: "WebSite",
|
|
1485
|
+
name: "Example Website",
|
|
1486
|
+
description: "Example Website Description",
|
|
1487
|
+
allowedOrigin: ["https://originator.example.org"]
|
|
1488
|
+
}
|
|
1489
|
+
};
|
|
1490
|
+
const caId = "urn:uuid:78550fa7-f846-4e0f-ad5c-8d34461cb95b";
|
|
1491
|
+
const caUrl = new URL("https://www.example.org/articles/example");
|
|
1492
|
+
const article = {
|
|
1493
|
+
"@context": [
|
|
1494
|
+
"https://www.w3.org/ns/credentials/v2",
|
|
1495
|
+
"https://originator-profile.org/ns/credentials/v1",
|
|
1496
|
+
"https://originator-profile.org/ns/cip/v1",
|
|
1497
|
+
{ "@language": "ja" }
|
|
1498
|
+
],
|
|
1499
|
+
type: ["VerifiableCredential", "ContentAttestation"],
|
|
1500
|
+
issuer: opId.originator,
|
|
1501
|
+
target: [],
|
|
1502
|
+
allowedUrl: ["https://www.example.org/articles*"],
|
|
1503
|
+
credentialSubject: {
|
|
1504
|
+
id: caId,
|
|
1505
|
+
type: "Article",
|
|
1506
|
+
headline: "\u30C6\u30B9\u30C8\u8A18\u4E8B",
|
|
1507
|
+
description: "\u8A18\u4E8B\u306E\u8AAC\u660E"
|
|
1508
|
+
}
|
|
1509
|
+
};
|
|
1510
|
+
|
|
1511
|
+
function prepareRegistry(ops) {
|
|
1512
|
+
const decoded = decodeOps(ops);
|
|
1513
|
+
if (decoded instanceof OpsInvalid) {
|
|
1514
|
+
return decoded;
|
|
1515
|
+
}
|
|
1516
|
+
const [issuer, keys] = getTupledKeys(decoded);
|
|
1517
|
+
return { ops, keys, issuer };
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1219
1520
|
class SiteProfileInvalid extends Error {
|
|
1220
1521
|
constructor(message, result) {
|
|
1221
1522
|
super(message);
|
|
@@ -1315,7 +1616,8 @@ function SpVerifier(sp, keys, issuer, origin, options = {}) {
|
|
|
1315
1616
|
}
|
|
1316
1617
|
}
|
|
1317
1618
|
await verifyImageDigestSri(verified.doc.credentialSubject.image, {
|
|
1318
|
-
logger
|
|
1619
|
+
logger,
|
|
1620
|
+
at: pointer("sites", index)
|
|
1319
1621
|
});
|
|
1320
1622
|
return verified;
|
|
1321
1623
|
})
|
|
@@ -1344,4 +1646,43 @@ function SpVerifier(sp, keys, issuer, origin, options = {}) {
|
|
|
1344
1646
|
return verify;
|
|
1345
1647
|
}
|
|
1346
1648
|
|
|
1347
|
-
|
|
1649
|
+
async function verifyWebsite(origin, options) {
|
|
1650
|
+
const { siteProfile, registry, logger, ...verifierOptions } = options;
|
|
1651
|
+
const { logger: collecting, warnings, info } = collectProblems(logger);
|
|
1652
|
+
const verifySp = SpVerifier(
|
|
1653
|
+
{
|
|
1654
|
+
...siteProfile,
|
|
1655
|
+
originators: [...registry.ops, ...siteProfile.originators]
|
|
1656
|
+
},
|
|
1657
|
+
registry.keys,
|
|
1658
|
+
registry.issuer,
|
|
1659
|
+
origin,
|
|
1660
|
+
{ ...verifierOptions, logger: collecting }
|
|
1661
|
+
);
|
|
1662
|
+
const verified = await verifySp();
|
|
1663
|
+
const failed = verified instanceof Error;
|
|
1664
|
+
const sp = failed ? verified.result : verified;
|
|
1665
|
+
const collect = createCollector();
|
|
1666
|
+
const outcome = {
|
|
1667
|
+
originators: convertOps(sp.originators, collect),
|
|
1668
|
+
sites: sp.sites.map(
|
|
1669
|
+
(site, index) => convertVc(site, pointer("sites", index), collect)
|
|
1670
|
+
)
|
|
1671
|
+
};
|
|
1672
|
+
return failed ? {
|
|
1673
|
+
status: false,
|
|
1674
|
+
outcome,
|
|
1675
|
+
securingResults: collect.securingResults,
|
|
1676
|
+
warnings,
|
|
1677
|
+
info,
|
|
1678
|
+
errors: [toProblemDetails(verified), ...collect.errors]
|
|
1679
|
+
} : {
|
|
1680
|
+
status: true,
|
|
1681
|
+
outcome,
|
|
1682
|
+
securingResults: collect.securingResults,
|
|
1683
|
+
warnings,
|
|
1684
|
+
info
|
|
1685
|
+
};
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
export { CaInvalid, CaVerifier, CaVerifyFailed, CasVerifyFailed, CertificateExpired, CoreProfileNotFound, IntegrityFetchFailed, IntegrityVerificationFailed, OpInvalid, OpVerifyFailed, OpsInvalid, OpsVerifier, OpsVerifyFailed, ProblemType, SiteProfileInvalid, SiteProfileVerifyFailed, SpVerifier, TargetIntegrityAlgorithm, VerifyResultFactory, article, caId, caUrl, certificate, childPointer, collectProblems, convertCas, convertOp, convertOps, convertVc, cp, createCollector, decodeOps, getAnnotationPolicy, getMappedKeys, getTupledKeys, isProfileAnnotationIssuerRegistration, normalizeCasItem, opId, patch, pointer, prepareRegistry, problemType, toProblemDetails, verifyAllowedOrigin, verifyAnnotationIssuerRegistration, verifyCas, verifyDigestSri, verifyDocuments, verifyImageDigestSri, verifyIntegrity, verifyWebsite, wmp, wsp };
|