From 38a6b2e6e79d2e0746324bf45d594d7ac0167b4f Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Tue, 25 Oct 2022 14:34:52 +0200 Subject: [PATCH 01/19] Add cause's message to the resulting report --- .../org/oneedtech/inspect/vc/probe/ExternalProofProbe.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ExternalProofProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ExternalProofProbe.java index b14ce3c..ba299aa 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ExternalProofProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ExternalProofProbe.java @@ -50,7 +50,7 @@ public class ExternalProofProbe extends Probe { try { verifySignature(crd, ctx); } catch (Exception e) { - return fatal("Error verifying jwt signature: " + e.getMessage(), ctx); + return fatal("Error verifying jwt signature: " + e.getMessage() + (e.getCause() != null ? ". Reason: " + e.getCause().getMessage() : ""), ctx); } return success(ctx); } @@ -75,7 +75,9 @@ public class ExternalProofProbe extends Probe { JsonNode alg = headerObj.get("alg"); if(alg == null || !alg.textValue().equals("RS256")) { throw new Exception("alg must be present and must be 'RS256'"); } - //TODO: decoded jwt will check timestamps, but shall we explicitly break these out? + // decoded jwt will check timestamps, but shall we explicitly break these out? + // JWT verifier throws and exception with the cause when claims are invalid. Adding that cause + // to the probe result can avoid having to explicitly check the claims. //Option 1, fetch directly from header JsonNode jwk = headerObj.get("jwk"); From 677e547add4477a0cfdf84a6ed15a492e2cf2d29 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Wed, 4 Jan 2023 12:27:31 +0100 Subject: [PATCH 02/19] Get credentialSchema node list using utility method --- .../org/oneedtech/inspect/vc/probe/InlineJsonSchemaProbe.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/InlineJsonSchemaProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/InlineJsonSchemaProbe.java index 30c4324..e0ec3f7 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/InlineJsonSchemaProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/InlineJsonSchemaProbe.java @@ -12,6 +12,7 @@ import org.oneedtech.inspect.core.probe.json.JsonSchemaProbe; import org.oneedtech.inspect.core.report.ReportItems; import org.oneedtech.inspect.schema.SchemaKey; import org.oneedtech.inspect.vc.VerifiableCredential; +import org.oneedtech.inspect.vc.util.JsonNodeUtil; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; @@ -42,7 +43,7 @@ public class InlineJsonSchemaProbe extends Probe { JsonNode credentialSchemaNode = root.get("credentialSchema"); if(credentialSchemaNode == null) return success(ctx); - ArrayNode schemas = (ArrayNode) credentialSchemaNode; //TODO guard this cast + List schemas = JsonNodeUtil.asNodeList(credentialSchemaNode); for(JsonNode schemaNode : schemas) { JsonNode typeNode = schemaNode.get("type"); From 1fe23b727811a3af944b33779da2d710c7674024 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Thu, 5 Jan 2023 10:48:15 +0100 Subject: [PATCH 03/19] Check required values for `type` attribute added attributes for required type and achievement return error instead of notRun --- .../inspect/vc/EndorsementInspector.java | 4 +- .../oneedtech/inspect/vc/OB30Inspector.java | 2 +- .../vc/probe/CredentialSubjectProbe.java | 109 ++++++++-- .../inspect/vc/probe/IssuerProbe.java | 66 ++++++ .../org/oneedtech/inspect/vc/OB30Tests.java | 159 ++++++++++----- .../org/oneedtech/inspect/vc/Samples.java | 5 + ...t-achievement-result-description-type.json | 188 ++++++++++++++++++ ...rr-credential-subject-identifier-type.json | 52 +++++ ...e-err-credential-subject-profile-type.json | 52 +++++ ...le-err-credential-subject-result-type.json | 92 +++++++++ .../simple-err-credential-subject-type.json | 47 +++++ 11 files changed, 711 insertions(+), 65 deletions(-) create mode 100644 inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/IssuerProbe.java create mode 100644 inspector-vc/src/test/resources/ob30/simple-err-credential-subject-achievement-result-description-type.json create mode 100644 inspector-vc/src/test/resources/ob30/simple-err-credential-subject-identifier-type.json create mode 100644 inspector-vc/src/test/resources/ob30/simple-err-credential-subject-profile-type.json create mode 100644 inspector-vc/src/test/resources/ob30/simple-err-credential-subject-result-type.json create mode 100644 inspector-vc/src/test/resources/ob30/simple-err-credential-subject-type.json diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/EndorsementInspector.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/EndorsementInspector.java index 1e8efab..ce1b10a 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/EndorsementInspector.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/EndorsementInspector.java @@ -188,8 +188,8 @@ public class EndorsementInspector extends VCInspector implements SubInspector { //credentialSubject probeCount++; - accumulator.add(new CredentialSubjectProbe().run(endorsement.getJson(), ctx)); - + accumulator.add(new CredentialSubjectProbe("EndorsementSubject").run(endorsement.getJson(), ctx)); + //signatures, proofs probeCount++; if(endorsement.getProofType() == EXTERNAL){ diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB30Inspector.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB30Inspector.java index 163b9ec..a2dde88 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB30Inspector.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB30Inspector.java @@ -170,7 +170,7 @@ public class OB30Inspector extends VCInspector implements SubInspector { //credentialSubject probeCount++; - accumulator.add(new CredentialSubjectProbe().run(ob.getJson(), ctx)); + accumulator.add(new CredentialSubjectProbe("AchievementSubject", true).run(ob.getJson(), ctx)); //signatures, proofs probeCount++; diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/CredentialSubjectProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/CredentialSubjectProbe.java index 2932563..b2c3494 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/CredentialSubjectProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/CredentialSubjectProbe.java @@ -1,21 +1,36 @@ package org.oneedtech.inspect.vc.probe; +import java.util.List; + import org.oneedtech.inspect.core.probe.Probe; import org.oneedtech.inspect.core.probe.RunContext; import org.oneedtech.inspect.core.report.ReportItems; +import org.oneedtech.inspect.vc.util.JsonNodeUtil; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; /** * A Probe that checks credential subject specifics not capturable by schemata. - * + * * @author mgylling */ public class CredentialSubjectProbe extends Probe { - - public CredentialSubjectProbe() { + + /** + * Required type to be present. + */ + private final String requiredType; + private boolean achivementRequired; + + public CredentialSubjectProbe(String requiredType) { + this(requiredType, false); + } + + public CredentialSubjectProbe(String requiredType, boolean achivementRequired) { super(ID); + this.requiredType = requiredType; + this.achivementRequired = achivementRequired; } @Override @@ -24,18 +39,86 @@ public class CredentialSubjectProbe extends Probe { JsonNode subject = root.get("credentialSubject"); if(subject == null) return notRun("no credentialSubject node found", ctx); //error reported by schema - /* - * Check that we have either .id or .identifier populated + /** + * Check that type contains AchievementSubject */ + if (!JsonNodeUtil.asStringList(subject.get("type")).contains(requiredType)) { + return error("credentialSubject is not of type \"" + requiredType + "\"", ctx); + } + + /* + * Check that we have either .id or .identifier populated + */ + if (idAndIdentifierEmpty(subject)) { + return error("no id in credentialSubject", ctx); + } + + /** + * if .identifier is provider, check its type + */ + if (subject.hasNonNull("identifier")) { + List identifiers = JsonNodeUtil.asNodeList(subject.get("identifier")); + for (JsonNode identifier : identifiers) { + // check that type contains "IdentityObject" + if (!JsonNodeUtil.asStringList(identifier.get("type")).contains("IdentityObject")) { + return error("identifier in credentialSubject is not of type \"IdentityObject\"", ctx); + } + } + } + + /* + * Check results + */ + if (subject.hasNonNull("result")) { + List results = JsonNodeUtil.asNodeList(subject.get("result")); + for (JsonNode result : results) { + // check that type contains "Result" + if (!JsonNodeUtil.asStringList(result.get("type")).contains("Result")) { + return error("result in credentialSubject is not of type \"Result\"", ctx); + } + } + } + + /* + * Check achievement result description + */ + if (subject.hasNonNull("achievement")) { + JsonNode achievement = subject.get("achievement"); + if (achievement.hasNonNull("resultDescription")) { + List resultDescriptions = JsonNodeUtil.asNodeList(achievement.get("resultDescription")); + for (JsonNode resultDescription : resultDescriptions) { + // check that type contains "ResultDescription" + if (!JsonNodeUtil.asStringList(resultDescription.get("type")).contains("ResultDescription")) { + return error("resultDescription in achievement of credentialSubject is not of type \"ResultDescription\"", ctx); + } + } + } + } else if (achivementRequired) { + return error("missing required achievement in credentialSubject", ctx); + } + + /** + * Check that source type contains "Profile" + */ + if (subject.hasNonNull("source")) { + JsonNode source = subject.get("source"); + // check that type contains "Profile" + if (!JsonNodeUtil.asStringList(source.get("type")).contains("Profile")) { + return error("source in credentialSubject is not of type \"Profile\"", ctx); + } + } + return success(ctx); + } + + private boolean idAndIdentifierEmpty(JsonNode root) { JsonNode id = root.get("id"); - if (id != null && id.textValue().strip().length() > 0) return success(ctx); - - JsonNode identifier = root.get("identifier"); - if(identifier != null && identifier instanceof ArrayNode - && ((ArrayNode)identifier).size() > 0) return success(ctx); - - return error("no id in credentialSubject", ctx); - + if (id != null && id.textValue().strip().length() > 0) return false; + + JsonNode identifier = root.get("identifier"); + if(identifier != null && identifier instanceof ArrayNode + && ((ArrayNode)identifier).size() > 0) return false; + + return true; } public static final String ID = CredentialSubjectProbe.class.getSimpleName(); diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/IssuerProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/IssuerProbe.java new file mode 100644 index 0000000..bb9238b --- /dev/null +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/IssuerProbe.java @@ -0,0 +1,66 @@ +package org.oneedtech.inspect.vc.probe; + +import java.net.URI; +import java.util.List; + +import org.oneedtech.inspect.core.probe.Probe; +import org.oneedtech.inspect.core.probe.RunContext; +import org.oneedtech.inspect.core.report.ReportItems; +import org.oneedtech.inspect.util.resource.UriResource; +import org.oneedtech.inspect.vc.util.JsonNodeUtil; + +import com.fasterxml.jackson.databind.JsonNode; + +public class IssuerProbe extends Probe { + public IssuerProbe() { + super(ID); + } + + @Override + public ReportItems run(JsonNode root, RunContext ctx) throws Exception { + JsonNode issuer = root.get("issuer"); + if(issuer == null) return error("no issuer node found", ctx); + + // check that type contains "Profile" + if (!JsonNodeUtil.asStringList(issuer.get("type")).contains("Profile")) { + return error("issuer is not of type \"Profile\"", ctx); + } + + // check url is accessible + if (issuer.hasNonNull("url")) { + try { + UriResource urlResource = new UriResource(new URI(issuer.get("url").asText().strip())); + if (!urlResource.exists()) { + return warning("url \"" + issuer.get("url").asText().strip() + "\" in issuer is not accessible", ctx); + } + } catch (Exception e) { + return warning("url \"" + issuer.get("url").asText().strip() + "\" in issuer is not accessible", ctx); + } + } + + // check other identifier + if (issuer.hasNonNull("otherIdentifier")) { + List otherIdentifiers = JsonNodeUtil.asNodeList(issuer.get("otherIdentifier")); + for (JsonNode otherIdentifier : otherIdentifiers) { + // check that type contains "IdentityObject" + if (!JsonNodeUtil.asStringList(otherIdentifier.get("type")).contains("IdentityObject")) { + return error("otherIdentifier in issuer is not of type \"IdentityObject\"", ctx); + } + } + } + + // check parent issuer + if (issuer.hasNonNull("parentOrg")) { + JsonNode parentOrg = issuer.get("parentOrg"); + // check that type contains "Profile" + if (!JsonNodeUtil.asStringList(parentOrg.get("type")).contains("Profile")) { + return error("parentOrg in issuer is not of type \"Profile\"", ctx); + } + } + + return success(ctx); + } + + public static final String ID = IssuerProbe.class.getSimpleName(); + +} diff --git a/inspector-vc/src/test/java/org/oneedtech/inspect/vc/OB30Tests.java b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/OB30Tests.java index 036eb9e..e764640 100644 --- a/inspector-vc/src/test/java/org/oneedtech/inspect/vc/OB30Tests.java +++ b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/OB30Tests.java @@ -11,6 +11,7 @@ import org.oneedtech.inspect.core.probe.json.JsonSchemaProbe; import org.oneedtech.inspect.core.report.Report; import org.oneedtech.inspect.test.PrintHelper; import org.oneedtech.inspect.vc.probe.ContextPropertyProbe; +import org.oneedtech.inspect.vc.probe.CredentialSubjectProbe; import org.oneedtech.inspect.vc.probe.ExpirationProbe; import org.oneedtech.inspect.vc.probe.InlineJsonSchemaProbe; import org.oneedtech.inspect.vc.probe.IssuanceProbe; @@ -20,51 +21,51 @@ import org.oneedtech.inspect.vc.probe.TypePropertyProbe; import com.google.common.collect.Iterables; public class OB30Tests { - private static OB30Inspector validator; + private static OB30Inspector validator; private static boolean verbose = true; - - @BeforeAll - static void setup() { - validator = new OB30Inspector.Builder() - .set(Behavior.TEST_INCLUDE_SUCCESS, true) + + @BeforeAll + static void setup() { + validator = new OB30Inspector.Builder() + .set(Behavior.TEST_INCLUDE_SUCCESS, true) .set(Behavior.VALIDATOR_FAIL_FAST, true) - .build(); + .build(); } - + @Test void testSimpleJsonValid() { assertDoesNotThrow(()->{ Report report = validator.run(Samples.OB30.JSON.SIMPLE_JSON.asFileResource()); if(verbose) PrintHelper.print(report, true); - assertValid(report); - }); + assertValid(report); + }); } - + @Test void testSimpleDidMethodJsonValid() { assertDoesNotThrow(()->{ Report report = validator.run(Samples.OB30.JSON.SIMPLE_DID_METHOD_JSON.asFileResource()); if(verbose) PrintHelper.print(report, true); - assertValid(report); - }); + assertValid(report); + }); } - + @Test void testSimplePNGPlainValid() { assertDoesNotThrow(()->{ Report report = validator.run(Samples.OB30.PNG.SIMPLE_JSON_PNG.asFileResource()); if(verbose) PrintHelper.print(report, true); - assertValid(report); - }); + assertValid(report); + }); } - + @Test void testSimplePNGJWTValid() { assertDoesNotThrow(()->{ Report report = validator.run(Samples.OB30.PNG.SIMPLE_JWT_PNG.asFileResource()); if(verbose) PrintHelper.print(report, true); - assertValid(report); - }); + assertValid(report); + }); } @Test @@ -72,17 +73,17 @@ public class OB30Tests { assertDoesNotThrow(()->{ Report report = validator.run(Samples.OB30.SVG.SIMPLE_JSON_SVG.asFileResource()); if(verbose) PrintHelper.print(report, true); - assertValid(report); - }); + assertValid(report); + }); } - + @Test void testSimpleJsonSVGJWTValid() { assertDoesNotThrow(()->{ Report report = validator.run(Samples.OB30.SVG.SIMPLE_JWT_SVG.asFileResource()); if(verbose) PrintHelper.print(report, true); - assertValid(report); - }); + assertValid(report); + }); } @Test @@ -94,9 +95,9 @@ public class OB30Tests { assertInvalid(report); assertFatalCount(report, 1); assertHasProbeID(report, TypePropertyProbe.ID, true); - }); + }); } - + @Test void testSimpleJsonInvalidProofMethod() { // add some garbage chars to the verification method fragment @@ -107,9 +108,9 @@ public class OB30Tests { assertInvalid(report); assertErrorCount(report, 1); assertHasProbeID(report, EmbeddedProofProbe.ID, true); - }); + }); } - + @Test void testSimpleJsonInvalidProofMethodNoScheme() { // The verificationMethod is not a URI (no scheme) @@ -119,9 +120,9 @@ public class OB30Tests { assertInvalid(report); assertErrorCount(report, 1); assertHasProbeID(report, EmbeddedProofProbe.ID, true); - }); + }); } - + @Test void testSimpleJsonInvalidProofMethodUnknownScheme() { // The verificationMethod is not a URI (no scheme) @@ -131,9 +132,9 @@ public class OB30Tests { assertInvalid(report); assertErrorCount(report, 1); assertHasProbeID(report, EmbeddedProofProbe.ID, true); - }); + }); } - + @Test void testSimpleJsonInvalidProofMethodUnknownDidMethod() { // The verificationMethod is an unknown DID Method @@ -143,9 +144,9 @@ public class OB30Tests { assertInvalid(report); assertErrorCount(report, 1); assertHasProbeID(report, EmbeddedProofProbe.ID, true); - }); + }); } - + @Test void testSimpleJsonInvalidProofValue() { //add some garbage chars to proofValue @@ -155,9 +156,9 @@ public class OB30Tests { assertInvalid(report); assertErrorCount(report, 1); assertHasProbeID(report, EmbeddedProofProbe.ID, true); - }); + }); } - + @Test void testSimpleJsonExpired() { //"expirationDate": "2020-01-20T00:00:00Z", @@ -166,20 +167,20 @@ public class OB30Tests { if(verbose) PrintHelper.print(report, true); assertInvalid(report); assertHasProbeID(report, ExpirationProbe.ID, true); - }); + }); } - + @Test - void testSimpleJsonContextError() { + void testSimpleJsonContextError() { //removed one of the reqd context uris assertDoesNotThrow(()->{ Report report = validator.run(Samples.OB30.JSON.SIMPLE_JSON_ERR_CONTEXT.asFileResource()); if(verbose) PrintHelper.print(report, true); assertInvalid(report); assertHasProbeID(report, ContextPropertyProbe.ID, true); - }); + }); } - + @Test void testSimpleJsonSchemaError() throws Exception { //issuer removed @@ -187,10 +188,70 @@ public class OB30Tests { Report report = validator.run(Samples.OB30.JSON.SIMPLE_JSON_ISSUER.asFileResource()); if(verbose) PrintHelper.print(report, true); assertInvalid(report); - assertHasProbeID(report, JsonSchemaProbe.ID, true); - }); + assertHasProbeID(report, JsonSchemaProbe.ID, true); + }); } - + + @Test + void testSimpleJsonInvalidCredentialSubjectType() { + //add a dumb value to .type and remove the ob type + assertDoesNotThrow(()->{ + Report report = validator.run(Samples.OB30.JSON.SIMPLE_JSON_UNKNOWN_CREDENTIAL_SUBJECT_TYPE.asFileResource()); + if(verbose) PrintHelper.print(report, true); + assertInvalid(report); + // assertFatalCount(report, 1); + assertHasProbeID(report, CredentialSubjectProbe.ID, true); + }); + } + + @Test + void testSimpleJsonInvalidCredentialSubjectIdentifierType() { + //add a dumb value to .type and remove the ob type + assertDoesNotThrow(()->{ + Report report = validator.run(Samples.OB30.JSON.SIMPLE_JSON_UNKNOWN_CREDENTIAL_SUBJECT_IDENTIFIER_TYPE.asFileResource()); + if(verbose) PrintHelper.print(report, true); + assertInvalid(report); + // assertFatalCount(report, 1); + assertHasProbeID(report, CredentialSubjectProbe.ID, true); + }); + } + + @Test + void testSimpleJsonInvalidCredentialSubjectResultType() { + //add a dumb value to .type and remove the ob type + assertDoesNotThrow(()->{ + Report report = validator.run(Samples.OB30.JSON.SIMPLE_JSON_UNKNOWN_CREDENTIAL_SUBJECT_RESULT_TYPE.asFileResource()); + if(verbose) PrintHelper.print(report, true); + assertInvalid(report); + // assertFatalCount(report, 1); + assertHasProbeID(report, CredentialSubjectProbe.ID, true); + }); + } + + @Test + void testSimpleJsonInvalidCredentialSubjectAchievementResultDescriptionType() { + //add a dumb value to .type and remove the ob type + assertDoesNotThrow(()->{ + Report report = validator.run(Samples.OB30.JSON.SIMPLE_JSON_UNKNOWN_CREDENTIAL_SUBJECT_ACHIEVEMENT_RESULT_DESCRIPTION_TYPE.asFileResource()); + if(verbose) PrintHelper.print(report, true); + assertInvalid(report); + // assertFatalCount(report, 1); + assertHasProbeID(report, CredentialSubjectProbe.ID, true); + }); + } + + @Test + void testSimpleJsonInvalidCredentialSubjectProfileType() { + //add a dumb value to .type and remove the ob type + assertDoesNotThrow(()->{ + Report report = validator.run(Samples.OB30.JSON.SIMPLE_JSON_UNKNOWN_CREDENTIAL_SUBJECT_PROFILE_TYPE.asFileResource()); + if(verbose) PrintHelper.print(report, true); + assertInvalid(report); + // assertFatalCount(report, 1); + assertHasProbeID(report, CredentialSubjectProbe.ID, true); + }); + } + @Disabled //TODO IssuanceVerifierProbe is not run because FATAL: InvalidSignature terminates @Test void testSimpleJsonNotIssued() { @@ -201,9 +262,9 @@ public class OB30Tests { if(verbose) PrintHelper.print(report, true); assertInvalid(report); assertHasProbeID(report, IssuanceProbe.ID, true); - }); + }); } - + @Test void testCompleteJsonInvalidInlineSchemaRef() throws Exception { //404 inline schema ref, and 404 refresh uri @@ -212,9 +273,9 @@ public class OB30Tests { if(verbose) PrintHelper.print(report, true); assertFalse(report.asBoolean()); assertTrue(Iterables.size(report.getErrors()) > 0); - assertTrue(Iterables.size(report.getExceptions()) > 0); - assertHasProbeID(report, InlineJsonSchemaProbe.ID, true); - }); + assertTrue(Iterables.size(report.getExceptions()) > 0); + assertHasProbeID(report, InlineJsonSchemaProbe.ID, true); + }); } } diff --git a/inspector-vc/src/test/java/org/oneedtech/inspect/vc/Samples.java b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/Samples.java index b484f56..2b44942 100644 --- a/inspector-vc/src/test/java/org/oneedtech/inspect/vc/Samples.java +++ b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/Samples.java @@ -14,6 +14,11 @@ public class Samples { public final static Sample SIMPLE_DID_METHOD_JSON = new Sample("ob30/simple-did-method.json", true); public final static Sample SIMPLE_JSON_NOPROOF = new Sample("ob30/simple-noproof.json", false); public final static Sample SIMPLE_JSON_UNKNOWN_TYPE = new Sample("ob30/simple-err-type.json", false); + public final static Sample SIMPLE_JSON_UNKNOWN_CREDENTIAL_SUBJECT_TYPE = new Sample("ob30/simple-err-credential-subject-type.json", false); + public final static Sample SIMPLE_JSON_UNKNOWN_CREDENTIAL_SUBJECT_IDENTIFIER_TYPE = new Sample("ob30/simple-err-credential-subject-identifier-type.json", false); + public final static Sample SIMPLE_JSON_UNKNOWN_CREDENTIAL_SUBJECT_RESULT_TYPE = new Sample("ob30/simple-err-credential-subject-result-type.json", false); + public final static Sample SIMPLE_JSON_UNKNOWN_CREDENTIAL_SUBJECT_ACHIEVEMENT_RESULT_DESCRIPTION_TYPE = new Sample("ob30/simple-err-credential-subject-achievement-result-description-type.json", false); + public final static Sample SIMPLE_JSON_UNKNOWN_CREDENTIAL_SUBJECT_PROFILE_TYPE = new Sample("ob30/simple-err-credential-subject-profile-type.json", false); public final static Sample SIMPLE_JSON_PROOF_METHOD_ERROR = new Sample("ob30/simple-err-proof-method.json", false); public final static Sample SIMPLE_JSON_PROOF_METHOD_NO_SCHEME_ERROR = new Sample("ob30/simple-err-proof-method-no-scheme.json", false); public final static Sample SIMPLE_JSON_PROOF_METHOD_UNKNOWN_SCHEME_ERROR = new Sample("ob30/simple-err-proof-method-unknown-scheme.json", false); diff --git a/inspector-vc/src/test/resources/ob30/simple-err-credential-subject-achievement-result-description-type.json b/inspector-vc/src/test/resources/ob30/simple-err-credential-subject-achievement-result-description-type.json new file mode 100644 index 0000000..dd6b7ae --- /dev/null +++ b/inspector-vc/src/test/resources/ob30/simple-err-credential-subject-achievement-result-description-type.json @@ -0,0 +1,188 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://purl.imsglobal.org/spec/ob/v3p0/context.json", + "https://w3id.org/security/suites/ed25519-2020/v1" + ], + "id": "http://example.com/credentials/3527", + "type": [ + "VerifiableCredential", + "OpenBadgeCredential" + ], + "issuer": { + "id": "https://example.com/issuers/876543", + "type": [ + "Profile" + ], + "name": "Example Corp" + }, + "issuanceDate": "2010-01-01T00:00:00Z", + "name": "Teamwork Badge", + "credentialSubject": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "type": [ + "AchievementSubject" + ], + "achievement": { + "id": "https://example.com/achievements/21st-century-skills/teamwork", + "type": [ + "Achievement" + ], + "criteria": { + "narrative": "Team members are nominated for this badge by their peers and recognized upon review by Example Corp management." + }, + "description": "This badge recognizes the development of the capacity to collaborate within a group environment.", + "name": "Teamwork", + "resultDescription": [ + { + "id": "urn:uuid:f6ab24cd-86e8-4eaf-b8c6-ded74e8fd41c", + "type": "ResultDescription", + "alignment": [ + { + "type": "Alignment", + "targetCode": "project", + "targetDescription": "Project description", + "targetName": "Final Project", + "targetFramework": "1EdTech University Program and Course Catalog", + "targetType": "CFItem", + "targetUrl": "https://1edtech.edu/catalog/degree/project" + } + ], + "allowedValue": [ + "D", + "C", + "B", + "A" + ], + "name": "Final Project Grade", + "requiredValue": "C", + "resultType": "LetterGrade" + }, + { + "id": "urn:uuid:a70ddc6a-4c4a-4bd8-8277-cb97c79f40c5", + "type": "ResultDescription", + "alignment": [ + { + "type": "Alignment", + "targetCode": "project", + "targetDescription": "Project description", + "targetName": "Final Project", + "targetFramework": "1EdTech University Program and Course Catalog", + "targetType": "CFItem", + "targetUrl": "https://1edtech.edu/catalog/degree/project" + } + ], + "allowedValue": [ + "D", + "C", + "B", + "A" + ], + "name": "Final Project Grade", + "requiredLevel": "urn:uuid:d05a0867-d0ad-4b03-bdb5-28fb5d2aab7a", + "resultType": "RubricCriterionLevel", + "rubricCriterionLevel": [ + { + "id": "urn:uuid:d05a0867-d0ad-4b03-bdb5-28fb5d2aab7a", + "type": "RubricCriterionLevel", + "alignment": [ + { + "type": "Alignment", + "targetCode": "project", + "targetDescription": "Project description", + "targetName": "Final Project", + "targetFramework": "1EdTech University Program and Course Catalog", + "targetType": "CFRubricCriterionLevel", + "targetUrl": "https://1edtech.edu/catalog/degree/project/rubric/levels/mastered" + } + ], + "description": "The author demonstrated...", + "level": "Mastered", + "name": "Mastery", + "points": "4" + }, + { + "id": "urn:uuid:6b84b429-31ee-4dac-9d20-e5c55881f80e", + "type": "RubricCriterionLevel", + "alignment": [ + { + "type": "Alignment", + "targetCode": "project", + "targetDescription": "Project description", + "targetName": "Final Project", + "targetFramework": "1EdTech University Program and Course Catalog", + "targetType": "CFRubricCriterionLevel", + "targetUrl": "https://1edtech.edu/catalog/degree/project/rubric/levels/basic" + } + ], + "description": "The author demonstrated...", + "level": "Basic", + "name": "Basic", + "points": "4" + } + ] + }, + { + "id": "urn:uuid:b07c0387-f2d6-4b65-a3f4-f4e4302ea8f7", + "type": "InvalidResultDescription", + "name": "Project Status", + "resultType": "Status" + } + ] + }, + "result": [ + { + "type": [ + "Result" + ], + "alignment": [ + { + "type": "Alignment", + "targetCode": "project", + "targetDescription": "Project description", + "targetName": "Final Project", + "targetFramework": "1EdTech University Program and Course Catalog", + "targetType": "CFItem", + "targetUrl": "https://1edtech.edu/catalog/degree/project/result/1" + } + ], + "resultDescription": "urn:uuid:f6ab24cd-86e8-4eaf-b8c6-ded74e8fd41c", + "value": "A" + }, + { + "type": [ + "Result" + ], + "achievedLevel": "urn:uuid:d05a0867-d0ad-4b03-bdb5-28fb5d2aab7a", + "alignment": [ + { + "type": "Alignment", + "targetCode": "project", + "targetDescription": "Project description", + "targetName": "Final Project", + "targetFramework": "1EdTech University Program and Course Catalog", + "targetType": "CFItem", + "targetUrl": "https://1edtech.edu/catalog/degree/project/result/1" + } + ], + "resultDescription": "urn:uuid:f6ab24cd-86e8-4eaf-b8c6-ded74e8fd41c" + }, + { + "type": [ + "Result" + ], + "resultDescription": "urn:uuid:f6ab24cd-86e8-4eaf-b8c6-ded74e8fd41c", + "status": "Completed" + } + ] + }, + "proof": [ + { + "type": "Ed25519Signature2020", + "created": "2022-11-16T18:54:22Z", + "verificationMethod": "https://example.com/issuers/876543#z6MknNHHrBzPytzu6CUBP9Lg7fg4KSBjzimc2Frh693YbMiv", + "proofPurpose": "assertionMethod", + "proofValue": "z5gJZKchSJEYPGeq6bsqiLKuxT6mXqAovPbqYX66CB7u9CSNFdV41vHtysjHFiitvoyhfPxsaZnWftrZZZW2txPQK" + } + ] +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob30/simple-err-credential-subject-identifier-type.json b/inspector-vc/src/test/resources/ob30/simple-err-credential-subject-identifier-type.json new file mode 100644 index 0000000..64960bd --- /dev/null +++ b/inspector-vc/src/test/resources/ob30/simple-err-credential-subject-identifier-type.json @@ -0,0 +1,52 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://purl.imsglobal.org/spec/ob/v3p0/context.json", + "https://w3id.org/security/suites/ed25519-2020/v1" + ], + "id": "http://example.com/credentials/3527", + "type": [ + "VerifiableCredential", + "OpenBadgeCredential" + ], + "issuer": { + "id": "https://example.com/issuers/876543", + "type": [ + "Profile" + ], + "name": "Example Corp" + }, + "issuanceDate": "2010-01-01T00:00:00Z", + "name": "Teamwork Badge", + "credentialSubject": { + "identifier": [{ + "type": "InvalidIdentityObject", + "hashed": true, + "identityHash": "asdjhsadas", + "identityType": "lisSourcedId" + }], + "type": [ + "AchievementSubject" + ], + "achievement": { + "id": "https://example.com/achievements/21st-century-skills/teamwork", + "type": [ + "Achievement" + ], + "criteria": { + "narrative": "Team members are nominated for this badge by their peers and recognized upon review by Example Corp management." + }, + "description": "This badge recognizes the development of the capacity to collaborate within a group environment.", + "name": "Teamwork" + } + }, + "proof": [ + { + "type": "Ed25519Signature2020", + "created": "2022-11-16T18:54:22Z", + "verificationMethod": "https://example.com/issuers/876543#z6MknNHHrBzPytzu6CUBP9Lg7fg4KSBjzimc2Frh693YbMiv", + "proofPurpose": "assertionMethod", + "proofValue": "z5gJZKchSJEYPGeq6bsqiLKuxT6mXqAovPbqYX66CB7u9CSNFdV41vHtysjHFiitvoyhfPxsaZnWftrZZZW2txPQK" + } + ] + } \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob30/simple-err-credential-subject-profile-type.json b/inspector-vc/src/test/resources/ob30/simple-err-credential-subject-profile-type.json new file mode 100644 index 0000000..57646c7 --- /dev/null +++ b/inspector-vc/src/test/resources/ob30/simple-err-credential-subject-profile-type.json @@ -0,0 +1,52 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://purl.imsglobal.org/spec/ob/v3p0/context.json", + "https://w3id.org/security/suites/ed25519-2020/v1" + ], + "id": "http://example.com/credentials/3527", + "type": [ + "VerifiableCredential", + "OpenBadgeCredential" + ], + "issuer": { + "id": "https://example.com/issuers/876543", + "type": [ + "Profile" + ], + "name": "Example Corp" + }, + "issuanceDate": "2010-01-01T00:00:00Z", + "name": "Teamwork Badge", + "credentialSubject": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "type": [ + "AchievementSubject" + ], + "achievement": { + "id": "https://example.com/achievements/21st-century-skills/teamwork", + "type": [ + "Achievement" + ], + "criteria": { + "narrative": "Team members are nominated for this badge by their peers and recognized upon review by Example Corp management." + }, + "description": "This badge recognizes the development of the capacity to collaborate within a group environment.", + "name": "Teamwork" + }, + "source": { + "id": "https://school.edu/issuers/201234", + "type": "InvalidProfile", + "name": "1EdTech College of Arts" + } + }, + "proof": [ + { + "type": "Ed25519Signature2020", + "created": "2022-11-16T18:54:22Z", + "verificationMethod": "https://example.com/issuers/876543#z6MknNHHrBzPytzu6CUBP9Lg7fg4KSBjzimc2Frh693YbMiv", + "proofPurpose": "assertionMethod", + "proofValue": "z5gJZKchSJEYPGeq6bsqiLKuxT6mXqAovPbqYX66CB7u9CSNFdV41vHtysjHFiitvoyhfPxsaZnWftrZZZW2txPQK" + } + ] +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob30/simple-err-credential-subject-result-type.json b/inspector-vc/src/test/resources/ob30/simple-err-credential-subject-result-type.json new file mode 100644 index 0000000..5275a9a --- /dev/null +++ b/inspector-vc/src/test/resources/ob30/simple-err-credential-subject-result-type.json @@ -0,0 +1,92 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://purl.imsglobal.org/spec/ob/v3p0/context.json", + "https://w3id.org/security/suites/ed25519-2020/v1" + ], + "id": "http://example.com/credentials/3527", + "type": [ + "VerifiableCredential", + "OpenBadgeCredential" + ], + "issuer": { + "id": "https://example.com/issuers/876543", + "type": [ + "Profile" + ], + "name": "Example Corp" + }, + "issuanceDate": "2010-01-01T00:00:00Z", + "name": "Teamwork Badge", + "credentialSubject": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "type": [ + "AchievementSubject" + ], + "achievement": { + "id": "https://example.com/achievements/21st-century-skills/teamwork", + "type": [ + "Achievement" + ], + "criteria": { + "narrative": "Team members are nominated for this badge by their peers and recognized upon review by Example Corp management." + }, + "description": "This badge recognizes the development of the capacity to collaborate within a group environment.", + "name": "Teamwork" + }, + "result": [ + { + "type": [ + "Result" + ], + "alignment": [ + { + "type": "Alignment", + "targetCode": "project", + "targetDescription": "Project description", + "targetName": "Final Project", + "targetFramework": "1EdTech University Program and Course Catalog", + "targetType": "CFItem", + "targetUrl": "https://1edtech.edu/catalog/degree/project/result/1" + } + ], + "resultDescription": "urn:uuid:f6ab24cd-86e8-4eaf-b8c6-ded74e8fd41c", + "value": "A" + }, + { + "type": [ + "InvalidResult" + ], + "achievedLevel": "urn:uuid:d05a0867-d0ad-4b03-bdb5-28fb5d2aab7a", + "alignment": [ + { + "type": "Alignment", + "targetCode": "project", + "targetDescription": "Project description", + "targetName": "Final Project", + "targetFramework": "1EdTech University Program and Course Catalog", + "targetType": "CFItem", + "targetUrl": "https://1edtech.edu/catalog/degree/project/result/1" + } + ], + "resultDescription": "urn:uuid:f6ab24cd-86e8-4eaf-b8c6-ded74e8fd41c" + }, + { + "type": [ + "Result" + ], + "resultDescription": "urn:uuid:f6ab24cd-86e8-4eaf-b8c6-ded74e8fd41c", + "status": "Completed" + } + ] + }, + "proof": [ + { + "type": "Ed25519Signature2020", + "created": "2022-11-16T18:54:22Z", + "verificationMethod": "https://example.com/issuers/876543#z6MknNHHrBzPytzu6CUBP9Lg7fg4KSBjzimc2Frh693YbMiv", + "proofPurpose": "assertionMethod", + "proofValue": "z5gJZKchSJEYPGeq6bsqiLKuxT6mXqAovPbqYX66CB7u9CSNFdV41vHtysjHFiitvoyhfPxsaZnWftrZZZW2txPQK" + } + ] +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob30/simple-err-credential-subject-type.json b/inspector-vc/src/test/resources/ob30/simple-err-credential-subject-type.json new file mode 100644 index 0000000..37712c5 --- /dev/null +++ b/inspector-vc/src/test/resources/ob30/simple-err-credential-subject-type.json @@ -0,0 +1,47 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://purl.imsglobal.org/spec/ob/v3p0/context.json", + "https://w3id.org/security/suites/ed25519-2020/v1" + ], + "id": "http://example.com/credentials/3527", + "type": [ + "VerifiableCredential", + "OpenBadgeCredential" + ], + "issuer": { + "id": "https://example.com/issuers/876543", + "type": [ + "Profile" + ], + "name": "Example Corp" + }, + "issuanceDate": "2010-01-01T00:00:00Z", + "name": "Teamwork Badge", + "credentialSubject": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "type": [ + "InvalidAchievementSubject" + ], + "achievement": { + "id": "https://example.com/achievements/21st-century-skills/teamwork", + "type": [ + "Achievement" + ], + "criteria": { + "narrative": "Team members are nominated for this badge by their peers and recognized upon review by Example Corp management." + }, + "description": "This badge recognizes the development of the capacity to collaborate within a group environment.", + "name": "Teamwork" + } + }, + "proof": [ + { + "type": "Ed25519Signature2020", + "created": "2022-11-16T18:54:22Z", + "verificationMethod": "https://example.com/issuers/876543#z6MknNHHrBzPytzu6CUBP9Lg7fg4KSBjzimc2Frh693YbMiv", + "proofPurpose": "assertionMethod", + "proofValue": "z5gJZKchSJEYPGeq6bsqiLKuxT6mXqAovPbqYX66CB7u9CSNFdV41vHtysjHFiitvoyhfPxsaZnWftrZZZW2txPQK" + } + ] + } \ No newline at end of file From d654b129194dd0400053fc7764d44bae023355c7 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Thu, 5 Jan 2023 11:43:19 +0100 Subject: [PATCH 04/19] Added probes for evidence --- .../oneedtech/inspect/vc/OB30Inspector.java | 6 ++ .../inspect/vc/probe/EvidenceProbe.java | 38 +++++++++++ .../org/oneedtech/inspect/vc/OB30Tests.java | 13 ++++ .../org/oneedtech/inspect/vc/Samples.java | 1 + .../ob30/simple-err-evidence-type.json | 66 +++++++++++++++++++ 5 files changed, 124 insertions(+) create mode 100644 inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/EvidenceProbe.java create mode 100644 inspector-vc/src/test/resources/ob30/simple-err-evidence-type.json diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB30Inspector.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB30Inspector.java index a2dde88..27fee6a 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB30Inspector.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB30Inspector.java @@ -44,6 +44,7 @@ import org.oneedtech.inspect.vc.probe.ExpirationProbe; import org.oneedtech.inspect.vc.probe.InlineJsonSchemaProbe; import org.oneedtech.inspect.vc.probe.IssuanceProbe; import org.oneedtech.inspect.vc.probe.EmbeddedProofProbe; +import org.oneedtech.inspect.vc.probe.EvidenceProbe; import org.oneedtech.inspect.vc.probe.RevocationListProbe; import org.oneedtech.inspect.vc.probe.ExternalProofProbe; import org.oneedtech.inspect.vc.probe.TypePropertyProbe; @@ -202,6 +203,11 @@ public class OB30Inspector extends VCInspector implements SubInspector { if(broken(accumulator)) return abort(ctx, accumulator, probeCount); } + // evidence + probeCount++; + accumulator.add(new EvidenceProbe().run(ob.getJson(), ctx)); + if(broken(accumulator)) return abort(ctx, accumulator, probeCount); + //embedded endorsements EndorsementInspector endorsementInspector = new EndorsementInspector.Builder().build(); diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/EvidenceProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/EvidenceProbe.java new file mode 100644 index 0000000..92f6c92 --- /dev/null +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/EvidenceProbe.java @@ -0,0 +1,38 @@ +package org.oneedtech.inspect.vc.probe; + +import java.util.List; + +import org.oneedtech.inspect.core.probe.Probe; +import org.oneedtech.inspect.core.probe.RunContext; +import org.oneedtech.inspect.core.report.ReportItems; +import org.oneedtech.inspect.vc.util.JsonNodeUtil; + +import com.fasterxml.jackson.databind.JsonNode; + +public class EvidenceProbe extends Probe { + public EvidenceProbe() { + super(ID); + } + + @Override + public ReportItems run(JsonNode root, RunContext ctx) throws Exception { + + if (root.hasNonNull("evidence")) { + /* + * evidence is an array, so check type of each element + */ + List evidences = JsonNodeUtil.asNodeList(root.get("evidence")); + for (JsonNode evidence : evidences) { + // check that type contains "Evidence" + if (!JsonNodeUtil.asStringList(evidence.get("type")).contains("Evidence")) { + return error("evidence is not of type \"Evidence\"", ctx); + } + } + } + + return success(ctx); + } + + public static final String ID = EvidenceProbe.class.getSimpleName(); + +} diff --git a/inspector-vc/src/test/java/org/oneedtech/inspect/vc/OB30Tests.java b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/OB30Tests.java index e764640..9f8c07f 100644 --- a/inspector-vc/src/test/java/org/oneedtech/inspect/vc/OB30Tests.java +++ b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/OB30Tests.java @@ -16,6 +16,7 @@ import org.oneedtech.inspect.vc.probe.ExpirationProbe; import org.oneedtech.inspect.vc.probe.InlineJsonSchemaProbe; import org.oneedtech.inspect.vc.probe.IssuanceProbe; import org.oneedtech.inspect.vc.probe.EmbeddedProofProbe; +import org.oneedtech.inspect.vc.probe.EvidenceProbe; import org.oneedtech.inspect.vc.probe.TypePropertyProbe; import com.google.common.collect.Iterables; @@ -252,6 +253,18 @@ public class OB30Tests { }); } + @Test + void testSimpleJsonInvalidEvidenceType() { + //add a dumb value to .type and remove the ob type + assertDoesNotThrow(()->{ + Report report = validator.run(Samples.OB30.JSON.SIMPLE_JSON_UNKNOWN_EVIDENCE_TYPE.asFileResource()); + if(verbose) PrintHelper.print(report, true); + assertInvalid(report); + // assertFatalCount(report, 1); + assertHasProbeID(report, EvidenceProbe.ID, true); + }); + } + @Disabled //TODO IssuanceVerifierProbe is not run because FATAL: InvalidSignature terminates @Test void testSimpleJsonNotIssued() { diff --git a/inspector-vc/src/test/java/org/oneedtech/inspect/vc/Samples.java b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/Samples.java index 2b44942..1508d23 100644 --- a/inspector-vc/src/test/java/org/oneedtech/inspect/vc/Samples.java +++ b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/Samples.java @@ -19,6 +19,7 @@ public class Samples { public final static Sample SIMPLE_JSON_UNKNOWN_CREDENTIAL_SUBJECT_RESULT_TYPE = new Sample("ob30/simple-err-credential-subject-result-type.json", false); public final static Sample SIMPLE_JSON_UNKNOWN_CREDENTIAL_SUBJECT_ACHIEVEMENT_RESULT_DESCRIPTION_TYPE = new Sample("ob30/simple-err-credential-subject-achievement-result-description-type.json", false); public final static Sample SIMPLE_JSON_UNKNOWN_CREDENTIAL_SUBJECT_PROFILE_TYPE = new Sample("ob30/simple-err-credential-subject-profile-type.json", false); + public final static Sample SIMPLE_JSON_UNKNOWN_EVIDENCE_TYPE = new Sample("ob30/simple-err-evidence-type.json", false); public final static Sample SIMPLE_JSON_PROOF_METHOD_ERROR = new Sample("ob30/simple-err-proof-method.json", false); public final static Sample SIMPLE_JSON_PROOF_METHOD_NO_SCHEME_ERROR = new Sample("ob30/simple-err-proof-method-no-scheme.json", false); public final static Sample SIMPLE_JSON_PROOF_METHOD_UNKNOWN_SCHEME_ERROR = new Sample("ob30/simple-err-proof-method-unknown-scheme.json", false); diff --git a/inspector-vc/src/test/resources/ob30/simple-err-evidence-type.json b/inspector-vc/src/test/resources/ob30/simple-err-evidence-type.json new file mode 100644 index 0000000..245cbd6 --- /dev/null +++ b/inspector-vc/src/test/resources/ob30/simple-err-evidence-type.json @@ -0,0 +1,66 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://purl.imsglobal.org/spec/ob/v3p0/context.json", + "https://w3id.org/security/suites/ed25519-2020/v1" + ], + "id": "http://example.com/credentials/3527", + "type": [ + "VerifiableCredential", + "OpenBadgeCredential" + ], + "issuer": { + "id": "https://example.com/issuers/876543", + "type": [ + "Profile" + ], + "name": "Example Corp" + }, + "issuanceDate": "2010-01-01T00:00:00Z", + "name": "Teamwork Badge", + "credentialSubject": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "type": [ + "AchievementSubject" + ], + "achievement": { + "id": "https://example.com/achievements/21st-century-skills/teamwork", + "type": [ + "Achievement" + ], + "criteria": { + "narrative": "Team members are nominated for this badge by their peers and recognized upon review by Example Corp management." + }, + "description": "This badge recognizes the development of the capacity to collaborate within a group environment.", + "name": "Teamwork" + } + }, + "evidence": [ + { + "id": "https://1edtech.edu/credentials/3732/evidence/1", + "type": "Evidence", + "narrative": "# Final Project Report \n This project was ...", + "name": "Final Project Report", + "description": "This is the final project report.", + "genre": "Research", + "audience": "Department" + }, + { + "id": "https://github.com/somebody/project", + "type": "InvalidEvidence", + "name": "Final Project Code", + "description": "This is the source code for the final project app.", + "genre": "Research", + "audience": "Department" + } + ], + "proof": [ + { + "type": "Ed25519Signature2020", + "created": "2022-11-16T18:54:22Z", + "verificationMethod": "https://example.com/issuers/876543#z6MknNHHrBzPytzu6CUBP9Lg7fg4KSBjzimc2Frh693YbMiv", + "proofPurpose": "assertionMethod", + "proofValue": "z5gJZKchSJEYPGeq6bsqiLKuxT6mXqAovPbqYX66CB7u9CSNFdV41vHtysjHFiitvoyhfPxsaZnWftrZZZW2txPQK" + } + ] +} \ No newline at end of file From 6f5fea32ed7153d27a01f4ae1e14673fbea7db1e Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Thu, 5 Jan 2023 12:20:22 +0100 Subject: [PATCH 05/19] Added probes for issuer --- .../oneedtech/inspect/vc/OB30Inspector.java | 6 ++ .../org/oneedtech/inspect/vc/OB30Tests.java | 37 ++++++++++++ .../org/oneedtech/inspect/vc/Samples.java | 3 + ...imple-err-issuer-otheridentifier-type.json | 59 +++++++++++++++++++ .../simple-err-issuer-parentorg-type.json | 54 +++++++++++++++++ .../ob30/simple-err-issuer-type.json | 47 +++++++++++++++ 6 files changed, 206 insertions(+) create mode 100644 inspector-vc/src/test/resources/ob30/simple-err-issuer-otheridentifier-type.json create mode 100644 inspector-vc/src/test/resources/ob30/simple-err-issuer-parentorg-type.json create mode 100644 inspector-vc/src/test/resources/ob30/simple-err-issuer-type.json diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB30Inspector.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB30Inspector.java index 27fee6a..1fd21c9 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB30Inspector.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB30Inspector.java @@ -43,6 +43,7 @@ import org.oneedtech.inspect.vc.probe.CredentialSubjectProbe; import org.oneedtech.inspect.vc.probe.ExpirationProbe; import org.oneedtech.inspect.vc.probe.InlineJsonSchemaProbe; import org.oneedtech.inspect.vc.probe.IssuanceProbe; +import org.oneedtech.inspect.vc.probe.IssuerProbe; import org.oneedtech.inspect.vc.probe.EmbeddedProofProbe; import org.oneedtech.inspect.vc.probe.EvidenceProbe; import org.oneedtech.inspect.vc.probe.RevocationListProbe; @@ -208,6 +209,11 @@ public class OB30Inspector extends VCInspector implements SubInspector { accumulator.add(new EvidenceProbe().run(ob.getJson(), ctx)); if(broken(accumulator)) return abort(ctx, accumulator, probeCount); + // issuer + probeCount++; + accumulator.add(new IssuerProbe().run(ob.getJson(), ctx)); + if(broken(accumulator)) return abort(ctx, accumulator, probeCount); + //embedded endorsements EndorsementInspector endorsementInspector = new EndorsementInspector.Builder().build(); diff --git a/inspector-vc/src/test/java/org/oneedtech/inspect/vc/OB30Tests.java b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/OB30Tests.java index 9f8c07f..e98e6c8 100644 --- a/inspector-vc/src/test/java/org/oneedtech/inspect/vc/OB30Tests.java +++ b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/OB30Tests.java @@ -15,6 +15,7 @@ import org.oneedtech.inspect.vc.probe.CredentialSubjectProbe; import org.oneedtech.inspect.vc.probe.ExpirationProbe; import org.oneedtech.inspect.vc.probe.InlineJsonSchemaProbe; import org.oneedtech.inspect.vc.probe.IssuanceProbe; +import org.oneedtech.inspect.vc.probe.IssuerProbe; import org.oneedtech.inspect.vc.probe.EmbeddedProofProbe; import org.oneedtech.inspect.vc.probe.EvidenceProbe; import org.oneedtech.inspect.vc.probe.TypePropertyProbe; @@ -265,6 +266,42 @@ public class OB30Tests { }); } + @Test + void testSimpleJsonInvalidIssuerType() { + //add a dumb value to .type and remove the ob type + assertDoesNotThrow(()->{ + Report report = validator.run(Samples.OB30.JSON.SIMPLE_JSON_UNKNOWN_ISSUER_TYPE.asFileResource()); + if(verbose) PrintHelper.print(report, true); + assertInvalid(report); + // assertFatalCount(report, 1); + assertHasProbeID(report, IssuerProbe.ID, true); + }); + } + + @Test + void testSimpleJsonInvalidIssuerParentOrgType() { + //add a dumb value to .type and remove the ob type + assertDoesNotThrow(()->{ + Report report = validator.run(Samples.OB30.JSON.SIMPLE_JSON_UNKNOWN_ISSUER_PARENTORG_TYPE.asFileResource()); + if(verbose) PrintHelper.print(report, true); + assertInvalid(report); + // assertFatalCount(report, 1); + assertHasProbeID(report, IssuerProbe.ID, true); + }); + } + + @Test + void testSimpleJsonInvalidIssuerOtherIdentifierType() { + //add a dumb value to .type and remove the ob type + assertDoesNotThrow(()->{ + Report report = validator.run(Samples.OB30.JSON.SIMPLE_JSON_UNKNOWN_ISSUER_OTHERIDENTIFIER_TYPE.asFileResource()); + if(verbose) PrintHelper.print(report, true); + assertInvalid(report); + // assertFatalCount(report, 1); + assertHasProbeID(report, IssuerProbe.ID, true); + }); + } + @Disabled //TODO IssuanceVerifierProbe is not run because FATAL: InvalidSignature terminates @Test void testSimpleJsonNotIssued() { diff --git a/inspector-vc/src/test/java/org/oneedtech/inspect/vc/Samples.java b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/Samples.java index 1508d23..c0ebdf8 100644 --- a/inspector-vc/src/test/java/org/oneedtech/inspect/vc/Samples.java +++ b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/Samples.java @@ -20,6 +20,9 @@ public class Samples { public final static Sample SIMPLE_JSON_UNKNOWN_CREDENTIAL_SUBJECT_ACHIEVEMENT_RESULT_DESCRIPTION_TYPE = new Sample("ob30/simple-err-credential-subject-achievement-result-description-type.json", false); public final static Sample SIMPLE_JSON_UNKNOWN_CREDENTIAL_SUBJECT_PROFILE_TYPE = new Sample("ob30/simple-err-credential-subject-profile-type.json", false); public final static Sample SIMPLE_JSON_UNKNOWN_EVIDENCE_TYPE = new Sample("ob30/simple-err-evidence-type.json", false); + public final static Sample SIMPLE_JSON_UNKNOWN_ISSUER_TYPE = new Sample("ob30/simple-err-issuer-type.json", false); + public final static Sample SIMPLE_JSON_UNKNOWN_ISSUER_OTHERIDENTIFIER_TYPE = new Sample("ob30/simple-err-issuer-otheridentifier-type.json", false); + public final static Sample SIMPLE_JSON_UNKNOWN_ISSUER_PARENTORG_TYPE = new Sample("ob30/simple-err-issuer-parentorg-type.json", false); public final static Sample SIMPLE_JSON_PROOF_METHOD_ERROR = new Sample("ob30/simple-err-proof-method.json", false); public final static Sample SIMPLE_JSON_PROOF_METHOD_NO_SCHEME_ERROR = new Sample("ob30/simple-err-proof-method-no-scheme.json", false); public final static Sample SIMPLE_JSON_PROOF_METHOD_UNKNOWN_SCHEME_ERROR = new Sample("ob30/simple-err-proof-method-unknown-scheme.json", false); diff --git a/inspector-vc/src/test/resources/ob30/simple-err-issuer-otheridentifier-type.json b/inspector-vc/src/test/resources/ob30/simple-err-issuer-otheridentifier-type.json new file mode 100644 index 0000000..94a134a --- /dev/null +++ b/inspector-vc/src/test/resources/ob30/simple-err-issuer-otheridentifier-type.json @@ -0,0 +1,59 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://purl.imsglobal.org/spec/ob/v3p0/context.json", + "https://w3id.org/security/suites/ed25519-2020/v1" + ], + "id": "http://example.com/credentials/3527", + "type": [ + "VerifiableCredential", + "OpenBadgeCredential" + ], + "issuer": { + "id": "https://example.com/issuers/876543", + "type": [ + "Profile" + ], + "name": "Example Corp", + "otherIdentifier": [ + { + "type": "IdentifierEntry", + "identifier": "12345", + "identifierType": "sourcedId" + }, + { + "type": "InvalidIdentifierEntry", + "identifier": "67890", + "identifierType": "nationalIdentityNumber" + } + ] + }, + "issuanceDate": "2010-01-01T00:00:00Z", + "name": "Teamwork Badge", + "credentialSubject": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "type": [ + "AchievementSubject" + ], + "achievement": { + "id": "https://example.com/achievements/21st-century-skills/teamwork", + "type": [ + "Achievement" + ], + "criteria": { + "narrative": "Team members are nominated for this badge by their peers and recognized upon review by Example Corp management." + }, + "description": "This badge recognizes the development of the capacity to collaborate within a group environment.", + "name": "Teamwork" + } + }, + "proof": [ + { + "type": "Ed25519Signature2020", + "created": "2022-11-16T18:54:22Z", + "verificationMethod": "https://example.com/issuers/876543#z6MknNHHrBzPytzu6CUBP9Lg7fg4KSBjzimc2Frh693YbMiv", + "proofPurpose": "assertionMethod", + "proofValue": "z5gJZKchSJEYPGeq6bsqiLKuxT6mXqAovPbqYX66CB7u9CSNFdV41vHtysjHFiitvoyhfPxsaZnWftrZZZW2txPQK" + } + ] +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob30/simple-err-issuer-parentorg-type.json b/inspector-vc/src/test/resources/ob30/simple-err-issuer-parentorg-type.json new file mode 100644 index 0000000..c2f1dad --- /dev/null +++ b/inspector-vc/src/test/resources/ob30/simple-err-issuer-parentorg-type.json @@ -0,0 +1,54 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://purl.imsglobal.org/spec/ob/v3p0/context.json", + "https://w3id.org/security/suites/ed25519-2020/v1" + ], + "id": "http://example.com/credentials/3527", + "type": [ + "VerifiableCredential", + "OpenBadgeCredential" + ], + "issuer": { + "id": "https://example.com/issuers/876543", + "type": [ + "Profile" + ], + "name": "Example Corp", + "parentOrg": { + "id": "https://example.com/issuers/876543", + "type": [ + "InvalidProfile" + ], + "name": "Example Parent Corp" + } + }, + "issuanceDate": "2010-01-01T00:00:00Z", + "name": "Teamwork Badge", + "credentialSubject": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "type": [ + "AchievementSubject" + ], + "achievement": { + "id": "https://example.com/achievements/21st-century-skills/teamwork", + "type": [ + "Achievement" + ], + "criteria": { + "narrative": "Team members are nominated for this badge by their peers and recognized upon review by Example Corp management." + }, + "description": "This badge recognizes the development of the capacity to collaborate within a group environment.", + "name": "Teamwork" + } + }, + "proof": [ + { + "type": "Ed25519Signature2020", + "created": "2022-11-16T18:54:22Z", + "verificationMethod": "https://example.com/issuers/876543#z6MknNHHrBzPytzu6CUBP9Lg7fg4KSBjzimc2Frh693YbMiv", + "proofPurpose": "assertionMethod", + "proofValue": "z5gJZKchSJEYPGeq6bsqiLKuxT6mXqAovPbqYX66CB7u9CSNFdV41vHtysjHFiitvoyhfPxsaZnWftrZZZW2txPQK" + } + ] +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob30/simple-err-issuer-type.json b/inspector-vc/src/test/resources/ob30/simple-err-issuer-type.json new file mode 100644 index 0000000..a42b0d1 --- /dev/null +++ b/inspector-vc/src/test/resources/ob30/simple-err-issuer-type.json @@ -0,0 +1,47 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://purl.imsglobal.org/spec/ob/v3p0/context.json", + "https://w3id.org/security/suites/ed25519-2020/v1" + ], + "id": "http://example.com/credentials/3527", + "type": [ + "VerifiableCredential", + "OpenBadgeCredential" + ], + "issuer": { + "id": "https://example.com/issuers/876543", + "type": [ + "InvalidProfile" + ], + "name": "Example Corp" + }, + "issuanceDate": "2010-01-01T00:00:00Z", + "name": "Teamwork Badge", + "credentialSubject": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "type": [ + "AchievementSubject" + ], + "achievement": { + "id": "https://example.com/achievements/21st-century-skills/teamwork", + "type": [ + "Achievement" + ], + "criteria": { + "narrative": "Team members are nominated for this badge by their peers and recognized upon review by Example Corp management." + }, + "description": "This badge recognizes the development of the capacity to collaborate within a group environment.", + "name": "Teamwork" + } + }, + "proof": [ + { + "type": "Ed25519Signature2020", + "created": "2022-11-16T18:54:22Z", + "verificationMethod": "https://example.com/issuers/876543#z6MknNHHrBzPytzu6CUBP9Lg7fg4KSBjzimc2Frh693YbMiv", + "proofPurpose": "assertionMethod", + "proofValue": "z5gJZKchSJEYPGeq6bsqiLKuxT6mXqAovPbqYX66CB7u9CSNFdV41vHtysjHFiitvoyhfPxsaZnWftrZZZW2txPQK" + } + ] +} \ No newline at end of file From f0cddf0906a646be1465faa1b153820d9587693b Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Thu, 5 Jan 2023 12:28:17 +0100 Subject: [PATCH 06/19] more comment in a TODO --- .../java/org/oneedtech/inspect/vc/probe/EmbeddedProofProbe.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/EmbeddedProofProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/EmbeddedProofProbe.java index 024a69f..6243586 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/EmbeddedProofProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/EmbeddedProofProbe.java @@ -41,7 +41,7 @@ public class EmbeddedProofProbe extends Probe { @Override public ReportItems run(VerifiableCredential crd, RunContext ctx) throws Exception { - // TODO: What there are multiple proofs? + // TODO: What there are multiple proofs? com.danubetech.verifiablecredentials.VerifiableCredential returns the first element, which is ok com.danubetech.verifiablecredentials.VerifiableCredential vc = com.danubetech.verifiablecredentials.VerifiableCredential.fromJson(new StringReader(crd.getJson().toString())); ConfigurableDocumentLoader documentLoader = new ConfigurableDocumentLoader(); From 4abd45937ca1e87353292e8443d0129f1ec9b28c Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Thu, 5 Jan 2023 14:30:25 +0100 Subject: [PATCH 07/19] Move probes up to follow verification section in spec document --- .../oneedtech/inspect/vc/OB30Inspector.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB30Inspector.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB30Inspector.java index 1fd21c9..a775f0c 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB30Inspector.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB30Inspector.java @@ -174,6 +174,16 @@ public class OB30Inspector extends VCInspector implements SubInspector { probeCount++; accumulator.add(new CredentialSubjectProbe("AchievementSubject", true).run(ob.getJson(), ctx)); + // evidence + probeCount++; + accumulator.add(new EvidenceProbe().run(ob.getJson(), ctx)); + if(broken(accumulator)) return abort(ctx, accumulator, probeCount); + + // issuer + probeCount++; + accumulator.add(new IssuerProbe().run(ob.getJson(), ctx)); + if(broken(accumulator)) return abort(ctx, accumulator, probeCount); + //signatures, proofs probeCount++; if(ob.getProofType() == EXTERNAL){ @@ -204,16 +214,6 @@ public class OB30Inspector extends VCInspector implements SubInspector { if(broken(accumulator)) return abort(ctx, accumulator, probeCount); } - // evidence - probeCount++; - accumulator.add(new EvidenceProbe().run(ob.getJson(), ctx)); - if(broken(accumulator)) return abort(ctx, accumulator, probeCount); - - // issuer - probeCount++; - accumulator.add(new IssuerProbe().run(ob.getJson(), ctx)); - if(broken(accumulator)) return abort(ctx, accumulator, probeCount); - //embedded endorsements EndorsementInspector endorsementInspector = new EndorsementInspector.Builder().build(); From 999b28b43d4ce811e265b3fcedb2f09f76c82686 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Mon, 9 Jan 2023 12:04:11 +0100 Subject: [PATCH 08/19] Added a holder for W3C's VC, with method for getting the array of proofs --- .../org/oneedtech/inspect/vc/W3CVCHolder.java | 39 ++++++++++++++++ .../inspect/vc/jsonld/JsonLDObjectUtils.java | 45 +++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 inspector-vc/src/main/java/org/oneedtech/inspect/vc/W3CVCHolder.java create mode 100644 inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/JsonLDObjectUtils.java diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/W3CVCHolder.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/W3CVCHolder.java new file mode 100644 index 0000000..e8b986b --- /dev/null +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/W3CVCHolder.java @@ -0,0 +1,39 @@ +package org.oneedtech.inspect.vc; + +import java.util.List; + +import org.oneedtech.inspect.vc.jsonld.JsonLDObjectUtils; + +import com.danubetech.verifiablecredentials.VerifiableCredential; + +import foundation.identity.jsonld.ConfigurableDocumentLoader; +import info.weboftrust.ldsignatures.LdProof; + +/** + * Holder for W3C's Verifiable Credential + */ +public class W3CVCHolder { + private VerifiableCredential credential; + + public W3CVCHolder(VerifiableCredential credential) { + this.credential = credential; + ConfigurableDocumentLoader documentLoader = new ConfigurableDocumentLoader(); + documentLoader.setEnableHttp(true); + documentLoader.setEnableHttps(true); + credential.setDocumentLoader(documentLoader); + } + + /** + * Get the list of proofs in the credential. + * {@link VerifiableCredential} contains the method getLdProof(), but only works with one proof. This methods + * returns a list of all proofs defined in the credential. + * @return proofs defined in the credential + */ + public List getLdProofs() { + return JsonLDObjectUtils.getListFromJsonLDObject(LdProof.class, credential); + } + + public VerifiableCredential getCredential() { + return credential; + } +} diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/JsonLDObjectUtils.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/JsonLDObjectUtils.java new file mode 100644 index 0000000..398ccc2 --- /dev/null +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/jsonld/JsonLDObjectUtils.java @@ -0,0 +1,45 @@ +package org.oneedtech.inspect.vc.jsonld; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import foundation.identity.jsonld.JsonLDObject; + +public class JsonLDObjectUtils { + @SuppressWarnings("unchecked") + public static List getListFromJsonLDObject(Class cl, JsonLDObject jsonLdObject) { + String term = JsonLDObject.getDefaultJsonLDPredicate(cl); + List> jsonObjects = jsonLdGetJsonObjectList(jsonLdObject.getJsonObject(), term); + if (jsonObjects == null) return null; + try { + Method method = cl.getMethod("fromMap", Map.class); + return jsonObjects.stream().map(jsonObject -> { + try { + return (C) method.invoke(null, jsonObject); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + throw new Error(e); + } + }).collect(Collectors.toList()); + } catch (NoSuchMethodException | SecurityException e) { + throw new Error(e); + } + } + + @SuppressWarnings("unchecked") + public static List> jsonLdGetJsonObjectList(Map jsonObject, String term) { + Object entry = jsonObject.get(term); + if (entry == null) return null; + + if (entry instanceof Map) { + return Collections.singletonList((Map) entry); + } else if (entry instanceof List && ((List) entry).stream().allMatch(e -> e instanceof Map)) { + return (List>) (List>) entry; + } else { + throw new IllegalArgumentException("Cannot get json object '" + term + "' from " + jsonObject); + } + } +} From a13bcf8f11287375abeacaa1de964522a614cee0 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Mon, 9 Jan 2023 12:04:35 +0100 Subject: [PATCH 09/19] Added a test for multiple proofs --- .../org/oneedtech/inspect/vc/OB30Tests.java | 9 ++++ .../org/oneedtech/inspect/vc/Samples.java | 1 + .../ob30/simple-multiple-proofs.json | 51 +++++++++++++++++++ 3 files changed, 61 insertions(+) create mode 100644 inspector-vc/src/test/resources/ob30/simple-multiple-proofs.json diff --git a/inspector-vc/src/test/java/org/oneedtech/inspect/vc/OB30Tests.java b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/OB30Tests.java index e98e6c8..53d2198 100644 --- a/inspector-vc/src/test/java/org/oneedtech/inspect/vc/OB30Tests.java +++ b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/OB30Tests.java @@ -52,6 +52,15 @@ public class OB30Tests { }); } + @Test + void testSimpleMultipleProofsJsonValid() { + assertDoesNotThrow(()->{ + Report report = validator.run(Samples.OB30.JSON.SIMPLE_MULTIPLE_PROOF_JSON.asFileResource()); + if(verbose) PrintHelper.print(report, true); + assertValid(report); + }); + } + @Test void testSimplePNGPlainValid() { assertDoesNotThrow(()->{ diff --git a/inspector-vc/src/test/java/org/oneedtech/inspect/vc/Samples.java b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/Samples.java index c0ebdf8..b9f9d8a 100644 --- a/inspector-vc/src/test/java/org/oneedtech/inspect/vc/Samples.java +++ b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/Samples.java @@ -12,6 +12,7 @@ public class Samples { public final static Sample COMPLETE_JSON = new Sample("ob30/complete.json", false); public final static Sample SIMPLE_JSON = new Sample("ob30/simple.json", true); public final static Sample SIMPLE_DID_METHOD_JSON = new Sample("ob30/simple-did-method.json", true); + public final static Sample SIMPLE_MULTIPLE_PROOF_JSON = new Sample("ob30/simple-multiple-proofs.json", true); public final static Sample SIMPLE_JSON_NOPROOF = new Sample("ob30/simple-noproof.json", false); public final static Sample SIMPLE_JSON_UNKNOWN_TYPE = new Sample("ob30/simple-err-type.json", false); public final static Sample SIMPLE_JSON_UNKNOWN_CREDENTIAL_SUBJECT_TYPE = new Sample("ob30/simple-err-credential-subject-type.json", false); diff --git a/inspector-vc/src/test/resources/ob30/simple-multiple-proofs.json b/inspector-vc/src/test/resources/ob30/simple-multiple-proofs.json new file mode 100644 index 0000000..d539520 --- /dev/null +++ b/inspector-vc/src/test/resources/ob30/simple-multiple-proofs.json @@ -0,0 +1,51 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://purl.imsglobal.org/spec/ob/v3p0/context.json", + "https://w3id.org/security/suites/ed25519-2020/v1" + ], + "id": "http://example.com/credentials/3527", + "type": [ + "VerifiableCredential", + "OpenBadgeCredential" + ], + "issuer": { + "id": "https://example.com/issuers/876543", + "type": [ + "Profile" + ], + "name": "Example Corp" + }, + "issuanceDate": "2010-01-01T00:00:00Z", + "name": "Teamwork Badge", + "credentialSubject": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "type": [ + "AchievementSubject" + ], + "achievement": { + "id": "https://example.com/achievements/21st-century-skills/teamwork", + "type": [ + "Achievement" + ], + "criteria": { + "narrative": "Team members are nominated for this badge by their peers and recognized upon review by Example Corp management." + }, + "description": "This badge recognizes the development of the capacity to collaborate within a group environment.", + "name": "Teamwork" + } + }, + "proof": [{ + "type": "SomeProofType", + "created": "2022-11-16T18:54:22Z", + "proofPurpose": "assertionMethod" + }, + { + "type": "Ed25519Signature2020", + "created": "2022-11-16T18:54:22Z", + "verificationMethod": "https://example.com/issuers/876543#z6MknNHHrBzPytzu6CUBP9Lg7fg4KSBjzimc2Frh693YbMiv", + "proofPurpose": "assertionMethod", + "proofValue": "z5gJZKchSJEYPGeq6bsqiLKuxT6mXqAovPbqYX66CB7u9CSNFdV41vHtysjHFiitvoyhfPxsaZnWftrZZZW2txPQK" + } + ] +} \ No newline at end of file From be6891e85763c1e0ba5397e2654598dc0c259b5b Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Mon, 9 Jan 2023 12:05:10 +0100 Subject: [PATCH 10/19] Allow multiple proofs --- .../inspect/vc/probe/EmbeddedProofProbe.java | 35 +++++++++++-------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/EmbeddedProofProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/EmbeddedProofProbe.java index 6243586..e8e35af 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/EmbeddedProofProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/EmbeddedProofProbe.java @@ -2,13 +2,14 @@ package org.oneedtech.inspect.vc.probe; import java.io.StringReader; import java.net.URI; +import java.util.List; import java.util.Optional; import org.oneedtech.inspect.core.probe.Probe; import org.oneedtech.inspect.core.probe.RunContext; import org.oneedtech.inspect.core.report.ReportItems; import org.oneedtech.inspect.vc.VerifiableCredential; -import org.oneedtech.inspect.vc.util.CachingDocumentLoader; +import org.oneedtech.inspect.vc.W3CVCHolder; import com.apicatalog.jsonld.StringUtils; import com.apicatalog.jsonld.document.Document; @@ -17,7 +18,6 @@ import com.apicatalog.multibase.Multibase; import com.apicatalog.multicodec.Multicodec; import com.apicatalog.multicodec.Multicodec.Codec; -import foundation.identity.jsonld.ConfigurableDocumentLoader; import info.weboftrust.ldsignatures.LdProof; import info.weboftrust.ldsignatures.verifier.Ed25519Signature2020LdVerifier; import jakarta.json.JsonObject; @@ -41,18 +41,23 @@ public class EmbeddedProofProbe extends Probe { @Override public ReportItems run(VerifiableCredential crd, RunContext ctx) throws Exception { - // TODO: What there are multiple proofs? com.danubetech.verifiablecredentials.VerifiableCredential returns the first element, which is ok + W3CVCHolder credentiaHolder = new W3CVCHolder(com.danubetech.verifiablecredentials.VerifiableCredential.fromJson(new StringReader(crd.getJson().toString()))); - com.danubetech.verifiablecredentials.VerifiableCredential vc = com.danubetech.verifiablecredentials.VerifiableCredential.fromJson(new StringReader(crd.getJson().toString())); - ConfigurableDocumentLoader documentLoader = new ConfigurableDocumentLoader(); - documentLoader.setEnableHttp(true); - documentLoader.setEnableHttps(true); - vc.setDocumentLoader(documentLoader); - - LdProof proof = vc.getLdProof(); - if (proof == null) { + List proofs = credentiaHolder.getLdProofs(); + if (proofs == null || proofs.size() == 0) { return error("The verifiable credential is missing a proof.", ctx); } + + // get proof of standard type and purpose + Optional selectedProof = proofs.stream().filter(proof -> proof.isType("Ed25519Signature2020") && proof.getProofPurpose().equals("assertionMethod")) + .findFirst(); + + if (!selectedProof.isPresent()) { + return error("No proof with type \"Ed25519Signature2020\" or proof purpose \"assertionMethod\" found", ctx); + } + + LdProof proof = selectedProof.get(); + if (!proof.isType("Ed25519Signature2020")) { return error("Unknown proof type: " + proof.getType(), ctx); } @@ -92,7 +97,7 @@ public class EmbeddedProofProbe extends Probe { } } else if (method.getScheme().equals("http") || method.getScheme().equals("https")) { try { - Document keyDocument = vc.getDocumentLoader().loadDocument(method, new DocumentLoaderOptions()); + Document keyDocument = credentiaHolder.getCredential().getDocumentLoader().loadDocument(method, new DocumentLoaderOptions()); Optional keyStructure = keyDocument.getJsonContent(); if (keyStructure.isEmpty()) { return error("Key document not found at " + method, ctx); @@ -135,8 +140,8 @@ public class EmbeddedProofProbe extends Probe { } if (controller != null) { - if (!controller.equals(vc.getIssuer().toString())) { - return error("Key controller does not match issuer: " + vc.getIssuer(), ctx); + if (!controller.equals(credentiaHolder.getCredential().getIssuer().toString())) { + return error("Key controller does not match issuer: " + credentiaHolder.getCredential().getIssuer(), ctx); } } @@ -146,7 +151,7 @@ public class EmbeddedProofProbe extends Probe { Ed25519Signature2020LdVerifier verifier = new Ed25519Signature2020LdVerifier(publicKey); try { - boolean verify = verifier.verify(vc); + boolean verify = verifier.verify(credentiaHolder.getCredential(), proof); if (!verify) { return error("Embedded proof verification failed.", ctx); } From 6f2b840fc4ab19c5130d5906f5eb5cff707314b4 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Mon, 9 Jan 2023 13:52:08 +0100 Subject: [PATCH 11/19] Renamed method --- .../src/main/java/org/oneedtech/inspect/vc/W3CVCHolder.java | 2 +- .../java/org/oneedtech/inspect/vc/probe/EmbeddedProofProbe.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/W3CVCHolder.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/W3CVCHolder.java index e8b986b..36a3849 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/W3CVCHolder.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/W3CVCHolder.java @@ -29,7 +29,7 @@ public class W3CVCHolder { * returns a list of all proofs defined in the credential. * @return proofs defined in the credential */ - public List getLdProofs() { + public List getProofs() { return JsonLDObjectUtils.getListFromJsonLDObject(LdProof.class, credential); } diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/EmbeddedProofProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/EmbeddedProofProbe.java index e8e35af..4a90cec 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/EmbeddedProofProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/EmbeddedProofProbe.java @@ -43,7 +43,7 @@ public class EmbeddedProofProbe extends Probe { W3CVCHolder credentiaHolder = new W3CVCHolder(com.danubetech.verifiablecredentials.VerifiableCredential.fromJson(new StringReader(crd.getJson().toString()))); - List proofs = credentiaHolder.getLdProofs(); + List proofs = credentiaHolder.getProofs(); if (proofs == null || proofs.size() == 0) { return error("The verifiable credential is missing a proof.", ctx); } From 08c56713425cdf9c258c72cd9c5c13c6a277630b Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Thu, 9 Feb 2023 13:24:59 +0100 Subject: [PATCH 12/19] updated version --- inspector-vc/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inspector-vc/pom.xml b/inspector-vc/pom.xml index 141cdc1..f00dd1e 100644 --- a/inspector-vc/pom.xml +++ b/inspector-vc/pom.xml @@ -5,7 +5,7 @@ org.1edtech inspector - 0.9.10 + 0.9.11 inspector-vc From 433032c6c30271005ca154431bb164e561926990 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Fri, 10 Feb 2023 10:11:48 +0100 Subject: [PATCH 13/19] Added context aliases and versioning --- .../org/oneedtech/inspect/vc/Assertion.java | 8 +++++++ .../org/oneedtech/inspect/vc/Credential.java | 2 ++ .../inspect/vc/VerifiableCredential.java | 21 ++++++++++++++++--- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java index 76e75d8..9769561 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Assertion.java @@ -148,6 +148,14 @@ public class Assertion extends Credential { public List getValidations() { return validationMap.get(this); } + @Override + public Map> getContextAliases() { + return Collections.emptyMap(); + } + @Override + public Map> getContextVersionPatterns() { + return Collections.emptyMap(); + } } public enum ValueType { diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Credential.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Credential.java index 84e1c9b..d33b3fd 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Credential.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Credential.java @@ -92,6 +92,8 @@ public abstract class Credential extends GeneratedObject { List getAllowedTypeValues(); boolean isAllowedTypeValuesRequired(); List getContextUris(); + Map> getContextAliases(); + Map> getContextVersionPatterns(); String toString(); } diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VerifiableCredential.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VerifiableCredential.java index 2b29f9f..9e93b9c 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VerifiableCredential.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VerifiableCredential.java @@ -53,17 +53,24 @@ public class VerifiableCredential extends Credential { private static final Map, List> contextMap = new ImmutableMap.Builder, List>() .put(Set.of(Type.OpenBadgeCredential, AchievementCredential, EndorsementCredential), List.of("https://www.w3.org/2018/credentials/v1", - //"https://purl.imsglobal.org/spec/ob/v3p0/context.json")) //dev legacy "https://purl.imsglobal.org/spec/ob/v3p0/context.json")) .put(Set.of(ClrCredential), List.of("https://www.w3.org/2018/credentials/v1", - // "https://dc.imsglobal.org/draft/clr/v2p0/context", //dev legacy - // "https://purl.imsglobal.org/spec/ob/v3p0/context.json")) //dev legacy "https://purl.imsglobal.org/spec/clr/v2p0/context.json", "https://purl.imsglobal.org/spec/ob/v3p0/context.json")) .build(); + private static final Map> contextAliasesMap = new ImmutableMap.Builder>() + .put("https://purl.imsglobal.org/spec/ob/v3p0/context.json", + List.of("https://purl.imsglobal.org/spec/ob/v3p0/context/ob_v3p0.jsonld")) + .build(); + + private static final Map> contextVersioningPatternMap = new ImmutableMap.Builder>() + .put("https://purl.imsglobal.org/spec/ob/v3p0/context.json", + List.of("https:\\/\\/purl\\.imsglobal\\.org\\/spec\\/ob\\/v3p0\\/context(-\\d+\\.\\d+\\.\\d+)*\\.json")) + .build(); + public enum Type implements CredentialEnum { AchievementCredential(Collections.emptyList()), OpenBadgeCredential(List.of("OpenBadgeCredential", "AchievementCredential")), //treated as an alias of AchievementCredential @@ -119,6 +126,14 @@ public class VerifiableCredential extends Credential { .findFirst() .orElseThrow(()-> new IllegalArgumentException(this.name() + " not recognized"))); } + @Override + public Map> getContextAliases() { + return contextAliasesMap; + } + @Override + public Map> getContextVersionPatterns() { + return contextVersioningPatternMap; + } } public enum ProofType { From 0d583f175f498072680e190aa9049e4e49a1dbd6 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Fri, 10 Feb 2023 10:12:08 +0100 Subject: [PATCH 14/19] Validate context against aliases and versioning --- .../vc/probe/ContextPropertyProbe.java | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ContextPropertyProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ContextPropertyProbe.java index f0ec2ab..865daca 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ContextPropertyProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ContextPropertyProbe.java @@ -36,7 +36,7 @@ public class ContextPropertyProbe extends StringValuePropertyProbe { int pos = 0; for (String uri : contextUris) { - if ((nodeValues.size() < pos + 1) || !nodeValues.get(pos).equals(uri)) { + if ((nodeValues.size() < pos + 1) || !contains(uri, nodeValues.get(pos))) { return error("missing required @context uri " + uri + " at position " + (pos + 1), ctx); } pos++; @@ -46,5 +46,25 @@ public class ContextPropertyProbe extends StringValuePropertyProbe { return success(ctx); } + private boolean contains(String uri, String nodeValue) { + // check equal case + if (nodeValue.equals(uri)) { + return true; + } + // check aliases + if (type.getContextAliases().containsKey(uri)) { + if (type.getContextAliases().get(uri).stream().anyMatch(alias -> nodeValue.equals(alias))) { + return true; + } + } + // check versioning + if (type.getContextVersionPatterns().containsKey(uri)) { + if (type.getContextVersionPatterns().get(uri).stream().anyMatch(version -> nodeValue.matches(version))) { + return true; + } + } + return false; + } + public static final String ID = ContextPropertyProbe.class.getSimpleName(); } From a75917fe29c623be7ae058bf52a9f6747fc24c02 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Fri, 10 Feb 2023 10:12:27 +0100 Subject: [PATCH 15/19] Added tests for context aliases and versioning --- .../org/oneedtech/inspect/vc/OB30Tests.java | 20 ++++++++ .../org/oneedtech/inspect/vc/Samples.java | 2 + .../resources/ob30/simple-context-alias.json | 47 +++++++++++++++++++ .../ob30/simple-context-version.json | 47 +++++++++++++++++++ 4 files changed, 116 insertions(+) create mode 100644 inspector-vc/src/test/resources/ob30/simple-context-alias.json create mode 100644 inspector-vc/src/test/resources/ob30/simple-context-version.json diff --git a/inspector-vc/src/test/java/org/oneedtech/inspect/vc/OB30Tests.java b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/OB30Tests.java index 53d2198..1f8484e 100644 --- a/inspector-vc/src/test/java/org/oneedtech/inspect/vc/OB30Tests.java +++ b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/OB30Tests.java @@ -192,6 +192,26 @@ public class OB30Tests { }); } + @Test + void testSimpleJsonContextAlias() { + //removed one of the reqd context uris + assertDoesNotThrow(()->{ + Report report = validator.run(Samples.OB30.JSON.SIMPLE_JSON_ALIAS_CONTEXT.asFileResource()); + if(verbose) PrintHelper.print(report, true); + assertValid(report); + }); + } + + @Test + void testSimpleJsonContextVersion() { + //removed one of the reqd context uris + assertDoesNotThrow(()->{ + Report report = validator.run(Samples.OB30.JSON.SIMPLE_JSON_VERSION_CONTEXT.asFileResource()); + if(verbose) PrintHelper.print(report, true); + assertHasValidProbeID(report, ContextPropertyProbe.ID); + }); + } + @Test void testSimpleJsonSchemaError() throws Exception { //issuer removed diff --git a/inspector-vc/src/test/java/org/oneedtech/inspect/vc/Samples.java b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/Samples.java index b9f9d8a..ab74a9e 100644 --- a/inspector-vc/src/test/java/org/oneedtech/inspect/vc/Samples.java +++ b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/Samples.java @@ -35,6 +35,8 @@ public class Samples { public final static Sample SIMPLE_JSON_ERR_CONTEXT = new Sample("ob30/simple-err-context.json", false); public final static Sample ENDORSEMENT_ERR_SCHEMA_STATUS_REFRESH = new Sample("ob30/endorsement-err-schema-status-refresh.json", false); public final static Sample ENDORSEMENT_VALID = new Sample("ob30/endorsement-valid.json", false); + public final static Sample SIMPLE_JSON_ALIAS_CONTEXT = new Sample("ob30/simple-context-alias.json", true); + public final static Sample SIMPLE_JSON_VERSION_CONTEXT = new Sample("ob30/simple-context-version.json", true); } public static final class PNG { public final static Sample SIMPLE_JWT_PNG = new Sample("ob30/simple-jwt.png", true); diff --git a/inspector-vc/src/test/resources/ob30/simple-context-alias.json b/inspector-vc/src/test/resources/ob30/simple-context-alias.json new file mode 100644 index 0000000..40ce9fe --- /dev/null +++ b/inspector-vc/src/test/resources/ob30/simple-context-alias.json @@ -0,0 +1,47 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://purl.imsglobal.org/spec/ob/v3p0/context/ob_v3p0.jsonld", + "https://w3id.org/security/suites/ed25519-2020/v1" + ], + "id": "http://example.com/credentials/3527", + "type": [ + "VerifiableCredential", + "OpenBadgeCredential" + ], + "issuer": { + "id": "https://example.com/issuers/876543", + "type": [ + "Profile" + ], + "name": "Example Corp" + }, + "issuanceDate": "2010-01-01T00:00:00Z", + "name": "Teamwork Badge", + "credentialSubject": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "type": [ + "AchievementSubject" + ], + "achievement": { + "id": "https://example.com/achievements/21st-century-skills/teamwork", + "type": [ + "Achievement" + ], + "criteria": { + "narrative": "Team members are nominated for this badge by their peers and recognized upon review by Example Corp management." + }, + "description": "This badge recognizes the development of the capacity to collaborate within a group environment.", + "name": "Teamwork" + } + }, + "proof": [ + { + "type": "Ed25519Signature2020", + "created": "2022-11-16T18:54:22Z", + "verificationMethod": "https://example.com/issuers/876543#z6MknNHHrBzPytzu6CUBP9Lg7fg4KSBjzimc2Frh693YbMiv", + "proofPurpose": "assertionMethod", + "proofValue": "z5gJZKchSJEYPGeq6bsqiLKuxT6mXqAovPbqYX66CB7u9CSNFdV41vHtysjHFiitvoyhfPxsaZnWftrZZZW2txPQK" + } + ] +} diff --git a/inspector-vc/src/test/resources/ob30/simple-context-version.json b/inspector-vc/src/test/resources/ob30/simple-context-version.json new file mode 100644 index 0000000..29ad406 --- /dev/null +++ b/inspector-vc/src/test/resources/ob30/simple-context-version.json @@ -0,0 +1,47 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.1.json", + "https://w3id.org/security/suites/ed25519-2020/v1" + ], + "id": "http://example.com/credentials/3527", + "type": [ + "VerifiableCredential", + "OpenBadgeCredential" + ], + "issuer": { + "id": "https://example.com/issuers/876543", + "type": [ + "Profile" + ], + "name": "Example Corp" + }, + "issuanceDate": "2010-01-01T00:00:00Z", + "name": "Teamwork Badge", + "credentialSubject": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "type": [ + "AchievementSubject" + ], + "achievement": { + "id": "https://example.com/achievements/21st-century-skills/teamwork", + "type": [ + "Achievement" + ], + "criteria": { + "narrative": "Team members are nominated for this badge by their peers and recognized upon review by Example Corp management." + }, + "description": "This badge recognizes the development of the capacity to collaborate within a group environment.", + "name": "Teamwork" + } + }, + "proof": [ + { + "type": "Ed25519Signature2020", + "created": "2022-11-16T18:54:22Z", + "verificationMethod": "https://example.com/issuers/876543#z6MknNHHrBzPytzu6CUBP9Lg7fg4KSBjzimc2Frh693YbMiv", + "proofPurpose": "assertionMethod", + "proofValue": "z5gJZKchSJEYPGeq6bsqiLKuxT6mXqAovPbqYX66CB7u9CSNFdV41vHtysjHFiitvoyhfPxsaZnWftrZZZW2txPQK" + } + ] +} From 98d86d556fc6a2b35d839d0b87a19a980637184f Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Fri, 10 Feb 2023 10:12:40 +0100 Subject: [PATCH 16/19] Added context to cache --- .../org/oneedtech/inspect/vc/util/CachingDocumentLoader.java | 1 + 1 file changed, 1 insertion(+) diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/CachingDocumentLoader.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/CachingDocumentLoader.java index a4ba642..c969f79 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/CachingDocumentLoader.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/CachingDocumentLoader.java @@ -108,6 +108,7 @@ public class CachingDocumentLoader extends ConfigurableDocumentLoader { static final ImmutableMap bundled = ImmutableMap.builder() .put("https://purl.imsglobal.org/spec/clr/v2p0/context.json",Resources.getResource("contexts/clr-v2p0.json")) + .put("https://purl.imsglobal.org/spec/ob/v3p0/context/ob_v3p0.jsonld",Resources.getResource("contexts/ob-v3p0.json")) .put("https://purl.imsglobal.org/spec/ob/v3p0/context.json",Resources.getResource("contexts/ob-v3p0.json")) .put("https://purl.imsglobal.org/spec/ob/v3p0/extensions.json",Resources.getResource("contexts/ob-v3p0-extensions.json")) .put("https://www.w3.org/ns/did/v1", Resources.getResource("contexts/did-v1.jsonld")) From 52f0fd4ed3f16b04cff1c066e7e939efdec50c9e Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Tue, 7 Feb 2023 19:10:48 +0100 Subject: [PATCH 17/19] Defined REFRESH_SERVICE_MIME_TYPES --- .../java/org/oneedtech/inspect/vc/VerifiableCredential.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VerifiableCredential.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VerifiableCredential.java index 9e93b9c..ae7daab 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VerifiableCredential.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VerifiableCredential.java @@ -9,10 +9,10 @@ import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.stream.Collectors; import org.oneedtech.inspect.schema.Catalog; import org.oneedtech.inspect.schema.SchemaKey; +import org.oneedtech.inspect.util.resource.MimeType; import org.oneedtech.inspect.util.resource.Resource; import org.oneedtech.inspect.vc.util.JsonNodeUtil; @@ -160,4 +160,5 @@ public class VerifiableCredential extends Credential { private static final String ISSUED_ON_PROPERTY_NAME = "issuanceDate"; private static final String EXPIRES_AT_PROPERTY_NAME = "expirationDate"; public static final String JWT_NODE_NAME = "vc"; + public static final List REFRESH_SERVICE_MIME_TYPES = List.of(MimeType.JSON, MimeType.JSON_LD, MimeType.TEXT_PLAIN); } From 2952fe68071dd53edf4d13d90ff4c27d3d584c81 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Tue, 7 Feb 2023 19:11:47 +0100 Subject: [PATCH 18/19] Set accept header for revocation list --- .../org/oneedtech/inspect/vc/probe/RevocationListProbe.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/RevocationListProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/RevocationListProbe.java index e083c42..7d2ae55 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/RevocationListProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/RevocationListProbe.java @@ -3,6 +3,7 @@ package org.oneedtech.inspect.vc.probe; import static org.oneedtech.inspect.core.probe.RunContext.Key.JACKSON_OBJECTMAPPER; import java.io.InputStream; +import java.net.HttpURLConnection; import java.net.URI; import java.net.URL; import java.util.List; @@ -10,6 +11,7 @@ import java.util.List; import org.oneedtech.inspect.core.probe.Probe; import org.oneedtech.inspect.core.probe.RunContext; import org.oneedtech.inspect.core.report.ReportItems; +import org.oneedtech.inspect.util.resource.MimeType; import org.oneedtech.inspect.vc.Credential; import org.oneedtech.inspect.vc.VerifiableCredential; import org.oneedtech.inspect.vc.util.JsonNodeUtil; @@ -45,7 +47,9 @@ public class RevocationListProbe extends Probe { if(listID != null) { try { URL url = new URI(listID.asText().strip()).toURL(); - try (InputStream is = url.openStream()) { + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestProperty("Accept", MimeType.JSON.toString()); + try (InputStream is = connection.getInputStream()) { JsonNode revocList = ((ObjectMapper)ctx.get(JACKSON_OBJECTMAPPER)).readTree(is.readAllBytes()); /* To check if a credential has been revoked, the verifier issues a GET request From 34962fd631e1c75b70c55cd964cd12039b436b85 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Tue, 7 Feb 2023 19:12:10 +0100 Subject: [PATCH 19/19] Refresh of credential may fail. If so, continue with old credential --- .../java/org/oneedtech/inspect/vc/OB30Inspector.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB30Inspector.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB30Inspector.java index a775f0c..e616709 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB30Inspector.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB30Inspector.java @@ -6,6 +6,7 @@ import static org.oneedtech.inspect.core.report.ReportUtil.onProbeException; import static org.oneedtech.inspect.util.code.Defensives.*; import static org.oneedtech.inspect.util.json.ObjectMapperCache.Config.DEFAULT; import static org.oneedtech.inspect.vc.Credential.CREDENTIAL_KEY; +import static org.oneedtech.inspect.vc.VerifiableCredential.REFRESH_SERVICE_MIME_TYPES; import static org.oneedtech.inspect.vc.VerifiableCredential.ProofType.EXTERNAL; import static org.oneedtech.inspect.vc.payload.PayloadParser.fromJwt; import static org.oneedtech.inspect.vc.util.JsonNodeUtil.asNodeList; @@ -200,9 +201,11 @@ public class OB30Inspector extends VCInspector implements SubInspector { if(resource.getContext().get(REFRESHED) != TRUE) { Optional newID = checkRefreshService(ob, ctx); if(newID.isPresent()) { - return this.run( - new UriResource(new URI(newID.get())) - .setContext(new ResourceContext(REFRESHED, TRUE))); + // If the refresh is not successful, continue the verification process using the original OpenBadgeCredential. + UriResource uriResource = new UriResource(new URI(newID.get()), null, REFRESH_SERVICE_MIME_TYPES); + if (uriResource.exists()) { + return this.run(uriResource.setContext(new ResourceContext(REFRESHED, TRUE))); + } } }