@originator-profile/verify 0.7.0-beta.3 → 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.
Files changed (3) hide show
  1. package/dist/index.d.ts +419 -67
  2. package/dist/index.js +609 -268
  3. 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, selectByIntegrity, fetchExternalResource, selectByCss, fetchVisibleTextContent, fetchTextContent, fetchHtmlContent, FetchFailed } from '@originator-profile/sign';
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
- logger.warn(`digestSRI is missing. ${WARN_SUFFIX}`);
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
- logger.warn(`digestSRI verification failed. ${WARN_SUFFIX}`);
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: selectByIntegrity
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
- async function checkUrlAndOrigin(result, url) {
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
- console.warn(
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
- async function verifyCas(cas, verifiedOps, url, verifyIntegrity, validator) {
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
- logger.warn(`Certificate is deprecated (${location})`);
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
- logger.info(
1040
- `Profile Annotation Issuer is not registered for this annotation scheme (${location} issuer: ${profileAnnotationIssuer}, scheme: ${policyId ?? "unknown"})`
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 OpVerifier(paOrWmpIssuerKeys, vc, validator) {
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
- export { CaInvalid, CaVerifier, CaVerifyFailed, CasVerifyFailed, CertificateExpired, CoreProfileNotFound, IntegrityFetchFailed, IntegrityVerificationFailed, OpInvalid, OpVerifyFailed, OpsInvalid, OpsVerifier, OpsVerifyFailed, SiteProfileInvalid, SiteProfileVerifyFailed, SpVerifier, TargetIntegrityAlgorithm, VerifyResultFactory, article, caId, caUrl, certificate, cp, decodeOps, getAnnotationPolicy, getMappedKeys, getTupledKeys, isProfileAnnotationIssuerRegistration, normalizeCasItem, opId, patch, verifyAllowedOrigin, verifyAnnotationIssuerRegistration, verifyCas, verifyDigestSri, verifyImageDigestSri, verifyIntegrity, wmp, wsp };
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 };