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