diff --git a/inspector-vc/pom.xml b/inspector-vc/pom.xml new file mode 100644 index 0000000..8545db0 --- /dev/null +++ b/inspector-vc/pom.xml @@ -0,0 +1,85 @@ + + 4.0.0 + + org.1edtech + inspector + 0.9.2 + + inspector-vc + + + org.1edtech + inspector-core + + + com.auth0 + auth0 + 1.42.0 + + + com.auth0 + jwks-rsa + 0.21.1 + + + com.auth0 + java-jwt + 3.19.2 + + + + + com.danubetech + verifiable-credentials-java + + 1.1-SNAPSHOT + + + + + com.danubetech + key-formats-java + 1.6-SNAPSHOT + + + + + com.apicatalog + iron-verifiable-credentials-jre8 + 0.7.0 + + + + + com.apicatalog + titanium-json-ld + 1.3.1 + + + + org.apache.httpcomponents + httpclient + 4.5.13 + + + + + org.glassfish + jakarta.json + 2.0.1 + + + + + + danubetech-maven-public + https://repo.danubetech.com/repository/maven-public/ + + + \ No newline at end of file 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 new file mode 100644 index 0000000..1b3c501 --- /dev/null +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Credential.java @@ -0,0 +1,133 @@ +package org.oneedtech.inspect.vc; + +import static org.oneedtech.inspect.util.code.Defensives.*; +import static org.oneedtech.inspect.util.resource.ResourceType.*; +import static org.oneedtech.inspect.vc.Credential.Type.*; + +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import org.oneedtech.inspect.core.probe.GeneratedObject; +import org.oneedtech.inspect.schema.Catalog; +import org.oneedtech.inspect.schema.SchemaKey; +import org.oneedtech.inspect.util.resource.Resource; +import org.oneedtech.inspect.util.resource.ResourceType; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.google.common.base.MoreObjects; +import com.google.common.collect.ImmutableMap; + +/** + * A wrapper object for a verifiable credential. This contains e.g. the origin resource + * and the extracted JSON data plus any other stuff Probes need. + * @author mgylling + */ +public class Credential extends GeneratedObject { + final Resource resource; + final JsonNode jsonData; + final Credential.Type credentialType; + final String jwt; + + public Credential(Resource resource, JsonNode data, String jwt) { + super(ID, GeneratedObject.Type.INTERNAL); + this.resource = checkNotNull(resource); + this.jsonData = checkNotNull(data); + this.jwt = jwt; //may be null + + checkTrue(RECOGNIZED_PAYLOAD_TYPES.contains(resource.getType())); + + ArrayNode typeNode = (ArrayNode)jsonData.get("type"); + this.credentialType = Credential.Type.valueOf(typeNode); + } + + public Credential(Resource resource, JsonNode data) { + this(resource, data, null); + } + + public Resource getResource() { + return resource; + } + + public JsonNode getJson() { + return jsonData; + } + + public Credential.Type getCredentialType() { + return credentialType; + } + + public Optional getJwt() { + return Optional.ofNullable(jwt); + } + + public ProofType getProofType() { + return jwt == null ? ProofType.EMBEDDED : ProofType.EXTERNAL; + } + + + private static final Map schemas = new ImmutableMap.Builder() + .put(AchievementCredential, Catalog.OB_30_ACHIEVEMENTCREDENTIAL_JSON) + .put(ClrCredential, Catalog.CLR_20_CLRCREDENTIAL_JSON) + .put(VerifiablePresentation, Catalog.CLR_20_CLRCREDENTIAL_JSON) + .put(EndorsementCredential, Catalog.OB_30_ENDORSEMENTCREDENTIAL_JSON) + .build(); + + /** + * Get the canonical schema for this credential if such exists. + */ + public Optional getSchemaKey() { + return Optional.ofNullable(schemas.get(credentialType)); + } + + public enum Type { + AchievementCredential, + OpenBadgeCredential, //treated as an alias of AchievementCredential + ClrCredential, + EndorsementCredential, + VerifiablePresentation, + VerifiableCredential, //this is an underspecifier in our context + Unknown; + + public static Credential.Type valueOf (ArrayNode typeArray) { + if(typeArray != null) { + Iterator iter = typeArray.iterator(); + while(iter.hasNext()) { + String value = iter.next().asText(); + if(value.equals("AchievementCredential") || value.equals("OpenBadgeCredential")) { + return AchievementCredential; + } else if(value.equals("ClrCredential")) { + return ClrCredential; + } else if(value.equals("VerifiablePresentation")) { + return VerifiablePresentation; + } else if(value.equals("EndorsementCredential")) { + return EndorsementCredential; + } + } + } + return Unknown; + } + } + + public enum ProofType { + EXTERNAL, + EMBEDDED + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("resource", resource.getID()) + .add("resourceType", resource.getType()) + .add("credentialType", credentialType) + .add("json", jsonData) + .toString(); + } + + public static final String ID = Credential.class.getCanonicalName(); + public static final List RECOGNIZED_PAYLOAD_TYPES = List.of(SVG, PNG, JSON, JWT); + public static final String CREDENTIAL_KEY = "CREDENTIAL_KEY"; + +} 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 new file mode 100644 index 0000000..2eb643c --- /dev/null +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/EndorsementInspector.java @@ -0,0 +1,141 @@ +package org.oneedtech.inspect.vc; + +import static java.lang.Boolean.TRUE; +import static org.oneedtech.inspect.core.probe.RunContext.Key.*; +import static org.oneedtech.inspect.core.report.ReportUtil.onProbeException; +import static org.oneedtech.inspect.util.code.Defensives.checkNotNull; +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.Credential.ProofType.EXTERNAL; + +import java.net.URI; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import org.oneedtech.inspect.core.SubInspector; +import org.oneedtech.inspect.core.probe.GeneratedObject; +import org.oneedtech.inspect.core.probe.Probe; +import org.oneedtech.inspect.core.probe.RunContext; +import org.oneedtech.inspect.core.probe.json.JsonPathEvaluator; +import org.oneedtech.inspect.core.report.Report; +import org.oneedtech.inspect.core.report.ReportItems; +import org.oneedtech.inspect.util.json.ObjectMapperCache; +import org.oneedtech.inspect.util.resource.Resource; +import org.oneedtech.inspect.util.resource.UriResource; +import org.oneedtech.inspect.util.resource.context.ResourceContext; +import org.oneedtech.inspect.vc.Credential.Type; +import org.oneedtech.inspect.vc.probe.ContextPropertyProbe; +import org.oneedtech.inspect.vc.probe.EmbeddedProofProbe; +import org.oneedtech.inspect.vc.probe.ExpirationProbe; +import org.oneedtech.inspect.vc.probe.ExternalProofProbe; +import org.oneedtech.inspect.vc.probe.InlineJsonSchemaProbe; +import org.oneedtech.inspect.vc.probe.IssuanceProbe; +import org.oneedtech.inspect.vc.probe.RevocationListProbe; +import org.oneedtech.inspect.vc.probe.TypePropertyProbe; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * An inspector for EndorsementCredential objects. + * @author mgylling + */ +public class EndorsementInspector extends VCInspector implements SubInspector { + + protected > EndorsementInspector(B builder) { + super(builder); + } + + @Override + public Report run(Resource resource, Map parentObjects) { + + /* + * The resource param is the top-level credential that embeds the endorsement, we + * expect parentObjects to provide a pointer to the JsonNode we should check. + * + * The parent inspector is responsible to decode away possible jwt-ness, so that + * what we get here is a verbatim json node. + * + */ + + Credential endorsement = (Credential) checkNotNull(parentObjects.get(CREDENTIAL_KEY)); + + ObjectMapper mapper = ObjectMapperCache.get(DEFAULT); + JsonPathEvaluator jsonPath = new JsonPathEvaluator(mapper); + + RunContext ctx = new RunContext.Builder() + .put(this) + .put(JACKSON_OBJECTMAPPER, mapper) + .put(JSONPATH_EVALUATOR, jsonPath) + .build(); + + List accumulator = new ArrayList<>(); + int probeCount = 0; + try { + + //context and type properties + Credential.Type type = Type.EndorsementCredential; + for(Probe probe : List.of(new ContextPropertyProbe(type), new TypePropertyProbe(type))) { + probeCount++; + accumulator.add(probe.run(endorsement.getJson(), ctx)); + if(broken(accumulator)) return abort(ctx, accumulator, probeCount); + } + + //inline schema (parent inspector has already validated against canonical) + accumulator.add(new InlineJsonSchemaProbe().run(endorsement.getJson(), ctx)); + + //signatures, proofs + probeCount++; + if(endorsement.getProofType() == EXTERNAL){ + //The credential originally contained in a JWT, validate the jwt and external proof. + accumulator.add(new ExternalProofProbe().run(endorsement, ctx)); + } else { + //The credential not contained in a jwt, must have an internal proof. + accumulator.add(new EmbeddedProofProbe().run(endorsement, ctx)); + + } + if(broken(accumulator)) return abort(ctx, accumulator, probeCount); + + //check refresh service if we are not already refreshed (check just like in external CLR) + probeCount++; + if(resource.getContext().get(REFRESHED) != TRUE) { + Optional newID = checkRefreshService(endorsement, ctx); + if(newID.isPresent()) { + //TODO resource.type + return this.run( + new UriResource(new URI(newID.get())) + .setContext(new ResourceContext(REFRESHED, TRUE))); + } + } + + //revocation, expiration and issuance + for(Probe probe : List.of(new RevocationListProbe(), + new ExpirationProbe(), new IssuanceProbe())) { + probeCount++; + accumulator.add(probe.run(endorsement, ctx)); + if(broken(accumulator)) return abort(ctx, accumulator, probeCount); + } + + } catch (Exception e) { + accumulator.add(onProbeException(Probe.ID.NO_UNCAUGHT_EXCEPTIONS, resource, e)); + } + + return new Report(ctx, new ReportItems(accumulator), probeCount); + } + + @Override + public Report run(R resource) { + throw new IllegalStateException("must use #run(resource, map)"); + } + + public static class Builder extends VCInspector.Builder { + @SuppressWarnings("unchecked") + @Override + public EndorsementInspector build() { + return new EndorsementInspector(this); + } + } + +} 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 new file mode 100644 index 0000000..96e4236 --- /dev/null +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB30Inspector.java @@ -0,0 +1,232 @@ +package org.oneedtech.inspect.vc; + +import static java.lang.Boolean.TRUE; +import static org.oneedtech.inspect.core.Inspector.Behavior.RESET_CACHES_ON_RUN; +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.Credential.ProofType.EXTERNAL; +import static org.oneedtech.inspect.vc.payload.PayloadParser.fromJwt; +import static org.oneedtech.inspect.vc.util.JsonNodeUtil.asNodeList; + +import java.net.URI; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import org.oneedtech.inspect.core.SubInspector; +import org.oneedtech.inspect.core.probe.GeneratedObject; +import org.oneedtech.inspect.core.probe.Probe; +import org.oneedtech.inspect.core.probe.RunContext; +import org.oneedtech.inspect.core.probe.RunContext.Key; +import org.oneedtech.inspect.core.probe.json.JsonPathEvaluator; +import org.oneedtech.inspect.core.probe.json.JsonSchemaProbe; +import org.oneedtech.inspect.core.report.Report; +import org.oneedtech.inspect.core.report.ReportItems; +import org.oneedtech.inspect.schema.JsonSchemaCache; +import org.oneedtech.inspect.schema.SchemaKey; +import org.oneedtech.inspect.util.code.Defensives; +import org.oneedtech.inspect.util.json.ObjectMapperCache; +import org.oneedtech.inspect.util.resource.Resource; +import org.oneedtech.inspect.util.resource.ResourceType; +import org.oneedtech.inspect.util.resource.UriResource; +import org.oneedtech.inspect.util.resource.context.ResourceContext; +import org.oneedtech.inspect.util.spec.Specification; +import org.oneedtech.inspect.vc.Credential.Type; +import org.oneedtech.inspect.vc.probe.ContextPropertyProbe; +import org.oneedtech.inspect.vc.probe.CredentialParseProbe; +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.EmbeddedProofProbe; +import org.oneedtech.inspect.vc.probe.RevocationListProbe; +import org.oneedtech.inspect.vc.probe.ExternalProofProbe; +import org.oneedtech.inspect.vc.probe.TypePropertyProbe; +import org.oneedtech.inspect.vc.util.CachingDocumentLoader; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.ImmutableList; + +/** + * A verifier for Open Badges 3.0. + * @author mgylling + */ +public class OB30Inspector extends VCInspector implements SubInspector { + protected final List> userProbes; + + protected OB30Inspector(OB30Inspector.Builder builder) { + super(builder); + this.userProbes = ImmutableList.copyOf(builder.probes); + } + + //https://docs.google.com/document/d/1_imUl2K-5tMib0AUxwA9CWb0Ap1b3qif0sXydih68J0/edit# + //https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#verificaton-and-validation + + /* + * This inspector supports both standalone openbadge verification, as well as verification of + * AchievementCredentials embedded in e.g. CLR. + * + * When verifying a standalone AchievementCredential, call the run(Resource) method. When verifying + * an embedded AchievementCredential, call the run(Resource, Map) method. + */ + + @Override + public Report run(Resource resource) { + super.check(resource); //TODO because URIs, this should be a fetch and cache + + if(getBehavior(RESET_CACHES_ON_RUN) == TRUE) { + JsonSchemaCache.reset(); + CachingDocumentLoader.reset(); + } + + ObjectMapper mapper = ObjectMapperCache.get(DEFAULT); + JsonPathEvaluator jsonPath = new JsonPathEvaluator(mapper); + + RunContext ctx = new RunContext.Builder() + .put(this) + .put(resource) + .put(Key.JACKSON_OBJECTMAPPER, mapper) + .put(Key.JSONPATH_EVALUATOR, jsonPath) + .build(); + + List accumulator = new ArrayList<>(); + int probeCount = 0; + + try { + //detect type (png, svg, json, jwt) and extract json data + probeCount++; + accumulator.add(new CredentialParseProbe().run(resource, ctx)); + if(broken(accumulator, true)) return abort(ctx, accumulator, probeCount); + + //we expect the above to place a generated object in the context + Credential ob = ctx.getGeneratedObject(Credential.ID); + + //call the subinspector method of this + Report subReport = this.run(resource, Map.of(Credential.CREDENTIAL_KEY, ob)); + probeCount += subReport.getSummary().getTotalRun(); + accumulator.add(subReport); + + //finally, run any user-added probes + for(Probe probe : userProbes) { + probeCount++; + accumulator.add(probe.run(ob, ctx)); + } + + } catch (Exception e) { + accumulator.add(onProbeException(Probe.ID.NO_UNCAUGHT_EXCEPTIONS, resource, e)); + } + + return new Report(ctx, new ReportItems(accumulator), probeCount); + } + + @Override + public Report run(Resource resource, Map parentObjects) { + + Credential ob = checkNotNull((Credential)parentObjects.get(CREDENTIAL_KEY)); + + ObjectMapper mapper = ObjectMapperCache.get(DEFAULT); + JsonPathEvaluator jsonPath = new JsonPathEvaluator(mapper); + RunContext ctx = new RunContext.Builder() + .put(this) + .put(resource) + .put(Key.JACKSON_OBJECTMAPPER, mapper) + .put(Key.JSONPATH_EVALUATOR, jsonPath) + .build(); + + List accumulator = new ArrayList<>(); + int probeCount = 0; + + try { + + //context and type properties + Credential.Type type = Type.OpenBadgeCredential; + for(Probe probe : List.of(new ContextPropertyProbe(type), new TypePropertyProbe(type))) { + probeCount++; + accumulator.add(probe.run(ob.getJson(), ctx)); + if(broken(accumulator)) return abort(ctx, accumulator, probeCount); + } + + //canonical schema and inline schemata + SchemaKey schema = ob.getSchemaKey().orElseThrow(); + for(Probe probe : List.of(new JsonSchemaProbe(schema), new InlineJsonSchemaProbe(schema))) { + probeCount++; + accumulator.add(probe.run(ob.getJson(), ctx)); + if(broken(accumulator)) return abort(ctx, accumulator, probeCount); + } + + //credentialSubject + probeCount++; + accumulator.add(new CredentialSubjectProbe().run(ob.getJson(), ctx)); + + //signatures, proofs + probeCount++; + if(ob.getProofType() == EXTERNAL){ + //The credential originally contained in a JWT, validate the jwt and external proof. + accumulator.add(new ExternalProofProbe().run(ob, ctx)); + } else { + //The credential not contained in a jwt, must have an internal proof. + accumulator.add(new EmbeddedProofProbe().run(ob, ctx)); + } + if(broken(accumulator)) return abort(ctx, accumulator, probeCount); + + //check refresh service if we are not already refreshed + probeCount++; + 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))); + } + } + + //revocation, expiration and issuance + for(Probe probe : List.of(new RevocationListProbe(), + new ExpirationProbe(), new IssuanceProbe())) { + probeCount++; + accumulator.add(probe.run(ob, ctx)); + if(broken(accumulator)) return abort(ctx, accumulator, probeCount); + } + + //embedded endorsements + EndorsementInspector endorsementInspector = new EndorsementInspector.Builder().build(); + + List endorsements = asNodeList(ob.getJson(), "$..endorsement", jsonPath); + for(JsonNode node : endorsements) { + probeCount++; + Credential endorsement = new Credential(resource, node); + accumulator.add(endorsementInspector.run(resource, Map.of(CREDENTIAL_KEY, endorsement))); + } + + //embedded jwt endorsements + endorsements = asNodeList(ob.getJson(), "$..endorsementJwt", jsonPath); + for(JsonNode node : endorsements) { + probeCount++; + String jwt = node.asText(); + JsonNode vcNode = fromJwt(jwt, ctx); + Credential endorsement = new Credential(resource, vcNode, jwt); + accumulator.add(endorsementInspector.run(resource, Map.of(CREDENTIAL_KEY, endorsement))); + } + + } catch (Exception e) { + accumulator.add(onProbeException(Probe.ID.NO_UNCAUGHT_EXCEPTIONS, resource, e)); + } + + return new Report(ctx, new ReportItems(accumulator), probeCount); + + } + + public static class Builder extends VCInspector.Builder { + @SuppressWarnings("unchecked") + @Override + public OB30Inspector build() { + set(Specification.OB30); + set(ResourceType.OPENBADGE); + return new OB30Inspector(this); + } + } +} \ No newline at end of file diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VCInspector.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VCInspector.java new file mode 100644 index 0000000..a4a90dc --- /dev/null +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/VCInspector.java @@ -0,0 +1,79 @@ +package org.oneedtech.inspect.vc; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.oneedtech.inspect.core.Inspector; +import org.oneedtech.inspect.core.probe.Outcome; +import org.oneedtech.inspect.core.probe.Probe; +import org.oneedtech.inspect.core.probe.RunContext; +import org.oneedtech.inspect.core.report.Report; +import org.oneedtech.inspect.core.report.ReportItems; + +import com.fasterxml.jackson.databind.JsonNode; + +/** + * Abstract base for verifiable credentials inspectors/verifiers. + * @author mgylling + */ +public abstract class VCInspector extends Inspector { + + protected > VCInspector(B builder) { + super(builder); + } + + protected Report abort(RunContext ctx, List accumulator, int probeCount) { + return new Report(ctx, new ReportItems(accumulator), probeCount); + } + + protected boolean broken(List accumulator) { + return broken(accumulator, false); + } + + protected boolean broken(List accumulator, boolean force) { + if(!force && getBehavior(Inspector.Behavior.VALIDATOR_FAIL_FAST) == Boolean.FALSE) { + return false; + } + for(ReportItems items : accumulator) { + if(items.contains(Outcome.FATAL, Outcome.EXCEPTION)) return true; + } + return false; + } + + /** + * If the AchievementCredential or EndorsementCredential has a “refreshService” property and the type of the + * RefreshService object is “1EdTechCredentialRefresh”, you should fetch the refreshed credential from the URL + * provided, then start the verification process over using the response as input. If the request fails, + * the credential is invalid. + */ + protected Optional checkRefreshService(Credential crd, RunContext ctx) { + JsonNode refreshServiceNode = crd.getJson().get("refreshService"); + if(refreshServiceNode != null) { + JsonNode serviceTypeNode = refreshServiceNode.get("type"); + if(serviceTypeNode != null && serviceTypeNode.asText().equals("1EdTechCredentialRefresh")) { + JsonNode serviceURINode = refreshServiceNode.get("id"); + if(serviceURINode != null) { + return Optional.of(serviceURINode.asText()); + } + } + } + return Optional.empty(); + } + + protected static final String REFRESHED = "is.refreshed.credential"; + + public abstract static class Builder> extends Inspector.Builder { + final List> probes; + + public Builder() { + super(); + this.probes = new ArrayList<>(); + } + + public VCInspector.Builder add(Probe probe) { + probes.add(probe); + return this; + } + } +} \ No newline at end of file diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/JsonParser.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/JsonParser.java new file mode 100644 index 0000000..6552312 --- /dev/null +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/JsonParser.java @@ -0,0 +1,32 @@ +package org.oneedtech.inspect.vc.payload; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.oneedtech.inspect.util.code.Defensives.checkTrue; + +import org.oneedtech.inspect.core.probe.RunContext; +import org.oneedtech.inspect.util.resource.Resource; +import org.oneedtech.inspect.util.resource.ResourceType; +import org.oneedtech.inspect.vc.Credential; + +import com.fasterxml.jackson.databind.JsonNode; + +/** + * A credential extractor for JSON files. + * @author mgylling + */ +public final class JsonParser extends PayloadParser { + + @Override + public boolean supports(ResourceType type) { + return type == ResourceType.JSON; + } + + @Override + public Credential parse(Resource resource, RunContext ctx) throws Exception { + checkTrue(resource.getType() == ResourceType.JSON); + String json = resource.asByteSource().asCharSource(UTF_8).read(); + JsonNode node = fromString(json, ctx); + return new Credential(resource, node); + } + +} diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/JwtParser.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/JwtParser.java new file mode 100644 index 0000000..3a946b1 --- /dev/null +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/JwtParser.java @@ -0,0 +1,32 @@ +package org.oneedtech.inspect.vc.payload; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.oneedtech.inspect.util.code.Defensives.checkTrue; + +import org.oneedtech.inspect.core.probe.RunContext; +import org.oneedtech.inspect.util.resource.Resource; +import org.oneedtech.inspect.util.resource.ResourceType; +import org.oneedtech.inspect.vc.Credential; + +import com.fasterxml.jackson.databind.JsonNode; + +/** + * A credential extractor for JWT files. + * @author mgylling + */ +public final class JwtParser extends PayloadParser { + + @Override + public boolean supports(ResourceType type) { + return type == ResourceType.JWT; + } + + @Override + public Credential parse(Resource resource, RunContext ctx) throws Exception { + checkTrue(resource.getType() == ResourceType.JWT); + String jwt = resource.asByteSource().asCharSource(UTF_8).read(); + JsonNode node = fromJwt(jwt, ctx); + return new Credential(resource, node, jwt); + } + +} diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/PayloadParser.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/PayloadParser.java new file mode 100644 index 0000000..41ccb2c --- /dev/null +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/PayloadParser.java @@ -0,0 +1,52 @@ +package org.oneedtech.inspect.vc.payload; + +import java.util.Base64; +import java.util.List; +import java.util.Base64.Decoder; + +import org.oneedtech.inspect.core.probe.RunContext; +import org.oneedtech.inspect.util.resource.Resource; +import org.oneedtech.inspect.util.resource.ResourceType; +import org.oneedtech.inspect.vc.Credential; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.base.Splitter; + +/** + * Abstract base for extracting Credential instances from payloads. + * @author mgylling + */ +public abstract class PayloadParser { + + public abstract boolean supports(ResourceType type); + + public abstract Credential parse(Resource source, RunContext ctx) throws Exception; + + protected static JsonNode fromString(String json, RunContext context) throws Exception { + return ((ObjectMapper)context.get(RunContext.Key.JACKSON_OBJECTMAPPER)).readTree(json); + } + + /** + * Decode as per https://www.imsglobal.org/spec/ob/v3p0/#jwt-proof + * @return The decoded JSON String + */ + public static JsonNode fromJwt(String jwt, RunContext context) throws Exception { + List parts = Splitter.on('.').splitToList(jwt); + if(parts.size() != 3) throw new IllegalArgumentException("invalid jwt"); + + final Decoder decoder = Base64.getUrlDecoder(); + /* + * For this step we are only deserializing the stored badge out of the payload. + * The entire jwt is stored separately for signature verification later. + */ + String jwtPayload = new String(decoder.decode(parts.get(1))); + + //Deserialize and fetch the 'vc' node from the object + JsonNode outerPayload = fromString(jwtPayload, context); + JsonNode vcNode = outerPayload.get("vc"); + + return vcNode; + } + +} diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/PayloadParserFactory.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/PayloadParserFactory.java new file mode 100644 index 0000000..a2f0cc3 --- /dev/null +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/PayloadParserFactory.java @@ -0,0 +1,25 @@ +package org.oneedtech.inspect.vc.payload; + +import static org.oneedtech.inspect.util.code.Defensives.checkNotNull; + +import java.util.List; + +import org.oneedtech.inspect.util.resource.Resource; + +/** + * A factory to create PayloadParser instances for various resource types. + * @author mgylling + */ +public class PayloadParserFactory { + private static final Iterable parsers = List.of( + new PngParser(), new SvgParser(), + new JsonParser(), new JwtParser()); + + public static PayloadParser of(Resource resource) { + checkNotNull(resource.getType()); + for(PayloadParser cex : parsers) { + if(cex.supports(resource.getType())) return cex; + } + throw new IllegalArgumentException(); + } +} diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/PngParser.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/PngParser.java new file mode 100644 index 0000000..f521e47 --- /dev/null +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/PngParser.java @@ -0,0 +1,102 @@ +package org.oneedtech.inspect.vc.payload; + +import static org.oneedtech.inspect.util.code.Defensives.checkTrue; + +import java.io.InputStream; + +import javax.imageio.ImageIO; +import javax.imageio.ImageReader; +import javax.imageio.metadata.IIOMetadata; + +import org.oneedtech.inspect.core.probe.RunContext; +import org.oneedtech.inspect.util.resource.Resource; +import org.oneedtech.inspect.util.resource.ResourceType; +import org.oneedtech.inspect.vc.Credential; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +import com.fasterxml.jackson.databind.JsonNode; + +/** + * A credential extractor for PNG images. + * @author mgylling + */ +public final class PngParser extends PayloadParser { + + @Override + public boolean supports(ResourceType type) { + return type == ResourceType.PNG; + } + + @Override + public Credential parse(Resource resource, RunContext ctx) throws Exception { + + checkTrue(resource.getType() == ResourceType.PNG); + + try(InputStream is = resource.asByteSource().openStream()) { + + ImageReader imageReader = ImageIO.getImageReadersByFormatName("png").next(); + imageReader.setInput(ImageIO.createImageInputStream(is), true); + IIOMetadata metadata = imageReader.getImageMetadata(0); + + String vcString = null; + String jwtString = null; + String formatSearch = null; + JsonNode vcNode = null; + + String[] names = metadata.getMetadataFormatNames(); + int length = names.length; + for (int i = 0; i < length; i++) { + //Check all names rather than limiting to PNG format to remain malleable through any library changes. (Could limit to "javax_imageio_png_1.0") + formatSearch = getOpenBadgeCredentialNodeText(metadata.getAsTree(names[i])); + if(formatSearch != null) { + vcString = formatSearch; + break; + } + } + + if(vcString == null) { + throw new IllegalArgumentException("No credential inside PNG"); + } + + vcString = vcString.trim(); + if(vcString.charAt(0) != '{'){ + //This is a jwt. Fetch either the 'vc' out of the payload and save the string for signature verification. + jwtString = vcString; + vcNode = fromJwt(vcString, ctx); + } + else { + vcNode = fromString(vcString, ctx); + } + + return new Credential(resource, vcNode, jwtString); + } + } + + private String getOpenBadgeCredentialNodeText(Node node){ + NamedNodeMap attributes = node.getAttributes(); + + //If this node is labeled with the attribute keyword: 'openbadgecredential' it is the right one. + Node keyword = attributes.getNamedItem("keyword"); + if(keyword != null && keyword.getNodeValue().equals("openbadgecredential")){ + Node textAttribute = attributes.getNamedItem("text"); + if(textAttribute != null) { + return textAttribute.getNodeValue(); + } + } + + //iterate over all children depth first and search for the credential node. + Node child = node.getFirstChild(); + while (child != null) { + String nodeValue = getOpenBadgeCredentialNodeText(child); + if(nodeValue != null) { + return nodeValue; + } + child = child.getNextSibling(); + } + + //Return null if we haven't found anything at this recursive depth. + return null; + } + +} diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/SvgParser.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/SvgParser.java new file mode 100644 index 0000000..f600bc9 --- /dev/null +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/payload/SvgParser.java @@ -0,0 +1,79 @@ +package org.oneedtech.inspect.vc.payload; + +import static org.oneedtech.inspect.util.code.Defensives.checkTrue; + +import java.io.InputStream; + +import javax.xml.namespace.QName; +import javax.xml.stream.XMLEventReader; +import javax.xml.stream.events.Attribute; +import javax.xml.stream.events.Characters; +import javax.xml.stream.events.XMLEvent; + +import org.oneedtech.inspect.core.probe.RunContext; +import org.oneedtech.inspect.util.code.Defensives; +import org.oneedtech.inspect.util.resource.Resource; +import org.oneedtech.inspect.util.resource.ResourceType; +import org.oneedtech.inspect.util.xml.XMLInputFactoryCache; +import org.oneedtech.inspect.vc.Credential; + +import com.fasterxml.jackson.databind.JsonNode; + +/** + * A credential extractor for SVG documents. + * @author mgylling + */ +public final class SvgParser extends PayloadParser { + + @Override + public boolean supports(ResourceType type) { + return type == ResourceType.SVG; + } + + @Override + public Credential parse(Resource resource, RunContext ctx) throws Exception { + + checkTrue(resource.getType() == ResourceType.SVG); + + try(InputStream is = resource.asByteSource().openStream()) { + XMLEventReader reader = XMLInputFactoryCache.getInstance().createXMLEventReader(is); + while(reader.hasNext()) { + XMLEvent ev = reader.nextEvent(); + if(isEndElem(ev, OB_CRED_ELEM)) break; + if(isStartElem(ev, OB_CRED_ELEM)) { + Attribute verifyAttr = ev.asStartElement().getAttributeByName(OB_CRED_VERIFY_ATTR); + if(verifyAttr != null) { + String jwt = verifyAttr.getValue(); + JsonNode node = fromJwt(jwt, ctx); + return new Credential(resource, node, jwt); + } else { + while(reader.hasNext()) { + ev = reader.nextEvent(); + if(isEndElem(ev, OB_CRED_ELEM)) break; + if(ev.getEventType() == XMLEvent.CHARACTERS) { + Characters chars = ev.asCharacters(); + if(!chars.isWhiteSpace()) { + JsonNode node = fromString(chars.getData(), ctx); + return new Credential(resource, node); + } + } + } + } + } + } //while(reader.hasNext()) { + } + throw new IllegalArgumentException("No credential inside SVG"); + + } + + private boolean isEndElem(XMLEvent ev, QName name) { + return ev.isEndElement() && ev.asEndElement().getName().equals(name); + } + + private boolean isStartElem(XMLEvent ev, QName name) { + return ev.isStartElement() && ev.asStartElement().getName().equals(name); + } + + private static final QName OB_CRED_ELEM = new QName("https://purl.imsglobal.org/ob/v3p0", "credential"); + private static final QName OB_CRED_VERIFY_ATTR = new QName("verify"); +} 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 new file mode 100644 index 0000000..5747336 --- /dev/null +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ContextPropertyProbe.java @@ -0,0 +1,76 @@ +package org.oneedtech.inspect.vc.probe; + +import static org.oneedtech.inspect.vc.Credential.Type.*; + +import static org.oneedtech.inspect.util.code.Defensives.checkNotNull; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +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.Credential; + +import org.oneedtech.inspect.vc.util.JsonNodeUtil; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.google.common.collect.ImmutableMap; + +/** + * A Probe that verifies a credential's context property. + * + * @author mgylling + */ +public class ContextPropertyProbe extends Probe { + private final Credential.Type type; + + public ContextPropertyProbe(Credential.Type type) { + super(ID); + this.type = checkNotNull(type); + } + + @Override + public ReportItems run(JsonNode root, RunContext ctx) throws Exception { + + ArrayNode contextNode = (ArrayNode) root.get("@context"); + if (contextNode == null) { + return notRun("No @context property", ctx); + } + + List expected = values.get(values.keySet() + .stream() + .filter(s->s.contains(type)) + .findFirst() + .orElseThrow(()-> new IllegalArgumentException(type.name() + " not recognized"))); + + List given = JsonNodeUtil.asStringList(contextNode); + int pos = 0; + for (String uri : expected) { + if ((given.size() < pos + 1) || !given.get(pos).equals(uri)) { + return error("missing required @context uri " + uri + " at position " + (pos + 1), ctx); + } + pos++; + } + + return success(ctx); + } + + private final static Map, List> values = new ImmutableMap.Builder, List>() + .put(Set.of(OpenBadgeCredential, AchievementCredential, EndorsementCredential), + List.of("https://www.w3.org/2018/credentials/v1", + //"https://imsglobal.github.io/openbadges-specification/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://imsglobal.github.io/openbadges-specification/context.json")) //dev legacy + "https://purl.imsglobal.org/spec/clr/v2p0/context.json", + "https://purl.imsglobal.org/spec/ob/v3p0/context.json")) + + .build(); + + public static final String ID = ContextPropertyProbe.class.getSimpleName(); +} diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/CredentialParseProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/CredentialParseProbe.java new file mode 100644 index 0000000..02a549b --- /dev/null +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/CredentialParseProbe.java @@ -0,0 +1,54 @@ +package org.oneedtech.inspect.vc.probe; + +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.util.resource.Resource; +import org.oneedtech.inspect.util.resource.ResourceType; +import org.oneedtech.inspect.util.resource.detect.TypeDetector; +import org.oneedtech.inspect.vc.Credential; +import org.oneedtech.inspect.vc.payload.PayloadParserFactory; + +/** + * A probe that verifies that the incoming credential resource is of a recognized payload type + * and if so extracts and stores the VC json data (a 'Credential' instance) + * in the RunContext. + * @author mgylling + */ +public class CredentialParseProbe extends Probe { + + @Override + public ReportItems run(Resource resource, RunContext context) throws Exception { + + try { + + //TODO if .detect reads from a URIResource twice. Cache the resource on first call. + + Optional type = Optional.ofNullable(resource.getType()); + if(type.isEmpty() || type.get() == ResourceType.UNKNOWN) { + type = TypeDetector.detect(resource, true); + if(type.isEmpty()) { + //TODO if URI fetch, TypeDetector likely to fail + System.err.println("typedetector fail: extend behavior here"); + return fatal("Could not detect credential payload type", context); + } else { + resource.setType(type.get()); + } + } + + if(!Credential.RECOGNIZED_PAYLOAD_TYPES.contains(type.get())) { + return fatal("Payload type not supported: " + type.get().getName(), context); + } + + Credential crd = PayloadParserFactory.of(resource).parse(resource, context); + context.addGeneratedObject(crd); + return success(this, context); + + } catch (Exception e) { + return fatal("Error while parsing credential: " + e.getMessage(), context); + } + } + +} 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 new file mode 100644 index 0000000..2932563 --- /dev/null +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/CredentialSubjectProbe.java @@ -0,0 +1,42 @@ +package org.oneedtech.inspect.vc.probe; + +import org.oneedtech.inspect.core.probe.Probe; +import org.oneedtech.inspect.core.probe.RunContext; +import org.oneedtech.inspect.core.report.ReportItems; + +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() { + super(ID); + } + + @Override + public ReportItems run(JsonNode root, RunContext ctx) throws Exception { + + 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 + */ + 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); + + } + + public static final String ID = CredentialSubjectProbe.class.getSimpleName(); +} 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 new file mode 100644 index 0000000..554a0cf --- /dev/null +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/EmbeddedProofProbe.java @@ -0,0 +1,159 @@ +package org.oneedtech.inspect.vc.probe; + +import java.io.StringReader; +import java.net.URI; + +import org.bouncycastle.util.Arrays; +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.Credential; +import org.oneedtech.inspect.vc.util.CachingDocumentLoader; + +import com.apicatalog.ld.DocumentError; +import com.apicatalog.multibase.Multibase; +import com.apicatalog.vc.processor.StatusVerifier; +import com.danubetech.verifiablecredentials.VerifiableCredential; + +import info.weboftrust.ldsignatures.verifier.Ed25519Signature2020LdVerifier; + +/** + * A Probe that verifies a credential's embedded proof. + * @author mgylling + */ +public class EmbeddedProofProbe extends Probe { + + public EmbeddedProofProbe() { + super(ID); + } + + + /* + * Using verifiable-credentials-java (https://github.com/danubetech/verifiable-credentials-java) + */ + @Override + public ReportItems run(Credential crd, RunContext ctx) throws Exception { + + //TODO check that proof is Ed25519 - issue error if not ("type": "Ed25519Signature2020", + //TODO check value "proofPurpose": "assertionMethod", if not error + + VerifiableCredential vc = VerifiableCredential.fromJson(new StringReader(crd.getJson().toString())); + vc.setDocumentLoader(new CachingDocumentLoader()); + + URI method = vc.getLdProof().getVerificationMethod(); + + // The verification method must dereference to an Ed25519VerificationKey2020. + // Danubetech's Ed25519Signature2020LdVerifier expects the decoded public key + // from the Ed25519VerificationKey2020 (32 bytes). + + String publicKeyMultibase = ""; + + // Formats accepted: + // + // [controller]#[publicKeyMultibase] + // did:key:[publicKeyMultibase] + // [publicKeyMultibase] + + // TODO fourth format that we don't support yet: a URL that returns a Ed25519VerificationKey2020 + // if starts with http and does not have hashcode, try fetch and see if returns Ed25519VerificationKey2020 + // property is publicKeyMultibase + + if (method.toString().contains("#")) { + publicKeyMultibase = method.getFragment(); + } else { + if (method.toString().startsWith("did")) { + String didScheme = method.getSchemeSpecificPart(); + if (didScheme.startsWith("key:")) { + publicKeyMultibase = didScheme.substring(4); + } else { + return error("Unknown verification method: " + method.toString(), ctx); + } + } else { + publicKeyMultibase = method.toString(); + } + } + + // Decode the Multibase to Multicodec and check that it is an Ed25519 public key + byte[] publicKeyMulticodec; + try { + publicKeyMulticodec = Multibase.decode(publicKeyMultibase); + if (publicKeyMulticodec[0] != -19 || publicKeyMulticodec[1] != 1) { + return error("Verification method does not contain an Ed25519 public key", ctx); + } + } catch (Exception e) { + return error("Verification method is invalid: " + e.getMessage(), ctx); + } + + // Extract the publicKey bytes from the Multicodec + byte[] publicKey = Arrays.copyOfRange(publicKeyMulticodec, 2, publicKeyMulticodec.length); + + Ed25519Signature2020LdVerifier verifier = new Ed25519Signature2020LdVerifier(publicKey); + + //TODO find out whether we also should check that controller matches issuer ID: + // if [controller]#[publicKeyMultibase] format - check [controller] segment + // if did:key:[publicKeyMultibase] format: issuer ID must match the entire URI + // if [publicKeyMultibase] -- don't check issuer ID. Maybe we should warn about this syntax. + + try { + boolean verify = verifier.verify(vc); + if (!verify) { + return error("Embedded proof verification failed.", ctx); + } + } catch (Exception e) { + return fatal("Embedded proof verification failed:" + e.getMessage(), ctx); + } + + return success(ctx); + } + + + + /* + * Note: if using com.apicatalog Iron, we get a generic VC verifier that + * will test other stuff than the Proof. So sometimes it may be that + * Iron internally retests something that we're already testing out in the + * Inspector class (e.g. expiration). But use this for now -- and remember + * that this probe is only run if the given credential has internal proof + * (aka is not a jwt). + */ + +// /* +// * Using iron-verifiable-credentials (https://github.com/filip26/iron-verifiable-credentials) +// */ +// @Override +// public ReportItems run(Credential crd, RunContext ctx) throws Exception { +// JsonDocument jsonDoc = JsonDocument.of(new StringReader(crd.getJson().toString())); +// JsonObject json = jsonDoc.getJsonContent().get().asJsonObject(); +// try { +// Vc.verify(json) +// .loader(new CachingDocumentLoader()) +// .useBundledContexts(false) //we control the cache in the loader +// .statusVerifier(new IronNoopStatusVerifier()) +// //.domain(...) +// //.didResolver(...) +// .isValid(); +// } catch (DocumentError e) { +// return error(e.getType() + " " + e.getSubject(), ctx); +// } catch (VerificationError e) { +// //System.err.println(e.getCode() + " (ProofVerifierProbe)"); +// if(e.getCode() == Code.Internal) { +// return exception(e.getMessage(), ctx.getResource()); +// } else if(e.getCode().equals(Code.Expired)) { +// //handled by other probe +// } else { +// return fatal(e.getCode().name() + " " + e.getMessage(), ctx); +// } +// +// } +// return success(ctx); +// } + + private static final class IronNoopStatusVerifier implements StatusVerifier { + @Override + public void verify(Status status) throws DocumentError, VerifyError { + // noop + } + } + + public static final String ID = EmbeddedProofProbe.class.getSimpleName(); +} diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ExpirationProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ExpirationProbe.java new file mode 100644 index 0000000..fcebd0c --- /dev/null +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ExpirationProbe.java @@ -0,0 +1,43 @@ +package org.oneedtech.inspect.vc.probe; + +import java.time.ZonedDateTime; + +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.Credential; + +import com.fasterxml.jackson.databind.JsonNode; + +/** + * A Probe that verifies a credential's expiration status + * @author mgylling + */ +public class ExpirationProbe extends Probe { + + public ExpirationProbe() { + super(ID); + } + + @Override + public ReportItems run(Credential crd, RunContext ctx) throws Exception { + /* + * If the AchievementCredential or EndorsementCredential has an “expirationDate” property + * and the expiration date is prior to the current date, the credential has expired. + */ + JsonNode node = crd.getJson().get("expirationDate"); + if(node != null) { + try { + ZonedDateTime expirationDate = ZonedDateTime.parse(node.textValue()); + if (ZonedDateTime.now().isAfter(expirationDate)) { + return fatal("The credential has expired (expiration date was " + node.asText() + ").", ctx); + } + } catch (Exception e) { + return exception("Error while checking expirationDate: " + e.getMessage(), ctx.getResource()); + } + } + return success(ctx); + } + + public static final String ID = ExpirationProbe.class.getSimpleName(); +} 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 new file mode 100644 index 0000000..b4bccb0 --- /dev/null +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ExternalProofProbe.java @@ -0,0 +1,151 @@ +package org.oneedtech.inspect.vc.probe; + +import static org.oneedtech.inspect.util.code.Defensives.checkTrue; + +import java.math.BigInteger; +import java.security.KeyFactory; +import java.security.PublicKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.RSAPublicKeySpec; +import java.util.Base64; +import java.util.Base64.Decoder; +import java.util.List; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpStatus; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.util.EntityUtils; +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.Credential; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.JWTVerifier; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.exceptions.AlgorithmMismatchException; +import com.auth0.jwt.exceptions.InvalidClaimException; +import com.auth0.jwt.exceptions.SignatureVerificationException; +import com.auth0.jwt.exceptions.TokenExpiredException; +import com.auth0.jwt.interfaces.DecodedJWT; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.base.Splitter; + +/** + * A Probe that verifies credential external proof (jwt) + * @author mlyon + */ +public class ExternalProofProbe extends Probe { + + public ExternalProofProbe() { + super(ID); + } + + @Override + public ReportItems run(Credential crd, RunContext ctx) throws Exception { + try { + verifySignature(crd, ctx); + } catch (Exception e) { + return fatal("Error verifying jwt signature: " + e.getMessage(), ctx); + } + return success(ctx); + } + + private void verifySignature(Credential crd, RunContext ctx) throws Exception { + checkTrue(crd.getJwt().isPresent(), "no jwt supplied"); + checkTrue(crd.getJwt().get().length() > 0, "no jwt supplied"); + + DecodedJWT decodedJwt = null; + String jwt = crd.getJwt().get(); + + List parts = Splitter.on('.').splitToList(jwt); + if(parts.size() != 3) throw new IllegalArgumentException("invalid jwt"); + + final Decoder decoder = Base64.getUrlDecoder(); + String joseHeader = new String(decoder.decode(parts.get(0))); + + ObjectMapper mapper = ((ObjectMapper)ctx.get(RunContext.Key.JACKSON_OBJECTMAPPER)); + JsonNode headerObj = mapper.readTree(joseHeader); + + //MUST be "RS256" + 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? + + //Option 1, fetch directly from header + JsonNode jwk = headerObj.get("jwk"); + + //Option 2, fetch from a hosting url + JsonNode kid = headerObj.get("kid"); + + if(jwk == null && kid == null) { throw new Exception("Key must present in either jwk or kid value."); } + if(kid != null){ + //Load jwk JsonNode from url and do the rest the same below. + //TODO Consider additional testing. + String kidUrl = kid.textValue(); + String jwkResponse = fetchJwk(kidUrl); + if(jwkResponse == null) { throw new Exception("Unable to retrieve jwk value from url specified in kid."); } + + jwk = mapper.readTree(jwkResponse); + } + + //Clean up may be required. Currently need to cleanse extra double quoting. + String modulusString = jwk.get("n").textValue(); + String exponentString = jwk.get("e").textValue(); + + BigInteger modulus = new BigInteger(1, decoder.decode(modulusString)); + BigInteger exponent = new BigInteger(1, decoder.decode(exponentString)); + + PublicKey pub = KeyFactory.getInstance("RSA").generatePublic(new RSAPublicKeySpec(modulus, exponent)); + + Algorithm algorithm = Algorithm.RSA256((RSAPublicKey)pub, null); + JWTVerifier verifier = JWT.require(algorithm) + .build(); //Reusable verifier instance + try { + decodedJwt = verifier.verify(jwt); + } + catch(SignatureVerificationException ex){ + throw new Exception("JWT Invalid signature", ex); + } + catch(AlgorithmMismatchException ex){ + throw new Exception("JWT Algorithm mismatch", ex); + } + catch(TokenExpiredException ex){ + throw new Exception("JWT Token expired", ex); + } + catch(InvalidClaimException ex){ + throw new Exception("JWT, one or more claims are invalid", ex); + } + } + + private String fetchJwk(String fetchUrl){ + String responseString = null; + + try { + CloseableHttpClient client = HttpClients.createDefault(); + HttpGet httpGet = new HttpGet(fetchUrl); + + CloseableHttpResponse response = client.execute(httpGet); + + if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { + HttpEntity entity = response.getEntity(); + responseString = EntityUtils.toString(entity, "UTF-8"); + } + + client.close(); + } + catch(Exception ex){ + responseString = null; + } + + return responseString; + } + + public static final String ID = ExternalProofProbe.class.getSimpleName(); + +} \ No newline at end of file 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 new file mode 100644 index 0000000..7f3f86e --- /dev/null +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/InlineJsonSchemaProbe.java @@ -0,0 +1,74 @@ +package org.oneedtech.inspect.vc.probe; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import org.oneedtech.inspect.core.probe.Probe; +import org.oneedtech.inspect.core.probe.RunContext; +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.Credential; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; + +/** + * Detect inline schemas in a credential and run them. + * @author mgylling + */ +public class InlineJsonSchemaProbe extends Probe { + private static final Set types = Set.of("1EdTechJsonSchemaValidator2019"); + private SchemaKey skip; + + public InlineJsonSchemaProbe() { + super(ID); + } + + public InlineJsonSchemaProbe(SchemaKey skip) { + super(ID); + this.skip = skip; + } + + @Override + public ReportItems run(JsonNode root, RunContext ctx) throws Exception { + List accumulator = new ArrayList<>(); + Set ioErrors = new HashSet<>(); + + //note - we don't get deep nested ones in e.g. EndorsementCredential + JsonNode credentialSchemaNode = root.get("credentialSchema"); + if(credentialSchemaNode == null) return success(ctx); + + ArrayNode schemas = (ArrayNode) credentialSchemaNode; //TODO guard this cast + + for(JsonNode schemaNode : schemas) { + JsonNode typeNode = schemaNode.get("type"); + if(typeNode == null || !types.contains(typeNode.asText())) continue; + JsonNode idNode = schemaNode.get("id"); + if(idNode == null) continue; + String id = idNode.asText().strip(); + if(ioErrors.contains(id)) continue; + if(equals(skip, id)) continue; + try { + accumulator.add(new JsonSchemaProbe(id).run(root, ctx)); + } catch (Exception e) { + if(!ioErrors.contains(id)) { + ioErrors.add(id); + accumulator.add(error("Could not read schema resource " + id, ctx)); + } + } + } + + return new ReportItems(accumulator); + } + + private boolean equals(SchemaKey key, String id) { + if(key == null) return false; + return key.getCanonicalURI().equals(id); + } + + public static final String ID = InlineJsonSchemaProbe.class.getSimpleName(); +} diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/IssuanceProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/IssuanceProbe.java new file mode 100644 index 0000000..04c5d6b --- /dev/null +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/IssuanceProbe.java @@ -0,0 +1,43 @@ +package org.oneedtech.inspect.vc.probe; + +import java.time.ZonedDateTime; + +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.Credential; + +import com.fasterxml.jackson.databind.JsonNode; + +/** + * A Probe that verifies a credential's issuance status + * @author mgylling + */ +public class IssuanceProbe extends Probe { + + public IssuanceProbe() { + super(ID); + } + + @Override + public ReportItems run(Credential crd, RunContext ctx) throws Exception { + /* + * If the AchievementCredential or EndorsementCredential “issuanceDate” + * property after the current date, the credential is not yet valid. + */ + JsonNode node = crd.getJson().get("issuanceDate"); + if(node != null) { + try { + ZonedDateTime issuanceDate = ZonedDateTime.parse(node.textValue()); + if (issuanceDate.isAfter(ZonedDateTime.now())) { + return fatal("The credential is not yet issued (issuance date is " + node.asText() + ").", ctx); + } + } catch (Exception e) { + return exception("Error while checking issuanceDate: " + e.getMessage(), ctx.getResource()); + } + } + return success(ctx); + } + + public static final String ID = IssuanceProbe.class.getSimpleName(); +} 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 new file mode 100644 index 0000000..ca11b0f --- /dev/null +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/RevocationListProbe.java @@ -0,0 +1,79 @@ +package org.oneedtech.inspect.vc.probe; + +import static org.oneedtech.inspect.core.probe.RunContext.Key.JACKSON_OBJECTMAPPER; + +import java.io.InputStream; +import java.net.URI; +import java.net.URL; +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.Credential; +import org.oneedtech.inspect.vc.util.JsonNodeUtil; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * A Probe that verifies a credential's revocation status. + * @author mgylling + */ +public class RevocationListProbe extends Probe { + + public RevocationListProbe() { + super(ID); + } + + @Override + public ReportItems run(Credential crd, RunContext ctx) throws Exception { + + /* + * If the AchievementCredential or EndorsementCredential has a “credentialStatus” property + * and the type of the CredentialStatus object is “1EdTechRevocationList”, fetch the + * credential status from the URL provided. If the request is unsuccessful, + * report a warning, not an error. + */ + + JsonNode credentialStatus = crd.getJson().get("credentialStatus"); + if(credentialStatus != null) { + JsonNode type = credentialStatus.get("type"); + if(type != null && type.asText().strip().equals("1EdTechRevocationList")) { + JsonNode listID = credentialStatus.get("id"); + if(listID != null) { + try { + URL url = new URI(listID.asText().strip()).toURL(); + try (InputStream is = url.openStream()) { + JsonNode revocList = ((ObjectMapper)ctx.get(JACKSON_OBJECTMAPPER)).readTree(is.readAllBytes()); + + /* To check if a credential has been revoked, the verifier issues a GET request + * to the URL of the issuer's 1EdTech Revocation List Status Method. If the + * credential's id is in the list of revokedCredentials and the value of + * revoked is true or ommitted, the issuer has revoked the credential. */ + + JsonNode crdID = crd.getJson().get("id"); //TODO these != checks sb removed (trigger warning) + if(crdID != null) { + List list = JsonNodeUtil.asNodeList(revocList.get("revokedCredentials")); + if(list != null) { + for(JsonNode item : list) { + JsonNode revID = item.get("id"); + JsonNode revoked = item.get("revoked"); + if(revID != null && revID.equals(crdID) && (revoked == null || revoked.asBoolean())) { + return fatal("Credential has been revoked", ctx); + } + } + } + } + } + } catch (Exception e) { + return warning("Error when fetching credentialStatus resource " + e.getMessage(), ctx); + } + } + } + } + return success(ctx); + } + + public static final String ID = RevocationListProbe.class.getSimpleName(); +} diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/TypePropertyProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/TypePropertyProbe.java new file mode 100644 index 0000000..804380e --- /dev/null +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/TypePropertyProbe.java @@ -0,0 +1,64 @@ +package org.oneedtech.inspect.vc.probe; + +import static org.oneedtech.inspect.util.code.Defensives.checkNotNull; + +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.Credential; +import org.oneedtech.inspect.vc.Credential.Type; +import org.oneedtech.inspect.vc.util.JsonNodeUtil; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; + +/** + * A Probe that verifies a credential's type property. + * + * @author mgylling + */ +public class TypePropertyProbe extends Probe { + private final Credential.Type expected; + + public TypePropertyProbe(Credential.Type expected) { + super(ID); + this.expected = checkNotNull(expected); + } + + @Override + public ReportItems run(JsonNode root, RunContext ctx) throws Exception { + + ArrayNode typeNode = (ArrayNode) root.get("type"); + if (typeNode == null) + return fatal("No type property", ctx); + + List values = JsonNodeUtil.asStringList(typeNode); + + if (!values.contains("VerifiableCredential")) { + return fatal("The type property does not contain the entry 'VerifiableCredential'", ctx); + } + + if (expected == Credential.Type.OpenBadgeCredential) { + if (!values.contains("OpenBadgeCredential") && !values.contains("AchievementCredential")) { + return fatal("The type property does not contain one of 'OpenBadgeCredential' or 'AchievementCredential'", ctx); + } + } else if (expected == Credential.Type.ClrCredential) { + if (!values.contains("ClrCredential")) { + return fatal("The type property does not contain the entry 'ClrCredential'", ctx); + } + } else if (expected == Credential.Type.EndorsementCredential) { + if (!values.contains("EndorsementCredential")) { + return fatal("The type property does not contain the entry 'EndorsementCredential'", ctx); + } + } else { + // TODO implement + throw new IllegalStateException(); + } + + return success(ctx); + } + + public static final String ID = TypePropertyProbe.class.getSimpleName(); +} 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 new file mode 100644 index 0000000..b656419 --- /dev/null +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/CachingDocumentLoader.java @@ -0,0 +1,78 @@ +package org.oneedtech.inspect.vc.util; + +import java.io.InputStream; +import java.net.URI; +import java.net.URL; +import java.time.Duration; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.oneedtech.inspect.util.code.Tuple; + +import com.apicatalog.jsonld.JsonLdError; +import com.apicatalog.jsonld.JsonLdErrorCode; +import com.apicatalog.jsonld.document.Document; +import com.apicatalog.jsonld.document.JsonDocument; +import com.apicatalog.jsonld.loader.DocumentLoader; +import com.apicatalog.jsonld.loader.DocumentLoaderOptions; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.collect.ImmutableMap; +import com.google.common.io.Resources; + +/** + * A com.apicatalog DocumentLoader with a threadsafe static cache. + * + * @author mgylling + */ +public class CachingDocumentLoader implements DocumentLoader { + + @Override + public Document loadDocument(URI url, DocumentLoaderOptions options) throws JsonLdError { + Tuple tpl = new Tuple<>(url.toASCIIString(), options); + try { + return documentCache.get(tpl); + } catch (Exception e) { + logger.error("documentCache not able to load {}", url); + throw new JsonLdError(JsonLdErrorCode.INVALID_REMOTE_CONTEXT, e.getMessage()); + } + } + + 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.json",Resources.getResource("contexts/ob-v3p0.json")) + .put("https://imsglobal.github.io/openbadges-specification/context.json",Resources.getResource("contexts/obv3x.jsonld")) + .put("https://www.w3.org/ns/did/v1", Resources.getResource("contexts/did-v1.jsonld")) + .put("https://www.w3.org/ns/odrl.jsonld", Resources.getResource("contexts/odrl.jsonld")) + .put("https://w3id.org/security/suites/ed25519-2020/v1",Resources.getResource("contexts/security-suites-ed25519-2020-v1.jsonld")) + .put("https://www.w3.org/2018/credentials/v1", Resources.getResource("contexts/2018-credentials-v1.jsonld")) + .put("https://w3id.org/security/v1", Resources.getResource("contexts/security-v1.jsonld")) + .put("https://w3id.org/security/v2", Resources.getResource("contexts/security-v2.jsonld")) + .put("https://w3id.org/security/v3", Resources.getResource("contexts/security-v3-unstable.jsonld")) + .put("https://w3id.org/security/bbs/v1", Resources.getResource("contexts/security-bbs-v1.jsonld")) + .put("https://w3id.org/security/suites/secp256k1-2019/v1", Resources.getResource("contexts/suites-secp256k1-2019.jsonld")) + .put("https://w3id.org/security/suites/ed25519-2018/v1", Resources.getResource("contexts/suites-ed25519-2018.jsonld")) + .put("https://w3id.org/security/suites/x25519-2019/v1", Resources.getResource("contexts/suites-x25519-2019.jsonld")) + .put("https://w3id.org/security/suites/jws-2020/v1", Resources.getResource("contexts/suites-jws-2020.jsonld")) + + .build(); + + static final LoadingCache, Document> documentCache = CacheBuilder.newBuilder() + .initialCapacity(32).maximumSize(64).expireAfterAccess(Duration.ofHours(24)) + .build(new CacheLoader, Document>() { + public Document load(final Tuple id) throws Exception { + try (InputStream is = bundled.keySet().contains(id.t1) + ? bundled.get(id.t1).openStream() + : new URI(id.t1).toURL().openStream();) { + return JsonDocument.of(is); + } + } + }); + + public static void reset() { + documentCache.invalidateAll(); + } + + private static final Logger logger = LogManager.getLogger(); +} diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/JsonNodeUtil.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/JsonNodeUtil.java new file mode 100644 index 0000000..aaf6083 --- /dev/null +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/util/JsonNodeUtil.java @@ -0,0 +1,58 @@ +package org.oneedtech.inspect.vc.util; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +import org.oneedtech.inspect.core.probe.json.JsonPathEvaluator; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; + +/** + * Json node utilities. + * @author mgylling + */ +public class JsonNodeUtil { + + public static List asNodeList(JsonNode root, String jsonPath, JsonPathEvaluator evaluator) { + List list = new ArrayList<>(); + ArrayNode array = evaluator.eval(jsonPath, root); + for(JsonNode node : array) { + if(!(node instanceof ArrayNode)) { + list.add(node); + } else { + ArrayNode values = (ArrayNode) node; + for(JsonNode value : values) { + list.add(value); + } + } + } + return list; + } + + public static List asStringList(JsonNode node) { + if(!(node instanceof ArrayNode)) { + return List.of(node.asText()); + } else { + ArrayNode arrayNode = (ArrayNode)node; + return StreamSupport + .stream(arrayNode.spliterator(), false) + .map(n->n.asText().strip()) + .collect(Collectors.toList()); + } + } + + public static List asNodeList(JsonNode node) { + if(node == null) return null; + if(!(node instanceof ArrayNode)) { + return List.of(node); + } else { + ArrayNode arrayNode = (ArrayNode)node; + return StreamSupport + .stream(arrayNode.spliterator(), false) + .collect(Collectors.toList()); + } + } +} diff --git a/inspector-vc/src/main/resources/contexts/2018-credentials-examples-v1.jsonld b/inspector-vc/src/main/resources/contexts/2018-credentials-examples-v1.jsonld new file mode 100644 index 0000000..425199c --- /dev/null +++ b/inspector-vc/src/main/resources/contexts/2018-credentials-examples-v1.jsonld @@ -0,0 +1,69 @@ +{ + "@context": [ + { + "@version": 1.1 + }, + "https://www.w3.org/ns/odrl.jsonld", + { + "ex": "https://example.org/examples#", + "schema": "http://schema.org/", + "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#", + "3rdPartyCorrelation": "ex:3rdPartyCorrelation", + "AllVerifiers": "ex:AllVerifiers", + "Archival": "ex:Archival", + "BachelorDegree": "ex:BachelorDegree", + "Child": "ex:Child", + "CLCredentialDefinition2019": "ex:CLCredentialDefinition2019", + "CLSignature2019": "ex:CLSignature2019", + "IssuerPolicy": "ex:IssuerPolicy", + "HolderPolicy": "ex:HolderPolicy", + "Mother": "ex:Mother", + "RelationshipCredential": "ex:RelationshipCredential", + "UniversityDegreeCredential": "ex:UniversityDegreeCredential", + "AlumniCredential": "ex:AlumniCredential", + "DisputeCredential": "ex:DisputeCredential", + "PrescriptionCredential": "ex:PrescriptionCredential", + "ZkpExampleSchema2018": "ex:ZkpExampleSchema2018", + "issuerData": "ex:issuerData", + "attributes": "ex:attributes", + "signature": "ex:signature", + "signatureCorrectnessProof": "ex:signatureCorrectnessProof", + "primaryProof": "ex:primaryProof", + "nonRevocationProof": "ex:nonRevocationProof", + "alumniOf": { + "@id": "schema:alumniOf", + "@type": "rdf:HTML" + }, + "child": { + "@id": "ex:child", + "@type": "@id" + }, + "degree": "ex:degree", + "degreeType": "ex:degreeType", + "degreeSchool": "ex:degreeSchool", + "college": "ex:college", + "name": { + "@id": "schema:name", + "@type": "rdf:HTML" + }, + "givenName": "schema:givenName", + "familyName": "schema:familyName", + "parent": { + "@id": "ex:parent", + "@type": "@id" + }, + "referenceId": "ex:referenceId", + "documentPresence": "ex:documentPresence", + "evidenceDocument": "ex:evidenceDocument", + "spouse": "schema:spouse", + "subjectPresence": "ex:subjectPresence", + "verifier": { + "@id": "ex:verifier", + "@type": "@id" + }, + "currentStatus": "ex:currentStatus", + "statusReason": "ex:statusReason", + "prescription": "ex:prescription" + } + ] +} \ No newline at end of file diff --git a/inspector-vc/src/main/resources/contexts/2018-credentials-v1.jsonld b/inspector-vc/src/main/resources/contexts/2018-credentials-v1.jsonld new file mode 100644 index 0000000..837104f --- /dev/null +++ b/inspector-vc/src/main/resources/contexts/2018-credentials-v1.jsonld @@ -0,0 +1,315 @@ +{ + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + "VerifiableCredential": { + "@id": "https://www.w3.org/2018/credentials#VerifiableCredential", + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + "cred": "https://www.w3.org/2018/credentials#", + "sec": "https://w3id.org/security#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + "credentialSchema": { + "@id": "cred:credentialSchema", + "@type": "@id", + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + "cred": "https://www.w3.org/2018/credentials#", + "JsonSchemaValidator2018": "cred:JsonSchemaValidator2018" + } + }, + "credentialStatus": { + "@id": "cred:credentialStatus", + "@type": "@id" + }, + "credentialSubject": { + "@id": "cred:credentialSubject", + "@type": "@id" + }, + "evidence": { + "@id": "cred:evidence", + "@type": "@id" + }, + "expirationDate": { + "@id": "cred:expirationDate", + "@type": "xsd:dateTime" + }, + "holder": { + "@id": "cred:holder", + "@type": "@id" + }, + "issued": { + "@id": "cred:issued", + "@type": "xsd:dateTime" + }, + "issuer": { + "@id": "cred:issuer", + "@type": "@id" + }, + "issuanceDate": { + "@id": "cred:issuanceDate", + "@type": "xsd:dateTime" + }, + "proof": { + "@id": "sec:proof", + "@type": "@id", + "@container": "@graph" + }, + "refreshService": { + "@id": "cred:refreshService", + "@type": "@id", + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + "cred": "https://www.w3.org/2018/credentials#", + "ManualRefreshService2018": "cred:ManualRefreshService2018" + } + }, + "termsOfUse": { + "@id": "cred:termsOfUse", + "@type": "@id" + }, + "validFrom": { + "@id": "cred:validFrom", + "@type": "xsd:dateTime" + }, + "validUntil": { + "@id": "cred:validUntil", + "@type": "xsd:dateTime" + } + } + }, + "VerifiablePresentation": { + "@id": "https://www.w3.org/2018/credentials#VerifiablePresentation", + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + "cred": "https://www.w3.org/2018/credentials#", + "sec": "https://w3id.org/security#", + "holder": { + "@id": "cred:holder", + "@type": "@id" + }, + "proof": { + "@id": "sec:proof", + "@type": "@id", + "@container": "@graph" + }, + "verifiableCredential": { + "@id": "cred:verifiableCredential", + "@type": "@id", + "@container": "@graph" + } + } + }, + "EcdsaSecp256k1Signature2019": { + "@id": "https://w3id.org/security#EcdsaSecp256k1Signature2019", + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + "sec": "https://w3id.org/security#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + "challenge": "sec:challenge", + "created": { + "@id": "http://purl.org/dc/terms/created", + "@type": "xsd:dateTime" + }, + "domain": "sec:domain", + "expires": { + "@id": "sec:expiration", + "@type": "xsd:dateTime" + }, + "jws": "sec:jws", + "nonce": "sec:nonce", + "proofPurpose": { + "@id": "sec:proofPurpose", + "@type": "@vocab", + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + "sec": "https://w3id.org/security#", + "assertionMethod": { + "@id": "sec:assertionMethod", + "@type": "@id", + "@container": "@set" + }, + "authentication": { + "@id": "sec:authenticationMethod", + "@type": "@id", + "@container": "@set" + } + } + }, + "proofValue": "sec:proofValue", + "verificationMethod": { + "@id": "sec:verificationMethod", + "@type": "@id" + } + } + }, + "EcdsaSecp256r1Signature2019": { + "@id": "https://w3id.org/security#EcdsaSecp256r1Signature2019", + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + "sec": "https://w3id.org/security#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + "challenge": "sec:challenge", + "created": { + "@id": "http://purl.org/dc/terms/created", + "@type": "xsd:dateTime" + }, + "domain": "sec:domain", + "expires": { + "@id": "sec:expiration", + "@type": "xsd:dateTime" + }, + "jws": "sec:jws", + "nonce": "sec:nonce", + "proofPurpose": { + "@id": "sec:proofPurpose", + "@type": "@vocab", + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + "sec": "https://w3id.org/security#", + "assertionMethod": { + "@id": "sec:assertionMethod", + "@type": "@id", + "@container": "@set" + }, + "authentication": { + "@id": "sec:authenticationMethod", + "@type": "@id", + "@container": "@set" + } + } + }, + "proofValue": "sec:proofValue", + "verificationMethod": { + "@id": "sec:verificationMethod", + "@type": "@id" + } + } + }, + "Ed25519Signature2018": { + "@id": "https://w3id.org/security#Ed25519Signature2018", + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + "sec": "https://w3id.org/security#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + "challenge": "sec:challenge", + "created": { + "@id": "http://purl.org/dc/terms/created", + "@type": "xsd:dateTime" + }, + "domain": "sec:domain", + "expires": { + "@id": "sec:expiration", + "@type": "xsd:dateTime" + }, + "jws": "sec:jws", + "nonce": "sec:nonce", + "proofPurpose": { + "@id": "sec:proofPurpose", + "@type": "@vocab", + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + "sec": "https://w3id.org/security#", + "assertionMethod": { + "@id": "sec:assertionMethod", + "@type": "@id", + "@container": "@set" + }, + "authentication": { + "@id": "sec:authenticationMethod", + "@type": "@id", + "@container": "@set" + } + } + }, + "proofValue": "sec:proofValue", + "verificationMethod": { + "@id": "sec:verificationMethod", + "@type": "@id" + } + } + }, + "RsaSignature2018": { + "@id": "https://w3id.org/security#RsaSignature2018", + "@context": { + "@version": 1.1, + "@protected": true, + "challenge": "sec:challenge", + "created": { + "@id": "http://purl.org/dc/terms/created", + "@type": "xsd:dateTime" + }, + "domain": "sec:domain", + "expires": { + "@id": "sec:expiration", + "@type": "xsd:dateTime" + }, + "jws": "sec:jws", + "nonce": "sec:nonce", + "proofPurpose": { + "@id": "sec:proofPurpose", + "@type": "@vocab", + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + "sec": "https://w3id.org/security#", + "assertionMethod": { + "@id": "sec:assertionMethod", + "@type": "@id", + "@container": "@set" + }, + "authentication": { + "@id": "sec:authenticationMethod", + "@type": "@id", + "@container": "@set" + } + } + }, + "proofValue": "sec:proofValue", + "verificationMethod": { + "@id": "sec:verificationMethod", + "@type": "@id" + } + } + }, + "proof": { + "@id": "https://w3id.org/security#proof", + "@type": "@id", + "@container": "@graph" + } + } +} \ No newline at end of file diff --git a/inspector-vc/src/main/resources/contexts/clr-v2p0.json b/inspector-vc/src/main/resources/contexts/clr-v2p0.json new file mode 100644 index 0000000..d1e1b93 --- /dev/null +++ b/inspector-vc/src/main/resources/contexts/clr-v2p0.json @@ -0,0 +1,53 @@ +{ + "@context": { + "id": "@id", + "type": "@type", + "ClrCredential": { + "@id": "https://purl.imsglobal.org/spec/clr/v2p0/vocab.html#ClrCredential", + "@context": { + "id": "@id", + "type": "@type" + } + }, + "ClrSubject": { + "@id": "https://purl.imsglobal.org/spec/clr/v2p0/vocab.html#ClrSubject", + "@context": { + "id": "@id", + "type": "@type", + "cred": "https://www.w3.org/2018/credentials#", + "obi": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#", + "achievement": { + "@id": "https://purl.imsglobal.org/spec/clr/v2p0/vocab.html#achievement", + "@type": "obi:Achievement", + "@container": "@set" + }, + "association": { + "@id": "https://purl.imsglobal.org/spec/clr/v2p0/vocab.html#association", + "@type": "https://purl.imsglobal.org/spec/clr/v2p0/vocab.html#Association", + "@container": "@set" + }, + "verifiableCredential": { + "@id": "https://purl.imsglobal.org/spec/clr/v2p0/vocab.html#verifiableCredential", + "@type": "cred:verifiableCredential", + "@container": "@set" + } + } + }, + "Association": { + "@id": "https://purl.imsglobal.org/spec/clr/v2p0/vocab.html#Association", + "@context": { + "associationType": { + "@id": "https://purl.imsglobal.org/spec/clr/v2p0/vocab.html#AssociationType" + }, + "sourceId": { + "@id": "https://purl.imsglobal.org/spec/clr/v2p0/vocab.html#sourceId", + "@type": "xsd:anyURI" + }, + "targetId": { + "@id": "https://purl.imsglobal.org/spec/clr/v2p0/vocab.html#targetId", + "@type": "xsd:anyURI" + } + } + } + } +} \ No newline at end of file diff --git a/inspector-vc/src/main/resources/contexts/did-v1.jsonld b/inspector-vc/src/main/resources/contexts/did-v1.jsonld new file mode 100644 index 0000000..55ab11c --- /dev/null +++ b/inspector-vc/src/main/resources/contexts/did-v1.jsonld @@ -0,0 +1,58 @@ +{ + "@context": { + "@protected": true, + "id": "@id", + "type": "@type", + + "alsoKnownAs": { + "@id": "https://www.w3.org/ns/activitystreams#alsoKnownAs", + "@type": "@id" + }, + "assertionMethod": { + "@id": "https://w3id.org/security#assertionMethod", + "@type": "@id", + "@container": "@set" + }, + "authentication": { + "@id": "https://w3id.org/security#authenticationMethod", + "@type": "@id", + "@container": "@set" + }, + "capabilityDelegation": { + "@id": "https://w3id.org/security#capabilityDelegationMethod", + "@type": "@id", + "@container": "@set" + }, + "capabilityInvocation": { + "@id": "https://w3id.org/security#capabilityInvocationMethod", + "@type": "@id", + "@container": "@set" + }, + "controller": { + "@id": "https://w3id.org/security#controller", + "@type": "@id" + }, + "keyAgreement": { + "@id": "https://w3id.org/security#keyAgreementMethod", + "@type": "@id", + "@container": "@set" + }, + "service": { + "@id": "https://www.w3.org/ns/did#service", + "@type": "@id", + "@context": { + "@protected": true, + "id": "@id", + "type": "@type", + "serviceEndpoint": { + "@id": "https://www.w3.org/ns/did#serviceEndpoint", + "@type": "@id" + } + } + }, + "verificationMethod": { + "@id": "https://w3id.org/security#verificationMethod", + "@type": "@id" + } + } +} \ No newline at end of file diff --git a/inspector-vc/src/main/resources/contexts/ob-v3p0.json b/inspector-vc/src/main/resources/contexts/ob-v3p0.json new file mode 100644 index 0000000..8e133c8 --- /dev/null +++ b/inspector-vc/src/main/resources/contexts/ob-v3p0.json @@ -0,0 +1,440 @@ +{ + "@context": { + "id": "@id", + "type": "@type", + + "xsd": "https://www.w3.org/2001/XMLSchema#", + + "OpenBadgeCredential": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#OpenBadgeCredential" + }, + "Achievement": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#Achievement", + "@context": { + "achievementType": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#achievementType", + "@type": "xsd:string" + }, + "alignment": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#alignment", + "@type": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#Alignment", + "@container": "@set" + }, + "creator": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#Profile" + }, + "creditsAvailable": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#creditsAvailable", + "@type": "xsd:float" + }, + "criteria": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#Criteria", + "@type": "@id" + }, + "fieldOfStudy": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#fieldOfStudy", + "@type": "xsd:string" + }, + "humanCode": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#humanCode", + "@type": "xsd:string" + }, + "otherIdentifier": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#otherIdentifier", + "@type": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#IdentifierEntry", + "@container": "@set" + }, + "related": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#related", + "@type": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#Related", + "@container": "@set" + }, + "resultDescription": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#resultDescription", + "@type": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#ResultDescription", + "@container": "@set" + }, + "specialization": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#specialization", + "@type": "xsd:string" + }, + "tag": { + "@id": "https://schema.org/keywords", + "@type": "xsd:string", + "@container": "@set" + }, + "version": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#version", + "@type": "xsd:string" + } + } + }, + "AchievementCredential": { + "@id": "OpenBadgeCredential" + }, + "AchievementSubject": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#AchievementSubject", + "@context": { + "achievement": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#Achievement" + }, + "activityEndDate": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#activityEndDate", + "@type": "xsd:date" + }, + "activityStartDate": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#activityStartDate", + "@type": "xsd:date" + }, + "creditsEarned": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#creditsEarned", + "@type": "xsd:float" + }, + "identifier": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#identifier", + "@type": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#IdentityObject", + "@container": "@set" + }, + "licenseNumber": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#licenseNumber", + "@type": "xsd:string" + }, + "result": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#result", + "@type": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#Result", + "@container": "@set" + }, + "role": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#role", + "@type": "xsd:string" + }, + "source": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#source", + "@type": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#Profile" + }, + "term": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#term", + "@type": "xsd:string" + } + } + }, + "Address": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#Address", + "@context": { + "addressCountry": { + "@id": "https://schema.org/addressCountry", + "@type": "xsd:string" + }, + "addressCountryCode": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#CountryCode", + "@type": "xsd:string" + }, + "addressLocality": { + "@id": "https://schema.org/addressLocality", + "@type": "xsd:string" + }, + "addressRegion": { + "@id": "https://schema.org/addressRegion", + "@type": "xsd:string" + }, + "geo": { + "@id" : "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#GeoCoordinates" + }, + "postOfficeBoxNumber": { + "@id": "https://schema.org/postOfficeBoxNumber", + "@type": "xsd:string" + }, + "postalCode": { + "@id": "https://schema.org/postalCode", + "@type": "xsd:string" + }, + "streetAddress": { + "@id": "https://schema.org/streetAddress", + "@type": "xsd:string" + } + } + }, + "Alignment": { + "@id": "https://schema.org/Alignment", + "@context": { + "targetCode": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#targetCode", + "@type": "xsd:string" + }, + "targetDescription": { + "@id": "https://schema.org/targetDescription", + "@type": "xsd:string" + }, + "targetFramework": { + "@id": "https://schema.org/targetFramework", + "@type": "xsd:string" + }, + "targetName": { + "@id": "https://schema.org/targetName", + "@type": "xsd:string" + }, + "targetType": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#targetType", + "@type": "xsd:string" + }, + "targetUrl": { + "@id": "https://schema.org/targetUrl", + "@type": "xsd:anyURI" + } + } + }, + "Criteria": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#Criteria" + }, + "EndorsementCredential": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#EndorsementCredential" + }, + "EndorsementSubject": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#EndorsementSubject", + "@context": { + "endorsementComment": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#endorsementComment", + "@type": "xsd:string" + } + } + }, + "Evidence": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#Evidence", + "@context": { + "audience": { + "@id": "https://schema.org/audience", + "@type": "xsd:string" + }, + "genre": { + "@id": "https://schema.org/genre", + "@type": "xsd:string" + } + } + }, + "GeoCoordinates": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#GeoCoordinates", + "@context": { + "latitude": { + "@id": "https://schema.org/latitude", + "@type": "xsd:string" + }, + "longitude": { + "@id": "https://schema.org/longitude", + "@type": "xsd:string" + } + } + }, + "IdentifierEntry": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#IdentifierEntry", + "@context": { + "identifier": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#identifier", + "@type": "xsd:string" + }, + "identifierType": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#identifierType", + "@type": "xsd:string" + } + } + }, + "IdentityObject": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#IdentityObject", + "@context": { + "hashed": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#hashed", + "@type": "xsd:boolean" + }, + "identityHash": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#identityHash", + "@type": "xsd:string" + }, + "identityType": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#identityType", + "@type": "xsd:string" + }, + "salt": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#salt", + "@type": "xsd:string" + } + } + }, + "Image": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#Image", + "@context": { + "caption": { + "@id": "https://schema.org/caption", + "@type": "xsd:string" + } + } + }, + "Profile": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#Profile", + "@context": { + "additionalName": { + "@id": "https://schema.org/additionalName", + "@type": "xsd:string" + }, + "address": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#Address" + }, + "dateOfBirth": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#dateOfBirth", + "@type": "xsd:date" + }, + "email": { + "@id": "https://schema.org/email", + "@type": "xsd:string" + }, + "familyName": { + "@id": "https://schema.org/familyName", + "@type": "xsd:string" + }, + "familyNamePrefix": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#familyNamePrefix", + "@type": "xsd:string" + }, + "givenName": { + "@id": "https://schema.org/givenName", + "@type": "xsd:string" + }, + "honorificPrefix": { + "@id": "https://schema.org/honorificPrefix", + "@type": "xsd:string" + }, + "honorificSuffix": { + "@id": "https://schema.org/honorificSuffix", + "@type": "xsd:string" + }, + "otherIdentifier": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#otherIdentifier", + "@type": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#IdentifierEntry", + "@container": "@set" + }, + "parentOrg": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#parentOrg", + "@type": "xsd:string" + }, + "patronymicName": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#patronymicName", + "@type": "xsd:string" + }, + "phone": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#PhoneNumber", + "@type": "xsd:string" + }, + "official": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#official", + "@type": "xsd:string" + } + } + }, + "Related": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#Related", + "@context": { + "version": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#version", + "@type": "xsd:string" + } + } + }, + "Result": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#Result", + "@context": { + "achievedLevel": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#achievedLevel", + "@type": "xsd:anyURI" + }, + "resultDescription": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#resultDescription", + "@type": "xsd:anyURI" + }, + "status": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#status", + "@type": "xsd:string" + }, + "value": { + "@id": "https://schema.org/value", + "@type": "xsd:string" + } + } + }, + "ResultDescription": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#ResultDescription", + "@context": { + "allowedValue": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#allowedValue", + "@type": "xsd:string", + "@container": "@set" + }, + "requiredLevel": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#requiredLevel", + "@type": "xsd:anyURI" + }, + "requiredValue": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#requiredValue", + "@type": "xsd:string" + }, + "resultType": { + "@id":"https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#resultType", + "@type": "xsd:string" + }, + "rubricCriterionLevel": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#rubricCriterionLevel", + "@type": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#RubricCriterionLevel", + "@container": "@set" + }, + "valueMax": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#valueMax", + "@type": "xsd:string" + }, + "valueMin": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#valueMin", + "@type": "xsd:string" + } + } + }, + "RubricCriterionLevel": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#RubricCriterionLevel", + "@context": { + "level": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#level", + "@type": "xsd:string" + }, + "points": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#points", + "@type": "xsd:string" + } + } + }, + "alignment": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#alignment", + "@type": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#Alignment", + "@container": "@set" + }, + "description": { + "@id": "https://schema.org/description", + "@type": "xsd:string" + }, + "endorsement": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#endorsement", + "@type": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#EndorsementCredential", + "@container": "@set" + }, + "image": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#image", + "@type": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#Image" + }, + "name": { + "@id": "https://schema.org/name", + "@type": "xsd:string" + }, + "narrative": { + "@id": "https://purl.imsglobal.org/spec/ob/v3p0/vocab.html#narrative", + "@type": "xsd:string" + }, + "url": { + "@id": "https://schema.org/url", + "@type": "xsd:anyURI" + } + } +} \ No newline at end of file diff --git a/inspector-vc/src/main/resources/contexts/obv3x.jsonld b/inspector-vc/src/main/resources/contexts/obv3x.jsonld new file mode 100644 index 0000000..301efa5 --- /dev/null +++ b/inspector-vc/src/main/resources/contexts/obv3x.jsonld @@ -0,0 +1,440 @@ +{ + "@context": { + "id": "@id", + "type": "@type", + + "xsd": "https://www.w3.org/2001/XMLSchema#", + + "OpenBadgeCredential": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#OpenBadgeCredential" + }, + "Achievement": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#Achievement", + "@context": { + "achievementType": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#achievementType", + "@type": "xsd:string" + }, + "alignment": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#alignment", + "@type": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#Alignment", + "@container": "@set" + }, + "creator": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#Profile" + }, + "creditsAvailable": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#creditsAvailable", + "@type": "xsd:float" + }, + "criteria": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#Criteria", + "@type": "@id" + }, + "fieldOfStudy": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#fieldOfStudy", + "@type": "xsd:string" + }, + "humanCode": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#humanCode", + "@type": "xsd:string" + }, + "otherIdentifier": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#otherIdentifier", + "@type": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#IdentifierEntry", + "@container": "@set" + }, + "related": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#related", + "@type": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#Related", + "@container": "@set" + }, + "resultDescription": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#resultDescription", + "@type": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#ResultDescription", + "@container": "@set" + }, + "specialization": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#specialization", + "@type": "xsd:string" + }, + "tag": { + "@id": "https://schema.org/keywords", + "@type": "xsd:string", + "@container": "@set" + }, + "version": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#version", + "@type": "xsd:string" + } + } + }, + "AchievementCredential": { + "@id": "OpenBadgeCredential" + }, + "AchievementSubject": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#AchievementSubject", + "@context": { + "achievement": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#Achievement" + }, + "activityEndDate": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#activityEndDate", + "@type": "xsd:date" + }, + "activityStartDate": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#activityStartDate", + "@type": "xsd:date" + }, + "creditsEarned": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#creditsEarned", + "@type": "xsd:float" + }, + "identifier": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#identifier", + "@type": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#IdentityObject", + "@container": "@set" + }, + "licenseNumber": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#licenseNumber", + "@type": "xsd:string" + }, + "result": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#result", + "@type": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#Result", + "@container": "@set" + }, + "role": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#role", + "@type": "xsd:string" + }, + "source": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#source", + "@type": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#Profile" + }, + "term": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#term", + "@type": "xsd:string" + } + } + }, + "Address": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#Address", + "@context": { + "addressCountry": { + "@id": "https://schema.org/addressCountry", + "@type": "xsd:string" + }, + "addressCountryCode": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#CountryCode", + "@type": "xsd:string" + }, + "addressLocality": { + "@id": "https://schema.org/addresLocality", + "@type": "xsd:string" + }, + "addressRegion": { + "@id": "https://schema.org/addressRegion", + "@type": "xsd:string" + }, + "geo": { + "@id" : "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#GeoCoordinates" + }, + "postOfficeBoxNumber": { + "@id": "https://schema.org/postOfficeBoxNumber", + "@type": "xsd:string" + }, + "postalCode": { + "@id": "https://schema.org/postalCode", + "@type": "xsd:string" + }, + "streetAddress": { + "@id": "https://schema.org/streetAddress", + "@type": "xsd:string" + } + } + }, + "Alignment": { + "@id": "https://schema.org/Alignment", + "@context": { + "targetCode": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#targetCode", + "@type": "xsd:string" + }, + "targetDescription": { + "@id": "https://schema.org/targetDescription", + "@type": "xsd:string" + }, + "targetFramework": { + "@id": "https://schema.org/targetFramework", + "@type": "xsd:string" + }, + "targetName": { + "@id": "https://schema.org/targetName", + "@type": "xsd:string" + }, + "targetType": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#targetType", + "@type": "xsd:string" + }, + "targetUrl": { + "@id": "https://schema.org/targetUrl", + "@type": "xsd:anyURI" + } + } + }, + "Criteria": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#Criteria" + }, + "EndorsementCredential": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#EndorsementCredential" + }, + "EndorsementSubject": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#EndorsementSubject", + "@context": { + "endorsementComment": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#endorsementComment", + "@type": "xsd:string" + } + } + }, + "Evidence": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#Evidence", + "@context": { + "audience": { + "@id": "https://schema.org/audience", + "@type": "xsd:string" + }, + "genre": { + "@id": "https://schema.org/genre", + "@type": "xsd:string" + } + } + }, + "GeoCoordinates": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#GeoCoordinates", + "@context": { + "latitude": { + "@id": "https://schema.org/latitude", + "@type": "xsd:string" + }, + "longitude": { + "@id": "https://schema.org/longitude", + "@type": "xsd:string" + } + } + }, + "IdentifierEntry": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#IdentifierEntry", + "@context": { + "identifier": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#identifier", + "@type": "xsd:string" + }, + "identifierType": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#identifierType", + "@type": "xsd:string" + } + } + }, + "IdentityObject": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#IdentityObject", + "@context": { + "hashed": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#hashed", + "@type": "xsd:boolean" + }, + "identityHash": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#identityHash", + "@type": "xsd:string" + }, + "identityType": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#identityType", + "@type": "xsd:string" + }, + "salt": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#salt", + "@type": "xsd:string" + } + } + }, + "Image": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#Image", + "@context": { + "caption": { + "@id": "https://schema.org/caption", + "@type": "xsd:string" + } + } + }, + "Profile": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#Profile", + "@context": { + "additionalName": { + "@id": "https://schema.org/additionalName", + "@type": "xsd:string" + }, + "address": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#Address" + }, + "dateOfBirth": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#dateOfBirth", + "@type": "xsd:date" + }, + "email": { + "@id": "https://schema.org/email", + "@type": "xsd:string" + }, + "familyName": { + "@id": "https://schema.org/familyName", + "@type": "xsd:string" + }, + "familyNamePrefix": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#familyNamePrefix", + "@type": "xsd:string" + }, + "givenName": { + "@id": "https://schema.org/givenName", + "@type": "xsd:string" + }, + "honorificPrefix": { + "@id": "https://schema.org/honorificPrefix", + "@type": "xsd:string" + }, + "honorificSuffix": { + "@id": "https://schema.org/honorificSuffix", + "@type": "xsd:string" + }, + "otherIdentifier": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#otherIdentifier", + "@type": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#IdentifierEntry", + "@container": "@set" + }, + "parentOrg": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#parentOrg", + "@type": "xsd:string" + }, + "patronymicName": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#patronymicName", + "@type": "xsd:string" + }, + "phone": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#PhoneNumber", + "@type": "xsd:string" + }, + "official": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#official", + "@type": "xsd:string" + } + } + }, + "Related": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#Related", + "@context": { + "version": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#version", + "@type": "xsd:string" + } + } + }, + "Result": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#Result", + "@context": { + "achievedLevel": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#achievedLevel", + "@type": "xsd:anyURI" + }, + "resultDescription": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#resultDescription", + "@type": "xsd:anyURI" + }, + "status": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#status", + "@type": "xsd:string" + }, + "value": { + "@id": "https://schema.org/value", + "@type": "xsd:string" + } + } + }, + "ResultDescription": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#ResultDescription", + "@context": { + "allowedValue": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#allowedValue", + "@type": "xsd:string", + "@container": "@set" + }, + "requiredLevel": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#requiredLevel", + "@type": "xsd:anyURI" + }, + "requiredValue": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#requiredValue", + "@type": "xsd:string" + }, + "resultType": { + "@id":"https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#resultType", + "@type": "xsd:string" + }, + "rubricCriterionLevel": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#rubricCriterionLevel", + "@type": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#RubricCriterionLevel", + "@container": "@set" + }, + "valueMax": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#valueMax", + "@type": "xsd:string" + }, + "valueMin": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#valueMin", + "@type": "xsd:string" + } + } + }, + "RubricCriterionLevel": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#RubricCriterionLevel", + "@context": { + "level": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#level", + "@type": "xsd:string" + }, + "points": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#points", + "@type": "xsd:string" + } + } + }, + "alignment": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#alignment", + "@type": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#Alignment", + "@container": "@set" + }, + "description": { + "@id": "https://schema.org/description", + "@type": "xsd:string" + }, + "endorsement": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#endorsement", + "@type": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#EndorsementCredential", + "@container": "@set" + }, + "image": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#image", + "@type": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#Image" + }, + "name": { + "@id": "https://schema.org/name", + "@type": "xsd:string" + }, + "narrative": { + "@id": "https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#narrative", + "@type": "xsd:string" + }, + "url": { + "@id": "https://schema.org/url", + "@type": "xsd:anyURI" + } + } +} \ No newline at end of file diff --git a/inspector-vc/src/main/resources/contexts/odrl.jsonld b/inspector-vc/src/main/resources/contexts/odrl.jsonld new file mode 100644 index 0000000..1da5c13 --- /dev/null +++ b/inspector-vc/src/main/resources/contexts/odrl.jsonld @@ -0,0 +1,301 @@ +{ + "@context": { + "odrl": "http://www.w3.org/ns/odrl/2/", + "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#", + "rdfs": "http://www.w3.org/2000/01/rdf-schema#", + "owl": "http://www.w3.org/2002/07/owl#", + "skos": "http://www.w3.org/2004/02/skos/core#", + "dct": "http://purl.org/dc/terms/", + "xsd": "http://www.w3.org/2001/XMLSchema#", + "vcard": "http://www.w3.org/2006/vcard/ns#", + "foaf": "http://xmlns.com/foaf/0.1/", + "schema": "http://schema.org/", + "cc": "http://creativecommons.org/ns#", + "uid": "@id", + "type": "@type", + "Policy": "odrl:Policy", + "Rule": "odrl:Rule", + "profile": { + "@type": "@id", + "@id": "odrl:profile" + }, + "inheritFrom": { + "@type": "@id", + "@id": "odrl:inheritFrom" + }, + "ConflictTerm": "odrl:ConflictTerm", + "conflict": { + "@type": "@vocab", + "@id": "odrl:conflict" + }, + "perm": "odrl:perm", + "prohibit": "odrl:prohibit", + "invalid": "odrl:invalid", + "Agreement": "odrl:Agreement", + "Assertion": "odrl:Assertion", + "Offer": "odrl:Offer", + "Privacy": "odrl:Privacy", + "Request": "odrl:Request", + "Set": "odrl:Set", + "Ticket": "odrl:Ticket", + "Asset": "odrl:Asset", + "AssetCollection": "odrl:AssetCollection", + "relation": { + "@type": "@id", + "@id": "odrl:relation" + }, + "hasPolicy": { + "@type": "@id", + "@id": "odrl:hasPolicy" + }, + "target": { + "@type": "@id", + "@id": "odrl:target" + }, + "output": { + "@type": "@id", + "@id": "odrl:output" + }, + "partOf": { + "@type": "@id", + "@id": "odrl:partOf" + }, + "source": { + "@type": "@id", + "@id": "odrl:source" + }, + "Party": "odrl:Party", + "PartyCollection": "odrl:PartyCollection", + "function": { + "@type": "@vocab", + "@id": "odrl:function" + }, + "PartyScope": "odrl:PartyScope", + "assignee": { + "@type": "@id", + "@id": "odrl:assignee" + }, + "assigner": { + "@type": "@id", + "@id": "odrl:assigner" + }, + "assigneeOf": { + "@type": "@id", + "@id": "odrl:assigneeOf" + }, + "assignerOf": { + "@type": "@id", + "@id": "odrl:assignerOf" + }, + "attributedParty": { + "@type": "@id", + "@id": "odrl:attributedParty" + }, + "attributingParty": { + "@type": "@id", + "@id": "odrl:attributingParty" + }, + "compensatedParty": { + "@type": "@id", + "@id": "odrl:compensatedParty" + }, + "compensatingParty": { + "@type": "@id", + "@id": "odrl:compensatingParty" + }, + "consentingParty": { + "@type": "@id", + "@id": "odrl:consentingParty" + }, + "consentedParty": { + "@type": "@id", + "@id": "odrl:consentedParty" + }, + "informedParty": { + "@type": "@id", + "@id": "odrl:informedParty" + }, + "informingParty": { + "@type": "@id", + "@id": "odrl:informingParty" + }, + "trackingParty": { + "@type": "@id", + "@id": "odrl:trackingParty" + }, + "trackedParty": { + "@type": "@id", + "@id": "odrl:trackedParty" + }, + "contractingParty": { + "@type": "@id", + "@id": "odrl:contractingParty" + }, + "contractedParty": { + "@type": "@id", + "@id": "odrl:contractedParty" + }, + "Action": "odrl:Action", + "action": { + "@type": "@vocab", + "@id": "odrl:action" + }, + "includedIn": { + "@type": "@id", + "@id": "odrl:includedIn" + }, + "implies": { + "@type": "@id", + "@id": "odrl:implies" + }, + "Permission": "odrl:Permission", + "permission": { + "@type": "@id", + "@id": "odrl:permission" + }, + "Prohibition": "odrl:Prohibition", + "prohibition": { + "@type": "@id", + "@id": "odrl:prohibition" + }, + "obligation": { + "@type": "@id", + "@id": "odrl:obligation" + }, + "use": "odrl:use", + "grantUse": "odrl:grantUse", + "aggregate": "odrl:aggregate", + "annotate": "odrl:annotate", + "anonymize": "odrl:anonymize", + "archive": "odrl:archive", + "concurrentUse": "odrl:concurrentUse", + "derive": "odrl:derive", + "digitize": "odrl:digitize", + "display": "odrl:display", + "distribute": "odrl:distribute", + "execute": "odrl:execute", + "extract": "odrl:extract", + "give": "odrl:give", + "index": "odrl:index", + "install": "odrl:install", + "modify": "odrl:modify", + "move": "odrl:move", + "play": "odrl:play", + "present": "odrl:present", + "print": "odrl:print", + "read": "odrl:read", + "reproduce": "odrl:reproduce", + "sell": "odrl:sell", + "stream": "odrl:stream", + "textToSpeech": "odrl:textToSpeech", + "transfer": "odrl:transfer", + "transform": "odrl:transform", + "translate": "odrl:translate", + "Duty": "odrl:Duty", + "duty": { + "@type": "@id", + "@id": "odrl:duty" + }, + "consequence": { + "@type": "@id", + "@id": "odrl:consequence" + }, + "remedy": { + "@type": "@id", + "@id": "odrl:remedy" + }, + "acceptTracking": "odrl:acceptTracking", + "attribute": "odrl:attribute", + "compensate": "odrl:compensate", + "delete": "odrl:delete", + "ensureExclusivity": "odrl:ensureExclusivity", + "include": "odrl:include", + "inform": "odrl:inform", + "nextPolicy": "odrl:nextPolicy", + "obtainConsent": "odrl:obtainConsent", + "reviewPolicy": "odrl:reviewPolicy", + "uninstall": "odrl:uninstall", + "watermark": "odrl:watermark", + "Constraint": "odrl:Constraint", + "LogicalConstraint": "odrl:LogicalConstraint", + "constraint": { + "@type": "@id", + "@id": "odrl:constraint" + }, + "refinement": { + "@type": "@id", + "@id": "odrl:refinement" + }, + "Operator": "odrl:Operator", + "operator": { + "@type": "@vocab", + "@id": "odrl:operator" + }, + "RightOperand": "odrl:RightOperand", + "rightOperand": "odrl:rightOperand", + "rightOperandReference": { + "@type": "xsd:anyURI", + "@id": "odrl:rightOperandReference" + }, + "LeftOperand": "odrl:LeftOperand", + "leftOperand": { + "@type": "@vocab", + "@id": "odrl:leftOperand" + }, + "unit": "odrl:unit", + "dataType": { + "@type": "xsd:anyType", + "@id": "odrl:datatype" + }, + "status": "odrl:status", + "absolutePosition": "odrl:absolutePosition", + "absoluteSpatialPosition": "odrl:absoluteSpatialPosition", + "absoluteTemporalPosition": "odrl:absoluteTemporalPosition", + "absoluteSize": "odrl:absoluteSize", + "count": "odrl:count", + "dateTime": "odrl:dateTime", + "delayPeriod": "odrl:delayPeriod", + "deliveryChannel": "odrl:deliveryChannel", + "elapsedTime": "odrl:elapsedTime", + "event": "odrl:event", + "fileFormat": "odrl:fileFormat", + "industry": "odrl:industry:", + "language": "odrl:language", + "media": "odrl:media", + "meteredTime": "odrl:meteredTime", + "payAmount": "odrl:payAmount", + "percentage": "odrl:percentage", + "product": "odrl:product", + "purpose": "odrl:purpose", + "recipient": "odrl:recipient", + "relativePosition": "odrl:relativePosition", + "relativeSpatialPosition": "odrl:relativeSpatialPosition", + "relativeTemporalPosition": "odrl:relativeTemporalPosition", + "relativeSize": "odrl:relativeSize", + "resolution": "odrl:resolution", + "spatial": "odrl:spatial", + "spatialCoordinates": "odrl:spatialCoordinates", + "systemDevice": "odrl:systemDevice", + "timeInterval": "odrl:timeInterval", + "unitOfCount": "odrl:unitOfCount", + "version": "odrl:version", + "virtualLocation": "odrl:virtualLocation", + "eq": "odrl:eq", + "gt": "odrl:gt", + "gteq": "odrl:gteq", + "lt": "odrl:lt", + "lteq": "odrl:lteq", + "neq": "odrl:neg", + "isA": "odrl:isA", + "hasPart": "odrl:hasPart", + "isPartOf": "odrl:isPartOf", + "isAllOf": "odrl:isAllOf", + "isAnyOf": "odrl:isAnyOf", + "isNoneOf": "odrl:isNoneOf", + "or": "odrl:or", + "xone": "odrl:xone", + "and": "odrl:and", + "andSequence": "odrl:andSequence", + "policyUsage": "odrl:policyUsage" + } +} \ No newline at end of file diff --git a/inspector-vc/src/main/resources/contexts/security-bbs-v1.jsonld b/inspector-vc/src/main/resources/contexts/security-bbs-v1.jsonld new file mode 100644 index 0000000..2bffafe --- /dev/null +++ b/inspector-vc/src/main/resources/contexts/security-bbs-v1.jsonld @@ -0,0 +1,92 @@ +{ + "@context": { + "@version": 1.1, + "id": "@id", + "type": "@type", + "BbsBlsSignature2020": { + "@id": "https://w3id.org/security#BbsBlsSignature2020", + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + "challenge": "https://w3id.org/security#challenge", + "created": { + "@id": "http://purl.org/dc/terms/created", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "domain": "https://w3id.org/security#domain", + "proofValue": "https://w3id.org/security#proofValue", + "nonce": "https://w3id.org/security#nonce", + "proofPurpose": { + "@id": "https://w3id.org/security#proofPurpose", + "@type": "@vocab", + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + "assertionMethod": { + "@id": "https://w3id.org/security#assertionMethod", + "@type": "@id", + "@container": "@set" + }, + "authentication": { + "@id": "https://w3id.org/security#authenticationMethod", + "@type": "@id", + "@container": "@set" + } + } + }, + "verificationMethod": { + "@id": "https://w3id.org/security#verificationMethod", + "@type": "@id" + } + } + }, + "BbsBlsSignatureProof2020": { + "@id": "https://w3id.org/security#BbsBlsSignatureProof2020", + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + + "challenge": "https://w3id.org/security#challenge", + "created": { + "@id": "http://purl.org/dc/terms/created", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "domain": "https://w3id.org/security#domain", + "nonce": "https://w3id.org/security#nonce", + "proofPurpose": { + "@id": "https://w3id.org/security#proofPurpose", + "@type": "@vocab", + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + "sec": "https://w3id.org/security#", + "assertionMethod": { + "@id": "https://w3id.org/security#assertionMethod", + "@type": "@id", + "@container": "@set" + }, + "authentication": { + "@id": "https://w3id.org/security#authenticationMethod", + "@type": "@id", + "@container": "@set" + } + } + }, + "proofValue": "https://w3id.org/security#proofValue", + "verificationMethod": { + "@id": "https://w3id.org/security#verificationMethod", + "@type": "@id" + } + } + }, + "Bls12381G2Key2020": "https://w3id.org/security#Bls12381G2Key2020" + } +} diff --git a/inspector-vc/src/main/resources/contexts/security-suites-ed25519-2020-v1.jsonld b/inspector-vc/src/main/resources/contexts/security-suites-ed25519-2020-v1.jsonld new file mode 100644 index 0000000..8500805 --- /dev/null +++ b/inspector-vc/src/main/resources/contexts/security-suites-ed25519-2020-v1.jsonld @@ -0,0 +1,93 @@ +{ + "@context": { + "id": "@id", + "type": "@type", + "@protected": true, + "proof": { + "@id": "https://w3id.org/security#proof", + "@type": "@id", + "@container": "@graph" + }, + "Ed25519VerificationKey2020": { + "@id": "https://w3id.org/security#Ed25519VerificationKey2020", + "@context": { + "@protected": true, + "id": "@id", + "type": "@type", + "controller": { + "@id": "https://w3id.org/security#controller", + "@type": "@id" + }, + "revoked": { + "@id": "https://w3id.org/security#revoked", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "publicKeyMultibase": { + "@id": "https://w3id.org/security#publicKeyMultibase", + "@type": "https://w3id.org/security#multibase" + } + } + }, + "Ed25519Signature2020": { + "@id": "https://w3id.org/security#Ed25519Signature2020", + "@context": { + "@protected": true, + "id": "@id", + "type": "@type", + "challenge": "https://w3id.org/security#challenge", + "created": { + "@id": "http://purl.org/dc/terms/created", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "domain": "https://w3id.org/security#domain", + "expires": { + "@id": "https://w3id.org/security#expiration", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "nonce": "https://w3id.org/security#nonce", + "proofPurpose": { + "@id": "https://w3id.org/security#proofPurpose", + "@type": "@vocab", + "@context": { + "@protected": true, + "id": "@id", + "type": "@type", + "assertionMethod": { + "@id": "https://w3id.org/security#assertionMethod", + "@type": "@id", + "@container": "@set" + }, + "authentication": { + "@id": "https://w3id.org/security#authenticationMethod", + "@type": "@id", + "@container": "@set" + }, + "capabilityInvocation": { + "@id": "https://w3id.org/security#capabilityInvocationMethod", + "@type": "@id", + "@container": "@set" + }, + "capabilityDelegation": { + "@id": "https://w3id.org/security#capabilityDelegationMethod", + "@type": "@id", + "@container": "@set" + }, + "keyAgreement": { + "@id": "https://w3id.org/security#keyAgreementMethod", + "@type": "@id", + "@container": "@set" + } + } + }, + "proofValue": { + "@id": "https://w3id.org/security#proofValue", + "@type": "https://w3id.org/security#multibase" + }, + "verificationMethod": { + "@id": "https://w3id.org/security#verificationMethod", + "@type": "@id" + } + } + } + } +} \ No newline at end of file diff --git a/inspector-vc/src/main/resources/contexts/security-v1.jsonld b/inspector-vc/src/main/resources/contexts/security-v1.jsonld new file mode 100644 index 0000000..7529505 --- /dev/null +++ b/inspector-vc/src/main/resources/contexts/security-v1.jsonld @@ -0,0 +1,50 @@ +{ + "@context": { + "id": "@id", + "type": "@type", + + "dc": "http://purl.org/dc/terms/", + "sec": "https://w3id.org/security#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + + "EcdsaKoblitzSignature2016": "sec:EcdsaKoblitzSignature2016", + "Ed25519Signature2018": "sec:Ed25519Signature2018", + "EncryptedMessage": "sec:EncryptedMessage", + "GraphSignature2012": "sec:GraphSignature2012", + "LinkedDataSignature2015": "sec:LinkedDataSignature2015", + "LinkedDataSignature2016": "sec:LinkedDataSignature2016", + "CryptographicKey": "sec:Key", + + "authenticationTag": "sec:authenticationTag", + "canonicalizationAlgorithm": "sec:canonicalizationAlgorithm", + "cipherAlgorithm": "sec:cipherAlgorithm", + "cipherData": "sec:cipherData", + "cipherKey": "sec:cipherKey", + "created": {"@id": "dc:created", "@type": "xsd:dateTime"}, + "creator": {"@id": "dc:creator", "@type": "@id"}, + "digestAlgorithm": "sec:digestAlgorithm", + "digestValue": "sec:digestValue", + "domain": "sec:domain", + "encryptionKey": "sec:encryptionKey", + "expiration": {"@id": "sec:expiration", "@type": "xsd:dateTime"}, + "expires": {"@id": "sec:expiration", "@type": "xsd:dateTime"}, + "initializationVector": "sec:initializationVector", + "iterationCount": "sec:iterationCount", + "nonce": "sec:nonce", + "normalizationAlgorithm": "sec:normalizationAlgorithm", + "owner": {"@id": "sec:owner", "@type": "@id"}, + "password": "sec:password", + "privateKey": {"@id": "sec:privateKey", "@type": "@id"}, + "privateKeyPem": "sec:privateKeyPem", + "publicKey": {"@id": "sec:publicKey", "@type": "@id"}, + "publicKeyBase58": "sec:publicKeyBase58", + "publicKeyPem": "sec:publicKeyPem", + "publicKeyWif": "sec:publicKeyWif", + "publicKeyService": {"@id": "sec:publicKeyService", "@type": "@id"}, + "revoked": {"@id": "sec:revoked", "@type": "xsd:dateTime"}, + "salt": "sec:salt", + "signature": "sec:signature", + "signatureAlgorithm": "sec:signingAlgorithm", + "signatureValue": "sec:signatureValue" + } +} diff --git a/inspector-vc/src/main/resources/contexts/security-v2.jsonld b/inspector-vc/src/main/resources/contexts/security-v2.jsonld new file mode 100644 index 0000000..5f43a0c --- /dev/null +++ b/inspector-vc/src/main/resources/contexts/security-v2.jsonld @@ -0,0 +1,59 @@ +{ + "@context": [{ + "@version": 1.1 + }, "https://w3id.org/security/v1", { + "AesKeyWrappingKey2019": "sec:AesKeyWrappingKey2019", + "DeleteKeyOperation": "sec:DeleteKeyOperation", + "DeriveSecretOperation": "sec:DeriveSecretOperation", + "EcdsaSecp256k1Signature2019": "sec:EcdsaSecp256k1Signature2019", + "EcdsaSecp256r1Signature2019": "sec:EcdsaSecp256r1Signature2019", + "EcdsaSecp256k1VerificationKey2019": "sec:EcdsaSecp256k1VerificationKey2019", + "EcdsaSecp256r1VerificationKey2019": "sec:EcdsaSecp256r1VerificationKey2019", + "Ed25519Signature2018": "sec:Ed25519Signature2018", + "Ed25519VerificationKey2018": "sec:Ed25519VerificationKey2018", + "EquihashProof2018": "sec:EquihashProof2018", + "ExportKeyOperation": "sec:ExportKeyOperation", + "GenerateKeyOperation": "sec:GenerateKeyOperation", + "KmsOperation": "sec:KmsOperation", + "RevokeKeyOperation": "sec:RevokeKeyOperation", + "RsaSignature2018": "sec:RsaSignature2018", + "RsaVerificationKey2018": "sec:RsaVerificationKey2018", + "Sha256HmacKey2019": "sec:Sha256HmacKey2019", + "SignOperation": "sec:SignOperation", + "UnwrapKeyOperation": "sec:UnwrapKeyOperation", + "VerifyOperation": "sec:VerifyOperation", + "WrapKeyOperation": "sec:WrapKeyOperation", + "X25519KeyAgreementKey2019": "sec:X25519KeyAgreementKey2019", + + "allowedAction": "sec:allowedAction", + "assertionMethod": {"@id": "sec:assertionMethod", "@type": "@id", "@container": "@set"}, + "authentication": {"@id": "sec:authenticationMethod", "@type": "@id", "@container": "@set"}, + "capability": {"@id": "sec:capability", "@type": "@id"}, + "capabilityAction": "sec:capabilityAction", + "capabilityChain": {"@id": "sec:capabilityChain", "@type": "@id", "@container": "@list"}, + "capabilityDelegation": {"@id": "sec:capabilityDelegationMethod", "@type": "@id", "@container": "@set"}, + "capabilityInvocation": {"@id": "sec:capabilityInvocationMethod", "@type": "@id", "@container": "@set"}, + "caveat": {"@id": "sec:caveat", "@type": "@id", "@container": "@set"}, + "challenge": "sec:challenge", + "ciphertext": "sec:ciphertext", + "controller": {"@id": "sec:controller", "@type": "@id"}, + "delegator": {"@id": "sec:delegator", "@type": "@id"}, + "equihashParameterK": {"@id": "sec:equihashParameterK", "@type": "xsd:integer"}, + "equihashParameterN": {"@id": "sec:equihashParameterN", "@type": "xsd:integer"}, + "invocationTarget": {"@id": "sec:invocationTarget", "@type": "@id"}, + "invoker": {"@id": "sec:invoker", "@type": "@id"}, + "jws": "sec:jws", + "keyAgreement": {"@id": "sec:keyAgreementMethod", "@type": "@id", "@container": "@set"}, + "kmsModule": {"@id": "sec:kmsModule"}, + "parentCapability": {"@id": "sec:parentCapability", "@type": "@id"}, + "plaintext": "sec:plaintext", + "proof": {"@id": "sec:proof", "@type": "@id", "@container": "@graph"}, + "proofPurpose": {"@id": "sec:proofPurpose", "@type": "@vocab"}, + "proofValue": "sec:proofValue", + "referenceId": "sec:referenceId", + "unwrappedKey": "sec:unwrappedKey", + "verificationMethod": {"@id": "sec:verificationMethod", "@type": "@id"}, + "verifyData": "sec:verifyData", + "wrappedKey": "sec:wrappedKey" + }] +} diff --git a/inspector-vc/src/main/resources/contexts/security-v3-unstable.jsonld b/inspector-vc/src/main/resources/contexts/security-v3-unstable.jsonld new file mode 100644 index 0000000..9c76d1a --- /dev/null +++ b/inspector-vc/src/main/resources/contexts/security-v3-unstable.jsonld @@ -0,0 +1,710 @@ +{ + "@context": [{ + "@version": 1.1, + "id": "@id", + "type": "@type", + "@protected": true, + "JsonWebKey2020": { + "@id": "https://w3id.org/security#JsonWebKey2020" + }, + "JsonWebSignature2020": { + "@id": "https://w3id.org/security#JsonWebSignature2020", + "@context": { + "@version": 1.1, + "id": "@id", + "type": "@type", + "@protected": true, + "challenge": "https://w3id.org/security#challenge", + "created": { + "@id": "http://purl.org/dc/terms/created", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "domain": "https://w3id.org/security#domain", + "expires": { + "@id": "https://w3id.org/security#expiration", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "jws": "https://w3id.org/security#jws", + "nonce": "https://w3id.org/security#nonce", + "proofPurpose": { + "@id": "https://w3id.org/security#proofPurpose", + "@type": "@vocab", + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + "assertionMethod": { + "@id": "https://w3id.org/security#assertionMethod", + "@type": "@id", + "@container": "@set" + }, + "authentication": { + "@id": "https://w3id.org/security#authenticationMethod", + "@type": "@id", + "@container": "@set" + }, + "capabilityInvocation": { + "@id": "https://w3id.org/security#capabilityInvocationMethod", + "@type": "@id", + "@container": "@set" + }, + "capabilityDelegation": { + "@id": "https://w3id.org/security#capabilityDelegationMethod", + "@type": "@id", + "@container": "@set" + }, + "keyAgreement": { + "@id": "https://w3id.org/security#keyAgreementMethod", + "@type": "@id", + "@container": "@set" + } + } + }, + "verificationMethod": { + "@id": "https://w3id.org/security#verificationMethod", + "@type": "@id" + } + } + }, + "Ed25519VerificationKey2020": { + "@id": "https://w3id.org/security#Ed25519VerificationKey2020" + }, + "Ed25519Signature2020": { + "@id": "https://w3id.org/security#Ed25519Signature2020", + "@context": { + "@protected": true, + "id": "@id", + "type": "@type", + "challenge": "https://w3id.org/security#challenge", + "created": { + "@id": "http://purl.org/dc/terms/created", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "domain": "https://w3id.org/security#domain", + "expires": { + "@id": "https://w3id.org/security#expiration", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "nonce": "https://w3id.org/security#nonce", + "proofPurpose": { + "@id": "https://w3id.org/security#proofPurpose", + "@type": "@vocab", + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + "assertionMethod": { + "@id": "https://w3id.org/security#assertionMethod", + "@type": "@id", + "@container": "@set" + }, + "authentication": { + "@id": "https://w3id.org/security#authenticationMethod", + "@type": "@id", + "@container": "@set" + }, + "capabilityInvocation": { + "@id": "https://w3id.org/security#capabilityInvocationMethod", + "@type": "@id", + "@container": "@set" + }, + "capabilityDelegation": { + "@id": "https://w3id.org/security#capabilityDelegationMethod", + "@type": "@id", + "@container": "@set" + }, + "keyAgreement": { + "@id": "https://w3id.org/security#keyAgreementMethod", + "@type": "@id", + "@container": "@set" + } + } + }, + "proofValue": { + "@id": "https://w3id.org/security#proofValue" + }, + "verificationMethod": { + "@id": "https://w3id.org/security#verificationMethod", + "@type": "@id" + } + } + }, + "publicKeyJwk": { + "@id": "https://w3id.org/security#publicKeyJwk", + "@type": "@json" + }, + "ethereumAddress": { + "@id": "https://w3id.org/security#ethereumAddress" + }, + "publicKeyHex": { + "@id": "https://w3id.org/security#publicKeyHex" + }, + "blockchainAccountId": { + "@id": "https://w3id.org/security#blockchainAccountId" + }, + "MerkleProof2019": { + "@id": "https://w3id.org/security#MerkleProof2019" + }, + "Bls12381G1Key2020": { + "@id": "https://w3id.org/security#Bls12381G1Key2020" + }, + "Bls12381G2Key2020": { + "@id": "https://w3id.org/security#Bls12381G2Key2020" + }, + "BbsBlsSignature2020": { + "@id": "https://w3id.org/security#BbsBlsSignature2020", + "@context": { + "@protected": true, + "id": "@id", + "type": "@type", + "challenge": "https://w3id.org/security#challenge", + "created": { + "@id": "http://purl.org/dc/terms/created", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "domain": "https://w3id.org/security#domain", + "nonce": "https://w3id.org/security#nonce", + "proofPurpose": { + "@id": "https://w3id.org/security#proofPurpose", + "@type": "@vocab", + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + "assertionMethod": { + "@id": "https://w3id.org/security#assertionMethod", + "@type": "@id", + "@container": "@set" + }, + "authentication": { + "@id": "https://w3id.org/security#authenticationMethod", + "@type": "@id", + "@container": "@set" + }, + "capabilityInvocation": { + "@id": "https://w3id.org/security#capabilityInvocationMethod", + "@type": "@id", + "@container": "@set" + }, + "capabilityDelegation": { + "@id": "https://w3id.org/security#capabilityDelegationMethod", + "@type": "@id", + "@container": "@set" + }, + "keyAgreement": { + "@id": "https://w3id.org/security#keyAgreementMethod", + "@type": "@id", + "@container": "@set" + } + } + }, + "proofValue": "https://w3id.org/security#proofValue", + "verificationMethod": { + "@id": "https://w3id.org/security#verificationMethod", + "@type": "@id" + } + } + }, + "BbsBlsSignatureProof2020": { + "@id": "https://w3id.org/security#BbsBlsSignatureProof2020", + "@context": { + "@protected": true, + "id": "@id", + "type": "@type", + "challenge": "https://w3id.org/security#challenge", + "created": { + "@id": "http://purl.org/dc/terms/created", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "domain": "https://w3id.org/security#domain", + "nonce": "https://w3id.org/security#nonce", + "proofPurpose": { + "@id": "https://w3id.org/security#proofPurpose", + "@type": "@vocab", + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + "assertionMethod": { + "@id": "https://w3id.org/security#assertionMethod", + "@type": "@id", + "@container": "@set" + }, + "authentication": { + "@id": "https://w3id.org/security#authenticationMethod", + "@type": "@id", + "@container": "@set" + }, + "capabilityInvocation": { + "@id": "https://w3id.org/security#capabilityInvocationMethod", + "@type": "@id", + "@container": "@set" + }, + "capabilityDelegation": { + "@id": "https://w3id.org/security#capabilityDelegationMethod", + "@type": "@id", + "@container": "@set" + }, + "keyAgreement": { + "@id": "https://w3id.org/security#keyAgreementMethod", + "@type": "@id", + "@container": "@set" + } + } + }, + "proofValue": "https://w3id.org/security#proofValue", + "verificationMethod": { + "@id": "https://w3id.org/security#verificationMethod", + "@type": "@id" + } + } + }, + + "EcdsaKoblitzSignature2016": "https://w3id.org/security#EcdsaKoblitzSignature2016", + "Ed25519Signature2018": { + "@id": "https://w3id.org/security#Ed25519Signature2018", + "@context": { + "@protected": true, + + "id": "@id", + "type": "@type", + + "challenge": "https://w3id.org/security#challenge", + "created": { + "@id": "http://purl.org/dc/terms/created", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "domain": "https://w3id.org/security#domain", + "expires": { + "@id": "https://w3id.org/security#expiration", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "jws": "https://w3id.org/security#jws", + "nonce": "https://w3id.org/security#nonce", + "proofPurpose": { + "@id": "https://w3id.org/security#proofPurpose", + "@type": "@vocab", + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + "assertionMethod": { + "@id": "https://w3id.org/security#assertionMethod", + "@type": "@id", + "@container": "@set" + }, + "authentication": { + "@id": "https://w3id.org/security#authenticationMethod", + "@type": "@id", + "@container": "@set" + }, + "capabilityInvocation": { + "@id": "https://w3id.org/security#capabilityInvocationMethod", + "@type": "@id", + "@container": "@set" + }, + "capabilityDelegation": { + "@id": "https://w3id.org/security#capabilityDelegationMethod", + "@type": "@id", + "@container": "@set" + }, + "keyAgreement": { + "@id": "https://w3id.org/security#keyAgreementMethod", + "@type": "@id", + "@container": "@set" + } + } + }, + "proofValue": "https://w3id.org/security#proofValue", + "verificationMethod": { + "@id": "https://w3id.org/security#verificationMethod", + "@type": "@id" + } + } + }, + "EncryptedMessage": "https://w3id.org/security#EncryptedMessage", + "GraphSignature2012": "https://w3id.org/security#GraphSignature2012", + "LinkedDataSignature2015": "https://w3id.org/security#LinkedDataSignature2015", + "LinkedDataSignature2016": "https://w3id.org/security#LinkedDataSignature2016", + "CryptographicKey": "https://w3id.org/security#Key", + "authenticationTag": "https://w3id.org/security#authenticationTag", + "canonicalizationAlgorithm": "https://w3id.org/security#canonicalizationAlgorithm", + "cipherAlgorithm": "https://w3id.org/security#cipherAlgorithm", + "cipherData": "https://w3id.org/security#cipherData", + "cipherKey": "https://w3id.org/security#cipherKey", + "created": { + "@id": "http://purl.org/dc/terms/created", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "creator": { + "@id": "http://purl.org/dc/terms/creator", + "@type": "@id" + }, + "digestAlgorithm": "https://w3id.org/security#digestAlgorithm", + "digestValue": "https://w3id.org/security#digestValue", + "domain": "https://w3id.org/security#domain", + "encryptionKey": "https://w3id.org/security#encryptionKey", + "expiration": { + "@id": "https://w3id.org/security#expiration", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "expires": { + "@id": "https://w3id.org/security#expiration", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "initializationVector": "https://w3id.org/security#initializationVector", + "iterationCount": "https://w3id.org/security#iterationCount", + "nonce": "https://w3id.org/security#nonce", + "normalizationAlgorithm": "https://w3id.org/security#normalizationAlgorithm", + "owner": "https://w3id.org/security#owner", + "password": "https://w3id.org/security#password", + "privateKey": "https://w3id.org/security#privateKey", + "privateKeyPem": "https://w3id.org/security#privateKeyPem", + "publicKey": "https://w3id.org/security#publicKey", + "publicKeyBase58": "https://w3id.org/security#publicKeyBase58", + "publicKeyPem": "https://w3id.org/security#publicKeyPem", + "publicKeyWif": "https://w3id.org/security#publicKeyWif", + "publicKeyService": "https://w3id.org/security#publicKeyService", + "revoked": { + "@id": "https://w3id.org/security#revoked", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "salt": "https://w3id.org/security#salt", + "signature": "https://w3id.org/security#signature", + "signatureAlgorithm": "https://w3id.org/security#signingAlgorithm", + "signatureValue": "https://w3id.org/security#signatureValue", + "proofValue": "https://w3id.org/security#proofValue", + + "AesKeyWrappingKey2019": "https://w3id.org/security#AesKeyWrappingKey2019", + "DeleteKeyOperation": "https://w3id.org/security#DeleteKeyOperation", + "DeriveSecretOperation": "https://w3id.org/security#DeriveSecretOperation", + "EcdsaSecp256k1Signature2019": { + "@id": "https://w3id.org/security#EcdsaSecp256k1Signature2019", + "@context": { + "@protected": true, + + "id": "@id", + "type": "@type", + + "challenge": "https://w3id.org/security#challenge", + "created": { + "@id": "http://purl.org/dc/terms/created", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "domain": "https://w3id.org/security#domain", + "expires": { + "@id": "https://w3id.org/security#expiration", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "jws": "https://w3id.org/security#jws", + "nonce": "https://w3id.org/security#nonce", + "proofPurpose": { + "@id": "https://w3id.org/security#proofPurpose", + "@type": "@vocab", + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + "assertionMethod": { + "@id": "https://w3id.org/security#assertionMethod", + "@type": "@id", + "@container": "@set" + }, + "authentication": { + "@id": "https://w3id.org/security#authenticationMethod", + "@type": "@id", + "@container": "@set" + }, + "capabilityInvocation": { + "@id": "https://w3id.org/security#capabilityInvocationMethod", + "@type": "@id", + "@container": "@set" + }, + "capabilityDelegation": { + "@id": "https://w3id.org/security#capabilityDelegationMethod", + "@type": "@id", + "@container": "@set" + }, + "keyAgreement": { + "@id": "https://w3id.org/security#keyAgreementMethod", + "@type": "@id", + "@container": "@set" + } + } + }, + "proofValue": "https://w3id.org/security#proofValue", + "verificationMethod": { + "@id": "https://w3id.org/security#verificationMethod", + "@type": "@id" + } + } + }, + "EcdsaSecp256r1Signature2019": { + "@id": "https://w3id.org/security#EcdsaSecp256r1Signature2019", + "@context": { + "@protected": true, + + "id": "@id", + "type": "@type", + + "challenge": "https://w3id.org/security#challenge", + "created": { + "@id": "http://purl.org/dc/terms/created", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "domain": "https://w3id.org/security#domain", + "expires": { + "@id": "https://w3id.org/security#expiration", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "jws": "https://w3id.org/security#jws", + "nonce": "https://w3id.org/security#nonce", + "proofPurpose": { + "@id": "https://w3id.org/security#proofPurpose", + "@type": "@vocab", + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + "assertionMethod": { + "@id": "https://w3id.org/security#assertionMethod", + "@type": "@id", + "@container": "@set" + }, + "authentication": { + "@id": "https://w3id.org/security#authenticationMethod", + "@type": "@id", + "@container": "@set" + }, + "capabilityInvocation": { + "@id": "https://w3id.org/security#capabilityInvocationMethod", + "@type": "@id", + "@container": "@set" + }, + "capabilityDelegation": { + "@id": "https://w3id.org/security#capabilityDelegationMethod", + "@type": "@id", + "@container": "@set" + }, + "keyAgreement": { + "@id": "https://w3id.org/security#keyAgreementMethod", + "@type": "@id", + "@container": "@set" + } + } + }, + "proofValue": "https://w3id.org/security#proofValue", + "verificationMethod": { + "@id": "https://w3id.org/security#verificationMethod", + "@type": "@id" + } + } + }, + "EcdsaSecp256k1VerificationKey2019": "https://w3id.org/security#EcdsaSecp256k1VerificationKey2019", + "EcdsaSecp256r1VerificationKey2019": "https://w3id.org/security#EcdsaSecp256r1VerificationKey2019", + "Ed25519VerificationKey2018": "https://w3id.org/security#Ed25519VerificationKey2018", + "EquihashProof2018": "https://w3id.org/security#EquihashProof2018", + "ExportKeyOperation": "https://w3id.org/security#ExportKeyOperation", + "GenerateKeyOperation": "https://w3id.org/security#GenerateKeyOperation", + "KmsOperation": "https://w3id.org/security#KmsOperation", + "RevokeKeyOperation": "https://w3id.org/security#RevokeKeyOperation", + "RsaSignature2018": { + "@id": "https://w3id.org/security#RsaSignature2018", + "@context": { + "@protected": true, + + "challenge": "https://w3id.org/security#challenge", + "created": { + "@id": "http://purl.org/dc/terms/created", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "domain": "https://w3id.org/security#domain", + "expires": { + "@id": "https://w3id.org/security#expiration", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "jws": "https://w3id.org/security#jws", + "nonce": "https://w3id.org/security#nonce", + "proofPurpose": { + "@id": "https://w3id.org/security#proofPurpose", + "@type": "@vocab", + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + "assertionMethod": { + "@id": "https://w3id.org/security#assertionMethod", + "@type": "@id", + "@container": "@set" + }, + "authentication": { + "@id": "https://w3id.org/security#authenticationMethod", + "@type": "@id", + "@container": "@set" + }, + "capabilityInvocation": { + "@id": "https://w3id.org/security#capabilityInvocationMethod", + "@type": "@id", + "@container": "@set" + }, + "capabilityDelegation": { + "@id": "https://w3id.org/security#capabilityDelegationMethod", + "@type": "@id", + "@container": "@set" + }, + "keyAgreement": { + "@id": "https://w3id.org/security#keyAgreementMethod", + "@type": "@id", + "@container": "@set" + } + } + }, + "proofValue": "https://w3id.org/security#proofValue", + "verificationMethod": { + "@id": "https://w3id.org/security#verificationMethod", + "@type": "@id" + } + } + }, + "RsaVerificationKey2018": "https://w3id.org/security#RsaVerificationKey2018", + "Sha256HmacKey2019": "https://w3id.org/security#Sha256HmacKey2019", + "SignOperation": "https://w3id.org/security#SignOperation", + "UnwrapKeyOperation": "https://w3id.org/security#UnwrapKeyOperation", + "VerifyOperation": "https://w3id.org/security#VerifyOperation", + "WrapKeyOperation": "https://w3id.org/security#WrapKeyOperation", + "X25519KeyAgreementKey2019": "https://w3id.org/security#X25519KeyAgreementKey2019", + + "allowedAction": "https://w3id.org/security#allowedAction", + "assertionMethod": { + "@id": "https://w3id.org/security#assertionMethod", + "@type": "@id", + "@container": "@set" + }, + "authentication": { + "@id": "https://w3id.org/security#authenticationMethod", + "@type": "@id", + "@container": "@set" + }, + "capability": { + "@id": "https://w3id.org/security#capability", + "@type": "@id" + }, + "capabilityAction": "https://w3id.org/security#capabilityAction", + "capabilityChain": { + "@id": "https://w3id.org/security#capabilityChain", + "@type": "@id", + "@container": "@list" + }, + "capabilityDelegation": { + "@id": "https://w3id.org/security#capabilityDelegationMethod", + "@type": "@id", + "@container": "@set" + }, + "capabilityInvocation": { + "@id": "https://w3id.org/security#capabilityInvocationMethod", + "@type": "@id", + "@container": "@set" + }, + "caveat": { + "@id": "https://w3id.org/security#caveat", + "@type": "@id", + "@container": "@set" + }, + "challenge": "https://w3id.org/security#challenge", + "ciphertext": "https://w3id.org/security#ciphertext", + "controller": { + "@id": "https://w3id.org/security#controller", + "@type": "@id" + }, + "delegator": { + "@id": "https://w3id.org/security#delegator", + "@type": "@id" + }, + "equihashParameterK": { + "@id": "https://w3id.org/security#equihashParameterK", + "@type": "http://www.w3.org/2001/XMLSchema#:integer" + }, + "equihashParameterN": { + "@id": "https://w3id.org/security#equihashParameterN", + "@type": "http://www.w3.org/2001/XMLSchema#:integer" + }, + "invocationTarget": { + "@id": "https://w3id.org/security#invocationTarget", + "@type": "@id" + }, + "invoker": { + "@id": "https://w3id.org/security#invoker", + "@type": "@id" + }, + "jws": "https://w3id.org/security#jws", + "keyAgreement": { + "@id": "https://w3id.org/security#keyAgreementMethod", + "@type": "@id", + "@container": "@set" + }, + "kmsModule": { + "@id": "https://w3id.org/security#kmsModule" + }, + "parentCapability": { + "@id": "https://w3id.org/security#parentCapability", + "@type": "@id" + }, + "plaintext": "https://w3id.org/security#plaintext", + "proof": { + "@id": "https://w3id.org/security#proof", + "@type": "@id", + "@container": "@graph" + }, + "proofPurpose": { + "@id": "https://w3id.org/security#proofPurpose", + "@type": "@vocab", + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + "assertionMethod": { + "@id": "https://w3id.org/security#assertionMethod", + "@type": "@id", + "@container": "@set" + }, + "authentication": { + "@id": "https://w3id.org/security#authenticationMethod", + "@type": "@id", + "@container": "@set" + }, + "capabilityInvocation": { + "@id": "https://w3id.org/security#capabilityInvocationMethod", + "@type": "@id", + "@container": "@set" + }, + "capabilityDelegation": { + "@id": "https://w3id.org/security#capabilityDelegationMethod", + "@type": "@id", + "@container": "@set" + }, + "keyAgreement": { + "@id": "https://w3id.org/security#keyAgreementMethod", + "@type": "@id", + "@container": "@set" + } + } + }, + "referenceId": "https://w3id.org/security#referenceId", + "unwrappedKey": "https://w3id.org/security#unwrappedKey", + "verificationMethod": { + "@id": "https://w3id.org/security#verificationMethod", + "@type": "@id" + }, + "verifyData": "https://w3id.org/security#verifyData", + "wrappedKey": "https://w3id.org/security#wrappedKey" + }] +} \ No newline at end of file diff --git a/inspector-vc/src/main/resources/contexts/suites-ed25519-2018.jsonld b/inspector-vc/src/main/resources/contexts/suites-ed25519-2018.jsonld new file mode 100644 index 0000000..6533c28 --- /dev/null +++ b/inspector-vc/src/main/resources/contexts/suites-ed25519-2018.jsonld @@ -0,0 +1,91 @@ +{ + "@context": { + "id": "@id", + "type": "@type", + "@protected": true, + "proof": { + "@id": "https://w3id.org/security#proof", + "@type": "@id", + "@container": "@graph" + }, + "Ed25519VerificationKey2018": { + "@id": "https://w3id.org/security#Ed25519VerificationKey2018", + "@context": { + "@protected": true, + "id": "@id", + "type": "@type", + "controller": { + "@id": "https://w3id.org/security#controller", + "@type": "@id" + }, + "revoked": { + "@id": "https://w3id.org/security#revoked", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "publicKeyBase58": { + "@id": "https://w3id.org/security#publicKeyBase58" + } + } + }, + "Ed25519Signature2018": { + "@id": "https://w3id.org/security#Ed25519Signature2018", + "@context": { + "@protected": true, + "id": "@id", + "type": "@type", + "challenge": "https://w3id.org/security#challenge", + "created": { + "@id": "http://purl.org/dc/terms/created", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "domain": "https://w3id.org/security#domain", + "expires": { + "@id": "https://w3id.org/security#expiration", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "nonce": "https://w3id.org/security#nonce", + "proofPurpose": { + "@id": "https://w3id.org/security#proofPurpose", + "@type": "@vocab", + "@context": { + "@protected": true, + "id": "@id", + "type": "@type", + "assertionMethod": { + "@id": "https://w3id.org/security#assertionMethod", + "@type": "@id", + "@container": "@set" + }, + "authentication": { + "@id": "https://w3id.org/security#authenticationMethod", + "@type": "@id", + "@container": "@set" + }, + "capabilityInvocation": { + "@id": "https://w3id.org/security#capabilityInvocationMethod", + "@type": "@id", + "@container": "@set" + }, + "capabilityDelegation": { + "@id": "https://w3id.org/security#capabilityDelegationMethod", + "@type": "@id", + "@container": "@set" + }, + "keyAgreement": { + "@id": "https://w3id.org/security#keyAgreementMethod", + "@type": "@id", + "@container": "@set" + } + } + }, + "jws": { + "@id": "https://w3id.org/security#jws" + }, + "verificationMethod": { + "@id": "https://w3id.org/security#verificationMethod", + "@type": "@id" + } + } + } + } +} diff --git a/inspector-vc/src/main/resources/contexts/suites-ed25519-2020.jsonld b/inspector-vc/src/main/resources/contexts/suites-ed25519-2020.jsonld new file mode 100644 index 0000000..b74da8c --- /dev/null +++ b/inspector-vc/src/main/resources/contexts/suites-ed25519-2020.jsonld @@ -0,0 +1,93 @@ +{ + "@context": { + "id": "@id", + "type": "@type", + "@protected": true, + "proof": { + "@id": "https://w3id.org/security#proof", + "@type": "@id", + "@container": "@graph" + }, + "Ed25519VerificationKey2020": { + "@id": "https://w3id.org/security#Ed25519VerificationKey2020", + "@context": { + "@protected": true, + "id": "@id", + "type": "@type", + "controller": { + "@id": "https://w3id.org/security#controller", + "@type": "@id" + }, + "revoked": { + "@id": "https://w3id.org/security#revoked", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "publicKeyMultibase": { + "@id": "https://w3id.org/security#publicKeyMultibase", + "@type": "https://w3id.org/security#multibase" + } + } + }, + "Ed25519Signature2020": { + "@id": "https://w3id.org/security#Ed25519Signature2020", + "@context": { + "@protected": true, + "id": "@id", + "type": "@type", + "challenge": "https://w3id.org/security#challenge", + "created": { + "@id": "http://purl.org/dc/terms/created", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "domain": "https://w3id.org/security#domain", + "expires": { + "@id": "https://w3id.org/security#expiration", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "nonce": "https://w3id.org/security#nonce", + "proofPurpose": { + "@id": "https://w3id.org/security#proofPurpose", + "@type": "@vocab", + "@context": { + "@protected": true, + "id": "@id", + "type": "@type", + "assertionMethod": { + "@id": "https://w3id.org/security#assertionMethod", + "@type": "@id", + "@container": "@set" + }, + "authentication": { + "@id": "https://w3id.org/security#authenticationMethod", + "@type": "@id", + "@container": "@set" + }, + "capabilityInvocation": { + "@id": "https://w3id.org/security#capabilityInvocationMethod", + "@type": "@id", + "@container": "@set" + }, + "capabilityDelegation": { + "@id": "https://w3id.org/security#capabilityDelegationMethod", + "@type": "@id", + "@container": "@set" + }, + "keyAgreement": { + "@id": "https://w3id.org/security#keyAgreementMethod", + "@type": "@id", + "@container": "@set" + } + } + }, + "proofValue": { + "@id": "https://w3id.org/security#proofValue", + "@type": "https://w3id.org/security#multibase" + }, + "verificationMethod": { + "@id": "https://w3id.org/security#verificationMethod", + "@type": "@id" + } + } + } + } +} diff --git a/inspector-vc/src/main/resources/contexts/suites-jws-2020.jsonld b/inspector-vc/src/main/resources/contexts/suites-jws-2020.jsonld new file mode 100644 index 0000000..17186cd --- /dev/null +++ b/inspector-vc/src/main/resources/contexts/suites-jws-2020.jsonld @@ -0,0 +1,82 @@ +{ + "@context": { + "privateKeyJwk": { + "@id": "https://w3id.org/security#privateKeyJwk", + "@type": "@json" + }, + "JsonWebKey2020": { + "@id": "https://w3id.org/security#JsonWebKey2020", + "@context": { + "@protected": true, + "id": "@id", + "type": "@type", + "publicKeyJwk": { + "@id": "https://w3id.org/security#publicKeyJwk", + "@type": "@json" + } + } + }, + "JsonWebSignature2020": { + "@id": "https://w3id.org/security#JsonWebSignature2020", + "@context": { + "@protected": true, + + "id": "@id", + "type": "@type", + + "challenge": "https://w3id.org/security#challenge", + "created": { + "@id": "http://purl.org/dc/terms/created", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "domain": "https://w3id.org/security#domain", + "expires": { + "@id": "https://w3id.org/security#expiration", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "jws": "https://w3id.org/security#jws", + "nonce": "https://w3id.org/security#nonce", + "proofPurpose": { + "@id": "https://w3id.org/security#proofPurpose", + "@type": "@vocab", + "@context": { + "@protected": true, + + "id": "@id", + "type": "@type", + + "assertionMethod": { + "@id": "https://w3id.org/security#assertionMethod", + "@type": "@id", + "@container": "@set" + }, + "authentication": { + "@id": "https://w3id.org/security#authenticationMethod", + "@type": "@id", + "@container": "@set" + }, + "capabilityInvocation": { + "@id": "https://w3id.org/security#capabilityInvocationMethod", + "@type": "@id", + "@container": "@set" + }, + "capabilityDelegation": { + "@id": "https://w3id.org/security#capabilityDelegationMethod", + "@type": "@id", + "@container": "@set" + }, + "keyAgreement": { + "@id": "https://w3id.org/security#keyAgreementMethod", + "@type": "@id", + "@container": "@set" + } + } + }, + "verificationMethod": { + "@id": "https://w3id.org/security#verificationMethod", + "@type": "@id" + } + } + } + } +} diff --git a/inspector-vc/src/main/resources/contexts/suites-secp256k1-2019.jsonld b/inspector-vc/src/main/resources/contexts/suites-secp256k1-2019.jsonld new file mode 100644 index 0000000..5a345df --- /dev/null +++ b/inspector-vc/src/main/resources/contexts/suites-secp256k1-2019.jsonld @@ -0,0 +1,102 @@ +{ + "@context": { + "id": "@id", + "type": "@type", + "@protected": true, + "proof": { + "@id": "https://w3id.org/security#proof", + "@type": "@id", + "@container": "@graph" + }, + "EcdsaSecp256k1VerificationKey2019": { + "@id": "https://w3id.org/security#EcdsaSecp256k1VerificationKey2019", + "@context": { + "@protected": true, + "id": "@id", + "type": "@type", + "controller": { + "@id": "https://w3id.org/security#controller", + "@type": "@id" + }, + "revoked": { + "@id": "https://w3id.org/security#revoked", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "blockchainAccountId": { + "@id": "https://w3id.org/security#blockchainAccountId" + }, + "publicKeyJwk": { + "@id": "https://w3id.org/security#publicKeyJwk", + "@type": "@json" + }, + "publicKeyBase58": { + "@id": "https://w3id.org/security#publicKeyBase58" + }, + "publicKeyMultibase": { + "@id": "https://w3id.org/security#publicKeyMultibase", + "@type": "https://w3id.org/security#multibase" + } + } + }, + "EcdsaSecp256k1Signature2019": { + "@id": "https://w3id.org/security#EcdsaSecp256k1Signature2019", + "@context": { + "@protected": true, + "id": "@id", + "type": "@type", + "challenge": "https://w3id.org/security#challenge", + "created": { + "@id": "http://purl.org/dc/terms/created", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "domain": "https://w3id.org/security#domain", + "expires": { + "@id": "https://w3id.org/security#expiration", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "nonce": "https://w3id.org/security#nonce", + "proofPurpose": { + "@id": "https://w3id.org/security#proofPurpose", + "@type": "@vocab", + "@context": { + "@protected": true, + "id": "@id", + "type": "@type", + "assertionMethod": { + "@id": "https://w3id.org/security#assertionMethod", + "@type": "@id", + "@container": "@set" + }, + "authentication": { + "@id": "https://w3id.org/security#authenticationMethod", + "@type": "@id", + "@container": "@set" + }, + "capabilityInvocation": { + "@id": "https://w3id.org/security#capabilityInvocationMethod", + "@type": "@id", + "@container": "@set" + }, + "capabilityDelegation": { + "@id": "https://w3id.org/security#capabilityDelegationMethod", + "@type": "@id", + "@container": "@set" + }, + "keyAgreement": { + "@id": "https://w3id.org/security#keyAgreementMethod", + "@type": "@id", + "@container": "@set" + } + } + }, + "jws": { + "@id": "https://w3id.org/security#jws" + }, + "verificationMethod": { + "@id": "https://w3id.org/security#verificationMethod", + "@type": "@id" + } + } + } + } +} diff --git a/inspector-vc/src/main/resources/contexts/suites-x25519-2019.jsonld b/inspector-vc/src/main/resources/contexts/suites-x25519-2019.jsonld new file mode 100644 index 0000000..d01bac0 --- /dev/null +++ b/inspector-vc/src/main/resources/contexts/suites-x25519-2019.jsonld @@ -0,0 +1,26 @@ +{ + "@context": { + "id": "@id", + "type": "@type", + "@protected": true, + "X25519KeyAgreementKey2019": { + "@id": "https://w3id.org/security#X25519KeyAgreementKey2019", + "@context": { + "@protected": true, + "id": "@id", + "type": "@type", + "controller": { + "@id": "https://w3id.org/security#controller", + "@type": "@id" + }, + "revoked": { + "@id": "https://w3id.org/security#revoked", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "publicKeyBase58": { + "@id": "https://w3id.org/security#publicKeyBase58" + } + } + } + } +} diff --git a/inspector-vc/src/test/java/org/oneedtech/inspect/vc/IronTests.java b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/IronTests.java new file mode 100644 index 0000000..02258fb --- /dev/null +++ b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/IronTests.java @@ -0,0 +1,88 @@ +package org.oneedtech.inspect.vc; + +import java.io.StringWriter; +import java.net.URI; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import com.apicatalog.did.key.DidKey; +import com.apicatalog.ld.signature.ed25519.Ed25519Proof2020Adapter; +import com.apicatalog.ld.signature.key.KeyPair; +import com.apicatalog.ld.signature.proof.ProofOptions; +import com.apicatalog.ld.signature.proof.VerificationMethod; +import com.apicatalog.vc.Vc; +import com.apicatalog.vc.processor.Issuer; + +import jakarta.json.Json; +import jakarta.json.JsonObject; +import jakarta.json.JsonWriter; +import jakarta.json.JsonWriterFactory; +import jakarta.json.stream.JsonGenerator; + +public class IronTests { + + @Disabled + @Test + void testOb_01() { + Assertions.assertDoesNotThrow(()->{ + + + final DidKey didKey = DidKey.from(URI.create("did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH")); + + //https://w3id.org/security#Ed25519KeyPair2020 + //https://w3id.org/security#Ed25519Signature2020 + URI unsigned = Samples.OB30.JSON.SIMPLE_JSON_NOPROOF.asURL().toURI(); + KeyPair kp = Vc.generateKeys("https://w3id.org/security#Ed25519Signature2020").get(URI.create("urn:1"), 256); + ProofOptions options = ProofOptions.create( + Ed25519Proof2020Adapter.TYPE, + //new VerificationMethod(URI.create("did:key:z6MkkUD3J14nkYzn46QeuaVSnp7dF85QJKwKvJvfsjx79aXj")), + new VerificationMethod(didKey.toUri()), + URI.create("https://w3id.org/security#assertionMethod")).created(Instant.now().truncatedTo(ChronoUnit.SECONDS)); + + Issuer issuer = Vc.sign(unsigned, kp, options); + System.err.println(pretty(issuer.getCompacted())); + JsonObject signed = issuer.getCompacted(); + JsonObject proof = signed.getJsonObject("sec:proof"); + Assertions.assertNotNull(proof); + + Vc.verify(issuer.getCompacted()).isValid(); + }); + } + + @Disabled + @Test + void testClr_01() { + Assertions.assertDoesNotThrow(()->{ + URI unsigned = Samples.CLR20.JSON.SIMPLE_JSON_NOPROOF.asURL().toURI(); + KeyPair kp = Vc.generateKeys("https://w3id.org/security#Ed25519Signature2020").get(URI.create("urn:1"), 256); + ProofOptions options = ProofOptions.create( + Ed25519Proof2020Adapter.TYPE, + new VerificationMethod(URI.create("did:key:z6MkkUD3J14nkYzn46QeuaVSnp7dF85QJKwKvJvfsjx79aXj")), + URI.create("https://w3id.org/security#assertionMethod")); + + Issuer issuer = Vc.sign(unsigned, kp, options); + JsonObject job = issuer.getCompacted().getJsonObject("sec:proof"); + + //System.err.println (pretty(issuer.getCompacted().toString())); + Assertions.assertNotNull(job); + Vc.verify(issuer.getCompacted()).isValid(); + }); + } + + String pretty(JsonObject jobj) { + Map properties = new HashMap<>(1); + properties.put(JsonGenerator.PRETTY_PRINTING, true); + StringWriter sw = new StringWriter(); + JsonWriterFactory writerFactory = Json.createWriterFactory(properties); + JsonWriter jsonWriter = writerFactory.createWriter(sw); + jsonWriter.writeObject(jobj); + jsonWriter.close(); + return sw.toString(); + } +} 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 new file mode 100644 index 0000000..0be0215 --- /dev/null +++ b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/OB30Tests.java @@ -0,0 +1,174 @@ +package org.oneedtech.inspect.vc; + +import static org.junit.jupiter.api.Assertions.*; +import static org.oneedtech.inspect.test.Assertions.*; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.oneedtech.inspect.core.Inspector.Behavior; +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.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.TypePropertyProbe; + +import com.google.common.collect.Iterables; + +public class OB30Tests { + private static OB30Inspector validator; + private static boolean verbose = true; + + @BeforeAll + static void setup() { + validator = new OB30Inspector.Builder() + .set(Behavior.TEST_INCLUDE_SUCCESS, true) + .set(Behavior.VALIDATOR_FAIL_FAST, true) + .build(); + } + + @Test + void testSimpleJsonValid() { + assertDoesNotThrow(()->{ + Report report = validator.run(Samples.OB30.JSON.SIMPLE_JSON.asFileResource()); + if(verbose) PrintHelper.print(report, true); + 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); + }); + } + + @Test + void testSimplePNGJWTValid() { + assertDoesNotThrow(()->{ + Report report = validator.run(Samples.OB30.PNG.SIMPLE_JWT_PNG.asFileResource()); + if(verbose) PrintHelper.print(report, true); + assertValid(report); + }); + } + + @Test + void testSimpleJsonSVGPlainValid() { + assertDoesNotThrow(()->{ + Report report = validator.run(Samples.OB30.SVG.SIMPLE_JSON_SVG.asFileResource()); + if(verbose) PrintHelper.print(report, true); + 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); + }); + } + + @Test + void testSimpleJsonInvalidUnknownType() { + //add a dumb value to .type and remove the ob type + assertDoesNotThrow(()->{ + Report report = validator.run(Samples.OB30.JSON.SIMPLE_JSON_UNKNOWN_TYPE.asFileResource()); + if(verbose) PrintHelper.print(report, true); + assertInvalid(report); + assertFatalCount(report, 1); + assertHasProbeID(report, TypePropertyProbe.ID, true); + }); + } + + @Test + void testSimpleJsonInvalidProofMethod() { + //add some garbage chars to proofValue + assertDoesNotThrow(()->{ + Report report = validator.run(Samples.OB30.JSON.SIMPLE_JSON_PROOF_METHOD_ERROR.asFileResource()); + if(verbose) PrintHelper.print(report, true); + assertInvalid(report); + assertErrorCount(report, 1); + assertHasProbeID(report, EmbeddedProofProbe.ID, true); + }); + } + + @Test + void testSimpleJsonInvalidProofValue() { + //add some garbage chars to proofValue + assertDoesNotThrow(()->{ + Report report = validator.run(Samples.OB30.JSON.SIMPLE_JSON_PROOF_VALUE_ERROR.asFileResource()); + if(verbose) PrintHelper.print(report, true); + assertInvalid(report); + assertErrorCount(report, 1); + assertHasProbeID(report, EmbeddedProofProbe.ID, true); + }); + } + + @Test + void testSimpleJsonExpired() { + //"expirationDate": "2020-01-20T00:00:00Z", + assertDoesNotThrow(()->{ + Report report = validator.run(Samples.OB30.JSON.SIMPLE_JSON_EXPIRED.asFileResource()); + if(verbose) PrintHelper.print(report, true); + assertInvalid(report); + assertHasProbeID(report, ExpirationProbe.ID, true); + }); + } + + @Test + 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 + assertDoesNotThrow(()->{ + Report report = validator.run(Samples.OB30.JSON.SIMPLE_JSON_ISSUER.asFileResource()); + if(verbose) PrintHelper.print(report, true); + assertInvalid(report); + assertHasProbeID(report, JsonSchemaProbe.ID, true); + }); + } + + @Disabled //TODO IssuanceVerifierProbe is not run because FATAL: InvalidSignature terminates + @Test + void testSimpleJsonNotIssued() { + //"issuanceDate": "2040-01-01T00:00:00Z", + //this breaks the proof too + assertDoesNotThrow(()->{ + Report report = validator.run(Samples.OB30.JSON.SIMPLE_JSON_ISSUED.asFileResource()); + 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 + assertDoesNotThrow(()->{ + Report report = validator.run(Samples.OB30.JSON.COMPLETE_JSON.asFileResource()); + 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); + }); + } + +} 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 new file mode 100644 index 0000000..3c10f32 --- /dev/null +++ b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/Samples.java @@ -0,0 +1,39 @@ +package org.oneedtech.inspect.vc; + +import org.oneedtech.inspect.test.Sample; + +public class Samples { + public static final class OB30 { + public static final class SVG { + public final static Sample SIMPLE_JSON_SVG = new Sample("ob30/simple-json.svg", true); + public final static Sample SIMPLE_JWT_SVG = new Sample("ob30/simple-jwt.svg", true); + } + public static final class JSON { + 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_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_PROOF_METHOD_ERROR = new Sample("ob30/simple-err-proof-method.json", false); + public final static Sample SIMPLE_JSON_PROOF_VALUE_ERROR = new Sample("ob30/simple-err-proof-value.json", false); + public final static Sample SIMPLE_JSON_EXPIRED = new Sample("ob30/simple-err-expired.json", false); + public final static Sample SIMPLE_JSON_ISSUED = new Sample("ob30/simple-err-issued.json", false); + public final static Sample SIMPLE_JSON_ISSUER = new Sample("ob30/simple-err-issuer.json", false); + public final static Sample SIMPLE_JSON_ERR_CONTEXT = new Sample("ob30/simple-err-context.json", false); + } + public static final class PNG { + public final static Sample SIMPLE_JWT_PNG = new Sample("ob30/simple-jwt.png", true); + public final static Sample SIMPLE_JSON_PNG = new Sample("ob30/simple-json.png", true); + } + public static final class JWT { + public final static Sample SIMPLE_JWT = new Sample("ob30/simple.jwt", true); + } + } + + public static final class CLR20 { + public static final class JSON { + public final static Sample SIMPLE_JSON = new Sample("clr20/simple.json", true); + public final static Sample SIMPLE_JSON_NOPROOF = new Sample("clr20/simple-noproof.json", true); + public final static Sample SIMPLE_JWT = new Sample("clr20/simple.jwt", true); + } + } +} diff --git a/inspector-vc/src/test/java/org/oneedtech/inspect/vc/credential/PayloadParserTests.java b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/credential/PayloadParserTests.java new file mode 100644 index 0000000..28b8e7c --- /dev/null +++ b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/credential/PayloadParserTests.java @@ -0,0 +1,119 @@ +package org.oneedtech.inspect.vc.credential; + +import static org.junit.jupiter.api.Assertions.*; +import static org.oneedtech.inspect.util.json.ObjectMapperCache.Config.DEFAULT; + +import java.util.Optional; + +import org.junit.jupiter.api.Test; +import org.oneedtech.inspect.core.probe.RunContext; +import org.oneedtech.inspect.core.probe.RunContext.Key; +import org.oneedtech.inspect.core.probe.json.JsonPathEvaluator; +import org.oneedtech.inspect.util.json.ObjectMapperCache; +import org.oneedtech.inspect.util.resource.Resource; +import org.oneedtech.inspect.util.resource.ResourceType; +import org.oneedtech.inspect.vc.Credential; +import org.oneedtech.inspect.vc.OB30Inspector; +import org.oneedtech.inspect.vc.Samples; +import org.oneedtech.inspect.vc.payload.PayloadParser; +import org.oneedtech.inspect.vc.payload.PayloadParserFactory; + +import com.fasterxml.jackson.databind.ObjectMapper; + +public class PayloadParserTests { + + @Test + void testSvgStringExtract() { + assertDoesNotThrow(()->{ + Resource res = Samples.OB30.SVG.SIMPLE_JSON_SVG.asFileResource(ResourceType.SVG); + PayloadParser ext = PayloadParserFactory.of(res); + assertNotNull(ext); + Credential crd = ext.parse(res, mockOB30Context(res)); + //System.out.println(crd.getJson().toPrettyString()); + assertNotNull(crd); + assertNotNull(crd.getJson()); + assertNotNull(crd.getJson().get("@context")); + }); + } + + @Test + void testSvgJwtExtract() { + assertDoesNotThrow(()->{ + Resource res = Samples.OB30.SVG.SIMPLE_JWT_SVG.asFileResource(ResourceType.SVG); + PayloadParser ext = PayloadParserFactory.of(res); + assertNotNull(ext); + Credential crd = ext.parse(res, mockOB30Context(res)); + //System.out.println(crd.getJson().toPrettyString()); + assertNotNull(crd); + assertNotNull(crd.getJson()); + assertNotNull(crd.getJson().get("@context")); + }); + } + + @Test + void testPngStringExtract() { + assertDoesNotThrow(()->{ + Resource res = Samples.OB30.PNG.SIMPLE_JSON_PNG.asFileResource(ResourceType.PNG); + PayloadParser ext = PayloadParserFactory.of(res); + assertNotNull(ext); + Credential crd = ext.parse(res, mockOB30Context(res)); + //System.out.println(crd.getJson().toPrettyString()); + assertNotNull(crd); + assertNotNull(crd.getJson()); + assertNotNull(crd.getJson().get("@context")); + }); + } + + @Test + void testPngJwtExtract() { + assertDoesNotThrow(()->{ + Resource res = Samples.OB30.PNG.SIMPLE_JWT_PNG.asFileResource(ResourceType.PNG); + PayloadParser ext = PayloadParserFactory.of(res); + assertNotNull(ext); + Credential crd = ext.parse(res, mockOB30Context(res)); + //System.out.println(crd.getJson().toPrettyString()); + assertNotNull(crd); + assertNotNull(crd.getJson()); + assertNotNull(crd.getJson().get("@context")); + }); + } + + @Test + void testJwtExtract() { + assertDoesNotThrow(()->{ + Resource res = Samples.OB30.JWT.SIMPLE_JWT.asFileResource(ResourceType.JWT); + PayloadParser ext = PayloadParserFactory.of(res); + assertNotNull(ext); + Credential crd = ext.parse(res, mockOB30Context(res)); + //System.out.println(crd.getJson().toPrettyString()); + assertNotNull(crd); + assertNotNull(crd.getJson()); + assertNotNull(crd.getJson().get("@context")); + }); + } + + @Test + void testJsonExtract() { + assertDoesNotThrow(()->{ + Resource res = Samples.OB30.JSON.SIMPLE_JSON.asFileResource(ResourceType.JSON); + PayloadParser ext = PayloadParserFactory.of(res); + assertNotNull(ext); + Credential crd = ext.parse(res, mockOB30Context(res)); + //System.out.println(crd.getJson().toPrettyString()); + assertNotNull(crd); + assertNotNull(crd.getJson()); + assertNotNull(crd.getJson().get("@context")); + }); + } + + private RunContext mockOB30Context(Resource res) { + ObjectMapper mapper = ObjectMapperCache.get(DEFAULT); + JsonPathEvaluator jsonPath = new JsonPathEvaluator(mapper); + return new RunContext.Builder() + .put(new OB30Inspector.Builder().build()) + .put(res) + .put(Key.JACKSON_OBJECTMAPPER, mapper) + .put(Key.JSONPATH_EVALUATOR, jsonPath) + .build(); + } +} diff --git a/inspector-vc/src/test/java/org/oneedtech/inspect/vc/util/CachingDocumentLoaderTests.java b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/util/CachingDocumentLoaderTests.java new file mode 100644 index 0000000..b56542f --- /dev/null +++ b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/util/CachingDocumentLoaderTests.java @@ -0,0 +1,34 @@ +package org.oneedtech.inspect.vc.util; + +import java.net.URI; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import com.apicatalog.jsonld.document.Document; +import com.apicatalog.jsonld.loader.DocumentLoader; +import com.apicatalog.jsonld.loader.DocumentLoaderOptions; + +public class CachingDocumentLoaderTests { + + @Test + void testStaticCachedDocumentBundled() { + Assertions.assertDoesNotThrow(()->{ + DocumentLoader loader = new CachingDocumentLoader(); + for(String id : CachingDocumentLoader.bundled.keySet()) { + Document doc = loader.loadDocument(new URI(id), new DocumentLoaderOptions()); + Assertions.assertNotNull(doc); + } + }); + } + + @Test + void testStaticCachedDocumentKey() { + Assertions.assertDoesNotThrow(()->{ + DocumentLoader loader = new CachingDocumentLoader(); + URI uri = new URI("https://www.w3.org/ns/did/v1"); + Document doc = loader.loadDocument(uri, new DocumentLoaderOptions()); + Assertions.assertNotNull(doc); + }); + } +} \ No newline at end of file diff --git a/inspector-vc/src/test/java/org/oneedtech/inspect/vc/util/JsonNodeUtilTests.java b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/util/JsonNodeUtilTests.java new file mode 100644 index 0000000..762bd65 --- /dev/null +++ b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/util/JsonNodeUtilTests.java @@ -0,0 +1,43 @@ +package org.oneedtech.inspect.vc.util; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.oneedtech.inspect.util.json.ObjectMapperCache.Config.DEFAULT; + +import java.util.List; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.oneedtech.inspect.core.probe.json.JsonPathEvaluator; +import org.oneedtech.inspect.util.json.ObjectMapperCache; +import org.oneedtech.inspect.vc.Samples; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; + +public class JsonNodeUtilTests { + static final ObjectMapper mapper = ObjectMapperCache.get(DEFAULT); + static final JsonPathEvaluator jsonPath = new JsonPathEvaluator(mapper); + + @Test + void testFlattenNodeList() { + Assertions.assertDoesNotThrow(()->{ + String json = Samples.OB30.JSON.COMPLETE_JSON.asString(); + JsonNode root = mapper.readTree(json); + List list = JsonNodeUtil.asNodeList(root, "$..endorsement", jsonPath); + Assertions.assertEquals(5, list.size()); + for(JsonNode node : list) { + ArrayNode types = (ArrayNode) node.get("type"); + boolean found = false; + for(JsonNode val : types) { + if(val.asText().equals("EndorsementCredential")) { + found = true; + } + } + assertTrue(found); + } + + }); + } + +} diff --git a/inspector-vc/src/test/resources/ob30/complete.json b/inspector-vc/src/test/resources/ob30/complete.json new file mode 100644 index 0000000..92a16e8 --- /dev/null +++ b/inspector-vc/src/test/resources/ob30/complete.json @@ -0,0 +1,712 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://imsglobal.github.io/openbadges-specification/context.json", + "https://w3id.org/security/suites/ed25519-2020/v1" + ], + "id": "http://1edtech.edu/credentials/3732", + "type": [ + "VerifiableCredential", + "OpenBadgeCredential" + ], + "name": "1EdTech University Degree for Example Student", + "description": "1EdTech University Degree Description", + "image": { + "id": "https://1edtech.edu/credentials/3732/image", + "type": "Image", + "caption": "1EdTech University Degree for Example Student" + }, + "credentialSubject": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "type": [ + "AchievementSubject" + ], + "activityEndDate": "2010-01-02T00:00:00Z", + "activityStartDate": "2010-01-01T00:00:00Z", + "creditsEarned": 42, + "licenseNumber": "A-9320041", + "role": "Major Domo", + "source": { + "id": "https://school.edu/issuers/201234", + "type": [ + "Profile" + ], + "name": "1EdTech College of Arts" + }, + "term": "Fall", + "identifier": [ + { + "type": "IdentityObject", + "identityHash": "student@1edtech.edu", + "identityType": "email", + "hashed": false, + "salt": "not-used" + }, + { + "type": "IdentityObject", + "identityHash": "somebody@gmail.com", + "identityType": "email", + "hashed": false, + "salt": "not-used" + } + ], + "achievement": { + "id": "https://1edtech.edu/achievements/degree", + "type": [ + "Achievement" + ], + "alignment": [ + { + "type": [ + "Alignment" + ], + "targetCode": "degree", + "targetDescription": "1EdTech University Degree programs.", + "targetName": "1EdTech University Degree", + "targetFramework": "1EdTech University Program and Course Catalog", + "targetType": "CFItem", + "targetUrl": "https://1edtech.edu/catalog/degree" + }, + { + "type": [ + "Alignment" + ], + "targetCode": "degree", + "targetDescription": "1EdTech University Degree programs.", + "targetName": "1EdTech University Degree", + "targetFramework": "1EdTech University Program and Course Catalog", + "targetType": "CTDL", + "targetUrl": "https://credentialengineregistry.org/resources/ce-98cb027b-95ef-4494-908d-6f7790ec6b6b" + } + ], + "achievementType": "Degree", + "creator": { + "id": "https://1edtech.edu/issuers/565049", + "type": [ + "Profile" + ], + "name": "1EdTech University", + "url": "https://1edtech.edu", + "phone": "1-222-333-4444", + "description": "1EdTech University provides online degree programs.", + "endorsement": [ + { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://imsglobal.github.io/openbadges-specification/context.json", + "https://w3id.org/security/suites/ed25519-2020/v1" + ], + "type": [ + "VerifiableCredential", + "EndorsementCredential" + ], + "issuer": { + "id": "https://accrediter.edu/issuers/565049", + "type": [ + "Profile" + ], + "name": "Example Accrediting Agency" + }, + "issuanceDate": "2010-01-01T00:00:00Z", + "expirationDate": "2020-01-01T00:00:00Z", + "credentialSubject": { + "id": "https://1edtech.edu/issuers/565049", + "type": [ + "EndorsementSubject" + ], + "endorsementComment": "1EdTech University is in good standing" + }, + "credentialSchema": [ + { + "id": "https://purl.imsglobal.org/spec/ob/v3p0/schema/endorsementcredential.json", + "type": "1EdTechJsonSchemaValidator2019" + }, + { + "id": "https://accrediter.edu/schema/endorsementcredential.json", + "type": "JsonSchemaValidator2018" + } + ], + "credentialStatus": { + "id": "https://1edtech.edu/credentials/3732/revocations", + "type": "1EdTechRevocationList" + }, + "refreshService": { + "id": "http://1edtech.edu/credentials/3732", + "type": "1EdTechCredentialRefresh" + }, + "proof": [ + { + "type": "Ed25519Signature2020", + "created": "2022-05-26T18:17:08Z", + "verificationMethod": "https://accrediter.edu/issuers/565049#key-1", + "proofPurpose": "assertionMethod", + "proofValue": "zvPkQiUFfJrgnCRhyPkTSkgrGXbnLR15pHH5HZVYNdM4TCAwQHqG7fMeMPLtYNRnEgoV1aJdR5E61eWu5sWRYgtA" + } + ] + }, + { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://imsglobal.github.io/openbadges-specification/context.json", + "https://w3id.org/security/suites/ed25519-2020/v1" + ], + "type": [ + "VerifiableCredential", + "EndorsementCredential" + ], + "issuer": { + "id": "https://state.gov/issuers/565049", + "type": [ + "Profile" + ], + "name": "State Department of Education" + }, + "issuanceDate": "2010-01-01T00:00:00Z", + "expirationDate": "2020-01-01T00:00:00Z", + "credentialSubject": { + "id": "https://1edtech.edu/issuers/565049", + "type": [ + "EndorsementSubject" + ], + "endorsementComment": "1EdTech University is in good standing" + }, + "credentialSchema": [ + { + "id": "https://purl.imsglobal.org/spec/ob/v3p0/schema/endorsementcredential.json", + "type": "1EdTechJsonSchemaValidator2019" + }, + { + "id": "https://state.gov/schema/endorsementcredential.json", + "type": "JsonSchemaValidator2018" + } + ], + "credentialStatus": { + "id": "https://state.gov/credentials/3732/revocations", + "type": "1EdTechRevocationList" + }, + "refreshService": { + "id": "http://state.gov/credentials/3732", + "type": "1EdTechCredentialRefresh" + }, + "proof": [ + { + "type": "Ed25519Signature2020", + "created": "2022-05-26T18:25:59Z", + "verificationMethod": "https://accrediter.edu/issuers/565049#key-1", + "proofPurpose": "assertionMethod", + "proofValue": "z5bDnmSgDczXwZGya6ZjxKaxkdKxzsCMiVSsgEVWxnaWK7ZqbKnzcCd7mUKE9DQaAL2QMXP5AquPeW6W2CWrZ7jNC" + } + ] + } + ], + "image": { + "id": "https://1edtech.edu/logo.png", + "type": "Image", + "caption": "1EdTech University logo" + }, + "email": "registrar@1edtech.edu", + "address": { + "type": [ + "Address" + ], + "addressCountry": "USA", + "addressCountryCode": "US", + "addressRegion": "TX", + "addressLocality": "Austin", + "streetAddress": "123 First St", + "postOfficeBoxNumber": "1", + "postalCode": "12345", + "geo": { + "type": "GeoCoordinates", + "latitude": 1, + "longitude": 1 + } + }, + "otherIdentifier": [ + { + "type": "IdentifierEntry", + "identifier": "12345", + "identifierType": "sourcedId" + }, + { + "type": "IdentifierEntry", + "identifier": "67890", + "identifierType": "nationalIdentityNumber" + } + ], + "official": "Horace Mann", + "parentOrg": { + "id": "did:example:123456789", + "type": [ + "Profile" + ], + "name": "Universal Universities" + } + }, + "creditsAvailable": 36, + "criteria": { + "id": "https://1edtech.edu/achievements/degree", + "narrative": "# Degree Requirements\nStudents must complete..." + }, + "description": "1EdTech University Degree Description", + "endorsement": [ + { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://imsglobal.github.io/openbadges-specification/context.json", + "https://w3id.org/security/suites/ed25519-2020/v1" + ], + "type": [ + "VerifiableCredential", + "EndorsementCredential" + ], + "issuer": { + "id": "https://accrediter.edu/issuers/565049", + "type": [ + "Profile" + ], + "name": "Example Accrediting Agency" + }, + "issuanceDate": "2010-01-01T00:00:00Z", + "expirationDate": "2020-01-01T00:00:00Z", + "credentialSubject": { + "id": "https://1edtech.edu/issuers/565049", + "type": [ + "EndorsementSubject" + ], + "endorsementComment": "1EdTech University is in good standing" + }, + "credentialSchema": [ + { + "id": "https://purl.imsglobal.org/spec/ob/v3p0/schema/endorsementcredential.json", + "type": "1EdTechJsonSchemaValidator2019" + }, + { + "id": "https://accrediter.edu/schema/endorsementcredential.json", + "type": "JsonSchemaValidator2018" + } + ], + "credentialStatus": { + "id": "https://1edtech.edu/credentials/3732/revocations", + "type": "1EdTechRevocationList" + }, + "refreshService": { + "id": "http://1edtech.edu/credentials/3732", + "type": "1EdTechCredentialRefresh" + }, + "proof": [ + { + "type": "Ed25519Signature2020", + "created": "2022-05-26T18:17:08Z", + "verificationMethod": "https://accrediter.edu/issuers/565049#key-1", + "proofPurpose": "assertionMethod", + "proofValue": "zvPkQiUFfJrgnCRhyPkTSkgrGXbnLR15pHH5HZVYNdM4TCAwQHqG7fMeMPLtYNRnEgoV1aJdR5E61eWu5sWRYgtA" + } + ] + } + ], + "fieldOfStudy": "Research", + "humanCode": "R1", + "image": { + "id": "https://1edtech.edu/achievements/degree/image", + "type": "Image", + "caption": "1EdTech University Degree" + }, + "name": "1EdTech University Degree", + "otherIdentifier": [ + { + "type": "IdentifierEntry", + "identifier": "abde", + "identifierType": "identifier" + } + ], + "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": [ + "ResultDescription" + ], + "name": "Project Status", + "resultType": "Status" + } + ], + "specialization": "Computer Science Research", + "tag": [ + "research", + "computer science" + ] + }, + "image": { + "id": "https://1edtech.edu/credentials/3732/image", + "type": "Image", + "caption": "1EdTech University Degree for Example Student" + }, + "narrative": "There is a final project report and source code evidence.", + "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" + } + ] + }, + "endorsement": [ + { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://imsglobal.github.io/openbadges-specification/context.json", + "https://w3id.org/security/suites/ed25519-2020/v1" + ], + "type": [ + "VerifiableCredential", + "EndorsementCredential" + ], + "issuer": { + "id": "https://accrediter.edu/issuers/565049", + "type": [ + "Profile" + ], + "name": "Example Accrediting Agency" + }, + "issuanceDate": "2010-01-01T00:00:00Z", + "expirationDate": "2020-01-01T00:00:00Z", + "credentialSubject": { + "id": "https://1edtech.edu/issuers/565049", + "type": [ + "EndorsementSubject" + ], + "endorsementComment": "1EdTech University is in good standing" + }, + "credentialSchema": [ + { + "id": "https://purl.imsglobal.org/spec/ob/v3p0/schema/endorsementcredential.json", + "type": "1EdTechJsonSchemaValidator2019" + }, + { + "id": "https://accrediter.edu/schema/endorsementcredential.json", + "type": "JsonSchemaValidator2018" + } + ], + "credentialStatus": { + "id": "https://1edtech.edu/credentials/3732/revocations", + "type": "1EdTechRevocationList" + }, + "refreshService": { + "id": "http://1edtech.edu/credentials/3732", + "type": "1EdTechCredentialRefresh" + }, + "proof": [ + { + "type": "Ed25519Signature2020", + "created": "2022-05-26T18:17:08Z", + "verificationMethod": "https://accrediter.edu/issuers/565049#key-1", + "proofPurpose": "assertionMethod", + "proofValue": "zvPkQiUFfJrgnCRhyPkTSkgrGXbnLR15pHH5HZVYNdM4TCAwQHqG7fMeMPLtYNRnEgoV1aJdR5E61eWu5sWRYgtA" + } + ] + } + ], + "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": [ + "Evidence" + ], + "name": "Final Project Code", + "description": "This is the source code for the final project app.", + "genre": "Research", + "audience": "Department" + } + ], + "issuer": { + "id": "https://1edtech.edu/issuers/565049", + "type": [ + "Profile" + ], + "name": "1EdTech University", + "url": "https://1edtech.edu", + "phone": "1-222-333-4444", + "description": "1EdTech University provides online degree programs.", + "endorsement": [ + { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://imsglobal.github.io/openbadges-specification/context.json", + "https://w3id.org/security/suites/ed25519-2020/v1" + ], + "type": [ + "VerifiableCredential", + "EndorsementCredential" + ], + "issuer": { + "id": "https://accrediter.edu/issuers/565049", + "type": [ + "Profile" + ], + "name": "Example Accrediting Agency" + }, + "issuanceDate": "2010-01-01T00:00:00Z", + "expirationDate": "2020-01-01T00:00:00Z", + "credentialSubject": { + "id": "https://1edtech.edu/issuers/565049", + "type": [ + "EndorsementSubject" + ], + "endorsementComment": "1EdTech University is in good standing" + }, + "credentialSchema": [ + { + "id": "https://purl.imsglobal.org/spec/ob/v3p0/schema/endorsementcredential.json", + "type": "1EdTechJsonSchemaValidator2019" + }, + { + "id": "https://accrediter.edu/schema/endorsementcredential.json", + "type": "JsonSchemaValidator2018" + } + ], + "credentialStatus": { + "id": "https://1edtech.edu/credentials/3732/revocations", + "type": "1EdTechRevocationList" + }, + "refreshService": { + "id": "http://1edtech.edu/credentials/3732", + "type": "1EdTechCredentialRefresh" + }, + "proof": [ + { + "type": "Ed25519Signature2020", + "created": "2022-05-26T18:17:08Z", + "verificationMethod": "https://accrediter.edu/issuers/565049#key-1", + "proofPurpose": "assertionMethod", + "proofValue": "zvPkQiUFfJrgnCRhyPkTSkgrGXbnLR15pHH5HZVYNdM4TCAwQHqG7fMeMPLtYNRnEgoV1aJdR5E61eWu5sWRYgtA" + } + ] + } + ], + "image": { + "id": "https://1edtech.edu/logo.png", + "type": "Image", + "caption": "1EdTech University logo" + }, + "email": "registrar@1edtech.edu", + "address": { + "type": [ + "Address" + ], + "addressCountry": "USA", + "addressCountryCode": "US", + "addressRegion": "TX", + "addressLocality": "Austin", + "streetAddress": "123 First St", + "postOfficeBoxNumber": "1", + "postalCode": "12345", + "geo": { + "type": "GeoCoordinates", + "latitude": 1, + "longitude": 1 + } + }, + "otherIdentifier": [ + { + "type": "IdentifierEntry", + "identifier": "12345", + "identifierType": "sourcedId" + }, + { + "type": "IdentifierEntry", + "identifier": "67890", + "identifierType": "nationalIdentityNumber" + } + ], + "official": "Horace Mann", + "parentOrg": { + "id": "did:example:123456789", + "type": [ + "Profile" + ], + "name": "Universal Universities" + } + }, + "issuanceDate": "2010-01-01T00:00:00Z", + "expirationDate": "2020-01-01T00:00:00Z", + "credentialSchema": [ + { + "id": "https://purl.imsglobal.org/spec/ob/v3p0/schema/achievementcredential.json", + "type": "1EdTechJsonSchemaValidator2019" + } + ], + "credentialStatus": { + "id": "https://1edtech.edu/credentials/3732/revocations", + "type": "1EdTechRevocationList" + }, + "refreshService": { + "id": "http://1edtech.edu/credentials/3732", + "type": "1EdTechCredentialRefresh" + }, + "proof": [ + { + "type": "Ed25519Signature2020", + "created": "2022-06-09T22:56:28Z", + "verificationMethod": "https://1edtech.edu/issuers/565049#key-1", + "proofPurpose": "assertionMethod", + "proofValue": "zPpg92pBBEqRMxAqHMFQJ6Kmwf1thF9GdzqCofyWTLE6AhuahQixBNuG9BLgk6vb8K3NoqzanajYYYJbEcEhvQtM" + } + ] +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob30/simple-err-context.json b/inspector-vc/src/test/resources/ob30/simple-err-context.json new file mode 100644 index 0000000..5258f76 --- /dev/null +++ b/inspector-vc/src/test/resources/ob30/simple-err-context.json @@ -0,0 +1,35 @@ +{ + "@context": [ + "https://imsglobal.github.io/openbadges-specification/context.json", + "https://w3id.org/security/suites/ed25519-2020/v1" + ], + "id": "http://example.edu/credentials/3732", + "type": [ + "VerifiableCredential", + "OpenBadgeCredential" + ], + "issuer": { + "id": "https://example.edu/issuers/565049", + "type": [ + "Profile" + ], + "name": "Example University" + }, + "issuanceDate": "2010-01-01T00:00:00Z", + "name": "Example University Degree", + "credentialSubject": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "type": [ + "AchievementSubject" + ] + }, + "proof": [ + { + "type": "Ed25519Signature2020", + "created": "2022-06-28T16:28:36Z", + "verificationMethod": "did:key:z6MkkUD3J14nkYzn46QeuaVSnp7dF85QJKwKvJvfsjx79aXj", + "proofPurpose": "assertionMethod", + "proofValue": "z3MUt2ZuU8Byqivxh6GphEM65AFYyNaGYibm97xLTafM7uGufZQLKvJR8itZwxKskvtFM3CUty46v26DZidMNoQnM" + } + ] +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob30/simple-err-expired.json b/inspector-vc/src/test/resources/ob30/simple-err-expired.json new file mode 100644 index 0000000..80c6959 --- /dev/null +++ b/inspector-vc/src/test/resources/ob30/simple-err-expired.json @@ -0,0 +1,37 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://imsglobal.github.io/openbadges-specification/context.json", + "https://w3id.org/security/suites/ed25519-2020/v1" + ], + "id": "http://example.edu/credentials/3732", + "type": [ + "VerifiableCredential", + "OpenBadgeCredential" + ], + "issuer": { + "id": "https://example.edu/issuers/565049", + "type": [ + "Profile" + ], + "name": "Example University" + }, + "issuanceDate": "2010-01-01T00:00:00Z", + "expirationDate": "2020-01-20T00:00:00Z", + "name": "Example University Degree", + "credentialSubject": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "type": [ + "AchievementSubject" + ] + }, + "proof": [ + { + "type": "Ed25519Signature2020", + "created": "2022-06-28T16:28:36Z", + "verificationMethod": "did:key:z6MkkUD3J14nkYzn46QeuaVSnp7dF85QJKwKvJvfsjx79aXj", + "proofPurpose": "assertionMethod", + "proofValue": "z3MUt2ZuU8Byqivxh6GphEM65AFYyNaGYibm97xLTafM7uGufZQLKvJR8itZwxKskvtFM3CUty46v26DZidMNoQnM" + } + ] +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob30/simple-err-issued.json b/inspector-vc/src/test/resources/ob30/simple-err-issued.json new file mode 100644 index 0000000..e129e3d --- /dev/null +++ b/inspector-vc/src/test/resources/ob30/simple-err-issued.json @@ -0,0 +1,37 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://imsglobal.github.io/openbadges-specification/context.json", + "https://w3id.org/security/suites/ed25519-2020/v1" + ], + "id": "http://example.edu/credentials/3732", + "type": [ + "VerifiableCredential", + "OpenBadgeCredential" + ], + "issuer": { + "id": "https://example.edu/issuers/565049", + "type": [ + "Profile" + ], + "name": "Example University" + }, + "issuanceDate": "2040-01-01T00:00:00Z", + "expirationDate": "2050-01-20T00:00:00Z", + "name": "Example University Degree", + "credentialSubject": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "type": [ + "AchievementSubject" + ] + }, + "proof": [ + { + "type": "Ed25519Signature2020", + "created": "2022-06-28T16:28:36Z", + "verificationMethod": "did:key:z6MkkUD3J14nkYzn46QeuaVSnp7dF85QJKwKvJvfsjx79aXj", + "proofPurpose": "assertionMethod", + "proofValue": "z3MUt2ZuU8Byqivxh6GphEM65AFYyNaGYibm97xLTafM7uGufZQLKvJR8itZwxKskvtFM3CUty46v26DZidMNoQnM" + } + ] +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob30/simple-err-issuer.json b/inspector-vc/src/test/resources/ob30/simple-err-issuer.json new file mode 100644 index 0000000..2e988ea --- /dev/null +++ b/inspector-vc/src/test/resources/ob30/simple-err-issuer.json @@ -0,0 +1,29 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://imsglobal.github.io/openbadges-specification/context.json", + "https://w3id.org/security/suites/ed25519-2020/v1" + ], + "id": "http://example.edu/credentials/3732", + "type": [ + "VerifiableCredential", + "OpenBadgeCredential" + ], + "issuanceDate": "2010-01-01T00:00:00Z", + "name": "Example University Degree", + "credentialSubject": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "type": [ + "AchievementSubject" + ] + }, + "proof": [ + { + "type": "Ed25519Signature2020", + "created": "2022-06-28T16:28:36Z", + "verificationMethod": "did:key:z6MkkUD3J14nkYzn46QeuaVSnp7dF85QJKwKvJvfsjx79aXj", + "proofPurpose": "assertionMethod", + "proofValue": "z3MUt2ZuU8Byqivxh6GphEM65AFYyNaGYibm97xLTafM7uGufZQLKvJR8itZwxKskvtFM3CUty46v26DZidMNoQnM" + } + ] +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob30/simple-err-proof-method.json b/inspector-vc/src/test/resources/ob30/simple-err-proof-method.json new file mode 100644 index 0000000..037a10c --- /dev/null +++ b/inspector-vc/src/test/resources/ob30/simple-err-proof-method.json @@ -0,0 +1,54 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://purl.imsglobal.org/spec/ob/v3p0/context.json", + "https://purl.imsglobal.org/spec/ob/v3p0/extensions.json", + "https://w3id.org/security/suites/ed25519-2020/v1" + ], + "id": "http://example.edu/credentials/3732", + "type": [ + "VerifiableCredential", + "OpenBadgeCredential" + ], + "issuer": { + "id": "https://example.edu/issuers/565049", + "type": [ + "Profile" + ], + "name": "Example University" + }, + "issuanceDate": "2010-01-01T00:00:00Z", + "name": "Example University Degree", + "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" + } + }, + "credentialSchema": [ + { + "id": "https://purl.imsglobal.org/spec/ob/v3p0/schema/json/ob_v3p0_achievementcredential_schema.json", + "type": "1EdTechJsonSchemaValidator2019" + } + ], + "proof": [ + { + "type": "Ed25519Signature2020", + "created": "2022-09-15T15:48:32Z", + "verificationMethod": "https://example.edu/issuers/565049#xxMkmY1R6tG2NEdRHzphdRT6JqxeYpHwLAHwbrDfQULpkMAj", + "proofPurpose": "assertionMethod", + "proofValue": "z3yUuWbFsLUp2CUrSZRaRbTk1UnkhpoJgJYu1SdMqd3AEMotpY41sKky7VzavnSfjApggtWJg1tcREvs5H4ZNnBRH" + } + ] +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob30/simple-err-proof-value.json b/inspector-vc/src/test/resources/ob30/simple-err-proof-value.json new file mode 100644 index 0000000..d26f49d --- /dev/null +++ b/inspector-vc/src/test/resources/ob30/simple-err-proof-value.json @@ -0,0 +1,54 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://purl.imsglobal.org/spec/ob/v3p0/context.json", + "https://purl.imsglobal.org/spec/ob/v3p0/extensions.json", + "https://w3id.org/security/suites/ed25519-2020/v1" + ], + "id": "http://example.edu/credentials/3732", + "type": [ + "VerifiableCredential", + "OpenBadgeCredential" + ], + "issuer": { + "id": "https://example.edu/issuers/565049", + "type": [ + "Profile" + ], + "name": "Example University" + }, + "issuanceDate": "2010-01-01T00:00:00Z", + "name": "Example University Degree", + "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" + } + }, + "credentialSchema": [ + { + "id": "https://purl.imsglobal.org/spec/ob/v3p0/schema/json/ob_v3p0_achievementcredential_schema.json", + "type": "1EdTechJsonSchemaValidator2019" + } + ], + "proof": [ + { + "type": "Ed25519Signature2020", + "created": "2022-09-15T15:48:32Z", + "verificationMethod": "https://example.edu/issuers/565049#z6MkmY1R6tG2NEdRHzphdRT6JqxeYpHwLAHwbrDfQULpkMAj", + "proofPurpose": "assertionMethod", + "proofValue": "z3fQCWGpz7b1HSH6DTwYiH5vutqtpJb5SHiP1VFK22xeBEW2D61tC9j3SktwPLNxPnTNZnPt4GeAZJPdVYserRqs4" + } + ] +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob30/simple-err-type.json b/inspector-vc/src/test/resources/ob30/simple-err-type.json new file mode 100644 index 0000000..a54f5c1 --- /dev/null +++ b/inspector-vc/src/test/resources/ob30/simple-err-type.json @@ -0,0 +1,36 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://imsglobal.github.io/openbadges-specification/context.json", + "https://w3id.org/security/suites/ed25519-2020/v1" + ], + "id": "http://example.edu/credentials/3732", + "type": [ + "VerifiableCredential", + "OtherCredential" + ], + "issuer": { + "id": "https://example.edu/issuers/565049", + "type": [ + "Profile" + ], + "name": "Example University" + }, + "issuanceDate": "2010-01-01T00:00:00Z", + "name": "Example University Degree", + "credentialSubject": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "type": [ + "AchievementSubject" + ] + }, + "proof": [ + { + "type": "Ed25519Signature2020", + "created": "2022-06-09T22:56:28Z", + "verificationMethod": "https://example.edu/issuers/565049#key-1", + "proofPurpose": "assertionMethod", + "proofValue": "z58ieJCh4kN6eE2Vq4TyYURKSC4hWWEK7b75NNUL2taZMhKqwTteuByG1wRoGDdCqqNLW5Gq1diUi4qyZ63tQRtyN" + } + ] + } \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob30/simple-json.png b/inspector-vc/src/test/resources/ob30/simple-json.png new file mode 100644 index 0000000..ee80f0a Binary files /dev/null and b/inspector-vc/src/test/resources/ob30/simple-json.png differ diff --git a/inspector-vc/src/test/resources/ob30/simple-json.svg b/inspector-vc/src/test/resources/ob30/simple-json.svg new file mode 100644 index 0000000..4a4f39c --- /dev/null +++ b/inspector-vc/src/test/resources/ob30/simple-json.svg @@ -0,0 +1,74 @@ + + + + + + { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://purl.imsglobal.org/spec/ob/v3p0/context.json", + "https://purl.imsglobal.org/spec/ob/v3p0/extensions.json", + "https://w3id.org/security/suites/ed25519-2020/v1" + ], + "id": "http://example.edu/credentials/3732", + "type": [ + "VerifiableCredential", + "OpenBadgeCredential" + ], + "issuer": { + "id": "https://example.edu/issuers/565049", + "type": [ + "Profile" + ], + "name": "Example University" + }, + "issuanceDate": "2010-01-01T00:00:00Z", + "name": "Example University Degree", + "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" + } + }, + "credentialSchema": [ + { + "id": "https://purl.imsglobal.org/spec/ob/v3p0/schema/json/ob_v3p0_achievementcredential_schema.json", + "type": "1EdTechJsonSchemaValidator2019" + } + ], + "proof": [ + { + "type": "Ed25519Signature2020", + "created": "2022-09-15T15:48:32Z", + "verificationMethod": "https://example.edu/issuers/565049#z6MkmY1R6tG2NEdRHzphdRT6JqxeYpHwLAHwbrDfQULpkMAj", + "proofPurpose": "assertionMethod", + "proofValue": "z3yUuWbFsLUp2CUrSZRaRbTk1UnkhpoJgJYu1SdMqd3AEMotpY41sKky7VzavnSfjApggtWJg1tcREvs5H4ZNnBRH" + } + ] + } + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob30/simple-jwt.png b/inspector-vc/src/test/resources/ob30/simple-jwt.png new file mode 100644 index 0000000..7211f53 Binary files /dev/null and b/inspector-vc/src/test/resources/ob30/simple-jwt.png differ diff --git a/inspector-vc/src/test/resources/ob30/simple-jwt.svg b/inspector-vc/src/test/resources/ob30/simple-jwt.svg new file mode 100644 index 0000000..1f1d17f --- /dev/null +++ b/inspector-vc/src/test/resources/ob30/simple-jwt.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob30/simple-noproof.json b/inspector-vc/src/test/resources/ob30/simple-noproof.json new file mode 100644 index 0000000..0496844 --- /dev/null +++ b/inspector-vc/src/test/resources/ob30/simple-noproof.json @@ -0,0 +1,27 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://imsglobal.github.io/openbadges-specification/context.json", + "https://w3id.org/security/suites/ed25519-2020/v1" + ], + "id": "http://example.edu/credentials/3732", + "type": [ + "VerifiableCredential", + "OpenBadgeCredential" + ], + "issuer": { + "id": "https://example.edu/issuers/565049", + "type": [ + "Profile" + ], + "name": "Example University" + }, + "issuanceDate": "2010-01-01T00:00:00Z", + "name": "Example University Degree", + "credentialSubject": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "type": [ + "AchievementSubject" + ] + } +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob30/simple.json b/inspector-vc/src/test/resources/ob30/simple.json new file mode 100644 index 0000000..6c48673 --- /dev/null +++ b/inspector-vc/src/test/resources/ob30/simple.json @@ -0,0 +1,54 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://purl.imsglobal.org/spec/ob/v3p0/context.json", + "https://purl.imsglobal.org/spec/ob/v3p0/extensions.json", + "https://w3id.org/security/suites/ed25519-2020/v1" + ], + "id": "http://example.edu/credentials/3732", + "type": [ + "VerifiableCredential", + "OpenBadgeCredential" + ], + "issuer": { + "id": "https://example.edu/issuers/565049", + "type": [ + "Profile" + ], + "name": "Example University" + }, + "issuanceDate": "2010-01-01T00:00:00Z", + "name": "Example University Degree", + "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" + } + }, + "credentialSchema": [ + { + "id": "https://purl.imsglobal.org/spec/ob/v3p0/schema/json/ob_v3p0_achievementcredential_schema.json", + "type": "1EdTechJsonSchemaValidator2019" + } + ], + "proof": [ + { + "type": "Ed25519Signature2020", + "created": "2022-09-15T15:48:32Z", + "verificationMethod": "https://example.edu/issuers/565049#z6MkmY1R6tG2NEdRHzphdRT6JqxeYpHwLAHwbrDfQULpkMAj", + "proofPurpose": "assertionMethod", + "proofValue": "z3yUuWbFsLUp2CUrSZRaRbTk1UnkhpoJgJYu1SdMqd3AEMotpY41sKky7VzavnSfjApggtWJg1tcREvs5H4ZNnBRH" + } + ] +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob30/simple.jwt b/inspector-vc/src/test/resources/ob30/simple.jwt new file mode 100644 index 0000000..6f64aad --- /dev/null +++ b/inspector-vc/src/test/resources/ob30/simple.jwt @@ -0,0 +1 @@ +eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vaW1zZ2xvYmFsLmdpdGh1Yi5pby9vcGVuYmFkZ2VzLXNwZWNpZmljYXRpb24vY29udGV4dC5qc29uIl0sImlkIjoiaHR0cDovL2V4YW1wbGUuZWR1L2NyZWRlbnRpYWxzLzM3MzIiLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiT3BlbkJhZGdlQ3JlZGVudGlhbCJdLCJpc3N1ZXIiOnsiaWQiOiJodHRwczovL2V4YW1wbGUuZWR1L2lzc3VlcnMvNTY1MDQ5IiwidHlwZSI6WyJQcm9maWxlIl0sIm5hbWUiOiJFeGFtcGxlIFVuaXZlcnNpdHkifSwiaXNzdWFuY2VEYXRlIjoiMjAxMC0wMS0wMVQwMDowMDowMFoiLCJuYW1lIjoiRXhhbXBsZSBVbml2ZXJzaXR5IERlZ3JlZSIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmV4YW1wbGU6ZWJmZWIxZjcxMmViYzZmMWMyNzZlMTJlYzIxIiwidHlwZSI6WyJBY2hpZXZlbWVudFN1YmplY3QiXX19LCJpc3MiOiJodHRwczovL2V4YW1wbGUuZWR1L2lzc3VlcnMvNTY1MDQ5IiwibmJmIjoxMjYyMzA0MDAwLCJqdGkiOiJodHRwOi8vZXhhbXBsZS5lZHUvY3JlZGVudGlhbHMvMzczMiIsInN1YiI6ImRpZDpleGFtcGxlOmViZmViMWY3MTJlYmM2ZjFjMjc2ZTEyZWMyMSJ9.G7W8od9rSZRsVyk26rXjg_fH2CyUihwNpepd6tWgLt_UHC1vUU0Clox8IicnOSkMyYEqAuNZAdCC9_35i1oUcyj1c076Aa0dsVQ2fFVuQPqXBlyZWcBmo5jqOK6R9NHzRAYXwLRXgrB8gz3lSK55cnHTnMtkpXXcUcHkS5ylWbXCLeOWKoygOCuxRN3N6kP-0HOyuk15PWlnkJ2zEKz2pBtVPaNEydcT0kEtoHFMEWVwqo6rnGV-Ea3M7ssDt3145mcl-DVYLXmBVdT8KoO47QAOBaVMR6k-hgrHNBcdhpI-o6IvLIFsGLgrNvWN67i8Z7Baum1mP-HBpsAigdmIpA \ No newline at end of file